Skip to content

Keep a linked collection's new saves out of the Queue's inbox - #703

Merged
disnet merged 3 commits into
mainfrom
claude/project-thread-jmlnvb
Oct 27, 2026
Merged

disnet merged 3 commits into
mainfrom
claude/project-thread-jmlnvb

Conversation

@disnet

@disnet disnet commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Requested by Tim · project thread

What

When you linked or imported a Semble/Margin collection to the Shelf, every article you hadn't saved yet became a new save. New saves start in the Queue's inbox, so a 200-item collection put 200 items in the inbox.

Now the reconciler in services/shelf-link.ts ("added there") archives the saves it creates, using archiveNewSave. Each new save gets an archived label under its rkey, plus its guid when it has one. The guid is the key the Queue card and the sidebar count check for a stub. The label is written in the same batch with INSERT … SELECT … WHERE rkey = <new rkey>. If the save already existed, the saved_articles insert does nothing and no label is written, so saves you made yourself keep their Queue state.

This follows the Shelf plan's existing rule: archiving is how a save leaves the Queue while staying on the Shelf. Tim chose this over adding a new "Shelf-only" state. Imported items show the existing "Archived" chip on the Shelf.

Client: after a sync that brought in saves, shelfStore.refreshAfterSync pulls the label delta before it reloads saves, so the new items don't flash through the inbox.

Docs: saving-and-highlights.md and SHELF_PLAN.md are updated.

Tests

  • shelf-link.spec.ts:
    • an imported save is archived, and your existing saves aren't
    • an imported save with a guid is archived under both keys
    • a save that predates its join key isn't archived
    • reset() now clears item_labels_cache, so labels no longer leak between tests
  • The shelf-link and shelf-collections specs pass (49 tests). frontend npm run check is clean.

Review (code-review skill at high effort, plus an independent adversarial agent)

Fixed

  • Stale label timestamp (reproduced). The labels were stamped with the reconcile's start time, before a snapshot that can run for many seconds. Another label pulled in the meantime could move a client's delta cursor past them, and since there is no full reconcile, they would never be delivered. They are now stamped with unixepoch() when the batch runs.
  • Label key. With only the rkey label, the Queue card, the reader bar (single-key isArchived(itemKey)) and the sidebar inbox count all missed stubs that have a guid. The label is now also written under the guid. Keeping the rkey copy means a tombstone already sitting on the guid can't push the save back into the inbox for alias-aware views.
  • Test isolation. Labels leaked between tests (see above).

Left alone

  • The save and its label could land in different runChunked batches, and if the second batch failed, the save would stay unarchived. This is rare, the cost is one inbox item, and fixing it means restructuring the batching.
  • pullDelta() can reuse a pull that is already in flight and started before the sync committed. That shows a brief flash until the next pull. Background reconciles and other devices have the same race.
  • archivedCount doesn't count rkey-keyed labels. It has no callers outside the store.
  • An imported save that later leaves its collection (removed over there, or the collection is deleted) ends up only in the Archive. That is consistent with "archived = filed", and it is your choice to keep it.
  • The label insert duplicates the shape of saved-import.ts's labelInsert. A shared helper would touch an unrelated route.
  • A stub created by whole-Library backing (backing/sync.ts) still lands in the inbox. That is a separate feature.

🤖 Generated with Claude Code

https://claude.ai/code/session_014mh1a3RgSwf33Y5jeWaudB

A Semble/Margin collection linked or imported to the Shelf turned every
article the reader hadn't saved into a new save in the Queue's inbox. The
reconciler now archives the saves it creates (keyed on the new rkey, so an
existing save keeps its Queue state), and the client pulls the label delta
before reloading saves after a sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mh1a3RgSwf33Y5jeWaudB
@disnet disnet self-assigned this Oct 27, 2026
…uid too

A label stamped with the reconcile's start time could sit behind a client's
label-delta cursor for good, and the Queue card checks a stub by its guid.
Tests now clear item labels between cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mh1a3RgSwf33Y5jeWaudB
@disnet
disnet marked this pull request as ready for review October 27, 2026 05:17
refreshAfterSync now pulls the label delta before reloading saves; the
test's itemLabels mock had no pullDelta, so every sync that brought saves
in threw. Asserts the order too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mh1a3RgSwf33Y5jeWaudB
@disnet
disnet merged commit 65e1eda into main Oct 27, 2026
14 checks passed
@disnet
disnet deleted the claude/project-thread-jmlnvb branch October 27, 2026 05:25
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.

2 participants