attaform 0.30.1 → 0.31.1

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 (55) hide show
  1. package/dist/abstract.d.mts +5 -5
  2. package/dist/abstract.mjs +4 -4
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/devtools.mjs +3 -3
  5. package/dist/dev/directive.mjs +1 -1
  6. package/dist/dev/history.mjs +15 -2
  7. package/dist/dev/index.mjs +5 -5
  8. package/dist/dev/runtime/plugins/attaform.mjs +1 -1
  9. package/dist/dev/shared/{attaform-dev.zlvDr0WB.mjs → attaform-dev.04aupFBW.mjs} +3 -2
  10. package/dist/dev/shared/{attaform-dev.DxHdkkPG.mjs → attaform-dev.3uTCYwy8.mjs} +770 -254
  11. package/dist/dev/shared/{attaform-dev.BJK1kqHY.mjs → attaform-dev.BpVGFSFa.mjs} +88 -115
  12. package/dist/dev/shared/{attaform-dev.Chm4D6SJ.mjs → attaform-dev.Bu6wgrA5.mjs} +3 -3
  13. package/dist/dev/shared/{attaform-dev.BMQ3GcgD.mjs → attaform-dev.Cbx0AiQF.mjs} +476 -337
  14. package/dist/dev/shared/attaform-dev.CwYTWyPX.mjs +120 -0
  15. package/dist/dev/shared/{attaform-dev.DuEE4GU1.mjs → attaform-dev.D7lKyM_7.mjs} +4 -4
  16. package/dist/dev/shared/{attaform-dev.CT7MDjPy.mjs → attaform-dev.OceE0X3v.mjs} +154 -229
  17. package/dist/{shared/attaform.DRsANMok.mjs → dev/shared/attaform-dev.PTGM9KE1.mjs} +16 -15
  18. package/dist/{shared/attaform.8mfKZCwH.mjs → dev/shared/attaform-dev.n6n00owJ.mjs} +49 -128
  19. package/dist/dev/zod-v3.mjs +6 -6
  20. package/dist/dev/zod-v4.mjs +6 -6
  21. package/dist/dev/zod.mjs +5 -5
  22. package/dist/directive.d.mts +2 -2
  23. package/dist/directive.mjs +1 -1
  24. package/dist/history.d.mts +1 -1
  25. package/dist/history.mjs +15 -2
  26. package/dist/index.d.mts +16 -55
  27. package/dist/index.mjs +5 -5
  28. package/dist/runtime/components/AttaformDevtoolsPanel.vue +3 -3
  29. package/dist/runtime/plugins/attaform.mjs +1 -1
  30. package/dist/shared/{attaform.CO918TdU.mjs → attaform.B5b-HmhH.mjs} +88 -115
  31. package/dist/shared/{attaform.2FslhB9R.d.mts → attaform.B9PYKlhB.d.mts} +43 -19
  32. package/dist/shared/{attaform.D_f1WgYf.mjs → attaform.BPMOl562.mjs} +458 -319
  33. package/dist/{dev/shared/attaform-dev.CZz4GYZ6.mjs → shared/attaform.BPSqtU7a.mjs} +44 -156
  34. package/dist/shared/{attaform.CdZIEf7_.mjs → attaform.BhtAZysa.mjs} +3 -2
  35. package/dist/shared/{attaform.BcQwgWaq.d.mts → attaform.CYlF4P6X.d.mts} +58 -17
  36. package/dist/shared/{attaform.WiGdIqgx.mjs → attaform.CdA9bfRv.mjs} +3 -3
  37. package/dist/shared/{attaform.BIWicztt.mjs → attaform.DAf4ehIe.mjs} +765 -251
  38. package/dist/shared/{attaform.ZYn2ahzJ.mjs → attaform.DBCbxlyk.mjs} +59 -1
  39. package/dist/shared/{attaform.L0kUn3vL.d.mts → attaform.DZOynI6c.d.mts} +1 -1
  40. package/dist/shared/{attaform.B2iebkPA.mjs → attaform.KEfNRDot.mjs} +4 -4
  41. package/dist/shared/attaform._1Zp3mDG.mjs +96 -0
  42. package/dist/shared/{attaform.Br8EBn5B.d.mts → attaform.i13OEHJF.d.mts} +195 -38
  43. package/dist/{dev/shared/attaform-dev.BUIFcTYf.mjs → shared/attaform.j9xI_ObC.mjs} +16 -15
  44. package/dist/shared/{attaform.D_pt1gOj.mjs → attaform.u6jfHoed.mjs} +154 -229
  45. package/dist/transforms.mjs +1 -1
  46. package/dist/vite.mjs +1 -1
  47. package/dist/zod-v3.d.mts +4 -4
  48. package/dist/zod-v3.mjs +6 -6
  49. package/dist/zod-v4.d.mts +22 -16
  50. package/dist/zod-v4.mjs +6 -6
  51. package/dist/zod.d.mts +4 -4
  52. package/dist/zod.mjs +5 -5
  53. package/package.json +2 -1
  54. package/skills/attaform/SKILL.md +2 -1
  55. package/skills/attaform/references/custom-components.md +4 -0
