attaform 0.30.0 → 0.31.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 (71) hide show
  1. package/dist/abstract.d.mts +15 -21
  2. package/dist/abstract.mjs +3 -3
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/devtools.mjs +4 -4
  5. package/dist/dev/directive.mjs +1 -1
  6. package/dist/dev/history.mjs +2 -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.DUZckyV8.mjs → attaform-dev.B1q-x9R9.mjs} +4 -4
  10. package/dist/dev/shared/{attaform-dev.BcYT6tFh.mjs → attaform-dev.CZz4GYZ6.mjs} +21 -9
  11. package/dist/dev/shared/{attaform-dev.BisFstxh.mjs → attaform-dev.Caibt7sb.mjs} +16 -15
  12. package/dist/dev/shared/{attaform-dev.DTrHonpc.mjs → attaform-dev.CtyqGia7.mjs} +176 -260
  13. package/dist/dev/shared/{attaform-dev.BK6Ge2qR.mjs → attaform-dev.DJ3nsWw6.mjs} +86 -122
  14. package/dist/dev/shared/{attaform-dev.BcNtyOmf.mjs → attaform-dev.DlaT4xGd.mjs} +4 -4
  15. package/dist/dev/shared/{attaform-dev.D6CLvaQT.mjs → attaform-dev.k5X47GMD.mjs} +520 -395
  16. package/dist/dev/shared/{attaform-dev.CQ2rJiAX.mjs → attaform-dev.y9x9Vp9F.mjs} +324 -234
  17. package/dist/dev/shared/{attaform-dev.BQc5QV3Y.mjs → attaform-dev.zlvDr0WB.mjs} +2 -2
  18. package/dist/dev/zod-v3.mjs +6 -6
  19. package/dist/dev/zod-v4.mjs +6 -6
  20. package/dist/dev/zod.mjs +5 -5
  21. package/dist/directive.d.mts +4 -4
  22. package/dist/directive.mjs +1 -1
  23. package/dist/esbuild.d.mts +11 -0
  24. package/dist/esbuild.mjs +1 -1
  25. package/dist/history.d.mts +3 -32
  26. package/dist/history.mjs +2 -2
  27. package/dist/index.d.mts +67 -70
  28. package/dist/index.mjs +4 -4
  29. package/dist/nuxt.d.mts +4 -3
  30. package/dist/nuxt.mjs +3 -6
  31. package/dist/rollup.d.mts +11 -0
  32. package/dist/rollup.mjs +1 -1
  33. package/dist/rspack.d.mts +19 -11
  34. package/dist/rspack.mjs +1 -1
  35. package/dist/runtime/components/AttaformDevtoolsPanel.vue +15 -15
  36. package/dist/runtime/components/DevtoolsValueTree.vue +2 -2
  37. package/dist/runtime/plugins/attaform.mjs +1 -1
  38. package/dist/shared/{attaform.BRDDQeHV.mjs → attaform.8mfKZCwH.mjs} +2 -2
  39. package/dist/shared/{attaform.DRUT703x.d.mts → attaform.B-Tb62qV.d.mts} +12 -12
  40. package/dist/shared/{attaform.v0fQtWyA.mjs → attaform.B1rH9ht6.mjs} +1 -1
  41. package/dist/shared/{attaform.DhnPb6nK.mjs → attaform.BAf3UNn4.mjs} +1 -1
  42. package/dist/shared/{attaform.BXneBxEj.mjs → attaform.BWUn8Kjy.mjs} +85 -121
  43. package/dist/shared/{attaform.WiGdIqgx.mjs → attaform.C4shmwyJ.mjs} +2 -2
  44. package/dist/shared/{attaform.Rc9u4Vfw.d.mts → attaform.CRVe8s82.d.mts} +83 -95
  45. package/dist/shared/attaform.D9RqPgGu.d.mts +42 -0
  46. package/dist/shared/attaform.DAo0QBmS.d.mts +2173 -0
  47. package/dist/shared/{attaform.BK0cgu2K.mjs → attaform.DBCbxlyk.mjs} +62 -4
  48. package/dist/shared/{attaform.CUfrMK7t.mjs → attaform.DL2nl_QR.mjs} +15 -14
  49. package/dist/shared/{attaform.DNJOWjQw.mjs → attaform.DNPJPOO7.mjs} +299 -218
  50. package/dist/shared/attaform.DZnrDrNb.d.mts +4657 -0
  51. package/dist/shared/{attaform.B3resMQ1.mjs → attaform.DxKQsqFZ.mjs} +496 -371
  52. package/dist/shared/{attaform.ByWP6kSx.mjs → attaform.L011tHgJ.mjs} +3 -3
  53. package/dist/shared/{attaform.CJ8DpRGj.mjs → attaform.LUPC-2Qg.mjs} +175 -257
  54. package/dist/transforms.d.mts +153 -26
  55. package/dist/transforms.mjs +1 -1
  56. package/dist/vite.d.mts +29 -52
  57. package/dist/vite.mjs +3 -3
  58. package/dist/webpack.d.mts +20 -12
  59. package/dist/webpack.mjs +1 -1
  60. package/dist/zod-v3.d.mts +90 -96
  61. package/dist/zod-v3.mjs +5 -5
  62. package/dist/zod-v4.d.mts +120 -118
  63. package/dist/zod-v4.mjs +5 -5
  64. package/dist/zod.d.mts +5 -5
  65. package/dist/zod.mjs +4 -4
  66. package/package.json +7 -6
  67. package/skills/attaform/SKILL.md +1 -1
  68. package/skills/attaform/references/custom-components.md +4 -0
  69. package/dist/shared/attaform.Clz0ArIi.d.mts +0 -44
  70. package/dist/shared/attaform.UGyOK83T.d.mts +0 -2366
  71. package/dist/shared/attaform.bKLzMU0o.d.mts +0 -4761
@@ -1,11 +1,11 @@
1
- import { computed, readonly, toRaw, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, nextTick, warn, reactive, shallowReactive, shallowRef, toValue, watch, triggerRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, onBeforeMount, onBeforeUpdate, onMounted, effectScope } from 'vue';
2
- import { A as ANONYMOUS_FORM_KEY_PREFIX, i as isShadowedKey, d as safeOwnHas, a as safeOwnRead, e as consumerHas, f as spreadConsumerRecord, g as copyConsumerArray, r as readConsumerIndex, b as safeAssign, h as consumerKeys, j as readConsumerProp, k as diffAndApply, p as pickDefined, s as structuralSnapshot, n as normalizeNumericOption, l as DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS, m as consumerSymbolKeys, o as applyChangedKeys, R as RESERVED_KEY_PREFIX, q as DEFAULT_MAX_RECURSION_DEPTH, t as ANONYMOUS_WIZARD_KEY_PREFIX } from './attaform-dev.BcYT6tFh.mjs';
3
- import { e as ensureAttaformInstalled, u as useRegistry, d as kFormContext, f as kFormInstanceId, b as kAttaformWizardActiveStepResolver, i as kAttaformAncestorWizard } from './attaform-dev.DUZckyV8.mjs';
4
- import { R as REGISTER_OWNER_MARKER, d as armDomBinding, V as V_REGISTER_MARKER } from './attaform-dev.D6CLvaQT.mjs';
5
- import { i as isPathPrefix, e as canonicalizePath, k as keyForSegments, m as mapSegmentKeys, b as ROOT_PATH_KEY, s as segmentsForPathKey, S as SubmitErrorHandlerError, t as toError, j as groupErrorsByKey, n as normalizeErrorInput, h as SET_MEMBER_SEGMENT, R as ROOT_PATH, l as normalizeErrorInputs, a as InvalidUseFormConfigError, d as ReservedFormKeyError } from './attaform-dev.BQc5QV3Y.mjs';
1
+ import { computed, readonly, toRaw, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, nextTick, warn, reactive, shallowReactive, shallowRef, toValue, watch, triggerRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, effectScope, onBeforeMount, onBeforeUpdate, onMounted } from 'vue';
2
+ import { A as ANONYMOUS_FORM_KEY_PREFIX, i as isShadowedKey, d as safeOwnHas, a as safeOwnRead, e as consumerHas, f as spreadConsumerRecord, g as copyConsumerArray, r as readConsumerIndex, b as safeAssign, h as consumerKeys, j as readConsumerProp, k as diffAndApply, p as pickDefined, s as structuralSnapshot, n as normalizeNumericOption, l as DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS, m as consumerSymbolKeys, o as applyChangedKeys, R as RESERVED_KEY_PREFIX, q as DEFAULT_MAX_RECURSION_DEPTH, c as callConsumerFn, t as reportConsumerThrow, u as ANONYMOUS_WIZARD_KEY_PREFIX } from './attaform-dev.CZz4GYZ6.mjs';
3
+ import { e as ensureAttaformInstalled, u as useRegistry, d as kFormContext, f as kFormInstanceId, b as kAttaformWizardActiveStepResolver, i as kAttaformAncestorWizard } from './attaform-dev.B1q-x9R9.mjs';
4
+ import { R as REGISTER_OWNER_MARKER, V as V_REGISTER_MARKER, d as armDomBinding, r as recordInheritedModifiers, w as withInheritedModifiers } from './attaform-dev.k5X47GMD.mjs';
5
+ import { i as isPathPrefix, e as canonicalizePath, k as keyForSegments, m as mapSegmentKeys, b as ROOT_PATH_KEY, s as segmentsForPathKey, S as SubmitErrorHandlerError, t as toError, j as groupErrorsByKey, n as normalizeErrorInput, h as SET_MEMBER_SEGMENT, R as ROOT_PATH, l as normalizeErrorInputs, a as InvalidUseFormConfigError, d as ReservedFormKeyError } from './attaform-dev.zlvDr0WB.mjs';
6
6
 
