Skip to content

feat(tasks): 任务管理页——workflow/teams 接入 + shadcn/ui 迁移 + DSH 0.1.6-alpha.2 适配 - #680

Open
Menghuan1918 wants to merge 23 commits into
mainfrom
feat/tasks-graph-workflow-teams
Open

Menghuan1918 wants to merge 23 commits into
mainfrom
feat/tasks-graph-workflow-teams

Conversation

@Menghuan1918

@Menghuan1918 Menghuan1918 commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

目的

三件事,按顺序叠在同一分支上(16 个提交):

  1. 适配 DSH 的 workflow 与 Agent Teams 两个新可观测面,重构任务管理页为工作流图(图/树双视图、聚合折叠、可拖动任务窗口、后台任务抽屉);
  2. 把任务管理页的视觉基座整体迁到 shadcn/ui(Tailwind v4 + radix vendoring),根因是上一版自绘风格的硬投影/粗边框在节点密集时"辣眼睛",读不下去;
  3. 跟随上游把 devDeps 推到 DSH 0.1.6-alpha.2(peer 下限不动),期间发现并修掉一批真实缺陷(见「关键返工」)。

变更

A. 宿主侧:三条新插件路由(零 DSH 源码改动、零实验包 import)

路由 行为
workflows.list 折叠整棵会话树的 tool-workflow/run-start|agent-start|agent-end|run-end(与官方 workflow 面板同 4 类事件);存储日志与 session/event 实时镜像按 seq 去重合并;无事件 → {runs:[]}(空而非错);subagents 缺席/失败 → 只折叠 root
teams.view 三分支:层缺席 → {available:false};root 非 lead/未命中 → {available:true, team:null};命中 → {team:{members,tasks}}
teams.taskCreate / teams.taskUpdate 结构镜像 Remote 词汇;CAS(expectedRevision),冲突以 team-task-conflict union 原样返回;层缺席 503 team-error,无团队 404

B. 任务页(图 / 树 / 任务窗口 / 任务板 / 后台任务)

  • 统一模型 tasks-model.ts 供两种视图共用:catalog 走树、workflow run 挂到发起代理下并重挂成员、team roster 富化、已完成叶子按父聚合折叠、每个 session id 只产出一个节点(防重复 React key)。
  • TasksGraph:分层节点 + 贝塞尔连线 + 点阵画布 + 拖拽平移 + 滚轮缩放 + 相位虚线框 + 右下控制条;卡片本身就是详情入口(无嵌套按钮),点击弹可拖动详情窗口。
  • TasksTree:缩进树 + 竖线连接 + 键盘导航(官方 catalog 配方)。
  • TaskWindow:查看/编辑/新建同一个可拖动非全屏窗口(默认 markdown 多行预览,点「编辑」进多行编辑;负责人 Pill 即点即改派;完成/重开/删除两击);四处入口复用同一组件。
  • 后台任务:底部抽屉(≥8 代理自动折叠,手动优先)+ 可拖动输出浮窗(事件回放、2s 轮询、复制、跟随最新开关、两击终止)。
  • tasksViewMode 设置(select,默认 graph)+ 页内临时切换;20 份词典键集同步。

C. shadcn/ui 迁移(视觉基座替换)

  • Tailwind v4 接进 tsdown 的 css-inline 插件:只引 theme + utilities,刻意排除 preflight(插件样式是全局 <style data-plugin>,preflight 会重置整个宿主页面);@source 限定扫描范围。
  • shadcn 令牌全部桥接到 --dsw-*,因此不需要 dark 调色板;src/client/ui/ 下 vendoring 16 个组件(button/card/badge/input/textarea/tooltip/popover/dropdown-menu/scroll-area/collapsible/toggle-group/skeleton/spinner/empty/separator),图标继续用宿主 IconXxx(不引 lucide、不自绘 SVG)。
  • 静态面板零阴影(层级靠 1px hairline + 表面阶梯 + hover:bg-muted),阴影只留给真正的浮层;已完成节点按墨色退后而非降透明度。
  • 任务页整体下沉为懒加载 chunk tasks:核心包 988.5 KiB → 836.3 KiB(净减 ~152 KiB),709.9 KiB 的 UI 负载只在打开任务页时下载(真实外壳 PERF 里可见 /sidebar/bundle/tasks.js 被请求)。
  • 风格回调到 shadcn 默认:静态卡片恢复 stock shadow-xs/shadow-sm(浮层保留 shadow-lg),正文/meta 回到 text-sm/text-xs,状态用 stock Badge 变体,控件用 stock Button 变体与尺寸,行内操作 hover/focus 显现但仍可键盘触达;画布节点 176×68;bandColsFor 改为按可读性下限(宽度/0.78)预算列数,窄面板不再退化成单列。
  • 三份新守卫:ui-foundation(入口/令牌桥/级联层/portal 作用域)、ui-bundle(核心包无 Tailwind/radix/oklch/lucide 且 ≤ 预算;chunk 必须携带)、ui-shadows(静态面板零 shadow-*);theme.spec.ts 扩展颜色字面量检查。

