feat(sidechat): Codex 风格转录重构 — 首问显示 / 注入拆分 / 折叠行 / 弹窗去抖 (#314)
* fix(sidechat): split context injection from the first user message The first composer message rode the same user/message event as the side boundary prompt (+ parked snapshot), and the transcript dropped that whole row by its boundary prefix — swallowing the user's first question. Deliver first contact as TWO messages: the boundary+snapshot rides agent.inject (queued next-step context, claimed first at the opening step, no wake) stamped with source kind 'plugin', and the question is the follow-up that wakes the driver. The log records injection and question as separate events; the transcript maps injections (plugin-sourced, or the legacy boundary-prefix blob of pre-split threads) onto a collapsible injection row and renders genuine user messages — the first one included — as user rows. boundaryDelivered still recognizes the injection by its unchanged boundary-prefix opening. * feat(sidechat): codex-style collapsible rows and creating shimmer Tool calls, thinking and context injections now share one quiet single-line chrome (chevron + label + a one-line argument summary) that expands into an indented body on a hairline thread — no cards or fills. Streaming labels and the creating hero shimmer (shimmer = generating); failed tools go danger; reduced-motion stills every loop. * fix(sidebar): debounce subagent auto-open past the title frame The host delivers a new child's origin and title in separate frames, so a Side Chat thread's first visible frame lacks its 'Side: ' label and tripped the 0→N auto-open — every thread creation popped the task page. The trigger now rearms for 500ms and re-evaluates the original baseline against the live snapshot, by which time the title filter recognizes the thread. Genuine subagents still auto-open, half a second later. * fix(sidechat): render legacy threads' first message as its own bubble Threads created before the host split still log the boundary (+snapshot) and the question as ONE 'user' message; the transcript mapped that row wholly onto the injection row, so the user's first question only appeared inside the expansion. The boundary prompt is a known constant, so the legacy row now splits THERE: the injection row keeps the prompt, the remainder renders as the user's real message (a pre-split snapshot, when present, rides along — the common case is a pure question). Row React keys become kind:seq so the split's two rows never collide.
This commit is contained in:
@@ -2,10 +2,12 @@
|
||||
* language mirrors the main conversation pane: right-aligned user bubbles
|
||||
* on --dsw-specific-bubble, full-width assistant markdown, a capsule
|
||||
* composer card on --dsw-specific-input-major with a circular accent
|
||||
* send/stop button, and shimmer reserved for the running status line and
|
||||
* executing tools. Motion is crossfade-only (≤200ms entrances, no
|
||||
* springs); reduced-motion disables every loop. All values consume DSW
|
||||
* tokens (fallbacks point at older alias tokens for rc-era hosts). */
|
||||
* send/stop button. Tool calls, thinking and context injections share one
|
||||
* collapsible-line chrome (no cards); shimmer is reserved for generating
|
||||
* states (running status line, streaming rows, the creating hero). Motion
|
||||
* is crossfade-only (≤200ms entrances, no springs); reduced-motion disables
|
||||
* every loop. All values consume DSW tokens (fallbacks point at older alias
|
||||
* tokens for rc-era hosts). */
|
||||
.sidechat {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -172,82 +174,99 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.sidechatReasoning {
|
||||
/* ── collapsible context rows (tool / thinking / injection, Codex-style) ──
|
||||
* One quiet line — chevron + label + one-line summary — that expands into
|
||||
* an indented body hung on a hairline thread. No cards, no fills: hierarchy
|
||||
* rides ink weight alone. Streaming rows shimmer the label (shimmer =
|
||||
* generating); failed rows go danger; reduced-motion stills every loop. */
|
||||
.sidechatRow {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.sidechatReasoningSummary {
|
||||
display: inline-block;
|
||||
.sidechatRowLine {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 1px 0;
|
||||
font: var(--dsw-font-xxs-12);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sidechatReasoningSummary::-webkit-details-marker {
|
||||
.sidechatRowSummary {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sidechatRowSummary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidechatReasoningBody {
|
||||
margin-top: 2px;
|
||||
padding-left: 2px;
|
||||
.sidechatRowSummary:hover {
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
}
|
||||
|
||||
.sidechatRowStatic {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.sidechatRowChevron {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
transition: transform 100ms ease-out;
|
||||
}
|
||||
|
||||
.sidechatRow[open] .sidechatRowChevron {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.sidechatRowLabel {
|
||||
flex: none;
|
||||
max-width: 60%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
}
|
||||
|
||||
.sidechatRowMono {
|
||||
font-family: var(--dsw-font-mono);
|
||||
}
|
||||
|
||||
.sidechatRowMeta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: var(--dsw-font-mono);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
.sidechatRowFailed .sidechatRowLabel,
|
||||
.sidechatRowFailed .sidechatRowMeta {
|
||||
color: var(--dsw-alias-danger);
|
||||
}
|
||||
|
||||
.sidechatRowBody {
|
||||
margin: 2px 0 4px 7px;
|
||||
padding: 2px 0 2px 10px;
|
||||
border-left: 1px solid var(--dsw-alias-hairline);
|
||||
}
|
||||
|
||||
.sidechatRowProse {
|
||||
font: var(--dsw-font-xxs-12);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidechatTool {
|
||||
align-self: stretch;
|
||||
border: 1px solid var(--dsw-alias-hairline);
|
||||
border-radius: 10px;
|
||||
background: var(--dsw-alias-bg-base);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidechatToolSummary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 5px 10px;
|
||||
font: var(--dsw-font-xxs-12);
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sidechatToolSummary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidechatToolName {
|
||||
font-family: var(--dsw-font-mono);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Running tool: a light sweep across the summary row (shimmer = generating). */
|
||||
.sidechatToolExecuting {
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
var(--dsw-alias-interactive-bg-hover) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: sidechatSweep 2.6s linear infinite;
|
||||
}
|
||||
|
||||
.sidechatToolFailed {
|
||||
color: var(--dsw-alias-danger);
|
||||
}
|
||||
|
||||
.sidechatToolArgs,
|
||||
.sidechatToolResult {
|
||||
.sidechatRowCode {
|
||||
margin: 0;
|
||||
padding: 6px 10px;
|
||||
border-top: 1px solid var(--dsw-alias-hairline);
|
||||
padding: 4px 0;
|
||||
font: var(--dsw-font-xxs-12);
|
||||
font-family: var(--dsw-font-mono);
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
@@ -257,6 +276,25 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidechatRowCode + .sidechatRowCode {
|
||||
border-top: 1px solid var(--dsw-alias-hairline);
|
||||
}
|
||||
|
||||
/* Shimmer sweep text: generating (streaming row labels, creating hero). */
|
||||
.sidechatShimmerText {
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
var(--dsw-alias-label-tertiary) 0%,
|
||||
var(--dsw-alias-label-primary) 50%,
|
||||
var(--dsw-alias-label-tertiary) 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
animation: sidechatSweep 2.6s linear infinite;
|
||||
}
|
||||
|
||||
/* ── running status line (above the composer, main-pane style) ──────── */
|
||||
.sidechatStatus {
|
||||
flex: none;
|
||||
@@ -388,9 +426,12 @@
|
||||
animation: none;
|
||||
}
|
||||
.sidechatStatusText,
|
||||
.sidechatToolExecuting {
|
||||
.sidechatShimmerText {
|
||||
animation: none;
|
||||
background-image: none;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
.sidechatRowChevron {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user