revert(tasks-ui): drop the shadcn/Tailwind layer and return to the plugin'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.
This commit is contained in:
+15
-47
@@ -5,16 +5,11 @@
|
||||
* line, and the iconified live activity row ("tool icon + tool + args").
|
||||
*
|
||||
* Card content contract (the page's answer to "everything is ellipsized"):
|
||||
* line 1 state dot + agent icon + name (text-sm semibold, one line)
|
||||
* line 2 mode/model · activity (text-xs mono, one line)
|
||||
* line 1 state dot + agent icon + name (11px semibold, one line)
|
||||
* line 2 mode/model · activity (9px mono, one line)
|
||||
* line 3 live tool line (running nodes only)
|
||||
* line 4 owned shared task (team members only)
|
||||
* Everything else lives in the popovers.
|
||||
*
|
||||
* Visual language: Tailwind utilities over the shadcn tokens
|
||||
* (src/client/ui/theme.css) — mono meta is `font-mono tabular-nums`, ink stays
|
||||
* on the three token levels, and color is semantic only (running = primary,
|
||||
* done = success, blocked = warning; errors only exist on nodes).
|
||||
*/
|
||||
import type { ReactNode } from 'react'
|
||||
import {
|
||||
@@ -25,6 +20,7 @@ import type { LastActivity } from '../subagent-activity.ts'
|
||||
import type { TasksAgentNode, TasksNodeState, TasksNodeTask, TasksWorkflowNode } from './tasks-model.ts'
|
||||
import { toolGlyph } from './tool-icons.tsx'
|
||||
import { t, type CopyKey } from './locales.ts'
|
||||
import css from './tasks-graph.module.css'
|
||||
|
||||
/** Preview cap of one tool-call argument line. */
|
||||
const ARGS_PREVIEW = 48
|
||||
@@ -142,25 +138,10 @@ export function primaryTask(tasks: readonly TasksNodeTask[]): TasksNodeTask | un
|
||||
?? tasks[tasks.length - 1]
|
||||
}
|
||||
|
||||
/**
|
||||
* The status word's semantic ink: done = success, in progress = primary,
|
||||
* blocked = warning, anything else stays a plain meta token. No hue is used
|
||||
* as decoration — the card stays quiet when many nodes are on screen.
|
||||
*/
|
||||
function taskTone(task: TasksNodeTask): string {
|
||||
if (task.status === 'completed') return 'text-success'
|
||||
if (task.status === 'in_progress') return 'text-primary'
|
||||
return task.ready ? 'text-muted-foreground' : 'text-warning'
|
||||
}
|
||||
|
||||
/**
|
||||
* The node's shared-task line: an icon, the primary task's subject, its
|
||||
* status word, and a `+N` tail when the agent owns more. Renders nothing
|
||||
* without tasks, so non-team nodes keep the three-line shape.
|
||||
*
|
||||
* The whole line is one inline hover slab (`hover:bg-muted`) that opens the
|
||||
* task window; it stops the click from reaching the node behind it, which is
|
||||
* the node's own detail affordance, not this one.
|
||||
*/
|
||||
export function TaskLine(props: {
|
||||
tasks: readonly TasksNodeTask[] | undefined
|
||||
@@ -173,7 +154,7 @@ export function TaskLine(props: {
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="group -mx-1 mt-1 flex min-w-0 cursor-pointer items-center gap-1 rounded-sm px-1 text-xs text-muted-foreground transition-colors hover:bg-muted focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-1 focus-visible:outline-ring"
|
||||
className={css.taskLine}
|
||||
title={tasks.map(task => `${task.subject}(${t(taskStatusKey(task.status))})`).join('\n')}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
@@ -186,12 +167,10 @@ export function TaskLine(props: {
|
||||
props.onOpenTask(primary.id, event.currentTarget)
|
||||
}}
|
||||
>
|
||||
<span className="flex-none" aria-hidden="true"><IconChecklistOutline14 size={11} /></span>
|
||||
<span className="min-w-0 flex-1 truncate group-hover:text-foreground">{primary.subject}</span>
|
||||
<span className={`flex-none font-mono tabular-nums ${taskTone(primary)}`}>{t(taskStatusKey(primary.status))}</span>
|
||||
{tasks.length > 1 && (
|
||||
<span className="flex-none rounded-sm border border-border px-1 font-mono tabular-nums text-foreground-3">{`+${tasks.length - 1}`}</span>
|
||||
)}
|
||||
<span className={css.taskGlyph} aria-hidden="true"><IconChecklistOutline14 size={9} /></span>
|
||||
<span className={css.taskSubject}>{primary.subject}</span>
|
||||
<span className={css.taskState}>{t(taskStatusKey(primary.status))}</span>
|
||||
{tasks.length > 1 && <span className={css.taskMore}>{`+${tasks.length - 1}`}</span>}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -199,35 +178,24 @@ export function TaskLine(props: {
|
||||
/**
|
||||
* The live activity row of a RUNNING agent: the tool's own icon + tool name +
|
||||
* args (the approved format), plus the flattened last text line underneath.
|
||||
* A running node with neither reads as thinking. Running ink is the accent
|
||||
* (primary); the args/text tails drop to the secondary token.
|
||||
* A running node with neither reads as thinking.
|
||||
*/
|
||||
export function LiveLine(props: { live: LastActivity | undefined }): ReactNode {
|
||||
const { live } = props
|
||||
if (live?.text === undefined && live?.tool === undefined) {
|
||||
return (
|
||||
<span className="mt-0.5 truncate font-mono text-xs tabular-nums text-muted-foreground">
|
||||
{t('subagentThinking')}
|
||||
</span>
|
||||
)
|
||||
return <span className={css.nodeMeta}>{t('subagentThinking')}</span>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{live.tool !== undefined && (
|
||||
<span className="mt-0.5 flex min-w-0 items-center gap-1 truncate font-mono text-xs tabular-nums text-primary">
|
||||
<span className="flex size-3 flex-none items-center justify-center rounded-sm border border-current" aria-hidden="true">
|
||||
{toolGlyph(live.tool.name)(9)}
|
||||
</span>
|
||||
<span className="flex-none">{live.tool.name}</span>
|
||||
{live.tool.args !== '' && (
|
||||
<span className="min-w-0 truncate text-muted-foreground">{preview(live.tool.args)}</span>
|
||||
)}
|
||||
<span className={css.live}>
|
||||
<span className={css.liveGlyph} aria-hidden="true">{toolGlyph(live.tool.name)(9)}</span>
|
||||
<span className={css.liveTool}>{live.tool.name}</span>
|
||||
{live.tool.args !== '' && <span className={css.liveArgs}>{preview(live.tool.args)}</span>}
|
||||
</span>
|
||||
)}
|
||||
{live.text !== undefined && (
|
||||
<span className="mt-0.5 truncate font-mono text-xs tabular-nums text-muted-foreground">
|
||||
{preview(flatten(live.text), TEXT_PREVIEW)}
|
||||
</span>
|
||||
<span className={css.liveText}>{preview(flatten(live.text), TEXT_PREVIEW)}</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user