@exxatdesignux/ui 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.8
4
+
5
+ ### Patch Changes
6
+
7
+ - **New consumer agent skill `exxat-ux-audit` (vendored via `sync-extras`)** — the backward-looking companion to `exxat-senior-ux`. Where senior-UX prevents bad designs from being created, ux-audit grades **existing** surfaces (route / file / component / customer-app path / screenshot) against the same P1–P20 / M1–M12 rubric. Output is a structured Markdown findings report with **Blocker / Issue / Nit** severity tiers, code citations, a fix plan, and a "Want me to apply the text-only Blocker fixes now?" closing prompt. Triggers: "audit X", "review the X page", "is this following DS?", "what's wrong with this screen", "do a UX review", or a route URL with a symptom.
8
+ - **Auto-fix policy:** only Blockers from P1–P8 with text-only, ≤ 3-file, no-architectural-change edits are offered for auto-apply. Issues (P9–P20 / Mx) and Nits are report-only — they need a human design call. The skill always waits for "yes" before applying.
9
+ - **Audit rubric is operational, not philosophical:** every dimension ships with concrete grep / Read signals (D1–D10 cheatsheet), so audits surface specific code references — not vague "the status is unclear" findings.
10
+ - **`exxat-ds-agents` Top-of-stack** updated to route review-mode prompts to `exxat-ux-audit` and build-mode prompts to `exxat-senior-ux`.
11
+
12
+ ## 0.5.7
13
+
14
+ ### Patch Changes
15
+
16
+ - **`tokens:check` ignores purely-metadata fields** — the `--check` diff in `scripts/build-tokens-index.mjs` now strips `version` (in addition to `generatedAt`) before comparing. A `package.json` version bump no longer reports `tokens/hooks-index.json` as stale on its own; only real token content drift (`tokens`, `namespaces`, `themeKeys`, `tokenCount`, `source`) fails CI. Prevents the failure mode that blocked the `0.5.6` release on its first tag push.
17
+ - **`DataTable` lint cleanup** — `scrollRef` added to the `useLayoutEffect` deps list so the `react-hooks/exhaustive-deps` annotation stops appearing on every CI run. The ref identity is stable, so this is behaviourally a no-op.
18
+
3
19
  ## 0.5.6
4
20
 
5
21
  ### Patch Changes
@@ -22,6 +22,8 @@ Before implementing or reviewing **list / table / board / dashboard / data-heavy
22
22
 
23
23
  On any **new** route / page / hub / detail / wizard / settings / dashboard / overlay, output a **design brief** in chat BEFORE writing files. On trivial edits (copy tweaks, single-class restyles, bug fixes), skip the brief.
24
24
 
25
+ **Reviewing existing work?** Load **`.cursor/skills/exxat-ux-audit/SKILL.md`** instead. It runs an EXISTING surface (route / file / component / customer-app path / screenshot) through the same P1–P20 / M1–M12 rubric and outputs a **Blocker / Issue / Nit** findings report with code citations, a fix plan, and an offer to auto-apply text-only Blocker fixes. Triggers: "audit X", "review the X page", "is this following DS?", "what's wrong with this screen", "do a UX review", or a route URL with a symptom.
26
+
25
27
  ## Non‑negotiables (if anything conflicts, open AGENTS.md §12–§13)
26
28
 
27
29
  1. **Product data lists** → `DataTable` + search + shared filters + `TablePropertiesDrawer` — not raw `<table>` / ui `Table` alone / ad-hoc grids. With **`ListPageTemplate`** view tabs, pass **`currentView`** + **`onViewChange`** into **`TablePropertiesDrawer`** (**`AGENTS.md` §4.2**, **`.cursor/rules/exxat-table-properties-drawer.mdc`**).
