astryx-dracula 0.6.2 → 0.7.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.
Files changed (59) hide show
  1. package/BRAND.md +54 -3
  2. package/README.md +11 -4
  3. package/USAGE.md +1 -1
  4. package/astryx-dracula.d.ts +2 -2
  5. package/astryx-dracula.js +26 -4
  6. package/astryx-dracula.variants.d.ts +2 -2
  7. package/package.json +11 -11
  8. package/shared/auth-chrome-config.ts +0 -4
  9. package/shared/auth-chrome.tsx +2 -3
  10. package/shared/chaptered-doc-config.ts +3 -2
  11. package/shared/order-desk.tsx +52 -54
  12. package/shared/revenue-chart.tsx +1 -1
  13. package/shared/scene-castle.tsx +21 -13
  14. package/templates/ai-chat-landing.tsx +6 -9
  15. package/templates/ai-chat.tsx +5 -1
  16. package/templates/centered-hero.tsx +2 -2
  17. package/templates/classic-gallery.tsx +5 -0
  18. package/templates/contact-form.tsx +9 -0
  19. package/templates/dashboard-portfolio.tsx +17 -4
  20. package/templates/dashboard.tsx +5 -5
  21. package/templates/detail-page.tsx +20 -28
  22. package/templates/documentation-design.tsx +3 -0
  23. package/templates/documentation-technical.tsx +8 -5
  24. package/templates/documentation.tsx +13 -6
  25. package/templates/form-two-column.tsx +11 -2
  26. package/templates/form-wizard.template.mjs +15 -0
  27. package/templates/form-wizard.tsx +878 -0
  28. package/templates/gallery-hero.tsx +10 -4
  29. package/templates/ide.tsx +177 -96
  30. package/templates/incident-console.tsx +15 -5
  31. package/templates/kanban-board.tsx +46 -4
  32. package/templates/library.tsx +12 -3
  33. package/templates/login-split.tsx +3 -3
  34. package/templates/login-sso.tsx +9 -17
  35. package/templates/messaging-shell.tsx +109 -46
  36. package/templates/mixed-gallery.tsx +6 -0
  37. package/templates/payment-form.tsx +29 -28
  38. package/templates/product-detail.tsx +12 -3
  39. package/templates/product-gallery.tsx +8 -3
  40. package/templates/product-tour.tsx +11 -7
  41. package/templates/settings-dialog.tsx +5 -0
  42. package/templates/settings-sidebar.tsx +603 -584
  43. package/templates/settings.tsx +5 -2
  44. package/templates/shell-nav.tsx +23 -3
  45. package/templates/shell-side-nav.tsx +94 -81
  46. package/templates/shell-top-nav.tsx +98 -86
  47. package/templates/table-grouped.tsx +146 -132
  48. package/templates/{table-page-shoe-store-heatmap.template.mjs → table-page-shoe-store.template.mjs} +1 -1
  49. package/templates/{table-page-shoe-store-heatmap.tsx → table-page-shoe-store.tsx} +1 -1
  50. package/templates/table-page.tsx +8 -0
  51. package/templates/table-tree.template.mjs +16 -0
  52. package/templates/table-tree.tsx +707 -0
  53. package/templates/tech-report.tsx +8 -5
  54. package/templates/theme-showcase.tsx +18 -11
  55. package/templates/work-item-detail.template.mjs +8 -0
  56. package/templates/work-item-detail.tsx +1049 -0
  57. package/theme.css +38 -3
  58. package/theme.css.d.ts +2 -2
  59. package/tokens.css +41 -4
package/BRAND.md CHANGED
@@ -9,7 +9,7 @@ Source of truth is `astryx-theme.ts`. Do not add hexes; `bun run audit` enforces
9
9
  |---|---|---|
10
10
  | Background | #282A36 | page bg |
11
11
  | Current line | #6272A4 | line highlight, subtle borders (doubles as comment)[^current-line] |
12
- | Selection | #44475A | selected rows, quiet surfaces |
12
+ | Selection | #44475A | selected rows and text selection (a semantic, never an elevation step) |
13
13
  | Background Light | #343746 | cards, surfaces |
14
14
  | Background Lighter | #424450 | popovers, floating elements |
15
15
  | Background Dark | #21222C | shadows, on-fill text |
@@ -29,6 +29,56 @@ Source of truth is `astryx-theme.ts`. Do not add hexes; `bun run audit` enforces
29
29
 
30
30
  [^current-line]: Upstream pins both Current Line and Comment to #6272A4. This kit follows suit for line affordances, including scrollbar thumbs in `tokens.css`. Do not "correct" one of them to #44475A.
31
31
 
