Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
11d7666
feat(web): redesign composer and conversation timeline
maria-rcks Oct 31, 2026
93d1c7c
refactor(chat): remove unused work log helpers
maria-rcks Oct 31, 2026
734c106
fix(chat): stop tool shine while waiting for input
maria-rcks Oct 31, 2026
d493f14
fix(chat): keep paused timer clear of question text
maria-rcks Oct 31, 2026
6ffa81d
fix(chat): keep approval decisions together on narrow screens
maria-rcks Oct 31, 2026
5d7c6c3
fix(chat): match composer corner and send slot dimensions
maria-rcks Oct 31, 2026
747e8ba
fix(chat): match the collapsed working window dimensions
maria-rcks Oct 31, 2026
bc45044
fix(chat): acknowledge custom question answers in the footer
maria-rcks Oct 31, 2026
b360183
style(chat): format grouped approval actions
maria-rcks Oct 31, 2026
665db04
fix(chat): summarize wake-ups and retain the latest answer
maria-rcks Oct 31, 2026
39f4c1c
fix(chat): use the pill radius token and accessible wake-up label
maria-rcks Oct 31, 2026
ee4fa9b
style(chat): format the composer surface
maria-rcks Oct 31, 2026
010775a
fix(chat): align timeline rows and attachment layouts with design
maria-rcks Oct 31, 2026
fe061e4
fix(chat): keep row class helper compiler eligible
maria-rcks Oct 31, 2026
bd0263a
fix(chat): expose the existing interrupt action on live work rows
maria-rcks Oct 31, 2026
666d190
fix(chat): keep interrupt available in grouped live rows
maria-rcks Oct 31, 2026
b4efcef
fix(chat): keep turn interrupt off task summary rows
maria-rcks Oct 31, 2026
80a3c2c
fix(chat): match composer suggestion and picker menus to the design
maria-rcks Oct 31, 2026
9e4fd33
fix(chat): keep branch picker separators stable
maria-rcks Oct 31, 2026
004fe97
fix(chat): let the picker text role own its weight
maria-rcks Oct 31, 2026
a36e9ac
fix(chat): match the option c composer and integrated tray
maria-rcks Oct 31, 2026
ae2c13f
fix(chat): validate plan actions and queued reorder commands
maria-rcks Oct 31, 2026
41d1036
fix(chat): keep work and notices in the composer tray
maria-rcks Oct 31, 2026
9597301
fix(chat): reserve the machine chip for remote threads
maria-rcks Oct 31, 2026
b657fc0
fix(chat): wire approval shortcuts in the composer tray
maria-rcks Oct 31, 2026
1a9b805
fix(chat): settle completed tasks and cap the prompt at eight lines
maria-rcks Oct 31, 2026
70170dd
fix(chat): keep tray work bound to the active run
maria-rcks Oct 31, 2026
44e37c8
fix(chat): accept approval keys from the focused workspace
maria-rcks Oct 31, 2026
24fc17f
fix(chat): collapse composer controls on narrow panes
maria-rcks Oct 31, 2026
0145ccb
fix(chat): keep picked attachments above the prompt
maria-rcks Oct 31, 2026
e2d151f
fix(chat): preview queue order while dragging
maria-rcks Oct 31, 2026
edffa13
fix(chat): show the branch picker chevron in the tray
maria-rcks Oct 31, 2026
758ebf1
fix(chat): stage file rows without an automatic reference
maria-rcks Oct 31, 2026
70b33c6
fix(chat): keep question actions in the tray panel
maria-rcks Oct 31, 2026
f2450ef
fix(chat): align composer and requests with final design
maria-rcks Oct 31, 2026
3e6624f
fix(chat): anchor composer and branch popup to design edges
maria-rcks Oct 31, 2026
b9df715
fix(chat): keep approval actions together and remove unused drop overlay
maria-rcks Oct 31, 2026
50d776e
fix(chat): keep tray border inside composer dimensions
maria-rcks Oct 31, 2026
0ad4edd
fix(chat): reconcile node views outside React lifecycle
maria-rcks Oct 31, 2026
a76cf79
fix(chat): preserve synchronous caret updates and popup width
maria-rcks Oct 31, 2026
e8a5c9d
fix(web): steered turns' fold rows each show their own time and edits…
flamboh Nov 2, 2026
11609bb
fix(web): a finished turn's label stays readable next to still-runnin…
flamboh Nov 2, 2026
182bf78
fix(web): steered turns read like a chat with one fold at the end (#2…
flamboh Nov 2, 2026
4c98623
test(web): drop the removed assistant timestamp assertion
maria-rcks Nov 3, 2026
783b9f7
fix(web): the working window expands downward without moving the bar …
flamboh Nov 2, 2026
eed3d04
refactor(chat): remove obsolete thinking interrupt flag
maria-rcks Nov 3, 2026
5767aa8
fix(web): keep background work off turn fold rows
maria-rcks Nov 2, 2026
41ed018
feat(web): context chips drop the pill and read as colored text, in r…
flamboh Nov 2, 2026
a296d02
fix(web): narrow composers collapse the env and branch chips instead …
flamboh Nov 2, 2026
f4d6a2a
fix(web): scroll collapsed working history independently
maria-rcks Nov 3, 2026
c322eef
fix(web): Limits opens the usage limits popover directly (#29222)
flamboh Nov 3, 2026
2d024fd
fix(web): clarify account scope in the usage limits popover
maria-rcks Nov 3, 2026
92ff1aa
fix(web): the working bar shows its fold caret again (#29969)
flamboh Nov 3, 2026
a75c959
fix(web): collapsing the working window keeps its top fade (#29972)
flamboh Nov 3, 2026
7498435
fix(web): a running turn's answer streams below the fold, not inside …
flamboh Nov 3, 2026
08e631a
fix(web): new wake-ups join their fold without shifting the timeline …
flamboh Nov 3, 2026
f42cfce
fix(web): retain composer compatibility after rebase
maria-rcks Nov 3, 2026
3229259
fix(web): composer text and placeholder stay clear of the send button…
flamboh Nov 5, 2026
d17e39f
fix(web): pasting an image or file drops its chip in the prompt again…
flamboh Nov 5, 2026
f03523b
fix(web): collapsing the working window no longer nudges the timeline…
flamboh Nov 5, 2026
2b7af3f
fix(web): the working window's scrollbar shows its whole history from…
flamboh Nov 5, 2026
a4be90b
fix(web): finished and wake-up folds work like the live working fold …
flamboh Nov 5, 2026
b770123
fix(web): finished turn folds show the live timer's duration format (…
flamboh Nov 5, 2026
1a9a38e
fix(web): tighten the bubble corner under the steer label
maria-rcks Nov 5, 2026
aacdd08
fix(web): keep main's /clear command and live transcript working in t…
maria-rcks Nov 6, 2026
75316ed
fix(web): carry the tool call setting through the working window and …
maria-rcks Nov 6, 2026
47c9c65
fix(web): hide all drops the duration row when a reply has nothing to…
maria-rcks Nov 6, 2026
51354f5
fix(web): composer rows share the tray row's left and right inset
maria-rcks Nov 8, 2026
9b8db5a
fix(web): a steer sent right after your message joins its bubble group
maria-rcks Nov 9, 2026
0684208
test(web): drop low-signal tests the redesign added to the composer a…
maria-rcks Nov 9, 2026
8dbcbf1
refactor(web): a queue drag is one value instead of two run ids
maria-rcks Nov 9, 2026
df8e4eb
fix(web): drop the plan commands and plan preview main removed from t…
maria-rcks Nov 9, 2026
9a36129
fix(web): the composer effort chip follows main's provider-free reaso…
maria-rcks Nov 9, 2026
b5167d3
style(web): format the main timeline integrations
maria-rcks Nov 11, 2026
8354c81
fix(web): keep citation editor source navigation on the parent thread
maria-rcks Nov 11, 2026
ad8e624
fix(web): remove morphing from the redesigned fold caret
maria-rcks Nov 11, 2026
593d87e
fix(web): carry stop and disclosure state through redesigned rows
maria-rcks Nov 11, 2026
e0e902f
fix(web): pass compaction and disclosure state through the composer l…
maria-rcks Nov 11, 2026
90b2f0e
fix(web): give the citation editor's source action the source thread
maria-rcks Nov 11, 2026
4ea8c07
fix(web): steered turns keep only final replies outside the fold
maria-rcks Nov 11, 2026
cee90a7
fix(web): terminal selection action and work rows follow the redesign…
shivamhwp Nov 12, 2026
a38a8ed
test(web): hover main's question option preview through the redesign'…
maria-rcks Nov 12, 2026
62d5ec1
test(web): find main's html image gallery in the redesigned viewer's …
maria-rcks Nov 12, 2026
c0ed3f7
test(web): drop two rule-excluded tests from the redesign (#33892)
shivamhwp Nov 12, 2026
d924d38
fix(web): the working line says which provider is starting until its …
maria-rcks Nov 12, 2026
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
4 changes: 2 additions & 2 deletions apps/mobile/src/features/threads/ComposerCommandPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function groupLabel(
): string | null {
switch (triggerKind) {
case "pull-request":
return "Pull requests";
return "Pull requests and threads";
case "slash-command":
return "Commands";
case "skill":
Expand All @@ -161,7 +161,7 @@ function emptyText(triggerKind: ComposerTriggerKind | null, isLoading: boolean):
}
switch (triggerKind) {
case "pull-request":
return "No matching pull requests.";
return "No matching pull requests or threads.";
case "path":
return "No matching mentions.";
case "skill":
Expand Down
21 changes: 20 additions & 1 deletion apps/mobile/src/features/threads/PendingApprovalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,26 @@ export function PendingApprovalCard(props: PendingApprovalCardProps) {
<Text className="font-t3-medium text-sm text-foreground-secondary">
{props.approval.appName ?? props.approval.requestKind}
</Text>
{props.approval.detail ? (
{props.approval.files && props.approval.files.length > 0 ? (
<View className="gap-1">
{props.approval.files.map((file) => (
<View key={`${file.operation ?? ""}:${file.path}`} className="flex-row gap-2">
<Text
className="flex-1 font-sans text-sm text-foreground-secondary"
numberOfLines={1}
ellipsizeMode="middle"
>
{file.path}
</Text>
{file.additions !== undefined || file.deletions !== undefined ? (
<Text className="font-mono text-xs text-foreground-secondary">
+{file.additions ?? 0} −{file.deletions ?? 0}
</Text>
) : null}
</View>
))}
</View>
) : props.approval.detail ? (
<Text className="font-sans text-sm leading-normal text-foreground-secondary">
{props.approval.detail}
</Text>
Expand Down
24 changes: 19 additions & 5 deletions apps/mobile/src/features/threads/use-composer-command-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,11 @@ export function useComposerCommandMenu({
cwd: trigger?.kind === "path" ? projectCwd : null,
query: trigger?.kind === "path" ? trigger.query : null,
});
const isThreadTrigger = trigger?.kind === "path" || trigger?.kind === "pull-request";
// Mounts older settled threads matching the mention, which shells may leave out.
useThreadSearch(
trigger?.kind === "path" && environmentId ? [environmentId] : [],
trigger?.kind === "path" ? trigger.query : "",
isThreadTrigger && environmentId ? [environmentId, ...linkedEnvironments.keys()] : [],
trigger?.query ?? "",
{ titlesOnly: true },
);
const pullRequestSearch = useComposerPullRequestSearch({
Expand All @@ -357,9 +358,21 @@ export function useComposerCommandMenu({
if (!trigger) return [];

if (trigger.kind === "pull-request") {
return pullRequestSearch.entries.map((entry) => ({
// `#` lists pull requests, then threads; a bare `#` offers the most recent threads.
const threadItems = environmentId
? matchComposerThreadItems({
shells: threadShells,
environmentId,
linkedEnvironments,
excludeThreadId: currentThreadId,
query: trigger.query,
allowEmptyQuery: true,
now,
})
: [];
const pullRequestItems = pullRequestSearch.entries.map((entry) => ({
id: `pr:${entry.projectId}:${entry.repository}:${entry.number}`,
type: "pull-request",
type: "pull-request" as const,
pullRequest: {
number: entry.number,
title: entry.title,
Expand All @@ -376,6 +389,7 @@ export function useComposerCommandMenu({
: `${entry.repository} · `
}${entry.title}`,
}));
return [...pullRequestItems, ...threadItems];
}

if (trigger.kind === "slash-command") {
Expand Down Expand Up @@ -564,7 +578,7 @@ export function useComposerCommandMenu({
return;
}
if (item.type === "thread") {
if (!ownerKey || trigger.kind !== "path") return;
if (!ownerKey || (trigger.kind !== "path" && trigger.kind !== "pull-request")) return;
const shell = threadShells.find(
(candidate) =>
candidate.environmentId === item.thread.environmentId &&
Expand Down
10 changes: 5 additions & 5 deletions apps/server/src/guest/GuestShareService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import {
} from "@t3tools/contracts";
import { chatMarkdownImageSources } from "@t3tools/shared/markdownPipeline";
import { htmlRenderFromToolItem } from "@t3tools/shared/toolOutput";
import {
projectTurnItemForDetail,
projectTurnItemForWire,
threadStreamEventItem,
} from "@t3tools/shared/wireProjection";
import * as Context from "effect/Context";
import * as SecureRandom from "effect/crypto/SecureRandom";
import * as DateTime from "effect/DateTime";
Expand Down Expand Up @@ -50,11 +55,6 @@ import { GUEST_FAILURE } from "../orchestration-v2/GuestShareDecider.ts";
import * as Orchestrator from "../orchestration-v2/Orchestrator.ts";
import { coalesceThreadLiveStream } from "../orchestration-v2/ThreadLiveEventCoalescer.ts";
import { buildBoundedThreadStreamSnapshot } from "../orchestration-v2/ThreadStream.ts";
import {
projectTurnItemForDetail,
projectTurnItemForWire,
threadStreamEventItem,
} from "../orchestration-v2/WireProjection.ts";
import * as ProviderRegistry from "../provider/ProviderRegistry.ts";
import * as ServerSettings from "../serverSettings.ts";
import * as AnalyticsService from "../telemetry/AnalyticsService.ts";
Expand Down
199 changes: 125 additions & 74 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2830,89 +2830,140 @@ describe("CodexAdapterV2 post-settle continuation", () => {
),
);

it.effect("compacts Codex with the native RPC and completes the compaction turn", () =>
Effect.scoped(
Effect.gen(function* () {
const nativeThreadId = "compact-thread";
const nativeTurnId = "compact-turn";
const item = { type: "contextCompaction", id: "compact-item" };
const transcript = makeCodexReplayTranscript({
scenario: "native-compaction",
entries: [
...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "unused" }).slice(0, 5),
{
type: "expect_outbound",
label: "compact",
frame: {
id: 3,
method: "thread/compact/start",
params: { threadId: nativeThreadId },
it.effect.each(["reset", "billable-only", "missing", "other-turn-reset", "late-reset"] as const)(
"compacts Codex with %s token telemetry",
(usageKind) =>
Effect.scoped(
Effect.gen(function* () {
const nativeThreadId = "compact-thread";
const nativeTurnId = "compact-turn";
const item = { type: "contextCompaction", id: "compact-item" };
const usageFrame = (tokens: number, inputTokens: number, turnId = nativeTurnId) => ({
type: "emit_inbound" as const,
label: "usage",
frame: {
method: "thread/tokenUsage/updated",
params: {
threadId: nativeThreadId,
turnId,
tokenUsage: {
total: {
totalTokens: 9437579,
inputTokens: 9408294,
cachedInputTokens: 8982400,
outputTokens: 29285,
reasoningOutputTokens: 8756,
},
last: {
totalTokens: tokens,
inputTokens,
cachedInputTokens: 0,
outputTokens: 0,
reasoningOutputTokens: 0,
},
modelContextWindow: 258400,
},
},
},
{ type: "emit_inbound", label: "compact", frame: { id: 3, result: {} } },
{
type: "emit_inbound",
label: "start",
frame: {
method: "turn/started",
params: {
threadId: nativeThreadId,
turn: makeCodexReplayTurn({ id: nativeTurnId, status: "inProgress" }),
});
const transcript = makeCodexReplayTranscript({
scenario: "native-compaction",
entries: [
...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "unused" }).slice(
0,
5,
),
{
type: "expect_outbound",
label: "compact",
frame: {
id: 3,
method: "thread/compact/start",
params: { threadId: nativeThreadId },
},
},
},
...(["item/started", "item/completed"] as const).map((method) => ({
type: "emit_inbound" as const,
label: method,
frame: { method, params: { threadId: nativeThreadId, turnId: nativeTurnId, item } },
})),
{
type: "emit_inbound",
label: "complete",
frame: {
method: "turn/completed",
params: {
threadId: nativeThreadId,
turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }),
{ type: "emit_inbound", label: "compact", frame: { id: 3, result: {} } },
{
type: "emit_inbound",
label: "start",
frame: {
method: "turn/started",
params: {
threadId: nativeThreadId,
turn: makeCodexReplayTurn({ id: nativeTurnId, status: "inProgress" }),
},
},
},
},
],
});
const harness = yield* makeCodexReplayHarness(transcript);
assert.isDefined(harness.runtime.compactThread);
yield* harness.runtime.compactThread!(
makeCodexTestTurnInput({
threadId: harness.threadId,
providerThread: harness.providerThread,
now: yield* DateTime.now,
attemptId: RunAttemptId.make("compact-attempt"),
text: "/compact",
}),
);
yield* harness.firstTerminal;
const items = harness.events.flatMap((event) =>
event.type === "turn_item.updated" && event.turnItem.type === "compaction"
? [event.turnItem]
: [],
);
assert.deepEqual(
items.map((entry) => entry.status),
["running", "completed"],
);
assert.equal(items[0]?.id, items[1]?.id);
assert.equal(harness.terminalEvents()[0]?.status, "completed");
}).pipe(
Effect.provide(
Layer.mergeAll(
IdAllocator.layer,
McpProviderSessions.layer,
NodeServices.layer,
NodeCrypto.layer,
...(usageKind === "missing" ? [] : [usageFrame(223313, 222435)]),
{
type: "emit_inbound",
label: "compaction-start",
frame: {
method: "item/started",
params: { threadId: nativeThreadId, turnId: nativeTurnId, item },
},
},
...(usageKind === "missing" ? [] : [usageFrame(226619, 223200)]),
...(usageKind === "reset" ? [usageFrame(21607, 0)] : []),
...(usageKind === "other-turn-reset" ? [usageFrame(21607, 0, "other-turn")] : []),
{
type: "emit_inbound",
label: "compaction-complete",
frame: {
method: "item/completed",
params: { threadId: nativeThreadId, turnId: nativeTurnId, item },
},
},
...(usageKind === "late-reset" ? [usageFrame(21607, 0)] : []),
{
type: "emit_inbound",
label: "complete",
frame: {
method: "turn/completed",
params: {
threadId: nativeThreadId,
turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }),
},
},
},
],
});
const harness = yield* makeCodexReplayHarness(transcript);
assert.isDefined(harness.runtime.compactThread);
yield* harness.runtime.compactThread!(
makeCodexTestTurnInput({
threadId: harness.threadId,
providerThread: harness.providerThread,
now: yield* DateTime.now,
attemptId: RunAttemptId.make("compact-attempt"),
text: "/compact",
}),
);
yield* harness.firstTerminal;
const items = harness.events.flatMap((event) =>
event.type === "turn_item.updated" && event.turnItem.type === "compaction"
? [event.turnItem]
: [],
);
assert.deepEqual(
items.map((entry) => entry.status),
["running", "completed"],
);
assert.equal(items[0]?.id, items[1]?.id);
assert.equal(items[1]?.beforeTokenCount, usageKind === "missing" ? undefined : 223313);
assert.equal(items[1]?.afterTokenCount, usageKind === "reset" ? 21607 : undefined);
assert.equal(harness.terminalEvents()[0]?.status, "completed");
}).pipe(
Effect.provide(
Layer.mergeAll(
IdAllocator.layer,
McpProviderSessions.layer,
NodeServices.layer,
NodeCrypto.layer,
),
),
),
),
),
);

it.effect.each(["failed", "interrupted", "Stop"] as const)(
Expand Down
Loading
Loading