You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Companion discussion thread for PR #13.
This issue exists so maintainers and the community can discuss the rewrite
separately from the line-by-line code review on the PR itself.
Abstract
FormGear v2.0 represents a fundamental architectural redesign aimed at solving critical performance, maintainability, and mobile compatibility issues present in v1. This RFC outlines the problems identified, solutions implemented, and measurable improvements achieved.
The original FormGear implementation suffered from severe performance issues:
Artificial Delays: Mandatory setTimeout(500ms) and setInterval(500ms) calls created a minimum 1000ms latency floor
Expensive Lookups: O(n²) array traversals via findIndex() for every component access
Repeated Parsing: eval() called 8+ times per render cycle, parsing expressions from scratch each time
Global State Pollution: Shared mutable state causing race conditions and unpredictable behavior
1.2 Security Vulnerabilities
The use of eval() for expression evaluation introduced significant security risks:
// Old approach - vulnerable to code injectioneval(userProvidedExpression)
1.3 Mobile WebView Incompatibility
The architecture was designed for desktop browsers and failed in mobile WebView contexts:
No native bridge abstraction
Tight coupling to browser-specific APIs
No support for offline-first patterns required by mobile apps
1.4 Maintainability Crisis
The codebase had grown into an unmaintainable monolith:
GlobalFunction.tsx: 1,530 lines of tightly coupled logic
FormGear.tsx: 545 lines mixing concerns
No clear separation between data, logic, and presentation
Circular dependencies making testing nearly impossible
2. Design Goals
Zero Artificial Delays - Synchronous where possible, reactive where necessary
O(1) Data Access - Indexed data structures for all lookups
Secure Expression Evaluation - No eval(), sandboxed execution
Multi-Platform Architecture - Web, Flutter, Android, iOS support from day one
Isolated State - Context-based stores with no global mutations
Testable Components - Clear interfaces and dependency injection
3. Architecture Overview
3.1 High-Level Data Flow
flowchart TB
subgraph Clients["Client Applications"]
A1[Web Browser]
A2[Flutter App]
A3[Android Native]
A4[iOS Native]
end
subgraph Integration["Integration Layer"]
B1[Direct Import<br/>npm package]
B2[FormGear SDK<br/>WebView Container]
end
subgraph Engine["FormGear Engine"]
C[createFormGear API]
D[Store Context]
E[Form Components]
F[MobileHandlers Bridge]
end
A1 -->|npm install| B1
A2 --> B2
A3 --> B2
A4 --> B2
B1 -->|Direct render| C
B2 -->|Load HTML + Config| C
C --> D
D --> E
E <-->|Native calls| F
F <-->|JS Bridge| B2
LoadingStore Architecture Diagram
flowchart LR
subgraph Context["StoreContext (Per Form Instance)"]
direction TB
A[FormStore]
B[InputStore]
C[NestedStore]
D[LookupStore]
E[ReferenceStore]
F[SidebarStore]
end
subgraph Hooks["Context Hooks"]
G[useFormStore]
H[useInputStore]
I[useNestedStore]
J[useLookupStore]
K[useReferenceStore]
L[useSidebarStore]
end
A --> G
B --> H
C --> I
D --> J
E --> K
F --> L
flowchart LR
subgraph Old["Old: Array Search O(n²)"]
A1[Find Component] --> A2["components.findIndex()"]
A2 -->|"Scan all items"| A3{Found?}
A3 -->|"Loop N times"| A2
A3 -->|Yes| A4[Return Index]
end
subgraph New["New: Map Lookup O(1)"]
B1[Find Component] --> B2["componentMap.get(key)"]
B2 -->|"Direct hash"| B3[Return Component]
end
style A2 fill:#ff6b6b,color:#fff
style B2 fill:#51cf66,color:#fff
Loading
4.3 Timing and Polling Removal
Before
After
setTimeout(500ms) per section
None - synchronous
setInterval(500ms) polling
Reactive updates
5. Performance Results
Bottleneck
Old Version
New Version
Improvement
Section delays
setTimeout(500ms)
None
-500ms
Principal polling
setInterval(500ms)
Reactive
-500ms
Expression parsing
eval() × 8+
Cached compiled fn
-100-200ms
Component lookups
findIndex() O(n²)
Map.get() O(1)
-100-200ms
Store access
Global mutations
Context injection
-50ms
Total
>500ms
<10ms
50x faster
Timeline Comparison (Gantt)
gantt
title Form Load Time Comparison
dateFormat X
axisFormat %Lms
section Old Version
Parse Template :a1, 0, 50
setTimeout (500ms) :crit, a2, 50, 550
eval() expressions :a3, 550, 650
findIndex() lookups :a4, 650, 750
First Render :milestone, m1, 750, 750
setInterval polling :crit, a5, 750, 1250
section New Version
Parse Template :b1, 0, 5
Create Stores :b2, 5, 7
First Render :milestone, m2, 7, 7
Reactive (no polling) :done, b3, 7, 10
LoadingOld vs New Version Flow Diagrams
Old Version Flow (500ms+ load time):
flowchart TB
subgraph Init["Initialization (Blocking)"]
A[FormGear Constructor] --> B[Parse Template]
B --> C[Global State Setup]
end
subgraph Delays["Artificial Delays"]
D[setTimeout 500ms]
E[setInterval 500ms]
end
subgraph Process["Processing (Slow)"]
F["eval() × 8+ calls"]
G["findIndex() O(n²)"]
H[Global Store Lookup]
end
C --> D
D -->|"+500ms"| F
F -->|"+100ms"| G
G -->|"+100ms"| H
H --> E
style D fill:#ff6b6b,color:#fff
style E fill:#ff6b6b,color:#fff
style F fill:#ffa94d,color:#fff
style G fill:#ffa94d,color:#fff
Loading
New Version Flow (<10ms load time):
flowchart TB
subgraph Init["Initialization (Instant)"]
A[createFormGear] --> B[Parse Template]
B --> C[Create Store Context]
end
subgraph Process["Processing (Fast)"]
H["Compiled fn cached"]
I["Map.get() O(1)"]
J[Context Injection]
end
subgraph Render["Render (Reactive)"]
K[StoreProvider]
L[Form Components]
M[Reactive Updates]
end
C --> K
K --> L
L --> H
H -->|"cached"| I
I -->|"O(1)"| J
J --> M
style H fill:#51cf66,color:#fff
style I fill:#51cf66,color:#fff
style J fill:#51cf66,color:#fff
Mobile platforms use FormGear through a WebView wrapper with native bridge communication.
Native Bridge Communication
sequenceDiagram
participant UI as Form Component
participant MH as MobileHandlers
participant SDK as Native SDK
UI->>MH: Action (e.g., saveData)
MH->>MH: Detect environment
alt Flutter WebView
MH->>SDK: flutter_inappwebview.callHandler()
SDK-->>MH: Response
else Android WebView
MH->>SDK: Android.callHandler()
SDK-->>MH: Result
else iOS WebView
MH->>SDK: webkit.messageHandlers
SDK-->>MH: Result
else Browser (Direct)
MH-->>UI: No bridge needed
end
MH-->>UI: Final Result
Loading
iOS-Specific Optimizations:
iOS WKWebView has limitations with loadData() for large HTML content. v2 implements a local HTTP server workaround:
iOS Flow:
1. Start local HTTP server on port 3310
2. Set dynamic HTML content on server
3. WebView loads from http://127.0.0.1:3310/formgear
4. Cleanup on form exit
7. Codebase Changes
7.1 Deleted Files (Legacy)
File
Lines
Reason
src/FormGear.tsx
-545
Replaced by createFormGear.tsx
src/GlobalFunction.tsx
-1,530
Distributed to focused services
src/stores/InputStore.tsx
-9
Replaced by context-based stores
tailwind.config.js
-955
Replaced by Tailwind v4
src/assets/font-montserrat/*
-145
Font binaries removed from bundle; currently imported from Google Fonts CDN (see section 8 caveat and section 10.7)
The PR (#13) is technically ready; this issue is for directional agreement before merge. Please weigh in on:
10.1 Breaking-change strategy
v2 changes the public API (new FormGear → createFormGear). Should we:
(a) Ship as 2.0.0 with a migration guide and deprecate v1, or
(b) Maintain v1 on a 1.x branch for a transition period, or
(c) Provide a thin compat shim that re-exports FormGear as a wrapper?
10.2 Mobile bridge contract
MobileHandlers is the new integration surface for Flutter / Android / iOS. Is the current interface stable enough to be a public contract, or should it be marked experimental for one minor release while consumers (notably form_gear_engine_sdk) validate it in production?
10.3 Expression evaluator
v2 replaces eval() with a cached compiled function. This removes the code-injection vector but still executes arbitrary expressions in the page context. Do we need a stricter sandbox (e.g., expression AST allowlist) before declaring "secure", or is the current approach sufficient for our threat model?
10.4 Tailwind v4 migration
Moving to Tailwind v4 affects every consumer that extends FormGear's classes or relies on the old config. Should v4 land in the same PR, or be split into a separate follow-up to keep the diff reviewable?
10.5 Test coverage gate
The PR adds ~8,000 LoC of tests. Should we adopt a coverage threshold in CI (e.g., 70% lines) now that we have a baseline, or treat coverage as informational only?
10.6 Distribution
v2 is distributed via form-gear-builder (web) and copied into form_gear_engine_sdk/example/assets/formengine/1/. Should the SDK pin a specific v2 version, or always track latest?
RFC: FormGear v2.0 — Complete Architecture Rewrite
Abstract
FormGear v2.0 represents a fundamental architectural redesign aimed at solving critical performance, maintainability, and mobile compatibility issues present in v1. This RFC outlines the problems identified, solutions implemented, and measurable improvements achieved.
Stats: 184 files changed, +34,666 insertions, -9,120 deletions
1. Problem Statement
1.1 Performance Degradation
The original FormGear implementation suffered from severe performance issues:
setTimeout(500ms)andsetInterval(500ms)calls created a minimum 1000ms latency floorfindIndex()for every component accesseval()called 8+ times per render cycle, parsing expressions from scratch each time1.2 Security Vulnerabilities
The use of
eval()for expression evaluation introduced significant security risks:1.3 Mobile WebView Incompatibility
The architecture was designed for desktop browsers and failed in mobile WebView contexts:
1.4 Maintainability Crisis
The codebase had grown into an unmaintainable monolith:
GlobalFunction.tsx: 1,530 lines of tightly coupled logicFormGear.tsx: 545 lines mixing concerns2. Design Goals
eval(), sandboxed execution3. Architecture Overview
3.1 High-Level Data Flow
flowchart TB subgraph Clients["Client Applications"] A1[Web Browser] A2[Flutter App] A3[Android Native] A4[iOS Native] end subgraph Integration["Integration Layer"] B1[Direct Import<br/>npm package] B2[FormGear SDK<br/>WebView Container] end subgraph Engine["FormGear Engine"] C[createFormGear API] D[Store Context] E[Form Components] F[MobileHandlers Bridge] end A1 -->|npm install| B1 A2 --> B2 A3 --> B2 A4 --> B2 B1 -->|Direct render| C B2 -->|Load HTML + Config| C C --> D D --> E E <-->|Native calls| F F <-->|JS Bridge| B2Store Architecture Diagram
flowchart LR subgraph Context["StoreContext (Per Form Instance)"] direction TB A[FormStore] B[InputStore] C[NestedStore] D[LookupStore] E[ReferenceStore] F[SidebarStore] end subgraph Hooks["Context Hooks"] G[useFormStore] H[useInputStore] I[useNestedStore] J[useLookupStore] K[useReferenceStore] L[useSidebarStore] end A --> G B --> H C --> I D --> J E --> K F --> L4. Key Technical Changes
4.1 Expression Evaluation
eval(expression)Expression Evaluation Flow
flowchart LR subgraph Old["Old: eval() - Slow & Unsafe"] A1[Expression String] --> A2["eval(expr)"] A2 -->|"Parse every time"| A3[Execute] A3 -->|"No caching"| A4[Result] A2 -.->|"Security Risk"| A5[Code Injection] end subgraph New["New: Safe Compiled Fn - Fast & Cached"] B1[Expression String] --> B2{In Cache?} B2 -->|Yes| B3[Get Cached Fn] B2 -->|No| B4["Compile fn"] B4 --> B5[Store in Cache] B5 --> B3 B3 --> B6[Execute with Context] B6 --> B7[Result] end style A2 fill:#ff6b6b,color:#fff style A5 fill:#ff6b6b,color:#fff style B3 fill:#51cf66,color:#fff style B5 fill:#51cf66,color:#fff4.2 Data Access Patterns
findIndex()O(n²)Map.get()O(1)Data Lookup Comparison
flowchart LR subgraph Old["Old: Array Search O(n²)"] A1[Find Component] --> A2["components.findIndex()"] A2 -->|"Scan all items"| A3{Found?} A3 -->|"Loop N times"| A2 A3 -->|Yes| A4[Return Index] end subgraph New["New: Map Lookup O(1)"] B1[Find Component] --> B2["componentMap.get(key)"] B2 -->|"Direct hash"| B3[Return Component] end style A2 fill:#ff6b6b,color:#fff style B2 fill:#51cf66,color:#fff4.3 Timing and Polling Removal
setTimeout(500ms)per sectionsetInterval(500ms)polling5. Performance Results
setTimeout(500ms)setInterval(500ms)eval()× 8+findIndex()O(n²)Map.get()O(1)Timeline Comparison (Gantt)
gantt title Form Load Time Comparison dateFormat X axisFormat %Lms section Old Version Parse Template :a1, 0, 50 setTimeout (500ms) :crit, a2, 50, 550 eval() expressions :a3, 550, 650 findIndex() lookups :a4, 650, 750 First Render :milestone, m1, 750, 750 setInterval polling :crit, a5, 750, 1250 section New Version Parse Template :b1, 0, 5 Create Stores :b2, 5, 7 First Render :milestone, m2, 7, 7 Reactive (no polling) :done, b3, 7, 10Old vs New Version Flow Diagrams
Old Version Flow (500ms+ load time):
flowchart TB subgraph Init["Initialization (Blocking)"] A[FormGear Constructor] --> B[Parse Template] B --> C[Global State Setup] end subgraph Delays["Artificial Delays"] D[setTimeout 500ms] E[setInterval 500ms] end subgraph Process["Processing (Slow)"] F["eval() × 8+ calls"] G["findIndex() O(n²)"] H[Global Store Lookup] end C --> D D -->|"+500ms"| F F -->|"+100ms"| G G -->|"+100ms"| H H --> E style D fill:#ff6b6b,color:#fff style E fill:#ff6b6b,color:#fff style F fill:#ffa94d,color:#fff style G fill:#ffa94d,color:#fffNew Version Flow (<10ms load time):
flowchart TB subgraph Init["Initialization (Instant)"] A[createFormGear] --> B[Parse Template] B --> C[Create Store Context] end subgraph Process["Processing (Fast)"] H["Compiled fn cached"] I["Map.get() O(1)"] J[Context Injection] end subgraph Render["Render (Reactive)"] K[StoreProvider] L[Form Components] M[Reactive Updates] end C --> K K --> L L --> H H -->|"cached"| I I -->|"O(1)"| J J --> M style H fill:#51cf66,color:#fff style I fill:#51cf66,color:#fff style J fill:#51cf66,color:#fff6. Client Integration
6.1 Web Browser (Direct)
6.2 Mobile SDK (WebView)
Mobile platforms use FormGear through a WebView wrapper with native bridge communication.
Native Bridge Communication
sequenceDiagram participant UI as Form Component participant MH as MobileHandlers participant SDK as Native SDK UI->>MH: Action (e.g., saveData) MH->>MH: Detect environment alt Flutter WebView MH->>SDK: flutter_inappwebview.callHandler() SDK-->>MH: Response else Android WebView MH->>SDK: Android.callHandler() SDK-->>MH: Result else iOS WebView MH->>SDK: webkit.messageHandlers SDK-->>MH: Result else Browser (Direct) MH-->>UI: No bridge needed end MH-->>UI: Final ResultiOS-Specific Optimizations:
iOS WKWebView has limitations with
loadData()for large HTML content. v2 implements a local HTTP server workaround:7. Codebase Changes
7.1 Deleted Files (Legacy)
src/FormGear.tsxcreateFormGear.tsxsrc/GlobalFunction.tsxsrc/stores/InputStore.tsxtailwind.config.jssrc/assets/font-montserrat/*7.2 New Architecture Files
src/services/*src/bridge/*src/stores/*src/utils/*src/components/modals/*src/components/navigation/*src/components/sidebar/*src/components/layout/*src/**/__tests__/*7.3 Refactored Components
All input components updated with:
useLocale,useReference, etc.) instead of global importstoastError,toastSuccess,toastWarning)8. UI/UX Improvements
9. Migration Path
9.1 Breaking Changes
FormGearclass constructorcreateFormGear()functionGlobalFunctionuseFormStore(), etc.eval()expressionsMobileHandlersinterface9.2 API Usage
Full API Example
10. Open Questions for Discussion
The PR (#13) is technically ready; this issue is for directional agreement before merge. Please weigh in on:
10.1 Breaking-change strategy
v2 changes the public API (
new FormGear→createFormGear). Should we:2.0.0with a migration guide and deprecate v1, or1.xbranch for a transition period, orFormGearas a wrapper?10.2 Mobile bridge contract
MobileHandlersis the new integration surface for Flutter / Android / iOS. Is the current interface stable enough to be a public contract, or should it be marked experimental for one minor release while consumers (notablyform_gear_engine_sdk) validate it in production?10.3 Expression evaluator
v2 replaces
eval()with a cached compiled function. This removes the code-injection vector but still executes arbitrary expressions in the page context. Do we need a stricter sandbox (e.g., expression AST allowlist) before declaring "secure", or is the current approach sufficient for our threat model?10.4 Tailwind v4 migration
Moving to Tailwind v4 affects every consumer that extends FormGear's classes or relies on the old config. Should v4 land in the same PR, or be split into a separate follow-up to keep the diff reviewable?
10.5 Test coverage gate
The PR adds ~8,000 LoC of tests. Should we adopt a coverage threshold in CI (e.g., 70% lines) now that we have a baseline, or treat coverage as informational only?
10.6 Distribution
v2 is distributed via
form-gear-builder(web) and copied intoform_gear_engine_sdk/example/assets/formengine/1/. Should the SDK pin a specific v2 version, or always tracklatest?10.7 Font decoupling (regression of issue #7)
Current state is misleading in section 8. v2 removed the bundled woff/ttf binaries (~145 KB out of the CSS), but
src/index.cssstill hardcodes:This means:
Proposed solution: three-tier opt-in font system.
Base CSS imports no font. Use a CSS variable with system-font fallback:
Result: zero font weight in base bundle, renders with system font by default.
Optional companion CSS
dist/form-gear-montserrat.cssshipped alongside the main bundle:Plus
dist/fonts/montserrat-*.woff2as static assets (Latin + Latin-ext subset, ~40 KB total).Consumer chooses integration path:
form-gear.cssonlyform-gear.css+ consumer sets--formgear-font-sansand provides own@font-faceform-gear.css+form-gear-montserrat.css+ copyfonts/folder to public assetsDiscussion points:
2.0.0, or accept the regression and ship in2.1.0?form-gear/montserrat.css) or just a sibling file indist/?11. Conclusion
FormGear v2 delivers:
eval())Related
Feedback, objections, and alternative proposals welcome. Please react with 👍 / 👎 on individual open questions to help gauge consensus before merge.