feat: merge hidden-user turns and move message metadata to turn footer

- Remove assistant message headers; show provider icon, model, agent,
  thinking variant, duration and time in the turn footer (metadata left,
  hover-revealed actions right)
- Merge turns started by hidden user messages (subagent nudges) into the
  previous turn so Activity, footer and spacing stay continuous
- Treat compaction summary text (info.summary) as justification activity
  in sorted mode and skip it when picking the turn summary
- Interleave activity segments with standalone tool rows so Agent Task
  sits chronologically between activity sections
This commit is contained in:
Bohdan Triapitsyn
2026-07-19 22:58:22 +03:00
parent 1014be696a
commit 8e718ee58c
13 changed files with 402 additions and 158 deletions
@@ -8,6 +8,7 @@ interface UseTurnRecordsOptions {
sessionKey?: string;
showTextJustificationActivity: boolean;
showTurnChangedFiles: boolean;
planModeEnabled: boolean;
}
export interface TurnRecordsResult {
@@ -26,15 +27,18 @@ export const useTurnRecords = (
const previousSessionKeyRef = React.useRef<string | undefined>(options.sessionKey);
const previousShowTextJustificationActivityRef = React.useRef(options.showTextJustificationActivity);
const previousShowTurnChangedFilesRef = React.useRef(options.showTurnChangedFiles);
const previousPlanModeEnabledRef = React.useRef(options.planModeEnabled);
if (
previousSessionKeyRef.current !== options.sessionKey
|| previousShowTextJustificationActivityRef.current !== options.showTextJustificationActivity
|| previousShowTurnChangedFilesRef.current !== options.showTurnChangedFiles
|| previousPlanModeEnabledRef.current !== options.planModeEnabled
) {
previousSessionKeyRef.current = options.sessionKey;
previousShowTextJustificationActivityRef.current = options.showTextJustificationActivity;
previousShowTurnChangedFilesRef.current = options.showTurnChangedFiles;
previousPlanModeEnabledRef.current = options.planModeEnabled;
previousProjectionRef.current = null;
staticTurnsRef.current = [];
streamingTurnRef.current = undefined;
@@ -44,15 +48,17 @@ export const useTurnRecords = (
previousProjectionRef.current = null;
staticTurnsRef.current = [];
streamingTurnRef.current = undefined;
}, [options.sessionKey, options.showTextJustificationActivity, options.showTurnChangedFiles]);
}, [options.sessionKey, options.showTextJustificationActivity, options.showTurnChangedFiles, options.planModeEnabled]);
const projection = React.useMemo(() => {
const sessionKey = options.sessionKey ?? '';
const mergeKey = options.planModeEnabled ? 'merge:plan' : 'merge';
const cached = getCachedProjection(
sessionKey,
messages,
options.showTextJustificationActivity,
options.showTurnChangedFiles,
mergeKey,
);
if (cached) {
previousProjectionRef.current = cached;
@@ -64,6 +70,7 @@ export const useTurnRecords = (
previousProjection: previousProjectionRef.current,
showTextJustificationActivity: options.showTextJustificationActivity,
showTurnChangedFiles: options.showTurnChangedFiles,
mergeHiddenUserTurns: { planModeEnabled: options.planModeEnabled },
});
previousProjectionRef.current = nextProjection;
@@ -72,12 +79,13 @@ export const useTurnRecords = (
messages,
options.showTextJustificationActivity,
options.showTurnChangedFiles,
mergeKey,
);
setCachedProjection(cacheKey, nextProjection);
return nextProjection;
});
}, [messages, options.showTextJustificationActivity, options.showTurnChangedFiles, options.sessionKey]);
}, [messages, options.showTextJustificationActivity, options.showTurnChangedFiles, options.sessionKey, options.planModeEnabled]);
const staticTurns = React.useMemo(() => {
const nextStatic = projection.turns.length <= 1