Skip to content

Commit a34fe84

Browse files
itspradeclaudeinteracsean
authored
docs(skill): update app-shell-patterns for new components (1.7.0) (#359)
* docs(skill): update app-shell-patterns for new components (1.7.0) Sync the app-shell-patterns skill with components shipped since 0.36.0: - Bump documented AppShell version 0.36.0 -> 1.7.0 - Add Grid entry + make it the canonical wrapper for metric/KPI strips - Add DocumentProgressCard entry (lifecycle/fulfilment progress card) - Document Table Head/Cell `align` (left|center|right) and DataTable Column `align` (numeric auto-right); switch examples off ad-hoc classes - Expand Badge to the full 15-variant set (filled / subtle / outline-*) with a primary/secondary status split - Document standalone accessible-name props on Select/Combobox/Autocomplete - Add cross-cutting UX + composition & emphasis rules (one primary action, no duplicate actions, action placement, metric tiles in Grid, forms default to modal, handle loading/empty/error states) - Refine detail/hero-with-actions layout variants and list/dense-scan status-variant guidance to match the primary/secondary badge split Reference images are intentionally left out of this PR and will follow separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(skill): add Alert/Tabs entries and fix component interlinking Complete the component-coverage pass on the app-shell-patterns skill: - Add missing component entries: Alert (variant/action/dismissible) and Tabs (Root/List/Tab/Panel, variant default|line|capsule) - Fix doc bug: Layout example used Tabs.Trigger; real subcomponent is Tabs.Tab - Wire up broken bidirectional links — Grid, MetricCard, DocumentProgressCard were documented as "Used in patterns: detail/*" but never referenced back. Add them to detail/hero-with-actions requiredImports + a new "Optional cards" section (metric strip, progress card, inline Alert) - Demonstrate the new alignment props in pattern examples: Table.Head/Cell align="right" on numeric columns (detail), DataTable Column.align on the Amount column (list-dense-scan) Reference images remain excluded (separate PR). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(skill): apply oxfmt formatting to skill docs Run oxfmt on the docs touched in this branch (the earlier commits skipped the pre-commit hook while local Node was on the wrong major). No content changes — emphasis-marker normalization and requiredImports array wrapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(skill): sharpen app-shell-patterns description for agent discovery Adopt the repo's "<does X>. Use when: …" convention so the skill triggers on UI-building intent (list/table/form/modal/wizard/interaction) and states the app-shell-installed precondition, instead of the passive "catalog" phrasing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: interacsean <seanhasselback@gmail.com>
1 parent a4f735c commit a34fe84

5 files changed

Lines changed: 219 additions & 28 deletions

File tree

packages/core/skills/app-shell-patterns/SKILL.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: app-shell-patterns
3-
description: UI pattern catalog for building pages with @tailor-platform/app-shell components
3+
description: "Best-practice UI patterns and correct component usage for building pages in apps that use @tailor-platform/app-shell. Use when: building or editing any screen, page, list, table, detail view, form, modal, dialog, wizard, or bulk/confirm/toast interaction in an app with @tailor-platform/app-shell installed — or when choosing the right AppShell component, layout, or design token for a UI."
44
---
55

66
# App-Shell Patterns
@@ -65,3 +65,15 @@ These are the foundational rules that underpin all patterns. All patterns build
6565
- NEVER mix patterns in a single page component
6666
- ALWAYS use AppShell components — do NOT use raw HTML or third-party UI libraries
6767
- If no entry matches, compose directly from fundamental references
68+
69+
### Cross-cutting UX rules (apply to every screen)
70+
71+
Full rationale in [`design-system.md`](references/fundamental/design-system.md) → Composition & emphasis rules.
72+
73+
- **One primary action per view:** at most one primary/filled `Button`; everything else is `outline`/`secondary`/`ghost`.
74+
- **Status badges by semantic color:** a **filled** semantic variant for the record's primary/lifecycle status, **`outline-*`** for secondary statuses, **`subtle-*`** for tags; reserve brand `default` for non-status emphasis.
75+
- **No duplicate actions:** an action lives in exactly one place — never repeat the same action in both `Layout.Header` and `ActionPanel`.
76+
- **Action placement:** primary CTA + status in `Layout.Header`; workflow actions in `ActionPanel`; back/navigation in the breadcrumb — never in `ActionPanel`.
77+
- **Metric tiles always go in a `Grid`** (`columns={{ initial: 1, md: 2, xl: 4 }}`) — never one-per-row.
78+
- **Forms default to `form/modal`** — only build a routed full-page form when the design explicitly calls for one.
79+
- **Handle every state:** loading (skeleton), empty (labelled empty state), and error (inline + retry) — never ship only the happy path.

packages/core/skills/app-shell-patterns/references/fundamental/components.md

Lines changed: 121 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AppShell Components
22

3-
> **AppShell version:** `0.36.0` (matches `packages/erp-kit/templates/scaffold/app/*/frontend/package.json` pinned `@tailor-platform/app-shell` semver)
3+
> **AppShell version:** `1.7.0` (matches `packages/erp-kit/templates/scaffold/app/*/frontend/package.json` pinned `@tailor-platform/app-shell` semver)
44
> **Source of truth:** `@tailor-platform/app-shell` exports
55
> **Update process:** see "Keeping this file in sync" at the bottom
66
@@ -79,8 +79,8 @@ import { Button, Layout, Tabs } from "@tailor-platform/app-shell";
7979
<Layout.Header title="Purchase orders" actions={[<Button key="create">Create</Button>]}>
8080
<Tabs.Root value={bucket} onValueChange={setBucket}>
8181
<Tabs.List>
82-
<Tabs.Trigger value="all">All</Tabs.Trigger>
83-
<Tabs.Trigger value="open">Open</Tabs.Trigger>
82+
<Tabs.Tab value="all">All</Tabs.Tab>
83+
<Tabs.Tab value="open">Open</Tabs.Tab>
8484
</Tabs.List>
8585
</Tabs.Root>
8686
</Layout.Header>
@@ -107,6 +107,28 @@ import { Button, Layout, Tabs } from "@tailor-platform/app-shell";
107107

108108
**Used in patterns:** every page pattern (`list/*`, `detail/*`, `form/*`).
109109

110+
### `Grid`
111+
112+
**Import:** `import { Grid } from '@tailor-platform/app-shell'`
113+
**Purpose:** Presentational CSS-Grid container for laying tiles/cards into equal or custom columns with responsive reflow — the canonical wrapper for **metric / KPI strips** and card galleries. Purely layout; makes no data assumptions.
114+
**API:** `GridProps``columns` (number | CSS track string like `"280px 1fr"` | responsive object `{ initial, sm, md, lg, xl }`), `gap` (spacing step, default `3`) plus `gapX` / `gapY`, `minChildWidth` (auto-fit: as many ≥Npx columns as fit, no breakpoints needed), `rows`, `flow`, `align`, `justify`. Sub-component **`Grid.Item`** for spanning/placement — `colSpan`, `rowSpan`, `colStart`, `colEnd` (all responsive). Root carries `data-slot="grid"`.
115+
**Example — responsive KPI grid:**
116+
117+
```tsx
118+
import { Grid, MetricCard } from "@tailor-platform/app-shell";
119+
120+
<Grid columns={{ initial: 1, md: 2, xl: 4 }} gap={4}>
121+
<MetricCard title="Net total" value="$1,500" trend={{ direction: "up", value: "+5%" }} />
122+
<MetricCard title="Open orders" value="42" />
123+
<MetricCard title="Overdue" value="7" />
124+
<MetricCard title="Suppliers" value="18" />
125+
</Grid>;
126+
```
127+
128+
**Used in patterns:** metric / KPI strips on `detail/*` and dashboards; any equal-column card layout.
129+
130+
**Notes:** Metric tiles and KPI cards **always** go in a `Grid` — never stacked one-per-row or in a single column. Use a responsive `columns` object (e.g. `{ initial: 1, md: 2, xl: 4 }`) so tiles reflow at smaller widths; reach for `minChildWidth` when the tile count is dynamic.
131+
110132
### `SidebarLayout`
111133

112134
**Import:** `import { SidebarLayout } from '@tailor-platform/app-shell'`
@@ -250,6 +272,28 @@ import { Button, Link } from '@tailor-platform/app-shell';
250272
**API:** Compound — `Tooltip.Root`, `Tooltip.Trigger`, `Tooltip.Content`.
251273
**Used in patterns:** any pattern with icon-only buttons (must have `aria-label` AND a tooltip).
252274

275+
### `Tabs`
276+
277+
**Import:** `import { Tabs } from '@tailor-platform/app-shell'`
278+
**Purpose:** In-page tab navigation — split one record's sections (Overview / Line items / Activity) or bucket a list (All / Open / …) into switchable panels. Presentational; owns only the active-tab state.
279+
**API:** Compound — `Tabs.Root` (`variant`: `default | line | capsule`; controlled `value` + `onValueChange`, or uncontrolled `defaultValue`), `Tabs.List`, `Tabs.Tab` (`value`), `Tabs.Panel` (`value`). Note: the sub-component is **`Tabs.Tab`**, not `Tabs.Trigger`.
280+
**Example:**
281+
282+
```tsx
283+
<Tabs.Root defaultValue="overview" variant="line">
284+
<Tabs.List>
285+
<Tabs.Tab value="overview">Overview</Tabs.Tab>
286+
<Tabs.Tab value="items">Line items</Tabs.Tab>
287+
</Tabs.List>
288+
<Tabs.Panel value="overview">{/**/}</Tabs.Panel>
289+
<Tabs.Panel value="items">{/**/}</Tabs.Panel>
290+
</Tabs.Root>
291+
```
292+
293+
**Used in patterns:** `list-dense-scan` (bucket tabs composed **above** `DataTable.Root`, synced to `useCollectionVariables` — see the `DataTable` "Bucket tabs" note), `detail/*` (sectioned record content).
294+
295+
**Notes:** For lists, AppShell's own filtering surface is **toolbar chips** (`DataTable.Filters`), not tabs — reach for `Tabs` only when the business genuinely thinks in a small set of named buckets. Don't render a `Tab` per enum value where a filter chip belongs.
296+
253297
---
254298

255299
## Display
@@ -260,22 +304,35 @@ import { Button, Link } from '@tailor-platform/app-shell';
260304
261305
**Import:** `import { Badge } from '@tailor-platform/app-shell'`
262306
**Purpose:** Status labels and small categorical chips.
263-
**API:** `BadgeProps``variant`: `default | success | warning | neutral | error | destructive`. Plus `badgeVariants` CVA for custom-styled siblings.
307+
**API:** `BadgeProps``variant` (15 total):
308+
309+
- **Filled** (high emphasis): `default` (primary), `success`, `warning`, `error`, `neutral`, `info`
310+
- **Subtle** (low emphasis, tinted): `subtle-success`, `subtle-warning`, `subtle-error`, `subtle-info`
311+
- **Outline** (renders a status dot — for row/list statuses): `outline-success`, `outline-warning`, `outline-error`, `outline-info`, `outline-neutral`
312+
313+
Plus `badgeVariants` CVA for custom-styled siblings.
264314
**Example:**
265315

266316
```tsx
267-
<Badge variant="success">Active</Badge>
317+
// Primary / lifecycle status — filled semantic variant
318+
<Badge variant="success">Confirmed</Badge>
319+
// Secondary status (delivery, billing) — outline with status dot
320+
<Badge variant="outline-warning">Partially received</Badge>
321+
// Tag / label — subtle
322+
<Badge variant="subtle-info">New</Badge>
268323
```
269324

270-
**Used in patterns:** `list/*` (status column), `detail/*` (header status).
325+
**Used in patterns:** `list/*` (status column → `outline-*`), `detail/*` (header status).
326+
327+
**Notes:** Encode status by **semantic color** with a primary/secondary split: a record's **primary/lifecycle status** uses a **filled** semantic variant (one per row / one in a detail header); **secondary statuses** (delivery, billing) use **`outline-*`** (status dot); tags use **`subtle-*`**. Reserve **`default`** (brand) for non-status emphasis. Full rule: **`design-system.md`** → Composition & emphasis rules.
271328

272329
### `Table`
273330

274331
> Full API: [https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/table.md](https://raw.githubusercontent.com/tailor-platform/app-shell/refs/heads/main/docs/components/table.md)
275332
276333
**Import:** `import { Table } from '@tailor-platform/app-shell'`
277334
**Purpose:** Semantic data table with scrollable container.
278-
**API:** Compound — `Table.Root`, `Table.Header`, `Table.Body`, `Table.Footer`, `Table.Row`, `Table.Head`, `Table.Cell`, `Table.Caption`. `Table.Root` accepts `containerClassName` for the outer wrapper, `className` for the inner `<table>`.
335+
**API:** Compound — `Table.Root`, `Table.Header`, `Table.Body`, `Table.Footer`, `Table.Row`, `Table.Head`, `Table.Cell`, `Table.Caption`. `Table.Root` accepts `containerClassName` for the outer wrapper, `className` for the inner `<table>`. `Table.Head` and `Table.Cell` accept **`align`** (`"left" | "center" | "right"`, default `"left"`) — prefer this over ad-hoc `className="astw:text-right"` and pair the same alignment on the head and its column's cells (right for numeric/money, center for compact status/icon columns).
279336
**Example:**
280337

281338
```tsx
@@ -284,7 +341,7 @@ import { Button, Link } from '@tailor-platform/app-shell';
284341
<Table.Row>
285342
<Table.Head>Order</Table.Head>
286343
<Table.Head>Status</Table.Head>
287-
<Table.Head className="astw:text-right">Total</Table.Head>
344+
<Table.Head align="right">Total</Table.Head>
288345
</Table.Row>
289346
</Table.Header>
290347
<Table.Body>
@@ -294,7 +351,7 @@ import { Button, Link } from '@tailor-platform/app-shell';
294351
<Table.Cell>
295352
<Badge variant={statusVariant(o.status)}>{o.status}</Badge>
296353
</Table.Cell>
297-
<Table.Cell className="astw:text-right">{formatMoney(o.total)}</Table.Cell>
354+
<Table.Cell align="right">{formatMoney(o.total)}</Table.Cell>
298355
</Table.Row>
299356
))}
300357
</Table.Body>
@@ -346,6 +403,8 @@ const table = useDataTable({
346403
</DataTable.Root>;
347404
```
348405

406+
**Column alignment:** each `Column` accepts **`align`** (`"left" | "right"`) applied to both header and body cell. Numeric `type` columns (`"number"`, `"money"`) default to `"right"` automatically so digits align on the decimal place — pass `align="left"` to opt out; everything else defaults to `"left"`.
407+
349408
**Metadata path:** Prefer `createColumnHelper` + `inferColumns(tableMetadata.order)` (`@tailor-platform/app-shell-sdk-plugin` codegen) when available so enum/datetime/string filters bind to the right editors.
350409

351410
**Bucket tabs / segmented UX:** AppShell defines **toolbar chips**, not lifecycle tabs. When design places **`Tabs`** (All / Draft / …) inside the card, compose them **above** `DataTable.Root` and synchronize tab-driven bucket state with **`useCollectionVariables`** (`variables.query` / filters)—see **`patterns/list/dense-scan.md`**.
@@ -410,19 +469,46 @@ const table = useDataTable({
410469
**Import:** `import { MetricCard } from '@tailor-platform/app-shell'`
411470
**Purpose:** KPI tile for dashboards or hero metric strip.
412471
**API:** `MetricCardProps``title`, `value`, `trend: { direction, value }`, `description`, `icon`.
472+
**Example:** metric tiles are laid out in a `Grid` (never one-per-row):
473+
474+
```tsx
475+
<Grid columns={{ initial: 1, md: 2, xl: 4 }} gap={4}>
476+
<MetricCard
477+
title="Net total"
478+
value="$1,500"
479+
trend={{ direction: "up", value: "+5%" }}
480+
description="vs last month"
481+
icon={<DollarSign />}
482+
/>
483+
<MetricCard title="Open orders" value="42" />
484+
{/* …more tiles */}
485+
</Grid>
486+
```
487+
488+
**Used in patterns:** KPI tiles, dashboards, **`detail/*`** metric strips where specs call for them.
489+
490+
**Notes:** Always wrap metric tiles in **`Grid`** (see the `Grid` entry) — a column of single-width `MetricCard`s stacked one-per-row is an anti-pattern.
491+
492+
### `DocumentProgressCard`
493+
494+
**Import:** `import { DocumentProgressCard } from '@tailor-platform/app-shell'`
495+
**Purpose:** Presentational card for a document's lifecycle / fulfilment state — an optional headline percentage, a stacked progress bar, and a status legend. Domain-agnostic and view-only: pass an arbitrary set of `segments` (e.g. shipped / returned / pending) plus an explicit `percent`; derive both in the consumer.
496+
**API:** `DocumentProgressCardProps``segments` (required; array of `{ label, value, color }`), `percent?` (0–100, headline shown top-right), `title?`, `legend?` (defaults to `segments`; supply only when the legend should differ from the bar, e.g. overlapping buckets), `total?` (bar denominator; defaults to the sum of segment values — a larger value leaves an unfilled remainder), `className?`. Segment `color`: `indigo | pink | green | amber | red | blue | neutral`.
413497
**Example:**
414498

415499
```tsx
416-
<MetricCard
417-
title="Net total"
418-
value="$1,500"
419-
trend={{ direction: "up", value: "+5%" }}
420-
description="vs last month"
421-
icon={<DollarSign />}
500+
<DocumentProgressCard
501+
title="Shipment status"
502+
percent={60}
503+
segments={[
504+
{ label: "Shipped", value: 30, color: "green" },
505+
{ label: "Returned", value: 3, color: "red" },
506+
{ label: "Pending", value: 17, color: "neutral" },
507+
]}
422508
/>
423509
```
424510

425-
**Used in patterns:** KPI tiles, dashboards, **`detail/*`** metric strips where specs call for them.
511+
**Used in patterns:** `detail/*` — fulfilment / lifecycle summary cards (e.g. a purchase-order receipt progress) in the main column or right rail.
426512

427513
### `Avatar`
428514

@@ -483,6 +569,24 @@ const table = useDataTable({
483569
**API:** Compound — `ActivityCard.Root`, `ActivityCard.Items` (generic over item type), plus `ActivityCardProps`, `ActivityCardItem`, `ActivityCardItemProps`. Items render with timestamp + actor + description.
484570
**Used in patterns:** `detail/hero-with-actions` (right column or bottom section).
485571

572+
### `Alert`
573+
574+
**Import:** `import { Alert } from '@tailor-platform/app-shell'`
575+
**Purpose:** Inline, in-page banner for a persistent status message — a form/record error, a warning, or a success/info notice attached to a section. (Transient notifications use `useToast`; blocking confirmations use `Dialog` — see `interaction/confirm`.)
576+
**API:** Compound — `Alert.Root` (`variant`: `neutral | success | warning | error | info`, default `neutral`; a matching icon is rendered automatically; optional `action?: ReactNode`, `dismissible?: boolean`, `onDismiss?: () => void`), `Alert.Title`, `Alert.Description`.
577+
**Example:**
578+
579+
```tsx
580+
<Alert.Root variant="error">
581+
<Alert.Title>Couldn't load line items</Alert.Title>
582+
<Alert.Description>Check your connection and try again.</Alert.Description>
583+
</Alert.Root>
584+
```
585+
586+
**Used in patterns:** any data-backed screen's **error** state (inline error + retry) and record-level warnings — the error affordance the composition rules require (**`design-system.md`** → Composition & emphasis rules → States).
587+
588+
**Notes:** Encode severity by `variant` (semantic color), same discipline as `Badge` — don't use `error` for a routine notice. Pair a retry/next-step control via `action` rather than a separate stray button.
589+
486590
---
487591

488592
## Forms
@@ -523,6 +627,7 @@ const table = useDataTable({
523627
**Import:** `import { Select } from '@tailor-platform/app-shell'`
524628
**Purpose:** Dropdown for fixed enumerations.
525629
**API:** Compound. Sync version (small option lists). For async/typeahead, see `Combobox` and `Autocomplete`. Type: `SelectAsyncFetcher<T>` for the async variant.
630+
**Accessible name:** inside a `Form` the field label provides it automatically. When used **standalone** (outside a `Form`), pass `aria-label` (or `aria-labelledby` / `id`) — these now forward to the underlying combobox element, so it isn't left named only by its current value. Same applies to `Combobox` and `Autocomplete`.
526631
**Used in patterns:** all `form/*` (enum fields).
527632

528633
### `Combobox`

packages/core/skills/app-shell-patterns/references/fundamental/design-system.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,27 @@ When a custom component proves reusable across 2+ apps, promote it upstream into
353353
| Two-column detail at <1024 | right column collapses below main — do not override |
354354
| Inline ID, code, table number | `text-mono` |
355355
| Timestamp, label, subtle metadata | `text-caption text-fg-subtle` |
356+
357+
### Composition & emphasis rules
358+
359+
These are visual-composition rules every screen must follow, regardless of pattern. They exist because emphasis only works when it is scarce.
360+
361+
**Emphasis budget.** Attention is a budget you spend once per scan region.
362+
363+
- **One primary action per view.** A screen (or a card/section) has at most one filled/primary `Button`; everything else is `outline`, `secondary`, or `ghost`. If two things look equally important, neither reads as important.
364+
- **Badges** encode status by semantic color, with a clear primary/secondary split:
365+
- A record's **primary / lifecycle status** (PO status, SO status) → a **filled semantic** variant (`success` / `warning` / `error` / `info` / `neutral`) — one per row in a list, one in a detail header.
366+
- **Secondary statuses** (delivery, billing, fulfilment) and dense supporting columns → **`outline-*`** (with status dot).
367+
- **Tags / labels** ("New", "Returned") → **`subtle-*`**.
368+
- Reserve **`default`** (brand fill) for non-status emphasis — never the brand color as a routine status. The defect to avoid: making _every_ chip a loud fill, or giving secondary statuses the same weight as the primary one. (Variants: **`components.md`**`Badge`.)
369+
- **Color:** status colors (`success`/`warning`/`error`/`info`) signal meaning, not decoration — don't tint neutral content.
370+
371+
**Hierarchy.** One `h1` per page (the `Layout.Header` title). Section headings step down (`h2``h3`); never skip levels for size — pick the role token (`design-system.md` §4 Typography), not the pixel size.
372+
373+
**States — never ship only the happy path.** Every data-backed screen handles:
374+
375+
- **Loading** — skeleton/placeholder, not a blank flash.
376+
- **Empty** — a labelled empty state (what it is, how to add the first record), not a bare empty table.
377+
- **Error** — an inline error with a retry affordance, not a silent failure.
378+
379+
**Spacing rhythm.** Use the spacing scale (§4) consistently — equal gaps between sibling sections, consistent card padding. A one-off `gap` or `padding` that doesn't match its siblings reads as a mistake.

0 commit comments

Comments
 (0)