@nqlib/nqui 0.6.0 → 0.6.1
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/dist/{command-palette-DhoWGyk_.js → command-palette-CHUiGh5m.js} +2 -2
- package/dist/{command-palette-D24rOeE6.cjs → command-palette-DsvP2QNP.cjs} +2 -2
- package/dist/command.cjs.js +1 -1
- package/dist/command.es.js +1 -1
- package/dist/components/custom/table-of-contents.d.ts +2 -2
- package/dist/components/custom/table-of-contents.d.ts.map +1 -1
- package/dist/components/ui/combobox.d.ts +5 -3
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/{debug-panel-BjfW-YVo.js → debug-panel-BcYzsTp2.js} +1 -1
- package/dist/{debug-panel-CpqsKuxd.cjs → debug-panel-mwtujy5J.cjs} +1 -1
- package/dist/debug.cjs.js +1 -1
- package/dist/debug.es.js +1 -1
- package/dist/{input-shared-CDgy_NdJ.cjs → input-shared-C9Try5fg.cjs} +1 -1
- package/dist/{input-shared-NnOiyHpu.js → input-shared-DXf3Edqt.js} +1 -1
- package/dist/lib/floating-surface.d.ts +6 -2
- package/dist/lib/floating-surface.d.ts.map +1 -1
- package/dist/nqui.cjs.js +10 -10
- package/dist/nqui.es.js +17 -16
- package/dist/styles.css +9 -9
- package/docs/components/README.md +2 -0
- package/docs/components/nqui-combobox.md +15 -2
- package/docs/components/nqui-command-palette.md +7 -0
- package/docs/components/nqui-command.md +41 -0
- package/docs/nqui-skills/ELEVATION.md +33 -6
- package/docs/nqui-skills/RECIPES.md +85 -0
- package/docs/nqui-skills/_claude-commands/README.md +50 -0
- package/docs/nqui-skills/_claude-commands/design/SKILL.md +111 -0
- package/docs/nqui-skills/_claude-commands/edit/SKILL.md +97 -0
- package/docs/nqui-skills/nqui-components/SKILL.md +1 -0
- package/package.json +1 -1
- package/scripts/build-styles.js +4 -3
- package/scripts/install-claude-skills.js +46 -7
package/dist/nqui.es.js
CHANGED
|
@@ -8,9 +8,9 @@ import { E as Fx, a as Gx, C as Bx, u as Vx } from "./enhanced-calendar-CHmWNi0l
|
|
|
8
8
|
import { z as Ir } from "./sonner-C1ndgVQY.js";
|
|
9
9
|
import { T as Wx, E as Kx, T as Ux } from "./sonner-C1ndgVQY.js";
|
|
10
10
|
import { jsx as o, jsxs as M, Fragment as te } from "react/jsx-runtime";
|
|
11
|
-
import { c as fe, b as ke, P as B, u as pe, d as Z, e as I, f as ie, o as re, g as xn, p as Tr, h as sc, q as fr, i as lc, R as cc, F as dc, D as Ar, n as Ve, l as Ee, m as be } from "./input-shared-
|
|
12
|
-
import { c as lt, a as wn, R as uc, T as pc, b as fc, u as He, d as Pr, e as Cn, f as Lt, g as Dr, A as yn, h as $e, I as zt, i as Ft, j as Sn, k as Nn, l as mc, m as gc, n as hc, o as Or, P as vc, p as bc, q as ot, r as nt, s as at, t as Gt, L as xc, v as wc, w as Cc } from "./debug-panel-
|
|
13
|
-
import { $ as jx, a1 as Xx, a0 as Yx, x as Zx, y as Qx, S as Jx, F as e0, z as t0, H as r0, G as o0, N as n0, K as a0, J as i0, E as s0, B as l0, X as c0, _ as d0, Y as u0, Z as p0, C as f0, D as m0, M as g0, a2 as h0, a3 as v0, a4 as b0, a5 as x0, S as w0, F as C0, z as y0, H as S0, G as N0, N as k0, K as M0, J as R0, E as _0, B as E0, a6 as I0, a9 as T0, O as A0, W as P0, Q as D0, V as O0, ac as $0, U as L0, a8 as z0, a7 as F0, ab as G0, aa as B0 } from "./debug-panel-
|
|
11
|
+
import { c as fe, b as ke, P as B, u as pe, d as Z, e as I, f as ie, o as re, g as xn, p as Tr, h as sc, q as fr, i as lc, R as cc, F as dc, D as Ar, n as Ve, l as Ee, m as be } from "./input-shared-DXf3Edqt.js";
|
|
12
|
+
import { c as lt, a as wn, R as uc, T as pc, b as fc, u as He, d as Pr, e as Cn, f as Lt, g as Dr, A as yn, h as $e, I as zt, i as Ft, j as Sn, k as Nn, l as mc, m as gc, n as hc, o as Or, P as vc, p as bc, q as ot, r as nt, s as at, t as Gt, L as xc, v as wc, w as Cc } from "./debug-panel-BcYzsTp2.js";
|
|
13
|
+
import { $ as jx, a1 as Xx, a0 as Yx, x as Zx, y as Qx, S as Jx, F as e0, z as t0, H as r0, G as o0, N as n0, K as a0, J as i0, E as s0, B as l0, X as c0, _ as d0, Y as u0, Z as p0, C as f0, D as m0, M as g0, a2 as h0, a3 as v0, a4 as b0, a5 as x0, S as w0, F as C0, z as y0, H as S0, G as N0, N as k0, K as M0, J as R0, E as _0, B as E0, a6 as I0, a9 as T0, O as A0, W as P0, Q as D0, V as O0, ac as $0, U as L0, a8 as z0, a7 as F0, ab as G0, aa as B0 } from "./debug-panel-BcYzsTp2.js";
|
|
14
14
|
import { cva as se } from "class-variance-authority";
|
|
15
15
|
import * as $r from "@radix-ui/react-slot";
|
|
16
16
|
import { createSlottable as yc, createSlot as Sc, Slot as We } from "@radix-ui/react-slot";
|
|
@@ -18,8 +18,8 @@ import { w as Xe, B as Bt, b as Nc, E as mr } from "./button-S6w9SzkF.js";
|
|
|
18
18
|
import { e as H0 } from "./button-S6w9SzkF.js";
|
|
19
19
|
import { HugeiconsIcon as q } from "@hugeicons/react";
|
|
20
20
|
import { Tick02Icon as Qe, CircleIcon as St, ArrowRight01Icon as ct, Cancel01Icon as Vt, ArrowUpDownIcon as kc, ArrowDown01Icon as Ht, ArrowUp01Icon as Mc, MoreHorizontalIcon as Rc, MinusSignIcon as _c, UnfoldMoreIcon as Ec, MoreHorizontalCircle01Icon as Ic, ArrowLeft01Icon as Tc, PanelLeftIcon as Ac, PaintBoardIcon as gr, Moon01Icon as Pc } from "@hugeicons/core-free-icons";
|
|
21
|
-
import { j as kn, R as Mn, T as Rn, W as Dc, k as _n, l as En, D as In, m as Wt, P as Tn, O as An, I as Pn, n as Oc, o as Dn, p as On, c as $c, f as Lc, d as zc, C as Fc, e as $n, h as Gc, b as Ln } from "./command-palette-
|
|
22
|
-
import { a as K0, i as U0, g as q0, q as j0, s as X0, t as Y0, x as Z0, v as Q0, u as J0, y as ew, z as tw, w as rw, r as ow, A as nw, B as aw } from "./command-palette-
|
|
21
|
+
import { j as kn, R as Mn, T as Rn, W as Dc, k as _n, l as En, D as In, m as Wt, P as Tn, O as An, I as Pn, n as Oc, o as Dn, p as On, c as $c, f as Lc, d as zc, C as Fc, e as $n, h as Gc, b as Ln } from "./command-palette-CHUiGh5m.js";
|
|
22
|
+
import { a as K0, i as U0, g as q0, q as j0, s as X0, t as Y0, x as Z0, v as Q0, u as J0, y as ew, z as tw, w as rw, r as ow, A as nw, B as aw } from "./command-palette-CHUiGh5m.js";
|
|
23
23
|
import { C as sw, a as lw, b as cw, d as dw, c as uw } from "./carousel-Cs7SCVhI.js";
|
|
24
24
|
import { D as fw, d as mw, e as gw, i as hw, g as vw, f as bw, b as xw, a as ww, h as Cw, c as yw } from "./drawer-DO26uhym.js";
|
|
25
25
|
import Bc from "react-dom";
|
|
@@ -5305,32 +5305,33 @@ function Mm(e) {
|
|
|
5305
5305
|
function lv({
|
|
5306
5306
|
className: e,
|
|
5307
5307
|
children: t,
|
|
5308
|
-
|
|
5308
|
+
renderItem: r,
|
|
5309
|
+
...n
|
|
5309
5310
|
}) {
|
|
5310
|
-
const { items:
|
|
5311
|
+
const { items: a, filterItems: i, search: l, open: c, setListboxIdAria: d } = Se(), { staticNodes: f, renderFn: u } = Mm(t), p = r ?? u, m = s.useMemo(() => !p || !a ? null : a.filter(i).map((b) => p(b)), [p, a, i, l]), v = s.useRef(null);
|
|
5311
5312
|
return s.useLayoutEffect(() => {
|
|
5312
|
-
if (!
|
|
5313
|
-
|
|
5313
|
+
if (!c) {
|
|
5314
|
+
d(void 0);
|
|
5314
5315
|
return;
|
|
5315
5316
|
}
|
|
5316
5317
|
requestAnimationFrame(() => {
|
|
5317
|
-
const
|
|
5318
|
-
|
|
5318
|
+
const b = v.current?.id;
|
|
5319
|
+
b && d(b);
|
|
5319
5320
|
});
|
|
5320
|
-
}, [
|
|
5321
|
+
}, [c, d, a, m, f]), /* @__PURE__ */ M(
|
|
5321
5322
|
$c,
|
|
5322
5323
|
{
|
|
5323
|
-
ref:
|
|
5324
|
+
ref: v,
|
|
5324
5325
|
"data-slot": "combobox-list",
|
|
5325
5326
|
className: h(
|
|
5326
5327
|
"pointer-events-auto relative z-[1] min-h-0 flex-1",
|
|
5327
5328
|
"max-h-[min(18rem,max(0px,calc(var(--radix-popover-content-available-height,24rem)-5.5rem)))]",
|
|
5328
5329
|
e
|
|
5329
5330
|
),
|
|
5330
|
-
...
|
|
5331
|
+
...n,
|
|
5331
5332
|
children: [
|
|
5332
|
-
|
|
5333
|
-
|
|
5333
|
+
f,
|
|
5334
|
+
m
|
|
5334
5335
|
]
|
|
5335
5336
|
}
|
|
5336
5337
|
);
|
package/dist/styles.css
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
@source inline("focus:bg-primary/80 focus:bg-secondary/80 focus:bg-destructive/80 focus:bg-success/80 focus:bg-warning/80 focus:bg-info/80 focus:border-primary/80 focus:border-secondary/80 focus:border-destructive/80 focus:border-success/80 focus:border-warning/80 focus:border-info/80 focus:border-border focus:outline-0 focus-visible:outline-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-destructive");
|
|
50
50
|
|
|
51
51
|
/* Active state utilities */
|
|
52
|
-
@source inline("active:bg-primary/75 active:bg-secondary/75 active:bg-destructive/75 active:bg-success/75 active:bg-warning/75 active:bg-info/75 active:border-primary/75 active:border-secondary/75 active:border-destructive/75 active:border-success/75 active:border-warning/75 active:border-info/75 active:border-border active:bg-none active:shadow-[
|
|
52
|
+
@source inline("active:bg-primary/75 active:bg-secondary/75 active:bg-destructive/75 active:bg-success/75 active:bg-warning/75 active:bg-info/75 active:border-primary/75 active:border-secondary/75 active:border-destructive/75 active:border-success/75 active:border-warning/75 active:border-info/75 active:border-border active:bg-none active:shadow-[inset_0_3px_5px_rgb(0_0_0/0.125)] active:scale-95");
|
|
53
53
|
|
|
54
54
|
/* Disabled state utilities */
|
|
55
55
|
@source inline("disabled:bg-transparent disabled:shadow-none disabled:opacity-65 disabled:pointer-events-none");
|
|
@@ -57,6 +57,9 @@
|
|
|
57
57
|
/* Interaction & Cursor utilities */
|
|
58
58
|
@source inline("cursor-pointer select-none touch-manipulation opacity-90 overflow-hidden");
|
|
59
59
|
|
|
60
|
+
/* cmdk row selection (React 19 — aria-selected, not data-selected) */
|
|
61
|
+
@source inline("aria-selected:bg-accent aria-selected:text-accent-foreground data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground");
|
|
62
|
+
|
|
60
63
|
/* Transition utilities */
|
|
61
64
|
@source inline("transition-[color,background-color,border-color,box-shadow,opacity,transform] duration-150 ease-in-out");
|
|
62
65
|
|
|
@@ -66,9 +69,6 @@
|
|
|
66
69
|
/* Group & State utilities */
|
|
67
70
|
@source inline("group/badge has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 light:aria-invalid:ring-destructive/40 aria-invalid:border-destructive");
|
|
68
71
|
|
|
69
|
-
/* Z-index elevation utilities */
|
|
70
|
-
@source inline("z-[var(--z-debug)] z-[var(--z-sticky-page)] z-[var(--z-background)] z-[var(--z-content)] z-[var(--z-popover)] z-[var(--z-modal-backdrop)] z-[var(--z-modal)] z-[var(--z-tooltip)] z-[var(--z-sticky-content)] z-[var(--z-floating)]");
|
|
71
|
-
|
|
72
72
|
/* Hit-area utilities (referenced from TSX / docs; @utility defines behavior) */
|
|
73
73
|
@source inline("hit-area-4 hit-area-6 hit-area-debug");
|
|
74
74
|
|
|
@@ -261,11 +261,7 @@
|
|
|
261
261
|
/* ─── Radius scale ──────────────────────────────────────────────────
|
|
262
262
|
* Lives in @theme (not a separate file) because Tailwind v4 requires
|
|
263
263
|
* --radius-* tokens here to generate the `rounded-*` utilities.
|
|
264
|
-
* Base: --radius = 0.45rem (
|
|
265
|
-
nqui Color System & Design Tokens
|
|
266
|
-
============================================ */
|
|
267
|
-
|
|
268
|
-
:root below).
|
|
264
|
+
* Base: --radius = 0.45rem (see merged theme variables).
|
|
269
265
|
*
|
|
270
266
|
* Usage convention:
|
|
271
267
|
* rounded-sm — kbd, small inline pills, dense controls
|
|
@@ -815,6 +811,10 @@
|
|
|
815
811
|
============================================ */
|
|
816
812
|
|
|
817
813
|
/* Default light theme variables (.dark block follows) */
|
|
814
|
+
/* ============================================
|
|
815
|
+
nqui Color System & Design Tokens
|
|
816
|
+
============================================ */
|
|
817
|
+
|
|
818
818
|
:root {
|
|
819
819
|
/* Primary scale — blue hue 240; toned down vs prior (lower L / slightly less chroma on saturated stops) */
|
|
820
820
|
--primary-100: oklch(0.655 0.11 240);
|
|
@@ -352,6 +352,8 @@ Use these rules to choose the right component. **Selection** = user picks from o
|
|
|
352
352
|
| CommandPalette | [nqui-command-palette.md](./nqui-command-palette.md) | Full Cmd+K palette. |
|
|
353
353
|
| TableOfContents | [nqui-table-of-contents.md](./nqui-table-of-contents.md) | Doc section links from headings. |
|
|
354
354
|
|
|
355
|
+
**Cmd+K / cmdk row highlight (React 19):** Fixed in **nqui ≥ 0.6.1** (`aria-selected:bg-accent` in `floatingListItemInteractive`). If every row still looks selected on an older version, see [nqui-command-palette.md](./nqui-command-palette.md).
|
|
356
|
+
|
|
355
357
|
---
|
|
356
358
|
|
|
357
359
|
## Overlays – When to Use
|
|
@@ -130,16 +130,29 @@ When `**showPanelSearch**` is false, the panel `**CommandInput**` is wrapped in
|
|
|
130
130
|
## cmdk constraints
|
|
131
131
|
|
|
132
132
|
- **List `id`:** cmdk **overwrites** the `id` on `Command.List`. Do not assume `useId()` matches the DOM — the combobox mirrors the mounted list element’s id into context for `**aria-controls`**.
|
|
133
|
-
- **Selection styling:** Prefer `**aria-selected`** / `**data-selected**` (boolean) utilities. Patterns like `**data-[selected=true]:**` can fail because the attribute is not always the string `"true"`.
|
|
134
133
|
- **Panel input:** Keep the cmdk input **mounted** when filtering (see `**sr-only`** branch above).
|
|
135
134
|
|
|
135
|
+
### Row selection styling (React 19 + cmdk)
|
|
136
|
+
|
|
137
|
+
cmdk sets `data-selected` / `aria-selected` on **every** row. **React 19** renders booleans as strings (`data-selected="false"`, `aria-selected="false"`), not absent attributes.
|
|
138
|
+
|
|
139
|
+
| Tailwind utility | Compiled selector | Safe with React 19? |
|
|
140
|
+
| ---------------- | ----------------- | ------------------- |
|
|
141
|
+
| `data-selected:bg-accent` | `[data-selected]` | **No** — matches `"false"` too → all rows highlighted |
|
|
142
|
+
| `data-[selected=true]:bg-accent` | `[data-selected=true]` | Yes — selected row only |
|
|
143
|
+
| `data-[selected=false]:bg-transparent` | `[data-selected=false]` | Yes — reset unselected rows when overriding CommandItem |
|
|
144
|
+
| `aria-selected:bg-accent` | `[aria-selected=true]` | **Yes** — preferred (used by `ComboboxItem` in source) |
|
|
145
|
+
|
|
146
|
+
nqui **ComboboxItem** and **CommandItem** (≥ 0.6.1) use `aria-selected:bg-accent` via `floatingListItemInteractive`.
|
|
147
|
+
|
|
136
148
|
## Troubleshooting
|
|
137
149
|
|
|
138
150
|
|
|
139
151
|
| Symptom | What to check |
|
|
140
152
|
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
141
153
|
| Trigger `**aria-controls**` missing or wrong | `**ComboboxList**` must mount while the popover is open so the list ref can read `**id**`. |
|
|
142
|
-
|
|
|
154
|
+
| **Every row** has muted/accent background (looks all selected) | DevTools: unselected rows have `data-selected="false"`. Bare `data-selected:` / `[data-selected]` CSS matches them. Use `aria-selected:bg-accent` or `data-[selected=true]:` + `data-[selected=false]:bg-transparent`. See `nqui-command.md`. |
|
|
155
|
+
| Row highlight / keyboard selection looks wrong | Not only `data-[selected=true]` — with React 19, also avoid `data-selected:bg-accent`. Prefer `aria-selected:bg-accent`. |
|
|
143
156
|
| **Mouse hover / click on rows does nothing; keyboard works** | Row styles must not use a broad `**data-[disabled]:pointer-events-none`** (can match any `data-disabled` value). Use `**aria-disabled:**` (or `**data-[disabled=true]:**`) so only disabled items drop pointer events. Hidden panel search is wrapped with `**sr-only**` + `**pointer-events-none**`; the cmdk `**CommandInput**` keeps `**pointer-events-auto**`. |
|
|
144
157
|
| Filter feels out of sync | Avoid hiding the panel `**CommandInput**` with `**display: none**`. |
|
|
145
158
|
|
|
@@ -23,7 +23,14 @@ import { CommandPalette } from "@nqlib/nqui"
|
|
|
23
23
|
<CommandPalette shortcutEnabled={false} ... />
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
## Row highlight (Cmd+K list)
|
|
27
|
+
|
|
28
|
+
`CommandItem` uses `floatingListItemInteractive` with **`aria-selected:bg-accent`** (nqui ≥ 0.6.1). Do not add `data-selected:bg-accent` on custom rows — React 19 sets `data-selected="false"` on unselected cmdk items, and `[data-selected]` would highlight every row.
|
|
29
|
+
|
|
30
|
+
If an older nqui version still shows all rows filled: upgrade to **0.6.1+** or see `nqui-command.md` for a temporary `CommandItem` className override.
|
|
31
|
+
|
|
26
32
|
## Notes
|
|
27
33
|
|
|
28
34
|
- Keyboard listener on window. May conflict with other global shortcuts.
|
|
29
35
|
- Custom title/description for accessibility.
|
|
36
|
+
- Prefer plain `CommandList` in the showcase; wrap in `ScrollArea` only when you need a bounded height — scroll behavior is separate from row highlight.
|
|
@@ -37,3 +37,44 @@ import {
|
|
|
37
37
|
<CommandList>...</CommandList>
|
|
38
38
|
</CommandDialog>
|
|
39
39
|
```
|
|
40
|
+
|
|
41
|
+
## CommandItem selection styling (cmdk + React 19)
|
|
42
|
+
|
|
43
|
+
Default `CommandItem` classes come from `floatingListItemInteractive` in `lib/floating-surface.ts`:
|
|
44
|
+
|
|
45
|
+
- `data-selected:bg-accent` → CSS **`[data-selected]`** (any value)
|
|
46
|
+
- `data-[highlighted]:bg-accent` — Radix pointer highlight
|
|
47
|
+
- `focus:bg-accent` — keyboard focus
|
|
48
|
+
|
|
49
|
+
cmdk sets **`data-selected` and `aria-selected` on every row**. With **React 19**, unselected rows render as `data-selected="false"` / `aria-selected="false"` (strings), not omitted attributes.
|
|
50
|
+
|
|
51
|
+
### Symptom
|
|
52
|
+
|
|
53
|
+
Every list row has a muted/accent pill background; hover/keyboard should highlight **one** row only.
|
|
54
|
+
|
|
55
|
+
### What to check
|
|
56
|
+
|
|
57
|
+
1. Inspect an idle row: `data-selected="false"` present → bare `data-selected:` utilities will still match.
|
|
58
|
+
2. Only **one** row should have `aria-selected="true"` (unless two items share the same cmdk `value`).
|
|
59
|
+
3. Do not assume “only one selected” in the DOM means correct CSS — check computed `background-color`.
|
|
60
|
+
|
|
61
|
+
### Do / don't (Tailwind)
|
|
62
|
+
|
|
63
|
+
| Do | Don't |
|
|
64
|
+
| -- | ----- |
|
|
65
|
+
| `aria-selected:bg-accent` | `data-selected:bg-accent` |
|
|
66
|
+
| `data-[selected=true]:bg-accent` | `data-[selected]:bg-accent` |
|
|
67
|
+
| `data-[selected=false]:bg-transparent` when overriding defaults | `bg-muted` on every row “to match design” |
|
|
68
|
+
|
|
69
|
+
**nqui ≥ 0.6.1:** `floatingListItemInteractive` uses `aria-selected:bg-accent` (same as ComboboxItem).
|
|
70
|
+
|
|
71
|
+
### Consumer override (nqui < 0.6.1 only)
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<CommandItem
|
|
75
|
+
className="bg-transparent data-[selected=false]:!bg-transparent aria-selected:bg-accent aria-selected:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground"
|
|
76
|
+
...
|
|
77
|
+
>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
See `nqui-command-palette.md` for Cmd+K-specific notes.
|
|
@@ -109,15 +109,42 @@ This rule has principled exceptions. Continuous is correct when:
|
|
|
109
109
|
|
|
110
110
|
If your screen isn't one of these three, you don't need continuous.
|
|
111
111
|
|
|
112
|
-
##
|
|
112
|
+
## Mode 3: Single-surface refinement (not just an "exception")
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
The most refined surface model: ONE inline surface (`bg-background`) for everything — page, sidebar, header, content — with structure carried entirely by spacing, type weight, and the occasional uppercase label. The `bg-popover` overlay surface (Mode 2's "+1") still exists for modals/sheets/popovers, but inline surfaces collapse to a single canvas.
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
2. **Reading views** — long-form text, documentation, blog posts.
|
|
118
|
-
3. **Single-concept screens** — a page that does one thing, deeply.
|
|
116
|
+
This is **not a rare special case** — it's a deliberate mode used by a meaningful slice of modern product / docs UIs. Recognize it as the third recognized model alongside continuous and 2+1 hybrid.
|
|
119
117
|
|
|
120
|
-
|
|
118
|
+
### When single-surface is the right mode
|
|
119
|
+
|
|
120
|
+
1. **Focus-mode editors** — iA Writer, Apple Notes detail, Things 3 task detail. Sustained-attention contexts.
|
|
121
|
+
2. **Reading views** — long-form text, documentation, blog posts, changelogs.
|
|
122
|
+
3. **Marketing / landing pages** — Linear's homepage, Vercel's docs, Boneyard's overview. One canvas, generous space, type does all the hierarchy.
|
|
123
|
+
4. **Docs sites** — Stripe docs, Tailwind docs. Content > chrome.
|
|
124
|
+
5. **Content-first apps** — note-taking, journaling, writing tools. The reader/writer needs uninterrupted visual flow.
|
|
125
|
+
6. **Single-concept product surfaces** — a page that does one thing, deeply, without branching sub-regions.
|
|
126
|
+
|
|
127
|
+
### How structure works without surfaces
|
|
128
|
+
|
|
129
|
+
Without surface contrast, hierarchy comes from:
|
|
130
|
+
- **Spacing rhythm** — vary gaps deliberately (gap-2 within a row, gap-6 between sections, gap-12 between major regions). Variation IS the structure.
|
|
131
|
+
- **Type weight + size** — bold display headlines, regular body, muted secondary text. A 3-step type ladder replaces 3 surfaces.
|
|
132
|
+
- **Uppercase labels** — `text-xs uppercase tracking-wider text-muted-foreground` to mark sections without drawing boxes around them.
|
|
133
|
+
- **Active-nav indicator as an inset bar** — not `bg-accent` pill. See `RECIPES.md` #23 "Active nav indicator."
|
|
134
|
+
- **One bordered/contained element per page** at most — the place that genuinely needs to feel "lifted" (a callout, a code block, a primary action area).
|
|
135
|
+
|
|
136
|
+
### Trade-offs (be honest about them)
|
|
137
|
+
|
|
138
|
+
| Use single-surface when… | Use 2+1 hybrid when… |
|
|
139
|
+
|--------------------------|----------------------|
|
|
140
|
+
| The page has ONE primary content stream | The page has SEPARATE sub-regions that need to feel like distinct topics (e.g. a card grid of metrics) |
|
|
141
|
+
| Reading/writing/scanning content is the task | Navigating between distinct workspaces / tools / lists is the task |
|
|
142
|
+
| Density is moderate-to-low | Density is high (data tables, kanban boards, dashboards) |
|
|
143
|
+
| The product personality is calm/editorial | The product personality is operational/dense |
|
|
144
|
+
|
|
145
|
+
### Anti-pattern within single-surface mode
|
|
146
|
+
|
|
147
|
+
The biggest failure mode: removing surfaces but keeping every horizontal divider. You then have NO surface contrast AND a stack of `border-b` lines, which reads as "broken" not "refined." Either commit to surfaces+borders (2+1) or commit to single-surface+spacing — don't mix.
|
|
121
148
|
|
|
122
149
|
## Anti-patterns this rule eliminates
|
|
123
150
|
|
|
@@ -723,6 +723,91 @@ Auth is the most-requested pattern that the kit must cover well. Get these wrong
|
|
|
723
723
|
|
|
724
724
|
---
|
|
725
725
|
|
|
726
|
+
## Single-surface / refined recipes
|
|
727
|
+
|
|
728
|
+
These recipes apply when the surrounding shell uses single-surface mode (see `ELEVATION.md` Mode 3 — landing pages, docs, content-first apps). They're cleaner alternatives to recipes that work well on multi-surface shells but feel heavy without surrounding chrome.
|
|
729
|
+
|
|
730
|
+
### 23. Active nav indicator (inset bar, not bg-fill)
|
|
731
|
+
|
|
732
|
+
**Intent:** mark the active route in a sidebar / docs nav without using `bg-accent` as a pill. Used by Linear sidebar, Notion docs, Vercel docs, Boneyard.
|
|
733
|
+
|
|
734
|
+
```tsx
|
|
735
|
+
<NavLink
|
|
736
|
+
to={item.to}
|
|
737
|
+
className={({ isActive }) =>
|
|
738
|
+
[
|
|
739
|
+
"relative flex items-center gap-2 rounded-md px-3 py-1.5 text-sm transition-colors",
|
|
740
|
+
// INSET LEFT BAR — appears only when active. 2px wide, full row height,
|
|
741
|
+
// sits in the left padding without shifting the label.
|
|
742
|
+
"before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2",
|
|
743
|
+
"before:h-4 before:w-[2px] before:rounded-full before:bg-foreground",
|
|
744
|
+
"before:opacity-0 before:transition-opacity",
|
|
745
|
+
isActive
|
|
746
|
+
? "text-foreground font-medium before:opacity-100"
|
|
747
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted/40",
|
|
748
|
+
].join(" ")
|
|
749
|
+
}
|
|
750
|
+
>
|
|
751
|
+
{item.label}
|
|
752
|
+
</NavLink>
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**Rules:**
|
|
756
|
+
- Bar is **2px wide** — wider reads as a divider, narrower disappears at low DPI.
|
|
757
|
+
- Bar uses **`bg-foreground`** (or the brand color) — needs full contrast to register as an active marker.
|
|
758
|
+
- Active row gets **bolder text** (`font-medium`) AND the bar. Either alone is too weak; both together makes the active state unambiguous.
|
|
759
|
+
- Hover on inactive rows uses **`bg-muted/40`** — a soft pill — so hover and active are distinguishable (active = bar + bold; hover = soft pill).
|
|
760
|
+
- **Do NOT also fill with `bg-accent`** — defeats the point. The bar+bold combo IS the indicator.
|
|
761
|
+
|
|
762
|
+
**When to use this instead of bg-fill pill:**
|
|
763
|
+
- Single-surface shell (no surface contrast available — bg-fill would clash with the borderless canvas)
|
|
764
|
+
- Docs / marketing / content-first app where the nav is meant to defer to the content
|
|
765
|
+
- Sidebars where 6+ items would create too many filled pills if multiple states were highlighted
|
|
766
|
+
|
|
767
|
+
**When to KEEP bg-fill instead:**
|
|
768
|
+
- Dense product UI with high information density (Linear's issue list nav, our PMO sidebar with badges + icons + counts) — there, the bg-fill survives the noise better than a thin bar.
|
|
769
|
+
|
|
770
|
+
### 24. Concept demonstration (show, don't tell)
|
|
771
|
+
|
|
772
|
+
**Intent:** explain an abstract feature by pairing the REAL artifact with the ABSTRACT artifact side-by-side in one frame. Used by Boneyard (real UI vs skeleton), Vercel marketing (code before vs after), Anthropic docs (prompt vs response).
|
|
773
|
+
|
|
774
|
+
```tsx
|
|
775
|
+
<figure className="rounded-xl border border-border overflow-hidden">
|
|
776
|
+
{/* Top — labels for each half, monospace-ish small caps */}
|
|
777
|
+
<div className="grid grid-cols-2 px-6 pt-5 pb-2 text-[10px] uppercase tracking-wider text-muted-foreground font-semibold">
|
|
778
|
+
<span>Real UI</span>
|
|
779
|
+
<span>Skeleton</span>
|
|
780
|
+
</div>
|
|
781
|
+
|
|
782
|
+
{/* Body — the two artifacts, equal-width, same vertical rhythm */}
|
|
783
|
+
<div className="grid grid-cols-2 gap-6 px-6 pb-6">
|
|
784
|
+
<div className="flex flex-col gap-3">
|
|
785
|
+
{/* REAL — full-fidelity rendering with colour, icons, labels, data */}
|
|
786
|
+
<RealDashboard />
|
|
787
|
+
</div>
|
|
788
|
+
<div className="flex flex-col gap-3">
|
|
789
|
+
{/* ABSTRACT — same shapes/positions but stripped of colour and detail */}
|
|
790
|
+
<SkeletonDashboard />
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
</figure>
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
**Rules:**
|
|
797
|
+
- **Equal column widths** — asymmetry implies one is more important than the other; for "concept = output" pairing, they need to read as equals.
|
|
798
|
+
- **Same vertical rhythm** — both halves use the same `gap-*` values so eye-line moves left-right at the same heights. Misaligned rows kill the visual rhyme.
|
|
799
|
+
- **The contrast IS the explanation** — don't add an arrow or "→" between them. The user's brain does the comparison automatically.
|
|
800
|
+
- **Real side gets all the colour** — accent colors, icons, brand. The abstract side stays monochrome / grey. The colour drop-off is the second signal that "this is the stripped version."
|
|
801
|
+
- **Containing frame is borderless or has the lightest possible border** (`border-border`) — heavy frame would compete with the contrast inside.
|
|
802
|
+
- **Labels go above each half** in tiny uppercase muted text — not floating labels in the middle, not headers in their own rows. The labels are minor; the demonstration is the point.
|
|
803
|
+
|
|
804
|
+
**Don't use this pattern for:**
|
|
805
|
+
- Single concepts that don't benefit from comparison (just show the thing)
|
|
806
|
+
- More than 2 panels — 3+ becomes a comparison table, different recipe
|
|
807
|
+
- Steps in a sequence — use a numbered flow, not a side-by-side
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
726
811
|
## Combo synthesis — how to think about new ones
|
|
727
812
|
|
|
728
813
|
When you face a situation not listed here, ask:
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Claude Code slash commands shipped with nqui
|
|
2
|
+
|
|
3
|
+
This folder contains the **slash command entry points** that the `init-claude-skills` CLI installs into `~/.claude/skills/` on a consumer machine. These become invocable as `/design` and `/edit` inside Claude Code and Claude Desktop after install.
|
|
4
|
+
|
|
5
|
+
## How a consumer installs (once they `pnpm install`)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install @nqlib/nqui # install the library
|
|
9
|
+
npx @nqlib/nqui init-claude-skills # install Claude Code skills to ~/.claude/skills
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
What that command does:
|
|
13
|
+
1. Copies all 15 root docs (SKILL.md, AGENT_PROMPT.md, ELEVATION.md, MOTION.md, RECIPES.md, STATES.md, WRITING.md, EVAL.md, THEMING.md, MIGRATION.md, COMPOSITION.md, COMPONENTS_INDEX.md, HUMAN_GUIDE.md, READ_BUDGET.md, README.md) → `~/.claude/skills/nqui/`
|
|
14
|
+
2. Copies all subdirectory skills (nqui-composition, nqui-components, nqui-design-system, nqui-shadcn, impeccable, audit, layout, polish, etc.) → `~/.claude/skills/<skill-name>/`
|
|
15
|
+
3. Copies all 68 per-component docs → `~/.claude/skills/nqui/components/`
|
|
16
|
+
4. Copies the slash command skills (this folder) → `~/.claude/skills/design/` and `~/.claude/skills/edit/`
|
|
17
|
+
|
|
18
|
+
After install, the consumer restarts Claude Code, and `/design` and `/edit` become available as tools.
|
|
19
|
+
|
|
20
|
+
## What `/design` does
|
|
21
|
+
|
|
22
|
+
When the user types `/design [a login form]` (or any UI build request), the agent:
|
|
23
|
+
|
|
24
|
+
1. Loads `AGENT_PROMPT.md` (the non-negotiable rules)
|
|
25
|
+
2. Loads design context from `.impeccable.md` (or asks the user if missing)
|
|
26
|
+
3. Picks a named layout pattern from `COMPOSITION.md`
|
|
27
|
+
4. Pulls combos from `RECIPES.md`
|
|
28
|
+
5. Applies the elevation rule (2+1 surfaces), state matrix, writing rules
|
|
29
|
+
6. Runs the 30-second Linear designer self-review
|
|
30
|
+
7. Ships work that's reliably 8/10 or better
|
|
31
|
+
|
|
32
|
+
## What `/edit` does
|
|
33
|
+
|
|
34
|
+
When the user points at existing UI (`/edit the sprint tracker page`), the agent:
|
|
35
|
+
|
|
36
|
+
1. Reads the target file(s)
|
|
37
|
+
2. Diagnoses against the design rules (elevation / states / writing / motion / composition / anti-patterns)
|
|
38
|
+
3. Names findings as P0/P1/P2 with file:line locations
|
|
39
|
+
4. Proposes minimum-viable fixes
|
|
40
|
+
5. Applies them via Edit tool, citing which rule each fix maps to
|
|
41
|
+
|
|
42
|
+
Different from `/design` (which builds from scratch).
|
|
43
|
+
|
|
44
|
+
## Updating the slash commands
|
|
45
|
+
|
|
46
|
+
The source of truth lives in this folder (`docs/nqui-skills/_claude-commands/`). Edit `design/SKILL.md` or `edit/SKILL.md` here; the next `init-claude-skills` run propagates changes.
|
|
47
|
+
|
|
48
|
+
## Why both files live in the package, not just in `~/.claude/skills/`
|
|
49
|
+
|
|
50
|
+
So consumers get the same versioned slash commands every install. The local `~/.claude/skills/` copy can drift (manual edits, partial deletes); the package source is the canonical version that ships with each nqui release.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design
|
|
3
|
+
description: Build a new product UI screen/page/feature with @nqlib/nqui. Apple-inspired craft, 2+1 elevation rule, full state coverage, restrained motion. Use when the user says "design", "build", "create a page", "make a screen", or any new-UI request. Loads the full nqui skill chain and follows the Agent Build Protocol from start to finish.
|
|
4
|
+
argument-hint: "[what to build, e.g. 'a login form' or 'a sprint dashboard']"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /design — Build with nqui
|
|
8
|
+
|
|
9
|
+
This is the **canonical entry point** for any UI build request when nqui is available. It orchestrates the full skill chain (composition → patterns → recipes → components) and produces production-quality output.
|
|
10
|
+
|
|
11
|
+
## What this skill does
|
|
12
|
+
|
|
13
|
+
When invoked, you build a new UI surface using `@nqlib/nqui` following the **Agent Build Protocol** (10 steps) and the **30-second Linear designer test** before declaring done.
|
|
14
|
+
|
|
15
|
+
The output bar: a senior Linear / Vercel / Stripe / Apple designer reviewing your diff would find **zero** AI-flavored cues (nested cards, "Submit" buttons, missing empty states, decorative shadows, generic copy).
|
|
16
|
+
|
|
17
|
+
## Mandatory protocol
|
|
18
|
+
|
|
19
|
+
### Step 1 — Load the entry contract
|
|
20
|
+
Read `~/.claude/skills/nqui/AGENT_PROMPT.md` if you have not in this conversation. This is the non-negotiable rule set: hard rules, anti-patterns, build flow. Don't skip it.
|
|
21
|
+
|
|
22
|
+
### Step 2 — Load design context
|
|
23
|
+
Check for `.impeccable.md` in the project root.
|
|
24
|
+
- If present: use that brand/voice/aesthetic context.
|
|
25
|
+
- If absent: ASK the user for brand tone, target audience, and any reference apps. Do not infer from the codebase — code tells you what was built, not who it's for.
|
|
26
|
+
|
|
27
|
+
### Step 3 — Understand the user's job (5-word outcome)
|
|
28
|
+
- One sentence: what is the outcome of this screen?
|
|
29
|
+
- One primary action?
|
|
30
|
+
- What can move off-screen (Sheet, route, hover card)?
|
|
31
|
+
|
|
32
|
+
If the user's request is vague ("build me a dashboard"), ask 2-3 clarifying questions before writing code. Generic prompts produce generic output.
|
|
33
|
+
|
|
34
|
+
### Step 4 — Pick a named layout pattern
|
|
35
|
+
Read `~/.claude/skills/nqui/COMPOSITION.md` and match the request to one of the named patterns:
|
|
36
|
+
- App Shell (sidebar + content)
|
|
37
|
+
- Settings page
|
|
38
|
+
- Dashboard (metrics + table/chart)
|
|
39
|
+
- Master-Detail Split (responsive → Sheet below 900px)
|
|
40
|
+
- Wizard / Stepper
|
|
41
|
+
- Command / Search Interface
|
|
42
|
+
- Form Dialog
|
|
43
|
+
|
|
44
|
+
Commit to one. Don't blend.
|
|
45
|
+
|
|
46
|
+
### Step 5 — Look up combos in RECIPES.md
|
|
47
|
+
Read `~/.claude/skills/nqui/RECIPES.md` and grab the specific recipes you need (status pill, bulk action, filter toolbar, the three states, auth recipes 16-22 for login/signup, etc.). Don't reinvent — use the canonical blueprints.
|
|
48
|
+
|
|
49
|
+
### Step 6 — Apply the elevation rule
|
|
50
|
+
Read `~/.claude/skills/nqui/ELEVATION.md`. Cap inline nesting at **2 surfaces**: page (`bg-background`) → card (`bg-muted/40`). Past that, use spacing + uppercase labels + type weight. `bg-popover` + `.nqui-elevated` is for Dialog / Sheet / Popover / DropdownMenu only.
|
|
51
|
+
|
|
52
|
+
### Step 7 — Cover all states
|
|
53
|
+
Read `~/.claude/skills/nqui/STATES.md`. For every interactive surface, design:
|
|
54
|
+
- Lists: idle + loading (Skeleton) + empty (Empty w/ CTA) + error (Alert w/ retry)
|
|
55
|
+
- Forms: idle + focus + filled + submitting + validation-error + server-error + success
|
|
56
|
+
- Buttons: idle + hover + focus + active + disabled + loading
|
|
57
|
+
- Async views: loading + populated + empty + error + refreshing
|
|
58
|
+
|
|
59
|
+
**No blank screens. No happy-path-only views.**
|
|
60
|
+
|
|
61
|
+
### Step 8 — Write the copy properly
|
|
62
|
+
Read `~/.claude/skills/nqui/WRITING.md`. Every button, label, error, empty state, toast must:
|
|
63
|
+
- Specific verb + object ("Send invite", not "Submit")
|
|
64
|
+
- Errors say what + how to fix
|
|
65
|
+
- Sentence case, present tense, no exclamation marks
|
|
66
|
+
- No "Welcome!" / "Awesome!" / "Let's get started!" — these are AI signatures
|
|
67
|
+
- No emoji in UI chrome
|
|
68
|
+
|
|
69
|
+
### Step 9 — Motion only with intent
|
|
70
|
+
Read `~/.claude/skills/nqui/MOTION.md` ONLY if you're adding animation. Default: no motion. When present: 150ms quick / 200ms standard, ease-out for entrances, ease-in for exits. Never bounce/elastic. Respect `prefers-reduced-motion`.
|
|
71
|
+
|
|
72
|
+
### Step 10 — Self-review (the 30-second Linear designer test)
|
|
73
|
+
Before declaring done, walk this checklist:
|
|
74
|
+
1. Hierarchy clear within 2 seconds?
|
|
75
|
+
2. All states designed (not just happy path)?
|
|
76
|
+
3. Copy specific, not generic?
|
|
77
|
+
4. One primary action per surface?
|
|
78
|
+
5. Spacing varied (not monotonous)?
|
|
79
|
+
6. Cards used only for bounded topics?
|
|
80
|
+
7. Focus styles on every interactive element?
|
|
81
|
+
8. No decorative shadows on flat elements?
|
|
82
|
+
9. No nested cards inside cards?
|
|
83
|
+
10. No banned anti-patterns (border-l-4 stripes, gradient text, hero metrics, "Submit" buttons)?
|
|
84
|
+
|
|
85
|
+
**If you can name 2+ failures → fix them BEFORE asking the user.**
|
|
86
|
+
|
|
87
|
+
## Routing reference
|
|
88
|
+
|
|
89
|
+
If uncertain at any step, the doc to load is in `~/.claude/skills/nqui/READ_BUDGET.md`. Don't bulk-read the skills folder.
|
|
90
|
+
|
|
91
|
+
## What you do NOT do in /design
|
|
92
|
+
|
|
93
|
+
- Don't ship a single happy-path view without the other states
|
|
94
|
+
- Don't invent custom CSS where a component exists
|
|
95
|
+
- Don't add transitions "to feel modern" — most state changes should not animate
|
|
96
|
+
- Don't ask "is this OK?" when you can name 2+ failures from the self-review
|
|
97
|
+
- Don't deliver work that would trigger any of the 10 "Linear designer" reactions
|
|
98
|
+
|
|
99
|
+
## What you DO in /design
|
|
100
|
+
|
|
101
|
+
- Cite which named pattern + which recipes you used (helps the user understand the choices)
|
|
102
|
+
- Apply the surface rule strictly (count surfaces before declaring done — never more than 2 inline)
|
|
103
|
+
- Use real-feeling content (not lorem ipsum, not "Card Title 1 / Card Title 2")
|
|
104
|
+
- Test the responsive split if using master-detail (does it switch to Sheet below 900px?)
|
|
105
|
+
- Verify accessibility minimums (focus visible, ARIA on interactive non-buttons, no tooltip-as-label)
|
|
106
|
+
|
|
107
|
+
## The promise
|
|
108
|
+
|
|
109
|
+
Output from `/design` is reliably 8/10 or better. Not 10/10 (that requires design context this skill can't infer alone), but never embarrassing, never AI-flavored, never missing critical states.
|
|
110
|
+
|
|
111
|
+
That's the bar. Hit it on every invocation.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: edit
|
|
3
|
+
description: Refine, fix, or improve existing UI built with @nqlib/nqui. Diagnoses issues against the design rules (elevation, states, writing, motion), then applies the smallest fix that resolves them. Use when the user says "fix this", "improve this", "this looks off", "edit", "refine", or references a specific page/component that needs polish. Different from /design (which builds from scratch) — /edit changes what's already there.
|
|
4
|
+
argument-hint: "[what to fix, e.g. 'the sprint tracker page' or 'this dialog']"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /edit — Refine existing nqui UI
|
|
8
|
+
|
|
9
|
+
This is the **canonical entry point** for fixing or improving UI that's already in the codebase. It runs a diagnostic pass against the nqui design rules, names what's wrong specifically, then applies the smallest fix.
|
|
10
|
+
|
|
11
|
+
Different from `/design`:
|
|
12
|
+
- `/design` = build from scratch (Agent Build Protocol, 10 steps)
|
|
13
|
+
- `/edit` = read what's there, diagnose, fix in place
|
|
14
|
+
|
|
15
|
+
## Mandatory protocol
|
|
16
|
+
|
|
17
|
+
### Step 1 — Load the entry contract (skip if loaded this session)
|
|
18
|
+
Read `~/.claude/skills/nqui/AGENT_PROMPT.md`. The rules you'll judge the existing code against.
|
|
19
|
+
|
|
20
|
+
### Step 2 — Read the target
|
|
21
|
+
- If the user named a file/component: read it.
|
|
22
|
+
- If the user pointed at a page route: find the page file in `src/pages/` (or wherever the project keeps them) and read it.
|
|
23
|
+
- Read enough context to understand the surrounding components/layout, not just the line they're complaining about.
|
|
24
|
+
|
|
25
|
+
### Step 3 — Diagnose against the design rules
|
|
26
|
+
Walk the code mentally against these checks (skip checks the user explicitly excluded):
|
|
27
|
+
|
|
28
|
+
| Rule | What to look for | Doc |
|
|
29
|
+
|------|------------------|-----|
|
|
30
|
+
| **Elevation** | Cards inside cards inside cards? `bg-muted/60` or `bg-muted/70` (third surface)? `shadow-*` on inline cards? | `ELEVATION.md` |
|
|
31
|
+
| **State coverage** | Lists without empty/loading/error? Buttons without loading state? Forms without validation states? | `STATES.md` |
|
|
32
|
+
| **Composition** | More than 3 surfaces on screen? Two competing primary buttons? Toolbar floating on bg-background? | `COMPOSITION.md` |
|
|
33
|
+
| **Writing** | "Submit" / "OK" / "Are you sure?"? Exclamation marks? "Welcome!" / "Awesome!"? Generic errors? | `WRITING.md` |
|
|
34
|
+
| **Motion** | Elastic/bounce easing? Durations > 250ms for routine state changes? Animating layout properties? | `MOTION.md` |
|
|
35
|
+
| **Component selection** | RadioGroup in a toolbar? Dialog for a confirmation? Tooltip as the only label? Sheet for >5 fields? | `COMPONENTS_INDEX.md` |
|
|
36
|
+
| **Anti-patterns** | `border-l-4` colored stripes? Gradient text? Hero metric template? Sparklines as decoration? Glassmorphism on inline cards? | `nqui-composition/SKILL.md` |
|
|
37
|
+
|
|
38
|
+
### Step 4 — Name the findings specifically
|
|
39
|
+
Before fixing anything, tell the user what you found. Use this format:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Findings (in order of severity):
|
|
43
|
+
|
|
44
|
+
🔴 [P0] {issue} — {file:line}. Why it matters: {one sentence}.
|
|
45
|
+
🟠 [P1] {issue} — {file:line}.
|
|
46
|
+
🟡 [P2] {issue} — {file:line}.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
P0 = clear rule violation (banned anti-pattern, missing critical state, broken accessibility).
|
|
50
|
+
P1 = noticeable quality issue (wrong component, lazy copy, monotonous spacing).
|
|
51
|
+
P2 = polish (could be tighter, but not embarrassing).
|
|
52
|
+
|
|
53
|
+
If you find nothing: say so. Don't invent issues to look thorough.
|
|
54
|
+
|
|
55
|
+
### Step 5 — Propose minimum-viable fixes
|
|
56
|
+
For each finding, propose the smallest change that resolves it. Don't refactor surrounding code unless the user asked. Examples:
|
|
57
|
+
|
|
58
|
+
- "Submit" → "Send invite" (change 1 word)
|
|
59
|
+
- Nested Cards → outer Card + `<section>` with uppercase label (replace 1 Card pair)
|
|
60
|
+
- Missing empty state → add Empty component with one CTA (insert 8 lines)
|
|
61
|
+
- Bouncy easing → swap to `ease-out` (change 1 className)
|
|
62
|
+
|
|
63
|
+
The goal is **edit, not rewrite**. Honor the existing structure; remove violations.
|
|
64
|
+
|
|
65
|
+
### Step 6 — Apply the fixes
|
|
66
|
+
Apply your proposed fixes via Edit tool. After each change, briefly state what changed and why ("Swapped to `Sheet` because the form has 8 fields — Dialog would feel trapped").
|
|
67
|
+
|
|
68
|
+
### Step 7 — Self-review the edit
|
|
69
|
+
Walk the same diagnostic again post-edit. If new issues surfaced from your fix, name them — don't pretend the edit is done if it created new violations.
|
|
70
|
+
|
|
71
|
+
## What you do NOT do in /edit
|
|
72
|
+
|
|
73
|
+
- Don't rebuild the page. The user said "edit," not "redesign."
|
|
74
|
+
- Don't bundle unrelated improvements ("while I was here I also refactored X"). Stay scoped.
|
|
75
|
+
- Don't fix one violation by introducing another (replacing nested Cards with a 4-color gradient header — that's just a different anti-pattern).
|
|
76
|
+
- Don't skip the diagnostic step ("I'll just fix obvious stuff"). The diagnostic IS the value — naming what's wrong is half the deliverable.
|
|
77
|
+
- Don't ask the user to verify before applying fixes if the fixes are P0 (clearly wrong) and small (< 20 lines). Just do them.
|
|
78
|
+
|
|
79
|
+
## What you DO in /edit
|
|
80
|
+
|
|
81
|
+
- Cite the rule each fix maps to ("removed the third nested Card per ELEVATION.md")
|
|
82
|
+
- Note what you intentionally left alone ("the Card-of-cards on line 80 looks suspect but I'm leaving it — it's a Sortable wrapper, not a hierarchy choice")
|
|
83
|
+
- When the user's complaint is vague ("it looks off"), do the diagnostic first, then offer 2-3 specific options with trade-offs
|
|
84
|
+
|
|
85
|
+
## Triggering examples
|
|
86
|
+
|
|
87
|
+
- "the sprint tracker feels heavy" → /edit, diagnose elevation + spacing + density
|
|
88
|
+
- "this dialog has too many fields" → /edit, suggest Sheet conversion + state coverage check
|
|
89
|
+
- "fix the empty state on the inbox page" → /edit, audit the empty state against STATES.md + WRITING.md
|
|
90
|
+
- "this button feels wrong" → /edit, check component selection + label specificity + state coverage
|
|
91
|
+
- "make this look more Apple" → /edit, diagnose against `nqui-composition/SKILL.md` clarity/deference/depth principles
|
|
92
|
+
|
|
93
|
+
## The promise
|
|
94
|
+
|
|
95
|
+
`/edit` finds the real issues, not imaginary ones. Names them with rules and locations. Fixes them minimally. Leaves the working parts alone.
|
|
96
|
+
|
|
97
|
+
That's the bar.
|