attaform 0.0.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -2
  3. package/dist/chunks/devtools.cjs +179 -0
  4. package/dist/chunks/devtools.cjs.map +1 -0
  5. package/dist/chunks/devtools.mjs +177 -0
  6. package/dist/chunks/devtools.mjs.map +1 -0
  7. package/dist/chunks/indexeddb.cjs +119 -0
  8. package/dist/chunks/indexeddb.cjs.map +1 -0
  9. package/dist/chunks/indexeddb.mjs +117 -0
  10. package/dist/chunks/indexeddb.mjs.map +1 -0
  11. package/dist/chunks/local-storage.cjs +58 -0
  12. package/dist/chunks/local-storage.cjs.map +1 -0
  13. package/dist/chunks/local-storage.mjs +56 -0
  14. package/dist/chunks/local-storage.mjs.map +1 -0
  15. package/dist/chunks/session-storage.cjs +58 -0
  16. package/dist/chunks/session-storage.cjs.map +1 -0
  17. package/dist/chunks/session-storage.mjs +56 -0
  18. package/dist/chunks/session-storage.mjs.map +1 -0
  19. package/dist/index.cjs +173 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +493 -0
  22. package/dist/index.d.mts +493 -0
  23. package/dist/index.d.ts +493 -0
  24. package/dist/index.mjs +141 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/nuxt.cjs +97 -0
  27. package/dist/nuxt.cjs.map +1 -0
  28. package/dist/nuxt.d.cts +38 -0
  29. package/dist/nuxt.d.mts +38 -0
  30. package/dist/nuxt.d.ts +38 -0
  31. package/dist/nuxt.mjs +94 -0
  32. package/dist/nuxt.mjs.map +1 -0
  33. package/dist/runtime/plugins/attaform.cjs +32 -0
  34. package/dist/runtime/plugins/attaform.cjs.map +1 -0
  35. package/dist/runtime/plugins/attaform.d.cts +5 -0
  36. package/dist/runtime/plugins/attaform.d.mts +5 -0
  37. package/dist/runtime/plugins/attaform.d.ts +5 -0
  38. package/dist/runtime/plugins/attaform.mjs +30 -0
  39. package/dist/runtime/plugins/attaform.mjs.map +1 -0
  40. package/dist/shared/attaform.B5GWYl76.cjs +386 -0
  41. package/dist/shared/attaform.B5GWYl76.cjs.map +1 -0
  42. package/dist/shared/attaform.BRTxpA3q.mjs +3283 -0
  43. package/dist/shared/attaform.BRTxpA3q.mjs.map +1 -0
  44. package/dist/shared/attaform.BYc9kugA.d.ts +124 -0
  45. package/dist/shared/attaform.Bubm_slq.cjs +622 -0
  46. package/dist/shared/attaform.Bubm_slq.cjs.map +1 -0
  47. package/dist/shared/attaform.BwaYWtMs.d.cts +126 -0
  48. package/dist/shared/attaform.BwaYWtMs.d.mts +126 -0
  49. package/dist/shared/attaform.BwaYWtMs.d.ts +126 -0
  50. package/dist/shared/attaform.CNJO3mME.cjs +3295 -0
  51. package/dist/shared/attaform.CNJO3mME.cjs.map +1 -0
  52. package/dist/shared/attaform.CRgix6_n.cjs +796 -0
  53. package/dist/shared/attaform.CRgix6_n.cjs.map +1 -0
  54. package/dist/shared/attaform.CXZgUECn.d.cts +124 -0
  55. package/dist/shared/attaform.CXpzmj38.mjs +617 -0
  56. package/dist/shared/attaform.CXpzmj38.mjs.map +1 -0
  57. package/dist/shared/attaform.Cc93zNzD.mjs +83 -0
  58. package/dist/shared/attaform.Cc93zNzD.mjs.map +1 -0
  59. package/dist/shared/attaform.DDXrY-1Q.d.cts +2568 -0
  60. package/dist/shared/attaform.DDXrY-1Q.d.mts +2568 -0
  61. package/dist/shared/attaform.DDXrY-1Q.d.ts +2568 -0
  62. package/dist/shared/attaform.DOKOyb3Y.d.mts +124 -0
  63. package/dist/shared/attaform.DlgKK10S.mjs +789 -0
  64. package/dist/shared/attaform.DlgKK10S.mjs.map +1 -0
  65. package/dist/shared/attaform.al_rpt7_.mjs +361 -0
  66. package/dist/shared/attaform.al_rpt7_.mjs.map +1 -0
  67. package/dist/shared/attaform.xKWYHMdq.cjs +89 -0
  68. package/dist/shared/attaform.xKWYHMdq.cjs.map +1 -0
  69. package/dist/transforms.cjs +11 -0
  70. package/dist/transforms.cjs.map +1 -0
  71. package/dist/transforms.d.cts +49 -0
  72. package/dist/transforms.d.mts +49 -0
  73. package/dist/transforms.d.ts +49 -0
  74. package/dist/transforms.mjs +2 -0
  75. package/dist/transforms.mjs.map +1 -0
  76. package/dist/vite.cjs +39 -0
  77. package/dist/vite.cjs.map +1 -0
  78. package/dist/vite.d.cts +53 -0
  79. package/dist/vite.d.mts +53 -0
  80. package/dist/vite.d.ts +53 -0
  81. package/dist/vite.mjs +37 -0
  82. package/dist/vite.mjs.map +1 -0
  83. package/dist/zod-v3.cjs +1511 -0
  84. package/dist/zod-v3.cjs.map +1 -0
  85. package/dist/zod-v3.d.cts +164 -0
  86. package/dist/zod-v3.d.mts +164 -0
  87. package/dist/zod-v3.d.ts +164 -0
  88. package/dist/zod-v3.mjs +1504 -0
  89. package/dist/zod-v3.mjs.map +1 -0
  90. package/dist/zod.cjs +1548 -0
  91. package/dist/zod.cjs.map +1 -0
  92. package/dist/zod.d.cts +67 -0
  93. package/dist/zod.d.mts +67 -0
  94. package/dist/zod.d.ts +67 -0
  95. package/dist/zod.mjs +1541 -0
  96. package/dist/zod.mjs.map +1 -0
  97. package/package.json +182 -6
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Oswald Kay Chisala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,143 @@
1
- # attaform
1
+ # 🙌🏽 Attaform
2
2
 
