notedeck · commit 3 of 5

Two halves of an open

A refactor that separates “which board does this note live on?” from “wait for it to fold, then pop the detail” — because a cross-app deep link only wants the first half.

4c4906ced6b5 headway:notedeck/cycle-crumble-jeans epic: obey-margin-scale +73 −34, one file

The epic behind this chain is a one-line bug report: clicking an inline headway card reference from another app needs two back presses, the first one landing on the headway board root. The cause is that one cross-app open pushes two global-nav entries — a bare app-switch entry, then a second one that Headway's own board → card diff emits a frame later. The fix (commit 5) is for the chrome to ask the target app for a route token and push one tagged entry.

This commit is the plumbing that makes that possible. Nothing user-visible changes.

01The shape of the problem

process_pending_open did four things in one straight line, and a deep link wants exactly the first two of them. Here is where the seam is:

BEFORE — one function, four steps 1. resolve the note kind → board + card 2. correct the board placed, not origin 3. switch + persist active, save_board_pref 4. fold? open_card else retry next frame the seam AFTER — the shared half is its own function activate_open_target(ctx, author, note_id) resolve → correct the board → switch + persist → Option<OpenTarget> { board, card, title } process_pending_open fold? open_card. retry. open_note_route mint a route token commit 3 (here) commit 4 — next

Step 2 is not an optimisation — it is a correctness step. A card's a tag records the board it was born on; a cross-board move changes only its placement. locate_card_in_boards finds where it actually is, and the deep link needs that answer just as much as the retry loop does.

02Why OpenTarget grew a title

A nav history entry carries a label. The in-app path gets one for free: by the time the board grid pushes a card entry, it is drawing that board, so card_title(&view, card) has a folded BoardView to read. A deep link has no such luck — it mints its route token during the very frame that switches boards, and a board you just switched to has not folded yet. Two different sources for the same string:

pathtitle fromavailable when
in-app
reconcile_nav → PushCard
card_title(&view, card) after the fold — the view is already on screen
cross-app
open_note_route
OpenTarget::title, off the event immediately — IssueEvent.subject needs no fold

Reading the subject straight off the note makes it a snapshot that can lag a later rename. That is not a regression: every HeadwayRoute title already works this way, for the same reason — nav_title is handed only the token, with no Ndb handle to re-resolve through. A back/forward label wants the name the entry had when you opened it anyway.

the one wart

Nothing reads title until commit 4, so it ships behind an #[allow(dead_code)] with a comment naming its reader. The alternative was to defer the field into the commit that consumes it, but it belongs with resolve_open_target — the resolve half is what this commit is about, and commit 4's spec already reaches for target.title.

03Two behavioural deltas, both benign

Collapsing the straight line into a call changes the timing slightly. Both changes are in the same direction:

The self.active != target.board test inside activate_open_target still matters, and not just for tidiness: process_pending_open re-runs every frame until the fold lands, and an unguarded switch would republish the encrypted board preference on each one of those frames.

what stays, deliberately

Headway::open, pending_open and process_pending_open are all still here. Commit 4 keeps the pending request alongside the route token: the retry loop corrects active for a card whose placement hasn't folded in, and it is the only open path in a chrome-less embedding (the nav_token: None branch the snapshot harness drives). It costs no extra history entry, because render_nav seeds before = Card(id) from the token and the retry's open_card sets that same card — so reconcile_nav sees before == after and pushes nothing.

04Verification

./scripts/ci-local passed whole: changelog-check, lint, linux-test, android, snapshot-test. The epic's drift number came out at 419 passing lib tests (351 notedeck + 12 notedeck_chrome + 56 notedeck_headway), unchanged from the 802f471a860f baseline. notedeck_headway's integration suite — the flaky one — ran clean at 10 passed, including all six existing nav tests (chrome_nav_loop_card_open_then_back_returns_to_board, a_card_that_has_not_folded_in_keeps_its_route_entry, and friends). cargo clippy reports nothing new; the two needless_borrow warnings in notedeck/src/note/context.rs and the private-intra-doc-link warnings in notedeck_headway/src/ui.rs all predate this commit.

not verified

No new test was added, and none of the above exercises the new seam as a seam — the existing coverage drives process_pending_open, which is the half that did not change shape. The refactor is only load-bearing once open_note_route calls activate_open_target, and that is commit 4, whose headline test (chrome_nav_loop_cross_app_open_pushes_one_entry) is where the split is actually proven. The one-frame-earlier fold path in particular is reasoned about, not observed: no test distinguishes a synchronous fold from a deferred one.