@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.
Files changed (32) hide show
  1. package/dist/{command-palette-DhoWGyk_.js → command-palette-CHUiGh5m.js} +2 -2
  2. package/dist/{command-palette-D24rOeE6.cjs → command-palette-DsvP2QNP.cjs} +2 -2
  3. package/dist/command.cjs.js +1 -1
  4. package/dist/command.es.js +1 -1
  5. package/dist/components/custom/table-of-contents.d.ts +2 -2
  6. package/dist/components/custom/table-of-contents.d.ts.map +1 -1
  7. package/dist/components/ui/combobox.d.ts +5 -3
  8. package/dist/components/ui/combobox.d.ts.map +1 -1
  9. package/dist/{debug-panel-BjfW-YVo.js → debug-panel-BcYzsTp2.js} +1 -1
  10. package/dist/{debug-panel-CpqsKuxd.cjs → debug-panel-mwtujy5J.cjs} +1 -1
  11. package/dist/debug.cjs.js +1 -1
  12. package/dist/debug.es.js +1 -1
  13. package/dist/{input-shared-CDgy_NdJ.cjs → input-shared-C9Try5fg.cjs} +1 -1
  14. package/dist/{input-shared-NnOiyHpu.js → input-shared-DXf3Edqt.js} +1 -1
  15. package/dist/lib/floating-surface.d.ts +6 -2
  16. package/dist/lib/floating-surface.d.ts.map +1 -1
  17. package/dist/nqui.cjs.js +10 -10
  18. package/dist/nqui.es.js +17 -16
  19. package/dist/styles.css +9 -9
  20. package/docs/components/README.md +2 -0
  21. package/docs/components/nqui-combobox.md +15 -2
  22. package/docs/components/nqui-command-palette.md +7 -0
  23. package/docs/components/nqui-command.md +41 -0
  24. package/docs/nqui-skills/ELEVATION.md +33 -6
  25. package/docs/nqui-skills/RECIPES.md +85 -0
  26. package/docs/nqui-skills/_claude-commands/README.md +50 -0
  27. package/docs/nqui-skills/_claude-commands/design/SKILL.md +111 -0
  28. package/docs/nqui-skills/_claude-commands/edit/SKILL.md +97 -0
  29. package/docs/nqui-skills/nqui-components/SKILL.md +1 -0
  30. package/package.json +1 -1
  31. package/scripts/build-styles.js +4 -3
  32. 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-NnOiyHpu.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-BjfW-YVo.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-BjfW-YVo.js";
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-DhoWGyk_.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-DhoWGyk_.js";
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
- ...r
5308
+ renderItem: r,
5309
+ ...n
5309
5310
  }) {
5310
- const { items: n, filterItems: a, search: i, open: l, setListboxIdAria: c } = Se(), { staticNodes: d, renderFn: f } = Mm(t), u = s.useMemo(() => !f || !n ? null : n.filter(a).map((v) => f(v)), [f, n, a, i]), p = s.useRef(null);
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 (!l) {
5313
- c(void 0);
5313
+ if (!c) {
5314
+ d(void 0);
5314
5315
  return;
5315
5316
  }
5316
5317
  requestAnimationFrame(() => {
5317
- const v = p.current?.id;
5318
- v && c(v);
5318
+ const b = v.current?.id;
5319
+ b && d(b);
5319
5320
  });
5320
- }, [l, c, n, u, d]), /* @__PURE__ */ M(
5321
+ }, [c, d, a, m, f]), /* @__PURE__ */ M(
5321
5322
  $c,
5322
5323
  {
5323
- ref: p,
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
- ...r,
5331
+ ...n,
5331
5332
  children: [
5332
- d,
5333
- u
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-[inset_0_3px_5px_rgba(0,0,0,0.125)] active:scale-95");
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 (defined in /* ============================================
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
- | Row highlight / keyboard selection looks wrong | Styles targeting only `**data-[selected=true]**`; use `**aria-selected:**` or boolean `**data-selected**` variants. |
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 &lt; 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
- ## When single-surface (no nesting) is the right call
112
+ ## Mode 3: Single-surface refinement (not just an "exception")
113
113
 
114
- Even more refined than 2-surface alternation:
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
- 1. **Focus-mode editors** — iA Writer, Apple Notes detail, Things 3 task detail. No nesting at all.
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
- For these, ONE surface + generous spacing + careful typography is the most refined option. Use it when the content's job is sustained attention rather than navigation between sub-regions.
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.