feat: FormGear v2.0 complete rewrite #13
Open
ryanaidilp wants to merge 64 commits intobps-statistics:mainfrom
Open
feat: FormGear v2.0 complete rewrite #13ryanaidilp wants to merge 64 commits intobps-statistics:mainfrom
ryanaidilp wants to merge 64 commits intobps-statistics:mainfrom
Conversation
- @types/node: 17.0.31 → 22.19.3 - autoprefixer: 10.4.7 → 10.4.23 - dayjs: 1.11.2 → 1.11.19 - papaparse: 5.3.2 → 5.5.3 - solid-icons: 1.0.1 → 1.1.0 - toastify-js: 1.11.2 → 1.12.0 - typescript: 4.6.4 → 5.9.3
- Add src/types/ with TypeScript enums (ClientMode, FormMode, etc.)
- Add src/types/controls.ts with component interfaces
- Add src/types/stores.ts with store state interfaces
- Add src/types/index.ts with FormGearConfig, FormGearOptions types
- Add src/createFormGear.ts as new options-based API entry point
- Update src/FormGear.tsx with deprecation notice and type annotations
- Update src/index.tsx to export new API and all types
New API replaces 16 positional parameters with options object pattern:
createFormGear({ data, config, mobileHandlers, callbacks })
Legacy FormGear constructor is preserved for backward compatibility.
Phase 2 foundation for store isolation: - Add src/stores/createStores.ts with factory function - Creates isolated store instances per FormGear instance - Includes dispose() method for cleanup - Tracks all 15 stores + 11 signals - Add src/stores/StoreContext.tsx with provider and hooks - StoreProvider for wrapping components - useStores() main hook - Convenience hooks: useReference(), useResponse(), etc. - Add src/stores/index.ts barrel export - Exports factory, context, and hooks - Maintains legacy store exports for backward compatibility Components can be gradually migrated from direct store imports to context-based hooks for full store isolation.
Phase 5 of v2 rewrite - creates unified bridge interface for platform-specific native communication: - Add NativeBridge interface with camera, GPS, file, location APIs - Add Android WebView bridge (window.Android) - Add iOS WKWebView bridge (webkit.messageHandlers) - Add Flutter bridges (InAppWebView + webview_flutter) - Add Web browser fallback with Web APIs - Add auto-detection and factory functions - Export all bridge types and utilities from main entry The bridge provides: - createBridge() factory with auto-detection - getBridge() singleton for shared usage - Platform detection utilities (isNativeApp, isMobile) - Support for: camera, GPS, file upload, barcode scan, data persistence, offline search, lifecycle events, logging
…ting Phase 3 foundation - creates reusable utility modules extracted from GlobalFunction.tsx patterns: - Add expression.ts: safe expression evaluator using Function constructor instead of eval(), with sandboxed context for getValue, getRowIndex, getProp - Add toast.ts: toast notification utilities (toastInfo, toastSuccess, etc.) - Add formatting.ts: string templating, date validation, checkbox utilities - Add reference.ts: dataKey parsing, reference map operations, dependency maps - Add index.ts: barrel export for all utilities The safe expression evaluator provides: - evaluateExpression() - main evaluation with ExpressionContext - evaluateEnableCondition() - for enable condition expressions - evaluateValidation() - for validation test expressions - evaluateVariableExpression() - for computed variable fields These utilities prepare for migration away from eval() in GlobalFunction.tsx.
- Update vite.config.ts with ES2015 target, sourcemaps, esbuild minification - Update package.json with sideEffects, additional CSS export path - Update tsconfig.json with Bundler module resolution, include/exclude - Add path alias configuration for cleaner imports Build output: ES 685kB (119kB gzip), UMD 516kB (103kB gzip)
- Replace 8 eval() calls in GlobalFunction.tsx with expression utilities - Replace 2 eval() calls in Form.tsx with expression utilities - Remove unused createSignal import from GlobalFunction.tsx - Use evaluateEnableCondition() for enable condition expressions - Use evaluateVariableExpression() for computed variable fields - Use evaluateValidation() for validation test expressions - Add createExpressionContext() helper for building evaluation context - Build now produces zero eval() warnings
- Create isolated stores using createFormStores() for each instance - Wrap both render calls with StoreProvider - Enables future component migration to context-based hooks - Maintains backward compatibility with global store imports
- RadioInput: useReference - CheckboxInput: useReference - NestedInput: useReference - UnitInput: useReference, useLocale - MultipleSelectInput: useReference, useLocale - ListTextInputRepeat: useLocale - ListSelectInputRepeat: useReference, useLocale - GpsInput: useLocale - CsvInput: useLocale - SelectInput: useReference, useLocale, useSidebar
- Fix PAPI partials file casing (Index.ts → index.ts) - Fix className to class in SolidJS components (6 files) - Add proper types for FilterDependency, SubResourceDependency, ParentCondition - Add sourceSelect type to FormType.tsx and ComponentType - Fix InputContainerBase to use ParentComponent for children prop - Add missing event handler imports in DateTimeLocalInput - Update ReferenceDetail with proper Option[] types - Enable strictFunctionTypes, strictBindCallApply, noImplicitThis - Enable useUnknownInCatchVariables, noImplicitReturns, noFallthroughCasesInSwitch
- Remove legacy FormGear export, keep only createFormGear API - Update client examples (CAWI/CAPI) with new API and platform bridge - Add TypeScript declaration generation with separate tsconfig - Create MIGRATION.md with comprehensive upgrade guide - Update CHANGELOG.md with v2.0.0 release notes - Bump version to 2.0.0
- Call initializeMaps() instead of rebuildIndexMap() in createFormGear - This ensures compEnableMap is populated for enable condition dependencies - Fixes checkbox components not responding to enable condition changes - Remove debug console.log statements from services and components
- Fix typos in template/reference JSON files for frec_total componentVar (frek_kopi -> frec_coffee, frek_teh -> frec_tea) - Add registerDynamicComponents method to ReferenceService for dynamically created nested components - Register nested component templates in nested store for second-level nested support - Update row marker resolution to properly transform @$ROW$ to #position - Add debug logging for variable dependency registration flow
- Add optional chaining (?.) to validationMessage.length checks across all input components to prevent runtime errors - Fix component layout styling in CheckboxInput and others
- Add example/.gitignore - Update package dependencies - Clean up unused constants and config files - Update store context and core types
- Add Template Builder section to README with link to separate repository - Add Migration Guide section with link to MIGRATION.md - Update MIGRATION.md with FormGear Builder documentation - Include builder features, output format, and backward compatibility info - Mark "Further development ideas" as completed (Template Builder)
Use Vite's define feature to inject version from package.json instead of hardcoding it in FormGear.tsx
- Remove FormGear.tsx with deprecated 16-parameter constructor - Consolidate gearVersion, templateVersion, validationVersion in createFormGear.tsx - Update Form.tsx and FormInput.tsx to import from createFormGear BREAKING CHANGE: The legacy FormGear() constructor is no longer available. Use createFormGear() options-based API instead.
- SingleCheckInput: use flexbox for checkbox-label alignment - RadioInput: use flexbox for radio-label alignment - GpsInput: restructure to flexbox for action button alignment - SignatureInput: restructure to flexbox for action button alignment - Replace grid-cols-12 with flex layouts for tighter spacing - Update validation message layouts to use flexbox
Change layout from CSS Grid to Flexbox for better responsive behavior. Replaced grid-cols-12/col-span-11 pattern with flex/flex-1/shrink-0.
- Read version from props.template.details.version and props.validation.details.version - Fix nested menu to show two levels deep in sidebar navigation - Always show version info in sidebar for all client modes
- Fix getNestedDependents to directly filter reference.details instead of using pre-built maps that miss dynamically created components - Compute correct runtime index from parent sidebar entry in insertFromArray and insertFromNumber methods - Pass parent index to createNestedComponents for level 2+ nested components to build correct index hierarchy - Simplify insertIntoSidebar to use parent's corrected runtime index This ensures second-level nested sidebar entries (e.g., "Nested Social Media" under "Most Favourite - Games") appear with correct indentation by matching Form.tsx's index-based rendering conditions.
- Fix remark dialog transparency using Tailwind v4 opacity syntax - Add smooth modal transition animations for all dialogs - Make header sticky on mobile screens - Fix UnitInput, SelectInput, MultipleSelectInput layouts to use flex pattern - Fix remark badge visibility and positioning across all input components
- Add flutter-adapter module for detecting and creating Flutter handlers - Export Flutter adapter utilities from bridge module - Fix nested components sidebar registration (type 2 handled dynamically) - Rename ValidationState to ValidationStoreState for clarity
- Increase sidebar z-index from z-10 to z-20 to prevent overlap with pinned app bar on mobile - Override Tailwind v4 default --font-sans with Montserrat font family - Change font-montserrat class to font-sans for proper font application
- Add /index suffix to constants and events imports for TypeScript moduleResolution: "Bundler" compatibility - Add glassmorphism effect to bottom navigation bars with backdrop-blur-md and reduced opacity backgrounds
- Update CI workflow to use Node 22 to match local environment - Replace directory imports with direct file imports for TypeScript bundler moduleResolution compatibility: - constants → core/constants - events → events/Focus, events/KeyDown - PAPI → PAPI/index
- Move font declarations from separate CSS file to index.css - Use @/ alias for Vite to properly resolve font paths - Fonts are now inlined as base64 in the built CSS - Fix PAPI import path to use explicit /index suffix
- Fix mobile sidebar scrolling with content by using fixed positioning - Add Tailwind safelist for dynamic HTML content (innerHTML components) - Copy stylesheets into Shadow DOM for InnerHTML component - Fix PAPI import path for TypeScript bundler resolution
Update InnerHTML tests to account for the content wrapper div that was added when implementing Shadow DOM for stylesheet isolation. Tests now query for the content div inside the shadow root.
- Remove Node 18 from test matrix (Vite 7/Vitest 4 require Node 20+) - Remove feature/** from push triggers to avoid duplicate CI runs (PRs from feature branches already trigger CI via pull_request)
- Replace local Montserrat font files with Google Fonts CDN import - Add smooth enter/exit animations for all modal dialogs - Add animated close helper for modals (uses .closing class pattern) - Fix lookInto function to animate modal close when clicking list items - Fix saveRemark function to animate modal close when saving - Add sidebar overlay with blur effect on mobile - Fix sidebar z-index to only apply on mobile (md:z-auto) - Add scroll-to-top button entry/exit animations - Remove unused loading.png asset - Remove unused imports in ListSelectInputRepeat and ListTextInputRepeat
- Extract modal components (ListModal, ErrorWarningModal, SubmitModal) - Extract sidebar components (FormSidebar, SidebarItem) - Extract layout components (FormHeader, ThemeToggle, FormConfigError) - Extract navigation component (NavigationBar) - Create reusable hooks (useListPagination, useFormInitialization) - Add utility modules (animations, device detection, navigation) - Add pagination info display with "X-Y of Z" format - Fix blank count calculation to count all sections - Fix Prev/Next button visibility based on page position - Improve navigation button animations with smooth transitions - Move scroll target to top of FormInput for better UX - Reduce Form.tsx from ~1859 to 863 lines (~54% reduction)
- Center pagination controls with icon buttons - Replace text Prev/Next with chevron arrows - Use disabled state instead of hiding buttons - Add subtle border separator above pagination - Apply consistent design to ListModal and ErrorWarningModal
- Remove validation checks from confirmSave (save draft only) - Move warning toast to top-right position - Fix border color for Tailwind v4 (default to gray-200)
feat: FormGear v2.0 complete rewrite
Enable back/forward button navigation between form sections by pushing history state on each section change and listening for popstate events. This allows mobile WebView back buttons to navigate between sections instead of closing the WebView.
feat(nav): integrate browser history API for section navigation
…ctionButton and RemarkButton
feat(ui): add shared UI components and new input types
feat(ui): add shared UI components and new input types
- @solid-primitives/input-mask: 0.0.100 → 0.3.1 - @thisbeyond/solid-select: 0.7.1 → 0.16.0 - signature_pad: 4.0.5 → 5.1.3 - @types/node: 22.x → 25.3.0 - jsdom: 27.4.0 → 28.1.0
chore(deps): upgrade dependencies to latest
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.
FormGear v2.0: A 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| B2📊 Store 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)new Function()with cache📊 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 Function - Fast & Cached"] B1[Expression String] --> B2{In Cache?} B2 -->|Yes| B3[Get Cached Fn] B2 -->|No| B4["new Function()"] 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+FunctionfindIndex()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, 10📊 Old 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["new Function() 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. Changelog
Recent Changes
fix(ui): Simplify save button (draft only), warning toast position, Tailwind v4 border colorfix(ui): Improve modal pagination design with centered icon buttonsrefactor(form): Extract Form.tsx into modular componentsfeat(ui): Add modal animations and use Google Fonts CDN📜 Full Commit History (40 commits)
11. Conclusion
FormGear v2 delivers:
eval())Test Plan