D. DSH 0.1.6-alpha.2 适配(devDeps 推进,peer 不动)

上游锐减到只有三处触达本插件:移除 IconSendOutline16(→IconSendOutline14)、TerminalBlockLabels 新增必需 noExitCode(+20 份词典词条)、ConnectionIndicator 移除 reconnectLabel(rc 线仍必需 → 加跨线 shim,两线都正确)。dsh-tools 不再 peer dsh-code-runtime → 删掉过时 hoist;primitives 新增未声明的裸 import diff/simple-icons → 提升进 dependencies。详见 docs/plans/2026-09-17-dsh-0.1.6-alpha.2-adaptation.md。

CI 钉版留在 0.1.5-rc.2:把挂载车道钉到 alpha.2 会让它在未改动的 main 上就红(外壳进新会话空态 → 侧栏拿不到 session scope → 底部 tab 条永不 attach)。已用探针分支(origin/main + 该一行钉版,run 35484961070)证伪与插件代码的关系,证据写在 AGENTS.md §2/§3;车道适配 alpha 线留作独立事项。

关键返工(都是实测发现,非推测)

# 症状 根因 修法
1 边框/强调色静默消失 裸用 DSH 主题里不存在的 --dsw-alias-accent(17 处),border-l1 只有 4% 黑 换 state-business-primary / border-l4
2 图里节点与 ⓘ 点不动 画布 pointerdown 里 setPointerCapture 把派生 click 重定向到容器(jsdom 不模拟,单测全绿) 去捕获,pan 仅从背景起手
3 图很小 / 不居中 / 标题只剩 7 字 fit 在零尺寸容器上静默放弃;13px 字号配 132px 卡片 ResizeObserver + 双轴居中 + 0.78 可读性下限 + 按容器宽度求解排布;卡片 150px + 两行标题
4 React 18 下浮层锚点错位 shadcn v4 组件按 React 19 写(不写 forwardRef),宿主是 React 18 vendored Button 补 forwardRef(注释标明升级时会被 CLI 抹掉)
5 portal 浮层字体/颜色/焦点环异常 createPortal 到 body 后掉出 .dsw-tasks 作用域重置 每个 portal 内容根补类 + 源码级守卫
6 重复 React key workflow member 的 childId 属于树中别处时被合成同 id 节点 先收集全树 id,仅对完全未知的 id 合成 + 兜底去重
8 真机“完全没有 CSS、边框发虚” 插件为不重绘宿主而排除 preflight 时连带丢了表单控件的归零:<button> 保留 UA 外观(实测 padding:1px 6px、border:2px outset、background:ButtonFace、13.33px 系统字体);另外 @layer 顺序按首次出现决定,utilities 被 import 注册在 base 之前,reset 反压 rounded-md 补一份限定 .dsw-tasks 作用域的 preflight 子集(:where() 保持 (0,1,0),只清 UA 外观)+ 显式 @layer theme, base, components, utilities;;加两条守卫(reset 声明存在、层序在 import 之前)。两个错误假设先用实验否掉:样式确实下发、级联不是原因(真实宿主样式表 + 真实产物做受控样本,important 加了测不出差别 → 已回退)
7 发布版任务页会打不开 package.json#files 逐个枚举 chunk,新增/既有 chunk 漏发(client-tasks.js、client-locale.js) 改 lib/client-*.js glob + 「每个 chunk 都被 files 覆盖」断言

