[{"data":1,"prerenderedAt":449},["ShallowReactive",2],{"content-\u002Fdocs\u002Fpersistence\u002Fedge-cases":3},{"id":4,"title":5,"body":6,"description":427,"extension":428,"meta":429,"metaRows":430,"navigation":443,"path":444,"seo":445,"source":446,"stem":447,"__hash__":448},"docs\u002Fdocs\u002Fpersistence\u002Fedge-cases.md","Edge cases & hydration",{"type":7,"value":8,"toc":416},"minimark",[9,14,21,24,40,45,50,53,63,74,97,100,111,115,121,136,147,151,162,176,197,201,204,223,226,230,237,248,252,262,342,352,356,391,395],[10,11,13],"h1",{"id":12},"persistence-edge-cases-hydration","Persistence edge cases & hydration",[15,16,17],"blockquote",{},[18,19,20],"p",{},"The gotchas worth knowing before you ship: how schema changes drop stale drafts, how hydration races first paint, and how concurrent writes resolve.",[22,23],"docs-meta-table",{},[18,25,26,27,31,32,35,36,39],{},"This form persists to ",[28,29,30],"code",{},"'session'"," with ",[28,33,34],{},"clearOnSubmitSuccess: false"," and ",[28,37,38],{},"score"," is a numeric field. Type a score, add tags, refresh: values hydrate before the first render and the draft survives a successful submit. Behind the scenes, the orphan-cleanup pass runs every mount, the schema fingerprint gates the read, and the numeric blank handling stays consistent across reload.",[41,42],"docs-demo",{"label":43,"slug":44},"Edge Cases Demo","persistence-edge-cases",[46,47,49],"h2",{"id":48},"schema-change-auto-invalidation","Schema-change auto-invalidation",[18,51,52],{},"Storage keys carry the schema's structural fingerprint:",[54,55,61],"pre",{"className":56,"code":58,"language":59,"meta":60},[57],"language-text","attaform:signup:7c3a0b   ← key on disk\n                       └────┘\n                       fingerprint of the current schema\n","text","",[28,62,58],{"__ignoreMap":60},[18,64,65,66,69,70,73],{},"When the schema changes shape (adding \u002F removing \u002F renaming a field, changing a leaf type, restructuring nested objects) the fingerprint changes. New writes go to a new key (",[28,67,68],{},"attaform:signup:9d2b1f","); the old key (",[28,71,72],{},"attaform:signup:7c3a0b",") becomes unreachable.",[18,75,76,77,80,81,84,85,88,89,92,93,96],{},"On the next mount, the orphan-cleanup pass enumerates keys under ",[28,78,79],{},"attaform:signup"," via ",[28,82,83],{},"FormStorage.listKeys",", keeps the current-fingerprint entry, and removes the rest. No manual ",[28,86,87],{},"version"," bump, no possibility of forgetting it, and refinements (",[28,90,91],{},".refine()",", ",[28,94,95],{},".transform()",") collapse to opaque sentinels in the fingerprint so tightening a refinement doesn't unnecessarily drop drafts.",[18,98,99],{},"Malformed entries (corrupted JSON, envelope-version mismatch, anything not matching the payload contract) wipe on read. \"Truly absent\" entries are a no-op; the wipe fires only when there's actually something to clean.",[18,101,102,103,106,107,110],{},"To force-invalidate without changing the schema (an unrelated tweak you want users to retest from scratch), call ",[28,104,105],{},"form.clearPersistedDraft()"," at mount or wrap the schema in a thin no-op layer that perturbs the fingerprint. Attaform deliberately doesn't expose a ",[28,108,109],{},"forceVersion"," knob; the schema fingerprint captures every legitimate \"shape changed\" signal.",[46,112,114],{"id":113},"hydration-timing","Hydration timing",[54,116,119],{"className":117,"code":118,"language":59,"meta":60},[57],"sync backends (local \u002F session)        async backend (indexeddb)\n─────────────────────────────          ─────────────────────────────\nmount                                  mount\n└─ pre-render hydration                ├─ render with schema defaults\n   └─ first render with values         └─ post-mount hydration\n                                          └─ render with values\n",[28,120,118],{"__ignoreMap":60},[18,122,123,35,126,128,129,132,133,135],{},[28,124,125],{},"'local'",[28,127,30],{}," hydrate before the first render: no flash, the user never sees the schema's defaults. ",[28,130,131],{},"'indexeddb'"," is async; the first paint uses the schema defaults, then the hydration applies on the next microtask. For most forms this is invisible; for first-paint-critical surfaces (a credit-card form on a checkout funnel), ",[28,134,30],{}," is the safer choice.",[18,137,138,139,142,143,146],{},"Numeric blank-state survives reload symmetrically. A field with no ",[28,140,141],{},"defaultValues"," entry and a numeric type starts blank-marked at construction; the persisted envelope includes the ",[28,144,145],{},"blankPaths"," set, so a numeric field cleared by the user stays visually empty after rehydration instead of resurrecting its slim default.",[46,148,150],{"id":149},"cross-tab-semantics","Cross-tab semantics",[18,152,153,156,157,161],{},[28,154,155],{},"localStorage"," writes from two tabs race; the persistence module does NOT coordinate. ",[158,159,160],"strong",{},"Last-writer-wins."," Two cases worth knowing:",[163,164,165,169],"ul",{},[166,167,168],"li",{},"Tab A is mid-debounce; Tab B writes; Tab A's debounce overwrites.",[166,170,171,172,175],{},"The persistence module doesn't subscribe to the ",[28,173,174],{},"storage"," event; fresh writes from another tab don't replay into the live form.",[18,177,178,179,181,182,187,188,192,193,196],{},"If multi-tab consistency matters, use ",[28,180,30],{}," (tab-scoped) or layer ",[183,184,186],"a",{"href":185},"\u002Fdocs\u002Fcross-cutting-state\u002Fmulti-tab-sync","Multi-tab sync"," on top. That ",[189,190,191],"em",{},"does"," mirror live edits across tabs via ",[28,194,195],{},"BroadcastChannel",", and works orthogonally to persistence.",[46,198,200],{"id":199},"cross-sfc-behavior","Cross-SFC behavior",[18,202,203],{},"Two SFCs binding the same path under the same form key share the FormStore and the persistence registry. Opt-ins are per-DOM-element, not per-SFC:",[163,205,206,213,220],{},[166,207,208,209,212],{},"SFC A renders ",[28,210,211],{},"\u003Cinput v-register=\"form.register('email', { persist: true })\">","; A's element opted in.",[166,214,215,216,219],{},"SFC B renders ",[28,217,218],{},"\u003Cinput v-register=\"form.register('email')\">"," (no opt-in); B's element NOT opted in.",[166,221,222],{},"Typing in A persists. Typing in B doesn't.",[18,224,225],{},"Unmount SFC A and B's typing stops persisting (no opt-ins remain). Re-mount A and the new element gets a fresh opt-in. The registry tracks elements via WeakMap; rapid mount\u002Funmount cycles auto-clean.",[46,227,229],{"id":228},"storage-degradation","Storage degradation",[18,231,232,233,236],{},"Backend failures (quota exceeded, Safari private mode, IndexedDB blocked) log a one-shot ",[28,234,235],{},"console.warn"," per form in dev mode and are silently swallowed in production. The form stays usable; writes just don't land.",[18,238,239,240,243,244,247],{},"If persistence appears to drop writes in dev, check the console first. For surfaces where storage failures need user-facing recovery (a wizard with a \"Save Failed\" toast), wrap ",[28,241,242],{},"form.persist()"," in your own ",[28,245,246],{},"try","; that surface throws on adapter errors, unlike the implicit debounced writes.",[46,249,251],{"id":250},"component-binding-patterns","Component binding patterns",[18,253,254,257,258,261],{},[28,255,256],{},"\u003CMyComponent v-register=\"register('name')\" \u002F>"," works through four patterns, each appropriate for different component shapes. The recommended pattern for most cases is ",[28,259,260],{},"useRegister",":",[263,264,265,281],"table",{},[266,267,268],"thead",{},[269,270,271,275,278],"tr",{},[272,273,274],"th",{},"Pattern",[272,276,277],{},"When to use",[272,279,280],{},"Persistence works?",[282,283,284,303,316,329],"tbody",{},[269,285,286,290,300],{},[287,288,289],"td",{},"Native input root",[287,291,292,293,296,297],{},"Component's root is ",[28,294,295],{},"\u003Cinput>"," \u002F ",[28,298,299],{},"\u003Cselect>",[287,301,302],{},"Yes, directly",[269,304,305,310,313],{},[287,306,307,309],{},[28,308,260],{}," (preferred)",[287,311,312],{},"Component wraps a native input in styling",[287,314,315],{},"Yes, on the inner native element",[269,317,318,323,326],{},[287,319,320],{},[28,321,322],{},"injectForm",[287,324,325],{},"Compound component touching multiple fields",[287,327,328],{},"Yes, per-register call inside",[269,330,331,336,339],{},[287,332,333],{},[28,334,335],{},"assignKey",[287,337,338],{},"Web Components \u002F non-Vue elements",[287,340,341],{},"Yes, but you wire the listeners",[18,343,344,345,351],{},"The full walkthrough on each pattern lives in ",[183,346,348,350],{"href":347},"\u002Fdocs\u002Fbinding-inputs\u002Fuse-register",[28,349,260],{}," for custom components",".",[46,353,355],{"id":354},"what-persistence-is-not-for","What persistence is NOT for",[163,357,358,369,375,381],{},[166,359,360,363,364,368],{},[158,361,362],{},"Sensitive data."," See ",[183,365,367],{"href":366},"\u002Fdocs\u002Fpersistence\u002Fsensitive-names","Sensitive-name protection",": the heuristic blocks the obvious cases, but per-field opt-in is the real defense.",[166,370,371,374],{},[158,372,373],{},"Authoritative state."," Persistence is for draft UX, not source-of-truth. The server still owns the canonical record.",[166,376,377,380],{},[158,378,379],{},"Cross-form coordination."," Each form persists independently. Multiple forms can share a key (sharing a FormStore and the persistence entry), but they're still one form to the persistence layer.",[166,382,383,386,387,390],{},[158,384,385],{},"Schema migrations."," Auto-invalidation handles the common case. To rename a field without losing state, read the raw entry before the schema change ships and massage it into the new shape before calling ",[28,388,389],{},"reset()",". Attaform deliberately doesn't ship a renaming-aware migration helper; renames are a write-once transformation the consumer owns.",[46,392,394],{"id":393},"where-to-next","Where to next",[163,396,397,402,409],{},[166,398,399,401],{},[183,400,186],{"href":185},": live convergence across same-origin tabs (different problem; complementary to persistence).",[166,403,404,408],{},[183,405,406],{"href":347},[28,407,260],{},": the recommended pattern for custom components, persistence-aware.",[166,410,411,415],{},[183,412,414],{"href":413},"\u002Fdocs\u002Fpersistence\u002Foverview","Persistence overview",": the two-gate model and schema-aware hydration in one page.",{"title":60,"searchDepth":417,"depth":417,"links":418},2,[419,420,421,422,423,424,425,426],{"id":48,"depth":417,"text":49},{"id":113,"depth":417,"text":114},{"id":149,"depth":417,"text":150},{"id":199,"depth":417,"text":200},{"id":228,"depth":417,"text":229},{"id":250,"depth":417,"text":251},{"id":354,"depth":417,"text":355},{"id":393,"depth":417,"text":394},"Schema-change auto-invalidation, the four component-binding patterns, cross-tab last-writer-wins, storage degradation, and how hydration races with the first render.","md",{},[431,434,437,440],{"label":432,"value":433},"Category","Module",{"label":435,"value":436},"Schema-change invalidation","automatic via fingerprint",{"label":438,"value":439},"Cross-tab","last-writer-wins (no coordination)",{"label":441,"value":442},"Hydration","pre-first-render (sync backends) \u002F post-mount (indexeddb)",true,"\u002Fdocs\u002Fpersistence\u002Fedge-cases",{"title":5,"description":427},null,"docs\u002Fpersistence\u002Fedge-cases","hgHHuIUpnpACSzRPRHsauT71sQp0wdvIIfPw5Yx1lHM",1780949761164]