Files
crewli/dev-docs/superpowers/specs/2026-05-15-crewli-starter-gui-redesign-design.md
bert.hausmans 890bcc88cb docs: add GUI redesign design spec (crewli-starter as design source)
Brainstorming outcome: pivot the PrimeVue redesign to use crewli-starter
as the design source of truth, parallel /v2/ routes, PrimeVue-first
fidelity, page-by-page cutover. Supersedes F4a-F4d of
RFC-WS-FRONTEND-PRIMEVUE.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 22:54:32 +02:00

339 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design — Crewli SPA GUI Redesign on the crewli-starter Design System
| Field | Value |
|---|---|
| **Status** | Design approved (brainstorming) — pending spec review |
| **Date** | 2026-05-15 |
| **Author** | Brainstorming session (Bert + Claude Code) |
| **Supersedes** | The F4aF4d component-migration sub-packages of `dev-docs/RFC-WS-FRONTEND-PRIMEVUE.md` |
| **Next artifact** | Implementation plan (writing-plans), then a new project RFC in `dev-docs/` |
| **Source design system** | `crewli-starter/` (sibling working directory) |
---
## 1. Context & problem
The Crewli SPA (`apps/app/`) is mid-migration from Vuetify+Vuexy to
PrimeVue 4.5 (Aura) + Tailwind v4. F3 (PrimeVue foundation, parallel-mode)
and F3.5 (a mockup-parity `AppShell`) have landed. Storybook 10 is wired
(PrimeVue + Tailwind) but contains only two smoke stories. The original
F4 plan was "translate existing Vuetify pages 1:1 to PrimeVue, preserve
UX."
A complete working prototype design system now exists in `crewli-starter/`:
~40 components, ~20 pages, a full app shell (sidebar / topbar / right
drawer / workspace switcher), and several rich modules (Timetable, Cue
editor, Section Builder). It already uses the **same stack**: PrimeVue 4.5,
Aura preset via `@primeuix/themes`, the Crewli teal token set, Tailwind v4,
Iconify-Tabler. It is pure JavaScript (no TypeScript).
**Decision: pivot the redesign to use `crewli-starter` as the design
source of truth**, building a new GUI in parallel with the existing one
and migrating page-by-page, rather than translating legacy Vuetify pages.
## 2. Decisions taken (this session)
1. **Cohabitation:** parallel routes under `/v2/*`. Old pages stay on
existing routes. Cutover is route-by-route.
2. **Fidelity rule:** PrimeVue-first. Default to a stock PrimeVue
component styled with Tailwind + `pt` API + Aura tokens. Port custom
CSS *only* when no PrimeVue primitive fits or the visual is genuinely
bespoke. Generic elements (KPI cards, error/empty states, status
badges, etc.) are **rebuilt on PrimeVue and accept the PrimeVue look**
— restyle freely. Pixel-perfect 1:1 is reserved for the genuinely
bespoke set only (see §7).
3. **Plan relationship:** a new project RFC supersedes F4aF4d. The F4
*architectural* decisions (PrimeVue + Tailwind + Aura + FormField +
DataTable conventions) all still stand; only the page-migration
strategy and design source change.
4. **Shell strategy:** a new `AppShellV2.vue` (and `OrganizerLayoutV2.vue`)
ports the crewli-starter shell 1:1. The existing `AppShell.vue` /
`OrganizerLayout.vue` stay untouched until `/v2/` supersedes them.
5. **Folder convention:** `pages-v2/` + `components-v2/` mirror the
existing structure. No per-file `V2` suffix inside those folders
(the folder name carries the distinction); the suffix is only on the
two shell/layout files that sit beside their v1 siblings.
6. **Sequencing approach:** Approach A — foundation-first vertical slice.
Build the minimum end-to-end first, then iterate page-by-page.
## 3. Folder structure & routing
```
apps/app/src/
├── pages-v2/ # NEW parallel page tree → /v2/*
├── components-v2/
│ ├── layout/ # AppSidebar, AppTopbar, SidebarNav, WorkspaceSwitcher, RightDrawer
│ ├── shared/ # PageHead, StatCard, StatusTag, StateBlock, EnergyDots, EnergyPicker, TagsInput, DraggableBlock
│ ├── templates/ # ListTemplate, FormTemplate, DetailTemplate, DashboardTemplate
│ ├── filters/ # SmartFilterBar + chip + popover + 5 editors + SmartListsRow
│ ├── timetable/ # (deferred — migrate with feature)
│ └── music/ # (deferred — migrate with feature)
│ # NOTE: no components-v2/forms/ — v2 reuses the existing
│ # apps/app/src/components/forms/FormField.vue. This folder is
│ # created ONLY if FormField provably forks (see §5).
├── layouts/
│ ├── OrganizerLayout.vue # EXISTING — untouched
│ ├── OrganizerLayoutV2.vue # NEW — wraps AppShellV2
│ └── components/
│ ├── AppShell.vue # EXISTING — untouched
│ └── AppShellV2.vue # NEW — ports crewli-starter shell 1:1
├── stories/ # EXPANDED — see §6
├── stores/ # + useWorkspaceStore
├── composables/ # + useRightDrawer
└── types/v2/ # shared v2 types
```
**Routing:** `unplugin-vue-router` generates routes from `pages-v2/`;
they are pinned under `/v2/*` (route prefix/meta). `vite-plugin-vue-meta-layouts`
selects `OrganizerLayoutV2` for the `pages-v2/*` tree. No conditional
logic inside layout files — each page tree picks its own layout. The old
layout stays inert (zero regression risk on un-migrated pages).
**Cutover convention (per page):** when a v2 page is approved to replace
its v1 counterpart, move `pages-v2/X.vue``pages/X.vue` (overwrite),
rewrite internal links `/v2/X``/X`, delete now-unused v1 components,
commit. Router auto-updates.
## 4. AppShellV2 composition
```
layouts/components/AppShellV2.vue # Tailwind grid: sidebar | (topbar + content) | rightDrawer
# (V2 suffix: sits beside v1 AppShell.vue)
└── composes components-v2/layout/:
├── AppSidebar.vue # permanent rail (≥lg); PrimeVue Drawer overlay (<lg)
│ ├── SidebarHeader.vue # logo + collapse toggle
│ ├── SidebarNav.vue # grouped nav + active highlight
│ └── WorkspaceSwitcher.vue # bottom switcher (custom visual + PrimeVue Popover)
├── AppTopbar.vue # Breadcrumb (PrimeVue) + actions (Button/Avatar/Menu/OverlayBadge)
└── RightDrawer.vue # PrimeVue Drawer position=right + slot scaffold
```
(No `V2` suffix on the `components-v2/layout/` files — the folder carries
the distinction per decision 5. Only `AppShellV2.vue` and
`OrganizerLayoutV2.vue` carry the suffix, since they sit beside their v1
siblings in `layouts/`.)
| Piece | Implementation | Rationale |
|---|---|---|
| Outer container | Tailwind grid (custom) | Matches crewli-starter `.app`; no PrimeVue equivalent |
| Sidebar desktop | `<aside>` + Tailwind (custom) | Permanent rail; no PrimeVue primitive |
| Sidebar mobile | PrimeVue `Drawer` | Correct off-canvas primitive |
| Sidebar nav rows | `<button>` + Tailwind + Icon | crewli-starter spec too bespoke for `PanelMenu` |
| Breadcrumb | PrimeVue `Breadcrumb` | Standard fits |
| Topbar actions | PrimeVue `Button` (text) + `OverlayBadge` | Standard |
| User menu | PrimeVue `Avatar` + `Menu` | Standard |
| RightDrawer | PrimeVue `Drawer` + custom slot scaffold | Plumbing free, content bespoke |
| WorkspaceSwitcher | custom visual + PrimeVue `Popover` | See §7.4 |
**State contract** (replaces crewli-starter `provide`/`inject`):
- `useWorkspaceStore` (Pinia): workspace, sidebar collapsed/expanded,
theme, density.
- `useRightDrawer()` composable: `open(component, props)`, `close()`.
- `vue-router`: replaces `provide('navigate', …)`.
- `<html data-theme>` / `<html data-density>` mechanism retained
(composes with Aura `darkModeSelector`); v2 bypasses Vuexy `useSkins.ts`.
F3.5's `AppShell` features (notification bell, help, breadcrumb,
org-switcher card) are not lost — the underlying state wires up
identically; only the visual treatment moves to the crewli-starter look.
## 5. Component conventions
Decision tree: (1) PrimeVue component exists → use it raw + Tailwind +
`pt`. (2) Same composition 3+ times → wrap it. (3) Genuinely outside
PrimeVue → custom in `components-v2/`.
| Category | Examples | Rule |
|---|---|---|
| Raw PrimeVue | Button, InputText, Tag, Card, Divider, Drawer, Dialog, Avatar, Breadcrumb, DataTable, Toast, Skeleton, Message | Import directly; Tailwind + `pt`; Aura tokens carry brand |
| Wrappers | FormField (reuse existing), Icon (reuse existing), AppDialog, DataTableLazy | Wrap when composition repeats 3+ times |
| Custom | WorkspaceSwitcher, DraggableBlock, EnergyDots, EnergyPicker, RightDrawer scaffold, the timetable/music modules | Hand-built; scoped CSS only when Tailwind can't express it |
- **No `V2` suffix** on files inside `pages-v2/` / `components-v2/` (folder
carries it). Suffix only on `AppShellV2.vue` / `OrganizerLayoutV2.vue`.
- **Forms:** reuse existing `apps/app/src/components/forms/FormField.vue`
(already PrimeVue + Zod). No fork unless it provably diverges.
- **Icons:** reuse existing `apps/app/src/components/Icon.vue`.
crewli-starter `<Icon icon="tabler:x" />` → project `<Icon name="tabler-x" />`
(colon→hyphen is the only delta).
- **TypeScript conversion:** `<script setup>``<script setup lang="ts">`;
`defineProps({...})``defineProps<{...}>()` + `withDefaults`;
`defineEmits([...])` → typed `defineEmits<{...}>()`; `inject(...)`
composable/store; component-local types inline, shared types in
`types/v2/`.
- **Boundaries:** existing `eslint-plugin-boundaries` 10-zone matrix
applies to v2 folders identically; violations stay errors.
## 6. Storybook organization
Custom components are first-class in Storybook exactly like PrimeVue
ones. crewli-starter `ComponentsPage.vue` (~80 PrimeVue components) is
the **source of truth for the standard-component catalog**.
**Story placement:** custom/wrapper stories co-located next to the
`.vue` (moves with the file at cutover); PrimeVue standard catalog and
Foundations centralized under `stories/`.
**Story tree:** `Foundations/` (Color, Typography, Spacing, Icons, Dark
mode, Density) · `PrimeVue/` (~80, grouped as in ComponentsPage) ·
`Layout/` (AppSidebar, AppTopbar, WorkspaceSwitcher, RightDrawer,
AppShell) · `Shared/` (StatusTag, EnergyDots, StatCard, PageHead,
StateBlock, DraggableBlock) · `Forms/` (FormField) · `Templates/`
(List, Form, Detail, Dashboard, StateBlock) · `Modules/` (later:
Timetable, Cue, SectionBuilder).
**Conventions (binding):** CSF3; `tags: ['autodocs']`; every prop wired
as an `argTypes` control; each meaningful state is its own named export
(not a kitchen-sink story); a11y addon runs per story.
**Global toolbar** (`.storybook/preview.ts`): theme toggle (light/dark,
sets `data-theme`) + density toggle (comfortable/compact, sets
`data-density`) — every story verifiable in all 4 theme×density combos.
## 7. Bespoke component specs (1:1 fidelity)
### 7.1 DraggableBlock — fully custom
Extracted from inline `TimetableGrid` markup so timetable + cue editor
render identically.
```ts
defineProps<{
line1Left: { tag?: { label: string; severity?: TagSeverity }; text?: string }
line1Right?: { tag?: { label: string; severity?: TagSeverity }; pill?: string }
line2Left?: string
line2Right?: { progress: number } | null
selected?: boolean
dragging?: boolean
density?: 'compact' | 'regular' | 'comfy' // 56 / 64 / 76 px row
}>()
defineEmits<{
click: []
dragstart: [e: PointerEvent]
dragend: [delta: { x: number; y: number }]
}>()
```
Internals: PrimeVue `Tag` + PrimeVue `ProgressBar`; 2-line flex grid in
scoped CSS (no Tailwind utility expresses crewli-starter spacing — the
justified "port custom CSS" case). Drag = crewli-starter native pointer
drag (coupled to timeline px/min math); component emits
`dragstart`/`dragend`, parent owns positioning. `vuedraggable` not used
(wrong abstraction for free-position blocks). Stories: ArtistBlock,
CueBlock, WithProgress, Selected, Dragging, DarkMode, density variants.
### 7.2 AppDialog — PrimeVue Dialog + slot scaffold
PrimeVue `Dialog` provides scrim / focus-trap / escape / teleport / a11y
(replaces crewli-starter `AppModal` hand-rolled plumbing). Slot contract
preserved: `title`, `sub`, `#tabs` (optional), default = scrollable body,
`#footer` (rendered only if provided). `:pt` matches crewli-starter
header/body/footer padding + body-scroll behaviour.
### 7.3 RightDrawer — PrimeVue Drawer + scaffold
PrimeVue `Drawer position="right"`. Slot scaffold: header (close +
title + `#actions`), scrollable default body, `flush` prop (removes body
padding for edge-to-edge content). Driven by `useRightDrawer()`.
### 7.4 WorkspaceSwitcher — custom visual + PrimeVue Popover
Bespoke visual (gradient-square logo with initials, name/sub stack,
chevron) stays 1:1 custom CSS. Dropdown plumbing (open/close,
click-outside, positioning) replaced by PrimeVue `Popover` (drops manual
`mousedown` listener). Panel content (Workspaces header + Manage link,
list with gradient logos + current checkmark, footer New workspace /
Invite) stays custom markup. Data shape
`{ initials, name, sub, gradient: [from, to] }` backed by
`useWorkspaceStore`.
**Pattern established:** overlay/focus/escape/click-outside behaviour
comes free from PrimeVue `Dialog`/`Drawer`/`Popover`; only bespoke
*visual content* keeps custom CSS.
## 8. Additional secured elements
**Tier-1 primitives (PrimeVue-based, secure now):**
| Element | v2 implementation | Custom CSS |
|---|---|---|
| StatCard | PrimeVue `Card` + Icon + trend `Tag` | none (replaces AppKpiCard) |
| StatusTag | PrimeVue `Tag` + `severity` map (+ dot via `pt`) | none |
| StateBlock | `Skeleton` (loading) · `Message`+retry (error) · `Card`+`Button` (empty) | none (replaces ErrorHeader / AppLoadingIndicator) |
| TagsInput | PrimeVue `AutoComplete` `multiple` + `typeahead` | none (drop custom) |
| PageHead | thin Tailwind flex (title/sub/`#actions`) | none (pure layout) |
| EnergyDots / EnergyPicker | 5-dot meter — no PrimeVue primitive (`Rating` is stars, wrong visual) | minimal, justified |
**Tier-2 — Smart Filter subsystem (secure generic version now):**
`SmartFilterBar` + `FilterChip` + `FilterPopover` + `AddFilterMenu` +
5 editors (Text / NumberRange / MultiSelect / EnumGrid / Tag) +
`SmartListsRow` + `useSmartFilters`. Music-specific `QueryChipsBar` is a
variant — deferred with the music module.
**Tier-3 — Template layer (highest consistency lever):**
`ListTemplate` (PageHead + SmartFilterBar + DataTable + states),
`FormTemplate` (PageHead + Form/FormField + footer actions),
`DetailTemplate` (PageHead + Tabs + RightDrawer hook),
`DashboardTemplate` (StatCard grid + widget slots),
`StateBlock` (the CLAUDE.md mandatory loading/error/empty three-state in
one place). Pages-v2 compose a template instead of hand-rolling layout.
**Tier-4 — Deferred (migrate with owning page):** Timetable suite
(`TimetableGrid` 947, `TimetableTab` 687, `TimetableModals`,
`ParkingColumn`, `TimetablePopover`), Music/Cue suite
(`CueTimelineEditor` 889, `SectionBuilderPro` 2084, `SongDrawerBody`,
`SongFilesPanel`). `DraggableBlock` is the one primitive extracted early.
**Reconciliation:** v2 uses crewli-starter-derived versions; existing
`AppKpiCard` / `AppSearchHeader` / `AppLoadingIndicator` / `ErrorHeader`
stay frozen on v1 surfaces and die at cutover. No back-porting either
direction.
## 9. Foundation sprint deliverables
1. Routing + layout scaffold: `OrganizerLayoutV2`, `AppShellV2`, `/v2/*`
mount, `useWorkspaceStore`, `useRightDrawer()`, theme/density wiring,
one empty `pages-v2/dashboard.vue` proving boot.
2. Shell pieces ported 1:1 (TS): AppSidebar, AppTopbar, SidebarNav,
WorkspaceSwitcher, RightDrawer, AppDialog.
3. Tier-1 primitives (PrimeVue-based per §8).
4. Template layer: List / Form / Detail / Dashboard / StateBlock.
5. Storybook: global theme/density toolbar; story tree; stories for
every Tier-1 primitive + 4 shell components + each template +
foundations. PrimeVue standard catalog (~80) split across foundation
+ Page-1 sprints.
6. New project RFC in `dev-docs/` superseding F4aF4d; pointer added
from `PRIMEVUE_COMPONENTS.md`.
## 10. Sequencing after foundation
- **Page-1 sprint:** events list end-to-end via `ListTemplate`
(exercises SmartFilterBar + lazy DataTable + RightDrawer — highest
pattern-validation value). Smart Filter subsystem secured here.
Finish PrimeVue standard catalog stories in parallel.
- **Subsequent sprints:** one page tree at a time (dashboard → settings
→ events detail → members → platform → portal). Each: build under
`pages-v2/`, add only needed components/stories, sign-off, cutover
commit.
- **Domain modules** migrate with their owning page.
- **Final cutover:** rename `pages-v2/``pages/`,
`components-v2/``components/`, drop `*V2` suffixes, delete dead v1
shell + Vuetify, one closure commit.
## 11. Quality gates (per sprint)
`pnpm test`, `pnpm typecheck`, `pnpm lint` (boundaries matrix on v2
folders), Storybook a11y panel clean on new stories, visual sign-off
against crewli-starter. No `any`. Existing test count must not regress.
## 12. Non-goals
- No backend changes.
- No deletion of v1 code until per-page cutover (or final cutover).
- No back-porting v2 components into v1 surfaces (and vice versa).
- Domain modules (Timetable, Cue, SectionBuilder) are *not* foundation
work — they migrate with their owning page.
- Flatpickr / vue-i18n / DatePicker decisions inherit from
RFC-WS-FRONTEND-PRIMEVUE unchanged.
- No PrimeVue Pro / template purchase.