32
+ ### The spec's four palettes
33
+
34
+ `draculatheme.com/spec` publishes four palettes, and the theme draws on all of
35
+ them. Read the spec before calling a hex off-brand — `#DE5735` looks like a
36
+ stray Stone colour until you find **Functional Red** in the UI section.
37
+
38
+ | Palette | Hexes |
39
+ |---|---|
40
+ | Syntax | the twelve above |
41
+ | ANSI | `#21222C` black, the eight base hues, and the brights `#6272A4`, `#FF6E6E`, `#69FF94`, `#FFFFA5`, `#D6ACFF`, `#FF92DF`, `#A4FFFF`, `#FFFFFF` |
42
+ | UI | `#343746` light, `#424450` lighter, `#21222C` dark, `#191A21` darker |
43
+ | Functional | `#DE5735`, `#A39514`, `#089108`, `#0081D6`, `#815CD6` — interactive elements, borders and indicators, never status and never editor text |
44
+
45
+ The categorical chart set draws its teal and indigo from the ANSI brights
46
+ (`#A4FFFF`, `#D6ACFF`), which is why those two are spec values rather than
47
+ inventions.
48
+
49
+ ### Derived values
50
+
51
+ `scripts/check.ts` allows every hex in `@layer astryx-theme` only if it is in one
52
+ of those four palettes or named in `DERIVED_HEXES` with a reason. Anything else
53
+ fails the build and prints the hex. That gate exists because core's builder
54
+ inlines a token it derives instead of aliasing it, so any colour role the theme
55
+ does not pin keeps Stone's value — 0.7.10 shipped `#E9AF08` for warning text and
56
+ `#F77D77` for error, and the light-dark symmetry gate caught the first only
57
+ because its two branches happened to differ.
58
+
59
+ Six values are ours, each traceable to a spec value:
60
+
61
+ - `#9AA1BC` — secondary text, border-emphasized and the gray family. Comment is
62
+ the obvious pick and fails the 4.5 floor on every surface.
63
+ - `#B0B3C4` — chart axis and caption text, one step above secondary so an axis
64
+ reads over a grid.
65
+ - `#8C939B` — chart neutral, deliberately not a Dracula hue so a neutral series
66
+ is not read as one.
67
+ - `#FFD5CC` — the pale inverted error surface behind the inverted Toast.
68
+ - `#FF7575` — error **text**: the ANSI Bright Red lifted 5% toward Foreground.
69
+ Bright Red alone is 5.23:1 on the body but 4.33:1 on a surface, just under the
70
+ 4.5 floor the spec itself sets; the lift reads 5.46 and 4.52.
71
+
72
+ Pure black `#000000` is also allowed, for `on-light` and the overlay hover and
73
+ press steps; the spec never asks for black text, but a black overlay is how a
74
+ scrollbar and a pressed state darken.
75
+
76
+ Two legacy text tokens (`#8288A6`, `#4C5067`) are allowed too, contrast-gated at
77
+ their own floors rather than an AA threshold.
78
+
79
+ To add a colour, pin it in `astryx-theme.ts` against a spec value, or put it in
80
+ `DERIVED_HEXES` with the reason it exists.
81
+
32
82
  ## Semantic map (glimpse)
33
83
 
34
84
  - Purple links/titles unvisited. Visited falls back to text-base, hover goes primary + underline. Users learn purple means tappable title.
@@ -59,8 +109,9 @@ Each status scope redefines its muted token to the categorical tint
59
109
  (`--color-background-<hue>`, 10% accent wash) with a semantic accent border.
60
110
  Text on fills is always `#21222C`; spec never uses raw black. Links render accent
61
111
  with underline and resolve to foreground on hover. Focus and interactive edges
62
- use accent, not Functional Purple: `--color-functional-*` is pinned for spec parity
63
- (token compat — never encode status in these) and no shipped component consumes it.
112
+ use accent, not Functional Purple: `--color-functional-*` carries the spec's UI
113
+ Functional palette for interactive elements, borders and indicators — never a
114
+ status channel, and no shipped component consumes it today.
64
115
 
65
116
  ## Interactions
66
117
 
package/README.md CHANGED
@@ -11,12 +11,14 @@ Live showcase: https://yuzu-octopus.github.io/astryx-dracula/
11
11
  ## Features
12
12
 
13
13
  - Dracula tokens pinned in `astryx-theme.ts` (`grep -c "pin(" astryx-theme.ts` pins) (the public props the prebuilt `theme.css` ships, `grep -c "^ --" theme.css`, plus 3 private `--_*-radius` aliases; `tokens.css` mirrors the theme's colour, spacing, radius, type and motion tokens as plain `:root` custom properties, unlayered so it beats core's layers, `grep -c "^\s*--" tokens.css` vars), dark-only. No gate asserts these counts — `bun run theme:check` asserts freshness, `bun run audit` asserts palette purity and contrast.
14
+ - The spec's four palettes (Syntax, ANSI, UI, Functional), with the ANSI bright row
15
+ available as `--dracula-ansi-bright-*` for emphasis and for a chart series past the base eight
14
16
  - Syntax theme plus chart series and sequential ramps
15
17
  - Prebuilt CSS plus runtime theme injection, or runtime injection alone for prototyping
16
18
  - WCAG contrast gates enforced in CI via `bun run audit`
17
19
  - JetBrains Mono fonts and Lucide icons, one runtime dependency (`lucide-react`)
18
20
  - Agent skill, `llms.txt`, and copy-paste quickstart for AI-assisted adoption
19
- - Around 45 themed pages in `templates/`, published as an Astryx integration pack and viewable live at `/astryx-dracula/#/templates`
21
+ - 48 themed pages in `templates/`, published as an Astryx integration pack and viewable live at `/astryx-dracula/#/templates`
20
22
 