外加一轮对抗式 UI 审查的 8 条 findings(portal 作用域、opacity 退后、常态红删除项、图模式键盘等价路径、11px 字号下限、error 不整卡换色、transition-all 收窄、图模式 loading 态)已全部修复。

验证

  • pnpm typecheck / pnpm lint 0 错误;pnpm vitest run 136 文件 / 1505 用例通过 / 9 skipped。
  • pnpm build:核心包 856,326 B(836.3 KiB,迁移前 1,012,271 B)、chunk 726,905 B(709.9 KiB);产物断言:有 Tailwind 产物、无 preflight / 无 oklch( / 无 lucide-react。
  • 挂载冒烟 pnpm test:mount:CI 三车道全绿(ci / ci-windows / plugin-mount);本地在 @deepseek-ai/dsh@0.1.6-alpha.2 上 7/7(mount=480ms、longtask 2/8、/sidebar/bundle/tasks.js 真实下载 726 KB)。
  • 降级矩阵实测:无实验层 profile → workflows.list {runs:[]} / teams.view {available:false};~/.dsh-web(有实验层)→ {available:true, team:null}、teams.taskCreate → 404 team-error。
  • 可视化 harness(真实组件 + 真实令牌 + Playwright,360/720 × 浅色 + 77 节点压力 fixture)65/65 检查通过、0 console 错误、0 重复 key。

已知边界 / 后续

  • 诊断行(corrupt catalog entry)不再逐 parent 内联,统一由页头「N 个分支加载失败 + 重试」承载(docs/plans/2026-09-14-...md 实施偏差节有理由)。
  • 图模式节点刻意 tabIndex=-1(几十个 tab stop 反而更差),键盘等价路径是控制条的「切换为树状图」;树模式有完整方向键导航。
  • 挂载车道尚未适配 alpha 线(见上);README 的"已在 rc.2 验证"徽章描述的是已发布的 v0.19.1,留待发版时更新。
  • 版本号未 bump(0.19.1):按仓库流程在发版准备阶段处理。

@Menghuan1918 Menghuan1918 changed the title feat(tasks): 任务管理页重构——工作流图 + Agent Teams 任务板 + 后台任务抽屉 feat(tasks): 任务管理页——workflow/teams 接入 + shadcn/ui 迁移 + DSH 0.1.6-alpha.2 适配 Sep 20, 2026
@Menghuan1918
Menghuan1918 force-pushed the feat/tasks-graph-workflow-teams branch from bdbf75f to 22baff7 Compare September 20, 2026 10:23
- workflows.list folds tool-workflow/* session events (the same four types
  the official workflow-run panel folds) across the whole session tree,
  merging the stored log with a live session/event mirror deduped by seq;
  absence of events yields an empty list, never an error.
- teams.view / teams.taskCreate / teams.taskUpdate ride the experimental
  agentTeams service's Remote vocabulary with structural degradation:
  layer absent -> {available:false}; root leads no team -> {team:null};
  CAS conflicts pass through the service's own result union.
- wire gains the team-error code; context-types gains structural mirrors
  of the team view/request vocabulary (no experimental package import).
- 29 unit tests across fold, route merging/degradation, and CAS shaping.
- tasks-model.ts: one pre-order node list for graph AND tree modes —
  catalog walking, workflow-run attachment with member re-parenting and
  synthesis, team enrichment, per-parent settled-leaf fold aggregates.
- tasks-graph-layout.ts: dependency-free tidy-tree layered layout
  (bundle-purity: no graph library in the core bundle).
- api.ts: workflowsList/teamsView/teamsTaskCreate/teamsTaskUpdate with
  the wire result types mirrored from the host routes.
- 11 unit tests across model derivation, edge classification, fold, and
  layout geometry.
The Subagent page becomes the Tasks page per the approved Variant-D design:

- tasks-model.ts feeds BOTH modes from one pre-order node list: graph
  canvas (TasksGraph: layered nodes over bezier edges, drag-pan,
  wheel-zoom-to-cursor, phase frames, bottom-right control cluster) and
  the classic tree (TasksTree: keyboard navigable) — the view toggle and
  fold toggle stay reachable in both modes.
- Workflow runs (workflows.list) hang under their origin agent; member
  agents re-parent under the run node, missing ones synthesize from run
  data; completed leaf agents fold into per-parent aggregate nodes
  (click or cluster toggle to expand/re-collapse).
- Agent Teams (experimental): roster enriches nodes; a header chip opens
  the shared task board popover with basic CAS operations (complete /
  reopen / delete / reassign / create / edit); no experimental layer →
  the whole block hides (structural degradation).
- Background jobs move into a bottom drawer that auto-collapses at 8+
  agents (manual toggle wins); job output opens as an anchored popover
  (event replay, never the model's cursor); two-click kill preserved.
- Live activity lines render as glyph + tool + args (tool-icons map onto
  host primitive icons).
- Node click jumps to the transcript; ⓘ opens the detail popover;
  popovers follow the selection-popup dismissal contract (Escape /
  outside click / anchor off-screen via IntersectionObserver).
- tasksViewMode pref (graph default) with a select row in settings;
  58 new copy keys across zh/en/ja + 18 third-language dictionaries;
  dead CSS from the old tree/dock pruned.
- Tests: 6 new page-interaction specs (auto-collapse, both-mode toggle,
  jump, fold, popover); jobs-view suite re-pointed at the popover;
  prefs/builtins/smoke lock tests updated for the new pref.
- docs/plans/2026-09-14-tasks-graph-workflow-teams-design.md: data-source
  facts, degradation matrix, tradeoffs, and the implementation-deviation
  record (diagnostics banner, mount-e2e desktop-shim pitfall, portal
  assertions).
- README feature list/table + external-plugin-guide tab registry row now
  describe the workflow-graph page, team task board, and jobs drawer.
- guideDescSubagent reworded in zh/en/ja (third-language retranslations
  follow separately).
…an cards

Review feedback from the live 3384 install, fixed at the root:

- COLORS: the new stylesheet used var(--dsw-alias-accent) in 17 places — a
  token the DSH theme does NOT define, so every declaration was dropped
  (older files only survived because they carry fallbacks); container edges
  used border-l1 (4% black, invisible). Accent now rides
  --dsw-alias-state-business-primary, container edges --dsw-alias-border-l4.
- CLICKS: the canvas called setPointerCapture on pointerdown, which
  retargets the derived click to the container and killed every node (and
  ⓘ) interaction; jsdom cannot see this, a real browser can. Capture is
  gone and panning only starts from the background.
- LAYOUT: fit gave up silently at zero container size and never retried,
  centered only horizontally, and capped at 100% while a five-child level
  scaled to 35%. Now: ResizeObserver + first non-zero fit, both axes
  centered, a 0.78 readability floor, and layoutTasksGraphForWidth picks
  the flattest sibling arrangement that still meets that floor.
- CARDS: title (one line) + mono meta + live line only; display title,
  team role, full model id and latest text moved into the ⓘ popover.
- PANEL: metrics follow the approved 360x660 mockup (132x46 cards, 112 row
  stride), explicit line-heights, user-select:none, horizontal control
  cluster with a labelled mode button, always-visible team board strip.
- Tokens for two new popover headings across all 20 dictionaries.

Tests: 3 new page-interaction specs (click after background pointerdown,
node gestures never pan, team board visible without a click) and a
rewritten 9-case layout suite (band wrap, row reservation, width solver,
reserved live row). Full suite 1400 passed / 133 files.
The host body line-height stretched the popover key/value grid and the
task/job rows; every micro-type block now declares its own line-height.
pnpm lint was never run before the first push (CI Lint + ci-windows Lint
both failed on it): the jobs drawer no longer polls (its output popover
does), and StateDot/css/TreeJob imports went unused when the cards and the
team chip were rewritten.
…icon glyphs, draggable output

Round-two review feedback, all three points:

- TASKS ON NODES: the model maps each shared task (ownerName -> member name
  -> member session id) onto its owner's node; graph cards and tree rows
  render a fourth line (icon + subject + status + '+N'), and the node detail
  popover lists every owned task.
- TEAM BOARD: member Pills double as the owner filter; each task row is a
  status dot + subject + owner + status Tag + ONE overflow Menu (complete /
  reopen / edit / reassign submenu / delete with a two-step arm). Creating
  and editing moved into a real Modal with labeled Inputs and owner Pills —
  the narrow strip is no longer where you type.
- HOST PRIMITIVES ONLY: the board and the jobs drawer now use Menu / Modal /
  Input / Button / Pill / Tag / Switch / StateDot; no native select or input
  remains (guarded by a spec).
- ICONS, NOT HAND-DRAWN GLYPHS: every text glyph (circle/diamond/triangle/
  check/chevrons/i/house) is replaced by a host icon — agent preset, user
  (teammate), branch (workflow run), checklist (fold + tasks), tree corner,
  fullscreen (fit), ellipsis (node details), copy/stop (jobs). The live line
  now shows the tool's own icon + name + args without the drawn box.
- JOBS UX: the output popover is DRAGGABLE (offset clamped to the viewport,
  double click re-anchors, controls do not start a drag), defaults to 380px,
  and gains copy-output, a follow-latest switch, kind/status Tags and a drag
  hint; the drawer highlights the open row.
- 19 new copy keys across zh/en/ja and the 18 third-language dictionaries.

Tests: 4 new specs (task rendered on its node, no native select/input +
menu-driven board, create-through-dialog posts the CAS-free create, job
popover drag moves the card). Full suite 1404 passed / 133 files.
…l entry

Round-three review feedback:

- NO PER-CARD DETAIL BUTTON: the ellipsis control is gone from graph cards
  and tree rows — the card IS the affordance, and clicking it opens the
  detail window (the transcript jump moved inside as the primary button).
  Fold aggregates keep click-to-expand.
- ONE TASK WINDOW: every task surface (board row, agent node task line, the
  node detail list, and creating a task) now opens the same non-fullscreen
  DRAGGABLE window (TaskWindow.tsx + its TaskPopover shell): markdown
  description by default, 编辑 switches the same card to multi-line editing,
  plus owner reassignment, complete/reopen and a two-step delete. The board
  lost its overflow menu and its Modal entirely; create uses the same window
  in create mode.
- REUSE: TaskPopover (4 call sites), MultilineField (the plugin's own
  multi-line input — the host primitive set has none), OwnerPicker,
  TaskCreateButton, and the shared glyph/TaskLine helpers across both modes.
- 3 new copy keys across zh/en/ja and the 18 third-language dictionaries.

Tests: the node-click spec now asserts the detail window (and that the jump
button inside it navigates), a new spec pins 'no nested control inside a
card', and two more cover the shared window: markdown-first view then
multi-line edit posting an 'edit' with the current revision, and immediate
owner reassignment. Full suite 1406 passed / 133 files.
…dix, lazy chunk)

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.
…om files)

package.json#files enumerated chunks by hand, so the new lib/client-tasks.js
never entered the tarball — a published install would have failed to open the
Tasks tab. The same list was also missing lib/client-locale.js since that
chunk landed. Replace the enumeration with the lib/client-*.js glob, derive the
test's chunk list from CHUNK_NAMES instead of a hand-mirrored copy, and add an
assertion that every chunk is covered by the publish list (verified against the
packed tarball and the live /sidebar/bundle route).
The maintainer's desktop already runs the 0.1.6-alpha line while the plugin
pinned 0.1.5-rc.2 (latest/next still point there). The peer range stays
^0.1.5-rc.1, so rc.1/rc.2 users are unaffected.

Upstream delta that actually touches this plugin (primitives .d.ts diff plus a
full scan of bare imports in its lib/**/*.js):