@@ -0,0 +1,303 @@
1
+ ---
2
+ name: exxat-ux-audit
3
+ description: >-
4
+ Audit an EXISTING Exxat DS surface (route, file, component, customer-app
5
+ path, or screenshot) against the senior-UX principles (P1–P20), modern SaaS
6
+ patterns (M1–M12), and binding DS rules. Produces a structured findings
7
+ report with Blocker / Issue / Nit severity, code citations, and a fix plan
8
+ — and offers to auto-apply text-only Blocker fixes. Load when the user asks
9
+ "audit X", "review the X page", "is this following DS?", "what's wrong with
10
+ this screen", "do a UX review", or pastes a route URL with a problem.
11
+ user-invocable: true
12
+ ---
13
+
14
+ # Exxat DS — UX audit (review existing design)
15
+
16
+ Companion to [`exxat-senior-ux/SKILL.md`](../exxat-senior-ux/SKILL.md). Where
17
+ senior-UX is **forward-looking** (design before code), this skill is
18
+ **backward-looking** (grade what already exists).
19
+
20
+ ## When to load this skill (not senior-UX)
21
+
22
+ | Cue | Use this |
23
+ |-----|----------|
24
+ | "audit `/students/[id]`" / "review this page" / "what's wrong with X" | ✓ |
25
+ | "is this following the DS rules?" / "does this match Library?" | ✓ |
26
+ | User pastes a route URL with a symptom ("duplicate breadcrumb here") | ✓ |
27
+ | "find all the issues on the placements detail" | ✓ |
28
+ | PR review of a hub / detail / wizard already in code | ✓ |
29
+ | **"design a new X"** / "build a settings page" | ✗ — use `exxat-senior-ux` |
30
+
31
+ If both apply (e.g. "audit and rebuild"), do the audit first, then switch.
32
+
33
+ ## The audit protocol (4 phases)
34
+
35
+ ### 1. Locate — find the surface
36
+
37
+ Accept any of:
38
+
39
+ - **Route path:** `/students/[id]` → resolve to `app/(app)/students/[id]/page.tsx` and walk its imported client / component tree.
40
+ - **File path:** `components/student-details.tsx` (relative or absolute, monorepo OR customer app like `../test-9/...`).
41
+ - **Component name:** `StudentDetails` → grep for the export.
42
+ - **Screenshot:** extract IA from the image (per `exxat-no-image-pixel-copy.mdc`), then run the audit against the rendered source if available; otherwise report against the IA alone.
43
+
44
+ State the resolved entry point in the report so the user can verify scope.
45
+
46
+ ### 2. Grade — run the 10-dimension pass
47
+
48
+ Walk all ten in order. Each dimension has concrete grep / Read signals listed
49
+ in §"Grep cheatsheet" below.
50
+
51
+ | # | Dimension | Source of truth |
52
+ |---|-----------|-----------------|
53
+ | 1 | **Navigation integrity** | P1, P2 + `exxat-breadcrumbs-no-back.mdc` |
54
+ | 2 | **Action hierarchy** | P3 + `exxat-page-header-actions.mdc` |
55
+ | 3 | **States (empty / error / loading)** | P5 + M8, M9 |
56
+ | 4 | **Keyboard + a11y** | P6, P7 + `exxat-accessibility.mdc`, `exxat-kbd-shortcuts.mdc` |
57
+ | 5 | **DS composition (no forks)** | P8 + `exxat-reuse-before-custom.mdc`, `exxat-data-tables.mdc`, `exxat-tabs-chrome.mdc`, `exxat-no-vaul.mdc`, `exxat-no-toast.mdc` |
58
+ | 6 | **Modern pattern adherence** | M1–M12 (`modern-saas-patterns.md`) |
59
+ | 7 | **Voice & tone** | `docs/voice-and-tone.md` + P9 |
60
+ | 8 | **Job alignment (IA shape)** | The relevant `docs/jobs/*.md` |
61
+ | 9 | **Token + SLDS discipline** | `exxat-token-discipline.mdc`, `exxat-no-slds-leakage.mdc`, `exxat-no-hex-color` lint rule |
62
+ | 10 | **Hub-specific (only if a hub)** | `exxat-data-tables.mdc`, `exxat-hub-supported-views.mdc`, `exxat-centralized-list-dataset.mdc`, `exxat-list-page-connected-views.mdc` |
63
+
64
+ ### 3. Cite — every finding points at code
65
+
66
+ For each finding, capture:
67
+
68
+ - **Code reference** — file path + line numbers using the CODE REFERENCES
69
+ block format from the editor (see citing_code rules).
70
+ - **Principle / pattern / rule** — `(P1)` or `(M4)` or `(exxat-no-toast.mdc)`.
71
+ - **Why it matters** — one sentence in plain language.
72
+ - **Fix** — concrete suggestion. For Blockers, the exact edit if possible.
73
+
74
+ **Never** report a finding without a code citation or, for screenshots, a
75
+ specific element of the IA. Vague findings ("status is unclear") are useless.
76
+
77
+ ### 4. Report — markdown, in chat, in this exact shape
78
+
79
+ ```
80
+ # UX audit: <entry point>
81
+
82
+ ## Summary
83
+ **Status:** N blockers · N issues · N nits
84
+ **Job match:** <doc name> — ~X% alignment.
85
+ **Scope:** <files walked>
86
+
87
+ ## Blockers (P1–P8 violations — fix before shipping)
88
+ ### B1. <Title> (P<x>)
89
+ - **Where:** `<file>:<lines>`
90
+ - **What:** <one-sentence symptom>
91
+ - **Why:** <one-sentence rationale>
92
+ - **Fix:** <concrete edit>
93
+
94
+ ## Issues (P9–P20 / Mx violations without stated reason)
95
+ ### I1. <Title> (P<x> | M<x> | exxat-<rule>.mdc)
96
+ - **Where:** ...
97
+ - **What:** ...
98
+ - **Fix:** ...
99
+
100
+ ## Nits (preferences / minor modern-anti-pattern signals)
101
+ ### N1. <Title>
102
+ - ...
103
+
104
+ ## What's working
105
+ - <Positive finding with citation>
106
+ - ...
107
+
108
+ ## Fix plan
109
+ 1. <Highest-impact Blocker fix>
110
+ 2. ...
111
+ N. <Lowest-priority Nit>
112
+
113
+ ## Next action
114
+ > Want me to apply the text-only Blocker fixes (B1, B2) now? Issues and Nits
115
+ > I'll leave for you to review.
116
+ ```
117
+
118
+ Always close with the **Next action** line so the user has one click forward.
119
+
120
+ ## Severity rubric — read this before you label anything
121
+
122
+ ### Blocker (B) — violates P1–P8 (always-follow)
123
+ Anything that ships as a bug. Examples:
124
+ - Duplicate way-back (breadcrumb + "Back to" button) — **P1**
125
+ - Record name as breadcrumb leaf + `PageHeader.title` + body `<h1>` — **P2**
126
+ - Two filled CTAs in the header — **P3**
127
+ - Missing `DialogTitle` / `SheetTitle` (even `sr-only`) — **P7**
128
+ - Contrast < 4.5:1 on body text — **P7**
129
+ - Touch target < 24×24 — **P7**
130
+ - Mouse-only action with no keyboard equivalent — **P6**
131
+ - No empty / error / loading state shipped for a list or detail — **P5**
132
+ - New shared primitive forked from `ui/`, `components/data-views/` or `templates/` without proof of ≥ 2 use cases — **P8**
133
+ - Pixel-copy of a competitor screenshot — **P4**
134
+
135
+ ### Issue (I) — violates P9–P20 / Mx without a stated reason
136
+ Costly but not a bug. Examples:
137
+ - `toast()` for product feedback — **`exxat-no-toast.mdc`** / M6
138
+ - `vaul` import — **`exxat-no-vaul.mdc`**
139
+ - `TabsList` stretched full-width — **`exxat-tabs-chrome.mdc`** / M1
140
+ - Centered modal dialog where a `Sheet` would keep context — **M3 / `exxat-drawer-vs-dialog.mdc`**
141
+ - Status only in body, hidden from list / breadcrumb — **M4 / P13**
142
+ - Spinner overlay on initial load instead of `Skeleton` — **M9**
143
+ - Edit-bounces-to-form for a single field — **M5 / P15**
144
+ - Raw `<table>` or third-party data grid on a hub — **`exxat-data-tables.mdc`**
145
+ - Forked allow-list narrower than `FULL_HUB_SUPPORTED_VIEWS` without comment — **`exxat-hub-supported-views.mdc`**
146
+ - KPI strip with > 4 tiles — **`exxat-kpi-max-four.mdc`**
147
+ - `MetricItem` with wrong `trendPolarity` (up arrow on a "lower-is-better" metric) — **`exxat-kpi-trends.mdc`**
148
+
149
+ ### Nit (N) — preferences / minor signals
150
+ Worth noting, not worth blocking. Examples:
151
+ - Missing `<Kbd>` hint on a primary CTA — **`exxat-kbd-shortcuts.mdc`**
152
+ - Color-only status communication without an icon or label — **M4 secondary**
153
+ - Empty-state copy doesn't match `voice-and-tone.md`
154
+ - Sparse density on a daily-power-user surface — **P14**
155
+ - No activity timeline on a record that changes over time — **M7**
156
+
157
+ If you can't decide between two tiers, pick the **lower** severity (Issue over
158
+ Blocker; Nit over Issue) and explain why. Over-flagging Blockers makes the
159
+ report ignorable.
160
+
161
+ ## The 10-dimension grep cheatsheet
162
+
163
+ Use these as starting signals. Read the file before grading — greps surface
164
+ candidates, not verdicts.
165
+
166
+ ### D1 — Navigation integrity
167
+
168
+ | Signal | What it might mean |
169
+ |--------|---------------------|
170
+ | `Back to` near a `SiteHeader.*breadcrumbs` in the same client | **B1: P1** duplicate way-back |
171
+ | `breadcrumbs` array whose last item label equals the `title` prop | **B2: P2** duplicate identity |
172
+ | `<h1>` inside a body component below a `PageHeader` | **B3: P2** duplicate H1 |
173
+
174
+ ### D2 — Action hierarchy
175
+
176
+ | Signal | What it might mean |
177
+ |--------|---------------------|
178
+ | Two adjacent `Button variant="default"` in the actions slot | **B: P3** two primaries |
179
+ | Hand-built `<button>` in `PageHeader.actions` instead of DS `Button` | **I: `exxat-page-header-actions.mdc`** |
180
+
181
+ ### D3 — States
182
+
183
+ | Signal | What it might mean |
184
+ |--------|---------------------|
185
+ | No `Skeleton` / Suspense boundary in a route loading path | **I: M9** spinner-on-load |
186
+ | Hub `renderEmpty` missing on a `ListPageTemplate` | **B: P5** |
187
+ | `<Spinner` / `animate-spin` overlay covering initial load | **I: M9** |
188
+
189
+ ### D4 — Keyboard + a11y
190
+
191
+ | Signal | What it might mean |
192
+ |--------|---------------------|
193
+ | Icon-only `<Button>` without `aria-label` | **B: P7** |
194
+ | `DialogTitle` / `SheetTitle` missing on an overlay | **B: P7** |
195
+ | `role="tablist"` containing `role="button"` / `aria-haspopup` children | **B: `exxat-accessibility.mdc`** |
196
+ | Workflow primary button without `Kbd` + `Shortcut` (form/sheet/dialog) | **I: `exxat-kbd-shortcuts.mdc`** |
197
+ | Bare `<table>` in product hub | **B: `exxat-data-tables.mdc`** + **P7** (scope/headers) |
198
+
199
+ ### D5 — DS composition (no forks)
200
+
201
+ | Signal | What it might mean |
202
+ |--------|---------------------|
203
+ | `import.*from ['"]sonner` or `toast(` call | **I: `exxat-no-toast.mdc`** |
204
+ | `import.*from ['"]vaul` or local `components/ui/drawer` | **I: `exxat-no-vaul.mdc`** |
205
+ | `TabsList.*className=['"].*w-full` | **I: `exxat-tabs-chrome.mdc`** |
206
+ | `slds-` class names / `<lightning-` elements | **B: `exxat-no-slds-leakage.mdc`** |
207
+ | New `ProfileHero` / `RecordHeader` / `EntityHead` component duplicating `PageHeader` | **B: P8** |
208
+ | Custom face-rail / avatar group beside `PageHeader collaboration` | **I: `exxat-collaboration-access.mdc`** |
209
+
210
+ ### D6 — Modern pattern adherence
211
+
212
+ | Signal | What it might mean |
213
+ |--------|---------------------|
214
+ | Centered `Dialog` for export / properties / invite | **I: M3** (use Sheet) |
215
+ | Edit-via-route for single field | **I: M5** (inline edit) |
216
+ | Status only on detail (missing from row / board card) | **I: M4** |
217
+ | Multi-step compose flow inside a Dialog | **I: M3** (use route at ≥ 3 steps) |
218
+ | AI feature auto-runs on record open | **I: M12** |
219
+
220
+ ### D7 — Voice & tone
221
+
222
+ | Signal | What it might mean |
223
+ |--------|---------------------|
224
+ | "Persist", "Materialize", "Submit" where "Save" / "Send" fits | **N: P9** |
225
+ | Empty state wall-of-text + multiple CTAs | **I: M8** |
226
+ | Apologetic / passive copy in errors | **N: voice-and-tone.md** |
227
+
228
+ ### D8 — Job alignment
229
+
230
+ Read the relevant `docs/jobs/*.md` and grade the IA shape against it:
231
+
232
+ - **Record detail** → identity → status → 2-col card grid → activity. Tabs only if ≥ 4 sections / 20+ fields.
233
+ - **List hub** → toolbar / view tabs / `DataTable` / centralized `useTableState`.
234
+ - ...
235
+
236
+ Cite the section number in `jobs/*.md` for any mismatch.
237
+
238
+ ### D9 — Token + SLDS discipline
239
+
240
+ | Signal | What it might mean |
241
+ |--------|---------------------|
242
+ | `#[0-9a-fA-F]{3,8}` hex literal in app code | **B: `exxat-token-discipline.mdc`** + ESLint `exxat-ds/no-hex-color` |
243
+ | `--slds-*` / `var(--slds-*)` | **B: `exxat-no-slds-leakage.mdc`** |
244
+ | `--deprecated-*` or any token marked `deprecated: true` in `tokens/hooks-index.json` | **I: token taxonomy** |
245
+
246
+ ### D10 — Hub-specific (only if the surface IS a hub)
247
+
248
+ | Signal | What it might mean |
249
+ |--------|---------------------|
250
+ | `<DataTable>` mounted in `ListPageTemplate.renderContent` instead of `<HubTable>` | **I: `exxat-data-tables.mdc`** |
251
+ | Forked mock array per view (table mock + tree mock + board mock) | **I: `exxat-centralized-list-dataset.mdc`** |
252
+ | `supportedViewTypes={["table"]}` on a primary hub | **I: `exxat-hub-supported-views.mdc`** |
253
+ | `KEY_METRICS_KPI_COUNT_MAX` exceeded (>4 KPIs) | **I: `exxat-kpi-max-four.mdc`** |
254
+ | `MetricItem` without `trendPolarity` on lower-is-better metric | **I: `exxat-kpi-trends.mdc`** |
255
+
256
+ ## Auto-fix policy
257
+
258
+ After the report, propose a **Next action** at the bottom. The default offers
259
+ auto-fix on **Blockers** that meet ALL of:
260
+
261
+ - Edit is **text-only** (no architectural change).
262
+ - Edit is **single-file** OR **≤ 3 files** that move in lock-step (e.g. remove
263
+ body Back button + trim breadcrumb array in the client).
264
+ - Edit doesn't change **route shape** / **API contract** / **component
265
+ hierarchy**.
266
+
267
+ Examples of auto-fixable Blockers:
268
+
269
+ - Remove a redundant "Back to <parent>" button.
270
+ - Trim the `breadcrumbs` array to ancestors-only.
271
+ - Demote one of two filled CTAs to `variant="outline"`.
272
+ - Add a missing `sr-only` `DialogTitle` / `SheetTitle`.
273
+ - Replace `<button>` with `<Button>` in a header.
274
+
275
+ Examples that are **NOT** auto-fixable (propose only, ask first):
276
+
277
+ - Modal → Sheet conversion (M3) — touches overlay primitives + URL state.
278
+ - Route → Sheet conversion (or vice versa) — IA change.
279
+ - New job-doc creation when none matches.
280
+ - KPI architecture refactor (>4 tiles → flat band + secondary stats).
281
+ - Replacing a forked primitive with composition — needs design review.
282
+ - Anything that touches > 3 files.
283
+
284
+ Always wait for a "yes" before applying.
285
+
286
+ ## Push back (same posture as senior-UX)
287
+
288
+ - Refuse to audit "everything in the repo at once" — request a single surface.
289
+ - Don't grade a screenshot as if it were code unless source is provided too.
290
+ - Don't over-flag Blockers; lean toward Issue when in doubt.
291
+ - If the user wants the **fix plan** without seeing the report, still output
292
+ the report — the report IS the plan's audit trail.
293
+
294
+ ## See also
295
+
296
+ - [`exxat-senior-ux/SKILL.md`](../exxat-senior-ux/SKILL.md) — the forward
297
+ persona (the §5 self-audit is the seed of this skill)
298
+ - [`exxat-ux-principles.mdc`](../../rules/exxat-ux-principles.mdc) — P1–P20
299
+ - [`exxat-ux-discovery-protocol.mdc`](../../rules/exxat-ux-discovery-protocol.mdc) — brief gate
300
+ - [`modern-saas-patterns.md`](../../../apps/web/docs/modern-saas-patterns.md) — M1–M12
301
+ - [`docs/jobs/`](../../../apps/web/docs/jobs/) — job IA references
302
+ - [`exxat-token-economy/SKILL.md`](../exxat-token-economy/SKILL.md) — minimum file set per task
303
+ - All `exxat-*.mdc` rules — concrete enforcement per pattern
@@ -2357,7 +2357,7 @@ function DataTableInner({
2357
2357
  const table = el.querySelector("table");
2358
2358
  if (table) ro.observe(table);
2359
2359
  return () => ro.disconnect();
2360
- }, [totalWidth, displayCols.length, checkOverflow]);
2360
+ }, [totalWidth, displayCols.length, checkOverflow, scrollRef]);
2361
2361
  const columnMenuPendingActionRef = React9.useRef(null);
2362
2362
  const pinnedScrollHintDoneRef = React9.useRef(false);
2363
2363
  React9.useEffect(() => {