7
7
  const AttaformErrorCode = {
8
- /** A required field is in the blank set — user hasn't supplied a value. */
8
+ /** A required field is in the blank set: no value was supplied. */
9
9
  NoValueSupplied: "atta:no-value-supplied",
10
10
  /** The schema adapter's `validateAtPath` threw synchronously. */
11
11
  AdapterThrew: "atta:adapter-threw",
@@ -531,7 +531,7 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
531
531
  const value = state.getValueAtPath(segments);
532
532
  const original = state.originals.get(key)?.value;
533
533
  const pristine = state.isPristineAtPathByKey(key, segments);
534
- const cell = state.errorCells.get(key);
534
+ const cell = state.errorCells().get(key);
535
535
  const schemaForKey = cell !== void 0 && cell.schema.length > 0 ? cell.schema : void 0;
536
536
  const blankForKey = state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments) ? [makeBlankRequiredError(segments)] : void 0;
537
537
  const userForKey = cell !== void 0 && cell.user.length > 0 ? cell.user : void 0;
@@ -565,9 +565,8 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
565
565
  updatedAt: record?.updatedAt ?? null,
566
566
  errors,
567
567
  // A leaf has no descendants, so its own-bucket errors ARE its subtree
568
- // errors. Reuse the same array reference: referential `ownErrors ===
569
- // errors`, and no second read of the whole-form `derivedBlankErrors`
570
- // map (preserves the P3 blank-reactivity optimization above).
568
+ // errors. Reuse the array reference, which makes `ownErrors === errors`
569
+ // and avoids a second read of the whole-form `derivedBlankErrors` map.
571
570
  ownErrors: errors,
572
571
  validating,
573
572
  valid,
@@ -747,19 +746,19 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
747
746
  elements: EMPTY_ELEMENTS,
748
747
  updatedAt,
749
748
  errors,
750
- // The container's OWN bucket only (this exact path), excluding every
751
- // descendant that `errors` rolls up: a cross-field `.refine()` here,
752
- // or `setErrors` pinned at this node. The same raw union the leaf
753
- // builds from the three stores; the container already depends on all
754
- // three via `aggregateErrorsAt`, so there's no new reactivity cost.
749
+ // The container's OWN bucket at this exact path, excluding every
750
+ // descendant `errors` rolls up: a cross-field `.refine()` here, or a
751
+ // `setErrors` pinned at this node. The same raw union the leaf builds
752
+ // from the three stores, and the container already depends on all three
753
+ // through `aggregateErrorsAt`, so it costs no new reactivity.
755
754
  ownErrors: state.getErrorsForPath(segments),
756
755
  validating,
757
756
  valid,
758
757
  transforming,
759
758
  busy: transforming || validating,
760
- // A container surfaces its OWN transform failure (a transform registered
761
- // on the container path, e.g. a file normalizer) but never rolls up a
762
- // descendant leaf's failure — that stays a per-field channel.
759
+ // A container surfaces its OWN transform failure, from a transform
760
+ // registered on the container path such as a file normalizer, and never
761
+ // rolls up a descendant's: that stays a per-field channel.
763
762
  transformError: ownTransformError,
764
763
  path: segments,
765
764
  ...computeFieldIdentity(formInstanceId, state.formKey, key),