- IconSendOutline16 was removed (the only icon drop) -> IconSendOutline14.
- TerminalBlockLabels gained a REQUIRED noExitCode label -> new
  sideChatBlockNoExitCode copy across all 20 dictionaries.
- ConnectionIndicator dropped reconnectLabel -> prop removed.
- Menu/Modal changed only in docs/keyboard semantics; new exports
  (Checkbox/isDarwinDesktop/MarkdownDelegateProvider) are additive.
- dsh-tools no longer peers on dsh-code-runtime, and that package was never
  published at alpha.2 -> its devDependencies hoist is removed instead of
  mixing an rc.2 package into an alpha.2 tree.
- the primitives bundle still declares zero dependencies but now bare-imports
  'diff' (DiffBlock) and 'simple-icons' -> hoisted as diff@^9.0.0 (matching the
  host) and simple-icons@^15.0.0; missing them failed six jsdom suites at
  vite's import-analysis stage.

Verified: pnpm install + peers check clean; typecheck/lint 0; 136 files /
1503 tests; build green with the core/chunk bundle guards; mount e2e 7/7 on
0.1.6-alpha.2 (longtasks 2/8, and /sidebar/bundle/tasks.js really downloads).

Also recorded: origin/main is an ancestor of this branch (nothing to rebase in
git terms), and README's published-version wording is left to release time.
0.1.6-alpha.2 removed ConnectionIndicator's reconnectLabel, but the peer range
(^0.1.5-rc.1) still covers the rc line, where the prop is REQUIRED and renders
the retry button's hover/focus action text. Widening the component type once
keeps both lines correct: rc renders the label, alpha.2 ignores the extra key.
The shim carries a comment naming the condition for deleting it.
…dified main)

