Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,216 @@
# Subframe-Migration: Komponenten-Mapping
Vertrag für die Migration der shadcn/Radix-UI (`apps/web/app/components/ui/*`) auf das
Subframe-Designsystem „Basedesign" (`apps/web/app/ui/*`, project `f023064a5ca0`).
## Grundsätze
- **Theme:** Subframe ist die alleinige Quelle (`apps/web/app/ui/theme.css`, eingebunden in
`globals.css`). Der Brand ist grayscale und deckt sich faktisch mit dem bisherigen
zinc-Primary. Die shadcn-Tokens (`--primary`, `--background` …) bleiben aktiv, bis ihre
Consumer migriert sind, und werden am Ende entfernt (samt JS-`@config` → beseitigt die
22 transitorischen CSS-Optimierungs-Warnungen).
- **Mechanismus:** Strangler-Shim. Pro Primitive wird die Implementierung in
`components/ui/<x>.tsx` durch einen Wrapper über die Subframe-Komponente ersetzt; die
bestehende API bleibt erhalten, Call-Sites ändern sich nicht. Schwer abbildbare Spezialfälle
bleiben vorerst auf dem Legacy-Pfad und werden später auf native Subframe-Komponenten
(LinkButton, IconButton …) umgestellt.
- **Verifikation pro Schritt:** `pnpm --filter @toolctrl/web typecheck` + `build` grün,
Komponenten-Tests grün, danach `pnpm verify:a11y-coverage`.
- **Generierte Dateien** in `app/ui/` gehören Subframe. Lokale Fixes nur mit
`// @subframe/sync-disable` (sonst überschreibt der nächste `pnpm subframe:sync`).
## Button ✅ migriert (Shim)
`components/ui/button.tsx` rendert für den Normalfall die Subframe-`Button`.
Prop-Mapping:
- `variant`: default→`primary`, destructive→`destructive`, outline→`tertiary`,
secondary→`secondary`, ghost→`ghost`, link→`ghost`
- `size`: default→`medium`, sm→`small`, xs→`small`, lg→`large`, icon→Legacy
- `isLoading`→`loading`; `dirty`→eigene Klasse (bleibt); `type`→Default `submit` erzwungen
(Subframe würde sonst `button` erzwingen und Formular-Buttons brechen)
Legacy-Pfad (noch shadcn, später nativ):
- `asChild` (46 Stellen) → Ziel: Subframe `LinkButton`
- `size="icon"` (16 Stellen) → Ziel: Subframe `IconButton`
- `buttonVariants`-Export (Consumer: alert-dialog, calendar, pagination, json/ShowModalButton)
## Formularfelder (`components/ui/input/*`) — der eigentliche Hebel
Die bare `input.tsx` wird kaum genutzt (9×); die Frequenz liegt in den Feld-Komponenten.
Subframes `TextInput`/`TextArea` sind `<label>`-Container (Label + Box + `*.Input`) und passen
hier — nicht auf die bare `input.tsx`. Beide haben eine `max-w`-Begrenzung, die für volle
Feldbreite mit `className="w-full max-w-none"` überschrieben werden muss.
- **InputText (129×) ✅ migriert** — volle Subframe `TextInput`/`TextArea` inkl. Label (rich
Label-Content als ReactNode), `error`, controlled+uncontrolled, type-Mapping. Editor-Branches
(monaco/wysiwyg) bleiben unverändert (in der Praxis ungenutzt). Label-Assoziation jetzt
**implizit** (Input im `<label>`) statt `for`/`id` → a11y-Test entsprechend angepasst.
Hinweis: `type="password"`/`"search"` zeigen jetzt Subframes eingebautes Lock-/Such-Icon.
- **Welle 2 ✅ migriert** (Shim → Subframe, typecheck/build/a11y grün): InputNumber→NumberInput,
InputSearch/InputSearchWithURL→TextInput(type=search), InputSearchButton→Button,
InputSelect/InputSelector→Select, InputCheckbox/Inline/WithDescription→Checkbox,
InputCheckboxCards→CheckboxCard, InputRadioGroup→RadioGroup, InputRadioGroupCards→RadioCardGroup,
InputChoicebox→RadioCardGroup, Toggle→Switch. InputResponsiveSelector erbt via InputSelector.
app/ui-Patch: NumberInput (hardcodiertes `value=""` entfernt, sync-disable).
Trade-offs: InputNumber `icon`-Prop jetzt visuell No-op; InputCheckboxWithDescription Layout-Shift.
## Welle 3 (Buttons-Folder / Banners / Atome)
- **Migriert ✅** (typecheck/build/a11y grün): `buttons/ButtonShare` + `buttons/ButtonFlyout` →
via Button-Shim (Rest des buttons/-Ordners lief schon über den Shim); `banners/*` (Error/Warning/
Success/Info) → BannerAlert (type-Mapping; `onDismiss` als Overlay erhalten, da BannerAlerts
dismiss-Button ein Mock ohne onClick ist); `progress.tsx` → Progress; `skeleton.tsx` → SkeletonText.
### ⚠️ Subframe-Komponenten, die NICHT passen (Mock oder Struktur-Mismatch) → bleiben shadcn
Rein präsentational (kein onChange/State/Navigation) oder strukturell inkompatibel — bewusst NICHT migriert:
- **Combobox / MultiSelect** — `<input value="">` hartkodiert, kein onChange → InputCombobox bleibt shadcn.
- **DatePicker** — hartkodierte Tage/Monate, Pfeile ohne onClick → InputDate/InputDatePicker bleiben shadcn (funktionaler `Calendar` wrappt react-day-picker und bleibt).
- **SearchInput** — value hartkodiert; echte Suchfelder nutzen `TextInput type=search`.
- **Tooltip** — statischer `<div>`-Mock (kein Radix-Trigger/Content/Hover) → `tooltip.tsx` bleibt shadcn.
- **Alert** — Subframe ist prop-basiert (title/description), shadcn ist Compound (Alert/AlertTitle/AlertDescription) → bleibt shadcn.
- **Avatar** — Subframe hat keine AvatarImage/AvatarFallback-Subkomponenten + keine Fallback-bei-Ladefehler-Logik → bleibt shadcn.
- **Badge** (+ badges/*) — BadgeFeedback erzwingt einen führenden Dot, ist `<div>` statt `<span>`, kein `asChild`, keine freien Farben (Badge nutzt 8+ freie Farben) → bleibt shadcn.
Damit diese migrierbar werden, müsste das Subframe-Design die Interaktionen/Struktur liefern (oder
wir behalten shadcn). Bei jedem Sync prüfen, ob upstream funktionale/passende Varianten kommen.
## Welle 4 (funktionale Compounds) — Decke erreicht
Nur **`slider.tsx` → Subframe Slider** migriert (funktional, gleicher Radix; im Repo aktuell ungenutzt;
ref nicht weitergereicht, da span vs. div). Alle anderen Compounds **gestoppt** — die Subframe-
Komponenten sind zwar teils Radix-funktional, aber **kompositorisch unvollständig**:
- **Dialog** — Subframe exportiert nur Root+Content (kein Trigger/Close/Portal/Overlay), Root hardcodiert
Portal+Overlay+Title → würde an jeder Modal-Stelle doppeltes Overlay erzeugen. Bleibt shadcn.
(Korrektur: `components/ui/dialog` wird direkt nur in 3 Dateien importiert; die Modal-Frequenz läuft
transitiv über `modals/DialogSticky`/`CommandDialog`/`ModalBlank`, ~57 Dateien.)
- **DropdownMenu** — Subframe ist nur Item-/Surface-Styling; das funktionale Radix ist DASSELBE Paket
wie bei shadcn (kein Gewinn, hohes Risiko). Bleibt shadcn.
- **Drawer / Sheet** — Subframe Drawer ist right-only, kein Trigger/Close, hardcodiertes Content-Styling;
`sheet` braucht `side`-Prop; `drawer` ist ein vaul-Drag-Drawer. Bleiben shadcn.
- **Accordion** — Subframe basiert auf Collapsible (kein Multi-Item `type`/`value`). Bleibt shadcn.
**Fazit:** Die sauber migrierbare Fläche ist mit dem aktuellen „Basedesign"-Stand weitgehend
ausgeschöpft. Compounds brauchen in Subframe **komponierbare Teile** (Trigger/Close/Portal/Content +
Sub-Items), Mocks brauchen **Funktionalität**. Weiterer Fortschritt = Subframe-Design-Arbeit, nicht Code.
## Welle 5 (Rest-Sweep „alles Migrierbare")
- **Migriert ✅** (typecheck/build/a11y grün): `switch`→Switch, `checkbox`→Checkbox, `radio-group`→RadioGroup
(alle mit controlled/uncontrolled-Shim + null→undefined-Coercion), `calendar`→Subframe Calendar
(= dasselbe react-day-picker, kein Feature-Verlust), `button-group`/`split-button`→ButtonGroup,
`loaders/Loading`+`FloatingLoader`→Loader, `steps/ProgressBar`→StepperHorizontal.
- **Gestoppt** (Struktur/Compound/Semantik-Mismatch, bleiben shadcn): `breadcrumb`(+breadcrumbs/) — Subframe-Items sind keine Links;
`empty`(+emptyState/) — Subframe-Button ist Mock ohne onClick; `stepper` — Subframe ist rein präsentational (keine
Step-State/Trigger); `toggle`/`toggle-group` — Subframe ist single-only + bricht `toggleVariants`-Export;
`links/` — URL-Filter-Toggle, kein Link-Button; `errors/ErrorPage`+`ServerError` — Full-Page-Layout vs. Inline-Karte,
Retry-Button ist Mock; `headers/HeaderFlyoutItem` — Popover-Flyout vs. Top-Bar; `stats/*` — StatsCard erzwingt
Trend-Icon + Pointer + bricht `<dl>`/sr-only-a11y; `select` — Compound (10 Exporte, kompositionale Inversion);
`table` — Subframe Table nicht komponierbar (keine Header/Body/Footer/Caption-Teile).
**Endstand der Code-Migration:** Was mit dem aktuellen Subframe-Projekt sauber ging, ist migriert. Alles Weitere
ist durch die Vollständigkeit von „Basedesign" blockiert (siehe Mock-/Compound-Listen oben).
## Direkte 1:1-Ziele (gleiche Reihenfolge wie Migrationswellen)
| shadcn | Subframe | Hinweis |
|---|---|---|
| input | TextInput | placeholder=string |
| textarea | TextArea | |
| select | Select | gepatcht (sync-disable) |
| checkbox | Checkbox | |
| radio-group | RadioGroup | |
| switch | Switch | |
| badge | BadgeFeedback / Tag_Label | kein generisches Badge |
| avatar | Avatar | + AvatarGroup |
| tooltip | Tooltip | |
| tabs | Tabs | |
| accordion | Accordion | |
| breadcrumb | Breadcrumbs | |
| progress | Progress | + ProgressCircle |
| slider | Slider | |
| skeleton | SkeletonText / SkeletonCircle | |
| table | Table | |
| dialog | Dialog | + ModalBlank/ConfirmDialog/ResultDialog |
| drawer / sheet | Drawer | |
| dropdown-menu | DropdownMenu | |
| context-menu | ContextMenu | |
| sidebar | SidebarWithSections | |
| stepper | StepperHorizontal/Vertical | |
| toggle-group / toggle | ToggleGroup | |
| input-otp | InputOtp | |
| command | Combobox / SearchInput | keine Command-Palette; gepatcht |
| multi-select | MultiSelect | + MultiSelectTags |
| calendar | Calendar | + DatePicker |
| chart | AreaChart/BarChart/LineChart/PieChart | konkrete Typen statt Wrapper |
| card | CardIcon/CardImage/StatsCard | kein generisches Card |
| empty | EmptyState | |
| alert | Alert | |
| toast/toaster/sonner | Toast | |
## Lücken — bleiben shadcn (kein Subframe-Pendant)
`aspect-ratio`, `carousel`, `collapsible`, `hover-card`, `input-group`, `label`,
`pagination`, `popover`, `resizable`, `scroll-area`, `separator`.
## Bekannte Generierungs-Bugs (gepatcht, sync-disabled)
- `Select`: undefined `checked`/`highlighted` → entfernt (Styling via data-state)
- `Combobox`, `SearchInput`: `placeholder` ReactNode → `string`
## Token-Zuordnung: shadcn → Subframe (für „Logik=Radix, Look=Subframe")
Strategie für alle noch nicht migrierten Komponenten: die bestehende Radix/shadcn-Komponente
BEHALTEN (Logik bleibt), NUR die Farb-/Stil-Klassen auf Subframe-Tokens umstellen. Keine
Struktur-/Import-/Export-Änderung — nur className. Sync-sicher (alles in components/ui; die
Subframe-Tokens kommen global aus app/ui/theme.css). Subframe-`app/ui`-Mock-Komponenten werden
für diese NICHT verwendet.
| shadcn-Token | Subframe-Token |
|---|---|
| `bg-background` | `bg-surface-neutral-primary` |
| `bg-card`, `bg-popover` | `bg-surface-neutral-primary` (Overlays/Modal: `bg-surface-neutral-modal`) |
| `text-foreground`, `text-card-foreground` | `text-text-neutral-primary` |
| `text-muted-foreground` | `text-text-neutral-tertiary` |
| `placeholder:text-muted-foreground` | `placeholder:text-text-neutral-subtle` |
| `bg-primary` (hover `/90`) | `bg-brand-brand-surface-solid-default` (hover `-hover`) |
| `text-primary-foreground` | `text-text-neutral-on-solid` |
| `bg-secondary`, `bg-muted`, `bg-accent` | `bg-surface-neutral-secondary` |
| `text-secondary-foreground`, `text-accent-foreground` | `text-text-neutral-primary` |
| `hover:bg-accent` | `hover:bg-surface-neutral-secondary` (aktiv: `-tertiary`) |
| `bg-destructive` (hover `/90`) | `bg-surface-feedback-solid-negative-default` (hover `-hover`) |
| `text-destructive` | `text-text-feedback-negative` |
| `border`, `border-border` | `border-border-default` |
| `border-input` | `border-border-strong` |
| Fokus: `ring-ring`, `ring-[3px]`, `focus-visible:ring-*` | `shadow-focus` (+ `border-brand-brand-border-solid-default`) |
| disabled: `opacity-50`/muted | `bg-surface-neutral-disabled`, `text-text-neutral-disabled`, `border-border-disabled` |
Regel: nur diese Art Klassen ersetzen. Layout-/Spacing-/Radius-Utilities (flex, gap, px, rounded …)
unverändert lassen. Im Zweifel an Button.tsx / TextInput.tsx orientieren (echte Subframe-Tokens).
## Welle 6+7 — „Logik=Radix, Look=Subframe" (Restyle aller restlichen Komponenten)
Strategie-Wechsel (vom Designer bestätigt: es geht um Logik, nicht um neue Subframe-Komponenten):
Die zuvor als „blockiert/Mock" eingestuften Komponenten werden NICHT durch Subframe-Mock-
Komponenten ersetzt — stattdessen behalten sie ihre funktionierende Radix/shadcn-Logik und
bekommen nur den Subframe-LOOK per Token-Swap (className). Sync-sicher (nur components/ui,
Tokens global aus theme.css). Carbon nicht nötig — Radix ist die headless Logik-Basis.
Restyled (typecheck/build/a11y/axe grün): dialog, alert-dialog, modals/*, dropdown-menu,
context-menu, dropdowns/*, tabs (+tabs/*), accordion, collapsible, tooltip, popover, hover-card,
tooltips/*, table (+tables/*), select, drawer, sheet, sidebar, slideOvers/*, card, alert, avatar,
badge (+badges/*, freie Farben bewusst erhalten), breadcrumb (+breadcrumbs/*), empty (+emptyState/*),
stepper, stats/*, separator, scroll-area, resizable, input-group, input, textarea, choicebox,
dividers/*, command (+commandPalettes/*), toast, notifications/*, errors/*, headers/*, chart
(nur Tooltip/Legende), carousel (erbt via Button).
Bewusst NICHT umgestellt: freie Farben (Badge-Paletten, Trend-grün/rot, Status-Icons),
Overlay-Backdrops (`bg-black/50`), Sidebar-eigener `sidebar-*`-Token-Namespace, recharts-Datenfarben,
sowie Komponenten ohne Farb-Tokens (label, pagination, aspect-ratio — Look kommt via Button/Tokens).
Langer Schwanz (noch offen, meist Spezial/Utility, geringe Sichtbarkeit): lists/, sort/, selectors/,
misc/, json/, code/, docs/, layouts/, media/, images/, sections/, wizards/, poll/, pdf/ + Spezial-
Inputs (InputColorSelector/MultiText/Image/Media/RichTextEditor). Icons (111) bleiben außen vor.
@@ -0,0 +1,60 @@
# Inline-SVG → Subframe Feather migration (cheat sheet)
The icon-**component** migration is complete (all `components/ui/icons/**`,
`components/layouts/icons/**`, `modules/workflowEngine/components/icons/**`, plus
the `lucide-react` / `@radix-ui/react-icons` / `@heroicons` vendor libraries via
`components/ui/icons/vendor.tsx`).
Remaining: ~178 files contain raw inline `<svg>…</svg>` glyphs (270 occurrences).
Many are standard heroicons/lucide glyphs that should be replaced with the Subframe
Feather icons exported from `~/components/ui/icons/vendor`. Replace the whole
`<svg>…</svg>` element with the mapped icon component, **preserving the original
`className` verbatim** (the vendor wrapper bridges sizing via `[&>svg]:size-full`).
## Known path `d` → vendor icon (deterministic)
| path `d` (prefix) | glyph | replace with |
| --- | --- | --- |
| `M12 6v6m0 0v6m0-6h6m-6 0H6` | plus (outline) | `PlusIcon` |
| `M12 4v16m8-8H4` | plus (outline) | `PlusIcon` |
| `M10.75 4.75a.75.75 0 00-1.5 0v4.5…` | plus (small) | `PlusIcon` |
| `M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293…` | chevron-down (solid) | `ChevronDownIcon` |
| `M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293…` | chevron-up (solid) | `ChevronUpIcon` |
| `M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707…` | chevron-right (solid) | `ChevronRightIcon` |
| `M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293…` | chevron-left (solid) | `ChevronLeftIcon` |
| `M15.75 19.5L8.25 12l7.5-7.5` | chevron-left (outline) | `ChevronLeftIcon` |
| `M9 5l7 7-7 7` | chevron-right (outline) | `ChevronRightIcon` |
| `M6 18L18 6M6 6l12 12` | x-mark (outline) | `XIcon` |
| `M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0…` | x-circle | `XCircleIcon` |
| `M16.707 5.293a1 1 0 010 1.414l-8 8…` | check (solid) | `CheckIcon` |
| `M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z` | check-circle | `CheckCircleIcon` |
| `M6 12L3.269 3.126A59.768 59.768 0 0121.485 12…` | paper-airplane / send | `SendIcon` |
| `M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z` | minus-circle | `MinusIcon` |
| `M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7…` | calendar | `CalendarIcon` |
| `M8 4a4 4 0 100 8 4 4 0 000-8z…` / `M21 21l-6-6m2-5a7 7 0 11-14 0…` | search | `SearchIcon` |
| `M2.458 12C3.732 7.943 7.523 5 12 5…` (+ `M15 12a3 3 0 11-6 0…`) | eye | `EyeIcon` |
| home / house glyph (`M2.25 12l8.954-8.955…` or `M3 12l2-2m0 0l7-7 7 7…`) | home | `HomeIcon` |
| star (`M11.48 3.499a.562…` or `M9.049 2.927c.3-.921…`) | star | `StarIcon` |
| envelope (`M2.25 6.75c0-1.243…` / `M3 8l7.89 5.26…` / `M4 4h16v16H4z`-mail) | mail | `MailIcon` |
| at-symbol (`M16.5 12a4.5 4.5 0 11-9 0…`) | at-sign | `AtSignIcon` |
| phone (`M2.25 6.75c0 8.284…` / `M3 5a2 2 0…`) | phone | `PhoneIcon` |
| exclamation-triangle (`M12 9v3.75m-9.303 3.376…` / `M10.29 3.86…`) | warning | `AlertTriangleIcon` |
| arrow-up (solid `M3.293 9.707a1 1 0 010-1.414l6-6…` / outline `M4.5 15.75l7.5-7.5 7.5 7.5`) | arrow-up | `ArrowUpIcon` |
| globe (`M12 21a9.004 9.004 0 008.716-6.747…` / `M21 12a9 9 0 11-18 0…` with meridians) | globe/language | `GlobeIcon` or `LanguagesIcon` |
| question-mark-circle (`M9.879 7.519c1.171-1.025…`) / info (`M11.25 11.25l.041-.02…`) | help/info | `HelpCircleIcon` / `InfoIcon` |
| trending-up (`M2.25 18L9 11.25l4.306 4.307…`) / trending-down | trend | `TrendingUpIcon` / `TrendingDownIcon` |
| pilcrow / paragraph (`M13 4v16M17 4v16M19 4H9.5a4.5…`) | pilcrow | `PilcrowIcon` |
## Rules for the migration
1. **Preserve `className` exactly** (copy the original `className=…` onto the
replacement icon). Sizing (`h-4 w-4`, `size-4`) and color (`text-*`) keep working.
2. Only replace SVGs that are a **standard single-glyph UI icon** identifiable from
the table above or unambiguous context. When in doubt, leave it.
3. **Leave bespoke graphics**: multi-path illustrations, brand logos, the
`viewBox="0 0 172 172"` icon8-style art, empty-state art, charts, the PDF viewer,
`dangerouslySetInnerHTML` icons. These are not icon-set glyphs.
4. Add `import { … } from "~/components/ui/icons/vendor"` (merge into an existing
vendor import if present).
5. Never insert JSX comments inside `{cond && ( … )}` expressions.
6. After each batch: `npm run typecheck` and `npm run build` must stay green.