fix(OneDataCollection): guard persisted state and the Save view chip - #5291
fix(OneDataCollection): guard persisted state and the Save view chip#5291sauldom102 wants to merge 1 commit into
Conversation
Two holes around the persisted collection status:
Stored state was applied verbatim on hydration — `featureProvider.setValue`
straight from the handler payload, with no check against the collection's
declared definition. Persisted state that predates a schema change (renamed
or removed filters, a dropped visualization), or that was written by a
different collection under the same key, went directly into the data adapter.
`seedFromStorage` already validates the same payload for the item-navigation
seeding path; hydration now does too, via `pruneStoredStatus`.
The "Save view" chip was offered from state divergence alone, never checking
that there is somewhere to persist a view. On a collection without an `id`
(or with `storage={false}`) the saved view lived in component state and was
lost on unmount, with no signal to the user. It is now gated on a resolved
storage key, as is the shared-link create dialog — the other way into the
save flow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🔍 Review policy: Code changeDefault rule: any other change needs one approval from f0-devs (rule 4). Required approvals
How this was decided
Policy source: |
✅ No untranslated copy addedEvery user-visible string in this PR comes from the i18n layer. Codebase total unchanged at 133. |
✅ No New Circular DependenciesNo new circular dependencies detected. Current count: 0 |
✅ Storybook docs — no pages lostEvery page reachable on Links point at this PR's Storybook build — browse the full Storybook. Snapshot of the Storybook index (docs pages + stories) compared against |
📦 Alpha Package Version PublishedUse Use |
🔍 Visual review for your branch is published 🔍Here are the links to: |
✅ No breaking public API changesNo public exports were removed, renamed, or had existing props/types changed in a breaking way compared to Comparing
|
♿ Accessibility (axe) — components changed in this PR7 issues across 4 stories — all non-blocking (
Scope: only stories in the files/component folders this PR changed. It can't yet flag downstream ripple from shared-code/token changes, or diff against |
Coverage Report for packages/react
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||
Description
Closes two holes around the persisted collection status. Hydration applied the stored payload verbatim, so state that no longer matches the collection — a renamed filter, a dropped visualization, or a payload written by a different collection under the same storage key — went straight into the data adapter; it is now validated against the declared definition first, the same way
seedFromStoragealready validates the same payload for the item-navigation seeding path. Separately, the "Save view" chip was offered from state divergence alone and never checked that a view could actually be persisted, so on a collection without anid(or withstorage={false}) a saved view lived in component state and vanished on unmount with no signal to the user. In the Factorial monorepo that is 149 of 226OneDataCollectioninstances, 74 of them page-level lists.Screenshots (if applicable)
No new UI. The one visual change is a removal: on a collection with no storage key, the dashed "Save view" chip no longer appears when the view diverges from its baseline. Collections that pass an
idare unaffected.Implementation details
fix: validate the stored status against the collection's declared definition before hydrating it
What gets checked
New
pruneStoredStatusruns on the payload returned by the storage handler, before anyfeatureProvider.setValue:{}is the user having cleared the filters and is honored; empty by pruning is schema drift, so the declared defaults survive instead.nullis preserved: that is an explicit "clear sorting", not drift.groupBy.A non-object payload from a misbehaving handler now yields an empty status instead of throwing on mount.
fix: stop offering "Save view" when there is nowhere to persist it
Why the chip and the shared link, both
presetActionStatenow returns"none"unless a storage key is resolved (idpresent andstorage !== false) — mirroringuseDataCollectionStorage, which is inactive under exactly those conditions. The shared-link (dc_shared_view) create dialog is gated on the same flag, since saving is the only way a shared view materializes and it was the other route into the save flow. The link param is still stripped either way, so a reload does not retry.This overlaps the existing
savingViewsDisabledopt-out but is not the same thing: that prop is a consumer choice, this is a capability check.test: cover
pruneStoredStatusper feature (23 cases) and the chip/shared-link gates in the presets suiteKnown remaining holes
Both are the same class of bug and out of scope here:
Datevalues that JSON round-trip into strings.idlands is the intended follow-up, and it depends on this PR to be safe.