Skip to content

Integrations

Browser session, keyboard shortcuts, analytics, and DevTools — attach with plugins or workflow config.

Previous: Formatters · Next: Adapters

Import path

FeatureImport
Lifecycle + keyboard plugins@jayoncode/form-intelligence/plugins
DevTools inspector@jayoncode/form-intelligence/devtools only
Analytics / offline / history helpersPrefer workflow config; optional /analytics, /offline, /history

Canonical table: Entrypoints & subpaths.

Browser lifecycle (draft on hide)

Optional peer: @jayoncode/browser-lifecycle.

ts
import { createForm } from "@jayoncode/form-intelligence";
import { createBrowserLifecyclePlugin } from "@jayoncode/form-intelligence/plugins";
import { createBrowserLifecycle } from "@jayoncode/browser-lifecycle";

const shared = createBrowserLifecycle({ idleTimeout: 60_000 });

const form = createForm({
  initialValues: { notes: "" },
  workflow: {
    draft: { enabled: true, storageKey: "editor-draft" },
  },
  plugins: [
    createBrowserLifecyclePlugin({
      // Both default ON when omitted (`!== false`)
      saveDraftOnHidden: true,
      flushOfflineQueueOnOnline: true,
      lifecycle: shared, // optional — inject a shared session (adapter does not dispose it)
    }),
  ],
});
// Or after create: form.use(createBrowserLifecyclePlugin({ ... }));
OptionDefaultEffect
saveDraftOnHiddenonpage:hiddenform.saveDraft()
flushOfflineQueueOnOnlineonconnection:onlineform.flushOfflineQueue()
lifecycleUse an existing BrowserLifecycle; otherwise the plugin creates and disposes its own

When the tab is hidden, the plugin calls form.saveDraft(). When connectivity returns, it can flush workflow.offlineQueue.


Keyboard shortcuts

Via workflow config

ts
createForm({
  initialValues: { body: "" },
  workflow: {
    draft: { enabled: true, storageKey: "doc" },
    keyboard: [
      { combo: "Ctrl+S", action: "saveDraft" },
      { combo: "Ctrl+Z", action: "undo" },
      { combo: "Ctrl+Shift+Z", action: "redo" },
      { combo: "Ctrl+Enter", action: "submit" },
    ],
  },
});

Via plugin

ts
import { createKeyboardPlugin, keyboard } from "@jayoncode/form-intelligence/plugins";

form.use(
  createKeyboardPlugin([
    keyboard.shortcut("mod+s", (f) => f.saveDraft()),
    keyboard.shortcut("mod+enter", (f) => {
      void f.submit();
    }),
  ]),
);

Analytics

Form UX metrics only — not a product analytics SDK (no pageviews, funnels-as-a-service, or vendor lock-in). Snapshots never include field values.

ts
createForm({
  initialValues: { email: "", ssn: "" },
  workflow: {
    analytics: {
      enabled: true,
      excludePaths: ["ssn"], // path denylist
      // includePaths: ["email"], // deny-by-default allowlist
      onSnapshot: (snapshot) => {
        // Fired whenever getAnalytics() produces a snapshot
        sendToYourPipeline(snapshot);
      },
    },
  },
});

const snap = form.getAnalytics();
// startedAt, completedAt, errorCount, errorsByField,
// fieldViews, dropOffField, abandonedAt, currentStep,
// timeToCompleteMs, timeToFirstErrorMs

Use for drop-off diagnosis and field-level error heatmaps. Export to Segment/GA yourself if needed — scrub paths before network.

Analytics module API

workflow.analytics registers a FormAnalyticsTracker module for you and exposes it through form.getAnalytics(). @jayoncode/form-intelligence/analytics exports the underlying pieces for a standalone tracker (no createForm module) or a custom plugin:

ExportRole
FormAnalyticsTrackerStateful tracker — recordFieldView, recordFieldError, getSnapshot()
createAnalyticsPlugin(tracker)Wraps a tracker as a FormPlugin you can pass to plugins: []
ts
import {
  FormAnalyticsTracker,
  createAnalyticsPlugin,
} from "@jayoncode/form-intelligence/analytics";

const tracker = new FormAnalyticsTracker();

createForm({
  plugins: [createAnalyticsPlugin(tracker)],
});

Prefer workflow.analytics + form.getAnalytics() — reach for the tracker/plugin directly only when you need a tracker instance that outlives the form or want to compose analytics into your own module.


Object diff plugin

Optional peer @jayoncode/object-diff. Audits changes vs defaults on successful submit:

ts
import { createObjectDiffPlugin } from "@jayoncode/form-intelligence/plugins";

form.use(
  createObjectDiffPlugin({
    auditOnSubmit: true, // default true when onSubmitDiff is set
    diffOptions: { maxDepth: 8, treatUndefinedAsMissing: true },
    onSubmitDiff: async (diff, values) => {
      await api.audit({
        changeCount: diff.metadata.changeCount,
        paths: diff.changes.map((c) => c.path),
      });
    },
  }),
);
OptionNotes
auditOnSubmitDefault on when onSubmitDiff is provided (!== false)
onSubmitDiff(diff, values) => void | Promise<void> after successful submit
diffOptionsPass-through FormDiffOptions (maxDepth, includeUnchanged, treatUndefinedAsMissing)

Instance helpers form.diffFromDefaults() / form.diffFrom() / submit({ includeDiff: true }) remain available without the plugin — see State.


DevTools

ts
import {
  connectFormDevToolsToGlobal,
  enableFormDevTools,
  getFormDevTools,
  redactFormStateSnapshot,
} from "@jayoncode/form-intelligence/devtools";

enableFormDevTools(form);
connectFormDevToolsToGlobal();

const inspector = getFormDevTools();
inspector.getActiveForms();
inspector.getStateSnapshot(form.id);
inspector.getUiProjection(form.id); // hard guard + UX explain + field status
inspector.getPlugins(form.id);
inspector.getPerformanceMarks(form.id);

Playground DevTools (/playground/form-intelligence/devtools) consumes this inspector: UI projection / explain panels, event log, validation log, workflow timeline, plugins, performance marks, export/import state.

Production redaction

  • Never enable DevTools by default in production builds — opt in explicitly (local, staging, or a feature flag).
  • Import only @jayoncode/form-intelligence/devtools (optional subpath). Core createForm does not pull it into the login bundle.
  • Prefer metadata-only recording (captureStateOnWorkflowEvents defaults to false).
  • When capturing state, redactValues defaults to true (values become ***).
  • For exports/logs use redactFormStateSnapshot / redactValuesRecord before sending anywhere.
ts
enableFormDevTools(form, {
  captureStateOnWorkflowEvents: true, // opt-in
  redactValues: true, // default
  maxEvents: 200,
  maxValidationEntries: 100,
  maxWorkflowEntries: 100,
  maxPerformanceMarks: 100,
});

const safe = redactFormStateSnapshot(form.getFormState());
Ring optionRole
maxEventsCap general event log
maxValidationEntriesCap validation log
maxWorkflowEntriesCap workflow timeline
maxPerformanceMarksCap performance marks

A dedicated browser extension is planned (Phase 5.4.8).


Subpath imports

For the complete main-vs-subpath map (including /format, /middleware, /presentation, …), see Entrypoints & subpaths. Rules dual-export notes: Rules → Which import.

Next: Adapters — React, Vue, Zod, and core interfaces.

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