Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
d9e3828
feat(web): redesign composer and conversation timeline
maria-rcks Oct 31, 2026
79ad8eb
refactor(chat): remove unused work log helpers
maria-rcks Oct 31, 2026
953cdaf
fix(chat): stop tool shine while waiting for input
maria-rcks Oct 31, 2026
47cddaf
fix(chat): keep paused timer clear of question text
maria-rcks Oct 31, 2026
edbb6d1
fix(chat): keep approval decisions together on narrow screens
maria-rcks Oct 31, 2026
e68603b
fix(chat): match composer corner and send slot dimensions
maria-rcks Oct 31, 2026
b4db2e7
fix(chat): match the collapsed working window dimensions
maria-rcks Oct 31, 2026
72bbce4
fix(chat): acknowledge custom question answers in the footer
maria-rcks Oct 31, 2026
bfd8cb3
style(chat): format grouped approval actions
maria-rcks Oct 31, 2026
792dcc6
fix(chat): summarize wake-ups and retain the latest answer
maria-rcks Oct 31, 2026
4d30ea4
fix(chat): use the pill radius token and accessible wake-up label
maria-rcks Oct 31, 2026
00dfe76
style(chat): format the composer surface
maria-rcks Oct 31, 2026
78cb6ef
fix(chat): align timeline rows and attachment layouts with design
maria-rcks Oct 31, 2026
ebd4dcd
fix(chat): keep row class helper compiler eligible
maria-rcks Oct 31, 2026
2e5079b
fix(chat): expose the existing interrupt action on live work rows
maria-rcks Oct 31, 2026
4ab6744
fix(chat): keep interrupt available in grouped live rows
maria-rcks Oct 31, 2026
5340e8d
fix(chat): keep turn interrupt off task summary rows
maria-rcks Oct 31, 2026
653c5d2
fix(chat): match composer suggestion and picker menus to the design
maria-rcks Oct 31, 2026
222f06f
fix(chat): keep branch picker separators stable
maria-rcks Oct 31, 2026
e331e5d
fix(chat): let the picker text role own its weight
maria-rcks Oct 31, 2026
18773e1
fix(chat): match the option c composer and integrated tray
maria-rcks Oct 31, 2026
8f1347b
fix(chat): validate plan actions and queued reorder commands
maria-rcks Oct 31, 2026
ebcc7b4
fix(chat): keep work and notices in the composer tray
maria-rcks Oct 31, 2026
1244a51
fix(chat): reserve the machine chip for remote threads
maria-rcks Oct 31, 2026
84bf84f
fix(chat): wire approval shortcuts in the composer tray
maria-rcks Oct 31, 2026
c66a2f9
fix(chat): settle completed tasks and cap the prompt at eight lines
maria-rcks Oct 31, 2026
c7fb78d
fix(chat): keep tray work bound to the active run
maria-rcks Oct 31, 2026
6ab83e4
fix(chat): accept approval keys from the focused workspace
maria-rcks Oct 31, 2026
8131dd6
fix(chat): collapse composer controls on narrow panes
maria-rcks Oct 31, 2026
43de07c
fix(chat): keep picked attachments above the prompt
maria-rcks Oct 31, 2026
d7c6d00
fix(chat): preview queue order while dragging
maria-rcks Oct 31, 2026
cdf8772
fix(chat): show the branch picker chevron in the tray
maria-rcks Oct 31, 2026
a70ae14
fix(chat): stage file rows without an automatic reference
maria-rcks Oct 31, 2026
ac880f5
fix(chat): keep question actions in the tray panel
maria-rcks Oct 31, 2026
30ed61d
fix(chat): align composer and requests with final design
maria-rcks Oct 31, 2026
e6bc263
fix(chat): anchor composer and branch popup to design edges
maria-rcks Oct 31, 2026
b0e9884
fix(chat): keep approval actions together and remove unused drop overlay
maria-rcks Oct 31, 2026
e73454c
fix(chat): keep tray border inside composer dimensions
maria-rcks Oct 31, 2026
284293e
fix(chat): reconcile node views outside React lifecycle
maria-rcks Oct 31, 2026
af3847e
fix(chat): preserve synchronous caret updates and popup width
maria-rcks Oct 31, 2026
e6e70b1
fix(web): steered turns' fold rows each show their own time and edits…
flamboh Nov 2, 2026
e379c56
fix(web): a finished turn's label stays readable next to still-runnin…
flamboh Nov 2, 2026
49a8e7f
fix(web): steered turns read like a chat with one fold at the end (#2…
flamboh Nov 2, 2026
6badf48
test(web): drop the removed assistant timestamp assertion
maria-rcks Nov 3, 2026
6e6b823
fix(web): the working window expands downward without moving the bar …
flamboh Nov 2, 2026
b99dda2
refactor(chat): remove obsolete thinking interrupt flag
maria-rcks Nov 3, 2026
49350f3
fix(web): keep background work off turn fold rows
maria-rcks Nov 2, 2026
6502a70
feat(web): context chips drop the pill and read as colored text, in r…
flamboh Nov 2, 2026
c48b949
fix(web): narrow composers collapse the env and branch chips instead …
flamboh Nov 2, 2026
d59abb8
fix(web): scroll collapsed working history independently
maria-rcks Nov 3, 2026
437962f
fix(web): Limits opens the usage limits popover directly (#29222)
flamboh Nov 3, 2026
03b37d8
fix(web): clarify account scope in the usage limits popover
maria-rcks Nov 3, 2026
fdfed59
fix(web): the working bar shows its fold caret again (#29969)
flamboh Nov 3, 2026
bd92acc
fix(web): collapsing the working window keeps its top fade (#29972)
flamboh Nov 3, 2026
f6b2e83
fix(web): a running turn's answer streams below the fold, not inside …
flamboh Nov 3, 2026
6726a16
fix(web): new wake-ups join their fold without shifting the timeline …
flamboh Nov 3, 2026
6e80394
fix(web): retain composer compatibility after rebase
maria-rcks Nov 3, 2026
9216309
fix(web): composer text and placeholder stay clear of the send button…
flamboh Nov 5, 2026
c697df0
fix(web): pasting an image or file drops its chip in the prompt again…
flamboh Nov 5, 2026
44987df
fix(web): collapsing the working window no longer nudges the timeline…
flamboh Nov 5, 2026
d0dd387
fix(web): the working window's scrollbar shows its whole history from…
flamboh Nov 5, 2026
5d3361b
fix(web): finished and wake-up folds work like the live working fold …
flamboh Nov 5, 2026
1d0c6fa
fix(web): finished turn folds show the live timer's duration format (…
flamboh Nov 5, 2026
9527833
fix(web): tighten the bubble corner under the steer label
maria-rcks Nov 5, 2026
f63cd84
fix(web): keep main's /clear command and live transcript working in t…
maria-rcks Nov 6, 2026
b45cc8a
fix(web): carry the tool call setting through the working window and …
maria-rcks Nov 6, 2026
ae695a7
fix(web): hide all drops the duration row when a reply has nothing to…
maria-rcks Nov 6, 2026
9cf23d9
fix(web): composer rows share the tray row's left and right inset
maria-rcks Nov 8, 2026
6dbc65e
fix(web): a steer sent right after your message joins its bubble group
maria-rcks Nov 9, 2026
74fc118
test(web): drop low-signal tests the redesign added to the composer a…
maria-rcks Nov 9, 2026
fa7203e
refactor(web): a queue drag is one value instead of two run ids
maria-rcks Nov 9, 2026
4ea9d93
fix(web): drop the plan commands and plan preview main removed from t…
maria-rcks Nov 9, 2026
6fb27b0
fix(web): the composer effort chip follows main's provider-free reaso…
maria-rcks Nov 9, 2026
fac8590
style(web): format the main timeline integrations
maria-rcks Nov 11, 2026
6f6b986
fix(web): keep citation editor source navigation on the parent thread
maria-rcks Nov 11, 2026
459fccf
fix(web): remove morphing from the redesigned fold caret
maria-rcks Nov 11, 2026
11cd68a
fix(web): carry stop and disclosure state through redesigned rows
maria-rcks Nov 11, 2026
f35d4ae
fix(web): pass compaction and disclosure state through the composer l…
maria-rcks Nov 11, 2026
c6bb1f4
fix(web): give the citation editor's source action the source thread
maria-rcks Nov 11, 2026
a3ea09e
fix(web): steered turns keep only final replies outside the fold
maria-rcks Nov 11, 2026
4c4ff58
fix(web): terminal selection action and work rows follow the redesign…
shivamhwp Nov 12, 2026
cbca272
test(web): hover main's question option preview through the redesign'…
maria-rcks Nov 12, 2026
8cbc52c
test(web): find main's html image gallery in the redesigned viewer's …
maria-rcks Nov 12, 2026
5a7f540
test(web): drop two rule-excluded tests from the redesign (#33892)
shivamhwp Nov 12, 2026
55b153f
fix(web): the working line says which provider is starting until its …
maria-rcks Nov 12, 2026
5f4a2b4
fix(web): main's subagent wait, queued image viewer and touch menu fo…
maria-rcks Nov 14, 2026
59a07da
test(web): fit citation cancel and bound task model checks to redesig…
maria-rcks Nov 14, 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 @@ -340,10 +340,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 @@ -358,9 +359,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}:${threadPullRequestKeyOf(entry)}`,
type: "pull-request",
type: "pull-request" as const,
pullRequest: {
number: entry.number,
title: entry.title,
Expand All @@ -377,6 +390,7 @@ export function useComposerCommandMenu({
: `${entry.repository} · `
}${entry.title}`,
}));
return [...pullRequestItems, ...threadItems];
}

if (trigger.kind === "slash-command") {
Expand Down Expand Up @@ -565,7 +579,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 DocumentText from "../provider/DocumentText.ts";
import * as ProviderRegistry from "../provider/ProviderRegistry.ts";
import * as ServerSettings from "../serverSettings.ts";
Expand Down
194 changes: 121 additions & 73 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3229,87 +3229,135 @@ describe("CodexAdapterV2 post-settle continuation", () => {
),
);

it.effect("compacts Codex with the native RPC and completes the compaction turn", () =>
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.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([
Expand Down
Loading
Loading