shadcn's defaults are built for a standalone app: 14px body, 36px controls,
solid primary fills and elevation read as a foreign surface next to this
plugin's own tabs (files, changes, side chat, settings), which are 12/11/10px
type, 28px controls, hairline borders and flat panels. Retuning the layer did
not converge, so the layer goes.
- Removed: src/client/ui/** (21 vendored components), components.json,
scripts/ui-css.mjs, the Tailwind/PostCSS compile step in tsdown, and the deps
tailwindcss / @tailwindcss/postcss / postcss / radix-ui /
class-variance-authority / tailwind-merge. (diff and simple-icons stay: they
patch undeclared bare imports of the host primitives, unrelated to shadcn.)
- The seven page surfaces are back on the plugin's own base: *.module.css with
--dsw-* tokens plus the host primitives (Button/Menu/Modal/Pill/Tag/Switch/
Input/Tooltip/StateDot/MarkdownText). Behaviour, data-* hooks, aria labels,
i18n keys and the AnchoredPopover geometry/drag/dismiss contract are
unchanged; the non-styling fixes landed during the shadcn era (model id
dedupe, task-window CAS, drawer arming, popover dragging, the chunk packaging
glob) were re-applied and are covered by the existing specs.
- The tasks page returns to the core bundle: `tasks` left CHUNK_NAMES /
CHUNKS / ChunkName, src/client/chunks/tasks.tsx is gone and tabs.tsx imports
SubagentView directly.
- The reverted stylesheet was retuned to the plugin's measured vocabulary
(12/11/10px, 28px controls, border-l2 with border-l1 hairlines, hover fills,
no static shadows, --dsw-shadow-lv2/3 on floating layers only) and verified
against a 70-assertion real-browser contract; the harness's own two
measurement defects were fixed with a falsification test proving the
assertions still catch injected regressions (56/70, 14 FAIL).
- Tests: ui-foundation / ui-bundle / ui-shadows (the shadcn guards) deleted,
theme.spec scoped back to the plugin's own stylesheets, bundle-route and
manifest-consistency chunk lists back to four. Docs: AGENTS.md and the plugin
guide drop the Tailwind/shadcn guidance; the design doc marks those sections
superseded and records why the layer was removed.
Verified: typecheck/lint clean; 133 files / 1410 tests; core bundle 987.3 KiB
with no radix-ui/--tw-/tailwind strings and no client-tasks.js; mount e2e 7/7;
20 real-browser screenshots at 360/720 for graph, tree, board, task window,
drawer (including armed), both popovers and the 77-node fixture.
Visual base replacement for the Tasks page after the 'hard shadows make a
dense graph unreadable' review. Behaviour, hooks, aria labels and i18n are
unchanged; the base layer is not.
- FOUNDATION: Tailwind v4 wired into tsdown's css-inline plugin (theme +
utilities ONLY — preflight is excluded because the plugin's stylesheet is a
global <style data-plugin> tag and would repaint the host page), shadcn
tokens bridged to --dsw-* so the DSH theme keeps flipping light/dark with no
dark: overrides, cn() util, components.json for the CLI, src/client/ui/.
- COMPONENTS: shadcn's component source vendored into src/client/ui (button,
card, badge, separator, input, textarea, tooltip, popover, dropdown-menu,
scroll-area, collapsible, toggle-group, skeleton, spinner, empty) with a
documented local diff: lucide-react replaced by host IconXxx (no new icon
dep), shadow-* dropped from non-floating variants, unused variants pruned,
and React.forwardRef restored on Button — the host runs React 18, where
radix's Slot cannot hand a ref to a plain function component (dev warns,
production silently mis-anchors the floating layer).
- SHADOWS: static panels are now hairline + surface tiers with hover:bg-muted;
shadows exist only on floating layers. Settled nodes recede by ink
(text-muted-foreground/foreground-3) instead of opacity.
- DENSITY: node cards widened 132 -> 150px with a two-line clamped title
(13px text in a 132px card left ~7 CJK characters), reserved height 46 -> 60.
- CHUNK: the whole page (graph, tree, task window, board, drawer, shadcn/radix
layer and its stylesheet) moved into the lazy 'tasks' chunk. Core bundle
988.5 -> 835.6 KiB (-153 KiB); the 709.8 KiB UI payload loads only when the
Tasks tab opens.
- MODEL: workflow members whose childId is already a real node elsewhere are
no longer synthesized into a duplicate id (React keys/edges), with a
last-resort per-id dedupe.
- GUARDS: tests/ui-foundation.spec.ts, tests/ui-bundle.spec.ts (core/chunk
split + size budget + no preflight/oklch/lucide), tests/ui-shadows.spec.ts,
extended tests/theme.spec.ts (no colour literal / palette class in
src/client/ui/** and the migrated files).
- REVIEW FIXES: portal popovers re-scoped under .dsw-tasks, graph loading
state, 11px floor, error nodes no longer tint a whole border, TeamBoard's
delete item is neutral (the window owns the two-step confirm).
Verified: typecheck / lint / 136 files 1502 tests / build bundle assertions /
mount e2e 7-7; browser harness 65-65 checks with 0 console errors.
DSH 0.1.6-alpha.2 ships a right-Sidebar terminal and a right-Sidebar browser
of its own. Both were things this plugin had built from scratch, so keeping
them meant two implementations of one surface plus a plugin that shadows a
host type the product is actively developing.
Terminal: the plugin's whole PTY stack goes — the UI-tab manager, the
agent-owned registry, the lazy node-pty loader, the eight model-facing
terminal_* tools, the xterm view and its font/link/scrollback helpers, and
the cross-session 'pinned terminal' rail that existed only to keep a terminal
alive across a session switch. The host's terminal kind becomes the only
provider, which also retires the node-pty dependency, its pnpm allowBuilds
entry, and the installer's -Repair mode (issue #140's whole class of
'node-pty failed to load' support).
Model-facing capability note: the plugin used to inject terminal_* tools
(off by default). Those are the model's only cross-call persistent terminal,
and DSH's own equivalent (@deepseek-ai/dsh-tool-terminal) is not mounted by
any shipped bundle, so removing them leaves the model with the one-shot
bash/pwsh tools only. That is the accepted cost of not maintaining a second
PTY surface; the README documents how to enable the upstream toolset.
Browser: the plugin's browser tab type is dropped, so the host's own browser
kind owns embedded pages. The DOM-level link takeover STAYS (it is the only
thing that can route an https link to the system browser by protocol, which
the host does not do) but now delegates to the host's browser tab via
ctx.sidebarRight.openTab('browser', …) with a window.open fallback, and the
two settings that only ever configured the plugin's own iframe (sandbox
escape hatch, loopback allowlist) go with it — along with the host-side
browser.probe route they fed.
Cards: the empty-pane welcome cards adopt the host's guide-capsule recipe
(0.5px l4 hairline, 24px radius, layer-1 fill, 56px floor, 14px/20px padding)
so the plugin's 'pick what opens here' surface reads as the same design as
DSH's own. A theme.spec guard pins the geometry and the token-only paint —
including that the upstream-only --dsw-alias-bg-l1/-l2 spellings (defined
nowhere, so their declarations are silently dropped upstream) never appear
here.
Also drops the dead state the removed features left behind (SidebarTab.pin,
nextTerminal, agentWaits, bottomOpenedOnce, the agent-tab helpers, the
pinned-tab memo key), re-points every test fixture that used them at
surviving equivalents, and prunes the specs of the deleted features.
The core client bundle statically imported every third-language
dictionary (ja/de/fr/…/zh-HK/MO/TW, ~640KB of source — roughly half
the bundle's weight) for a code path most sessions never enter: the
sidebar's own t() only consults zh/en plus the better-locale override
store, and the dicts' ONLY consumer is that store's register() call.
They now ride the existing lazy-chunk machinery as lib/client-locale.js
(fetched from /sidebar/bundle only when better-locale is actually
installed; a generation counter keeps the async registration moot when
the integration effect re-runs or disposes mid-flight). Until the chunk
lands, t() keeps the zh/en chain; the store's own revision bump on
register re-renders the chrome once the dicts arrive.
Core client.js: 1.46MB → 822KB (-44%). The chunk-boundary key-set
check moves to the existing runtime test (every shipped dict stays
key-set-equal to zh).