Pinning the lane to 0.1.6-alpha.2 makes plugin-mount fail deterministically:
the shell boots into the new-session empty state, no session scope reaches the
sidebar, and [data-dsh-better-sidebar] [title] never attaches
(mount.e2e.ts:216 plus perf.e2e.ts:182). Falsified against this branch with a
probe: origin/main + that one-line pin, run 35484961070, same red, zero plugin
changes involved.

So the dev tree keeps alpha.2 (the line the maintainer's desktop runs) while
the lane stays on the released pin; the evidence and the follow-up (teach the
lane to activate the seeded session on the alpha line) are recorded in
AGENTS.md §2/§3 and docs/plans/2026-09-17-dsh-0.1.6-alpha.2-adaptation.md.
…e to stock shadcn

Real-device report: the Tasks page "looks like it has no CSS at all, with
blurry borders". Two hypotheses were falsified before the real cause surfaced:
the sheet IS delivered (the chunk carries both the CSS and its injector, and
the bundle route serves it) and the cascade is NOT the problem (with the real
host stylesheet plus the real compiled sheet, a controlled sample keeps our
padding, radius, border, background and font; host CSS-module selectors only
match host nodes, and its global element rules are element-level specificity).

The cause is what we deliberately excluded: PREFLIGHT is also what normalizes
form controls. Measured on a plugin icon button that sets only an icon size,
the UA chrome survived as padding 1px 6px, border 2px outset, background
ButtonFace (#efefef) and a 13.33px system font - gray beveled buttons with a
soft double border, i.e. "no CSS".

- ui/theme.css: a .dsw-tasks-scoped preflight subset (zero margins/padding,
  border width+style, background, appearance none, inherit color/spacing,
  cursor rules) inside @layer base, written with :where() so it stays at
  (0,1,0) and utilities in the later layer still win; plus an explicit
  "@layer theme, base, components, utilities;" statement - layer order follows
  first appearance, and without it the utilities import registered utilities
  BEFORE base, so the reset beat rounded-md (measured: square buttons). The
  built chunk's order was re-checked: properties, theme, base, components,
  utilities.
- Stock shadcn restyle: static cards carry stock shadow-xs/shadow-sm again
  (floating layers keep shadow-lg), body/meta text is text-sm/text-xs instead
  of 11-13px, status uses stock Badge variants, controls use stock Button
  variants and sizes, row actions appear on hover/focus while staying keyboard
  reachable, node cards are 176x68 with shadow-xs.
- bandColsFor budgets columns at the readability floor (width / 0.78) instead
  of 1:1, so a 360px panel keeps two columns; FIT_MIN_SCALE moved into the
  layout module as GRAPH_FIT_MIN_SCALE so the fit and the column budget share
  one constant.
- Guards: tests/ui-foundation.spec.ts pins the scoped reset declarations and
  the layer-order statement; tests/ui-shadows.spec.ts now allows shadow-xs and
  shadow-sm on static panels and bans md and above; the theme guard's inert
  paint rule forced the reset to spell border-width/border-style instead of the
  "border: 0 solid" shorthand.

Verified: typecheck and lint clean; 136 files / 1505 tests; build with the
core/chunk bundle guards; mount e2e 7/7 (mount 444ms, longtasks 2 of 8); the
visual mirror page (real host stylesheet + real components) re-shot at 360 and
720 for graph, tree and the 77-node stress fixture.
The previous round kept a hand-trimmed, shadow-less, 11-13px variant of the
component set. This replaces it with the registry's own defaults and the
registry's own composition primitives, keeping only the adaptations this host
actually requires.

Registry refresh (npx shadcn@latest add --overwrite): the whole vendored set is
back to upstream sources, plus label, field, input-group, item and button-group.
Re-applied adaptations (documented in the design doc):
- cn from ./utils and explicit ./x.tsx specifiers (the registry writes virtual
  aliases this repo has no resolver for);
- host IconXxx instead of lucide (skin contract);
- no dark: variants (the DSH tokens already flip) and no palette literals;
- React.forwardRef on Button (the host is React 18, so radix Slot needs it);
- tw-animate-css animation utilities stripped: installing that dependency would
  inject generic keyframes into a page we do not own.
Restored to stock: the full Button variant/size set, shadow-xs on outline,
transition-all, badge ghost/link.

Recomposition on the newest primitives (per-file, verified here):
- board: full Card composition, Item rows, ToggleGroup filter, dropdown items
  grouped, Empty for the empty state;
- task window: Card composition, FieldGroup/Field with data-invalid validation,
  ToggleGroup owner (default variant: the outline variant's hover and pressed
  states measured identically, hiding the selection), destructive-armed delete,
  FieldError for conflicts;
- popovers: both portal cards use the same Card composition, rows are Item;
- drawer/header: Item rows with ItemActions, Separator bands, Spinner, Empty;
- graph/tree: stock node recipe, outline buttons, cn() for conditional classes.

Two no-preflight traps found by rendering, not by types:
- stock Empty ships border-dashed without a width, so the UA's 3px medium dashed
  frame wrapped the whole page: border-0 on that Empty;
- <Spinner size={12}/> stayed type-clean after the refresh (size is a valid span
  attribute) while the glyph silently rendered at size-4: className="size-3".

Verified: typecheck/lint clean; 136 files / 1515 tests; build with the core and
chunk bundle guards (core 836.3 KiB, chunk 734.7 KiB); mount e2e 7/7; and the
real-host-stylesheet mirror page re-shot for graph, tree, board, task window and
the 77-node stress fixture at 360 and 720.
Stock shadcn defaults are designed for a standalone app; next to this plugin's
other tabs (files, changes, side chat, settings) they read as foreign: 14px body
against 12px, 36px controls against 28px, #29 borders against the plugin's
10%/4% hairlines, solid primary badges against flat state tints, and elevation
where every other panel is flat.

Measured the plugin's own vocabulary from its CSS modules (12px body / 11px meta
/ 10px micro; 16-20px line heights; 28px controls; 6x8 and 6x10 paddings; 8/6/4
gaps; 6/8px radii; `border-l2` for 1px borders and `border-l1` for hairlines;
`--dsw-shadow-lv2/3` only on floating layers) and mapped the vendored set onto
it instead of editing every page:

- theme.css bridge: `--border`/`--input` -> `border-l2` (l4 was visibly darker
  than every other tab), new `--border-subtle` -> `border-l1`, the type scale
  remapped (text-xs 11px, text-sm 12px, text-base 13px, text-lg 14px), elevation
  mapped to the plugin's tokens (static shadows none, md/lg -> --dsw-shadow-lv2
  /lv3), and the state tints bridged.
