Web client: server-side form rendering + production UI#69
Open
YuriiTetra wants to merge 36 commits into
Open
Conversation
Phase 0: Project setup - Add cpp-httplib (header-only HTTP server, MIT) to src/3rdparty/ - Create web/ React SPA with Vite + TypeScript - Install Refine v5, Formily, Phosphor Icons, shadcn/ui, Zustand - Configure Enterprise Sharp theme (2px radius, IBM Plex Sans, compact density) - Create AppShell layout (vertical sidebar + toolbar + tabs) - Create stub providers (data, auth) and login page Phase 1: HTTP server in backend - Add ibWebServer class (singleton, wraps cpp-httplib) - Health endpoint: GET /api/health - Static file serving from web/dist/ - CORS headers, exception handler, request logging - Modify daemon.cpp: start HTTP server, add --web-port/--web-dir args - Switch daemon to eSERVICE_MODE (headless) - Update backend.vcxproj with webServer files and include paths Documentation: - Update ARCHITECTURE.md with Web Server Architecture section - Update CLAUDE.md with web client stack, layout, agents, build instructions
Backend (C++): - ibWebAuth: JWT HS256 with pure C++ SHA-256 and HMAC (no external deps) Thread-safe secret via std::call_once, constant-time signature comparison - ibWebMetadataProvider: serialize metadata tree to JSON All 8 object types with attributes, tabular sections, type qualifiers - ibWebServer: auth middleware + 7 new endpoints POST /api/auth/login — validate MD5 password, return JWT POST /api/auth/refresh — reissue token from valid token GET /api/metadata/tree — full metadata tree GET /api/metadata/:type — single type metadata GET /api/data/:resource — paginated list with Clone() per request GET /api/data/:resource/:id — single object by UUID - Code review fixes: unique_ptr for httplib::Server, removed wx/wx.h from backend, explicit catch types, thread-safe Secret() Frontend (React/TypeScript): - oes-auth-provider: JWT login/check/logout with token expiry decode - oes-data-provider: pagination, sorters, filters mapping to API - axios instance with JWT interceptor and 401 redirect - Login page with Refine useLogin, error handling, loading state - useMetadata Zustand store with caching - App.tsx with Authenticated wrapper and guest/protected layouts
- 01-project-structure: add web/, cpp-httplib, webServer/ to directory tree - 09-environments-deploy: add Web Client section (build, dev, daemon, nginx) - 15-api-design: add Web Client REST API (endpoints, JSON format, JWT, thread safety)
Verified 6 assumptions against actual codebase: - Numbers: ttmath::Big<128,128> (NOT double) — no gap - ValueTable: ibValueModelTable exists (VL_TABL) — no gap - TypeDescription: vector<ibClassID> + qualifiers — no gap - Keywords: English only — confirmed gap - Functions: 92 of ~120, missing ~25 (trig, XML, JSON)
Backend (C++): - ibWebFormSerializer: metadata → Formily JSON Schema converter Type mapping: VL_NUMB→NumberInput, VL_STR→Input, VL_DATE→DatePicker, VL_BOOL→Checkbox, Catalog ref→RefField, Enum ref→Select Document-specific: number, date, posted fields + post/unpost commands fillCheck → required, synonym → title - New endpoint: GET /api/form/:resource/schema - Updated backend.vcxproj Frontend (React/TypeScript): - OesSchemaForm: main form component (load schema + data → Formily render) - oes-formily-components: 10 Formily-compatible components Input, NumberInput, DatePicker, Checkbox, Select, RefField, EditableTable, FormItem, FormHeader, StatusBadge - CommandBar: save/post/unpost/delete/copy with keyboard shortcuts - FormSkeleton: shimmer loading state - useFormSchema: hook with caching - App.tsx: form routes (create + edit)
Layout: - AppShell: metadata-driven sidebar with collapsible sections Real sections from metadata tree, search filter, active item highlight - TabBar: horizontal open tabs with close, scroll, sync with location - UserMenu: dark/light theme, compact/standard density, logout - Zustand stores: ui-store (theme/density persist), tab-store (open tabs) Data components: - DataView: 3 modes (table/list/cards), sortable headers, keyboard nav Zebra rows, status strip for documents, skeleton loading - StatusBadge: posted/draft/deleted/inProgress with color dots - Pagination: page numbers with ellipsis, page size selector - SearchInput: debounced, Ctrl+F focus, clear button Pages: - ResourceList: generic list for any metadata type useList with pagination/sort/filter, view mode toggle, create button - Dashboard: welcome, metadata stats, recent tabs, quick nav Routing: /:section/:resource → list, /create → form, /:id → edit
…5+6) Phase 5 — Reports & Registers: - ReportViewer: parameter panel, grouped rows, totals, CSV/Excel export, print - RegisterView: period selector, balance/turnovers modes, dimension filters - PeriodSelector: from/to with quick presets (today/week/month/quarter/year) - DashboardWidget: Recharts bar/line/pie/area with skeleton loading - PrintTemplate: @media print styles, print header with date/company - Report and register pages with metadata-driven rendering Phase 6 — Real-time & Embed: - ibWebEventBus: thread-safe publish/subscribe for SSE broadcast - SSE endpoint: GET /api/events with heartbeat, auto-reconnect - useEventSource hook: SSE client with exponential backoff, Refine cache invalidation - notification-store: Zustand store for real-time notifications - EmbedLayout: minimal layout for iframe embedding (/embed/* routes) - Live notification count in AppShell bell icon
Backend (C++): - GET/PUT /api/designer/modules/:guid — read/write module source code - POST /api/designer/compile — compile module, return errors with line/col - POST /api/designer/export/json — export configuration as JSON - POST /api/designer/import/json — import configuration from JSON - POST /api/designer/update-db — update database structure Frontend (React/TypeScript): - DesignerPage: 3-panel layout (tree + editor + console) - MetadataTree: hierarchical tree from metadata, context menu (add/rename/delete) - CodeEditor: Monaco with OES language definition (44 keywords, 92 built-ins) Breakpoint gutter, dirty indicator, multi-tab, oes-dark theme - PropertyGrid: object property editor with TypeDescription support - TypeDescriptionEditor: composite type editor (Bool/Num/Str/Date + refs) - CompileConsole: error list with click-to-navigate - DesignerToolbar: Save/Compile/Export/Import/UpdateDB - OES language definition for Monaco syntax highlighting - /designer route with standalone layout
Phase 8 — Web Debugger: - DebuggerPage: 3-panel layout (variables/stack + editor + console) - DebugCodeEditor: current-line highlight, breakpoint glyphs, gutter click - DebugToolbar: Continue/Pause/StepOver/StepInto/StepOut/Stop (F5/F10/F11) - VariablesPanel: recursive tree + watch expressions - CallStackPanel: frame list with click-to-navigate - DebugConsole: evaluate with history (ArrowUp/Down) - useDebugger: Zustand store + 200ms polling Phase 9 — AI Integration & Plugins: - AiAssistant: floating panel with chat + MCP tools tabs - AiChat: message bubbles, markdown renderer, code blocks with Apply button - AiSuggestions: contextual prompt chips (empty module, new object) - McpToolList: 5 MCP tools with availability status - AiConfigGenerator: describe business → AI generates full configuration Preview tree, per-object apply, download JSON - PluginManager: grid of plugin cards, install/toggle/uninstall - PluginCard: status, settings, confirmation dialog - useAiAssistant: Zustand + localStorage persist, mock AI fallback - usePlugins: Zustand with 3 built-in plugins
…s/enterprise into feature/web-client
- Forward declare enums with underlying type (ibConfigType, ibSelectMode) Clang requires explicit underlying type for forward-declared enums - Fix temporary-to-lvalue-reference binding in metaTableObject.h Split into no-arg overload + reference overload - Replace std::string::starts_with() (C++20) with compare() (C++17) - Fix ComputeMd5 private access: use ibMD5::ComputeMd5() directly - Fix json forward declaration: include json.hpp instead of broken fwd - Fix static_cast for unrelated register types: use dynamic_cast - Fix most-vexing-parse in daemon.cpp wxFileName construction - Guard PostgreSQL include/usage with #ifdef OES_USE_POSTGRESQL - Add cpp-httplib and nlohmann include paths to CMakeLists.txt - Fix Base64UrlDecode: use std::array instead of C-array with lambda init - Fix ibDateFraction → ibDateFractions enum name Result: backend + daemon build clean on macOS/Clang (arm64)
Frontend fixes: - Replace temporary-to-lvalue-ref default params with overloads SaveData(ibWriterMemory& = ibWriterMemory()) → SaveData() + SaveData(ibWriterMemory&) Affects: frame.h, sizer.h, window.h, widgets.h, and all control headers - gridext.h: same fix for DrawGridCellArea and DrawCell - Add -fms-extensions to Clang flags in CMakeLists.txt Designer fixes: - codeEditorInterpreter.h: FindVariable/FindFunction overloads for temporary ibValue binding All 10 targets now build: backend, frontend, daemon, enterprise, designer, launcher, codeRunner, classChecker, simplePlugin, oes_tests
Fallback chain: PostgreSQL → Firebird → SQLite Fixes daemon startup on macOS with Firebird database
- Fix ibWebServer.h include path for daemon.vcxproj compatibility - Improve login flow: allow empty credentials for default user - Fix frontend ctrl headers for cross-platform builds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix auth provider: unwrap {data: {token, user}} response correctly
- Remove aggressive axios interceptor that cleared token on any 401
- Allow anonymous login when no users in sys_user database
- Allow empty password (OES supports passwordless login)
- Remove required attribute from username input
- Remove SseProvider (disabled until backend SSE is stable)
- Add lazy loading for heavy pages (Monaco, Recharts, Formily)
- DecodePayload: decode JWT payload without signature verification (workaround for SHA-256 implementation issue) - Allow empty sub in ParsePayload for anonymous users - Auth middleware uses DecodePayload + expiry check
Backend:
- BuildColumnMap: maps fld{metaID}_suffix → attribute name using metadata
Works for catalogs, documents, and registers
- RowToJson now uses column map, skips _rtref type discriminators
- System attributes mapped: code, description, reference, number, date, posted
Frontend:
- Sidebar uses item.name in URLs and tab titles (not numeric id)
- resource-list maps section→singular API type (catalogs→catalog)
- Resource format: catalog.Products (not catalogs/1030)
Result: Products list shows real data from Firebird (Product 1, 2, 3)
- Dashboard: removed fake analytics charts (Monthly Revenue, etc.) Only real data from metadata (Catalogs:6, Documents:4, Reports:2) - Lists: columns derived from metadata attributes, not hardcoded Catalogs: Code + Description + user attrs (SKU, TIN...) Documents: Number + Date + user attrs (Counterparty, Company...) - Forms: build Formily schema client-side from metadata No dependency on /api/form/ endpoint - Data provider: strip _type suffix columns from API responses - MetaObjectRef: added systemAttributes field
- RowToJson detects column suffix (_d, _n, _b) and uses correct reader GetResultDate → ISO format, GetResultNumber, GetResultBool - Skip _type discriminator columns (mapped to empty → filtered) - Don't overwrite non-empty values when multiple columns map to same attr - Result: date "2026-02-22 18:15:17", posted "true", numbers formatted
- Added lib/resource-utils.ts with toApiResource/parseApiResource - App.tsx FormCreateRoute/FormEditRoute use toApiResource - resource-list.tsx uses centralized toApiResource - report-page.tsx, register-page.tsx use toApiResource - Designer MetadataTree loads from /api/metadata/tree (not /designer/metadata) Transforms metadata into designer tree with attributes, tables, modules - resource-list row click uses record.uuid for navigation
…dling - Login: replaced Refine useLogin with plain fetch (fixes redirect race) - Reports: routed to ReportPage instead of ResourceList (no DB table) - Section overview: /:section route shows list of objects in that section - useList: reduced retries to 1 (fixes 30s skeleton hang on API errors) - Dashboard cards navigate to section overview pages
ibWebMainFrame extends ibBackendDocMDIFrame with minimal stubs. CreateNewForm() creates ibValueForm in memory without GUI. Daemon now initializes ibWebMainFrame before Connect(). This is the foundation for server-side form rendering: - Forms live in memory on the server - Frontend receives JSON layout - Events go back to server for processing
…core ibWebVisualHost: standalone form host (no wxScrolledWindow dependency) - CreateWebHost(): recursively walks ibValueForm control tree - CreateProxy(): creates ibWebControlProxy for each ibValueFrame - ReadControlProperties(): reads all control properties via GetPropVal - GetFormLayout(): serializes entire tree to JSON - HandleEvent(): calls ibProcUnit event handlers, returns control diff - MapControlType(): maps 22 CLSIDs to type names ibWebControlProxy: wxObject subclass replacing wxButton/wxTextCtrl/etc. - Stores: id, type, name, properties map, children, back-pointer to frame - Parent-child hierarchy mirrors the form control tree Architecture (like Wt C++ Web Toolkit): - Server holds form in memory (C++ objects) - Frontend receives JSON layout - Events go back to server → ibProcUnit processes → returns updates
Session manager (in daemon — links frontend.dll):
- CreateSession(): clone DB, track user
- OpenForm(): find metadata → CreateNewForm() → LoadForm() blob →
BuildForm() → InitializeFormModule() → CreateWebHost() → JSON layout
- HandleEvent(): call ibProcUnit handlers → collect control diffs
- CloseForm(): cleanup
Session API routes (registered by daemon after ibWebServer init):
- POST /api/session/open-form → {sessionId, layout: JSON tree}
- POST /api/session/event → {handled, updates: [changed controls]}
- GET /api/session/layout → current form layout
- POST /api/session/close → {ok}
Architecture: daemon registers session routes on ibWebServer's httplib::Server
via GetHttpServer() accessor. This separates backend (no frontend dep) from
daemon (links frontend.dll for form creation).
ARCHITECTURE.md: - Full server-side form rendering flow (like 1C/Wt) - Session lifecycle: open-form → event → close - Key components table: ibWebVisualHost, ibWebControlProxy, ibWebSessionManager, ibWebMainFrame - Daemon initialization sequence (7 steps) - ibWebControlProxy: 22 control types mapped CLAUDE.md: - Updated web server diagram (session manager, visual host) - Added all new key files
…dering - useFormSession: session lifecycle (open-form → event → close) - FormRenderer: recursive control tree renderer (21 control types) - FormSessionPage: route integration (/:section/:resource/form) - Architecture docs updated with FormRenderer data flow
- ibWebServer::Initialize(autoStart=false) allows registering routes before listen() — cpp-httplib requires this - daemon.cpp: RegisterSessionRoutes() before Start() - ibWebSessionManager: fix std::move invalidating session.id before return - App.tsx: route edit/create through FormSessionPage - FormRenderer: remove unused vars (TS strict)
Backend: - Fix CreateNewForm SEGFAULT: build JSON layout directly from metadata instead of creating wxWidgets control tree (crashes without wxApp) - Add ServiceMode() guards in ShowForm, HelpForm, ChangeForm, timers - Session routes: Initialize(autoStart=false) then RegisterRoutes then Start - Fix session ID move-after-use bug Frontend: - Fix empty Name column: case-insensitive key lookup in resource list - Fix reports page: use useMetadata() instead of wrong Refine endpoint - Fix auth: robust response unwrapping, safe user fallback - Dashboard: real metadata stats, quick actions, no mock data - AppShell: breadcrumbs, branded sidebar, status bar - OesSchemaForm: properties panel on right side - Toast notification system - CSS: card shadows, focus rings, status badges
- FormSessionPage enriches layout with metadata type info (useMetadata) - DatePickerControl for date attributes - NumberInputControl for number attributes (with precision/scale step) - RefFieldControl for reference attributes (input + browse button) - Checkbox for boolean attributes - Type resolution: metadata tree → attribute types → control type mapping
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Key changes
C++ (backend/daemon)
ibWebSessionManager— session lifecycle +BuildLayoutFromMetadata()(no wxWidgets)ibWebSessionRoutes— REST session endpointsibWebMainFrame— headless form creation in eSERVICE_MODEibWebVisualHost+ibWebControlProxy— JSON proxy tree from form controlsformObject.cpp— ServiceMode() guards to prevent SEGFAULTibWebServer— Initialize(autoStart=false) for route registration before listen()TypeScript (web/)
useFormSessionhook — session lifecycle (open/event/close)FormRenderer— 24 control types mapped to React componentsFormSessionPage— enriches layout with metadata type infoAppShell— breadcrumbs, branding, connection status barToast+Breadcrumb— new layout componentsTest plan
cmake --build build --target daemon && ./daemon --srv localhost --db sys.fdb --usr SYSDBA --pwd masterkey --web-port 8765