Skip to content

Form Intelligence — Performance

Package: @jayoncode/form-intelligence
Gates: pnpm --filter @jayoncode/form-intelligence check:size
Policy: ADR-013 (performance budget) — see Ratcheting below.

Tree-shaking — what you import, you pay for

Optional engines (/devtools, /format, /plugins, offline queue implementation) stay out of the login graph until you import them. Prefer:

  • Main entry for createForm + validators + when
  • Narrow subpaths only when needed (Entrypoints)
  • Dynamic import() for DevTools in non-production builds

Budgets below measure entry chunks, not “all features in one bundle.”

Bundle budgets

Measured as entry chunk gzip (esbuild minify + splitting: true) so dynamic import() of offline/analytics/object-diff/integrations stays out of the login graph.

FixturemaxGzipKbNotes
core-login27createForm login graph; forbids DevTools / offline queue class / analytics module
workflow-rules3Rules-only subpath
format-only2Format subpath

Ratcheting (ADR-013)

  1. Prefer tree-shake / lazy import() over raising maxGzipKb
  2. Re-measure with pnpm exec tsc -b packages/form-intelligence && pnpm --filter @jayoncode/form-intelligence check:size
  3. Update scripts/bundle-budgets.json, this doc, and a changeset with rationale
  4. Optional: add forbid needles proving optional engines stayed out of the entry chunk

Phase 18 measurement note (2026-07)

  • Node 20 / esbuild budget script
  • core-login entry ≈ 22.8 KB gzip after removing static workflow → browser-lifecycle re-export and lazy keyboard/draft modules
  • Raised budget 19 → 24 for engines shipped in Phases 1–17 (middleware, presentation, a11y, controllers, …) while keeping forbid list for /devtools + offline queue implementation

UI projection + submission guards (2026-07)

  • form.ui / submissionGuard() live on the main instance (not lazy) so adapters share one projection
  • core-login entry ≈ 24.7 KB gzip after derived /ui projection + hard submit guards
  • Raised budget 24 → 26; DevTools / offline queue forbid list unchanged

HTML constraints Phase 1 (2026-07)

  • Kind merge (mergeValidatorsByKind) runs on createForm; HTML extract stays sync on DOM attach (lazy import() would race target / form.ref attach)
  • core-login entry ≈ 26.1 KB gzip with merge + extract on the always-on graph
  • Raised budget 26 → 27; DevTools / offline / captcha forbid list unchanged

Timing budgets (Vitest)

See tests/performance/performance-budgets.test.ts.

Budgets catch order-of-magnitude regressions. Limits scale by 1.5× locally and 3× on CI (CI / GITHUB_ACTIONS) because shared runners are noisy.

WorkloadBaseLocal (×1.5)CI (×3)
Validate 50 fields (warm median)150 ms225 ms450 ms
setValue 50 × 100350 ms525 ms1050 ms
Undo depth 50200 ms300 ms600 ms

Complexity (Big-O targets)

OperationTarget
setValue (no rules)O(1) path + O(L) listeners
setValue + rulesO(R + F_affected)
Sync validate one fieldO(V_field)
Validate allO(Σ V)
Async scheduleO(1) debounce map
Subscribe notifyO(L)

Memory

  • form.destroy() owns teardown (timers, plugins, DOM, history, DevTools unregister)
  • Offline queue data in localStorage survives destroy by design — not a leak (Spec 25)
  • Stress: tests/performance/memory-leak-stress.test.ts

JOC — headless @jayoncode/* TypeScript libraries. Docs sync from package source via TypeDoc and sync scripts.