Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Settings → General → Compact transcript** keeps the work the agent is doing folded behind the summary line that already titles it, instead of opening the group while the turn runs. The line still shimmers, names the phase, and counts the steps, so a busy turn reads as one row rather than a scrolling wall. Clicking it opens the steps as before, and a call waiting on your approval opens itself regardless.
- Subagents now get a row each in the transcript, sitting under the agent's own work with an animated mascot, a shimmering name while the run is live, and a count of the steps it has taken. Clicking a row opens the subagent's trail inline, grouped into phases the same way the main transcript groups work, so a long run reads as what it said and the calls that followed rather than one flat dump. The rows keep their place while the work above them folds and re-folds. Claude and Codex both report their subagents' work; Codex spawns are named from their brief, stay running until the agent itself reports otherwise, and one spawned agent no longer shows up as several rows.
- GitLab's **Needs attention** Inbox view now uses pending GitLab To-Dos to include assignments, mentions, and review requests from every accessible repository. Remote-only items support details, discussions, comments, and merge-request diffs without requiring a local checkout.

Expand Down
19 changes: 19 additions & 0 deletions src/hooks/useTranscriptCompact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from "react";
import {
loadTranscriptCompact,
TRANSCRIPT_COMPACT_CHANGE_EVENT,
} from "../lib/appearance";

/** Subscribes to compact-transcript changes triggered by saveTranscriptCompact(). */
export function useTranscriptCompact(): boolean {
const [compact, setCompact] = useState<boolean>(loadTranscriptCompact);
useEffect(() => {
const onChange = (event: Event) => {
setCompact((event as CustomEvent<boolean>).detail === true);
};
window.addEventListener(TRANSCRIPT_COMPACT_CHANGE_EVENT, onChange);
return () =>
window.removeEventListener(TRANSCRIPT_COMPACT_CHANGE_EVENT, onChange);
}, []);
return compact;
}
23 changes: 23 additions & 0 deletions src/lib/appearance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
loadTranscriptAnchor,
saveTranscriptAnchor,
TRANSCRIPT_ANCHOR_DEFAULT,
loadTranscriptCompact,
saveTranscriptCompact,
TRANSCRIPT_COMPACT_DEFAULT,
loadThemePreference,
saveThemePreference,
resolveColorScheme,
Expand All @@ -23,6 +26,7 @@ import {
const KEY = "monocode.transcriptLayout";
const SCHEME_KEY = "monocode.colorScheme";
const ANCHOR_KEY = "monocode.transcriptAnchor";
const COMPACT_KEY = "monocode.transcriptCompact";
const CHAT_BACKGROUND_PATH_KEY = "monocode.chatBackgroundPath";
const CHAT_BACKGROUND_OPACITY_KEY = "monocode.chatBackgroundOpacity";
const CHAT_BACKGROUND_SCOPE_KEY = "monocode.chatBackgroundScope";
Expand Down Expand Up @@ -95,6 +99,25 @@ describe("transcript prompt-to-top setting", () => {
});
});

describe("compact transcript setting", () => {
beforeEach(mockLocalStorage);
afterEach(() => {
localStorage.removeItem(COMPACT_KEY);
});

it("defaults to off", () => {
expect(TRANSCRIPT_COMPACT_DEFAULT).toBe(false);
expect(loadTranscriptCompact()).toBe(false);
});

it("persists across loads", () => {
saveTranscriptCompact(true);
expect(loadTranscriptCompact()).toBe(true);
saveTranscriptCompact(false);
expect(loadTranscriptCompact()).toBe(false);
});
});

describe("chat background setting", () => {
beforeEach(mockLocalStorage);
afterEach(() => {
Expand Down
21 changes: 21 additions & 0 deletions src/lib/appearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const SIDEBAR_TAB_ORDER_KEY = "monocode.sidebarTabOrder";
const PROJECT_RAIL_WIDTH_KEY = "monocode.projectRailWidth";
const TRANSCRIPT_LAYOUT_KEY = "monocode.transcriptLayout";
const TRANSCRIPT_ANCHOR_KEY = "monocode.transcriptAnchor";
const TRANSCRIPT_COMPACT_KEY = "monocode.transcriptCompact";
const CHAT_BACKGROUND_PATH_KEY = "monocode.chatBackgroundPath";
const CHAT_BACKGROUND_OPACITY_KEY = "monocode.chatBackgroundOpacity";
const CHAT_BACKGROUND_SCOPE_KEY = "monocode.chatBackgroundScope";
Expand Down Expand Up @@ -40,9 +41,15 @@ export const CHANGES_VIEW_DEFAULT: ChangesView = "list";

export const TRANSCRIPT_ANCHOR_DEFAULT = true;

export const TRANSCRIPT_COMPACT_DEFAULT = false;

/** Fired on `window` whenever prompt-to-top anchoring flips (detail: boolean). */
export const TRANSCRIPT_ANCHOR_CHANGE_EVENT = "monocode:transcriptanchorchange";

/** Fired on `window` whenever compact transcript flips (detail: boolean). */
export const TRANSCRIPT_COMPACT_CHANGE_EVENT =
"monocode:transcriptcompactchange";

/** Fired on `window` whenever the transcript layout flips (detail: TranscriptLayout). */
export const TRANSCRIPT_LAYOUT_CHANGE_EVENT = "monocode:transcriptlayoutchange";

Expand Down Expand Up @@ -544,3 +551,17 @@ export function saveTranscriptAnchor(value: boolean) {
}),
);
}

export function loadTranscriptCompact(): boolean {
return readFlag(TRANSCRIPT_COMPACT_KEY) ?? TRANSCRIPT_COMPACT_DEFAULT;
}

export function saveTranscriptCompact(value: boolean) {
writeFlag(TRANSCRIPT_COMPACT_KEY, value);
if (typeof window === "undefined") return;
window.dispatchEvent(
new CustomEvent<boolean>(TRANSCRIPT_COMPACT_CHANGE_EVENT, {
detail: value,
}),
);
}
32 changes: 31 additions & 1 deletion src/surfaces/AgentTranscript.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createElement, type ReactNode } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { Block } from "../lib/session";
import { AgentTranscript } from "./AgentTranscript";