@@ -1,4 +1,5 @@
1
- import { m as mapSegmentKeys, f as pathsEqual, i as isPathPrefix } from './attaform-dev.zlvDr0WB.mjs';
1
+ import { s as safeAssign, a as safeOwnRead, r as readConsumerIndex, c as consumerKeys, b as readConsumerProp } from './attaform._1Zp3mDG.mjs';
2
+ import { m as mapSegmentKeys, f as pathsEqual, i as isPathPrefix } from './attaform.BhtAZysa.mjs';
2
3
 
3
4
  const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
4
5
  const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
@@ -9,11 +10,6 @@ const DEFAULT_MAX_RECURSION_DEPTH = 64;
9
10
  function normalizeNumericOption(config) {
10
11
  const { value, source, min, defaultValue } = config;
11
12
  if (typeof value !== "number" || !Number.isFinite(value)) {
12
- {
13
- console.warn(
14
- `[attaform] ${source} must be a non-negative finite integer; got ${String(value)}. Falling back to ${String(defaultValue)}.`
15
- );
16
- }
17
13
  return defaultValue;
18
14
  }
19
15
  return Math.max(min, Math.floor(value));
@@ -26,124 +22,6 @@ function pickDefined(bag) {
26
22
  return out;
27
23
  }
28
24
 
29
- const reported = /* @__PURE__ */ new Set() ;
30
- function reportConsumerThrow(site, detail, err) {
31
- const key = detail === void 0 ? site : `${site}|${detail}`;
32
- if (reported === null || reported.has(key)) return;
33
- reported.add(key);
34
- const at = detail === void 0 ? "" : ` at \`${detail}\``;
35
- const advice = {
36
- "default-factory": "a `.default(() => ...)` factory threw. Attaform used `undefined` for that field's initial value.",
37
- "catch-factory": "a `.catch(() => ...)` fallback threw. Attaform used `undefined` for that value.",
38
- "lazy-getter": "a `z.lazy(() => ...)` factory threw. Attaform stopped descending at that node, so paths below it will not resolve.",
39
- "wizard-slot": `a function step slot threw${at}. Attaform dropped that step from the compiled list; the rest of the flow still navigates.`,
40
- "wizard-lazy-slot": `a \`lazy()\` step resolver threw${at}. Attaform dropped that step from the compiled list until the resolver's own dependencies change.`,
41
- "wizard-progress": "a `useWizard({ progress })` override threw. Attaform fell back to its own valid-step ratio.",
42
- "wizard-restore": "a `useWizard({ restore })` callback threw. Attaform read no step from it, so the wizard stays where it is.",
43
- "wizard-persist": "a `useWizard({ persist })` callback threw. Attaform navigated anyway, so the step is live but unrecorded.",
44
- "wizard-default-statuses": "a `useWizard({ defaultStatuses })` factory threw or rejected. Attaform seeded no statuses, the same as omitting the option."
45
- };
46
- console.error(
47
- `[attaform] ${advice[site]} Attaform has nowhere to surface this, so it must not throw; wrap your own try/catch if the failure is recoverable. Original error:`,
48
- err
49
- );
50
- }
51
- function callConsumerFn(fn, fallback, site, detail) {
52
- try {
53
- return fn();
54
- } catch (err) {
55
- reportConsumerThrow(site, detail, err);
56
- return fallback;
57
- }
58
- }
59
- function readConsumerProp(obj, key) {
60
- try {
61
- return obj[key];
62
- } catch {
63
- return void 0;
64
- }
65
- }
66
- function consumerKeys(obj) {
67
- try {
68
- return Object.keys(obj);
69
- } catch {
70
- return [];
71
- }
72
- }
73
- function readConsumerIndex(arr, i) {
74
- try {
75
- return arr[i];
76
- } catch {
77
- return void 0;
78
- }
79
- }
80
- function consumerSymbolKeys(obj) {
81
- try {
82
- return Object.getOwnPropertySymbols(obj);
83
- } catch {
84
- return [];
85
- }
86
- }
87
- function consumerHas(obj, key) {
88
- try {
89
- return key in obj;
90
- } catch {
91
- return false;
92
- }
93
- }
94
-
95
- function safeAssign(target, key, value) {
96
- if (key === "__proto__") {
97
- Object.defineProperty(target, key, {
98
- value,
99
- writable: true,
100
- enumerable: true,
101
- configurable: true
102
- });
103
- return;
104
- }
105
- target[key] = value;
106
- }
107
- const SHADOWED_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
108
- function isShadowedKey(key) {
109
- return SHADOWED_KEYS.has(key);
110
- }
111
- function safeOwnRead(target, key) {
112
- if (isShadowedKey(key)) {
113
- const desc = Object.getOwnPropertyDescriptor(target, key);
114
- if (desc === void 0) return void 0;
115
- return "value" in desc ? desc.value : readConsumerProp(target, key);
116
- }
117
- return readConsumerProp(target, key);
118
- }
119
- function safeOwnHas(target, key) {
120
- try {
121
- return Object.prototype.hasOwnProperty.call(target, key);
122
- } catch {
123
- return false;
124
- }
125
- }
126
- function spreadConsumerRecord(src) {
127
- try {
128
- return { ...src };
129
- } catch {
130
- const out = {};
131
- for (const key of Object.keys(src)) {
132
- safeAssign(out, key, readConsumerProp(src, key));
133
- }
134
- return out;
135
- }
136
- }
137
- function copyConsumerArray(src) {
138
- try {
139
- return src.slice();
140
- } catch {
141
- const out = new Array(src.length);
142
- for (let i = 0; i < src.length; i++) out[i] = readConsumerIndex(src, i);
143
- return out;
144
- }
145
- }
146
-
147
25
  function isDescendable(value) {
148
26
  if (value === null || typeof value !== "object") return false;
149
27
  if (Array.isArray(value)) return true;
@@ -151,6 +29,9 @@ function isDescendable(value) {
151
29
  const proto = Object.getPrototypeOf(value);
152
30
  return proto === null || proto === Object.prototype;
153
31
  }
32
+ function isBarePatch(patch) {
33
+ return patch.kind === "added" ? isDescendable(patch.newValue) : patch.kind === "removed" && isDescendable(patch.oldValue);
34
+ }
154
35
  function appendSegment(prefix, segment) {
155
36
  const next = new Array(prefix.length + 1);
156
37
  for (let i = 0; i < prefix.length; i++) {
@@ -165,11 +46,11 @@ function diffAndApply(oldValue, newValue, prefix, visit) {
165
46
  const oldIsDescendable = isDescendable(oldValue);
166
47
  const newIsDescendable = isDescendable(newValue);
167
48
  if (oldValue === void 0 && newIsDescendable) {
168
- walkNewDescendable(newValue, prefix, visit);
49
+ walkDescendable(newValue, prefix, visit, true);
169
50
  return;
170
51
  }
171
52
  if (oldIsDescendable && newValue === void 0) {
172
- walkOldDescendable(oldValue, prefix, visit);
53
+ walkDescendable(oldValue, prefix, visit, false);
173
54
  return;
174
55
  }
175
56
  if (oldIsDescendable && newIsDescendable) {
@@ -219,50 +100,51 @@ function diffAndApply(oldValue, newValue, prefix, visit) {
219
100
  }
220
101
  visit({ kind: "changed", path: prefix, oldValue, newValue });
221
102
  }
222
- function walkNewDescendable(newValue, prefix, visit) {
223
- if (Array.isArray(newValue)) {
224
- for (let i = 0; i < newValue.length; i++) {
225
- diffAndApply(void 0, readConsumerIndex(newValue, i), appendSegment(prefix, i), visit);
103
+ function walkDescendable(value, prefix, visit, arrived) {
104
+ let reported = false;
105
+ if (Array.isArray(value)) {
106
+ for (let i = 0; i < value.length; i++) {
107
+ diffSlot(readConsumerIndex(value, i), appendSegment(prefix, i), visit, arrived);
226
108
  }
227
- } else if (newValue instanceof Map) {
228
- for (const [key, entry] of newValue) {
229
- diffAndApply(void 0, entry, appendSegment(prefix, key), visit);
109
+ reported = value.length > 0;
110
+ } else if (value instanceof Map) {
111
+ for (const [key, entry] of value) {
112
+ diffSlot(entry, appendSegment(prefix, key), visit, arrived);
230
113
  }
114
+ reported = value.size > 0;
231
115
  } else {
232
- const rec = newValue;
116
+ const rec = value;
233
117
  for (const k of consumerKeys(rec)) {
234
- diffAndApply(void 0, readConsumerProp(rec, k), appendSegment(prefix, k), visit);
118
+ const entry = readConsumerProp(rec, k);
119
+ if (entry === void 0) continue;
120
+ reported = true;
121
+ if (arrived) diffAndApply(void 0, entry, appendSegment(prefix, k), visit);
122
+ else diffAndApply(entry, void 0, appendSegment(prefix, k), visit);
235
123
  }
236
124
  }
125
+ if (!reported) visit(presencePatch(arrived, prefix, value));
237
126
  }
238
- function walkOldDescendable(oldValue, prefix, visit) {
239
- if (Array.isArray(oldValue)) {
240
- for (let i = 0; i < oldValue.length; i++) {
241
- diffAndApply(readConsumerIndex(oldValue, i), void 0, appendSegment(prefix, i), visit);
242
- }
243
- } else if (oldValue instanceof Map) {
244
- for (const [key, entry] of oldValue) {
245
- diffAndApply(entry, void 0, appendSegment(prefix, key), visit);
246
- }
247
- } else {
248
- const rec = oldValue;
249
- for (const k of consumerKeys(rec)) {
250
- diffAndApply(readConsumerProp(rec, k), void 0, appendSegment(prefix, k), visit);
251
- }
252
- }
127
+ function diffSlot(entry, path, visit, arrived) {
128
+ if (entry === void 0) visit(presencePatch(arrived, path, entry));
129
+ else if (arrived) diffAndApply(void 0, entry, path, visit);
130
+ else diffAndApply(entry, void 0, path, visit);
131
+ }
132
+ function presencePatch(arrived, path, value) {
133
+ return arrived ? { kind: "added", path, newValue: value } : { kind: "removed", path, oldValue: value };
253
134
  }
254
135
  function diffMapsLockstep(oldMap, newMap, prefix, visit) {
255
136
  for (const [key, entry] of newMap) {
256
- diffAndApply(oldMap.get(key), entry, appendSegment(prefix, key), visit);
137
+ const path = appendSegment(prefix, key);
138
+ if (oldMap.has(key)) diffAndApply(oldMap.get(key), entry, path, visit);
139
+ else diffSlot(entry, path, visit, true);
257
140
  }
258
141
  for (const [key, entry] of oldMap) {
259
- if (newMap.has(key)) continue;
260
- diffAndApply(entry, void 0, appendSegment(prefix, key), visit);
142
+ if (!newMap.has(key)) diffSlot(entry, appendSegment(prefix, key), visit, false);
261
143
  }
262
144
  }
263
145
  function diffArraysLockstep(oldArr, newArr, prefix, visit) {
264
- const max = Math.max(oldArr.length, newArr.length);
265
- for (let i = 0; i < max; i++) {
146
+ const min = Math.min(oldArr.length, newArr.length);
147
+ for (let i = 0; i < min; i++) {
266
148
  diffAndApply(
267
149
  readConsumerIndex(oldArr, i),
268
150
  readConsumerIndex(newArr, i),
@@ -270,6 +152,12 @@ function diffArraysLockstep(oldArr, newArr, prefix, visit) {
270
152
  visit
271
153
  );
272
154
  }
155
+ for (let i = min; i < newArr.length; i++) {
156
+ diffSlot(readConsumerIndex(newArr, i), appendSegment(prefix, i), visit, true);
157
+ }
158
+ for (let i = min; i < oldArr.length; i++) {
159
+ diffSlot(readConsumerIndex(oldArr, i), appendSegment(prefix, i), visit, false);
160
+ }
273
161
  }
274
162
  function diffObjectsLockstep(oldRec, newRec, prefix, visit) {
275
163
  const seen = /* @__PURE__ */ new Set();
@@ -383,4 +271,4 @@ function structuralSnapshot(value) {
383
271
  return out;
384
272
  }
385
273
 
386
- export { ANONYMOUS_FORM_KEY_PREFIX as A, DEFAULT_HISTORY_MAX_SNAPSHOTS as D, RESERVED_KEY_PREFIX as R, safeOwnRead as a, safeAssign as b, callConsumerFn as c, safeOwnHas as d, consumerHas as e, spreadConsumerRecord as f, copyConsumerArray as g, consumerKeys as h, isShadowedKey as i, readConsumerProp as j, diffAndApply as k, DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS as l, consumerSymbolKeys as m, normalizeNumericOption as n, applyChangedKeys as o, pickDefined as p, DEFAULT_MAX_RECURSION_DEPTH as q, readConsumerIndex as r, structuralSnapshot as s, reportConsumerThrow as t, ANONYMOUS_WIZARD_KEY_PREFIX as u };
274
+ export { ANONYMOUS_FORM_KEY_PREFIX as A, DEFAULT_HISTORY_MAX_SNAPSHOTS as D, RESERVED_KEY_PREFIX as R, DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS as a, isDescendable as b, applyChangedKeys as c, diffAndApply as d, DEFAULT_MAX_RECURSION_DEPTH as e, ANONYMOUS_WIZARD_KEY_PREFIX as f, isBarePatch as i, normalizeNumericOption as n, pickDefined as p, structuralSnapshot as s };
@@ -114,7 +114,8 @@ const canonicalStringCache = /* @__PURE__ */ new Map();
114
114
  const PATHKEY_TO_SEGMENTS_MAX = 4096;
115
115
  const pathKeyToSegments = /* @__PURE__ */ new Map();
116
116
  function rememberSegmentsForPathKey(key, segments) {
117
- if (!pathKeyToSegments.has(key) && pathKeyToSegments.size >= PATHKEY_TO_SEGMENTS_MAX) {
117
+ if (pathKeyToSegments.has(key)) return;
118
+ if (pathKeyToSegments.size >= PATHKEY_TO_SEGMENTS_MAX) {
118
119
  const oldest = pathKeyToSegments.keys().next().value;
119
120
  if (oldest !== void 0) pathKeyToSegments.delete(oldest);
120
121
  }
@@ -156,7 +157,7 @@ function canonicalizePath(input) {
156
157
  return keyForSegments(input);
157
158
  }
158
159
  function keyForSegments(input) {
159
- const segments = Array.from(input).map(normalizeSegment);
160
+ const segments = input.map(normalizeSegment);
160
161
  const key = JSON.stringify(segments);
161
162
  rememberSegmentsForPathKey(key, segments);
162
163
  return { segments, key };
@@ -1,4 +1,4 @@
1
- import { G as GenericForm, F as FormKey, a as UseFormReturnType, _ as RegisterValue, v as Path, w as PathKey, ar as DisplayCtx, as as GetDisplayState, at as DisplayMachine, au as FieldRecord, av as ErrorCell, ad as ValidationError, aw as OriginalsRecord, A as AbstractSchema, ah as WriteMeta, d as DeepPartial, ai as WriteShape, ax as AttaformDomBinding, ay as TransformAbortHolder } from './attaform.Br8EBn5B.mjs';
1
+ import { G as GenericForm, F as FormKey, a as UseFormReturnType, _ as RegisterValue, v as Path, w as PathKey, aq as DisplayCtx, ar as GetDisplayState, as as DisplayMachine, at as FieldRecord, au as ErrorCell, ac as ValidationError, av as OriginalsRecord, A as AbstractSchema, ag as WriteMeta, d as DeepPartial, ah as WriteShape, aw as StructureSnapshot, ax as AttaformDomBinding, ay as TransformAbortHolder } from './attaform.i13OEHJF.mjs';
2
2
  import { Ref, ComputedRef, ShallowRef, App, InjectionKey, Plugin } from 'vue';
3
3
 
4
4
  /** Options accepted by `injectForm` in place of a bare key string. */
@@ -48,6 +48,10 @@ type InjectFormInput = {
48
48
  * Pass the `Form` generic explicitly, since Vue's provide/inject erases
49
49
  * generics. The form stays alive for this component's lifetime and is
50
50
  * cleaned up once every consumer has unmounted.
51
+ *
52
+ * Components whose nearest `useForm`-calling ancestor is the same one get
53
+ * the same form object for the same form: `injectForm('cart')` in two of
54
+ * its descendants returns one form, with one set of field caches.
51
55
  */
52
56
  declare function injectForm<Form extends GenericForm, GetValueFormType extends GenericForm = Form>(input?: FormKey | InjectFormInput): UseFormReturnType<Form, GetValueFormType> | null;
53
57
 
@@ -135,6 +139,10 @@ type UseRegisterOptions = {
135
139
  * `.value` unwrap. Reads inside a `computed` or `watchEffect` re-run
136
140
  * when the parent rebinds to a different path.
137
141
  *
142
+ * Modifiers on the parent's binding apply at the inner control, so
143
+ * `<MyInput v-register.trim="form.register('email')" />` trims there,
144
+ * together with any the wrapper writes on its own `v-register`.
145
+ *
138
146
  * When no parent bound, every field reads `undefined`, so reach for
139
147
  * `rv?.path`. `v-register="rv"` is still safe. A wrapper that is meant
140
148
  * to render both with and without a form declares it with
@@ -159,9 +167,10 @@ type DynamicPathSweep = {
159
167
  * `PathKey` of a path the form no longer has, and deletes whatever it
160
168
  * files under that path. A cache keying by more than the path (a
161
169
  * shape sigil, say) deletes every variant; a key it never held is a
162
- * no-op.
170
+ * no-op. Returns the release, which a registrant that does not live
171
+ * as long as the form must call.
163
172
  */
164
- readonly onEvict: (evict: (key: PathKey) => void) => void;
173
+ readonly onEvict: (evict: (key: PathKey) => void) => () => void;
165
174
  };
166
175
 
167
176
  /**
@@ -277,6 +286,13 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
277
286
  readonly pathSweep: DynamicPathSweep;
278
287
  readonly formKey: FormKey;
279
288
  readonly form: Ref<F>;
289
+ /**
290
+ * Per-path field records: one per leaf from construction, and one per
291
+ * container, the root (`[]`) included, once a directive binds to it or a
292
+ * write takes leaves away beneath it. A container's own record holds the
293
+ * stamp of that write and the interaction history of the descendants it
294
+ * took; see `stampContainers` and `foldInteractionHistory`.
295
+ */
280
296
  readonly fields: Map<PathKey, FieldRecord>;
281
297
  /**
282
298
  * The tagged error store: one cell per error-bearing path, each segregating
@@ -290,8 +306,14 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
290
306
  * mutated so Vue's per-key Map tracking fires for either side. Derived blank
291
307
  * entries are not stored here; they synthesize at read
292
308
  * (`derivedBlankErrors`).
309
+ *
310
+ * A method, not a property, because it is also the gate on the
311
+ * construction seed: the first call runs it (see `createErrorCells`). A
312
+ * getter on the store object would gate a property read the same way,
313
+ * but V8 builds an object literal holding an accessor in dictionary mode,
314
+ * and every kernel read of the store would then pay a hash lookup.
293
315
  */
294
- readonly errorCells: Map<PathKey, ErrorCell>;
316
+ readonly errorCells: () => Map<PathKey, ErrorCell>;
295
317
  /**
296
318
  * Reactively-derived "No value supplied" errors: a pure function of
297
319
  * `(blankPaths, schema.isRequiredAtPath)` with no writers and no clears.
@@ -492,10 +514,15 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
492
514
  * side, because no errors at frame 1 means "not checked yet", not "checked
493
515
  * and clean".
494
516
  *
495
- * The window it closes: where the slim default-derivation parse strips
496
- * refinements (`.refine`, `.superRefine`, async validators) the slim parse
497
- * passes, no construction errors land, and the queued microtask has not run,
498
- * so frame 1 would paint the form valid a tick before the real verdict.
517
+ * The window it closes: a schema with async work gets no verdict from the
518
+ * construction parse (v4 skips it for an async refine or transform; v3 skips
519
+ * it for an async transform and mounts clean when it throws over an async
520
+ * refine), so no construction errors land, and until the queued async pass
521
+ * runs, frame 1 would paint the form valid a tick before the real verdict.
522
+ * A sync-only schema's construction parse seeds its verdict synchronously,
523
+ * before the first read of its errors returns, so its gate starts open. v3
524
+ * reports every `.refine` as async, so a v3 schema with a sync refine
525
+ * starts gated and seeded.
499
526
  *
500
527
  * `reset()` restores it to the same construction-time seed, so the post-reset
501
528
  * window gates exactly like the post-mount one.
@@ -621,6 +648,19 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
621
648
  * empty is skipped.
622
649
  */
623
650
  restoreErrorCells(entries: ReadonlyArray<readonly [PathKey, ErrorCell]>): void;
651
+ /**
652
+ * The per-element bookkeeping as it stands, for a history position to put
653
+ * back with its value (see `StructureSnapshot`). The entries are copied,
654
+ * and the records they hold are never mutated in place, so the snapshot
655
+ * stays detached from the live store.
656
+ */
657
+ captureStructure(): StructureSnapshot;
658
+ /**
659
+ * Put a `captureStructure()` back, before the value it was taken with is
660
+ * restored, so the write that restores it compares each row against its
661
+ * own baseline. Every reading it can change is woken.
662
+ */
663
+ restoreStructure(snapshot: StructureSnapshot): void;
624
664
  /**
625
665
  * Merged read of the cell at `path`, in schema -> blank -> user order:
626
666
  * structural validation first, the synthesized blank entry between, user
@@ -709,18 +749,19 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
709
749
  * insert or removal) against its construction / reset baseline. The
710
750
  * structural half of `dirty`: per-element baselines travel with their
711
751
  * element across a mutation, so a positional value comparison alone cannot
712
- * see the shape change.
752
+ * see the shape change. `key` is the canonical key of `path`.
713
753
  */
714
- hasStructuralChangeUnder(path: Path): boolean;
754
+ hasStructuralChangeUnder(path: Path, key: PathKey): boolean;
715
755
  /**
716
- * Whether a baseline-present container under `path` was replaced wholesale
717
- * by a non-container (`setValue('profile', undefined)` and the like) and is
718
- * still absent. The other half of removal-driven `dirty`: every leaf in such
719
- * a subtree vanishes at once, so neither the present-leaf walk nor the array
720
- * tracker sees the loss. Self-filters by current liveness, so a refilled
721
- * path stops counting.
756
+ * Whether a path under `path` that held a real baseline value was removed
757
+ * and is still absent: a dropped key, an emptied container, a row a
758
+ * wholesale write took away. The other half of removal-driven `dirty`: the
759
+ * present-leaf walk never visits an absent leaf, and the array tracker
760
+ * follows only array lengths and orders. A path a later write brought back
761
+ * stops counting, and the present-leaf walk judges it instead. `key` is the
762
+ * canonical key of `path`.
722
763
  */
723
- hasRemovedSubtreeUnder(path: Path): boolean;
764
+ hasRemovedSubtreeUnder(path: Path, key: PathKey): boolean;
724
765
  getFieldRecord(path: Path): FieldRecord | undefined;
725
766
  /**
726
767
  * Cancel every in-flight field-level validation run: clear the timers of
@@ -1,4 +1,4 @@
1
- import { c as RegistryNotInstalledError, O as OutsideSetupError, g as pathKeyToDotted } from './attaform.CdZIEf7_.mjs';
1
+ import { c as RegistryNotInstalledError, O as OutsideSetupError, g as pathKeyToDotted } from './attaform.BhtAZysa.mjs';
2
2
  import { shallowReactive, getCurrentInstance, inject } from 'vue';
3
3
 
4
4
  function detectSSR(options = {}) {
@@ -168,8 +168,8 @@ function renderAttaformState(app) {
168
168
  // The wire shape stays two per-source arrays; the tagged store
169
169
  // splits at the boundary and the client replays each side into
170
170
  // its cell half.
171
- schemaErrors: [...state.errorCells.entries()].filter(([, cell]) => cell.schema.length > 0).map(([key2, cell]) => [key2, cell.schema]),
172
- userErrors: [...state.errorCells.entries()].filter(([, cell]) => cell.user.length > 0).map(([key2, cell]) => [key2, cell.user]),
171
+ schemaErrors: [...state.errorCells().entries()].filter(([, cell]) => cell.schema.length > 0).map(([key2, cell]) => [key2, cell.schema]),
172
+ userErrors: [...state.errorCells().entries()].filter(([, cell]) => cell.user.length > 0).map(([key2, cell]) => [key2, cell.user]),
173
173
  fields: Array.from(state.fields.entries()),
174
174
  ...transientList.length > 0 ? { blankPaths: transientList } : {}
175
175
  }