- component defaults retuned to the same scale: button sizes 28/24/20px with
  text-xs, card padding 12px with gap-3 and no shadow, input 28px, badge 18px
  with 11px ink, empty at p-4, separators on the hairline token.
- badges: the default variant is the plugin's flat state tint
  (bg-state-business-tertiary + state ink, the same recipe sidebar.module.css
  uses for revealed rows) rather than a solid primary fill.

Guards updated with the reasoning inline: the border bridge expectations, a
`none` value being the plugin's way of saying "flat", and the skin-contract
check now derives local token names from the file instead of a hand-kept list.

Verified: typecheck/lint clean; 136 files / 1515 tests; build with the bundle
guards; mount e2e 7/7; mirror-page screenshots (real host stylesheet + real
components) re-shot at 360/720 for graph, tree, board and the 77-node fixture.
…ugin's own UI system

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.
Removes the stylesheet rules nothing references any more (popPrimary, teamBtn,
teamOwnerSelect, teamForm, teamInput, treeInfo, teamMember, teamTaskStatus,
nodeTitle variants), keeping the page at the plugin's own scale: typecheck and
lint clean, 133 files / 1410 tests, and the 70-assertion real-browser contract
still passes at 70/70 with the 20 reference screenshots regenerated.

Note: the accompanying per-node height refactor for the task-line clipping bug
was reverted — it removed the exported GRAPH_ROW_STRIDE the layout spec depends
on and broke four layout tests, so it needs a proper round of its own.
The native port is finished off with the readability work the earlier rounds
left open, plus the cleanup of everything the shadcn era or the older canvas
left behind.

