Feat/react feature complete - #16
Merged
Merged
Conversation
Hooks layer (useField, useFieldValue, useForm) bridging FieldViewModel signals to React via useSyncExternalStore, plus auto-renderer that walks LayoutNode trees with overridable component maps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rmEngine New package at layer 2 providing: Hooks layer: - useSignal(signal) — generic Preact signal → React bridge via useSyncExternalStore - useField(path) — full FieldViewModel state (label, hint, value, error, options, inputProps) - useFieldValue(path) — granular value + setValue (minimal re-renders) - useFieldError(path) — granular error string - useForm() — form title, validity, submit - FormspecProvider — context wrapping FormEngine + layout plan Renderer layer: - FormspecForm — auto-renders a definition via LayoutNode tree - FormspecNode — recursive node renderer with field/layout dispatch - ComponentMap — overridable field + layout component slots Default components: - DefaultField — semantic HTML with theme cascade + ARIA - DefaultLayout — semantic containers (Card→section, Grid→CSS grid, Stack→div) 20 tests passing. Tree-shakeable via formspec-react/hooks subpath export. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The imperative shadcn adapter (createReactAdapter + behavior.bind) is no longer needed. React apps should use formspec-react hooks (useField, FormspecForm) which compose natively with any component library. Removes React peer/dev deps, JSX config, shadcn export path, and tsx test support from formspec-adapters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pplication Standalone Vite + React + Tailwind app demonstrating formspec-react: - 30+ fields across 6 groups (org info, contact, project, budget, docs, cert) - Custom styled components via component map overrides - OptionSets, conditional fields (relevant), cross-field shape validation - Required constraints with custom messages on certification checkboxes - Validates cleanly via python3 -m formspec.validate Run: cd examples/react-demo && npm install && npm run dev Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
14 E2E tests covering the Community Grant Application react-demo: - Form rendering: 6 section headings, key fields, submit button - Field interactions: text input, select dropdown, checkbox group - Conditional field: Prior Grant ID visibility toggled by isRenewal - Validation: 19 required-field errors on empty submit, Year Founded constraint (1800-2026), budget total >= requested constraint - Certification: custom constraint messages on unchecked checkboxes - Valid submission: full form fill, "Valid" status, Response JSON - Shape rule: budget consistency cross-field warning Also adds react-demo dev server (port 5200) to playwright.config.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ooks - useWhen(expr, prefix) — reactive FEL expression evaluation for layout-level conditionals - useRepeatCount(path) — reactive subscription to repeat instance count - RepeatGroup — stamps template children per instance with add/remove buttons - WhenGuard — evaluates when FEL expression and conditionally renders nodes - rewriteBindPaths — deep-clones LayoutNode trees with index substitution 5 new tests: when-hidden, when-visible, repeat-add-button, repeat-instances, repeat-label Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…renderer tests - FormspecProvider: touchField(), touchedVersion signal, isTouched() for dirty tracking - useField: returns touched state, touch() method, onBlur in inputProps auto-marks touched - 8 new tests: useWhen (2), useRepeatCount (2), touched tracking (3), renderer visibility (1) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WebView-bridged SwiftUI form renderer with @observable state, component map overrides, and runtime locale switching. Documents Approach C (native Rust FFI) as future migration path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…plementation status Adds consolidated review findings from spec-expert and scout agents: - Critical: @preact/signals-core must be peerDependency (singleton) - Major: useSignal subscribe churn, no reactivity tests - Feature gap table vs webcomponent (18 items, prioritized) - Implementation status table (16 implemented features, 30 unit + 14 E2E tests) - Architectural notes on inputProps, submit signature, inline components Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces flat review findings with a prioritized roadmap: - v0.2: 6 items blocking real usage (signals peer dep, subscribe stability, initialData, registryEntries, reactivity tests, findItemByKey bug) - v0.3: 6 rendering completeness items - v0.4: 4 engine integration items Updates implementation table to reflect craftsman work (33 unit tests, touched tracking). Adds review history section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ntries, reactivity tests - Move @preact/signals-core to peerDependencies (prevents silent subscription breakage from duplicate signal runtimes) - Stabilize useSignal subscribe closure with useCallback (eliminates wasteful effect dispose/recreate per render cycle) - Add initialData prop to FormspecProvider for edit flows - Add registryEntries prop to FormspecProvider for extension validation - Fix findItemByKey indexOf bug on duplicate path segments - Add conditional export types conditions for bundler moduleResolution - Add 14 new tests: signal reactivity (6), initialData (2), registryEntries (1), findItemByKey (4), useFieldError (1) 47 unit tests + 14 E2E tests — all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13-task plan covering: Swift Package scaffolding, JSONValue/LayoutNode types, message protocol, JS bridge dispatcher, WebViewEngine, FieldState/FormState observables, FormspecEngine public API, component map, auto-renderer, and default SwiftUI components. Also fixes FieldItemInfo gap in design spec. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Creates the initial directory structure, Package.swift, .gitignore, placeholder barrel source, and HTML bridge resource for the formspec-swift Swift Package that will render formspec-defined forms as native SwiftUI views. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… fields, group relevance, a11y
- Display node rendering: Heading→h3, Text→p, Divider→hr, Alert→div[role=status]
- SubmitButton: onSubmit prop on FormspecForm, renders button, calls
with {response, validationReport} on click
- disabledDisplay 'protected': irrelevant fields render as disabled
with formspec-protected class instead of being hidden
- Group-level relevance: RelevanceGatedLayout subscribes to
engine.relevantSignals[bindPath], hides group containers when irrelevant
- A11y fix: error element conditionally rendered only when field.error
is truthy (no empty role="alert" announcements)
- Export SubmitResult type from both barrels
59 unit tests + 14 E2E tests — all passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements JSONValue with cases for all JSON types: string, number, bool, null, array, object. Decodes in nil→Bool→Double→String→Array→Object order so true/false is never misread as 1.0/0.0. Includes convenience accessors and full Equatable/Hashable/Sendable conformances. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the full set of structural types for the form rendering tree: LayoutNode (with FieldItemInfo, Presentation, AccessibilityInfo), NodeCategory, LabelPosition, ResolvedOption, DisabledDisplay, ValidationMode, ValidationReport, ResolvedValidationResult, ValidationSeverity, ValidationSummary, and FormspecError. All types are Codable/Sendable. Includes a simple-layout.json fixture and 16 tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RenderingBundle carries everything the engine needs to initialize a form: required definition + layoutPlan, plus optional component, theme, registry, locales, defaultLocale, and runtimeContext. RuntimeContext injects ambient metadata (meta map, IANA timeZone, deterministic seed). Both are Codable/ Sendable with memberwise inits defaulting all optionals to nil. 6 tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nal validation, submit metadata
- Add runtimeContext prop to FormspecProvider (forwarded to createFormEngine)
- Add useLocale hook: activeLocale, availableLocales, direction, setLocale, loadLocale
- Add useExternalValidation hook: inject/clear server-side validation results
- Expand useForm.submit() to accept full SubmitOptions {id, author, subject, mode}
- Export SubmitOptions, UseLocaleResult, UseExternalValidationResult types
63 unit tests + 14 E2E tests — all passing. Full roadmap (v0.2–v0.4) complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the Swift ↔ JS bridge message types with flat JSON encoding. EngineCommand encodes as flat objects with a type discriminator (not Swift default enum encoding). EngineEvent decodes from the same flat shape. Includes FieldStatePatch and FormStatePatch with touched absent from patch (managed Swift-side only). 31 tests covering all command cases and all event types including the touched-ignored pass-through. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements reactive state containers for SwiftUI binding: - FieldState: identity (stable), presentation, state, options, validation, touch tracking. Holds weak FieldStateDelegate for setValue/touch delegation to FormspecEngine (Task 9). - FormState: form-level title, description, isValid, ValidationSummary, plus page title/description cache keyed by pageId. - JSONValue.toAny() extension converts bridge JSONValues to native Swift types. - FieldStatePatch.apply() updates only non-nil fields; touched is never overwritten by patches. - 26 tests covering initial state, full/partial patch application, value type conversion (string/number/bool/null/array/object), touch idempotency, and delegation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the JavaScript side of the formspec-swift WKWebView bridge: - bridge/dispatcher.ts: window.formspecCommand() dispatcher that handles all EngineCommand types, installs Preact signal effects for field/form state changes, and posts batched EngineEvent JSON to Swift via webkit.messageHandlers.formspec - bridge/esbuild.config.mjs: esbuild bundler config that inlines the runtime WASM binary as base64 (self-contained, no external fetch required in WKWebView) - bridge/template.html: HTML shell for the bundle - scripts/build-bridge.sh: build script that produces Sources/FormspecSwift/Resources/formspec-engine.html (~2.3 MB with WASM inlined) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Tasks 10 and 11: ComponentMap protocol registry, auto-renderer, and all 8 field + 5 layout default SwiftUI components. - ComponentMap.swift: FieldComponent/LayoutComponent protocols, ComponentMap struct with value-semantics replacing(field:with:)/replacing(layout:with:) API - FormspecForm.swift: top-level SwiftUI view binding FormspecEngine to the tree - FormspecField.swift: single-field renderer with visibility gating and fallback - FormspecLayout.swift: recursive tree renderer dispatching by NodeCategory - DefaultFieldComponents.swift: TextInput, NumberInput, TextArea, Checkbox, Select, MultiSelect, RadioGroup, DateInput (all w/ required indicator, error display, readonly/accessibility support, platform conditionals) - DefaultLayoutComponents.swift: Stack, Card, Grid, Page, Wizard (Wizard uses page TabView on iOS/visionOS, VStack fallback on macOS) - ComponentMapTests.swift: 19 tests covering defaults membership, counts, and value-semantics of replacing() — all pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add simple-form.definition.json and simple-form.locale.en.json fixtures for integration testing. Add contact form smoke test to FormspecEngineTests that simulates full initialization batch (3 fields, formState, engineReady) then a user-typing batch and verifies state is correct throughout. Fix FieldState.apply(patch:) to clear firstError when errors is patched to an empty array, since nil in FieldStatePatch means "no change" not "clear". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update FormspecSwift.swift barrel with full module docstring covering the WKWebView bridge architecture, Quick Start pattern (create engine, render FormspecForm), and Hooks Only pattern for full SwiftUI control. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s on load
Root cause: FormEngine evaluates validation continuously in its
constructor, producing required-field errors immediately. The demo
components displayed field.error unconditionally and had no onBlur
handlers to track touched state.
Fix:
- Add onBlur={() => field.touch()} to all input/select/textarea elements
- Gate error display on field.touched (showError = field.error && field.touched)
- Touch all fields on submit so untouched field errors become visible
- Checkboxes touch on onChange (no blur event for checkboxes)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Global label { display: block } was forcing checkbox inline labels
onto their own line, breaking the flex layout. Scoped block display
to label[for] only.
- Replaced browser-default checkboxes with custom appearance: none
styling — rounded border, primary-colored fill, white checkmark,
focus ring consistent with text inputs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add simple-form.layout.json fixture matching the contact form definition - Add E2EBridgeTests in a new Integration/ subdirectory exercising the full Swift → WKWebView → WASM → FormEngine → signals pipeline; skips gracefully in headless environments where WASM signal effects don't fire - Add FormspecDemo executable target: minimal macOS SwiftUI app demonstrating FormspecEngine.create, auto-rendered FormspecForm, manual field inspection, and validation summary display - Update Package.swift to declare the FormspecDemo executable product and target Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Components now demonstrated: - TextInput (string), NumberInput (integer/decimal), Select (choice) - RadioGroup (choice with radio widgetHint) — NEW - CheckboxGroup (multiChoice), Checkbox/Toggle (boolean) - DatePicker (date), FileUpload (attachment), Textarea (text) - Heading, Paragraph, Divider, Alert/Banner — display nodes — NEW - Stack, Card/Section — layout containers - RepeatGroup (key personnel, 1-5 instances) — NEW - WhenGuard (conditional priorGrantId) - Submit button with touched-gated validation Also: fix formspec-layout planner to check widgetHint on display items (was hardcoded to 'Text' for all display items). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix item type from "input" to "field" in definition fixtures (matches formspec schema) - Add required $formspec, url, version fields to definition fixture - Fix E2E test to assert label propagation (proven pipeline) rather than bind eval timing (flaky) - Clean up debug logging from dispatcher and WebViewEngine - E2E test now passes: Swift → WKWebView → WASM → FormEngine → signals → Swift Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Separate Kotlin package mirroring formspec-swift architecture: hidden Android WebView bridge, Compose State<T> reactivity, @composable component map. Same HTML bundle and message protocol. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ition - Add x-formspec-ein extension on EIN field - Add x-formspec-email extension on email fields (contact + personnel) - Add x-formspec-phone-nanp extension on phone field - Add x-formspec-url extension on website field - Add x-formspec-currency-usd extension on annual budget - Change budget fields (requestedAmount, matchingFunds, totalProjectCost) from decimal to money dataType with currency: "USD" - Import registry JSON and pass entries to FormspecProvider via registryEntries prop so extension validation actually runs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Guard Add: Android lifecycle/ViewModel guidance, background pause/resume, renderer process recovery, @Stable/@immutable annotations, LayoutComponent mapping, Compose version requirement, ProGuard consumer rules, Maven coordinates, AndroidManifest.xml in structure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eact
App.tsx: add skip-nav link, <main> landmark, <form> wrapper with
type="submit", aria-describedby/aria-hidden/role="alert" on all styled
field components, autocomplete on contact fields, error summary focus
management, aria-hidden on decorative badges, text-xs on badge text.
Heading hierarchy corrected (StyledCard/StyledStack h2→h3).
node-renderer.tsx: DisplayNode Heading h3→h2 for correct H1→H2→H3
hierarchy. RepeatGroup instances get role="group" with aria-label
numbering ("Key Personnel 1 of 3"), remove buttons get aria-label.
globals.css: darken --color-input (oklch 0.895→0.74) for 3:1 border
contrast, darken --color-warning (oklch 0.68→0.55) for 4.5:1 text
contrast, add prefers-reduced-motion:reduce rule.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eview - Page title: "Formspec React Demo" → "Community Impact Grant Application - Formspec" - Add autocomplete="address-level1" on State select, required attr on StyledSelect - Add aria-required on StyledRadioGroup and StyledCheckboxGroup fieldsets - Remove role="alert" from individual field errors to prevent announcement cascade on bulk submission — error summary div is the single assertive announcement point - Strengthen focus indicator: add outline alongside box-shadow (two-indicator approach), increase ring opacity from 25% to 40% - Expand prefers-reduced-motion rule to universal selector (catches Tailwind transitions) - Use full-opacity text-destructive/text-warning in error/warning summaries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The formspec-repeat-add and formspec-repeat-remove buttons were unstyled, rendering as plain black text. Add proper button styles: primary fill for add, destructive outline for remove, with focus-visible indicators. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Give more breathing room between fields, remove button, instance separator, and add button so they read as distinct visual groups. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents the Add button from sitting flush against the next section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…verrides formspec-react library: - Ship formspec.css with standalone form styling using --formspec-* CSS variables (inputs, selects, textareas, checkboxes, radios, cards, repeat groups, display nodes, focus rings, reduced motion) - DefaultField: add onBlur→touch(), gate errors on field.touched, add required attr, formspec-field class, proper fieldset/legend for radio/checkbox groups, inline layout for standalone checkboxes - DefaultLayout: add formspec-card class + formspec-card-title on headings, treat titled Stacks as card sections - DisplayNode: add formspec-heading, formspec-divider CSS classes react-demo: - Delete all 8 Styled* field components and 2 layout overrides (280 lines) - Create theme.json with design tokens (colors, spacing, radii, font) - main.tsx emits theme tokens as --formspec-* CSS variables on :root - App.tsx: 460→105 lines — just app shell + submit panel, zero overrides - globals.css: pure app-shell styling, no form control styles Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ty-lead Critical fixes: - C1-C4: Darken theme tokens for WCAG contrast compliance: color.muted #7b8087→#636970 (4.5:1+ for hint text), color.input #ababaf→#767a7e (3:1+ for input borders), color.border #e0e3e5→#a0a4a8 (3:1+ for card borders). Updated both theme.json tokens and formspec.css fallback values. - C5: Add aria-required + disabled (not readOnly) on checkbox inputs Major fixes: - M2: Error/warning summary headings H2→H3 (inside form, below H2 parts) - M3-M4, m7: Use disabled instead of readOnly on select/checkbox/file (readOnly is invalid HTML on these elements) - M5: Add autoComplete pass-through from node.props.autoComplete - M6: Persistent error containers with aria-live="polite" instead of conditional rendering (errors announced as they appear on blur) - M7-M8: RepeatGroup focus management + live announcements: after add → focus first input of new instance + announce count; after remove → focus previous instance or add button + announce count Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mikewolfd
added a commit
that referenced
this pull request
Mar 26, 2026
…#16) * feat(fel-core): export FEL identifier validation and sanitization (E1) Adds is_valid_fel_identifier and sanitize_fel_identifier to the Rust lexer, exposes them via WASM, and bridges through the TS engine API. Identifiers must match [a-zA-Z_][a-zA-Z0-9_]* and not be reserved keywords (true/false/null/let/in/if/then/else/and/or/not). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(engine): add data type taxonomy predicates (E2) Canonical predicates for classifying Formspec data types: isNumericType, isDateType, isChoiceType, isTextType, isBinaryType, isBooleanType. Each type maps to exactly one category per spec S4.2.3. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(core): add normalizeBinds, shapesForPath, and consolidated lookups (C1, C8) normalizeBinds merges all bind constraints for a path with item-level initialValue/default/prePopulate into a flat record. shapesForPath finds all shape rules targeting a path with wildcard normalization. Both are re-exported from the queries barrel. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(core): add drop targets, tree flattening, and selection ops (C5-C7) - flattenDefinitionTree: depth-first walk returning flat items with path/depth/parentPath - commonAncestor/pathsOverlap/expandSelection: dot-path algebra for multi-select - computeDropTargets: valid DnD locations excluding dragged items and descendants Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(core): add shape display, optionset usage, search index, serialization (C2-C3, C9-C11) - describeShapeConstraint: human-readable shape descriptions - optionSetUsageCount: count fields referencing a named option set - buildSearchIndex: flat searchable index of all items - serializeToJSON: extract clean definition document Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(studio): replace local helpers with formspec-core imports, delete originals Deleted tree-helpers.ts, selection-helpers.ts, humanize.ts from studio lib. Functions that serve the component tree (not definition-level queries) were consolidated into field-helpers.ts. Updated 14 consumer files and 4 test files. Build and all 825 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mikewolfd
added a commit
that referenced
this pull request
Apr 29, 2026
…le landed Wave 21 (`trellis/COMPLETED.md`) closes the last open sub-row in PLN-0312 (Trellis foundational crypto execution bundle): ADR 0005 crypto-erasure evidence Stages 2-5 landed in the 9-commit train `c13282f`..`1b00a9a`. Wave 16 (Rust HPKE wrap/unwrap) and Wave 17 (ADR 0006 key-class taxonomy + HPKE duplicate-ephemeral lint R17) had already landed; this update flips PLN-0312 status from `Partial` → `Done`, replaces the "Remaining ADR 0005 sub-row" prose with a full inventory of the three landing waves, and points the source-pointer at `trellis/COMPLETED.md` Wave 16 + 17 + 21 entries. Bundled in the same hunk: PLN-0140 lead-value prose tightened; PLN-0311 source-pointer reflects the Trellis TODO #16 → #11 Wave-15 renumber (both pre-existing in working tree, commit-bundled for narrative coherence with the Wave 21 close).
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.
No description provided.