refactor(tasks-ui): align the page with the latest shadcn/ui defaults

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.
This commit is contained in:
Menghuan1918
2026-09-27 22:18:19 +08:00
parent d1a9d520ae
commit 5deb5285da
30 changed files with 2107 additions and 1097 deletions
+5 -2
View File
@@ -3,7 +3,7 @@
*
* Static panels (cards, rows, the drawer) may carry the stock LIGHT shadows
* only — `shadow-xs` (button-like compact surfaces such as graph node cards)
* and `shadow-sm` (cards / panels). Anything heavier (`shadow-md|lg|xl|2xl`,
* and `shadow-sm` (cards / panels); `shadow-none` (a reset) is fine. Anything heavier (`shadow-md|lg|xl|2xl`,
* bare `shadow`, `shadow-inner`, arbitrary `shadow-[…]`) on a static panel is
* the regression: elevation beyond the stock pair reads as a floating layer
* that nothing anchors.
@@ -54,7 +54,10 @@ const FLOATING_FILES = [
const SHADOW_UTILITY = /(?<![-\w])shadow(?:-(?:2xs|xs|sm|md|lg|xl|2xl|inner|none)|\[|\()?/g
/** The stock pair a static panel may paint. */
const STATIC_ALLOWED = new Set(['shadow-xs', 'shadow-sm'])
// `shadow-none` is not an elevation at all (it clears the ring plumbing), so it
// is allowed anywhere a light shadow is; upstream's input-group uses it to keep
// nested controls flat inside the group's own surface.
const STATIC_ALLOWED = new Set(['shadow-xs', 'shadow-sm', 'shadow-none'])
/** Every non-allowed shadow utility in the source, for a readable failure. */
function offendingShadows(source: string): string[] {