- Geometry: node height is now composed from named parts (chrome, two title
  lines, meta row) and per-node extras, so a teammate card that carries a task
  line no longer gets clipped by the old fixed 68px budget; the layout spec
  follows the new model and GRAPH_ROW_STRIDE stays exported for it.
- Readability: row rhythm (28-32px), three ink levels for title/meta/owner,
  truncation with title hints, current-row and hover expressed as an accent bar
  plus a hover fill (no shadows), phase frames and edges back on the l1/l2
  hairlines, control cluster uniformly 28px, drawer durations on tabular
  figures, kill action revealed on hover/focus without shifting the row, no
  horizontal overflow at 360px.
- Task window: the markdown body is pulled onto the page scale (12px body,
  12/13px headings, tightened list indent and paragraph spacing) while the
  shared MarkdownText/markdownTextProps contract is untouched.
- Cleanup: dead stylesheet rules, unused TSX exports and helpers, stale
  comments that still described the retired Variant-D canvas, zero-reference
  i18n keys across the dictionaries, and the Tailwind/shadcn/Variant-D
  narrative in the design doc, AGENTS.md and the plugin guide (kept as one
  short "why it was rolled back" section).

Verified here: typecheck and lint clean; 133 files / 1413 tests; build with no
radix-ui or --tw- strings in lib/client.js (988.9 KiB); the 70-assertion
real-browser contract at 70/70 with the 20 reference screenshots regenerated;
mount e2e 7/7.
…onto v0.21.1)

Rebases the whole branch onto main's new 0.1.7-rc.1 baseline and lands the
adaptation the new host contract requires: the ui-primitives icon family rename
(Icon<Name><14|16> -> Icon<Name>Regular/Medium, with the 14px defaults pinned
back explicitly) and the StateDot redraw, the rewritten dsh-settings surface
(entry-id namespace, DSH schemastery with collected volatile returns,
configure auto:false), session format v4 (producer-owned source kind plus the
rewritten tool-result shape, parsers accepting both), the client snapshot move
to projectionsBySession.subagentCatalog with the jobs.list route behind it, the
^0.1.7-rc.1 peer floor with vendor and bare-import devDeps, the reduced chunk
set mirrored in all eight places, and the capability hand-backs (read-only
previews, terminal, browser).

Verified: typecheck and lint clean; 119 files / 1200 tests; build with no old
contract residue in the bundles; mount e2e 7/7; the tasks page still passes its
70-assertion real-browser contract.
@Menghuan1918
Menghuan1918 force-pushed the feat/tasks-graph-workflow-teams branch from ad382bf to c3a9730 Compare September 24, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant