Files
deepseek-harness/packages/client
Chinesezjc fdcb45b619 fix(web): bound the SGR state and follow real terminal widths
Nine findings, one critical. Terminal cases verified in a real terminal first.

CRITICAL: cells held the accumulated SGR history, so every state boundary
re-emitted the whole chain — output switching color without a full reset
emitted O(n^2) characters. Measured: 3200 such cells produced 25 MB, and the
reviewer's ~90 KB alternating-color case is well under bash's own output cap.
State is now a normalized record (foreground, background, attribute set) with
one canonical sequence per boundary, so the emitted text is linear in cells;
the 90 KB case parses in 36 ms. That also makes the attribute closers every
chalk-based tool writes actually close: `\x1b[1mbold\x1b[22mplain` leaves the
following write PLAIN, which a real terminal confirms.

Width follows emoji presentation, not the U+2600-U+27BF block: `A✓B` redrawn
with `XY` shows `XYB`, so the check every progress line writes is ONE column.
Taking the block as wide misaligned exactly the output this card exists for.

Writing over either half of a wide pair blanks the other, since a terminal
cannot leave one cell of a two-cell glyph standing.

`line\n\x1b[0m` does not end in a newline as a string yet its last parsed line
holds nothing visible, so the terminator check now reads the parsed lines — it
had added a blank row and inflated the collapse count.

A line with no cursor movement no longer builds a column buffer at all; only
its SGR is folded, so an `ls -R` or a 5k-line log allocates nothing per
character.

The `.terminalDescription` rule had been inserted into an existing grouped
selector, silently giving `.codeBody` description typography and changing its
bottom margin from 4px to 0 — a pre-existing surface this PR does not own.
Split out, `.codeBody`'s margin restored.

Three comments contradicted their code: the fixture's exit-marker line (still
claiming recovery from a marker deliberately removed), `bash-sample`'s header
(still routing a click to the details panel, and calling the consumer's cap the
block's own), and a DetailsPanel comment stacked above the wrong rule. The
ui-primitives README documented only the CR/BS half of the replay, so a reader
would expect `OK0%` where `100%\r\x1b[KOK` renders `OK`.
2026-07-29 16:42:10 +08:00
..
2026-07-26 05:06:39 +08:00

client/ — web-GUI browser half

English | 中文

The browser side of the dsh web GUI: shell kernel, module system, wire consumer, React-free object services, the slot system, and the ui-* feature-plugin roster. Authoring rules live in AGENTS.md; the host half is host/. All product packages, named @deepseek-ai/dsh-client-<name>.

Package Role ctx key / slot
web/ Shell kernel: AppWebEntry runs the two-stage boot over the host-pushed entry graph (boots the tree)
modules/ Client module system: browser peer of Node's ESM loader as a lazy CJS table under the vendored cordis Loader (module face)
web-react/ Shell-side React glue: createSlotRenderer + SessionProvider render seats (renderer install)
connection/ Wire consumer both ends: browser ctx.connection (shared api client + stream loop) and the node half mounting the /api route with its browser-trust fence ctx.connection
runtime/ Client cordis boot and React-free object services: slots, Sessions, Workspaces, per-session bindings ctx.slots ctx.sessions ctx.workspaces
hmr/ Dev-only hot reload for fetch-arrival client plugins (--dev graphs) (dev entry)
locale/ Browser locale preference (zh/en) plus the ns×locale dictionary registry ctx.locale
ui-slots/ Slot registry pure core: SlotMap merging, single register API, the four-share props family (types + core)
ui-theme/ Theme preference over the --dsw-* token stylesheets (light/dark/system) ctx.theme
ui-primitives/ Pure React atoms: icons, Button/Pill/Menu/Modal/Input, markdown family (component library)
ui-layout/ Shell three-column AppFrame; declares sidebar / conversation / details / conversation.empty ctx.layout
ui-sidebar/ Sidebar shell: Workspace/session rail, search, collapse; declares sidebar.workspaces (slot host)
ui-workspace/ Shared Workspace picker: browser region + hero picker over the same creation flow (fills sidebar.workspaces, conversation.hero.workspace)
ui-conversation/ Conversation domain: skeleton, chat view, input dock, per-tool row slots (slot host)
ui-trajectory/ Trajectory/Waterfall view tabs; the minimal pure-consumer plugin exemplar (fills conversation.view)
ui-command/ Command surface: session-keyed directory cache, / source, three-kind dispatch ctx.command
ui-slash/ Input trigger pipeline: / and @ detection, grouped candidate menu, source roster ctx.slash
ui-skill/ /-trigger skill reference source over the skill.list RPC (registers into ctx.slash)
ui-subagent/ @-trigger subagent reference source over the sessions snapshot (registers into ctx.slash)
ui-model/ Model selection: /model popupSelect + the composer model seat over ModelService ctx.models
ui-question/ Web ask_user_question: host half mounts the tool, browser half fills the composer seat (fills conversation.composer)
ui-settings/ Settings shell: trigger chrome + modal panel; declares the settings.* slots (slot host)
ui-settings-general/ Settings ownerless copy: chrome content + General section skeleton (fills settings.*)
ui-models/ Models settings nav entry (content column lands in a later phase) (fills settings.section)

Feature UI composes only through the slot system (ctx.slots.register) — the slot system standard is the definitive model; the web client architecture note owns the loading chain and object layer.