21
23
  ## Installation
22
24
 
@@ -52,7 +54,7 @@ theme.css prebuilt output of bun run theme:build
52
54
  tokens.css plain-CSS :root mirror for any stack
53
55
  icons.tsx Lucide icon registry (runtime `lucide-react` dependency)
54
56
  fonts/ JetBrains Mono woff2 files
55
- templates/ around 45 themed pages, each with an XLE structure header
57
+ templates/ 48 themed pages, each with an XLE structure header
56
58
  shared/ reusable modules templates compose (23 files)
57
59
  scripts/check.ts palette, contrast, packaging, and XLE-header gates
58
60
  demo/ showcase source, deployed to GitHub Pages
@@ -68,11 +70,16 @@ demo/ showcase source, deployed to GitHub Pages
68
70
  | `bun run build` | Typecheck plus showcase build |
69
71
  | `bun run fmt` | Format the source (`bun fmt`; style in `bunfig.toml`) |
70
72
  | `bun run fmt:check` | Fail if the source is not formatted |
71
- | `bun run lint` | Biome, lint only — the formatter is `bun fmt` |
73
+ | `bun run lint` | `biome check`: lint plus import order. Formatting is `bun fmt` (Biome's formatter is off) |
74
+ | `bun audit` | Bun's builtin vulnerability scan — not this repo's `audit` script |
72
75
  | `bunx react-doctor@latest` | Component health scan (currently 100/100) |
73
76
 
77
+ `bun audit` is a bun builtin and `bun run audit` is the kit gate. Bun's own
78
+ command runs first, so the two never collide, but a bare `bun audit` checks
79
+ dependencies and says nothing about the palette.
80
+
74
81
  ## Attribution & License
75
82
 
76
- - **Astryx Design System**: Created by the Astryx team at Meta Platforms, Inc. under the [MIT License](https://github.com/facebook/astryx/blob/main/LICENSE). The template files in `templates/` (around 45) are derived from Astryx's open source page templates and adapted for the Dracula brand (pure Dracula spec retokening, recharts replaced with zero-dependency inline SVGs, heroicons mapped to Lucide, XLE/XLO structural expression headers, and responsive layout hardening).
83
+ - **Astryx Design System**: Created by the Astryx team at Meta Platforms, Inc. under the [MIT License](https://github.com/facebook/astryx/blob/main/LICENSE). The template files in `templates/` (46) are derived from Astryx's open source page templates and adapted for the Dracula brand (pure Dracula spec retokening, recharts replaced with zero-dependency inline SVGs, heroicons mapped to Lucide, XLE/XLO structural expression headers, and responsive layout hardening).
77
84
  - **Dracula Theme**: Color palette and specification by [Zeno Rocha and the Dracula Theme community](https://draculatheme.com).
78
85
  - **Package & Additions**: Released under the [MIT License](LICENSE).
package/USAGE.md CHANGED
@@ -73,7 +73,7 @@ Then use `var(--color-primary)`, `var(--dracula-purple)`, `var(--space-gap)`.
73
73
 
74
74
  ## Templates
75
75
 
76
- Around 45 themed pages ship in `templates/`, each with a `<id>.template.mjs` spec, published
76
+ 48 themed pages ship in `templates/`, each with a `<id>.template.mjs` spec, published
77
77
  as an Astryx integration pack (`astryx.integration.mjs`). List this package in your
78
78
  `astryx.config`, then scaffold any of them:
79
79
 
@@ -2,8 +2,8 @@
2
2
  * @generated by `astryx theme build` — do not edit manually.
3
3
  * Source: astryx-theme.ts
4
4
  * Command: astryx theme build astryx-theme.ts --out theme.css
5
- * CLI: @astryxdesign/cli@0.7.2
6
- * Core: @astryxdesign/core@0.7.2
5
+ * CLI: @astryxdesign/cli@0.7.10
6
+ * Core: @astryxdesign/core@0.7.10
7
7
  */
8
8
 
9
9
  /// <reference path="./astryx-dracula.variants.d.ts" />
package/astryx-dracula.js CHANGED
@@ -2,8 +2,8 @@
2
2
  * @generated by `astryx theme build` — do not edit manually.
3
3
  * Source: astryx-theme.ts
4
4
  * Command: astryx theme build astryx-theme.ts --out theme.css
5
- * CLI: @astryxdesign/cli@0.7.2
6
- * Core: @astryxdesign/core@0.7.2
5
+ * CLI: @astryxdesign/cli@0.7.10
6
+ * Core: @astryxdesign/core@0.7.10
7
7
  */
8
8
 
9
9
  import { draculaIconRegistry } from "./icons.mjs";
@@ -81,6 +81,18 @@ export const astryxDraculaTheme = {
81
81
  "--duration-slow-min": "730ms",
82
82
  "--duration-slow": "975ms",
83
83
  "--duration-slow-max": "1300ms",
84
+ "--_astryx-pre-tempo-duration-fast-min": "130ms",
85
+ "--_astryx-pre-tempo-duration-fast": "175ms",
86
+ "--_astryx-pre-tempo-duration-fast-max": "235ms",
87
+ "--_astryx-pre-tempo-duration-medium-min": "310ms",
88
+ "--_astryx-pre-tempo-duration-medium": "410ms",
89
+ "--_astryx-pre-tempo-duration-medium-max": "545ms",
90
+ "--_astryx-pre-tempo-duration-slow-min": "730ms",
91
+ "--_astryx-pre-tempo-duration-slow": "975ms",
92
+ "--_astryx-pre-tempo-duration-slow-max": "1300ms",
93
+ "--_astryx-motion-tempo": "1",
94
+ "--ease-entrance": "cubic-bezier(0.24, 1, 0.4, 1)",
95
+ "--ease-move": "cubic-bezier(0.24, 1, 0.4, 1)",
84
96
  "--font-family-body": "\"JetBrains Mono\", monospace",
85
97
  "--font-family-heading": "\"JetBrains Mono\", monospace",
86
98
  "--font-family-code": "\"JetBrains Mono\", monospace",
@@ -146,7 +158,7 @@ export const astryxDraculaTheme = {
146
158
  "--color-background-surface": "light-dark(#343746, #343746)",
147
159
  "--color-background-card": "light-dark(#343746, #343746)",
148
160
  "--color-background-popover": "light-dark(#424450, #424450)",
149
- "--color-background-muted": "light-dark(#44475A, #44475A)",
161
+ "--color-background-muted": "light-dark(#21222C, #21222C)",
150
162
  "--color-overlay": "light-dark(#191A21CC, #191A21CC)",
151
163
  "--color-overlay-hover": "light-dark(#0000001F, #0000001F)",
152
164
  "--color-overlay-pressed": "light-dark(#00000033, #00000033)",
@@ -161,6 +173,14 @@ export const astryxDraculaTheme = {
161
173
  "--color-text-secondary": "light-dark(#9AA1BC, #9AA1BC)",
162
174
  "--color-text-disabled": "light-dark(#6272A4, #6272A4)",
163
175
  "--color-text-accent": "light-dark(#BD93F9, #BD93F9)",
176
+ "--color-text-info": "light-dark(#8BE9FD, #8BE9FD)",
177
+ "--color-text-success": "light-dark(#50FA7B, #50FA7B)",
178
+ "--color-text-warning": "light-dark(#F1FA8C, #F1FA8C)",
179
+ "--color-text-error": "light-dark(#FF7575, #FF7575)",
180
+ "--color-on-info-muted": "light-dark(#8BE9FD, #8BE9FD)",
181
+ "--color-on-success-muted": "light-dark(#50FA7B, #50FA7B)",
182
+ "--color-on-warning-muted": "light-dark(#F1FA8C, #F1FA8C)",
183
+ "--color-on-error-muted": "light-dark(#FF7575, #FF7575)",
164
184
  "--color-icon-primary": "light-dark(#F8F8F2, #F8F8F2)",
165
185
  "--color-icon-secondary": "light-dark(#9AA1BC, #9AA1BC)",
166
186
  "--color-icon-disabled": "light-dark(#6272A4, #6272A4)",
@@ -689,7 +709,9 @@ export const astryxDraculaTheme = {
689
709
  "fast": 175,
690
710
  "medium": 410,
691
711
  "slow": 975,
692
- "ratio": 0.75
712
+ "ratio": 0.75,
713
+ "tempo": 1,
714
+ "character": "standard"
693
715
  }
694
716
  },
695
717
  icons: draculaIconRegistry,
@@ -2,8 +2,8 @@
2
2
  * @generated by `astryx theme build` — do not edit manually.
3
3
  * Source: astryx-theme.ts
4
4
  * Command: astryx theme build astryx-theme.ts --out theme.css
5
- * CLI: @astryxdesign/cli@0.7.2
6
- * Core: @astryxdesign/core@0.7.2
5
+ * CLI: @astryxdesign/cli@0.7.10
6
+ * Core: @astryxdesign/core@0.7.10
7
7
  */
8
8
 
9
9
  // Generated by astryx theme build
package/package.json CHANGED
@@ -4,20 +4,20 @@
4
4
  "url": "https://github.com/yuzu-octopus/astryx-dracula/issues"
5
5
  },
6
6
  "dependencies": {
7
- "lucide-react": "^1.61.0"
7
+ "lucide-react": "1.70.0"
8
8
  },
9
9
  "description": "Pure Dracula brand theme for Astryx React sites. Dark-only tokens, syntax highlighting, and chart colors with prebuilt CSS.",
10
10
  "devDependencies": {
11
- "@astryxdesign/cli": "0.7.2",
12
- "@astryxdesign/core": "0.7.2",
13
- "@biomejs/biome": "2.6.2",
14
- "@types/bun": "^1.4.3",
15
- "@types/node": "^26.6.5",
11
+ "@astryxdesign/cli": "0.7.10",
12
+ "@astryxdesign/core": "0.7.10",
13
+ "@biomejs/biome": "2.6.6",
14
+ "@types/bun": "1.4.4",
15
+ "@types/node": "26.6.6",
16
16
  "@types/react": "^19.3.0",
17
17
  "@types/react-dom": "^19.3.0",
18
- "@vitejs/plugin-react": "^6.1.2",
18
+ "@vitejs/plugin-react": "6.2.1",
19
19
  "typescript": "^7.0.2",
20
- "vite": "^8.3.5"
20
+ "vite": "8.3.6"
21
21
  },
22
22
  "exports": {
23
23
  "./shared/auth-chrome": "./shared/auth-chrome.tsx",
@@ -110,10 +110,10 @@
110
110
  "theme:check": "bun run icons:check && bunx astryx theme build astryx-theme.ts -o theme.css --icons-specifier ./icons.mjs --check --json | bun -e 'const r = JSON.parse(await Bun.stdin.text()); if (r.type !== \"theme.build.check\") { console.error(\"theme:check: unexpected CLI response \" + JSON.stringify(r).slice(0, 200)); process.exit(1); } const stale = r.data.stale; for (const s of stale) console.error(\"stale: \" + s.path + \" (\" + s.reason + \")\"); if (stale.length) process.exit(1); const entry = await import(\"./astryx-dracula.js\"); if (!entry.astryxDraculaTheme || !entry.draculaIconRegistry) { console.error(\"astryx-dracula.js is missing its theme or icon registry export\"); process.exit(1); } console.log(\"theme outputs up to date; astryx-dracula.js (hand-maintained) loads and exports\");' && bun run entry:check",
111
111
  "icons:check": "NODE_ENV=production bun build icons.tsx --outfile .tmp-icons.mjs --format esm --external react --external lucide-react; s=$?; cmp -s .tmp-icons.mjs icons.mjs; c=$?; rm -f .tmp-icons.mjs; [ $s -eq 0 ] && [ $c -eq 0 ] || { echo 'stale: icons.mjs (rerun bun run icons:build)'; exit 1; }",
112
112
  "entry:check": "node -e \"const{pathToFileURL}=require('url');const{resolve}=require('path');import(pathToFileURL(resolve('astryx-dracula.js')).href).then(m=>{if(!m.astryxDraculaTheme||!m.draculaIconRegistry){console.error('astryx-dracula.js is missing its theme or icon registry export');process.exit(1)}console.log('astryx-dracula.js loads under plain Node ESM')}).catch(e=>{console.error('astryx-dracula.js is not importable by Node ESM: '+e.message);process.exit(1)})\"",
113
- "prepublishOnly": "bun run theme:check && bun run icons:check && bun run entry:check && bun run audit && bun check && bun run fmt:check && bun run lint && bunx react-doctor@latest --score",
113
+ "prepublishOnly": "bun audit && bun run theme:check && bun run icons:check && bun run entry:check && bun run audit && bun check && bun run fmt:check && bun run lint && bunx react-doctor@latest --score",
114
114
  "fmt": "bun fmt '**/*.{ts,tsx,mjs,js,jsx,json,css}'",
115
115
  "fmt:check": "bun fmt --check '**/*.{ts,tsx,mjs,js,jsx,json,css}'",
116
- "lint": "biome lint"
116
+ "lint": "biome check"
117
117
  },
118
118
  "sideEffects": [
119
119
  "**/*.css"
@@ -123,5 +123,5 @@
123
123
  "@astryxdesign/cli"
124
124
  ],
125
125
  "type": "module",
126
- "version": "0.6.2"
126
+ "version": "0.7.0"
127
127
  }
@@ -12,7 +12,3 @@ export const authPageStyle: CSSProperties = {
12
12
  };
13
13
  // Cap the column at 400px but let it shrink to fit narrow screens.
14
14
  export const authContentStyle: CSSProperties = { width: "100%", maxWidth: 400 };
15
- // WCAG 1.3.5 autocomplete: TextInput forwards unknown props to <input> but its
16
- // prop type omits input-only attributes, so spread through a widened record.
17
- export const inputAutoComplete = (value: string) =>
18
- ({ autoComplete: value }) as Record<string, string>;
@@ -8,7 +8,6 @@ import { VStack } from "@astryxdesign/core/Layout";
8
8
  import { Link } from "@astryxdesign/core/Link";
9
9
  import { Heading, Text } from "@astryxdesign/core/Text";
10
10
  import { TextInput } from "@astryxdesign/core/TextInput";
11
- import { inputAutoComplete } from "astryx-dracula/shared/auth-chrome-config";
12
11
  import {
13
12
  AUTH_BRAND_NAME,
14
13
  AUTH_EMAIL_PLACEHOLDER,
@@ -116,7 +115,7 @@ export function AuthLoginFields({
116
115
  onChange={onEmailChange}
117
116
  placeholder={AUTH_EMAIL_PLACEHOLDER}
118
117
  type="email"
119
- {...inputAutoComplete("email")}
118
+ autoComplete="email"
120
119
  size="lg"
121
120
  onEnter={onSubmit}
122
121
  status={error ? { type: "error", message: error } : undefined}
@@ -129,7 +128,7 @@ export function AuthLoginFields({
129
128
  onChange={onPasswordChange}
130
129
  placeholder={AUTH_PASSWORD_PLACEHOLDER}
131
130
  type="password"
132
- {...inputAutoComplete("current-password")}
131
+ autoComplete="current-password"
133
132
  size="lg"
134
133
  onEnter={onSubmit}
135
134
  status={error ? { type: "error", message: error } : undefined}
@@ -57,11 +57,12 @@ export const outlinePanel: CSSProperties = {
57
57
  top: "var(--spacing-6)",
58
58
  alignSelf: "start",
59
59
  // No paddingBlockStart. This object is applied via `style=` to the outline
60
- // LayoutPanels (chaptered-doc.tsx:316, documentation-design.tsx:592,
61
- // documentation-technical.tsx:82), and an inline style prop BEATS the stylex
60
+ // LayoutPanels, and an inline style prop BEATS the stylex
62
61
  // class -- so this 8px silently overrode the panel's own 16px block padding
63
62
  // at the top edge only, leaving every "On this page" rail in the kit
64
63
  // asymmetric by 8px. The panel already owns its block padding.
64
+ // Call sites are named by file, not by line: line numbers in a comment go
65
+ // stale the first time anyone edits above them, and these three did.
65
66
  };
66
67
 
67
68
  // Derived once per page so the Outline receives a stable array identity
@@ -1,5 +1,5 @@
1
1
  // Parametric order desk shared by the table-page-chart (Matcha Bar) and
2
- // table-page-shoe-store-heatmap (Midnight Kicks) templates. The two pages are
2
+ // table-page-shoe-store (Midnight Kicks) templates. The two pages are
3
3
  // one archetype — revenue line over the order log — differing only in catalogue,
4
4
  // rows, and chart scale, so the frame/columns live here once.
5
5
 
@@ -37,7 +37,7 @@ import type { CSSProperties } from "react";
37
37
  // minHeight appears in none of them.
38
38
  //
39
39
  // The wrapper is shared by all three callers (table-page-chart.tsx:410,
40
- // table-page-shoe-store-heatmap.tsx:759, and this module's own default), so
40
+ // table-page-shoe-store.tsx:759, and this module's own default), so
41
41
  // anchoring it here covers them together. Anchoring inside each caller instead
42
42
  // would put a 100dvh box inside this one and overflow by the header height.
43
43
  //
@@ -200,63 +200,61 @@ export function OrderDesk({
200
200
  gridTicks: number[];
201
201
  }) {
202
202
  return (
203
- // The anchor lives on a page-owned wrapper, never on the Layout. A bare
204
- // `height="fill"` resolves against `min-height: 100%`, which collapses when
205
- // the host is content-sized — and templates ship standalone, so that is the
206
- // common case. Putting the height on the Layout instead would trade
207
- // a soft failure (the document scrolls) for a hard one (content clipped,
208
- // because `LayoutContent` is `overflow: auto` with a definite height).
209
- <div style={pageStyle}>
210
- <Layout
211
- height="fill"
212
- header={
213
- <LayoutHeader hasDivider padding={6}>
214
- <HStack gap={2} vAlign="center">
215
- <StackItem size="fill">
216
- {/* maxLines={1}, not wrap="wrap": at 390 the 35px display-2 title
203
+ // Templates ship standalone, so the page owns the definite ancestor for
204
+ // `height="fill"` — 100dvh on the Layout itself, the same anchor every
205
+ // other page uses. It used to sit on a wrapper <div>, which contradicted
206
+ // the no-raw-div rule for a reason that does not hold: the height lands on
207
+ // the same box either way.
208
+ <Layout
209
+ style={pageStyle}
210
+ height="fill"
211
+ header={
212
+ <LayoutHeader hasDivider padding={6}>
213
+ <HStack gap={2} vAlign="center">
214
+ <StackItem size="fill">
215
+ {/* maxLines={1}, not wrap="wrap": at 390 the 35px display-2 title
217
216
  wraps 2-3 lines in a 342px header (measured: 136px tall for
218
217
  "Midnight Kicks", 180px for an 18-char title). Wrapping the
219
218
  row measured IDENTICAL header heights in both configurations
220
219
  — the controls already fit, the title is what is tall. And
221
220
  maxLines wires core's useTruncation, so the full value stays
222
221
  reachable in a Tooltip (SC 1.4.10, "truncation must reveal"). */}
223
- <Heading level={1} type="display-2" maxLines={1}>
224
- {title}
225
- </Heading>
226
- </StackItem>
227
- <IconButton
228
- label="Filter"
229
- icon={<Icon icon={Filter} size="sm" />}
230
- variant="ghost"
231
- tooltip="Filter"
232
- />
233
- <IconButton
234
- label="Export"
235
- icon={<Icon icon={Download} size="sm" />}
236
- variant="ghost"
237
- tooltip="Export"
238
- />
239
- <Button label="New order" variant="primary" icon={<Icon icon={Plus} size="sm" />} />
240
- </HStack>
241
- </LayoutHeader>
242
- }
243
- content={
244
- <LayoutContent padding={3}>
245
- <VStack gap={4}>
246
- <RevenueChart data={revenueData} chartMax={chartMax} gridTicks={gridTicks} />
247
- <Table<OrderDeskRow>
248
- data={orders}
249
- columns={columnsFor(selfHash, products)}
250
- idKey="id"
251
- density="balanced"
252
- dividers="rows"
253
- textOverflow="truncate"
254
- hasHover
255
- />
256
- </VStack>
257
- </LayoutContent>
258
- }
259
- />
260
- </div>
222
+ <Heading level={1} type="display-2" maxLines={1}>
223
+ {title}
224
+ </Heading>
225
+ </StackItem>
226
+ <IconButton
227
+ label="Filter"
228
+ icon={<Icon icon={Filter} size="sm" />}
229
+ variant="ghost"
230
+ tooltip="Filter"
231
+ />
232
+ <IconButton
233
+ label="Export"
234
+ icon={<Icon icon={Download} size="sm" />}
235
+ variant="ghost"
236
+ tooltip="Export"
237
+ />
238
+ <Button label="New order" variant="primary" icon={<Icon icon={Plus} size="sm" />} />
239
+ </HStack>
240
+ </LayoutHeader>
241
+ }
242
+ content={
243
+ <LayoutContent padding={3}>
244
+ <VStack gap={4}>
245
+ <RevenueChart data={revenueData} chartMax={chartMax} gridTicks={gridTicks} />
246
+ <Table<OrderDeskRow>
247
+ data={orders}
248
+ columns={columnsFor(selfHash, products)}
249
+ idKey="id"
250
+ density="balanced"
251
+ dividers="rows"
252
+ textOverflow="truncate"
253
+ hasHover
254
+ />
255
+ </VStack>
256
+ </LayoutContent>
257
+ }
258
+ />
261
259
  );
262
260
  }
@@ -169,7 +169,7 @@ export function RevenueChart({
169
169
  y1={y}
170
170
  x2={CHART_W - CHART_PAD_RIGHT}
171
171
  y2={y}
172
- stroke="var(--color-separator)"
172
+ stroke="var(--color-graph-gridlines)"
173
173
  strokeDasharray={tick === 0 ? undefined : "3 3"}
174
174
  opacity={tick === 0 ? 1 : 0.5}
175
175
  />
@@ -23,13 +23,13 @@ const CARD_HILLS = {
23
23
 
24
24
  const TALL_HILL = "M0 380 Q200 320 400 370 V500 H0 Z" as const;
25
25
 
26
- function WideCastle() {
26
+ function WideCastle({ label }: { label?: string }) {
27
27
  return (
28
28
  <svg
29
29
  style={galleryImage}
30
30
  viewBox="0 0 800 450"
31
31
  role="img"
32
- aria-label="Moonlit castle on a hill in Dracula theme colors"
32
+ aria-label={label ?? "Moonlit castle on a hill in Dracula theme colors"}
33
33
  >
34
34
  <rect width="800" height="450" fill="var(--dracula-bg-dark)" />
35
35
  {/* Stars */}
@@ -95,13 +95,13 @@ function WideCastle() {
95
95
  );
96
96
  }
97
97
 
98
- function CardCastle() {
98
+ function CardCastle({ label }: { label?: string }) {
99
99
  return (
100
100
  <svg
101
101
  style={galleryImage}
102
102
  viewBox="0 0 400 500"
103
103
  role="img"
104
- aria-label="Moonlit castle on midnight hills"
104
+ aria-label={label ?? "Moonlit castle on midnight hills"}
105
105
  >
106
106
  <rect width="400" height="500" fill="var(--dracula-bg-dark)" />
107
107
  {/* Stars */}
@@ -142,13 +142,13 @@ function CardCastle() {
142
142
  );
143
143
  }
144
144
 
145
- function TallPines() {
145
+ function TallPines({ label }: { label?: string }) {
146
146
  return (
147
147
  <svg
148
148
  style={galleryImage}
149
149
  viewBox="0 0 400 500"
150
150
  role="img"
151
- aria-label="Harvest moon over midnight pines"
151
+ aria-label={label ?? "Harvest moon over midnight pines"}
152
152
  >
153
153
  <rect width="400" height="500" fill="var(--dracula-bg-dark)" />
154
154
  {/* Stars */}
@@ -186,14 +186,14 @@ function TallPines() {
186
186
  );
187
187
  }
188
188
 
189
- function Rooftops() {
189
+ function Rooftops({ label }: { label?: string }) {
190
190
  return (
191
191
  <svg
192
192
  style={galleryImage}
193
193
  viewBox="0 0 400 300"
194
194
  preserveAspectRatio="xMidYMid slice"
195
195
  role="img"
196
- aria-label="Harvest moon over the castle rooftops"
196
+ aria-label={label ?? "Harvest moon over the castle rooftops"}
197
197
  >
198
198
  <rect width="400" height="300" fill="var(--dracula-bg)" />
199
199
  <g fill="var(--dracula-fg)">
@@ -229,9 +229,17 @@ function Rooftops() {
229
229
  );
230
230
  }
231
231
 
232
- export function SceneCastle({ variant }: { variant: SceneCastleVariant }) {
233
- if (variant === "wide") return <WideCastle />;
234
- if (variant === "card") return <CardCastle />;
235
- if (variant === "rooftops") return <Rooftops />;
236
- return <TallPines />;
232
+ export function SceneCastle({
233
+ variant,
234
+ label,
235
+ }: {
236
+ variant: SceneCastleVariant;
237
+ // Callers that keep their own alt text pass it here, so the accessible name
238
+ // cannot drift from the data the way a hardcoded label lets it.
239
+ label?: string;
240
+ }) {
241
+ if (variant === "wide") return <WideCastle label={label} />;
242
+ if (variant === "card") return <CardCastle label={label} />;
243
+ if (variant === "rooftops") return <Rooftops label={label} />;
244
+ return <TallPines label={label} />;
237
245
  }
@@ -295,15 +295,10 @@ const INITIAL_ATTACHMENTS: Attachment[] = [
295
295
  "dracula_spec.pdf",
296
296
  ].map((name) => ({ id: crypto.randomUUID(), name }));
297
297
 
298
- // The composer's imperative insert methods mutate the DOM without emitting a
299
- // change, so dispatch an input event to sync its value and clear the placeholder.
300
- const syncComposerValue = () => {
301
- document.activeElement?.dispatchEvent(new Event("input", { bubbles: true }));
302
- };
303
-
304
298
  // One inserter for every composer write. `insertText` replaces the selection
305
299
  // (suggestion prompts); `insertToken` inserts a chip at the cursor without
306
- // touching the selection (mentions, mode tokens).
300
+ // touching the selection (mentions, mode tokens). Both emit a change in 0.7.10,
301
+ // so no input-event sync is needed after either call.
307
302
  function insertIntoComposer(
308
303
  input: ChatComposerInputHandle | null,
309
304
  write: { text: string } | { token: ChatComposerToken },
@@ -327,7 +322,6 @@ function insertIntoComposer(
327
322
  } else {
328
323
  input.insertToken(write.token);
329
324
  }
330
- syncComposerValue();
331
325
  }
332
326
 
333
327
  // Main component
@@ -535,7 +529,10 @@ export default function AiChatLanding() {
535
529
  <ClickableCard
536
530
  key={suggestion.heading}
537
531
  label={suggestion.heading}
538
- variant="muted"
532
+ // Raised tappable card. `muted` is a recessed well since
533
+ // the 2026-11 token change, so it would read as a hole
534
+ // in the page rather than a prompt you can press.
535
+ variant="default"
539
536
  padding={4}
540
537
  onClick={() => {
541
538
  applySuggestion(suggestion.prompt);
@@ -231,7 +231,9 @@ function ArtifactCard({ onOpen }: { onOpen: () => void }) {
231
231
  <ClickableCard
232
232
  label={`Open ${ARTIFACT_TITLE}`}
233
233
  onClick={onOpen}
234
- variant="muted"
234
+ // A raised tappable chip, not a well: `muted` is #21222C since the
235
+ // 2026-11 token change and would sink this card below the chat body.
236
+ variant="default"
235
237
  padding={3}
236
238
  maxWidth={360}
237
239
  style={artifactCard}
@@ -374,6 +376,7 @@ export default function AiChat() {
374
376
  </ChatMessageBubble>
375
377
  <ChatToolCalls
376
378
  defaultIsExpanded
379
+ label="Explored the auth files"
377
380
  calls={[
378
381
  {
379
382
  name: "read",
@@ -480,6 +483,7 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
480
483
  <ChatMessage sender="assistant" avatar={<Avatar name="Agent" size="md" />}>
481
484
  <ChatToolCalls
482
485
  defaultIsExpanded
486
+ label="Added and ran the refresh test"
483
487
  calls={[
484
488
  {
485
489
  name: "read",
@@ -2,7 +2,7 @@
2
2
  // XLE (canonical structure of the tree below; the CLI had no XLE validator
3
3
  // after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
4
4
  // human drift check — update it with the file, never alone):
5
- // L[h=auto] > LC[p=6] > V[g=10] > ((V[g=6 a=center] > (V[g=3 a=center] > Hd"Small joys for creatures of the night"[level=1 t=display-2] + Tx"Sometimes all it takes is one small ritual to turn the whole night around."[t=body] + Tx"astryx-dracula v0.6.2"[t=supporting]) + (H[j=center g=3] > B.primary"Embrace the night" + B.secondary"Read the lore")) + (S.transparent[p=0] > AR[ratio=16/9] > Scene{SceneCastle}))
5
+ // L[h=auto] > LC[p=6] > V[g=10] > ((V[g=6 a=center] > (V[g=3 a=center] > Hd"Small joys for creatures of the night"[level=1 t=display-2] + Tx"Sometimes all it takes is one small ritual to turn the whole night around."[t=body] + Tx"astryx-dracula v0.7.0"[t=supporting]) + (H[j=center g=3] > B.primary"Embrace the night" + B.secondary"Read the lore")) + (S.transparent[p=0] > AR[ratio=16/9] > Scene{SceneCastle}))
6
6
 
7
7
  import { AspectRatio } from "@astryxdesign/core/AspectRatio";
8
8
  import { Button } from "@astryxdesign/core/Button";
@@ -41,7 +41,7 @@ export default function CenteredHero() {
41
41
  Sometimes all it takes is one small ritual to turn the whole night around.
42
42
  </Text>
43
43
  <Text type="supporting" color="secondary" justify="center" hasTabularNumbers>
44
- astryx-dracula v0.6.2
44
+ astryx-dracula v0.7.0
45
45
  </Text>
46
46
  </VStack>
47
47
  <HStack gap={3} wrap="wrap" justify="center">