Expand Down Expand Up @@ -312,3 +312,33 @@ describe("AgentTranscript collapsed work", () => {
);
});
});

describe("AgentTranscript compact transcript", () => {
beforeEach(() => {
Object.defineProperty(globalThis, "localStorage", {
value: {
getItem: (key: string) =>
key === "monocode.transcriptCompact" ? "1" : null,
},
configurable: true,
});
});
afterEach(() => {
Reflect.deleteProperty(globalThis, "localStorage");
});

it("folds live work behind the line that titles it", () => {
const markup = render([tool("one"), tool("two"), tool("three")], true);
expect(markup).toContain("Running 3 commands");
expect(markup.includes("hidden-detail-")).toBe(false);
});

it("still opens a group holding a call that waits on an approval", () => {
const markup = render(
[tool("one"), tool("two"), tool("approval", { requestId: 1 })],
true,
);
expect(markup).toContain("hidden-detail-approval");
expect(markup).toContain("hidden-detail-one");
});
});
6 changes: 5 additions & 1 deletion src/surfaces/AgentTranscript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { HarnessIcon } from "../chrome/HarnessIcon";
import { useLockOverscroll } from "../hooks/useLockOverscroll";
import { useTranscriptLayout } from "../hooks/useTranscriptLayout";
import { useTranscriptAnchor } from "../hooks/useTranscriptAnchor";
import { useTranscriptCompact } from "../hooks/useTranscriptCompact";
import { useTranscriptSelection } from "../hooks/useTranscriptSelection";
import type { TranscriptLayout } from "../lib/appearance";
import { AgentMarkdown } from "./AgentMarkdown";
Expand Down Expand Up @@ -1391,7 +1392,10 @@ function ActivityPhaseGroup({
}) {
const [override, setOverride] = useState<boolean | null>(null);
const waiting = phase.steps.some(needsApproval);
const open = waiting || (override ?? active);
// Compact drops the auto-open: the group stays the one line that already
// titles it. A click, or a step waiting on you, still opens it.
const compact = useTranscriptCompact();
const open = waiting || (override ?? (active && !compact));
const [liveScroller, setLiveScroller] = useState<HTMLDivElement | null>(null);
useLivePhaseScroll(liveScroller, active && open, phase.steps);
const title = activityPhaseTitle(phase, active);
Expand Down
19 changes: 19 additions & 0 deletions src/surfaces/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
loadThemeSaturation,
loadTranscriptLayout,
loadTranscriptAnchor,
loadTranscriptCompact,
saveBodyGlass,
saveChatBackgroundOpacity,
saveChatBackgroundPath,
Expand All @@ -65,6 +66,7 @@ import {
saveThemeSaturation,
saveTranscriptLayout,
saveTranscriptAnchor,
saveTranscriptCompact,
TRANSCRIPT_ANCHOR_CHANGE_EVENT,
SIDEBAR_BLUR_DEFAULT,
SIDEBAR_BLUR_MAX,
Expand Down Expand Up @@ -354,6 +356,8 @@ function GeneralPage({
useState<TranscriptLayout>(loadTranscriptLayout);
const [transcriptAnchor, setTranscriptAnchor] =
useState(loadTranscriptAnchor);
const [transcriptCompact, setTranscriptCompact] =
useState(loadTranscriptCompact);
const [diffViewer, setDiffViewer] = useState<DiffViewer>(loadDiffViewer);
const [followUpBehavior, setFollowUpBehavior] =
useState<FollowUpBehavior>(loadFollowUpBehavior);
Expand Down Expand Up @@ -408,6 +412,11 @@ function GeneralPage({
setTranscriptAnchor(next);
};

const onTranscriptCompact = (next: boolean) => {
saveTranscriptCompact(next);
setTranscriptCompact(next);
};

const onDiffViewer = (next: DiffViewer) => {
saveDiffViewer(next);
setDiffViewer(next);
Expand Down Expand Up @@ -514,6 +523,16 @@ function GeneralPage({
onChange={onTranscriptAnchor}
/>
</Row>
<Row
label="Compact transcript"
description="Keep the work the agent is doing folded behind its own summary line instead of opening it while it runs. Click the line to read the steps; anything waiting on your approval still opens itself."
>
<Toggle
label="Compact transcript"
on={transcriptCompact}
onChange={onTranscriptCompact}
/>
</Row>
<Row
label="Effort control"
description="Show the current effort as a separate control beside the model picker for quicker changes. When off, effort stays inside the model menu."
Expand Down