Skip to content

feat(dashboard): parent-link, genuine-continuation badge, and modal perf fixes - #12448

Merged
diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
hartmark:feat-conversation-continuation-tracking
Sep 3, 2026
Merged

diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
hartmark:feat-conversation-continuation-tracking

Conversation

@hartmark

@hartmark hartmark commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Three related dashboard gaps, all on the same continuation-store surface:

  1. No way to click through from a continuation request to the parent response it continued.
  2. No way to tell, at a glance, which conversations on /dashboard/conversations are genuinely using HTTP continuation (previous_response_id) vs. just being tracked as multi-turn by the client-side content-hash tracker while still resending full history.
  3. The conversation modal's auto-refresh (configurable down to 1s) re-fetched and re-annotated the entire up-to-100-row list every tick just to keep one open conversation's summary fields fresh.

Why This Change Was Made

  1. Parent link: extractPreviousResponseId/resolveCallLogIdByResponseId added to responsesContinuationStore.ts, wired into /api/logs/[id] — a request that used continuation now links to the call-log row that produced its parent response.
  2. Genuine-continuation badge: isGenuineContinuationTurn() reads the row's own artifact to check whether the latest turn's previous_response_id actually resolved server-side. Permanently memoized by artifactRelPath — the underlying artifact is immutable once written, so the answer can never change once computed. Without this, the 1s auto-refresh made the badge's own cost the dominant cost of the whole list endpoint.
  3. Modal perf: added GET /api/conversations/[id] (single-row, reuses the same annotation helpers as the list route) and switched the modal's poll to it while open, falling back to the full list poll only when no modal is open.

No schema changes — all three reuse existing indexed columns (call_logs.response_id, .artifact_relpath, .api_key_id) already added for the continuation store, or existing tables.

User Impact

  • Faster, clickable navigation between a continuation request and its parent.
  • At-a-glance visibility into which conversations are actually using continuation.
  • /dashboard/conversations?limit=100 went from ~800ms cold / repeated-every-second cost to effectively free once the (permanent, correctness-safe) cache is warm; the conversation modal no longer hits the 100-row list endpoint at all while open.

Evidence

  • Verified all touched routes compile and serve cleanly (401 auth gate, not 500) after each change, live.
  • Verified the badge logic against 8 real production conversations: correctly resolved true/false matching each one's actual continuation usage.
  • Verified via real request logs that the modal's poll now hits the new single-row endpoint (10–33ms) instead of the full list.

🤖 Generated with Claude Code

…erf fixes

Three related changes, same underlying continuation-store module:

1. "Continues from parent" link on /dashboard/logs -- a request that used
   HTTP continuation (previous_response_id) now links to the call-log row
   that produced the parent response, instead of only showing an
   independently-resolved (server-side merged) view with no way to click
   through to it. Added extractPreviousResponseId/resolveCallLogIdByResponseId
   to responsesContinuationStore.ts, wired into /api/logs/[id].

2. "Genuine continuation" badge on /dashboard/conversations -- distinguishes
   a conversation whose latest turn actually used previous_response_id and
   it resolved server-side, from one the client-side content-hash tracker
   (conversationTracker.ts) merely counts as multi-turn while still
   resending full history each request. isGenuineContinuationTurn() reads
   the row's own artifact; permanently memoized by artifactRelPath (the
   underlying call-log artifact is immutable once written, so the answer
   can never change once computed) -- without this, a 1s dashboard
   auto-refresh at limit=100 re-read and re-parsed up to 100 artifacts every
   tick.

3. Conversation-modal poll no longer refetches the whole up-to-100-row list
   every tick just to keep one open conversation's summary fields fresh --
   added GET /api/conversations/[id] (single-row, reuses the same
   annotation helpers as the list route) and switched the modal's poll to
   it while open, falling back to the full list poll when no modal is open.

