Repository navigation
fix(renderer): turn numbering, session origin strip, stable Context pill - #52
Merged
Merged
Conversation
Turn N must equal the Nth user message of the transcript, 1:1. The old numbering counted AI chunks (aiCount), so user messages that produced no response silently shifted every later label: a 24-user-message session displayed "22 turns" and Turn 21 pointed at transcript turn 24. - groupTransformer: both transform paths now derive an AI group's turnIndex from the user-chunk counter (userCount - 1, 0-based display convention unchanged); compact/system chunks consume no number; AI groups before the first user message fall back to AI-sequence numbering. - jsonl analyzeSessionFileMetadata: turnCount now counts user turns (isUserChunkLine semantics, sidechain excluded) instead of closed AI runs. Red→green: new test/renderer/utils/groupTransformer.test.ts failed 5/7 on the old numbering ([0,2] expected, [0,1] received for an empty turn), now 7/7; jsonl turn-count and parity tests flipped to user semantics, subsystem green (171 passed). Known effect: two AI groups of the same turn (post- compaction continuation) now share a turn number — transcript-true; turn navigation lands on the first group of that turn. Co-Authored-By: Claude Code <noreply@anthropic.com>
The chat column showed no indication of which project/directory/worktree
a session belongs to, and the sidebar only showed a truncated worktree
name ("agent-orc…").
- New formatSessionOrigin util: "repo · worktree · branch · ~/short/path"
strip line and a "worktree · branch" sidebar tag; reuses shortenDisplayPath.
- New SessionOriginStrip component between SearchBar and ChatHistory;
renders nothing when the origin is unknown.
- MiddlePanel reads the session from per-tab data (tabSessionData[tabId]
?? sessionDetail) and repoName from repositoryGroups.
- SessionItem renders the origin tag with the full path as tooltip.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The "Context (N)" pill above the chat kept disappearing: - During a live refresh the conversation gains new AI groups whose stats are not computed until the next full fetch. The pill's memo picked the last AI group overall, found no stats for it and rendered nothing. Now it targets the last AI group that actually has stats (new findLastTrackedAiGroupId helper), and a phase's lastAIGroupId is only used when it is tracked. - Phase 1 of fetchSessionDetail reset sessionContextStats / sessionPhaseInfo / sessionClaudeMdStats to null on every fetch, briefly unmounting the pill until Phase 2 finished. Phase 1 now leaves existing stats in place (per-tab entries spread the previous data; a replaced tab's data is already cleaned up by openTab). selectSession still clears the stats when switching sessions, now including sessionPhaseInfo and sessionClaudeMdStats. Behavioral note: between a live refresh and the next full fetch the pill shows the stats of the last tracked turn instead of nothing. Co-Authored-By: Claude Code <noreply@anthropic.com>
Found during live verification (session 61e54b3a): the sidebar showed 36 turns while the chat showed 34 user turns — the two "This session is being continued..." compact-summary user lines were counted as turns, but the chat renders them as CompactBoundary, not user turns. Red: turn-count test with a compact-summary line in the fixture (5 != 4). Green: skip isCompactSummary lines, same rule as the chat pipeline. Co-Authored-By: Claude Code <noreply@anthropic.com>
…harden stats hand-off Addressed verified findings from the PR #52 code review: - The origin strip above the chat read session.worktreeName from SessionDetail, but that field is only ever tagged onto sidebar LIST rows — the detail path never populates it, so non-main worktree sessions rendered "repo · main · branch" (wrong worktree) and the tooltip lost the worktree part. MiddlePanel now resolves repo/worktree from repositoryGroups with the same rule the sidebar tags use (resolveSessionOriginGroups; main worktree stays unnamed). - Both hand-rolled tooltip joins replaced with formatSessionOriginTooltip; local basename() replaced with the canonical getBaseName from pathUtils. - Phase 1 of fetchSessionDetail spreads the previous per-tab data only for the same session: selectSession reuses the active tab id for a NEW session, so the previous session's context stats could render next to the new conversation until Phase 2 replaced them. - With a context phase explicitly selected but its last AI group untracked (live refresh, stats not yet recomputed) the pill now renders empty instead of falling back to the last tracked group of another phase. Not addressed (documented trade-offs / follow-ups): duplicate "Turn N" numbers for two AI groups of the same user turn (affects the turnIndex navigation lookup and the injection id scheme "ai-N" — needs a group-id-based navigation/id redesign), live-refresh pill staleness until the next full fetch (root-cause fix: schedule Phase-2 recompute in refreshSessionInPlace), zero turnCount for agent-team sessions whose only inputs are teammate relays. Co-Authored-By: Claude Code <noreply@anthropic.com>
This was referenced Oct 7, 2026
…extraction, reset completeness
Addressed verified findings from the second /code-review pass:
- The origin strip no longer synthesizes a "main" worktree segment: it
duplicated the branch for main-worktree sessions on branch "main"
("repo · main · main · ~/path" — the most common session shape) and
asserted "main" for unresolvable origins in flat sidebar mode. The
worktree segment renders only when explicitly known.
- Turn-index policy (userCount-1 with AI-sequence fallback) extracted into
aiTurnIndex — full and incremental transforms must number the same
transcript identically; the policy now lives in one place with one comment.
- The context pill's target-group resolution moved into contextTracker as
resolveContextTargetAiGroupId (unit-tested without React: selected phase
resolves within itself, untracked phase renders empty, no selection picks
the last tracked group); the memo's dead `else if` condition removed and
its empty result deduplicated into EMPTY_CONTEXT_RESULT.
- clearSelection and the shared session reset helper now also clear
sessionPhaseInfo / sessionClaudeMdStats — same incomplete-reset class
the selectSession hardening fixed.
- jsonl turnCount doc comment, the awaitingAIGroup comment and the
SessionItem turns tooltip no longer promise "same count as Turn chips"
for a session whose last turn has no response; tooltip states the rule.
- Parity test hardened: an assistant line after the trailing empty turn
makes AI-run counting give 4 while user-message counting gives 3 — a
regression to AI-run semantics now fails the test instead of passing it.
Refuted during verification: switching the scanner's turn predicate to
categorizeMessage would CHANGE semantics — isParsedUserChunkMessage wraps
the canonical isUserChunkLine shared with the turn-budget hook (system-tag
prefixed lines are excluded by both), so scanner and hook stay aligned by
construction. Chunk-factory hoisting declined: the two test factory sets
look alike but differ (seq ids, rawMessages, inline userMessage); the
shape drift it guards against is caught by typecheck.
Known follow-ups unchanged: #53 (turnIndex uniqueness for navigation and
injection ids), #54 (stats recompute in the refresh path; this pass also
showed the pill's token cost freezes with it — noted there).
Co-Authored-By: Claude Code <noreply@anthropic.com>
axisrow
added a commit
that referenced
this pull request
Oct 7, 2026
* feat(tracker): use real group.id for injection ids and navigation Issue #53. After #52 two AI groups of one turn share turnIndex (compact or system boundaries tear a turn; aiCount fallback collides too), so the synthetic "ai-${turnIndex}" id collided: duplicate injection ids and wrong-group navigation. Trackers now carry the group's real id (= stable chunk id, unique across re-parses): - contextTracker: turnGroupId = aiGroup.id; injection id generators take the group id instead of turnIndex (tool-output-<id>, loop-<id>, wait-loop-<id>, thinking-text-<id>, task-coord-<id>, user-msg-<id>); reread id follows. - claudeMdTracker: same turnGroupId change; firstSeenTurnIndex (display-only "@turn N") added next to firstSeenInGroup. - ClaudeMdInjection gains firstSeenTurnIndex so labels survive real uuid ids. Red test (contextTracker.test.ts): two AI groups sharing turnIndex 0 must keep disjoint injection ids, their own aiGroupId, and claude-md display metadata — failed with 'ai-0' expected 'ai-xxx'. Co-Authored-By: Claude Code <noreply@anthropic.com> * refactor(panel): navigate by group.id instead of turn index Issue #53, panel half. SessionContextPanel callbacks (onNavigateToTurn / onNavigateToTool / onNavigateToUserGroup) take the AI group's id instead of turnIndex; ChatHistory resolves the target by group.id, so a click from the LATER group of a split turn lands in that group, not the first one sharing the index. - Sections/items pass injection.aiGroupId (or firstSeenInGroup for claude-md/mentioned-file); clickability gate is a non-empty id. - FlatInjectionList rows carry groupId; RankedInjectionList swaps getInjectionTurnIndex for getInjectionGroupId. - parseTurnIndex dropped; formatFirstSeen formats a turnIndex number — with uuid ids the "ai-N" parse was already unfixable. - DirectoryTree nodes carry firstSeenTurnIndex for the "@turn N" label. Burn-header nav tests now assert the callback receives the group id string; ClaudeMdItem test covers a click with a uuid id (previously not clickable: parseTurnIndex returned -1). Co-Authored-By: Claude Code <noreply@anthropic.com> * chore(test): cap vitest workers at n-1 explicitly Pin poolOptions.threads (minThreads 1, maxThreads cpus-1) so worker count does not depend on vitest defaults and the suite cannot saturate the machine. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(review): harden group-id migration per code-review Follow-up to the #53 migration from /code-review on PR #56: - vitest.critical.config.ts: mirror the n-1 worker cap — test:coverage:critical would otherwise spawn uncapped workers, the exact load that hung the machine. - ClaudeMdItem: guard firstSeenTurnIndex with ?? -1 like DirectoryTreeNode — an injection without the field rendered "@turn NaN" instead of degrading. - createGlobalInjections: turnIndex becomes a required parameter (was `= 0`); both callers already pass aiGroup.turnIndex, the default silently mislabeled a future caller as "@turn 1". - Stale doc examples in contextInjection.ts: ids are now "<prefix>-<group id>" / real group ids, not "ai-0". - burnHeaderNav fixtures: injection ids follow the real scheme ("wait-loop-<group id>"), so an id-scheme regression can't keep them green. - formatFirstSeen moved into utils/formatting.ts; pathParsing.ts deleted — it parsed no paths after parseTurnIndex's removal. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: axisrow <axisrow@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Что сломано
Три бага UI, найденные при разборе сессии
61e54b3a(четвёртый — найден приживой верификации, см. коммит 4):
по AI-группам, поэтому user-сообщения без ответа номер не потребляли:
24 user-сообщения → «22 turns», метки сдвинуты.
ни worktree; в сайдбаре — усечённый тег «agent-orc…».
conversation получает новые AI-группы без статистики → дефолтная цель memo
выбирала последнюю AI-группу →
?? []→ размонтирование. Phase 1 полнойзагрузки дополнительно затирала статистику null'ом.
Фиксы (по коммиту, каждый по красному тесту)
1.
41539b0— нумерация по user-сообщениямtest/renderer/utils/groupTransformer.test.ts— 5/7 падали:[U1, A1, U2, U3, A3]давало[0, 1], ожидание[0, 2](пустой ход потребил номер).groupTransformer.ts: turnIndex AI-группы =userCount - 1(ordinal еёuser-сообщения; пустые ходы потребляют номер) в полном и инкрементальном пути.
jsonl.ts: sidebar-счётчикturnCount— по user-сообщениям не-sidechain,ai-run-логика удалена; парити-тест с числом UserChunk.
2.
cc65095— origin сессии над чатом и в сайдбареformatSessionOrigin.test.ts(7 кейсов),sessionOriginStrip.test.ts(mount, 2 кейса).formatSessionOrigin.ts: стрипrepo · worktree · branch · ~/pathworktree · branch;SessionOriginStripмежду SearchBar иChatHistory;
SessionItemрендерит тег, полный путь в тултипе. Вкладки не тронуты.3.
befa49e— плашка Context не мигаетfindLastTrackedAiGroupIdвcontextTracker.test.ts— 4/4 падали.contextTracker.ts: хелпер «последняя AI-группа со статистикой»;ChatHistorymemo использует его + сброс, еслиphase.lastAIGroupIdне отслежен.fetchSessionDetailбольше не затирает статистику null'ом (per-tab —spread предыдущих данных; заменённый таб уже чистится
cleanupTabSessionData),selectSessionявно чистит все три статистических поля при смене сессии.показывает статистику последнего отслеженного хода вместо пустоты.
4.
3ea71cb— компакт-сводки не считаются ходами (найден при живой верификации)61e54b3aсайдбар показывал 36 turns, чат — 34 user-хода: две строки«This session is being continued…» считались ходами, хотя чат рендерит их как
CompactBoundary.
isCompactSummaryвturnCount— правило совпадает с чанк-пайплайном.5.
956f9f4— раунд/code-review: верифицированные находкиsession.worktreeNameиз SessionDetail, но поле заполняетсятолько на списках сайдбара — ворктри-сессии показали бы
repo · main · branch.MiddlePanelтеперь деривирует repo/worktree изrepositoryGroups(
resolveSessionOriginGroups, правило то же, что у сайдбар-тегов).selectSessionпереиспользует active tab id для новой сессии — раньше её статы оставались бы
рядом с чужим conversation до Phase 2.
инъекций другой фазы.
formatSessionOriginTooltip(2 ручных join),getBaseNameиз pathUtilsвместо локального basename.
одного хода (навигация и id-схема
ai-N— отдельная задача на group.id),стагнация плашки между полными загрузками,
turnCount 0для team-сессий.Верификация
pnpm test— 929/929 (71 файл),typecheck и lint чистые.
956f9f4): зелёный.61e54b3a—сайдбар «34 turns» = фильтр «User 34» = последний чип «Turn 34» (1:1);
стрип
agent-orchestrator · main · main · ~/Projects/agent-orchestratorнад чатом;плашка «Context (24)» остаётся на месте через живые обновления сессии.
компенсировано ассертами на точные значения.
typecheck + живая проверка.
🤖 Generated with Claude Code