fix: keep a design this build cannot open instead of erasing it - #54
Merged
xevrion merged 1 commit intoSep 5, 2026
Merged
Conversation
Every mutation on the shelf is read, change, write, and the read drops rows that fail to parse. That is right for display and wrong for storage: a row dropped on the way in was a row deleted on the way out. Saving an unrelated design erased someone else's, permanently, with nothing said, and so did renaming or deleting a different one. Deleting one row of two wiped both. readShelf now returns what parsed and what did not, and the three mutations carry the second half back into storage untouched. parseEntry is unchanged and still does one job. A row this build cannot open is not necessarily a row that is gone. It may be a newer format, or a design tripped by a bug a later build fixes, so keeping the bytes leaves that recovery possible where rewriting the shelf without them does not. That matters most for the duplicate-id designs in issue xevrion#46: that bug costs the design, this one made the loss permanent. The unreadable half is capped at MAX_SAVED for the same reason the readable half is. localStorage is a shared 5MB budget, and rows nobody can open must not grow without limit. Measured before the fix, two rows on the shelf with the first unopenable: saveDesign("Something new") -> [ "Something new", "Fine one" ] deleteDesign("b") -> [ ] and after it, the unopenable row is still there byte for byte in both cases. savedDesigns.rejected.test.ts pins it: that such a row stays off the shelf because it cannot be shown, that it survives a save, a rename, a delete and a save-over-by-name, that it comes back identical, and that the readable half still saves, renames, deletes and evicts at the cap exactly as before. Seven of the nine fail when the old read is put back. Closes xevrion#53.
|
@yashksaini-coder is attempting to deploy a commit to the whoarrryou's projects Team on Vercel. A member of the Team first needs to authorize it. |
Owner
|
Merged. Keeping the bytes rather than normalising them is the right call, and the duplicate-id fixture is a good choice given #46. Thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #53.
Every mutation on the shelf is read, change, write, and the read drops rows that fail to parse.
That is right for display and wrong for storage: a row dropped on the way in was a row deleted on
the way out. Saving an unrelated design erased someone else's, permanently, and so did renaming or
deleting a different one. Deleting one row of two wiped both.
Measured before, two rows with the first unopenable:
After, the unopenable row is still there in both cases, byte for byte.
What changed
readShelfreturns what parsed and what did not, andsaveDesign,deleteDesignandrenameDesigncarry the second half back into storage untouched.parseEntryis unchanged andstill does one job;
loadDesignskeeps its signature and is nowreadShelf().designs.The rows come back identical rather than normalised, which is the point of keeping them. A row
this build cannot open is not necessarily a row that is gone: it may be a newer format, or a
design tripped by a bug a later build fixes. That is the case for the duplicate-id designs in #46,
where the bug costs the design and this one made the loss permanent.
The unreadable half is capped at
MAX_SAVED, for the same reason the readable half is. Rowsnobody can open must not grow without limit in a shared 5MB budget, and past the cap the oldest
bytes go, which is the trade the readable half already makes.
What I did not change
The shelf still does not show these rows, because there is nothing useful to render for one. It
is worth deciding separately whether a row that says it cannot be opened would be better than a
row that is simply absent, since silence is what made this invisible in the first place. That is a
copy and design question rather than a storage one, so it is not in here.
Tests
savedDesigns.rejected.test.ts, nine cases. The fixture is a topology with duplicate node ids,which is the shape a palette-add produces after a reload, so this is the failure that actually
happens rather than hand-corrupted JSON.
It pins that such a row stays off the shelf, survives a save, a rename, a delete and a
save-over-by-name, and comes back identical; that the readable half still saves, renames, deletes
and evicts at the cap as before; and that the unreadable half stays bounded.
Seven of the nine fail when the old read is put back. The two that pass are the two that should:
the row is still hidden, and the readable half still behaves. Checked by reverting.
904 tests pass. typecheck, lint and format:check clean, lint on the same 26 warnings as main.