Skip to content

fix(stores): close persistence fallbacks flagged in PR #358 review#359

Merged
cssbruno merged 1 commit into
feat/store-persistfrom
worktree-store-persist-fixes
Jun 13, 2026
Merged

fix(stores): close persistence fallbacks flagged in PR #358 review#359
cssbruno merged 1 commit into
feat/store-persistfrom
worktree-store-persist-fixes

Conversation

@cssbruno

Copy link
Copy Markdown
Owner

Addresses the three P2 review findings on #358 (client-side store persistence). Stacked on feat/store-persist so it contains only the fixes; merge into that branch to resolve the review comments. All three are the same class — persisted-store behavior silently depending on navigation history, or a check that skipped data it should cover.

1. Reject same-name stores with different persist scopes

validate_stores.go only compared struct shape, so two pages persisting the same store name with the same shape but different scopes (persist "local" vs persist "session") passed silently. They share one storage key, and the runtime keeps whichever scope initialized first — so the effective backend depended on navigation order. Now warns with page_store_persist_scope_conflict (registered in the diagnostics registry + explain entry).

2. Preserve persistence when a store is already initialized

registry.init early-returned for an already-initialized store, so SPA-navigating from a page with an unpersisted cart to a page that declares cart as persist "local" silently dropped the persist config — subsequent writes never reached storage, while a direct load did persist. init now adopts the persist config for an existing-but-unpersisted store and restores its saved value, so persistence no longer depends on which route loaded first. Conflicting scopes stay first-wins (and are reported by fix #1), so navigation can't thrash storage.

3. Check nested persisted fields for secret-like names

The secret-field check scanned only top-level fields, so a persisted store nesting e.g. Profile.Token wrote a credential-shaped value to localStorage/sessionStorage with no page_store_persist_secret_field warning. It now scans nested field paths from gotypes.Struct.FieldTypes (slice/array markers stripped, deduped, sorted), reporting the full path (Account.Token).

Tests

  • internal/compiler: TestValidateWarnsOnPersistedStoreScopeConflict, TestValidatePageWarnsOnNestedPersistedSecretField (+ ProfileState/Credentials nested fixture in testfixture/islands).
  • internal/buildgen: new step in the Node store-runtime harness asserting persistence adoption + storage write-through across SPA navigation.
  • Full go test ./... (55 non-example packages) green; gofmt + vet clean.

Docs

CHANGELOG, docs/language/syntax.md, and docs/language/components.md updated for the new diagnostic, nested-secret scanning, and the adoption behavior.

Three same-class silent-fallback issues where persisted page-store
behavior depended on navigation history or skipped a check:

1. Mismatched persist scopes (validate_stores.go): two pages persisting
   the same store name with the same shape but different scopes (local
   vs session) share one storage key. The runtime keeps whichever scope
   initialized first, so the effective backend depended on nav order.
   Now warns with page_store_persist_scope_conflict (registered +
   explain entry).

2. Dropped persist config on re-hydration (runtime_assets.go): when a
   store was first declared without persistence and a later route
   declared it persisted, registry.init early-returned and silently
   discarded the persist config, so writes never reached storage. init
   now adopts the persist config for an existing-but-unpersisted store
   and restores the saved value, independent of navigation order.

3. Nested secret fields (validate_page.go): the secret-field check only
   scanned top-level fields, so a persisted store nesting e.g.
   Profile.Token wrote a credential-shaped value to browser storage with
   no warning. It now scans nested field paths from FieldTypes.

Tests: scope-conflict + nested-secret compiler tests, a node
store-runtime step asserting persistence adoption across SPA
navigation, and a nested-secret fixture type. CHANGELOG, docs
(syntax/components), and explain updated. Full non-example suite green.
@cssbruno cssbruno merged commit e44826e into feat/store-persist Jun 13, 2026
1 check passed
@cssbruno cssbruno deleted the worktree-store-persist-fixes branch June 13, 2026 15:24

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32c10568f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +149 to +151
if (persist && persist.scope && persist.key && persist.version && !registry.persist[name]) {
registry.persist[name] = persist;
const restored = readPersisted(persist, registry.fields[name]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the later seed fields when adopting persistence

When SPA navigation first initializes a store without persistence and later reaches a persisted declaration with the same store name but a different shape, this adoption path restores and later writes using registry.fields[name] from the first, unpersisted seed rather than the persisted route's seed. That can drop all fields from the saved blob and then overwrite storage under the persisted route's version with the wrong field set, corrupting persisted state; either update the registry fields/seeds from state during adoption or reject/warn on unpersisted-to-persisted shape mismatches.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant