@trading-game/design-intelligence-layer 1.0.7 → 1.1.0

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/AGENTS.md CHANGED
@@ -14,7 +14,7 @@ Rules of engagement for AI agents building with `@trading-game/design-intelligen
14
14
  8. **Dark is not a mirror.** Never build a light-theme construction and assume it inverts. If a token pair doesn't exist for what you need, stop and ask — don't improvise per-theme values in a component.
15
15
  9. **Typography via type classes.** Component text uses its `.type-<component>-*` class (backed by private `--<component>-*` tokens). No `text-sm`, no `text-[13px]`, no `font-bold`.
16
16
  10. **`.type-*` classes are plain CSS, not utilities**: variants can't gate them (`data-[x]:type-foo` does nothing — pick the class in JSX or write a CSS data-attribute rule), and they beat layered `font-*` utilities.
17
- 11. **Radius encodes element class** — buttons/chips `rounded-full`, icon buttons circles, cards/dialogs `rounded-2xl`, menus `rounded-lg`, menu rows `rounded-md`, inputs `rounded-xs` (deliberate, don't "fix" it).
17
+ 11. **Radius encodes element class** — buttons/chips `rounded-full`, icon buttons circles, cards/dialogs **and Item** `rounded-2xl`, menus `rounded-lg`, menu rows `rounded-md`, inputs `rounded-xs` (deliberate, don't "fix" it). `rounded-md` means a row *inside* a menu or list panel (menu items, Tabs triggers, Sidebar rows, Numpad keys). An `Item` is a content shell in its own right, so it takes the card corner — don't "correct" it back to 8px.
18
18
  12. **Sizes sit on the 32/40/48 rail**, default `md` (40px). Button padding-x = height/2 − 4, gap 8, and padding never changes when icons are hidden.
19
19
  13. **Disabled is chosen per variant and per theme.** There is no single rule; each cell is a deliberate decision and changing one must not disturb the others. The default construction is the token trio — `text-disabled-default` for ink, `background-disabled-default` for the surface, `border-disabled-default` for the edge — where the background token is a **glaze**, laid on `background-image` (`[background-image:linear-gradient(var(--background-disabled-default),var(--background-disabled-default))]`) so the variant's own fill stays underneath and each variant keeps its identity. Fill-less variants (`tertiary`, ghost rows, menu items) take the ink only. On-brand surfaces stay on the static white ladder with `text-on-brand-disabled-default`. **Sanctioned `opacity` exceptions**, by explicit decision (2026-09-09): Button `primary` in both themes and Button `secondary` in light take `opacity-(--opacity-disabled)` (24%); Button `secondary` in dark keeps full opacity and takes a black-24% glaze instead; Button `frosted` fades to `opacity-(--opacity-disabled-frosted)` (40%) in both themes, keeping its brand ink — 24% left almost nothing visible on a fill that is already only a 10–24% brand tint, so the strength is per-variant and lives in its own token. Button's `loading` also stays on `opacity-24`. Be aware of what element `opacity` costs: it dims a fill and the ink on top of it by the same factor, so their relationship collapses — the light disabled `primary` label lands at 1.14:1 on its own washed fill and is not readable. WCAG 1.4.3 exempts inactive controls, so this is a legibility trade accepted on purpose, not a compliance break. Do not extend `opacity` to other components without the same explicit decision.
20
20
  14. **Focus** = `focus-visible:ring-[3px] focus-visible:ring-ring-focus-strong` (controls) or `ring-ring-focus-soft` (text fields). Error ring = `ring-ring-error-soft`. Every colour must be a plain named token — no alpha-modified constructions, no colour primitives outside the sanctioned glass-ladder set; `npm run check:tokens` enforces both and diffs the Colors page against styles.css.
@@ -48,7 +48,7 @@ Docs: `docs/components/<kebab-name>.md` for every entry.
48
48
  | Alert Dialog | Blocking confirm/cancel decisions (destructive actions) |
49
49
  | Aspect Ratio | Fixed-ratio media containers |
50
50
  | Avatar | User images with fallback initials, groups, count overflow |
51
- | Badge | Small status/category markers (solid, tint, outline; status = 600 fills) |
51
+ | Badge | Small status/category markers (`solid` / `tint` families, meaning as the suffix; status = 600 fills). `size="count"` is the 18px numeric disc — circle at one digit, pill beyond; caller owns zero-hiding, the 99+ cap and any knockout ring |
52
52
  | Banner | Editorial gradient promo panel with art, copy, white pill CTA |
53
53
  | Bottom Navigation | Mobile bottom nav, max 4 destinations (3 is the norm) — chrome frost bar, liquid selection bubble; the phone counterpart of Sidebar |
54
54
  | Breadcrumb | Hierarchical location trail |
@@ -69,7 +69,7 @@ Docs: `docs/components/<kebab-name>.md` for every entry.
69
69
  | Input | Single-line text field; sizes sm/md/lg; `on-brand` variant |
70
70
  | Input Group | Things inside a field: prefix/suffix text, icons, pill buttons |
71
71
  | Input OTP | Segmented one-time-code entry |
72
- | Item | List rows with media/title/description/actions (default, outline, muted) |
72
+ | Item | List rows with media/title/description/actions. `default` transparent, `outline` hairline only, `fill` hairline + solid surface (the ledger pattern — use it on the page canvas, where `outline` lets the canvas through), `muted` secondary surface |
73
73
  | Label | Form label (14/medium); colour inherits so Field error ink flows through |
74
74
  | Link | Inline/standalone links — quiet at rest, underline + darker ink on hover; `tone="on-brand"` |
75
75
  | Menubar | Horizontal app menu bar (menu family) |
package/dist/index.cjs CHANGED
@@ -1269,9 +1269,11 @@ var badgeVariants = (0, import_class_variance_authority4.cva)(
1269
1269
  "solid-fail": "bg-background-error-solid text-text-on-status-static",
1270
1270
  "solid-warning": "bg-background-warning-solid text-text-on-status-static",
1271
1271
  // Tint — the Section Message surface tokens, coloured foreground.
1272
- // Brand tint = the static brand-container recipe (layered value,
1273
- // so it paints via `background`, not a bg-* utility).
1274
- tint: "[background:var(--background-brand-container)] border-transparent text-text-brand-container-static",
1272
+ // Brand tint rides `brand-selected`, the theme-aware selection
1273
+ // family, for the same reason Calendar's range band does: the
1274
+ // static `brand-container` is one value in both themes and sits
1275
+ // near-white on a dark surface.
1276
+ tint: "bg-background-brand-selected border-transparent text-text-brand-selected",
1275
1277
  "tint-demo": "bg-background-demo-default border-transparent text-text-demo-default",
1276
1278
  "tint-neutral": "bg-background-secondary-surface border-transparent text-text-prominent-default",
1277
1279
  "tint-success": "bg-background-success-default border-transparent text-text-success-default",
@@ -1282,7 +1284,18 @@ var badgeVariants = (0, import_class_variance_authority4.cva)(
1282
1284
  xs: "type-badge-xs h-5 px-2 py-0.5",
1283
1285
  sm: "type-badge-sm h-6 px-2.5 py-0.5",
1284
1286
  md: "type-badge-md h-8 px-3 py-1",
1285
- lg: "type-badge-lg h-10 px-4 py-1.5"
1287
+ lg: "type-badge-lg h-10 px-4 py-1.5",
1288
+ /* `count` is a shape, not a step on the xs/sm/md/lg rail: an
1289
+ 18px disc that grows into a pill as digits are added. The
1290
+ padding is deliberately tight (`px-1`): one digit then
1291
+ measures under the 18px min-width, so min-width wins and the
1292
+ disc is a true circle. Widen the padding and a single digit
1293
+ overshoots 18px, turning it back into an oval. `tabular-nums` keeps every digit
1294
+ the same width, so a ticking count only resizes when the
1295
+ digit count changes, not on every update. Callers that pin
1296
+ it over other chrome add their own knockout ring — Badge
1297
+ cannot know what sits behind it. */
1298
+ count: "type-badge-xs min-w-[1.125rem] h-[1.125rem] px-1 tabular-nums"
1286
1299
  }
1287
1300
  },
1288
1301
  defaultVariants: {
@@ -4604,12 +4617,21 @@ function ItemSeparator(_a) {
4604
4617
  );
4605
4618
  }
4606
4619
  var itemVariants = (0, import_class_variance_authority10.cva)(
4607
- "group/item type-item-description flex flex-wrap items-center rounded-md border border-transparent transition-colors duration-instant outline-none focus-visible:border-ring-focus-default focus-visible:ring-[3px] focus-visible:ring-ring-focus-strong [a]:transition-colors [a]:hover:bg-background-hover-default",
4620
+ "group/item type-item-description flex flex-wrap items-center rounded-2xl border border-transparent transition-colors duration-instant outline-none focus-visible:border-ring-focus-default focus-visible:ring-[3px] focus-visible:ring-ring-focus-strong [a]:transition-colors [a]:hover:bg-background-hover-default",
4608
4621
  {
4609
4622
  variants: {
4610
4623
  variant: {
4611
4624
  default: "bg-transparent",
4612
4625
  outline: "border-border-default-default text-text-prominent-default hover:bg-background-hover-default",
4626
+ /* `fill` is `outline` plus a solid surface: a bordered card that reads
4627
+ as its own plane on the page canvas, the wallet-ledger pattern.
4628
+ Its hover steps primary -> secondary surface like the default Chip,
4629
+ because the shared `background-hover-default` glaze is translucent —
4630
+ laid over a filled row it would let the canvas show through instead
4631
+ of darkening the fill. The `[a]:` rule repeats for the same reason:
4632
+ the base link hover is that same glaze and outranks a plain
4633
+ `hover:` on an anchor. */
4634
+ fill: "bg-background-primary-surface border-border-default-default text-text-prominent-default hover:bg-background-secondary-surface [a]:hover:bg-background-secondary-surface",
4613
4635
  muted: "bg-background-secondary-surface"
4614
4636
  },
4615
4637
  size: {
@@ -8168,10 +8190,11 @@ function HistoryButton({
8168
8190
  return /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("div", { className: "relative inline-flex", children: [
8169
8191
  /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(NavigationButton, { type: "button", size: "md", "aria-label": ariaLabel, onClick, children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_lucide_react30.History, { className: "size-5" }) }),
8170
8192
  show && /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
8171
- "span",
8193
+ Badge,
8172
8194
  {
8173
8195
  "aria-hidden": true,
8174
- className: "pointer-events-none absolute -top-0.5 -right-0.5 min-w-[1.125rem] h-[1.125rem] px-1 rounded-full bg-primary text-on-primary text-[10px] leading-none font-semibold tabular-nums inline-flex items-center justify-center ring-2 ring-prominent",
8196
+ size: "count",
8197
+ className: "pointer-events-none absolute -top-0.5 -right-0.5 ring-2 ring-background-primary-canvas",
8175
8198
  children: label
8176
8199
  }
8177
8200
  )