frontend-patterns/research/reviews/ZulipTargetLookupCallPath.md
tegwick fad8abdc2c Record LEARN-0017: traced Zulip target-lookup/anchor guard gap (corroboration)
Second cycle in comparison group B-independent-navigation-source-v1, a
naturally-motivated follow-up to LEARN-0014's ZN07 finding rather than a
manufactured repetition. Traces the target-message lookup and anchor-
navigation fetch continuation to their unconditional completion points in
message_fetch.ts: neither participates in the opt-in current-list-identity
guard used elsewhere in the same file, and no located test covers a
stale-navigation scenario. No reproduction or defect claim; static source
read only. CAND-0009/CAND-0010 evidence and gaps updated accordingly.

Declined to reopen the paused local navigation fixture to force a third
cycle in an existing group, per the prior agreed pause and WP-0006 T03's
own rule against manufacturing cycles to hit a count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 290719@bnt-lap001
Assistant-Session: 55cbe504-1bfe-4932-b761-29016523c87b
2026-09-27 23:21:02 +02:00

7.7 KiB
Raw Permalink Blame History

Zulip target-lookup and anchor-continuation guard trace — 2026-09-27

LEARN-0017; Codex author-model review. Protocol committed as 884803c before this inspection. Stream B, comparison group B-independent-navigation-source-v1 — second cycle in this group, following LEARN-0014. Static source evidence only: no application build, browser run, upstream test execution or inference call.

Pinned Zulip revision 5b06fa3d34f28fa01f459fbf3ba053e3c0858e9c, deliberately reused from the earlier navigation and acceptance/projection studies, not claimed latest. Source manifest records four files, raw SHA256 and reviewed ranges. This is the same Zulip family already known to this research through other paths, not a new independent family.

Question

ZN07 (ZulipNavigationSourceStudy.md) located the auxiliary target-message lookup and the anchor-navigation fetch continuation and noted they have "no visible captured-current-list guard like ZN02," but did not trace their actual callers or check whether an existing test covers a stale-navigation race for either path. This cycle traces both call paths to their concrete completion point and checks test coverage.

Located mechanism

Claim Exact pinned locators Observed code Interpretation / limit
ZT01 — target-message lookup channel.get to /json/messages/:id On success or error, the callback calls show(raw_terms, {...opts, fetched_target_message: true}) unconditionally — there is no check that the user is still on the same narrow/hash that triggered the lookup. A completed lookup re-enters the main show function regardless of intervening navigation. show itself resets UI state on entry, so a stale re-entry would still apply its own reset, but no code at this call site rejects a stale re-entry before doing so.
ZT02 — anchor-navigation continuation navigate_to_anchor_message / load_messages_around_anchor The continuation closure (select_anchor_using_data) captures message_lists.current and a freshly constructed msg_list_data at call time, then, once the fetch completes, unconditionally calls message_lists.update_current_message_list(...) and select_msg_id(...). If the user navigates away while load_messages_around_anchor is in flight, the completion still installs a message list built from the pre-navigation message_lists.current as the new current list. Nothing in this path re-checks that the captured list is still current before installing it.
ZT03 — where the continuation is actually invoked process_result calls opts.cont(data, opts) unconditionally whenever opts.cont !== undefined. load_messages_around_anchor never sets opts.msg_list (only opts.msg_list_data), so this call path does not go through the opts.msg_list !== message_lists.current guard that exists elsewhere (see ZT04). The continuation always fires once the request resolves.
ZT04 — the guard that does exist, and why it doesn't apply here load_messages error path rejects further processing when opts.msg_list !== undefined && opts.msg_list !== message_lists.current && !opts.msg_list.is_combined_feed_view; get_messages_success has an analogous success-path check. Both guards are conditioned on opts.msg_list, which the anchor-fetch caller never supplies. The mechanism ZN02 relied on (list-identity checking before continuing) exists in this file, but is opt-in per caller, not automatic for every fetch that carries a cont callback.

Test coverage

  • web/tests/message_fetch.test.cjs is 40 lines total (already reviewed in full at ZN08); it exercises get_parameters_for_message_fetch_api's date-anchor parameter handling only. No test in this file exercises opts.cont, process_result, or a stale-message_lists.current scenario.
  • web/tests/message_view.test.cjs mocks message_fetch.load_messages_around_anchor directly (bypassing message_fetch.ts entirely) in its fast_track_current_msg_list_to_anchor tests (L1106–L1244); the mock invokes the callback synchronously with no intervening navigation. No occurrence of target_id or fetched_target_message was found anywhere in this file, so the target-message-lookup path (ZT01) has no located test coverage at all.

Interpretation and boundary

This corroborates and sharpens ZN07 rather than revising it: the two flagged continuations genuinely lack the kind of captured-list guard that protects message_view.ts's main narrow-fetch continuation (ZN02) and that message_fetch.ts itself implements for callers that pass opts.msg_list (ZT04). The absence is now traced to a concrete cause — load_messages_around_anchor and the target-lookup channel.get simply don't participate in that opt-in mechanism — rather than only observed as "no visible guard." Neither path has any located automated test coverage for the stale-navigation case.

This is not a reproduced race, a defect report, or a claim about runtime behavior: show() itself performs its own state reset on entry (see ZT01), which may or may not make a stale re-entry externally observable as wrong behavior — that requires actual execution, which is out of scope here. No production dependency, upstream message, or bug report follows from a source trace. Do not treat this as established misbehavior without reproduction.

Pattern disposition: corroboration, no promotion

CAND-0009 (URL-addressable State) and CAND-0010 (Progressive Enhancement) remain draft/hypothesis; no candidate, relation or maturity change. The existing "Zulip auxiliary target/anchor callbacks... remain unverified" gap is sharpened, not resolved: it is now specifically "no captured-current-list guard exists on these two paths, and no test covers the gap," rather than a general unknown. Independent full-contract use counts stay unassessed.

Scope closure and follow-up

Four files, all already implicated by ZN07; no further callees were fetched. Files were inspected at the manifest's ranges except the two full test files, which were short enough to review completely. No upstream code or frozen evaluation artifact changed. Tests were read, not executed.

Effort receipt records active source work; token/cost unknown. This is the second cycle in comparison group B-independent-navigation-source-v1 (with LEARN-0014); still short of the three comparable cycles WP-0006 T03 needs before a returns review of this specific group, and the workplan's own rule against manufacturing cycles to reach that count still applies — a third cycle here should only happen if a genuinely useful next question arises, not to close the count.

WP-0003 T01 follow-up, if pursued: before any defect or race claim, a preregistered reproduction would need to actually run Zulip's frontend code (not just read it) with a controlled clock/interception to hold the target-lookup or anchor-fetch response while installing a competing navigation — outside the static-source protocol used here. T02 retains independent human/AT and offline-case obligations. WP-0006 T03 retains review-date/comparable-window work; the local authored navigation fixture remains paused.