Conversation
Deleting a context - locally, from another device, or over MCP - only marked the context deleted. Its stashes kept pointing at the dead id, so no view showed them (every filter compares against contextId || "default"), yet they were still stored, counted, exported and synced. The cloud had the same gap: it never deleted a context's stashes either, and a stash with no context at all - an unknown id on create, an explicit null on move, a device pushing a context this server had not seen - was stored with context_id NULL, which the app renders as Default but the server never queries as "default". Both sides now apply the same two rules. No context, or one that does not exist, means the default context: every installation creates it and nothing can delete it. A stash whose context is deleted is deleted with it. Deleting a context now deletes the stashes in it, and the confirmation dialog says how many. A database trigger refuses a NULL context_id on both sides rather than a NOT NULL column, since the column has been nullable since the first migration and adding the constraint would mean rebuilding the table under attachments' ON DELETE CASCADE. Every stash write now runs enforce_context_rules(): on startup, after importing contexts or stashes from a sync pull, and after a local context deletion. It moves stashes with no valid context to Default and deletes any still sitting under an already-deleted one, returning what it deleted so the caller can remove their cached attachment files. Matches the migration and rules just committed to cloud/, so a stash ends up in the same place whichever side writes it. Ran the Rust suite (159 tests, 6 new) and the frontend suite (219 tests, 2 new), plus `tsc` and `svelte-check` with zero errors. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkYvnuq8Y4nYD1jSCi9GCR
… none Exporting a context wrote each attachment as <8 chars of stash id>_<name>. Two pasted screenshots in one stash, both image.png, collided on that name; the zip writer refused the second write and the export died part way with the first file written and the dialog left open with nothing said. Files are now keyed by attachment id, which cannot repeat, with the original name kept after it so a particular file can still be found by eye. An export also used to drop any attachment not yet downloaded to this device (synced from elsewhere, background download still pending) while the document went on linking to it - an archive that looked complete and was not. It now refuses instead, names what is missing, and the dialog fetches those files and tries again before giving up with a real error. Separately, importing never found a single attachment: the importer looked for each file under the *new* id an imported stash gets, which is never the id the archive was written with. The markdown now carries the archive path itself, which the importer reads back directly. Both the new attachment-id naming and every older stash-id-prefixed archive are recognised. A copy that fails now rolls back what it had already placed, and both dialogs show an error instead of failing to the console with nothing on screen. Added i18n for the save/pick file-type labels, which were hardcoded English. Rust: 164 tests (new: the name clash, refusing an incomplete archive, older archives still resolving, a copy failure rolling back). Frontend: 219 tests, tsc and svelte-check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Deleting a context - locally, from another device, or over MCP - only marked the context deleted. Its stashes kept pointing at the dead id, so no view showed them (every filter compares against contextId || "default"), yet they were still stored, counted, exported and synced. The cloud had the same gap: it never deleted a context's stashes either, and a stash with no context at all - an unknown id on create, an explicit null on move, a device pushing a context this server had not seen - was stored with context_id NULL, which the app renders as Default but the server never queries as "default".
Both sides now apply the same two rules. No context, or one that does not exist, means the default context: every installation creates it and nothing can delete it. A stash whose context is deleted is deleted with it. Deleting a context now deletes the stashes in it, and the confirmation dialog says how many. A database trigger refuses a NULL context_id on both sides rather than a NOT NULL column, since the column has been nullable since the first migration and adding the constraint would mean rebuilding the table under attachments' ON DELETE CASCADE.
Every stash write now runs enforce_context_rules(): on startup, after importing contexts or stashes from a sync pull, and after a local context deletion. It moves stashes with no valid context to Default and deletes any still sitting under an already-deleted one, returning what it deleted so the caller can remove their cached attachment files.
Matches the migration and rules just committed to cloud/, so a stash ends up in the same place whichever side writes it.
Ran the Rust suite (159 tests, 6 new) and the frontend suite (219 tests, 2 new), plus
tscandsvelte-checkwith zero errors.Claude-Session: https://claude.ai/code/session_01KkYvnuq8Y4nYD1jSCi9GCR