3
- Name reserved. A Vue 3 forms library by [Oswald Chisala](mailto:oswald.kay.chisala@gmail.com) is in the works.
3
+ [![npm version][npm-version-src]][npm-version-href]
4
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
+ [![License][license-src]][license-href]
6
+ [![Node.js Test Suite](https://github.com/attaform/attaform/actions/workflows/matrix.yml/badge.svg)](https://github.com/attaform/attaform/actions/workflows/matrix.yml)
7
+ [![Nuxt][nuxt-src]][nuxt-href]
8
+
9
+ A type-safe, schema-driven form library for Vue 3 and Nuxt with first-class Zod support.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install attaform zod
15
+ ```
16
+
17
+ **Nuxt 3 / 4** — install the module:
18
+
19
+ ```ts
20
+ // nuxt.config.ts
21
+ export default defineNuxtConfig({
22
+ modules: ['attaform/nuxt'],
23
+ })
24
+ ```
25
+
26
+ **Bare Vue 3** — install the plugin and the Vite plugin:
27
+
28
+ ```ts
29
+ // main.ts
30
+ import { createApp } from 'vue'
31
+ import { createAttaform } from 'attaform'
32
+
33
+ createApp(App).use(createAttaform()).mount('#app')
34
+ ```
35
+
36
+ ```ts
37
+ // vite.config.ts
38
+ import vue from '@vitejs/plugin-vue'
39
+ import { attaform } from 'attaform/vite'
40
+
41
+ export default defineConfig({
42
+ plugins: [vue(), attaform()],
43
+ })
44
+ ```
45
+
46
+ ### Recommended tsconfig
47
+
48
+ We pair well with `noUncheckedIndexedAccess: true`:
49
+
50
+ ```json
51
+ {
52
+ "compilerOptions": {
53
+ "strict": true,
54
+ "noUncheckedIndexedAccess": true
55
+ }
56
+ }
57
+ ```
58
+
59
+ It catches stale `form.values.contacts[N]` reads at compile time. Nuxt 3 / 4 sets this for you.
60
+
61
+ ## Quick start
62
+
63
+ ```vue
64
+ <script setup lang="ts">
65
+ import { z } from 'zod'
66
+ import { useForm } from 'attaform/zod' // zod v4; use /zod-v3 for v3
67
+
68
+ const schema = z.object({
69
+ email: z.email(),
70
+ password: z.string().min(8),
71
+ })
72
+
73
+ const form = useForm({ schema, key: 'signup' })
74
+
75
+ const onSubmit = form.handleSubmit(async (values) => {
76
+ await $fetch('/api/signup', { method: 'POST', body: JSON.stringify(values) })
77
+ })
78
+ </script>
79
+
80
+ <template>
81
+ <form @submit.prevent="onSubmit">
82
+ <input v-register="form.register('email')" placeholder="Email" />
83
+ <small v-if="form.errors.email?.[0]">{{ form.errors.email[0].message }}</small>
84
+
85
+ <input v-register="form.register('password')" type="password" placeholder="Password" />
86
+ <small v-if="form.errors.password?.[0]">{{ form.errors.password[0].message }}</small>
87
+
88
+ <button :disabled="form.meta.isSubmitting">Sign up</button>
89
+ </form>
90
+ </template>
91
+ ```
92
+
93
+ `useForm({ schema, key })` returns a Pinia-style reactive object — read leaves directly, no `.value`:
94
+
95
+ - **`form.values`** — current values. `form.values.email`, `form.values.address.city`.
96
+ - **`form.errors`** — per-field errors, keyed by dotted path. `form.errors.email?.[0]?.message`.
97
+ - **`form.fields`** — per-field flags (`dirty`, `touched`, `errors`, `blank`, …). `form.fields.email.dirty`.
98
+ - **`form.meta`** — form-level flags + counters (`isSubmitting`, `isValid`, `canUndo`, `submitCount`, the flat `meta.errors` aggregate, the per-mount `instanceId`, …).
99
+ - **`form.register(path)`** — typed two-way binding; pair with `v-register` on `<input>` / `<textarea>` / `<select>`.
100
+ - **`form.handleSubmit(onValid, onInvalid?)`** — runs validation, dispatches. The valid callback receives the strict zod-inferred type.
101
+ - **`form.setValue(path, value)`**, **`form.reset()`**, field-array helpers, undo / redo, persistence — see the [API reference](./docs/api.md).
102
+
103
+ ## Features
104
+
105
+ - **Schema-driven types** — every path, value, and error is inferred from the schema; no `any`.
106
+ - **Live validation** — `validateOn: 'change'` by default with synchronous `debounceMs: 0`; `'blur'` and `'submit'` (opt-out) modes available; async refinements await before submit dispatches.
107
+ - **Schema-driven coercion** — string DOM input → schema's typed slot (`string→number`, `string→boolean`) at the directive layer. Default-on; pass `useForm({ coerce: false })` to disable or a custom `CoercionRegistry` to extend.
108
+ - **Register transforms** — `register('email', { transforms: [trim, lowercase] })` runs sync user-input normalization before storage commit. See [recipe](./docs/recipes/transforms.md).
109
+ - **Discriminated-union variant memory** — switching a discriminator (`notify.channel: 'email' → 'sms' → 'email'`) restores the previous variant's typed subtree by default. Set `useForm({ rememberVariants: false })` to drop on switch.
110
+ - **Field arrays** — `append` / `prepend` / `insert` / `remove` / `swap` / `move` / `replace`, fully typed at the call site.
111
+ - **Drafts + undo / redo** — per-field opt-in persistence (`localStorage` / `sessionStorage` / IndexedDB / [custom backend](./docs/recipes/persistence.md#picking-a-backend)) and a bounded undo stack.
112
+ - **Server errors** — `parseApiErrors(payload)` normalises a `{ message, code }[]` wire format; pair with `form.setFieldErrors(...)`. User errors survive schema revalidation.
113
+ - **Stable error codes** — every `ValidationError` carries `code: string`. Library codes (`atta:`) live on the exported `AttaformErrorCode` enum; adapter codes use a `zod:` prefix; consumers pick their own (`api:`, `auth:`, …).
114
+ - **Clearable required fields** — the `unset` sentinel marks a field displayed-empty while storage holds the schema's slim default. Submit fails with `'No value supplied'` for required schemas; `.optional()` / `.nullable()` / `.default(N)` opt out.
115
+ - **SSR** — Nuxt handles the payload round-trip automatically; bare Vue uses `renderAttaformState` / `hydrateAttaformState` ([recipe](./docs/recipes/ssr-hydration.md)).
116
+
117
+ ## Documentation
118
+
119
+ - [**API reference**](./docs/api.md) — every public export with signatures and return shapes
120
+ - [**Recipes**](./docs/recipes) — task-oriented walkthroughs for every feature above
121
+ - [**Troubleshooting**](./docs/troubleshooting.md) — common gotchas and fixes
122
+ - [**Migration guides**](./docs/migration) — per-release upgrade notes
123
+ - [**Performance**](./docs/perf.md) — how it scales; when to worry
124
+ - [**Changelog**](./CHANGELOG.md) — full release history
125
+
126
+ ## Status
127
+
128
+ Pre-1.0. SemVer applies from `v1.0` onward; 0.x minor bumps may still include breaking changes, each documented under [`docs/migration/`](./docs/migration).
129
+
130
+ ## License
131
+
132
+ MIT — see [LICENSE](./LICENSE).
133
+
134
+ <!-- Badges -->
135
+
136
+ [npm-version-src]: https://img.shields.io/npm/v/attaform/latest.svg?style=flat&colorA=020420&colorB=00DC82
137
+ [npm-version-href]: https://npmjs.com/package/attaform
138
+ [npm-downloads-src]: https://img.shields.io/npm/dm/attaform.svg?style=flat&colorA=020420&colorB=00DC82
139
+ [npm-downloads-href]: https://npm.chart.dev/attaform
140
+ [license-src]: https://img.shields.io/npm/l/attaform.svg?style=flat&colorA=020420&colorB=00DC82
141
+ [license-href]: https://npmjs.com/package/attaform
142
+ [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
143
+ [nuxt-href]: https://nuxt.com
@@ -0,0 +1,179 @@
1
+ 'use strict';
2
+
3
+ const paths = require('../shared/attaform.xKWYHMdq.cjs');
4
+ const sensitiveNames = require('../shared/attaform.B5GWYl76.cjs');
5
+
6
+ const INSPECTOR_ID = "attaform";
7
+ const TIMELINE_LAYER_ID = "attaform:events";
8
+ const REDACTED = "[redacted]";
9
+ function redactSensitiveLeaves(value) {
10
+ return redactImpl(value, false);
11
+ }
12
+ function redactImpl(value, inSensitiveSubtree) {
13
+ if (value === null || value === void 0) return value;
14
+ if (typeof value !== "object") {
15
+ return inSensitiveSubtree ? REDACTED : value;
16
+ }
17
+ if (Array.isArray(value)) {
18
+ return value.map((item) => redactImpl(item, inSensitiveSubtree));
19
+ }
20
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
21
+ return inSensitiveSubtree ? REDACTED : value;
22
+ }
23
+ const out = {};
24
+ for (const key of Object.keys(value)) {
25
+ const childSensitive = inSensitiveSubtree || sensitiveNames.segmentMatchesSensitive(key);
26
+ out[key] = redactImpl(value[key], childSensitive);
27
+ }
28
+ return out;
29
+ }
30
+ async function setupAttaformDevtools(app, registry) {
31
+ let mod;
32
+ try {
33
+ mod = await import('@vue/devtools-api');
34
+ } catch {
35
+ return false;
36
+ }
37
+ const setupDevtoolsPlugin = mod.setupDevtoolsPlugin;
38
+ if (typeof setupDevtoolsPlugin !== "function") return false;
39
+ setupDevtoolsPlugin(
40
+ {
41
+ id: INSPECTOR_ID,
42
+ label: "Attaform",
43
+ packageName: "attaform",
44
+ homepage: "https://github.com/attaform/attaform",
45
+ app,
46
+ componentStateTypes: ["Attaform form"]
47
+ },
48
+ (api) => wire(api, app, registry)
49
+ );
50
+ return true;
51
+ }
52
+ function wire(api, app, registry) {
53
+ const subscriberUnsubs = /* @__PURE__ */ new Map();
54
+ api.addInspector({ id: INSPECTOR_ID, label: "Attaform", app });
55
+ api.addTimelineLayer({ id: TIMELINE_LAYER_ID, label: "Attaform", color: 6000111 });
56
+ function refreshTree() {
57
+ api.sendInspectorTree(INSPECTOR_ID);
58
+ }
59
+ function refreshState() {
60
+ api.sendInspectorState(INSPECTOR_ID);
61
+ }
62
+ function subscribeForm(state) {
63
+ if (subscriberUnsubs.has(state.formKey)) return;
64
+ const unsubChange = state.onFormChange(() => {
65
+ refreshState();
66
+ api.addTimelineEvent({
67
+ layerId: TIMELINE_LAYER_ID,
68
+ event: {
69
+ time: Date.now(),
70
+ title: "form.change",
71
+ subtitle: state.formKey,
72
+ // Redact sensitive-named leaves before they land in the
73
+ // timeline event log — events accumulate for the whole
74
+ // session and a screen-share / paired-debugging session
75
+ // would otherwise expose any password / token / etc. the
76
+ // user typed since DevTools was opened.
77
+ data: { form: redactSensitiveLeaves(state.form.value) }
78
+ }
79
+ });
80
+ });
81
+ const unsubSubmit = state.onSubmitSuccess(() => {
82
+ api.addTimelineEvent({
83
+ layerId: TIMELINE_LAYER_ID,
84
+ event: {
85
+ time: Date.now(),
86
+ title: "submit.success",
87
+ subtitle: state.formKey,
88
+ data: { form: redactSensitiveLeaves(state.form.value) }
89
+ }
90
+ });
91
+ });
92
+ const unsubReset = state.onReset(() => {
93
+ refreshState();
94
+ api.addTimelineEvent({
95
+ layerId: TIMELINE_LAYER_ID,
96
+ event: {
97
+ time: Date.now(),
98
+ title: "reset",
99
+ subtitle: state.formKey
100
+ }
101
+ });
102
+ });
103
+ subscriberUnsubs.set(state.formKey, () => {
104
+ unsubChange();
105
+ unsubSubmit();
106
+ unsubReset();
107
+ });
108
+ }
109
+ function syncForms() {
110
+ for (const [, state] of registry.forms) {
111
+ subscribeForm(state);
112
+ }
113
+ for (const [formKey, unsub] of subscriberUnsubs) {
114
+ if (!registry.forms.has(formKey)) {
115
+ unsub();
116
+ subscriberUnsubs.delete(formKey);
117
+ }
118
+ }
119
+ }
120
+ api.on.getInspectorTree((payload) => {
121
+ if (payload.inspectorId !== INSPECTOR_ID) return;
122
+ syncForms();
123
+ payload.rootNodes = [...registry.forms.keys()].map((key) => ({
124
+ id: `form:${key}`,
125
+ label: key,
126
+ tags: []
127
+ }));
128
+ });
129
+ api.on.getInspectorState((payload) => {
130
+ if (payload.inspectorId !== INSPECTOR_ID) return;
131
+ if (!payload.nodeId.startsWith("form:")) return;
132
+ const formKey = payload.nodeId.slice("form:".length);
133
+ const state = registry.forms.get(formKey);
134
+ if (state === void 0) return;
135
+ payload.state["Form value"] = [
136
+ { key: "form", value: redactSensitiveLeaves(state.form.value), editable: true }
137
+ ];
138
+ payload.state["Schema Errors"] = [
139
+ ...[...state.schemaErrors.entries()].map(([k, v]) => ({
140
+ key: String(k),
141
+ value: v
142
+ }))
143
+ ];
144
+ payload.state["User Errors"] = [
145
+ ...[...state.userErrors.entries()].map(([k, v]) => ({
146
+ key: String(k),
147
+ value: v
148
+ }))
149
+ ];
150
+ payload.state["Aggregates"] = [
151
+ { key: "isSubmitting", value: state.isSubmitting.value },
152
+ { key: "submitCount", value: state.submitCount.value },
153
+ { key: "submitError", value: state.submitError.value },
154
+ { key: "activeValidations", value: state.activeValidations.value }
155
+ ];
156
+ });
157
+ api.on.editInspectorState((payload) => {
158
+ if (payload.inspectorId !== INSPECTOR_ID) return;
159
+ if (!payload.nodeId.startsWith("form:")) return;
160
+ const formKey = payload.nodeId.slice("form:".length);
161
+ const state = registry.forms.get(formKey);
162
+ if (state === void 0) return;
163
+ if (payload.path.length < 3) return;
164
+ const section = payload.path[0];
165
+ if (section !== "Form value") return;
166
+ const segments = payload.path.slice(2);
167
+ const { segments: canonicalPath, key: canonicalKey } = paths.canonicalizePath(segments);
168
+ if (sensitiveNames.isSensitivePath([...canonicalPath])) return;
169
+ state.setValueAtPath(canonicalPath, payload.state.value, {
170
+ persist: state.persistOptIns.hasAnyOptInForPath(canonicalKey)
171
+ });
172
+ refreshState();
173
+ });
174
+ syncForms();
175
+ refreshTree();
176
+ }
177
+
178
+ exports.setupAttaformDevtools = setupAttaformDevtools;
179
+ //# sourceMappingURL=devtools.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools.cjs","sources":["../../src/runtime/core/devtools.ts"],"sourcesContent":["import type { App } from 'vue'\nimport type { FormStore } from './create-form-store'\nimport type { AttaformRegistry } from './registry'\nimport type { GenericForm } from '../types/types-core'\nimport type { FormKey } from '../types/types-api'\nimport { canonicalizePath } from './paths'\nimport { isSensitivePath, segmentMatchesSensitive } from './persistence/sensitive-names'\n\n/**\n * Vue DevTools plugin wiring for attaform. Lazy-imported by\n * `createAttaform` under dev-mode guards so the production\n * bundle tree-shakes it out entirely.\n *\n * Registers:\n * - An inspector (per-app) that lists every registered form, with\n * nodes for form value / errors / aggregates / history.\n * - A timeline layer that emits events on submit start/success/\n * failure, reset, undo, redo, and form mutations.\n * - State editing — modifying a leaf inside the inspector tree\n * pushes through `state.setValueAtPath`, mutating the form.\n *\n * Tolerant of missing `@vue/devtools-api` — the peer dep is marked\n * optional. If the import fails, `setupAttaformDevtools` silently\n * no-ops so production builds / users without DevTools installed\n * don't see errors.\n */\n\nconst INSPECTOR_ID = 'attaform'\nconst TIMELINE_LAYER_ID = 'attaform:events'\n\nconst REDACTED = '[redacted]'\n\n/**\n * Walk `value` and replace any leaf whose enclosing path matches the\n * sensitive-name heuristic with the string `'[redacted]'`. Returns a\n * new tree (no mutation of the input). Object keys + array indices\n * are preserved; only the leaf payloads change.\n *\n * Applied to BOTH the DevTools timeline events and the inspector\n * `Form value` panel — leaks via either surface are treatable as\n * \"any developer with the panel open during user testing can read\n * a customer's password,\" which is exactly the failure mode the\n * sensitive-name guard exists to prevent on the storage side.\n *\n * Leaves whose path doesn't match a pattern pass through untouched.\n * `acknowledgeSensitive: true` on persistence does NOT bypass this —\n * if the consumer opted into persisting the value, they still\n * shouldn't see it in DevTools timelines that grow unbounded.\n *\n * Implementation note: tracks an `inSensitiveSubtree` flag through\n * the recursion instead of allocating a fresh path array per node\n * + calling `isSensitivePath` per leaf. Once any ancestor segment\n * matches the heuristic, the flag stays set for every descendant —\n * the leaf simply returns `REDACTED` without re-scanning the path.\n * For a 100-leaf form: ~100 path allocations + ~100 full-path regex\n * sweeps → 0 path allocations + ~100 single-segment regex sweeps,\n * with whole-subtree short-circuit when sensitive ancestors are\n * found early.\n */\nfunction redactSensitiveLeaves(value: unknown): unknown {\n return redactImpl(value, false)\n}\n\nfunction redactImpl(value: unknown, inSensitiveSubtree: boolean): unknown {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') {\n return inSensitiveSubtree ? REDACTED : value\n }\n if (Array.isArray(value)) {\n // Numeric segments never match the sensitive-name heuristic\n // (segmentMatchesSensitive rejects non-string segments), so the\n // flag passes through unchanged when descending into arrays.\n return value.map((item) => redactImpl(item, inSensitiveSubtree))\n }\n // Non-plain object (Map / Set / Date / class instance) — redact\n // wholesale if we're already in a sensitive subtree; otherwise pass\n // through. DevTools rendering of these is already heuristic, so we\n // don't try to descend into them.\n if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {\n return inSensitiveSubtree ? REDACTED : value\n }\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const childSensitive = inSensitiveSubtree || segmentMatchesSensitive(key)\n out[key] = redactImpl((value as Record<string, unknown>)[key], childSensitive)\n }\n return out\n}\n\ntype UnsafeDevtoolsApi = {\n addInspector(opts: { id: string; label: string; icon?: string; app: App }): void\n addTimelineLayer(opts: { id: string; label: string; color: number }): void\n sendInspectorTree(inspectorId: string): void\n sendInspectorState(inspectorId: string): void\n addTimelineEvent(payload: {\n layerId: string\n event: {\n time: number\n title: string\n subtitle?: string\n data?: Record<string, unknown>\n groupId?: string | number\n }\n }): void\n on: {\n getInspectorTree(\n handler: (payload: {\n inspectorId: string\n filter: string\n rootNodes: Array<{ id: string; label: string; tags?: unknown[] }>\n }) => void\n ): void\n getInspectorState(\n handler: (payload: {\n inspectorId: string\n nodeId: string\n state: Record<string, Array<{ key: string; value: unknown; editable?: boolean }>>\n }) => void\n ): void\n editInspectorState(\n handler: (payload: {\n inspectorId: string\n nodeId: string\n path: string[]\n state: { value: unknown; newKey?: string | null; remove?: boolean }\n }) => void\n ): void\n }\n}\n\ntype SetupDevtoolsPluginFn = (\n descriptor: {\n id: string\n label: string\n packageName?: string\n homepage?: string\n componentStateTypes?: string[]\n app: App\n },\n setup: (api: UnsafeDevtoolsApi) => void\n) => void\n\n/**\n * Install the DevTools plugin for the given Vue app + registry. Safe\n * to call in production — if `@vue/devtools-api` isn't installed, the\n * dynamic import fails and we log nothing. Returns `true` when\n * DevTools was wired successfully, `false` otherwise — useful for\n * tests.\n */\nexport async function setupAttaformDevtools(\n app: App,\n registry: AttaformRegistry\n): Promise<boolean> {\n let mod: { setupDevtoolsPlugin?: SetupDevtoolsPluginFn }\n try {\n mod = (await import('@vue/devtools-api')) as {\n setupDevtoolsPlugin?: SetupDevtoolsPluginFn\n }\n } catch {\n // Peer dep not installed — silently skip. Production builds pass\n // `{ devtools: false }` explicitly, but this catch covers the\n // \"dev without the peer dep\" case without a noisy warning.\n return false\n }\n const setupDevtoolsPlugin = mod.setupDevtoolsPlugin\n if (typeof setupDevtoolsPlugin !== 'function') return false\n\n setupDevtoolsPlugin(\n {\n id: INSPECTOR_ID,\n label: 'Attaform',\n packageName: 'attaform',\n homepage: 'https://github.com/attaform/attaform',\n app,\n componentStateTypes: ['Attaform form'],\n },\n (api) => wire(api, app, registry)\n )\n return true\n}\n\nfunction wire(api: UnsafeDevtoolsApi, app: App, registry: AttaformRegistry): void {\n // Per-form subscriber bookkeeping — we keep the unsubscribers so\n // the registry's eviction path can detach them when a form is\n // disposed. Using a Map keyed by FormKey mirrors the registry.\n const subscriberUnsubs = new Map<FormKey, () => void>()\n\n api.addInspector({ id: INSPECTOR_ID, label: 'Attaform', app })\n api.addTimelineLayer({ id: TIMELINE_LAYER_ID, label: 'Attaform', color: 0x5b8def })\n\n function refreshTree(): void {\n api.sendInspectorTree(INSPECTOR_ID)\n }\n\n function refreshState(): void {\n api.sendInspectorState(INSPECTOR_ID)\n }\n\n function subscribeForm(state: FormStore<GenericForm>): void {\n if (subscriberUnsubs.has(state.formKey)) return\n const unsubChange = state.onFormChange(() => {\n refreshState()\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'form.change',\n subtitle: state.formKey,\n // Redact sensitive-named leaves before they land in the\n // timeline event log — events accumulate for the whole\n // session and a screen-share / paired-debugging session\n // would otherwise expose any password / token / etc. the\n // user typed since DevTools was opened.\n data: { form: redactSensitiveLeaves(state.form.value) as Record<string, unknown> },\n },\n })\n })\n const unsubSubmit = state.onSubmitSuccess(() => {\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'submit.success',\n subtitle: state.formKey,\n data: { form: redactSensitiveLeaves(state.form.value) as Record<string, unknown> },\n },\n })\n })\n const unsubReset = state.onReset(() => {\n refreshState()\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'reset',\n subtitle: state.formKey,\n },\n })\n })\n subscriberUnsubs.set(state.formKey, () => {\n unsubChange()\n unsubSubmit()\n unsubReset()\n })\n }\n\n // Subscribe all currently-registered forms + register as they're\n // added. The registry's `forms` Map is shallowReactive — we poll\n // once per render on refresh; for live change detection, each\n // useForm call that adds a new form triggers a tree/state refresh\n // via the form's own onFormChange emission on the first\n // applyFormReplacement.\n function syncForms(): void {\n for (const [, state] of registry.forms) {\n subscribeForm(state)\n }\n // Drop subscribers for forms that were evicted.\n for (const [formKey, unsub] of subscriberUnsubs) {\n if (!registry.forms.has(formKey)) {\n unsub()\n subscriberUnsubs.delete(formKey)\n }\n }\n }\n\n api.on.getInspectorTree((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n syncForms()\n payload.rootNodes = [...registry.forms.keys()].map((key) => ({\n id: `form:${key}`,\n label: key,\n tags: [],\n }))\n })\n\n api.on.getInspectorState((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n if (!payload.nodeId.startsWith('form:')) return\n const formKey = payload.nodeId.slice('form:'.length)\n const state = registry.forms.get(formKey)\n if (state === undefined) return\n // Redact sensitive-named leaves in the inspector panel for the\n // same reason as the timeline events: a screen-share with an\n // open DevTools panel shouldn't expose passwords / tokens.\n // Editing stays enabled at the section level — the editInspector\n // handler refuses sensitive-path edits at write time so a dev\n // can't accidentally write the literal string `'[redacted]'` over\n // a real value.\n payload.state['Form value'] = [\n { key: 'form', value: redactSensitiveLeaves(state.form.value), editable: true },\n ]\n // Schema-driven and user-injected errors land in separate inspector\n // sections so devs can see the source distinction at a glance — a\n // user-injected entry surviving a successful submit, or a schema\n // entry that should have cleared after a value fix, are immediately\n // visible without cross-referencing call sites.\n payload.state['Schema Errors'] = [\n ...[...state.schemaErrors.entries()].map(([k, v]) => ({\n key: String(k),\n value: v as unknown,\n })),\n ]\n payload.state['User Errors'] = [\n ...[...state.userErrors.entries()].map(([k, v]) => ({\n key: String(k),\n value: v as unknown,\n })),\n ]\n payload.state['Aggregates'] = [\n { key: 'isSubmitting', value: state.isSubmitting.value },\n { key: 'submitCount', value: state.submitCount.value },\n { key: 'submitError', value: state.submitError.value },\n { key: 'activeValidations', value: state.activeValidations.value },\n ]\n })\n\n api.on.editInspectorState((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n if (!payload.nodeId.startsWith('form:')) return\n const formKey = payload.nodeId.slice('form:'.length)\n const state = registry.forms.get(formKey)\n if (state === undefined) return\n // payload.path is `['Form value', 'form', ...pathSegments]` — the\n // first two segments are the inspector section + key, the rest is\n // the target form path the user edited. Pass the segment array\n // directly to `canonicalizePath`: join('.') would collapse a\n // literal-dot field key (`{\"user.email\": ...}`) into two segments,\n // writing to the wrong leaf.\n if (payload.path.length < 3) return\n const section = payload.path[0]\n if (section !== 'Form value') return\n const segments = payload.path.slice(2)\n const { segments: canonicalPath, key: canonicalKey } = canonicalizePath(segments)\n // Refuse edits on sensitive-named paths. The inspector renders\n // them as `'[redacted]'`, so a dev who confirms the field would\n // overwrite the real value with the literal masked string. Edits\n // to sensitive paths must go through the bound input element.\n if (isSensitivePath([...canonicalPath])) return\n // A devtools edit on a path that any element has opted in to should\n // persist (matches the user's expectation: editing via the inspector\n // should be indistinguishable from typing into the bound input).\n // No opt-in for this path → no write.\n state.setValueAtPath(canonicalPath, payload.state.value, {\n persist: state.persistOptIns.hasAnyOptInForPath(canonicalKey),\n })\n refreshState()\n })\n\n // Initial sync so existing forms show up.\n syncForms()\n refreshTree()\n}\n"],"names":["segmentMatchesSensitive","canonicalizePath","isSensitivePath"],"mappings":"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"}
@@ -0,0 +1,177 @@
1
+ import { c as canonicalizePath } from '../shared/attaform.Cc93zNzD.mjs';
2
+ import { i as isSensitivePath, s as segmentMatchesSensitive } from '../shared/attaform.al_rpt7_.mjs';
3
+
4
+ const INSPECTOR_ID = "attaform";
5
+ const TIMELINE_LAYER_ID = "attaform:events";
6
+ const REDACTED = "[redacted]";
7
+ function redactSensitiveLeaves(value) {
8
+ return redactImpl(value, false);
9
+ }
10
+ function redactImpl(value, inSensitiveSubtree) {
11
+ if (value === null || value === void 0) return value;
12
+ if (typeof value !== "object") {
13
+ return inSensitiveSubtree ? REDACTED : value;
14
+ }
15
+ if (Array.isArray(value)) {
16
+ return value.map((item) => redactImpl(item, inSensitiveSubtree));
17
+ }
18
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
19
+ return inSensitiveSubtree ? REDACTED : value;
20
+ }
21
+ const out = {};
22
+ for (const key of Object.keys(value)) {
23
+ const childSensitive = inSensitiveSubtree || segmentMatchesSensitive(key);
24
+ out[key] = redactImpl(value[key], childSensitive);
25
+ }
26
+ return out;
27
+ }
28
+ async function setupAttaformDevtools(app, registry) {
29
+ let mod;
30
+ try {
31
+ mod = await import('@vue/devtools-api');
32
+ } catch {
33
+ return false;
34
+ }
35
+ const setupDevtoolsPlugin = mod.setupDevtoolsPlugin;
36
+ if (typeof setupDevtoolsPlugin !== "function") return false;
37
+ setupDevtoolsPlugin(
38
+ {
39
+ id: INSPECTOR_ID,
40
+ label: "Attaform",
41
+ packageName: "attaform",
42
+ homepage: "https://github.com/attaform/attaform",
43
+ app,
44
+ componentStateTypes: ["Attaform form"]
45
+ },
46
+ (api) => wire(api, app, registry)
47
+ );
48
+ return true;
49
+ }
50
+ function wire(api, app, registry) {
51
+ const subscriberUnsubs = /* @__PURE__ */ new Map();
52
+ api.addInspector({ id: INSPECTOR_ID, label: "Attaform", app });
53
+ api.addTimelineLayer({ id: TIMELINE_LAYER_ID, label: "Attaform", color: 6000111 });
54
+ function refreshTree() {
55
+ api.sendInspectorTree(INSPECTOR_ID);
56
+ }
57
+ function refreshState() {
58
+ api.sendInspectorState(INSPECTOR_ID);
59
+ }
60
+ function subscribeForm(state) {
61
+ if (subscriberUnsubs.has(state.formKey)) return;
62
+ const unsubChange = state.onFormChange(() => {
63
+ refreshState();
64
+ api.addTimelineEvent({
65
+ layerId: TIMELINE_LAYER_ID,
66
+ event: {
67
+ time: Date.now(),
68
+ title: "form.change",
69
+ subtitle: state.formKey,
70
+ // Redact sensitive-named leaves before they land in the
71
+ // timeline event log — events accumulate for the whole
72
+ // session and a screen-share / paired-debugging session
73
+ // would otherwise expose any password / token / etc. the
74
+ // user typed since DevTools was opened.
75
+ data: { form: redactSensitiveLeaves(state.form.value) }
76
+ }
77
+ });
78
+ });
79
+ const unsubSubmit = state.onSubmitSuccess(() => {
80
+ api.addTimelineEvent({
81
+ layerId: TIMELINE_LAYER_ID,
82
+ event: {
83
+ time: Date.now(),
84
+ title: "submit.success",
85
+ subtitle: state.formKey,
86
+ data: { form: redactSensitiveLeaves(state.form.value) }
87
+ }
88
+ });
89
+ });
90
+ const unsubReset = state.onReset(() => {
91
+ refreshState();
92
+ api.addTimelineEvent({
93
+ layerId: TIMELINE_LAYER_ID,
94
+ event: {
95
+ time: Date.now(),
96
+ title: "reset",
97
+ subtitle: state.formKey
98
+ }
99
+ });
100
+ });
101
+ subscriberUnsubs.set(state.formKey, () => {
102
+ unsubChange();
103
+ unsubSubmit();
104
+ unsubReset();
105
+ });
106
+ }
107
+ function syncForms() {
108
+ for (const [, state] of registry.forms) {
109
+ subscribeForm(state);
110
+ }
111
+ for (const [formKey, unsub] of subscriberUnsubs) {
112
+ if (!registry.forms.has(formKey)) {
113
+ unsub();
114
+ subscriberUnsubs.delete(formKey);
115
+ }
116
+ }
117
+ }
118
+ api.on.getInspectorTree((payload) => {
119
+ if (payload.inspectorId !== INSPECTOR_ID) return;
120
+ syncForms();
121
+ payload.rootNodes = [...registry.forms.keys()].map((key) => ({
122
+ id: `form:${key}`,
123
+ label: key,
124
+ tags: []
125
+ }));
126
+ });
127
+ api.on.getInspectorState((payload) => {
128
+ if (payload.inspectorId !== INSPECTOR_ID) return;
129
+ if (!payload.nodeId.startsWith("form:")) return;
130
+ const formKey = payload.nodeId.slice("form:".length);
131
+ const state = registry.forms.get(formKey);
132
+ if (state === void 0) return;
133
+ payload.state["Form value"] = [
134
+ { key: "form", value: redactSensitiveLeaves(state.form.value), editable: true }
135
+ ];
136
+ payload.state["Schema Errors"] = [
137
+ ...[...state.schemaErrors.entries()].map(([k, v]) => ({
138
+ key: String(k),
139
+ value: v
140
+ }))
141
+ ];
142
+ payload.state["User Errors"] = [
143
+ ...[...state.userErrors.entries()].map(([k, v]) => ({
144
+ key: String(k),
145
+ value: v
146
+ }))
147
+ ];
148
+ payload.state["Aggregates"] = [
149
+ { key: "isSubmitting", value: state.isSubmitting.value },
150
+ { key: "submitCount", value: state.submitCount.value },
151
+ { key: "submitError", value: state.submitError.value },
152
+ { key: "activeValidations", value: state.activeValidations.value }
153
+ ];
154
+ });
155
+ api.on.editInspectorState((payload) => {
156
+ if (payload.inspectorId !== INSPECTOR_ID) return;
157
+ if (!payload.nodeId.startsWith("form:")) return;
158
+ const formKey = payload.nodeId.slice("form:".length);
159
+ const state = registry.forms.get(formKey);
160
+ if (state === void 0) return;
161
+ if (payload.path.length < 3) return;
162
+ const section = payload.path[0];
163
+ if (section !== "Form value") return;
164
+ const segments = payload.path.slice(2);
165
+ const { segments: canonicalPath, key: canonicalKey } = canonicalizePath(segments);
166
+ if (isSensitivePath([...canonicalPath])) return;
167
+ state.setValueAtPath(canonicalPath, payload.state.value, {
168
+ persist: state.persistOptIns.hasAnyOptInForPath(canonicalKey)
169
+ });
170
+ refreshState();
171
+ });
172
+ syncForms();
173
+ refreshTree();
174
+ }
175
+
176
+ export { setupAttaformDevtools };
177
+ //# sourceMappingURL=devtools.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools.mjs","sources":["../../src/runtime/core/devtools.ts"],"sourcesContent":["import type { App } from 'vue'\nimport type { FormStore } from './create-form-store'\nimport type { AttaformRegistry } from './registry'\nimport type { GenericForm } from '../types/types-core'\nimport type { FormKey } from '../types/types-api'\nimport { canonicalizePath } from './paths'\nimport { isSensitivePath, segmentMatchesSensitive } from './persistence/sensitive-names'\n\n/**\n * Vue DevTools plugin wiring for attaform. Lazy-imported by\n * `createAttaform` under dev-mode guards so the production\n * bundle tree-shakes it out entirely.\n *\n * Registers:\n * - An inspector (per-app) that lists every registered form, with\n * nodes for form value / errors / aggregates / history.\n * - A timeline layer that emits events on submit start/success/\n * failure, reset, undo, redo, and form mutations.\n * - State editing — modifying a leaf inside the inspector tree\n * pushes through `state.setValueAtPath`, mutating the form.\n *\n * Tolerant of missing `@vue/devtools-api` — the peer dep is marked\n * optional. If the import fails, `setupAttaformDevtools` silently\n * no-ops so production builds / users without DevTools installed\n * don't see errors.\n */\n\nconst INSPECTOR_ID = 'attaform'\nconst TIMELINE_LAYER_ID = 'attaform:events'\n\nconst REDACTED = '[redacted]'\n\n/**\n * Walk `value` and replace any leaf whose enclosing path matches the\n * sensitive-name heuristic with the string `'[redacted]'`. Returns a\n * new tree (no mutation of the input). Object keys + array indices\n * are preserved; only the leaf payloads change.\n *\n * Applied to BOTH the DevTools timeline events and the inspector\n * `Form value` panel — leaks via either surface are treatable as\n * \"any developer with the panel open during user testing can read\n * a customer's password,\" which is exactly the failure mode the\n * sensitive-name guard exists to prevent on the storage side.\n *\n * Leaves whose path doesn't match a pattern pass through untouched.\n * `acknowledgeSensitive: true` on persistence does NOT bypass this —\n * if the consumer opted into persisting the value, they still\n * shouldn't see it in DevTools timelines that grow unbounded.\n *\n * Implementation note: tracks an `inSensitiveSubtree` flag through\n * the recursion instead of allocating a fresh path array per node\n * + calling `isSensitivePath` per leaf. Once any ancestor segment\n * matches the heuristic, the flag stays set for every descendant —\n * the leaf simply returns `REDACTED` without re-scanning the path.\n * For a 100-leaf form: ~100 path allocations + ~100 full-path regex\n * sweeps → 0 path allocations + ~100 single-segment regex sweeps,\n * with whole-subtree short-circuit when sensitive ancestors are\n * found early.\n */\nfunction redactSensitiveLeaves(value: unknown): unknown {\n return redactImpl(value, false)\n}\n\nfunction redactImpl(value: unknown, inSensitiveSubtree: boolean): unknown {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') {\n return inSensitiveSubtree ? REDACTED : value\n }\n if (Array.isArray(value)) {\n // Numeric segments never match the sensitive-name heuristic\n // (segmentMatchesSensitive rejects non-string segments), so the\n // flag passes through unchanged when descending into arrays.\n return value.map((item) => redactImpl(item, inSensitiveSubtree))\n }\n // Non-plain object (Map / Set / Date / class instance) — redact\n // wholesale if we're already in a sensitive subtree; otherwise pass\n // through. DevTools rendering of these is already heuristic, so we\n // don't try to descend into them.\n if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {\n return inSensitiveSubtree ? REDACTED : value\n }\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const childSensitive = inSensitiveSubtree || segmentMatchesSensitive(key)\n out[key] = redactImpl((value as Record<string, unknown>)[key], childSensitive)\n }\n return out\n}\n\ntype UnsafeDevtoolsApi = {\n addInspector(opts: { id: string; label: string; icon?: string; app: App }): void\n addTimelineLayer(opts: { id: string; label: string; color: number }): void\n sendInspectorTree(inspectorId: string): void\n sendInspectorState(inspectorId: string): void\n addTimelineEvent(payload: {\n layerId: string\n event: {\n time: number\n title: string\n subtitle?: string\n data?: Record<string, unknown>\n groupId?: string | number\n }\n }): void\n on: {\n getInspectorTree(\n handler: (payload: {\n inspectorId: string\n filter: string\n rootNodes: Array<{ id: string; label: string; tags?: unknown[] }>\n }) => void\n ): void\n getInspectorState(\n handler: (payload: {\n inspectorId: string\n nodeId: string\n state: Record<string, Array<{ key: string; value: unknown; editable?: boolean }>>\n }) => void\n ): void\n editInspectorState(\n handler: (payload: {\n inspectorId: string\n nodeId: string\n path: string[]\n state: { value: unknown; newKey?: string | null; remove?: boolean }\n }) => void\n ): void\n }\n}\n\ntype SetupDevtoolsPluginFn = (\n descriptor: {\n id: string\n label: string\n packageName?: string\n homepage?: string\n componentStateTypes?: string[]\n app: App\n },\n setup: (api: UnsafeDevtoolsApi) => void\n) => void\n\n/**\n * Install the DevTools plugin for the given Vue app + registry. Safe\n * to call in production — if `@vue/devtools-api` isn't installed, the\n * dynamic import fails and we log nothing. Returns `true` when\n * DevTools was wired successfully, `false` otherwise — useful for\n * tests.\n */\nexport async function setupAttaformDevtools(\n app: App,\n registry: AttaformRegistry\n): Promise<boolean> {\n let mod: { setupDevtoolsPlugin?: SetupDevtoolsPluginFn }\n try {\n mod = (await import('@vue/devtools-api')) as {\n setupDevtoolsPlugin?: SetupDevtoolsPluginFn\n }\n } catch {\n // Peer dep not installed — silently skip. Production builds pass\n // `{ devtools: false }` explicitly, but this catch covers the\n // \"dev without the peer dep\" case without a noisy warning.\n return false\n }\n const setupDevtoolsPlugin = mod.setupDevtoolsPlugin\n if (typeof setupDevtoolsPlugin !== 'function') return false\n\n setupDevtoolsPlugin(\n {\n id: INSPECTOR_ID,\n label: 'Attaform',\n packageName: 'attaform',\n homepage: 'https://github.com/attaform/attaform',\n app,\n componentStateTypes: ['Attaform form'],\n },\n (api) => wire(api, app, registry)\n )\n return true\n}\n\nfunction wire(api: UnsafeDevtoolsApi, app: App, registry: AttaformRegistry): void {\n // Per-form subscriber bookkeeping — we keep the unsubscribers so\n // the registry's eviction path can detach them when a form is\n // disposed. Using a Map keyed by FormKey mirrors the registry.\n const subscriberUnsubs = new Map<FormKey, () => void>()\n\n api.addInspector({ id: INSPECTOR_ID, label: 'Attaform', app })\n api.addTimelineLayer({ id: TIMELINE_LAYER_ID, label: 'Attaform', color: 0x5b8def })\n\n function refreshTree(): void {\n api.sendInspectorTree(INSPECTOR_ID)\n }\n\n function refreshState(): void {\n api.sendInspectorState(INSPECTOR_ID)\n }\n\n function subscribeForm(state: FormStore<GenericForm>): void {\n if (subscriberUnsubs.has(state.formKey)) return\n const unsubChange = state.onFormChange(() => {\n refreshState()\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'form.change',\n subtitle: state.formKey,\n // Redact sensitive-named leaves before they land in the\n // timeline event log — events accumulate for the whole\n // session and a screen-share / paired-debugging session\n // would otherwise expose any password / token / etc. the\n // user typed since DevTools was opened.\n data: { form: redactSensitiveLeaves(state.form.value) as Record<string, unknown> },\n },\n })\n })\n const unsubSubmit = state.onSubmitSuccess(() => {\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'submit.success',\n subtitle: state.formKey,\n data: { form: redactSensitiveLeaves(state.form.value) as Record<string, unknown> },\n },\n })\n })\n const unsubReset = state.onReset(() => {\n refreshState()\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'reset',\n subtitle: state.formKey,\n },\n })\n })\n subscriberUnsubs.set(state.formKey, () => {\n unsubChange()\n unsubSubmit()\n unsubReset()\n })\n }\n\n // Subscribe all currently-registered forms + register as they're\n // added. The registry's `forms` Map is shallowReactive — we poll\n // once per render on refresh; for live change detection, each\n // useForm call that adds a new form triggers a tree/state refresh\n // via the form's own onFormChange emission on the first\n // applyFormReplacement.\n function syncForms(): void {\n for (const [, state] of registry.forms) {\n subscribeForm(state)\n }\n // Drop subscribers for forms that were evicted.\n for (const [formKey, unsub] of subscriberUnsubs) {\n if (!registry.forms.has(formKey)) {\n unsub()\n subscriberUnsubs.delete(formKey)\n }\n }\n }\n\n api.on.getInspectorTree((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n syncForms()\n payload.rootNodes = [...registry.forms.keys()].map((key) => ({\n id: `form:${key}`,\n label: key,\n tags: [],\n }))\n })\n\n api.on.getInspectorState((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n if (!payload.nodeId.startsWith('form:')) return\n const formKey = payload.nodeId.slice('form:'.length)\n const state = registry.forms.get(formKey)\n if (state === undefined) return\n // Redact sensitive-named leaves in the inspector panel for the\n // same reason as the timeline events: a screen-share with an\n // open DevTools panel shouldn't expose passwords / tokens.\n // Editing stays enabled at the section level — the editInspector\n // handler refuses sensitive-path edits at write time so a dev\n // can't accidentally write the literal string `'[redacted]'` over\n // a real value.\n payload.state['Form value'] = [\n { key: 'form', value: redactSensitiveLeaves(state.form.value), editable: true },\n ]\n // Schema-driven and user-injected errors land in separate inspector\n // sections so devs can see the source distinction at a glance — a\n // user-injected entry surviving a successful submit, or a schema\n // entry that should have cleared after a value fix, are immediately\n // visible without cross-referencing call sites.\n payload.state['Schema Errors'] = [\n ...[...state.schemaErrors.entries()].map(([k, v]) => ({\n key: String(k),\n value: v as unknown,\n })),\n ]\n payload.state['User Errors'] = [\n ...[...state.userErrors.entries()].map(([k, v]) => ({\n key: String(k),\n value: v as unknown,\n })),\n ]\n payload.state['Aggregates'] = [\n { key: 'isSubmitting', value: state.isSubmitting.value },\n { key: 'submitCount', value: state.submitCount.value },\n { key: 'submitError', value: state.submitError.value },\n { key: 'activeValidations', value: state.activeValidations.value },\n ]\n })\n\n api.on.editInspectorState((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n if (!payload.nodeId.startsWith('form:')) return\n const formKey = payload.nodeId.slice('form:'.length)\n const state = registry.forms.get(formKey)\n if (state === undefined) return\n // payload.path is `['Form value', 'form', ...pathSegments]` — the\n // first two segments are the inspector section + key, the rest is\n // the target form path the user edited. Pass the segment array\n // directly to `canonicalizePath`: join('.') would collapse a\n // literal-dot field key (`{\"user.email\": ...}`) into two segments,\n // writing to the wrong leaf.\n if (payload.path.length < 3) return\n const section = payload.path[0]\n if (section !== 'Form value') return\n const segments = payload.path.slice(2)\n const { segments: canonicalPath, key: canonicalKey } = canonicalizePath(segments)\n // Refuse edits on sensitive-named paths. The inspector renders\n // them as `'[redacted]'`, so a dev who confirms the field would\n // overwrite the real value with the literal masked string. Edits\n // to sensitive paths must go through the bound input element.\n if (isSensitivePath([...canonicalPath])) return\n // A devtools edit on a path that any element has opted in to should\n // persist (matches the user's expectation: editing via the inspector\n // should be indistinguishable from typing into the bound input).\n // No opt-in for this path → no write.\n state.setValueAtPath(canonicalPath, payload.state.value, {\n persist: state.persistOptIns.hasAnyOptInForPath(canonicalKey),\n })\n refreshState()\n })\n\n // Initial sync so existing forms show up.\n syncForms()\n refreshTree()\n}\n"],"names":[],"mappings":"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"}