fix(web): diff card review — TUI parity, path-header overlap, double-resolve
Bring the TUI diff footer onto the same terminator rule and distinct-path count the Web DiffBlock uses (a trailing newline terminates its line; two hunks in one file read as 1 file), so the two front ends' `+A -R · N file(s)` footers agree. Reserve space in the diff path header for the floating copy button so a long path no longer scrolls under it. Pass the tool's raw path to the injected openFile (which already resolves against cwd) instead of resolving twice. Rename the shared block-body CSS class to a card-neutral cardBody so a terminal-spacing tweak cannot silently move the diff card. Add a same-file two-hunk TUI unit test and an assembled built-boot assertion that the write turn renders +1 -0 · 1 file end to end.
This commit is contained in:
@@ -113,14 +113,15 @@
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
/* The two block-shaped expanded bodies: the code variant's run_code program
|
||||
through CodeBlock (shiki-highlighted TypeScript) and a terminal card's
|
||||
command output through TerminalBlock. Both are drawn by the shared
|
||||
primitive, so only the row's indentation is this file's concern — the margin
|
||||
also replaces each primitive's own standalone vertical spacing with the
|
||||
flow's row rhythm. */
|
||||
/* The block-shaped expanded bodies: the code variant's run_code program through
|
||||
CodeBlock (shiki-highlighted TypeScript), a terminal card's command output
|
||||
through TerminalBlock, and a write/edit diff through DiffBlock. All are drawn
|
||||
by a shared primitive, so only the row's indentation is this file's concern —
|
||||
the margin also replaces each primitive's own standalone vertical spacing with
|
||||
the flow's row rhythm. Card-neutral: it carries no terminal- or diff-specific
|
||||
value, so it fits every block body. */
|
||||
.codeBody,
|
||||
.terminalBody {
|
||||
.cardBody {
|
||||
margin: 4px 0 4px 22px;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,9 +83,10 @@ export function ToolRow({
|
||||
// A row that names a single file keeps one interaction (open that path);
|
||||
// args expand is off whether or not the open callback is wired yet. A card
|
||||
// body (terminal or diff) still expands: only the file variants carry a
|
||||
// path. A write/edit row carries both a file path and a diff card, so its
|
||||
// path link and its expandable card coexist — the card expands, the summary
|
||||
// stays a link.
|
||||
// path. A write/edit row carries both a file path and a diff card, so both
|
||||
// the path link and the expandable card are offered — the collapsed row shows
|
||||
// the path link, and expanding swaps it for the card body (DisclosureRow
|
||||
// renders collapsedContent only while closed).
|
||||
const singleFile = filePath !== undefined
|
||||
const fileLink = singleFile && onOpenFile !== undefined
|
||||
const cardBody = terminalBody !== null || diffBody !== null
|
||||
@@ -138,9 +139,9 @@ export function ToolRow({
|
||||
<div className={css.terminalDescription}>{terminalBody.description}</div>
|
||||
)}
|
||||
{terminalBody !== null
|
||||
? <TerminalBlock {...terminalBody.card} maxLines={CHAT_TERMINAL_MAX_LINES} className={css.terminalBody} />
|
||||
? <TerminalBlock {...terminalBody.card} maxLines={CHAT_TERMINAL_MAX_LINES} className={css.cardBody} />
|
||||
: diffBody !== null
|
||||
? <DiffBlock {...diffBody.card} maxLines={CHAT_DIFF_MAX_LINES} className={css.terminalBody} />
|
||||
? <DiffBlock {...diffBody.card} maxLines={CHAT_DIFF_MAX_LINES} className={css.cardBody} />
|
||||
: variant === 'code'
|
||||
? <CodeBlock code={text} lang="typescript" className={css.codeBody} />
|
||||
: <div className={css.body}>{text}</div>}
|
||||
|
||||
@@ -101,8 +101,9 @@
|
||||
font: var(--dsw-font-xs-13);
|
||||
}
|
||||
|
||||
/* The terminal card sits directly under its section label, so it drops the
|
||||
primitive's standalone vertical margin; the section owns the spacing. */
|
||||
.terminal {
|
||||
/* A card body (terminal or diff) sits directly under its section label, so it
|
||||
drops the primitive's standalone vertical margin; the section owns the
|
||||
spacing. Card-neutral: no terminal- or diff-specific value. */
|
||||
.cardBody {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -146,12 +146,12 @@ function OutputBody({ material, cwd }: { material: CallMaterial; cwd: string | u
|
||||
{terminal.description !== undefined && (
|
||||
<div className={css.terminalDescription}>{terminal.description}</div>
|
||||
)}
|
||||
<TerminalBlock {...terminal.card} className={css.terminal} />
|
||||
<TerminalBlock {...terminal.card} className={css.cardBody} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
const diff = diffCardModel(material.block)
|
||||
if (diff !== null) return <DiffBlock {...diff.card} className={css.terminal} />
|
||||
if (diff !== null) return <DiffBlock {...diff.card} className={css.cardBody} />
|
||||
// A settled call always carries the result node the flattened form needs;
|
||||
// the running shape has no result to flatten.
|
||||
if (!('kind' in material.block)) return <div className={css.empty}>运行中…</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { Context } from 'cordis'
|
||||
import { DiffBlock, IconEditOutline16, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { ToolRowProps } from '../contract/slots.ts'
|
||||
import { CHAT_DIFF_MAX_LINES, diffCardModel } from '../contract/diff-card-model.ts'
|
||||
import { resolveToolPath, toolRowModel, type ToolRowState } from '../contract/tool-call-model.ts'
|
||||
import { toolRowModel, type ToolRowState } from '../contract/tool-call-model.ts'
|
||||
import css from './file-mutation-row.module.css'
|
||||
|
||||
function leadingFor(state: ToolRowState) {
|
||||
@@ -63,8 +63,9 @@ function errorText(block: ToolRowProps['block']): string | null {
|
||||
/**
|
||||
* File-mutation row: icon + {Edit,Write} · {path} in the shared ToolRow chrome,
|
||||
* with the applied diff resident below it. The summary is a path link (a file
|
||||
* tool's interaction) resolved against the session cwd and opened through the
|
||||
* host; the card's copy and expand controls are the row's only other actions.
|
||||
* tool's interaction); the host's `openFile` resolves it against the session
|
||||
* cwd, so this passes the tool's own path verbatim. The card's copy and expand
|
||||
* controls are the row's only other actions.
|
||||
*/
|
||||
export function FileMutationRow({ toolName, block, cwd, openFile }: ToolRowProps) {
|
||||
const model = toolRowModel(toolName, block, cwd)
|
||||
@@ -85,7 +86,7 @@ export function FileMutationRow({ toolName, block, cwd, openFile }: ToolRowProps
|
||||
<button
|
||||
type="button"
|
||||
className={css.fileLink}
|
||||
onClick={() => { openFile(resolveToolPath(cwd, filePath)) }}
|
||||
onClick={() => { openFile(filePath) }}
|
||||
>
|
||||
{model.summary}
|
||||
</button>
|
||||
|
||||
@@ -46,10 +46,14 @@
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* A file header: the path in the primary tone, set apart by weight. */
|
||||
/* A file header: the path in the primary tone, set apart by weight. The copy
|
||||
button floats over this first row's top-right corner, so reserve space at the
|
||||
line's end for it — a long path scrolls under the button otherwise, and the
|
||||
button's hit area would eat clicks on the path's tail. */
|
||||
.path {
|
||||
color: var(--dsw-alias-label-primary);
|
||||
font-weight: 600;
|
||||
padding-right: 56px;
|
||||
}
|
||||
|
||||
/* A same-file second hunk's separator (a scattered edit), in the dim tone. */
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
// color), with a dim `└ +A -R · N file(s)` footer. The +/- block form mirrors
|
||||
// the TUI transcript's diff card (packages/ui/tui: diffLines) so a diff reads
|
||||
// the same across front ends: the removed side is the old text in full, the
|
||||
// added side the new text in full. Output never soft-wraps — an aligned source
|
||||
// line keeps its indentation and scrolls horizontally instead of folding.
|
||||
// Colors resolve through --dsw-* tokens; geometry mirrors CodeBlock.
|
||||
// added side the new text in full, both split on the same terminator rule, and
|
||||
// the footer counts distinct paths on both ends. Output never soft-wraps — an
|
||||
// aligned source line keeps its indentation and scrolls horizontally instead of
|
||||
// folding. Colors resolve through --dsw-* tokens; geometry mirrors CodeBlock.
|
||||
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
@@ -68,9 +69,8 @@ const ROW_CLASS: Record<DiffRow['kind'], string | undefined> = {
|
||||
* opens each new file; a same-file second hunk (a scattered edit) opens with a
|
||||
* `⋯` gap instead of repeating the path. Every old-side line counts toward
|
||||
* `removed` and every new-side line toward `added`. The file count is of
|
||||
* DISTINCT paths, which is the one deliberate divergence from the TUI diff card:
|
||||
* the TUI footer uses `diffs.length`, so two hunks in one file read there as
|
||||
* `2 files`, whereas this counts the one file they belong to.
|
||||
* DISTINCT paths, matching the TUI diff card's footer, so two hunks in one file
|
||||
* read as `1 file` on both front ends.
|
||||
* @param diffs - the hunks to render.
|
||||
* @returns the body rows, the +/- totals, and the distinct-file count.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user