Skip to content

Control: an anchor link opens its thread at the anchored turn, which the host resolves - #1006

Merged
Tryanks merged 1 commit into
mainfrom
fix/thread-anchor-links
Oct 28, 2026
Merged

Tryanks merged 1 commit into
mainfrom
fix/thread-anchor-links

Conversation

@Tryanks

@Tryanks Tryanks commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Summary

Clicking a cited tcode-thread://<thread>#<position> link opened the thread at its tail. The client tried to find the turn by itself, as held.records[position - held.from]. That index is wrong for two reasons:

  • windows merge consecutive deltas and skip blank rows, so a record index is not a log position, and the index usually ran past the end of the records;
  • an anchor above the opening window was never paged in, because only content-search turns trigger load_pending_chat_history.

Only the host knows each record's row, so the host now resolves the anchor. The answer comes from the same IndexedLog that gives control anchors their positions.

 on_open_thread_anchor
   select_session_at_position
-    pending_chat_anchor = (thread, position)   # client: position - from → record index → fold prefix
+    select_session
+    query ThreadAnchorTurn { session_id, position }      # host: last entry recorded ≤ position → entry.turn
+      pending_chat_turn = (thread, turn)                 # the path palette search hits already use
+      load_pending_chat_history                          # pages earlier history in when needed
  • Query::ThreadAnchorTurn → QueryResponse::ThreadAnchorTurn(Option<usize>) (scoped per session like ReadItemOutput). It is noted as Unreleased above PROTOCOL_VERSION, and the version is not bumped (Principle 9).
  • pending_chat_anchor and its record-index fold are removed. A single cancellable anchor_turn_task replaces them, so a newer anchor or a palette jump supersedes an older one.

Evidence

New test chat::tests::an_anchor_link_opens_its_thread_at_the_anchored_turn. It uses a real in-process host and a 12-turn thread whose replies each stream as 40 deltas. It opens anchors to turn 1's user message (above the opening window) and to turn 9's (inside the window, with the thread already open), and asserts that the chat scrolls to and highlights each anchored turn.

  • Before (old store code, same test): FAIL — panicked: waiting for the anchored turn (the chat stays at the tail)
  • After: PASS

Checks run locally: cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, and nextest for tcode-protocol, tcode-runtime, tcode-ui and control-mcp (669 passed). Not run locally: the full workspace tests, the mobile and Web checks, and a click-through in the live app.

Merge Danger

Door: two-way. One new query and no persisted data.

Blast Radius: narrow. It covers opening anchor links only. A client and host from different commits of main would not share the new query; an older host answers it with an error, and the link then opens at the tail as before.

…the host resolves

A tcode-thread://<thread>#<position> link always opened at the tail. The
client mapped the position to a held record by subtracting the window's
start, but windows merge streamed deltas and skip blank rows, so the index
ran past the records; an anchor above the opening window was never paged
in at all. The host, which knows each record's row, now answers
ThreadAnchorTurn from the same index that numbers control anchors, and the
client opens the turn through the path content-search hits already use,
which pages earlier history in.
@Tryanks
Tryanks force-pushed the fix/thread-anchor-links branch from 199d59f to 45ac136 Compare October 28, 2026 04:55
@Tryanks
Tryanks merged commit 684869e into main Oct 28, 2026
13 checks passed
@Tryanks
Tryanks deleted the fix/thread-anchor-links branch October 28, 2026 05:06
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.

1 participant