No schema changes -- all three reuse existing indexed columns
(call_logs.response_id, .artifact_relpath, .api_key_id) already added for
the continuation store, or existing tables (call_logs itself).
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Sep 2, 2026
…ntinuation badge, and modal perf fixes) into dev/omniroute-dev-combined
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Sep 2, 2026
…ntinuation badge, and modal perf fixes) into dev/omniroute-dev-combined
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Sep 2, 2026
…ntinuation badge, and modal perf fixes) into dev/omniroute-dev-combined
…ishes

Live incident (2026-09-02): a user reported the conversation tree still
showing "(empty)" bubbles for turns that had clearly completed -- closing
and reopening the conversation modal showed the real content fine.

Root cause: resolveConversationId reassigns a turn node's
last_correlation_id to the CURRENT request at request-START (before its
reply streams) -- but that request's call-log artifact, which
resolveTurnDisplayContent needs to produce real text, is only written at
completion. A node touched by a still-in-flight request therefore
legitimately resolves empty if the tree is fetched during that window
(confirmed directly: replayed the exact same resolution logic against the
same conversation a few minutes later, once the artifact existed, and it
resolved perfectly -- the backend logic itself was never wrong).

The afterSeq poll only ever APPENDS strictly newer nodes to local state, so
a node already rendered empty during that race stays empty forever in the
open tab, even once its artifact becomes available moments later -- the
exact "empty until you close and reopen" symptom.

Fix: track the activeCallLogId true -> false transition (a reply that was
streaming just finished) and re-fetch the recent page on it, merging the
result into conversationNodes by id (never dropping older "Load more"
history the user already paged in). This is the same moment
livePartialText already gets cleared -- exactly when the artifact for that
request becomes available.
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Sep 2, 2026
…ntinuation badge, and modal perf fixes) into dev/omniroute-dev-combined
@diegosouzapw

Copy link
Copy Markdown
Owner

sweep-reds / babysit: No new ESLint warnings is inherited from base-red #12581 — CodeQL ratchet 12 open > baseline 11 (repo-wide Security alerts, not this diff). No unused/@eloqnt findings here. Skipping a code change. Did not merge origin/release/v3.8.51.

@diegosouzapw

Copy link
Copy Markdown
Owner

sweep-reds round 6 / babysit: re-checked No new ESLint warnings. eslintWarnings=0 / eslintErrors=0; the job still fails on CodeQL ratchet 12 open > baseline 11 (repo-wide Security alerts, not this dashboard diff). Same inherited #12581 signal as last round. No unused/@eloqnt in this diff. Did not merge origin/release/v3.8.51. STOP.

diegosouzapw added a commit to hartmark/OmniRoute that referenced this pull request Sep 3, 2026
isGenuineContinuationTurn (and its parent-link helpers) have no
callers in this branch — they belong to diegosouzapw#12448. Leaving them here
trips the new-code dead-code gate. Keep only the fail-closed
_truncated / empty-output checks this PR is about.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw merged commit ffdc736 into diegosouzapw:release/v3.8.51 Sep 3, 2026
15 of 16 checks passed
@hartmark
hartmark deleted the feat-conversation-continuation-tracking branch September 3, 2026 18:06
alvinveroy added a commit to alvinveroy/OmniRoute that referenced this pull request Sep 4, 2026
…in TLS watchdog; stryker tap registration

- dashboard/combos/page.tsx: defer the post-hydration localStorage correction
  one microtask so setShowUsageGuide is not a synchronous setState inside the
  effect body (react-hooks/set-state-in-effect base red on release/v3.8.51
  from diegosouzapw#12448); eslint:json gate green.
- proxyFetch.ts withTlsFirstByteWatchdog: the watchdog race consumed the first
  body chunk from the reader but never re-emitted it — the first SSE frame
  was silently dropped. The rest stream now re-emits the captured chunk
  before continuing the original stream.
- stryker.conf.json: register 10281-combo-reasoning-probe + transport-bounded-abort
  in tap.testFiles (they cover mutated modules; --strict gate).
- callLogArtifacts.ts / earlier dashboard stray: restore pristine remote state
  for files untouched by this branch (resolves unused-var lint).
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…erf fixes (diegosouzapw#12448)

Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.

O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (diegosouzapw#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (diegosouzapw#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.

Obrigado, @hartmark.
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