@@ -821,7 +820,7 @@ function aggregateErrorsAt(state, prefix, prefixKey) {
821
820
  const candidates = state.errorWindowAt(prefix, prefixKey);
822
821
  if (candidates.length === 0) return [];
823
822
  const formValue = state.form.value;
824
- const cells = state.errorCells;
823
+ const cells = state.errorCells();
825
824
  const buckets = /* @__PURE__ */ new Map();
826
825
  for (const { key, segments } of candidates) {
827
826
  if (!isPathPrefix(prefix, segments)) continue;
@@ -886,7 +885,7 @@ function isArrayPath(state, segments) {
886
885
  function warnReadOnly(surface, action, key) {
887
886
  const phrase = action === "write" ? `write to "${String(key)}"` : `${action} of "${String(key)}"`;
888
887
  console.warn(
889
- `[attaform] ${surface} is read-only \u2014 ${phrase} was ignored. Mutate the form via setValue / the directive / field-array helpers instead.`
888
+ `[attaform] ${surface} is read-only; ${phrase} was ignored. Mutate the form via setValue / the directive / field-array helpers instead.`
890
889
  );
891
890
  }
892
891
  function makeReadonlyCoercion(snapshot) {
@@ -1094,16 +1093,16 @@ function buildErrorsSurface(state, sweep) {
1094
1093
  // non-schema keys reachable while a genuinely-absent key stays out.
1095
1094
  hasOwn: (segments, key) => liveContainerHasKey(state, segments, key) || errorAwareContainerKeys(state, segments).includes(key),
1096
1095
  isArrayAt: (segments) => isArrayPath(state, segments),
1097
- // `errors()` / `errors([])` / `errors(path)` are all the subtree
1098
- // aggregate — the same helper `meta.errors` reads, so the surfaces
1099
- // never drift.
1096
+ // `errors()`, `errors([])` and `errors(path)` are all the subtree
1097
+ // aggregate, through the helper `meta.errors` reads, so they cannot
1098
+ // drift.
1100
1099
  call: (path) => aggregateErrorsAt(state, path, keyForSegments(path).key),
1101
1100
  surface: "form.errors",
1102
1101
  sweep
1103
1102
  });
1104
1103
  }
1105
1104
  function collectErrorsAt(state, key, segments, active, into) {
1106
- const cell = state.errorCells.get(key);
1105
+ const cell = state.errorCells().get(key);
1107
1106
  if (active) {
1108
1107
  if (cell !== void 0) into.push(...cell.schema);
1109
1108
  if (state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments)) {
@@ -1690,7 +1689,7 @@ function createArrayBookkeeping(deps) {
1690
1689
  ...record,
1691
1690
  path: segments
1692
1691
  }));
1693
- migrateMapSubtree(errorCells, arrayPath, remap, (cell, segments) => ({
1692
+ migrateMapSubtree(errorCells(), arrayPath, remap, (cell, segments) => ({
1694
1693
  schema: NO_ERRORS,
1695
1694
  user: cell.user.map((error) => ({ ...error, path: [...segments] }))
1696
1695
  }));
@@ -1740,7 +1739,7 @@ function createArrayBookkeeping(deps) {
1740
1739
  migrateElementState(arrayPath, remap);
1741
1740
  for (const freshIndex of remap.fresh) seedFreshElement(arrayPath, freshIndex);
1742
1741
  const changed = changedIndices(remap);
1743
- evictSchemaVerdictsAtIndices(errorCells, arrayPath, changed);
1742
+ evictSchemaVerdictsAtIndices(errorCells(), arrayPath, changed);
1744
1743
  abortValidationAtVacatedIndices(arrayPath, remap);
1745
1744
  variantMemory.dropAtIndices(arrayPath, changed);
1746
1745
  arrayIdentity.applyOp(arrayPath, remap);
@@ -2061,7 +2060,7 @@ function adapterThrowMessage(err) {
2061
2060
  return "Adapter validateAtPath threw a non-Error value";
2062
2061
  }
2063
2062
  function hasUserErrorEntries(state) {
2064
- for (const cell of state.errorCells.values()) {
2063
+ for (const cell of state.errorCells().values()) {
2065
2064
  if (cell.user.length > 0) return true;
2066
2065
  }
2067
2066
  return false;
@@ -2161,9 +2160,9 @@ function reportRejection(store, path, kind, accepted) {
2161
2160
  if (!shouldWarnOnce(store, key)) return;
2162
2161
  if (accepted.size === 0) {
2163
2162
  console.warn(
2164
- `[attaform] Cannot write to '${dotted}' \u2014 this path is not in your schema.
2163
+ `[attaform] Cannot write to '${dotted}': this path is not in your schema.
2165
2164
  Fix: check for a typo in register('${dotted}'); it should match a leaf key in your schema.
2166
- (If the path resolves to a never-typed schema, it explicitly admits no values \u2014 relax the schema if intentional.)
2165
+ (If the path resolves to a never-typed schema, it explicitly admits no values; relax the schema if intentional.)
2167
2166
  The write was a no-op.`
2168
2167
  );
2169
2168
  return;
@@ -2171,16 +2170,16 @@ function reportRejection(store, path, kind, accepted) {
2171
2170
  const expected = formatExpectedKinds(accepted);
2172
2171
  if (kind === "string" && accepted.has("number")) {
2173
2172
  console.warn(
2174
- `[attaform] Cannot write a string to '${dotted}' \u2014 the schema expects ${expected}.
2175
- Fix: add type="number" to the input, OR use the .number modifier on v-register:
2176
- <input type="number" v-register="register('${dotted}')" />
2173
+ `[attaform] Cannot write a string to '${dotted}': the schema expects ${expected}.
2174
+ Fix: use the .number modifier on v-register, OR add type="number" to a native input:
2177
2175
  <input v-register.number="register('${dotted}')" />
2176
+ <input type="number" v-register="register('${dotted}')" />
2178
2177
  The write was a no-op.`
2179
2178
  );
2180
2179
  return;
2181
2180
  }
2182
2181
  console.warn(
2183
- `[attaform] Cannot write a ${kind} to '${dotted}' \u2014 the schema expects ${expected}.
2182
+ `[attaform] Cannot write a ${kind} to '${dotted}': the schema expects ${expected}.
2184
2183
  The write was a no-op.`
2185
2184
  );
2186
2185
  }
@@ -2330,6 +2329,7 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2330
2329
  const isRequired = state.schema.isRequiredAtPath(segments);
2331
2330
  const ariaDisplayState = getDisplayStateAt !== void 0 ? computed(() => getDisplayStateAt(segments)) : void 0;
2332
2331
  const markBlank = () => state.setValueAtPath(segments, slimDefault, withInstanceMeta({ blank: true }));
2332
+ const commitFromHost = (value) => value === "" && !acceptsString || value === null && !slimTypes.has("null") || value === void 0 && !acceptsUndefined ? markBlank() : state.setValueAtPath(segments, value, withInstanceMeta(void 0));
2333
2333
  const internalRv = {
2334
2334
  innerRef,
2335
2335
  displayValue,
@@ -2346,7 +2346,7 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2346
2346
  },
2347
2347
  ensureDomBinding: (factory) => {
2348
2348
  var _a;
2349
- (_a = state.domBinding).value ?? (_a.value = factory(state));
2349
+ return (_a = state.domBinding).value ?? (_a.value = factory(state));
2350
2350
  },
2351
2351
  registerElement: (element) => {
2352
2352
  const dom = state.domBinding.value;
@@ -2366,10 +2366,10 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2366
2366
  setValueWithInternalPath: (value, meta) => {
2367
2367
  return state.setValueAtPath(segments, value, withInstanceMeta(meta));
2368
2368
  },
2369
- setValueFromHost: (value) => {
2369
+ commitFromHost,
2370
+ setValueFromHost: (value, modifiers) => {
2370
2371
  state.markInteracted(segments);
2371
- const isBlankSignal = value === "" && !acceptsString || value === null && !slimTypes.has("null") || value === void 0 && !acceptsUndefined;
2372
- const accepted = isBlankSignal ? markBlank() : state.setValueAtPath(segments, value, withInstanceMeta(void 0));
2372
+ const accepted = state.domBinding.value?.hostChannel.write(internalRv, value, modifiers) ?? commitFromHost(value);
2373
2373
  {
2374
2374
  const dedupeKey = `${formInstanceId}:${pathKey}`;
2375
2375
  const isWired = () => (state.domBinding.value?.elements.get(pathKey)?.elements.size ?? 0) > 0 || state.getFieldRecord(segments)?.connected === true;
@@ -2385,7 +2385,8 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2385
2385
  }
2386
2386
  return accepted;
2387
2387
  },
2388
- // Called by the `vRegisterHint` compile-time transform's wrapping
2388
+ setValueFromHostInput: (payload, modifiers) => state.domBinding.value?.hostChannel.writeInput(internalRv, payload, modifiers) ?? false,
2389
+ // Called by the `vRegisterHintTransform` compile-time transform's wrapping
2389
2390
  // IIFE on every server-side render of `<element v-register="…">`.
2390
2391
  // Without it, every SSR'd FieldState serialises `connected: false`
2391
2392
  // (because Vue skips directive lifecycle during SSR) and the client
@@ -2414,12 +2415,12 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2414
2415
  }
2415
2416
  return false;
2416
2417
  },
2417
- // --- Async transform lifecycle (internal; the directive's
2418
- // deferred orchestrator is the only legitimate consumer). Thin
2419
- // path-bound delegates to the store's per-path token / counter
2420
- // machinery — same pattern as `markBlank` / `setValueWithInternalPath`,
2421
- // so the directive (which holds only this RegisterValue, never the
2422
- // store) can drive the busy/discard/error bookkeeping. ---
2418
+ // --- Async transform lifecycle (internal; the directive's deferred
2419
+ // orchestrator is the only legitimate consumer). Thin path-bound
2420
+ // delegates to the store's per-path token and counter machinery,
2421
+ // the same pattern as `markBlank` and `setValueWithInternalPath`, so
2422
+ // the directive (which holds this RegisterValue and never the store)
2423
+ // can drive the busy / discard / error bookkeeping. ---
2423
2424
  beginTransform: (holder) => state.beginTransform(pathKey, holder),
2424
2425
  isCurrentTransform: (token) => state.isCurrentTransform(pathKey, token),
2425
2426
  endTransform: (token) => state.endTransform(pathKey, token),
@@ -2432,10 +2433,10 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2432
2433
  return (state.fieldTransformCounts.get(pathKey) ?? 0) > 0;
2433
2434
  },
2434
2435
  path: pathKey,
2435
- // Frozen so a wrapper component can pass `rv.segments` directly
2436
- // to `form.fields(...)` without defensive copying — and so test
2437
- // fixtures or downstream code can't mutate the canonical
2438
- // segment list out from under the directive.
2436
+ // Frozen so a wrapper component can pass `rv.segments` straight to
2437
+ // `form.fields(...)` with no defensive copy, and so neither a test
2438
+ // fixture nor downstream code can mutate the canonical segment list
2439
+ // out from under the directive.
2439
2440
  segments: Object.freeze(segments.slice()),
2440
2441
  formKey: state.formKey,
2441
2442
  formInstanceId,
@@ -2458,25 +2459,24 @@ function isUnset(value) {
2458
2459
  return value === unset;
2459
2460
  }
2460
2461
 
2461
- function walkUnsetSentinels(values, schema) {
2462
+ function walkUnsetSentinels(values, schema, rootDefault) {
2462
2463
  const paths = [];
2463
2464
  if (values === void 0) {
2464
- const rootSlim = schema.getDefaultAtPath([]);
2465
- walkUnspecified(rootSlim, [], paths);
2465
+ walkUnspecified(rootDefault, [], paths);
2466
2466
  return { cleanedValues: void 0, paths };
2467
2467
  }
2468
- const cleaned = walkCore(values, [], schema, paths, true);
2468
+ const cleaned = walkCore(values, [], schema, paths, { rootDefault });
2469
2469
  return { cleanedValues: cleaned, paths };
2470
2470
  }
2471
2471
  function isRecursable(value) {
2472
2472
  return Array.isArray(value) || isPlainRecord(value);
2473
2473
  }
2474
- function walkCore(input, segments, schema, paths, synthesizeSchemaKeys) {
2474
+ function walkCore(input, segments, schema, paths, synthesis) {
2475
2475
  if (isUnset(input)) {
2476
2476
  return expandUnsetAt(segments, schema, paths);
2477
2477
  }
2478
2478
  if (input === void 0) {
2479
- if (synthesizeSchemaKeys) {
2479
+ if (synthesis !== null) {
2480
2480
  const slim = schema.getDefaultAtPath(segments);
2481
2481
  return walkUnspecified(slim, segments, paths);
2482
2482
  }
@@ -2489,7 +2489,7 @@ function walkCore(input, segments, schema, paths, synthesizeSchemaKeys) {
2489
2489
  let mutated = false;
2490
2490
  for (let i = 0; i < input.length; i++) {
2491
2491
  const original = readConsumerIndex(input, i);
2492
- const walked = walkCore(original, [...segments, i], schema, paths, synthesizeSchemaKeys);
2492
+ const walked = walkCore(original, [...segments, i], schema, paths, synthesis);
2493
2493
  out[i] = walked;
2494
2494
  if (walked !== original) mutated = true;
2495
2495
  }
@@ -2501,10 +2501,10 @@ function walkCore(input, segments, schema, paths, synthesizeSchemaKeys) {
2501
2501
  let keys = inputKeys;
2502
2502
  let mutated = false;
2503
2503
  let inputKeysSet = null;
2504
- if (synthesizeSchemaKeys) {
2504
+ if (synthesis !== null) {
2505
2505
  inputKeysSet = new Set(inputKeys);
2506
2506
  const allKeys = new Set(inputKeys);
2507
- const slim = schema.getDefaultAtPath(segments);
2507
+ const slim = segments.length === 0 ? synthesis.rootDefault : schema.getDefaultAtPath(segments);
2508
2508
  if (isPlainRecord(slim)) {
2509
2509
  for (const k of Object.keys(slim)) allKeys.add(k);
2510
2510
  }
@@ -2514,12 +2514,12 @@ function walkCore(input, segments, schema, paths, synthesizeSchemaKeys) {
2514
2514
  const out = {};
2515
2515
  for (const key of keys) {
2516
2516
  const orig = readConsumerProp(obj, key);
2517
- if (synthesizeSchemaKeys && orig === void 0 && inputKeysSet?.has(key) === true) {
2517
+ if (synthesis !== null && orig === void 0 && inputKeysSet?.has(key) === true) {
2518
2518
  safeAssign(out, key, void 0);
2519
2519
  mutated = true;
2520
2520
  continue;
2521
2521
  }
2522
- const walked = walkCore(orig, [...segments, key], schema, paths, synthesizeSchemaKeys);
2522
+ const walked = walkCore(orig, [...segments, key], schema, paths, synthesis);
2523
2523
  safeAssign(out, key, walked);
2524
2524
  if (walked !== orig) mutated = true;
2525
2525
  }
@@ -2543,7 +2543,7 @@ function walkUnspecified(slim, segments, paths) {
2543
2543
  }
2544
2544
  function substituteUnsetSentinels(value, prefix, schema) {
2545
2545
  const paths = [];
2546
- const cleaned = walkCore(value, [...prefix], schema, paths, false);
2546
+ const cleaned = walkCore(value, [...prefix], schema, paths, null);
2547
2547
  return { cleanedValues: cleaned, paths };
2548
2548
  }
2549
2549
  function isPrimitiveOrEmpty(value) {
@@ -2641,15 +2641,15 @@ function buildMetaProxy(keys, read) {
2641
2641
  return new Proxy(target, {
2642
2642
  get: (_, key) => owns.has(key) ? read(key) : Reflect.get(target, key),
2643
2643
  has: (_, key) => owns.has(key) || Reflect.has(target, key),
2644
- // Safe to hand back the same array every time: the spec copies a
2645
- // trap's key list before using it.
2644
+ // Safe to hand back the same array every time: the spec copies a trap's
2645
+ // key list before using it.
2646
2646
  ownKeys: () => keys,
2647
2647
  getOwnPropertyDescriptor(_, key) {
2648
2648
  if (!owns.has(key)) return Reflect.getOwnPropertyDescriptor(target, key);
2649
2649
  return { configurable: true, enumerable: true, value: read(key), writable: false };
2650
2650
  },
2651
- // A getter with no setter throws on assignment under strict mode,
2652
- // which every module here is. `false` keeps that.
2651
+ // A getter with no setter throws on assignment under strict mode, which
2652
+ // every module here is; `false` keeps that.
2653
2653
  set: denyWrite,
2654
2654
  deleteProperty: denyWrite,
2655
2655
  defineProperty: denyWrite
@@ -2708,7 +2708,18 @@ function buildFormApi(state, formInstanceId, options = {}) {
2708
2708
  void state.submitted.value;
2709
2709
  return metaBase;
2710
2710
  };
2711
- const pathSweep = state.pathSweep;
2711
+ const releases = [];
2712
+ const lease = (release) => {
2713
+ releases.push(release);
2714
+ return release;
2715
+ };
2716
+ onScopeDispose(() => {
2717
+ for (const release of releases) release();
2718
+ }, true);
2719
+ const pathSweep = {
2720
+ track: state.pathSweep.track,
2721
+ onEvict: (evict) => lease(state.pathSweep.onEvict(evict))
2722
+ };
2712
2723
  const getRootFieldStateAt = buildFieldStateAccessor(
2713
2724
  state,
2714
2725
  formInstanceId,
@@ -2766,7 +2777,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
2766
2777
  const next = typeof pathOrValue === "function" ? pathOrValue(structuralSnapshot(state.form.value)) : pathOrValue;
2767
2778
  const walked2 = walkUnsetSentinels(
2768
2779
  next,
2769
- state.schema
2780
+ state.schema,
2781
+ state.schema.getDefaultAtPath([])
2770
2782
  );
2771
2783
  const ok2 = state.setValueAtPath([], walked2.cleanedValues, writeMeta());
2772
2784
  if (!ok2) return false;
@@ -2831,7 +2843,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2831
2843
  const errorsProxy = buildErrorsSurface(state, pathSweep);
2832
2844
  function flattenUserErrors() {
2833
2845
  const all = [];
2834
- for (const cell of state.errorCells.values()) all.push(...cell.user);
2846
+ for (const cell of state.errorCells().values()) all.push(...cell.user);
2835
2847
  return all;
2836
2848
  }
2837
2849
  function setErrors(arg1, arg2) {
@@ -2839,7 +2851,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2839
2851
  if (isScoped) {
2840
2852
  const { segments, key } = canonicalizePath(arg1);
2841
2853
  const input2 = arg2;
2842
- const resolved2 = typeof input2 === "function" ? input2([...state.errorCells.get(key)?.user ?? []]) : input2;
2854
+ const resolved2 = typeof input2 === "function" ? input2([...state.errorCells().get(key)?.user ?? []]) : input2;
2843
2855
  state.setUserErrorsForPath(
2844
2856
  segments,
2845
2857
  normalizeErrorInputs(resolved2, segments, AttaformErrorCode.UserError)
@@ -2867,7 +2879,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2867
2879
  const departAttempts = computed(() => state.departAttempts.value);
2868
2880
  const validating = computed(() => state.activeValidations.value > 0);
2869
2881
  const valid = computed(
2870
- () => state.firstValidationDone.value && state.errorCells.size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
2882
+ () => state.firstValidationDone.value && state.errorCells().size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
2871
2883
  );
2872
2884
  const history = options.history;
2873
2885
  const formHistory = history === void 0 ? inertHistory() : readonly(
@@ -2885,12 +2897,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
2885
2897
  );
2886
2898
  const rootFieldState = getRootFieldStateAt([]);
2887
2899
  const metaOwn = {
2888
- // Whole-form work signals compose the LIFECYCLE counters with the
2889
- // per-leaf rollup: a submit-time validate shows up as
2890
- // activeValidations, per-field debounced validators as
2891
- // fieldValidationCounts — either flips the flag. `valid` keeps the
2892
- // form-level `firstValidationDone` mount gate; `errors` is the
2893
- // unfiltered whole-form aggregate.
2900
+ // Whole-form work signals compose the LIFECYCLE counters with the per-leaf
2901
+ // rollup: a submit-time validate shows up in `activeValidations` and a
2902
+ // per-field debounced validator in `fieldValidationCounts`, and either
2903
+ // flips the flag. `valid` keeps the form-level mount gate, and `errors` is
2904
+ // the unfiltered whole-form aggregate.
2894
2905
  validating: computed(
2895
2906
  () => state.activeValidations.value > 0 || rootFieldState.value.validating
2896
2907
  ),
@@ -2902,7 +2913,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2902
2913
  busy: computed(
2903
2914
  () => state.activeValidations.value > 0 || state.activeTransforms.value > 0 || rootFieldState.value.validating || rootFieldState.value.transforming
2904
2915
  ),
2905
- // Lifecycle (form-level only — not on FieldState).
2916
+ // Lifecycle, form-level only and never on FieldState.
2906
2917
  submitting,
2907
2918
  submissionAttempts,
2908
2919
  departAttempts,
@@ -2984,7 +2995,10 @@ function buildFormApi(state, formInstanceId, options = {}) {
2984
2995
  };
2985
2996
  return Object.freeze(view);
2986
2997
  });
2987
- const valuesProxy = buildValuesSurface(state.form, state.onFormChange);
2998
+ const valuesProxy = buildValuesSurface(
2999
+ state.form,
3000
+ (listener) => lease(state.onFormChange(listener))
3001
+ );
2988
3002
  const fieldStateProxy = buildFieldsSurface(state, getRootFieldStateAt, pathSweep);
2989
3003
  const needsLazyGate = state.defaultValuesFactory.value !== void 0 || state.hasSsrPrefetch;
2990
3004
  function gated(fn) {
@@ -3019,10 +3033,10 @@ function buildFormApi(state, formInstanceId, options = {}) {
3019
3033
  }
3020
3034
  const api = {
3021
3035
  handleSubmit: gated(handleSubmit),
3022
- // Callable readonly Proxies (`values`, `fields`, `errors`) and the
3023
- // reactive containers (`meta`, `history`, `blankPaths`) are exposed
3024
- // through getters so reading them activates the form on first
3025
- // touch. Each underlying object is identity-stable across reads.
3036
+ // The callable readonly Proxies (`values`, `fields`, `errors`) and the
3037
+ // reactive containers (`meta`, `history`, `blankPaths`) are exposed through
3038
+ // getters, so reading one activates the form on first touch. Each
3039
+ // underlying object is identity-stable across reads.
3026
3040
  get values() {
3027
3041
  void state.activate();
3028
3042
  return valuesProxy;
@@ -3039,7 +3053,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
3039
3053
  key: state.formKey,
3040
3054
  // Auto-unwrapping views over the per-store async-defaults lifecycle
3041
3055
  // refs (see FormStore.hydrating / hydrateError). Reading either
3042
- // activates the form — observing factory state implies use.
3056
+ // activates the form: observing factory state implies use.
3043
3057
  get hydrating() {
3044
3058
  void state.activate();
3045
3059
  return state.hydrating.value;
@@ -3048,20 +3062,18 @@ function buildFormApi(state, formInstanceId, options = {}) {
3048
3062
  void state.activate();
3049
3063
  return state.hydrateError.value;
3050
3064
  },
3051
- // Orthogonal to `hydrating` and `hydrateError`: `ready` flips true
3052
- // once defaults are applied (sync at construction or async factory
3053
- // resolved successfully). One-way latch — stays true through later
3054
- // refetches even when those refetches fail, so stale-while-
3055
- // revalidate UIs keep rendering the prior values while
3056
- // `hydrateError` surfaces the refresh failure.
3065
+ // Orthogonal to `hydrating` and `hydrateError`. `ready` flips true once
3066
+ // defaults are applied, whether sync at construction or from a factory
3067
+ // that resolved, and it is a one-way latch: it stays true through later
3068
+ // refetches even when those fail, so a stale-while-revalidate UI keeps
3069
+ // rendering the prior values while `hydrateError` surfaces the failure.
3057
3070
  get ready() {
3058
3071
  void state.activate();
3059
3072
  return state.defaultsResolved.value;
3060
3073
  },
3061
- // `rehydrate` and `activate` are themselves activation entry points
3062
- // — they fire the factory by design. Wrapping them with `gated`
3063
- // would double-fire (`state.activate()` plus the underlying call),
3064
- // so they call `state` directly.
3074
+ // `rehydrate` and `activate` are activation entry points themselves and
3075
+ // fire the factory by design, so wrapping them in `gated` would
3076
+ // double-fire. They call `state` directly.
3065
3077
  rehydrate: () => state.rehydrate(),
3066
3078
  activate: () => state.activate(),
3067
3079
  get errors() {
@@ -3134,7 +3146,7 @@ function shortenSourceFrame(frame) {
3134
3146
  const SWEEP_SLICE = 8;
3135
3147
  function createDynamicPathSweep(deps) {
3136
3148
  const dynamicKeys = /* @__PURE__ */ new Set();
3137
- const evictors = [];
3149
+ const evictors = /* @__PURE__ */ new Set();
3138
3150
  const isDynamicPath = (segments) => {
3139
3151
  for (let i = 0; i < segments.length; i++) {
3140
3152
  if (typeof segments[i] === "number") return true;
@@ -3165,7 +3177,10 @@ function createDynamicPathSweep(deps) {
3165
3177
  if (isDynamicPath(segments)) dynamicKeys.add(key);
3166
3178
  },
3167
3179
  onEvict(evict) {
3168
- evictors.push(evict);
3180
+ evictors.add(evict);
3181
+ return () => {
3182
+ evictors.delete(evict);
3183
+ };
3169
3184
  }
3170
3185
  };
3171
3186
  }
@@ -3369,7 +3384,7 @@ function mergeObjectKeys(target, source, path, schema) {
3369
3384
  async function runFactoryAndApply(st, factory) {
3370
3385
  try {
3371
3386
  const value = await factory();
3372
- walkAuthoredFromConstraints(value, [], st.authoredPaths);
3387
+ if (st.hasPreprocessOrCoerce) walkAuthoredFromConstraints(value, [], st.authoredPaths);
3373
3388
  const full = mergeSparseHydration(
3374
3389
  toRaw(st.form.value),
3375
3390
  value,
@@ -3393,7 +3408,7 @@ async function runFactoryAndApply(st, factory) {
3393
3408
  }
3394
3409
  }
3395
3410
  function clearHydrationFailedEntry(st) {
3396
- const existing = st.errorCells.get(ROOT_PATH_KEY)?.schema;
3411
+ const existing = st.errorCells().get(ROOT_PATH_KEY)?.schema;
3397
3412
  if (existing === void 0 || existing.length === 0) return;
3398
3413
  const filtered = existing.filter((e) => e.code !== AttaformErrorCode.HydrationFailed);
3399
3414
  if (filtered.length !== existing.length) {
@@ -3407,7 +3422,7 @@ function appendHydrationFailedEntry(st, error) {
3407
3422
  path: [...ROOT_PATH],
3408
3423
  code: AttaformErrorCode.HydrationFailed
3409
3424
  };
3410
- const existing = st.errorCells.get(ROOT_PATH_KEY)?.schema ?? [];
3425
+ const existing = st.errorCells().get(ROOT_PATH_KEY)?.schema ?? [];
3411
3426
  st.setSchemaErrorsForPath([...ROOT_PATH], [...existing, entry]);
3412
3427
  return entry;
3413
3428
  }
@@ -3541,23 +3556,24 @@ function walkAuthoredFromSchemaDiff(withDefaults, withoutDefaults, prefix, out)
3541
3556
  out.add(canonicalizePath(prefix).key);
3542
3557
  }
3543
3558
  }
3544
- function computeBaselineResponse(schema, source) {
3545
- const completed = source === void 0 ? void 0 : mergeStructural(schema, [], source);
3559
+ function computeBaseline(schema, source, schemaDefaults) {
3560
+ if (source === void 0) return schemaDefaults;
3546
3561
  return schema.getDefaultValues({
3547
3562
  useDefaultSchemaValues: true,
3548
- constraints: completed
3563
+ constraints: mergeStructural(schema, [], source, schemaDefaults)
3549
3564
  });
3550
3565
  }
3551
3566
  function initialFirstValidationGate(schema) {
3552
3567
  return schema.needsAsyncValidation?.() !== true;
3553
3568
  }
3554
- function seedOriginalsFromBaseline(st, baseline, ensureOrdinals) {
3569
+ function seedOriginalsFromBaseline(st, baseline) {
3555
3570
  st.originals.clear();
3556
3571
  diffAndApply({}, baseline, [], (patch) => {
3557
3572
  if (patch.kind !== "added") return;
3558
- const { key } = canonicalizePath(patch.path);
3559
- st.originals.set(key, { segments: patch.path, value: patch.newValue });
3560
- if (ensureOrdinals) ensurePathOrdinal(st, key);
3573
+ st.originals.set(canonicalizePath(patch.path).key, {
3574
+ segments: patch.path,
3575
+ value: patch.newValue
3576
+ });
3561
3577
  });
3562
3578
  }
3563
3579
  function queueInitialAsyncValidation(st) {
@@ -3571,6 +3587,7 @@ function queueInitialAsyncValidation(st) {
3571
3587
  }
3572
3588
  }
3573
3589
  function rebuildAuthoredPaths(st, constraints, schemaWithDefaultsData) {
3590
+ if (!st.hasPreprocessOrCoerce) return;
3574
3591
  st.authoredPaths.clear();
3575
3592
  if (constraints !== void 0) {
3576
3593
  walkAuthoredFromConstraints(constraints, [], st.authoredPaths);
@@ -3604,6 +3621,7 @@ function ensurePathOrdinal(st, key) {
3604
3621
  return ordinal;
3605
3622
  }
3606
3623
  function pathHasAsyncValidationByKey(st, key, segments) {
3624
+ if (st.schema.needsAsyncValidation?.() === false) return false;
3607
3625
  const cached = st.pathAsyncCache.get(key);
3608
3626
  if (cached !== void 0) return cached;
3609
3627
  const candidates = st.schema.getSchemasAtPath(segments);
@@ -3724,19 +3742,16 @@ function touchFieldRecord(st, pathKey, path, patch) {
3724
3742
  path,
3725
3743
  updatedAt: patch.updatedAt ?? current?.updatedAt ?? null,
3726
3744
  connected: patch.connected ?? current?.connected ?? false,
3727
- // focused/blurred use an explicit-undefined guard because
3728
- // patches legitimately carry `null` to mark a disconnect — the
3729
- // `??` operator would short-circuit on null and fall through to
3730
- // `current`, losing the intent. `!== undefined` honours an
3731
- // explicit null and preserves current only on absence.
3745
+ // `focused` / `blurred` need an explicit-undefined guard: a patch
3746
+ // legitimately carries `null` to mark a disconnect, and `??` would
3747
+ // short-circuit on it and fall through to `current`, losing the intent.
3732
3748
  focused: patch.focused !== void 0 ? patch.focused : current?.focused ?? null,
3733
3749
  blurred: patch.blurred !== void 0 ? patch.blurred : current?.blurred ?? null,
3734
- // touched is plain `boolean`; `??` is equivalent to the explicit
3735
- // guard here because `false` is not nullish.
3750
+ // `touched` is a plain boolean, so `??` is equivalent to the guard above.
3736
3751
  touched: patch.touched ?? current?.touched ?? false,
3737
- // interacted is sticky-true; a merge patch only ever sets it, so
3738
- // `??` preserves the current bit. It flips back to false solely
3739
- // through the reset paths, which reconstruct the record outright.
3752
+ // `interacted` is sticky-true and a merge patch only ever sets it, so `??`
3753
+ // preserves the current bit. Only the reset paths clear it, and they
3754
+ // reconstruct the record outright.
3740
3755
  interacted: patch.interacted ?? current?.interacted ?? false,
3741
3756
  blurredAfterInteraction: patch.blurredAfterInteraction ?? current?.blurredAfterInteraction ?? false
3742
3757
  });
@@ -3941,16 +3956,19 @@ function setValueAtPath(st, path, value, meta) {
3941
3956
  }
3942
3957
  }
3943
3958
  }
3944
- const wasAuthoredBefore = st.authoredPaths.has(pathKey);
3945
- if (arrayOpRemap !== null && Array.isArray(value)) {
3946
- if (path.length > 0) st.authoredPaths.add(pathKey);
3947
- for (const idx of arrayOpRemap.fresh) {
3948
- walkAuthoredFromConstraints(value[idx], [...path, idx], st.authoredPaths);
3959
+ let newlyAuthored = false;
3960
+ if (st.hasPreprocessOrCoerce) {
3961
+ const wasAuthoredBefore = st.authoredPaths.has(pathKey);
3962
+ if (arrayOpRemap !== null && Array.isArray(value)) {
3963
+ if (path.length > 0) st.authoredPaths.add(pathKey);
3964
+ for (const idx of arrayOpRemap.fresh) {
3965
+ walkAuthoredFromConstraints(value[idx], [...path, idx], st.authoredPaths);
3966
+ }
3967
+ } else {
3968
+ walkAuthoredFromConstraints(value, path, st.authoredPaths);
3949
3969
  }
3950
- } else {
3951
- walkAuthoredFromConstraints(value, path, st.authoredPaths);
3970
+ newlyAuthored = !wasAuthoredBefore && st.authoredPaths.has(pathKey);
3952
3971
  }
3953
- const newlyAuthored = !wasAuthoredBefore && st.authoredPaths.has(pathKey);
3954
3972
  let completedValue;
3955
3973
  if (arrayOpRemap !== null && Array.isArray(value)) {
3956
3974
  for (const idx of arrayOpRemap.fresh) {
@@ -4207,22 +4225,38 @@ function getValueAtPath(st, path) {
4207
4225
  return getAtPath(st.form.value, path);
4208
4226
  }
4209
4227
  const ERROR_SOURCES = ["schema", "user"];
4228
+ function createErrorCells(schema, pristine) {
4229
+ const raw = /* @__PURE__ */ new Map();
4230
+ const cells = shallowReactive(raw);
4231
+ return (seed) => {
4232
+ const data = pristine;
4233
+ if (data !== void 0) {
4234
+ pristine = void 0;
4235
+ if (seed) {
4236
+ for (const [key, list] of groupErrorsByKey(schema.validateSync(data))) {
4237
+ raw.set(key, { schema: list, user: NO_ERRORS });
4238
+ }
4239
+ }
4240
+ }
4241
+ return cells;
4242
+ };
4243
+ }
4210
4244
  function setErrorChannelForKey(st, key, src, entries) {
4211
- const current = st.errorCells.get(key);
4245
+ const current = st.errorCells().get(key);
4212
4246
  const schema = src === "schema" ? entries : current?.schema ?? NO_ERRORS;
4213
4247
  const user = src === "user" ? entries : current?.user ?? NO_ERRORS;
4214
4248
  if (schema.length === 0 && user.length === 0) {
4215
- if (current !== void 0) st.errorCells.delete(key);
4249
+ if (current !== void 0) st.errorCells().delete(key);
4216
4250
  return;
4217
4251
  }
4218
- st.errorCells.set(key, { schema, user });
4252
+ st.errorCells().set(key, { schema, user });
4219
4253
  }
4220
4254
  function replaceErrorChannel(st, src, entries) {
4221
4255
  const other = src === "schema" ? "user" : "schema";
4222
4256
  const grouped = groupErrorsByKey(entries);
4223
- for (const [key, cell] of st.errorCells) {
4257
+ for (const [key, cell] of st.errorCells()) {
4224
4258
  if (cell[other].length === 0) {
4225
- st.errorCells.delete(key);
4259
+ st.errorCells().delete(key);
4226
4260
  continue;
4227
4261
  }
4228
4262
  const fresh = grouped.get(key);
@@ -4247,8 +4281,9 @@ function clearErrorChannel(st, src, path) {
4247
4281
  function applySchemaErrorsForSubtree(st, path, entries) {
4248
4282
  const parentKey = canonicalizePath(path).key;
4249
4283
  const grouped = groupErrorsByKey(entries);
4284
+ if (path.length === 0) st.skipErrorSeed();
4250
4285
  if (!grouped.has(parentKey)) setErrorChannelForKey(st, parentKey, "schema", NO_ERRORS);
4251
- for (const [existingKey, cell] of st.errorCells) {
4286
+ for (const [existingKey, cell] of st.errorCells()) {
4252
4287
  if (existingKey === parentKey) continue;
4253
4288
  if (cell.schema.length === 0) continue;
4254
4289
  if (isPathKeyUnder(existingKey, path) && !grouped.has(existingKey)) {
@@ -4260,15 +4295,15 @@ function applySchemaErrorsForSubtree(st, path, entries) {
4260
4295
  }
4261
4296
  }
4262
4297
  function restoreErrorCells(st, entries) {
4263
- st.errorCells.clear();
4298
+ st.skipErrorSeed().clear();
4264
4299
  for (const [key, cell] of entries) {
4265
4300
  if (cell.schema.length === 0 && cell.user.length === 0) continue;
4266
- st.errorCells.set(key, { schema: [...cell.schema], user: [...cell.user] });
4301
+ st.errorCells().set(key, { schema: [...cell.schema], user: [...cell.user] });
4267
4302
  }
4268
4303
  }
4269
4304
  function getErrorsForPath(st, path) {
4270
4305
  const { key } = canonicalizePath(path);
4271
- const cell = st.errorCells.get(key);
4306
+ const cell = st.errorCells().get(key);
4272
4307
  const blankForKey = st.derivedBlankErrors.value.get(key);
4273
4308
  if (cell === void 0 && blankForKey === void 0) {
4274
4309
  return [];
@@ -4305,13 +4340,13 @@ function markFocused(st, path, focused, meta) {
4305
4340
  touchFieldRecord(st, key, path, {
4306
4341
  focused,
4307
4342
  blurred: !focused,
4308
- // `touched` flips to true on blur and stays true thereafter; while
4309
- // a field is currently focused we keep whatever value it held.
4343
+ // `touched` flips true on blur and stays; a focused field keeps whatever
4344
+ // it held.
4310
4345
  touched: focused ? current?.touched ?? false : true,
4311
- // `blurredAfterInteraction` flips true on the first blur that lands
4312
- // after a value edit and stays true. A tab-through blur before any
4313
- // edit leaves it false (`interacted` is still false at that blur),
4314
- // which is what keeps a clean tab-through from arming the gate.
4346
+ // `blurredAfterInteraction` flips true on the first blur after a value
4347
+ // edit and stays. A tab-through blur before any edit leaves it false,
4348
+ // since `interacted` is still false, which is what keeps a clean
4349
+ // tab-through from arming the gate.
4315
4350
  blurredAfterInteraction: !focused && current?.interacted === true ? true : current?.blurredAfterInteraction ?? false
4316
4351
  });
4317
4352
  const focusMode = meta?.instance?.validateOn ?? st.fieldValidationMode;
@@ -4430,7 +4465,14 @@ function adoptResolvedDefaults(st, value) {
4430
4465
  st.schema
4431
4466
  )
4432
4467
  );
4433
- seedOriginalsFromBaseline(st, computeBaselineResponse(st.schema, st.defaultValues).data, false);
4468
+ seedOriginalsFromBaseline(
4469
+ st,
4470
+ computeBaseline(
4471
+ st.schema,
4472
+ st.defaultValues,
4473
+ st.schema.getDefaultValues({ useDefaultSchemaValues: true })
4474
+ )
4475
+ );
4434
4476
  }
4435
4477
  function reset(st, nextDefaultValues) {
4436
4478
  const resetSource = nextDefaultValues === void 0 ? st.defaultValues : mergeSparseHydration(
@@ -4439,13 +4481,16 @@ function reset(st, nextDefaultValues) {
4439
4481
  st.schema
4440
4482
  );
4441
4483
  st.defaultValues = structuralSnapshot(resetSource);
4442
- const resetResponse = computeBaselineResponse(st.schema, resetSource);
4443
- const next = resetResponse.data;
4484
+ const next = computeBaseline(
4485
+ st.schema,
4486
+ resetSource,
4487
+ st.schema.getDefaultValues({ useDefaultSchemaValues: true })
4488
+ );
4444
4489
  rebuildAuthoredPaths(st, resetSource, next);
4445
4490
  applyFormReplacement(st, next);
4446
4491
  st.arrayIdentity.rebaselineAll();
4447
4492
  st.removedSubtrees.clear();
4448
- seedOriginalsFromBaseline(st, next, false);
4493
+ seedOriginalsFromBaseline(st, next);
4449
4494
  if (nextDefaultValues !== void 0) {
4450
4495
  const mentioned = /* @__PURE__ */ new Set();
4451
4496
  walkAuthoredFromConstraints(nextDefaultValues, [], mentioned);
@@ -4455,14 +4500,8 @@ function reset(st, nextDefaultValues) {
4455
4500
  for (const key of st.originalBlankPaths) {
4456
4501
  st.blankPaths.add(key);
4457
4502
  }
4458
- st.errorCells.clear();
4459
- if (!resetResponse.success) {
4460
- replaceErrorChannel(st, "schema", resetResponse.errors);
4461
- }
4462
- const syncResult = st.schema.validateAtPath(st.form.value, void 0, { sync: true });
4463
- if (!(syncResult instanceof Promise) && !syncResult.success) {
4464
- applySchemaErrorsForSubtree(st, [], syncResult.errors);
4465
- }
4503
+ st.skipErrorSeed().clear();
4504
+ replaceErrorChannel(st, "schema", st.schema.validateSync(next));
4466
4505
  st.firstValidationDone.value = initialFirstValidationGate(st.schema);
4467
4506
  queueInitialAsyncValidation(st);
4468
4507
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4507,7 +4546,7 @@ function resetField(st, path) {
4507
4546
  const wrote = setValueAtPath(st, targetSegments, leafEntry.value);
4508
4547
  if (!wrote) {
4509
4548
  console.error(
4510
- `[attaform] resetField: leaf write rejected for path '${targetKey}' \u2014 originals contain a value that doesn't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
4549
+ `[attaform] resetField: leaf write rejected for path '${targetKey}'. Originals contain a value that doesn't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
4511
4550
  );
4512
4551
  }
4513
4552
  } else {
@@ -4528,7 +4567,7 @@ function resetField(st, path) {
4528
4567
  const wroteSubtree = setValueAtPath(st, targetSegments, subtree);
4529
4568
  if (!wroteSubtree) {
4530
4569
  console.error(
4531
- `[attaform] resetField: subtree write rejected at path '${targetKey}' \u2014 originals contain values that don't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
4570
+ `[attaform] resetField: subtree write rejected at path '${targetKey}'. Originals contain values that don't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
4532
4571
  );
4533
4572
  }
4534
4573
  }
@@ -4539,7 +4578,7 @@ function resetField(st, path) {
4539
4578
  }
4540
4579
  }
4541
4580
  function deleteErrorCellsUnderPrefix(st, prefix) {
4542
- for (const [errorKey, cell] of st.errorCells) {
4581
+ for (const [errorKey, cell] of st.errorCells()) {
4543
4582
  for (const src of ERROR_SOURCES) {
4544
4583
  const first = cell[src][0];
4545
4584
  if (first !== void 0 && isPathPrefix(prefix, first.path)) {
@@ -4604,8 +4643,8 @@ function createFormStore(options) {
4604
4643
  const fieldValidatingSince = reactive(/* @__PURE__ */ new Map());
4605
4644
  const displayEngine = createDisplayEngine(ssr);
4606
4645
  cleanupHooks.push(() => displayEngine.dispose());
4607
- const schemaResponse = computeBaselineResponse(schema, defaultValues);
4608
- const schemaInitialData = schemaResponse.data;
4646
+ const ownDefaultValues = structuralSnapshot(defaultValues);
4647
+ const schemaInitialData = computeBaseline(schema, ownDefaultValues, options.schemaDefaults);
4609
4648
  const authoredPaths = /* @__PURE__ */ new Set();
4610
4649
  const initialData = hydration !== void 0 ? hydration.form : structuralSnapshot(schemaInitialData);
4611
4650
  const hasDU = schema.hasDiscriminatedUnions?.() !== false;
@@ -4619,7 +4658,11 @@ function createFormStore(options) {
4619
4658
  });
4620
4659
  const fields = shallowReactive(/* @__PURE__ */ new Map());
4621
4660
  const domBinding = shallowRef(null);
4622
- const errorCells = shallowReactive(/* @__PURE__ */ new Map());
4661
+ const errorStore = createErrorCells(
4662
+ schema,
4663
+ hydration === void 0 ? schemaInitialData : void 0
4664
+ );
4665
+ const errorCells = () => errorStore(true);
4623
4666
  const originals = shallowReactive(/* @__PURE__ */ new Map());
4624
4667
  const removedSubtrees = /* @__PURE__ */ new Set();
4625
4668
  const blankPaths = reactive(/* @__PURE__ */ new Set());
@@ -4643,7 +4686,7 @@ function createFormStore(options) {
4643
4686
  return result;
4644
4687
  });
4645
4688
  const errorPathIndex = computed(
4646
- () => buildErrorPathIndex(errorCells, derivedBlankErrors.value)
4689
+ () => buildErrorPathIndex(errorCells(), derivedBlankErrors.value)
4647
4690
  );
4648
4691
  const submitting = ref(false);
4649
4692
  const activeSubmissions = ref(0);
@@ -4783,21 +4826,16 @@ function createFormStore(options) {
4783
4826
  blankPaths,
4784
4827
  originalBlankPaths,
4785
4828
  // --- kernel-internal state ---
4786
- // Defensive copy, not a fix for an observed alias: today
4787
- // `getDefaultValues` happens to build a fresh tree, so form storage
4788
- // does not currently share structure with the consumer's object.
4789
- // That is an adapter implementation detail rather than a contract,
4790
- // and this field is now durable state that every `reset()` reads, so
4791
- // it should not depend on it. The copy also means a consumer who
4792
- // keeps a reference to the literal they passed cannot mutate the
4793
- // form's defaults from outside. `reset()` snapshots for a harder
4794
- // reason (see there) where an alias IS reachable.
4795
- defaultValues: structuralSnapshot(defaultValues),
4829
+ // The copy taken above, which also stops a consumer holding a reference to
4830
+ // the literal they passed from mutating the form's defaults from outside.
4831
+ // `reset()` snapshots for a harder reason, where an alias IS reachable.
4832
+ defaultValues: ownDefaultValues,
4796
4833
  ssrPrefetch,
4797
4834
  rememberVariants,
4798
4835
  fieldValidationMode,
4799
4836
  fieldValidationDebounceMs,
4800
4837
  hasDU,
4838
+ hasPreprocessOrCoerce: schema.hasPreprocessOrCoerce?.() !== false,
4801
4839
  fieldValidationState,
4802
4840
  formChangeListeners,
4803
4841
  submitSuccessListeners,
@@ -4813,6 +4851,7 @@ function createFormStore(options) {
4813
4851
  transformRuns,
4814
4852
  transformWaiters,
4815
4853
  arrayBookkeeping,
4854
+ skipErrorSeed: () => errorStore(false),
4816
4855
  nextOrdinal: 0,
4817
4856
  scheduleEpoch: 0,
4818
4857
  lastCommittedEpoch: 0,
@@ -4829,10 +4868,10 @@ function createFormStore(options) {
4829
4868
  arrayElementKey: bind(arrayElementKey),
4830
4869
  reset: bind(reset),
4831
4870
  resetField: bind(resetField),
4832
- // The schema/user pair below reads as a fold waiting to happen. It was
4833
- // tried: a `setErrorsForPathIn(channel)` factory measured 10 B LARGER,
4834
- // because gzip had already collected the rent on two adjacent copies
4835
- // and the helper added a name the original did not need.
4871
+ // The schema/user pair below reads as a fold waiting to happen. Folding
4872
+ // them into a `setErrorsForPathIn(channel)` factory measures 10 B LARGER:
4873
+ // gzip has already collected the rent on two adjacent copies, and the
4874
+ // helper adds a name the original does not need.
4836
4875
  setSchemaErrorsForPath: (path, entries) => setErrorChannelForKey(
4837
4876
  st,
4838
4877
  canonicalizePath(path).key,
@@ -4880,7 +4919,25 @@ function createFormStore(options) {
4880
4919
  dispose: bind(dispose)
4881
4920
  };
4882
4921
  rebuildAuthoredPaths(st, defaultValues, schemaInitialData);
4883
- seedOriginalsFromBaseline(st, schemaInitialData, true);
4922
+ const initStamp = (/* @__PURE__ */ new Date()).toISOString();
4923
+ diffAndApply({}, schemaInitialData, [], (patch) => {
4924
+ if (patch.kind !== "added") return;
4925
+ const { key } = canonicalizePath(patch.path);
4926
+ originals.set(key, { segments: patch.path, value: patch.newValue });
4927
+ ensurePathOrdinal(st, key);
4928
+ if (hydration === void 0) {
4929
+ fields.set(key, {
4930
+ path: patch.path,
4931
+ updatedAt: initStamp,
4932
+ connected: false,
4933
+ focused: null,
4934
+ blurred: null,
4935
+ touched: false,
4936
+ interacted: false,
4937
+ blurredAfterInteraction: false
4938
+ });
4939
+ }
4940
+ });
4884
4941
  if (hydration !== void 0) {
4885
4942
  for (const [rawKey, record] of hydration.fields) {
4886
4943
  if (typeof rawKey !== "string" || !isHydratedFieldRecord(record)) {
@@ -4903,25 +4960,6 @@ function createFormStore(options) {
4903
4960
  }
4904
4961
  setErrorChannelForKey(st, rawKey, "user", errs);
4905
4962
  }
4906
- } else {
4907
- const initStamp = (/* @__PURE__ */ new Date()).toISOString();
4908
- diffAndApply({}, initialData, [], (patch) => {
4909
- if (patch.kind !== "added") return;
4910
- const { key } = canonicalizePath(patch.path);
4911
- fields.set(key, {
4912
- path: patch.path,
4913
- updatedAt: initStamp,
4914
- connected: false,
4915
- focused: null,
4916
- blurred: null,
4917
- touched: false,
4918
- interacted: false,
4919
- blurredAfterInteraction: false
4920
- });
4921
- });
4922
- if (!schemaResponse.success) {
4923
- replaceErrorChannel(st, "schema", schemaResponse.errors);
4924
- }
4925
4963
  }
4926
4964
  queueInitialAsyncValidation(st);
4927
4965
  return st;
@@ -5019,9 +5057,11 @@ const HISTORY_MODULE_KEY = "history";
5019
5057
  function buildFreshState(key, schema, configuration, registry) {
5020
5058
  const pending = registry.pendingHydration.get(key);
5021
5059
  if (pending !== void 0) registry.pendingHydration.delete(key);
5060
+ const schemaDefaults = schema.getDefaultValues({ useDefaultSchemaValues: true });
5022
5061
  const walked = walkUnsetSentinels(
5023
5062
  configuration.defaultValues,
5024
- schema
5063
+ schema,
5064
+ schemaDefaults
5025
5065
  );
5026
5066
  let initialBlankPaths;
5027
5067
  if (pending === void 0) {
@@ -5031,6 +5071,7 @@ function buildFreshState(key, schema, configuration, registry) {
5031
5071
  formKey: key,
5032
5072
  schema,
5033
5073
  defaultValues: walked.cleanedValues,
5074
+ schemaDefaults,
5034
5075
  hydration: pending,
5035
5076
  ssr: registry.ssr,
5036
5077
  ...pickDefined({
@@ -5041,12 +5082,10 @@ function buildFreshState(key, schema, configuration, registry) {
5041
5082
  coerce: configuration.coerce,
5042
5083
  initialBlankPaths
5043
5084
  }),
5044
- // Server-only: bind the SSR prefetch coordination handles. `enqueue`
5045
- // records intent on every `state.activate()` so a wizard skip-list
5046
- // override or a future transform mark has a consistent set to diff
5047
- // against; `shouldFire` lets the activate path bail when the
5048
- // wizard explicitly skipped this key — even an explicit
5049
- // `form.activate()` defers to the wizard's render-efficiency
5085
+ // Server only. `enqueue` records intent on every `state.activate()`
5086
+ // so a wizard skip-list override has a consistent set to diff
5087
+ // against; `shouldFire` lets the activate path bail on a key the
5088
+ // wizard skipped. Even an explicit `form.activate()` defers to that
5050
5089
  // skip-list on the server.
5051
5090
  ...registry.ssr ? {
5052
5091
  ssrPrefetch: {
@@ -5096,6 +5135,7 @@ function resolveFormKey(key) {
5096
5135
  }
5097
5136
 
5098
5137
  let injectedInstanceCounter = 0;
5138
+ const sharedApis = /* @__PURE__ */ new WeakMap();
5099
5139
  function injectForm(input) {
5100
5140
  const key = typeof input === "string" ? input : input?.key;
5101
5141
  const ssrAccessed = typeof input === "object" && input !== null ? input.__ssrAccessed === true : false;
@@ -5118,12 +5158,37 @@ function injectForm(input) {
5118
5158
  apiOptions.history = history;
5119
5159
  }
5120
5160
  const ambientInstanceId = getCurrentInstance() !== null ? inject(kFormInstanceId, null) : null;
5121
- const formInstanceId = ambientInstanceId ?? (getCurrentInstance() !== null ? useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`);
5122
- return buildFormApi(
5161
+ const api = ambientInstanceId !== null ? shareApi(state, ambientInstanceId, apiOptions) : buildFormApi(
5123
5162
  state,
5124
- formInstanceId,
5163
+ getCurrentInstance() !== null ? useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`,
5125
5164
  apiOptions
5126
5165
  );
5166
+ return api;
5167
+ }
5168
+ function shareApi(state, formInstanceId, options) {
5169
+ let instances = sharedApis.get(state);
5170
+ if (instances === void 0) {
5171
+ instances = /* @__PURE__ */ new Map();
5172
+ sharedApis.set(state, instances);
5173
+ }
5174
+ const byInstance = instances;
5175
+ let shared = byInstance.get(formInstanceId);
5176
+ if (shared === void 0) {
5177
+ const scope = effectScope(true);
5178
+ const api = scope.run(() => buildFormApi(state, formInstanceId, options));
5179
+ if (api === void 0) return buildFormApi(state, formInstanceId, options);
5180
+ shared = { api, scope, users: 0 };
5181
+ byInstance.set(formInstanceId, shared);
5182
+ }
5183
+ const entry = shared;
5184
+ entry.users += 1;
5185
+ onScopeDispose(() => {
5186
+ entry.users -= 1;
5187
+ if (entry.users > 0) return;
5188
+ entry.scope.stop();
5189
+ byInstance.delete(formInstanceId);
5190
+ });
5191
+ return entry.api;
5127
5192
  }
5128
5193
  function resolveState(key, registry) {
5129
5194
  if (key !== void 0) {
@@ -5218,22 +5283,23 @@ function useRegister(options) {
5218
5283
  const capturedRegisterValue = shallowRef(void 0);
5219
5284
  const refreshAndStripBridgeAttrs = () => {
5220
5285
  const rawAttrs = instance.attrs;
5286
+ const parentBinding = instance.vnode.dirs?.find(
5287
+ (dir) => dir.dir?.[V_REGISTER_MARKER] === true
5288
+ );
5221
5289
  if ("registerValue" in rawAttrs) {
5222
5290
  capturedRegisterValue.value = rawAttrs["registerValue"];
5223
5291
  delete rawAttrs["registerValue"];
5224
- if (capturedRegisterValue.value !== void 0) armDomBinding(capturedRegisterValue.value);
5225
- } else {
5226
- const dirs = instance.vnode.dirs;
5227
- if (dirs !== null && dirs !== void 0) {
5228
- for (const dir of dirs) {
5229
- const marked = dir.dir?.[V_REGISTER_MARKER];
5230
- if (marked === true) {
5231
- capturedRegisterValue.value = dir.value;
5232
- if (capturedRegisterValue.value !== void 0)
5233
- armDomBinding(capturedRegisterValue.value);
5234
- break;
5235
- }
5236
- }
5292
+ } else if (parentBinding !== void 0) {
5293
+ capturedRegisterValue.value = parentBinding.value;
5294
+ }
5295
+ const captured = capturedRegisterValue.value;
5296
+ if (captured !== void 0) {
5297
+ armDomBinding(captured);
5298
+ if (parentBinding !== void 0) {
5299
+ recordInheritedModifiers(
5300
+ captured,
5301
+ withInheritedModifiers(parentBinding.value, parentBinding.modifiers)
5302
+ );
5237
5303
  }
5238
5304
  }
5239
5305
  if ("value" in rawAttrs) delete rawAttrs["value"];
@@ -5259,7 +5325,7 @@ function warnOutsideSetup() {
5259
5325
  warnedOutsideSetup = true;
5260
5326
  const frame = captureUserCallSite();
5261
5327
  console.warn(
5262
- `[attaform] useRegister() called outside a component setup; returning an unbound RegisterValue proxy. Fix: call it inside <script setup> or a setup() function \u2014 not from an event handler or async callback.` + (frame !== void 0 ? ` ${frame}` : "")
5328
+ `[attaform] useRegister() called outside a component setup; returning an unbound RegisterValue proxy. Fix: call it inside <script setup> or a setup() function, not from an event handler or async callback.` + (frame !== void 0 ? ` ${frame}` : "")
5263
5329
  );
5264
5330
  }
5265
5331
  function warnNoParentRV(instance) {
@@ -5359,11 +5425,11 @@ const NOOP_SUCCESS = {
5359
5425
  data: EMPTY_VALUE,
5360
5426
  errors: void 0
5361
5427
  };
5362
- const NOOP_DEFAULTS = NOOP_SUCCESS;
5363
5428
  let NOOP_SCHEMA;
5364
5429
  function buildNoopWizardSchema() {
5365
5430
  NOOP_SCHEMA ?? (NOOP_SCHEMA = {
5366
- getDefaultValues: () => NOOP_DEFAULTS,
5431
+ getDefaultValues: () => EMPTY_VALUE,
5432
+ validateSync: () => [],
5367
5433
  getDefaultAtPath: () => void 0,
5368
5434
  getEmptyValueAtPath: () => void 0,
5369
5435
  isPreprocessOrCoerceLeaf: () => false,
@@ -5379,7 +5445,8 @@ function buildNoopWizardSchema() {
5379
5445
  isLeafAtPath: () => false,
5380
5446
  isRequiredAtPath: () => false,
5381
5447
  getUnionDiscriminatorAtPath: () => void 0,
5382
- hasDiscriminatedUnions: () => false
5448
+ hasDiscriminatedUnions: () => false,
5449
+ hasPreprocessOrCoerce: () => false
5383
5450
  });
5384
5451
  return NOOP_SCHEMA;
5385
5452
  }
@@ -5490,7 +5557,7 @@ function useWizard(options) {
5490
5557
  const rawSteps = Array.isArray(options.steps) ? options.steps : [];
5491
5558
  if (rawSteps.length === 0 && true) {
5492
5559
  console.error(
5493
- "[attaform] useWizard({ steps }): expected a non-empty array of step slots. Continuing with an empty step list \u2014 wizard.currentStep reads as undefined, navigation refuses, handleSubmit no-ops."
5560
+ "[attaform] useWizard({ steps }): expected a non-empty array of step slots. Continuing with an empty step list: wizard.currentStep reads as undefined, navigation refuses, handleSubmit no-ops."
5494
5561
  );
5495
5562
  }
5496
5563
  const registry = useRegistry();
@@ -5579,33 +5646,45 @@ function useWizard(options) {
5579
5646
  return activeKey.value === "" ? void 0 : activeKey.value;
5580
5647
  }
5581
5648
  };
5582
- function lazyComputedFor(marker) {
5649
+ function lazyComputedFor(marker, index) {
5583
5650
  const cached = lazyComputeds.get(marker);
5584
5651
  if (cached !== void 0) return cached;
5585
5652
  const c = computed(() => {
5586
5653
  void lazyEpoch.value;
5587
- return marker.resolve(slotCtx);
5654
+ return callConsumerFn(
5655
+ () => marker.resolve(slotCtx),
5656
+ void 0,
5657
+ "wizard-lazy-slot",
5658
+ `steps[${index}]`
5659
+ );
5588
5660
  });
5589
5661
  lazyComputeds.set(marker, c);
5590
5662
  return c;
5591
5663
  }
5592
5664
  const MAX_SLOT_DEPTH = 32;
5593
- function normalizeSlot(slot, ctx, gated, depth) {
5665
+ function normalizeSlot(slot, ctx, gated, depth, index) {
5594
5666
  if (depth > MAX_SLOT_DEPTH) {
5595
5667
  {
5596
5668
  console.warn(
5597
- `[attaform] useWizard: a step slot nested past ${MAX_SLOT_DEPTH} levels (gate / lazy / function); dropping it. Check for a resolver that returns itself.`
5669
+ `[attaform] useWizard: steps[${index}] nested past ${MAX_SLOT_DEPTH} levels (gate / lazy / function); dropping it. Check for a resolver that returns itself.`
5598
5670
  );
5599
5671
  }
5600
5672
  return void 0;
5601
5673
  }
5602
5674
  if (slot === void 0 || slot === null) return void 0;
5603
5675
  if (typeof slot === "string") return { form: getOrBuildNoop(slot), gated };
5604
- if (isGateMarker(slot)) return normalizeSlot(slot.inner, ctx, true, depth + 1);
5605
- if (isLazyMarker(slot)) return normalizeSlot(lazyComputedFor(slot).value, ctx, gated, depth + 1);
5676
+ if (isGateMarker(slot)) return normalizeSlot(slot.inner, ctx, true, depth + 1, index);
5677
+ if (isLazyMarker(slot)) {
5678
+ return normalizeSlot(lazyComputedFor(slot, index).value, ctx, gated, depth + 1, index);
5679
+ }
5606
5680
  if (typeof slot === "function") {
5607
- const result = slot(ctx);
5608
- return normalizeSlot(result, ctx, gated, depth + 1);
5681
+ const result = callConsumerFn(
5682
+ () => slot(ctx),
5683
+ void 0,
5684
+ "wizard-slot",
5685
+ `steps[${index}]`
5686
+ );
5687
+ return normalizeSlot(result, ctx, gated, depth + 1, index);
5609
5688
  }
5610
5689
  if (isAnyForm(slot)) return { form: slot, gated };
5611
5690
  return void 0;
@@ -5614,7 +5693,7 @@ function useWizard(options) {
5614
5693
  const out = [];
5615
5694
  const seen = /* @__PURE__ */ new Set();
5616
5695
  for (let i = 0; i < rawSteps.length; i++) {
5617
- const norm = normalizeSlot(rawSteps[i], slotCtx, false, 0);
5696
+ const norm = normalizeSlot(rawSteps[i], slotCtx, false, 0, i);
5618
5697
  if (norm === void 0) continue;
5619
5698
  const { form, gated } = norm;
5620
5699
  if (seen.has(form.key)) {
@@ -5927,13 +6006,15 @@ function useWizard(options) {
5927
6006
  seedRef.value = resolved.value;
5928
6007
  applyGateSeed(resolved.value);
5929
6008
  } else {
5930
- const eager = resolved.factory();
6009
+ const eager = callConsumerFn(() => resolved.factory(), void 0, "wizard-default-statuses");
5931
6010
  if (eager instanceof Promise) {
5932
6011
  void eager.then((value) => {
5933
6012
  seedRef.value = value;
5934
6013
  applyGateSeed(value);
6014
+ }).catch((err) => {
6015
+ reportConsumerThrow("wizard-default-statuses", void 0, err);
5935
6016
  });
5936
- } else {
6017
+ } else if (eager !== void 0) {
5937
6018
  seedRef.value = eager;
5938
6019
  applyGateSeed(eager);
5939
6020
  }
@@ -6019,10 +6100,15 @@ function useWizard(options) {
6019
6100
  }
6020
6101
  const progressOverride = options.progress;
6021
6102
  const progress = computed(() => {
6103
+ const list = compiledSteps.value;
6022
6104
  if (progressOverride !== void 0) {
6023
- return progressOverride(compiledSteps.value);
6105
+ const override = callConsumerFn(
6106
+ () => progressOverride(list),
6107
+ null,
6108
+ "wizard-progress"
6109
+ );
6110
+ if (override !== null) return override;
6024
6111
  }
6025
- const list = compiledSteps.value;
6026
6112
  if (list.length === 0) return 0;
6027
6113
  let valid = 0;
6028
6114
  for (const step of list) {
@@ -6050,11 +6136,15 @@ function useWizard(options) {
6050
6136
  historyHandle.subscribe((value) => {
6051
6137
  urlMirror.value = value;
6052
6138
  });
6053
- const restoreCallback = options.restore === false ? void 0 : options.restore !== void 0 ? options.restore : () => {
6139
+ const consumerRestore = options.restore;
6140
+ const restoreCallback = consumerRestore === false ? void 0 : consumerRestore !== void 0 ? () => callConsumerFn(() => consumerRestore(), void 0, "wizard-restore") : () => {
6054
6141
  const value = urlMirror.value;
6055
6142
  return value === void 0 ? void 0 : { step: value };
6056
6143
  };
6057
- const persistCallback = options.persist === false ? void 0 : options.persist !== void 0 ? options.persist : (state) => {
6144
+ const consumerPersist = options.persist;
6145
+ const persistCallback = consumerPersist === false ? void 0 : consumerPersist !== void 0 ? (state) => {
6146
+ callConsumerFn(() => consumerPersist(state), void 0, "wizard-persist");
6147
+ } : (state) => {
6058
6148
  if (state.step === void 0) return;
6059
6149
  const current = historyHandle.read();
6060
6150
  const effectiveCurrent = current !== void 0 && isCompiledKey(current) ? current : firstKey();
@@ -6360,7 +6450,7 @@ function useWizard(options) {
6360
6450
  for (const key of keys) {
6361
6451
  const store = registry.forms.get(key);
6362
6452
  if (store === void 0) continue;
6363
- for (const cell of store.errorCells.values()) {
6453
+ for (const cell of store.errorCells().values()) {
6364
6454
  for (const err of cell.user) out.push(toWizardAggregateError(err, key));
6365
6455
  }
6366
6456
  }