@archetypeai/ds-cli 0.14.0 → 0.16.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/README.md +1 -1
- package/commands/create.js +13 -1
- package/commands/init.js +13 -1
- package/files/AGENTS.md +25 -2
- package/files/CLAUDE.md +25 -2
- package/files/ds-manifest.json +104 -10
- package/lib/add-ds-lib-tokens.js +9 -1
- package/lib/add-ds-ui-svelte.js +9 -1
- package/lib/install-fonts-local.js +54 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ The configuration is two files: the instruction file (`CLAUDE.md`/`AGENTS.md`
|
|
|
138
138
|
|
|
139
139
|
## Fonts
|
|
140
140
|
|
|
141
|
-
The `--fonts <path>` flag expects a local folder containing all
|
|
141
|
+
The `--fonts <path>` flag expects a local folder containing all 13 PP Neue Montreal `.woff2` files (8 Sans + 5 Mono). The CLI validates the folder and copies the files to `static/fonts/`, where the `@font-face` declarations in `@archetypeai/ds-lib-tokens/fonts.css` expect them. With `--no-fonts`, the theme falls back to system fonts.
|
|
142
142
|
|
|
143
143
|
## Registries
|
|
144
144
|
|
package/commands/create.js
CHANGED
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
createDemoPage,
|
|
13
13
|
installAgentConfig
|
|
14
14
|
} from '../lib/scaffold-ds-svelte-project.js';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
validateFontsPath,
|
|
17
|
+
installLocalFonts,
|
|
18
|
+
preloadLinkTags
|
|
19
|
+
} from '../lib/install-fonts-local.js';
|
|
16
20
|
import { isInteractive, DEFAULTS } from '../lib/is-interactive.js';
|
|
17
21
|
|
|
18
22
|
// parse flags
|
|
@@ -250,6 +254,14 @@ export async function create(args) {
|
|
|
250
254
|
const fontResult = installLocalFonts(fontsPath, projectPath);
|
|
251
255
|
if (fontResult.success) {
|
|
252
256
|
s.stop(`${fontResult.fileCount} font files installed`);
|
|
257
|
+
if (fontResult.preload.patched) {
|
|
258
|
+
p.log.info('Preloading Book, Medium and Mono Book in src/app.html');
|
|
259
|
+
} else {
|
|
260
|
+
p.log.warn(
|
|
261
|
+
`Could not add font preloads (${fontResult.preload.reason}). Add these to your <head>:\n` +
|
|
262
|
+
preloadLinkTags().join('\n')
|
|
263
|
+
);
|
|
264
|
+
}
|
|
253
265
|
} else {
|
|
254
266
|
s.stop('Failed to install fonts');
|
|
255
267
|
p.log.warn(fontResult.error);
|
package/commands/init.js
CHANGED
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
installLinting,
|
|
12
12
|
installAgentConfig
|
|
13
13
|
} from '../lib/scaffold-ds-svelte-project.js';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
validateFontsPath,
|
|
16
|
+
installLocalFonts,
|
|
17
|
+
preloadLinkTags
|
|
18
|
+
} from '../lib/install-fonts-local.js';
|
|
15
19
|
import { isInteractive, DEFAULTS } from '../lib/is-interactive.js';
|
|
16
20
|
|
|
17
21
|
// parse flags
|
|
@@ -210,6 +214,14 @@ export async function init(args) {
|
|
|
210
214
|
const fontResult = installLocalFonts(fontsPath, projectPath);
|
|
211
215
|
if (fontResult.success) {
|
|
212
216
|
s.stop(`${fontResult.fileCount} font files installed`);
|
|
217
|
+
if (fontResult.preload.patched) {
|
|
218
|
+
p.log.info('Preloading Book, Medium and Mono Book in src/app.html');
|
|
219
|
+
} else {
|
|
220
|
+
p.log.warn(
|
|
221
|
+
`Could not add font preloads (${fontResult.preload.reason}). Add these to your <head>:\n` +
|
|
222
|
+
preloadLinkTags().join('\n')
|
|
223
|
+
);
|
|
224
|
+
}
|
|
213
225
|
} else {
|
|
214
226
|
s.stop('Failed to install fonts');
|
|
215
227
|
p.log.warn(fontResult.error);
|
package/files/AGENTS.md
CHANGED
|
@@ -81,6 +81,25 @@ use them decoratively.
|
|
|
81
81
|
|
|
82
82
|
## Typography
|
|
83
83
|
|
|
84
|
+
The theme pairs every `text-*` step with its own line-height and letter-spacing
|
|
85
|
+
(`--text-sm--line-height`, `--text-sm--letter-spacing`, …), so `text-sm` alone
|
|
86
|
+
is on-brand. Never add `leading-*` or `tracking-*` next to a `text-*` utility —
|
|
87
|
+
the ramp already supplies both. `text-2xs` (10px) is the floor for dense
|
|
88
|
+
metadata; never use arbitrary sizes like `text-[11px]`.
|
|
89
|
+
|
|
90
|
+
The theme's base layer styles only authored-content elements: `body` (14px
|
|
91
|
+
sans), `h1`–`h6` (30px → 14px, `h4`–`h6` muted, `h1` title-cased), `p`,
|
|
92
|
+
`small`, `strong`, `blockquote`, plus `cursor-pointer` on `a`. It deliberately
|
|
93
|
+
does **not** style `span`, `label`, `em`, `code` or `pre` — and gives `a` no
|
|
94
|
+
typography either — they inherit from their parent, so set the size once on the
|
|
95
|
+
parent instead of on every inline child (`code`/`pre` get the mono family from
|
|
96
|
+
Tailwind preflight and stay the same size as their text). Bare links are
|
|
97
|
+
visually flat until you style them, but every `a` shows the pointer cursor even
|
|
98
|
+
without an `href`; use the `Label` primitive for labels.
|
|
99
|
+
`h1` is `capitalize` by default (use `normal-case` for ids or sentence-case
|
|
100
|
+
copy); uppercase belongs to `Label` variants and `typography="mono"`, never to
|
|
101
|
+
a heading.
|
|
102
|
+
|
|
84
103
|
Default to sans for all text. `font-mono` is reserved for exactly two cases:
|
|
85
104
|
|
|
86
105
|
- Primitives that already use it in the design system source of truth (their
|
|
@@ -110,8 +129,9 @@ darkMode.toggle(); // switch
|
|
|
110
129
|
darkMode.current; // reactive read
|
|
111
130
|
```
|
|
112
131
|
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
`CodeBlock` needs nothing here: it highlights with both themes at once (shiki
|
|
133
|
+
emits the dark colours as `--shiki-dark` custom props) and flips them in CSS, so
|
|
134
|
+
theme switches need no `{#key}` wrapper and no re-highlight.
|
|
115
135
|
|
|
116
136
|
## Page shell
|
|
117
137
|
|
|
@@ -173,6 +193,9 @@ Compose project-specific components from package primitives in
|
|
|
173
193
|
`sideBarSelection`, `lensTray*`, `backToDashboard`) exist in the package
|
|
174
194
|
types but are internal to the Archetype AI console product — never use them
|
|
175
195
|
- Keep the `@source` directives and the CSS import order exactly as above
|
|
196
|
+
- Never add global element rules (`span`, `a`, `label`, `p`, `h1`…) in app CSS,
|
|
197
|
+
and never pair `leading-*`/`tracking-*` with a `text-*` utility — the theme's
|
|
198
|
+
type scale and base layer own both; size a parent, not its inline children
|
|
176
199
|
- Never style scrollbars in app code — no `::-webkit-scrollbar` rules, no
|
|
177
200
|
`scrollbar-color`/`scrollbar-width`. The theme sets both, and setting
|
|
178
201
|
`scrollbar-*` locally also silently disables the webkit pseudo-elements on
|
package/files/CLAUDE.md
CHANGED
|
@@ -81,6 +81,25 @@ use them decoratively.
|
|
|
81
81
|
|
|
82
82
|
## Typography
|
|
83
83
|
|
|
84
|
+
The theme pairs every `text-*` step with its own line-height and letter-spacing
|
|
85
|
+
(`--text-sm--line-height`, `--text-sm--letter-spacing`, …), so `text-sm` alone
|
|
86
|
+
is on-brand. Never add `leading-*` or `tracking-*` next to a `text-*` utility —
|
|
87
|
+
the ramp already supplies both. `text-2xs` (10px) is the floor for dense
|
|
88
|
+
metadata; never use arbitrary sizes like `text-[11px]`.
|
|
89
|
+
|
|
90
|
+
The theme's base layer styles only authored-content elements: `body` (14px
|
|
91
|
+
sans), `h1`–`h6` (30px → 14px, `h4`–`h6` muted, `h1` title-cased), `p`,
|
|
92
|
+
`small`, `strong`, `blockquote`, plus `cursor-pointer` on `a`. It deliberately
|
|
93
|
+
does **not** style `span`, `label`, `em`, `code` or `pre` — and gives `a` no
|
|
94
|
+
typography either — they inherit from their parent, so set the size once on the
|
|
95
|
+
parent instead of on every inline child (`code`/`pre` get the mono family from
|
|
96
|
+
Tailwind preflight and stay the same size as their text). Bare links are
|
|
97
|
+
visually flat until you style them, but every `a` shows the pointer cursor even
|
|
98
|
+
without an `href`; use the `Label` primitive for labels.
|
|
99
|
+
`h1` is `capitalize` by default (use `normal-case` for ids or sentence-case
|
|
100
|
+
copy); uppercase belongs to `Label` variants and `typography="mono"`, never to
|
|
101
|
+
a heading.
|
|
102
|
+
|
|
84
103
|
Default to sans for all text. `font-mono` is reserved for exactly two cases:
|
|
85
104
|
|
|
86
105
|
- Primitives that already use it in the design system source of truth (their
|
|
@@ -110,8 +129,9 @@ darkMode.toggle(); // switch
|
|
|
110
129
|
darkMode.current; // reactive read
|
|
111
130
|
```
|
|
112
131
|
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
`CodeBlock` needs nothing here: it highlights with both themes at once (shiki
|
|
133
|
+
emits the dark colours as `--shiki-dark` custom props) and flips them in CSS, so
|
|
134
|
+
theme switches need no `{#key}` wrapper and no re-highlight.
|
|
115
135
|
|
|
116
136
|
## Page shell
|
|
117
137
|
|
|
@@ -173,6 +193,9 @@ Compose project-specific components from package primitives in
|
|
|
173
193
|
`sideBarSelection`, `lensTray*`, `backToDashboard`) exist in the package
|
|
174
194
|
types but are internal to the Archetype AI console product — never use them
|
|
175
195
|
- Keep the `@source` directives and the CSS import order exactly as above
|
|
196
|
+
- Never add global element rules (`span`, `a`, `label`, `p`, `h1`…) in app CSS,
|
|
197
|
+
and never pair `leading-*`/`tracking-*` with a `text-*` utility — the theme's
|
|
198
|
+
type scale and base layer own both; size a parent, not its inline children
|
|
176
199
|
- Never style scrollbars in app code — no `::-webkit-scrollbar` rules, no
|
|
177
200
|
`scrollbar-color`/`scrollbar-width`. The theme sets both, and setting
|
|
178
201
|
`scrollbar-*` locally also silently disables the webkit pseudo-elements on
|
package/files/ds-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archetypeai-design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "alert",
|
|
@@ -245,8 +245,8 @@
|
|
|
245
245
|
"source": "https://design-system-console.archetypeai.workers.dev/r/codeblock.json",
|
|
246
246
|
"description": "Syntax-highlighted code display (CodeBlock, shiki; yaml/json/python/shell) and editable code field with JSON-schema validation for YAML (CodeBlockEditor).",
|
|
247
247
|
"usage": [
|
|
248
|
-
"`<CodeBlock code language=\"yaml\" />` for read-only code (border built in). `language` accepts `\"yaml\" | \"json\" | \"python\" | \"shell\"` — use `\"shell\"` for bash/sh/curl snippets. Theme switches are CSS-only — no {#key} wrapper needed. Grows to content by default
|
|
249
|
-
"Config editors: `<CodeBlockEditor bind:value bind:valid bind:validating bind:schemaUnavailable schema={jsonSchema} placeholder showDownload downloadBaseName disabled />` + a status badge row driven by the bindables (validating → 'Checking…' spinner; !valid → 'Invalid'; valid+schemaUnavailable → 'YAML parses'; valid → 'Valid YAML'). Grows to content (floor via `minHeight`, default 10rem); cap
|
|
248
|
+
"`<CodeBlock code language=\"yaml\" />` for read-only code (border built in). `language` accepts `\"yaml\" | \"json\" | \"python\" | \"shell\"` — use `\"shell\"` for bash/sh/curl snippets. Theme switches are CSS-only — no {#key} wrapper needed. Grows to content by default and never clips: whenever its height is constrained — by a `max-h-*` class, by a flex/grid parent like `Dialog.Body`, or by the `maxHeight`/`scroll` props — the internal scroller shows a scrollbar instead. To cap it yourself pass `maxHeight=\"20rem\"` (any CSS length) or the `scroll` shorthand (=600px); neither is needed just to avoid clipping. `showLineNumbers={false}` hides the gutter; `wrap` wraps long lines instead of scrolling horizontally.",
|
|
249
|
+
"Config editors: `<CodeBlockEditor bind:value bind:valid bind:validating bind:schemaUnavailable schema={jsonSchema} placeholder showDownload downloadBaseName disabled />` + a status badge row driven by the bindables (validating → 'Checking…' spinner; !valid → 'Invalid'; valid+schemaUnavailable → 'YAML parses'; valid → 'Valid YAML'). Grows to content (floor via `minHeight`, default 10rem) and scrolls rather than clips whenever its height is constrained; cap it yourself via `maxHeight` (any length) or `scroll` (=600px). Visually identical to CodeBlock. `language` (same union, default `\"yaml\"`) switches highlighting; live validation runs only for `\"yaml\"` — for other languages `schema` is ignored and `valid` stays true. The download button's extension/MIME follows the language (.yaml/.json/.py/.sh)."
|
|
250
250
|
]
|
|
251
251
|
},
|
|
252
252
|
{
|
|
@@ -357,7 +357,8 @@
|
|
|
357
357
|
"usage": [
|
|
358
358
|
"Trigger via child snippet spread onto a Button (profile menu: `variant=\"accent\"` avatar + chevron, rounded-full).",
|
|
359
359
|
"Styled triggers: `DropdownMenu.Trigger variant=\"filter\"` for toolbar/list filter dropdowns; `variant=\"input\"` for form-field-styled dropdowns in pickers.",
|
|
360
|
-
"`DropdownMenu.Content align=\"end\" class=\"min-w-64\"`; items `justify=\"between\"` with a trailing icon; `closeOnSelect={false}` for in-menu toggles (dark mode); Separator before Sign Out."
|
|
360
|
+
"`DropdownMenu.Content align=\"end\" class=\"min-w-64\"`; items `justify=\"between\"` with a trailing icon; `closeOnSelect={false}` for in-menu toggles (dark mode); Separator before Sign Out.",
|
|
361
|
+
"Section headings: `DropdownMenu.Label` for a standalone heading, `DropdownMenu.GroupHeading` inside a `Group`. Both render in the console's section-header treatment by default (10px mono, uppercase, muted, more space above than below so the heading binds to the rows it introduces), so a heading recedes rather than competing with its items. Set no type or colour classes on them; if a Label is carrying a row of content rather than a heading, override with `class`."
|
|
361
362
|
],
|
|
362
363
|
"variants": {
|
|
363
364
|
"dropdownMenuTriggerVariants": {
|
|
@@ -754,11 +755,17 @@
|
|
|
754
755
|
"source": "https://design-system-labs.archetypeai.workers.dev/r/logo.json",
|
|
755
756
|
"description": "Archetype AI brand logo mark.",
|
|
756
757
|
"usage": [
|
|
757
|
-
"Headers: `<Logo class=\"h-6 w-auto\" />` next to the app title; sizes sm | default | lg."
|
|
758
|
+
"Headers: `<Logo class=\"h-6 w-auto\" />` next to the app title; sizes sm | default | lg.",
|
|
759
|
+
"Compact lockup: `variant=\"compact\"` is the stacked social/avatar artwork (~1.6:1 instead of the wordmark's ~5.4:1). Use it where a horizontal wordmark will not fit — square tiles, avatars, narrow sidebars, splash marks. Defaults to `variant=\"default\"`.",
|
|
760
|
+
"Sizing is shared: both variants honour size sm | default | lg (h-4 | h-6 | h-8) and take their width from the artwork aspect, so compact renders far narrower at the same height. Prefer default or lg for compact."
|
|
758
761
|
],
|
|
759
762
|
"variants": {
|
|
760
763
|
"logoVariants": {
|
|
761
764
|
"axes": {
|
|
765
|
+
"variant": [
|
|
766
|
+
"default",
|
|
767
|
+
"compact"
|
|
768
|
+
],
|
|
762
769
|
"size": [
|
|
763
770
|
"sm",
|
|
764
771
|
"default",
|
|
@@ -766,6 +773,7 @@
|
|
|
766
773
|
]
|
|
767
774
|
},
|
|
768
775
|
"defaults": {
|
|
776
|
+
"variant": "default",
|
|
769
777
|
"size": "default"
|
|
770
778
|
}
|
|
771
779
|
}
|
|
@@ -900,6 +908,54 @@
|
|
|
900
908
|
"Horizontal between stacked sections; `orientation=\"vertical\"` in multi-column layouts."
|
|
901
909
|
]
|
|
902
910
|
},
|
|
911
|
+
{
|
|
912
|
+
"name": "side-panel",
|
|
913
|
+
"import": "@archetypeai/ds-ui-svelte-console/primitives/side-panel",
|
|
914
|
+
"source": "https://design-system-console.archetypeai.workers.dev/r/side-panel.json",
|
|
915
|
+
"description": "Non-modal side panel for peeking at or inspecting one object without leaving the list it came from.",
|
|
916
|
+
"usage": [
|
|
917
|
+
"Bind the panel to an object id, never to a row: `<SidePanel.Root open={selectedId !== null} onOpenChange={(o) => !o && (selectedId = null)}>`. Keep the id in the URL so the state survives refresh, back and sharing; the primitive itself never reads or writes the URL. Do NOT derive the panel's content straight from that id: the panel stays mounted while it closes, so the content would blank out before it leaves. Latch the last opened object instead, `let shown = $state(null); $effect(() => { const next = find(selectedId); if (next) shown = next; })`, and render from `shown`.",
|
|
918
|
+
"Peek vs inspect: content with no page of its own just fills `SidePanel.Body`. Content that has a full page renders the page's body component inside Body and puts the handoff ONCE, in the Header `actions` snippet, as an outline icon button beside the built-in Close: `<SidePanel.Header>{#snippet actions()}<Button variant=\"outline\" size=\"icon\" class=\"size-8\" aria-label=\"Open full page\" href={fullPageUrl}><ArrowUpRightIcon /></Button>{/snippet}<SidePanel.Title>…</SidePanel.Title></SidePanel.Header>`. Header actions are outline icon buttons at the sm height (`class=\"size-8\"`, Button has no small icon size) so they read as one set with Close, pinned to the header's top-right, and match `size=\"sm\"` Footer buttons; never repeat the handoff in the Footer.",
|
|
919
|
+
"It never renders an overlay or scrim and never traps focus or scroll: the list behind stays fully interactive, clicking outside does nothing, Escape closes. If the task needs the page blocked, use Dialog instead.",
|
|
920
|
+
"One panel at a time. Drilling in replaces Body's content and adds an in-panel back; it never opens a second SidePanel.",
|
|
921
|
+
"Read-mostly: actions inside are fine, but destructive confirmations spawn a Dialog over the panel. The panel does not own confirmation.",
|
|
922
|
+
"`size=\"sm\"` (384px) for a record and its fields; `size=\"md\"` (576px) when Body carries a preview, a chart or a code block. `side=\"left\"` only when the list itself lives on the right.",
|
|
923
|
+
"Header layout follows Dialog: a `space-y-2` block, so `SidePanel.Title` and then either `SidePanel.Description` (plain text) or `SidePanel.Meta` (badges, a status line, key/value metadata) stack full width. The `actions` cluster and the close sit absolutely at the top right, so the header reserves right padding and a long Title truncates rather than running under them. Give header actions the same bare icon treatment as the close via `sidePanelCloseButtonVariants`; keep `Description` when the subtitle is text, because it is what wires `aria-describedby` and `Meta` cannot.",
|
|
924
|
+
"Content renders in place (no portal) so Tab reaches it after the list; do not mount it inside a transformed ancestor, where `fixed` misplaces. Focus does not move on open. Header renders an outline icon Close by default (`showCloseButton={false}` to place `SidePanel.Close` yourself). `SidePanel.Footer` is optional: it holds the actions on the data being viewed, never Close or the full-page handoff. One primary action sits right; with `justify=\"between\"` secondary actions (copy an id, compare) go first and land left."
|
|
925
|
+
],
|
|
926
|
+
"variants": {
|
|
927
|
+
"sidePanelContentVariants": {
|
|
928
|
+
"axes": {
|
|
929
|
+
"side": [
|
|
930
|
+
"right",
|
|
931
|
+
"left"
|
|
932
|
+
],
|
|
933
|
+
"size": [
|
|
934
|
+
"sm",
|
|
935
|
+
"md"
|
|
936
|
+
],
|
|
937
|
+
"// one value today; reserves `data-mode` for a later push mode\n mode": [
|
|
938
|
+
"overlay"
|
|
939
|
+
]
|
|
940
|
+
},
|
|
941
|
+
"defaults": {
|
|
942
|
+
"side": "right",
|
|
943
|
+
"size": "sm"
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
"sidePanelFooterVariants": {
|
|
947
|
+
"axes": {
|
|
948
|
+
"justify": [
|
|
949
|
+
"default",
|
|
950
|
+
"between"
|
|
951
|
+
]
|
|
952
|
+
},
|
|
953
|
+
"defaults": {
|
|
954
|
+
"justify": "default"
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
},
|
|
903
959
|
{
|
|
904
960
|
"name": "slider",
|
|
905
961
|
"import": "@archetypeai/ds-ui-svelte-labs/primitives/slider",
|
|
@@ -1120,8 +1176,12 @@
|
|
|
1120
1176
|
"description": "Hover hint.",
|
|
1121
1177
|
"usage": [
|
|
1122
1178
|
"Wrap the app/section in `Tooltip.Provider delayDuration={200}`.",
|
|
1123
|
-
"Field help: `Tooltip.Trigger variant=\"info\"`
|
|
1124
|
-
"`
|
|
1179
|
+
"Field help: `Tooltip.Trigger variant=\"info\"` is a filled 16px disc holding a mono `i` — pass it no children, it renders its own glyph. Explanation goes in `Tooltip.Content`.",
|
|
1180
|
+
"`aria-label` on the info trigger is required and is the caller's: name what is being explained (`aria-label=\"About Detection Log\"`). The glyph is `aria-hidden`, so without a label the button has no accessible name.",
|
|
1181
|
+
"The info disc squares against `Counter size=\"sm\"`'s height on purpose — a section title, its `Counter` and its info disc in one `flex items-center gap-2` row is the intended cluster, the solid pill reading as the count and the muted one as the meaning. 16px is also exactly the height of a `Label variant=\"sectionHeader\"` box, so the disc sits level with the title rather than towering over it.",
|
|
1182
|
+
"`size=\"lg\"` is the 20px disc, for a cluster built around `Counter size=\"default\"` (`h-5`) rather than a section label. `size` is inert on the other trigger treatments, which have no box.",
|
|
1183
|
+
"`variant=\"inlineLink\"` — underlined trigger flowing inside prose (supported file requirements).",
|
|
1184
|
+
"The arrow centres itself on the trigger on all four sides and protrudes half its box — `arrowClasses` is for its ink or size, not for centring corrections. If a tip looks off, it is a bug in `tooltipArrowVariants`, not something to counter-shift at the call site."
|
|
1125
1185
|
],
|
|
1126
1186
|
"variants": {
|
|
1127
1187
|
"tooltipTriggerVariants": {
|
|
@@ -1130,10 +1190,15 @@
|
|
|
1130
1190
|
"default",
|
|
1131
1191
|
"info",
|
|
1132
1192
|
"inlineLink"
|
|
1193
|
+
],
|
|
1194
|
+
"size": [
|
|
1195
|
+
"default",
|
|
1196
|
+
"lg"
|
|
1133
1197
|
]
|
|
1134
1198
|
},
|
|
1135
1199
|
"defaults": {
|
|
1136
|
-
"variant": "default"
|
|
1200
|
+
"variant": "default",
|
|
1201
|
+
"size": "default"
|
|
1137
1202
|
}
|
|
1138
1203
|
}
|
|
1139
1204
|
}
|
|
@@ -1142,9 +1207,16 @@
|
|
|
1142
1207
|
"name": "video-player",
|
|
1143
1208
|
"import": "@archetypeai/ds-ui-svelte-labs/primitives/video-player",
|
|
1144
1209
|
"source": "https://design-system-labs.archetypeai.workers.dev/r/video-player.json",
|
|
1145
|
-
"description": "Media-playback card
|
|
1210
|
+
"description": "Media-playback card whose seek track doubles as a timeline: named marker windows, tinted highlight bands and bindable playback state. Composes Card + AspectRatio + Button + Slider.",
|
|
1146
1211
|
"usage": [
|
|
1147
|
-
"`<VideoPlayer src muted />` for the standard controlled player; `controls={false}` for ambient/looping footage."
|
|
1212
|
+
"`<VideoPlayer src muted />` for the standard controlled player; `controls={false}` for ambient/looping footage. `preload=\"metadata\"` by default, so `duration` is known before the first play.",
|
|
1213
|
+
"Playback state: `bind:time` / `bind:duration` / `bind:paused` mirror the media element out, and `time` and `paused` are writable - assigning to `time` seeks, assigning `paused = false` plays. That is how another view drives the player.",
|
|
1214
|
+
"Markers: `markers={[{ start, end, label?, color? }]}` in **seconds, not fractions** draws one focusable dot per window on the seek track. Clicking a dot expands it into a range pill, seeks to its start and plays; one is expanded at a time, and `bind:activeMarker` is its index (or null). Scrubbing by hand, or a click on the video surface, collapses it. `loopActiveMarker` loops playback inside the expanded window.",
|
|
1215
|
+
"Linked brushing: `highlights={[{ start, end, color? }]}` tints regions another view is pointing at (brushing in), and `onMarkerHover(index | null)` fires on hover **and focus** so a hovered marker can highlight the matching row elsewhere (brushing out).",
|
|
1216
|
+
"Two lifecycle traps when `src` changes: a new `src` remounts the player and Svelte's media bindings write your bound values into the fresh element, so reset `time = 0` and `paused = true` yourself or a stale position becomes the new clip's start; and `bind:duration` holds the previous clip's value until `loadedmetadata`, so reseed `duration` from a known length if marker geometry must be right on the first frame (`duration === 0` collapses every marker to the left edge).",
|
|
1217
|
+
"`controlsVisibility=\"always\"` pins the control bar open - required whenever markers must stay clickable during playback, since the default `\"auto\"` fades the bar (and the markers with it) 2.5s into playback. `formatTime` replaces the mm:ss clock in the time readout and the marker captions.",
|
|
1218
|
+
"Style hooks: `data-slot` on `video-player`, `-video`, `-overlay`, `-controls`, `-time`, `-markers`, `-marker` (carries `data-expanded`), `-marker-caption`, `-highlight`. A per-marker `color` inks the outline and fill via `currentColor`.",
|
|
1219
|
+
"Marker seeking needs an origin that serves HTTP Range requests; against one that does not, seeks silently do nothing (serve the clip from a blob URL instead)."
|
|
1148
1220
|
],
|
|
1149
1221
|
"variants": {
|
|
1150
1222
|
"videoPlayerOverlayVariants": {
|
|
@@ -1168,6 +1240,28 @@
|
|
|
1168
1240
|
"defaults": {
|
|
1169
1241
|
"visible": "true"
|
|
1170
1242
|
}
|
|
1243
|
+
},
|
|
1244
|
+
"videoPlayerMarkerVariants": {
|
|
1245
|
+
"axes": {
|
|
1246
|
+
"expanded": [
|
|
1247
|
+
"true",
|
|
1248
|
+
"false"
|
|
1249
|
+
],
|
|
1250
|
+
"filled": [
|
|
1251
|
+
"true",
|
|
1252
|
+
"false"
|
|
1253
|
+
],
|
|
1254
|
+
"emphasis": [
|
|
1255
|
+
"hidden",
|
|
1256
|
+
"full",
|
|
1257
|
+
"ambient"
|
|
1258
|
+
]
|
|
1259
|
+
},
|
|
1260
|
+
"defaults": {
|
|
1261
|
+
"expanded": "false",
|
|
1262
|
+
"filled": "false",
|
|
1263
|
+
"emphasis": "ambient"
|
|
1264
|
+
}
|
|
1171
1265
|
}
|
|
1172
1266
|
}
|
|
1173
1267
|
}
|
package/lib/add-ds-lib-tokens.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execFile } from 'child_process';
|
|
|
2
2
|
import { promisify } from 'util';
|
|
3
3
|
import * as p from '@clack/prompts';
|
|
4
4
|
import { detectPm, getPm } from './use-package-manager.js';
|
|
5
|
-
import { validateFontsPath, installLocalFonts } from './install-fonts-local.js';
|
|
5
|
+
import { validateFontsPath, installLocalFonts, preloadLinkTags } from './install-fonts-local.js';
|
|
6
6
|
import { isInteractive } from './is-interactive.js';
|
|
7
7
|
import { prependCss } from './scaffold-ds-svelte-project.js';
|
|
8
8
|
|
|
@@ -124,6 +124,14 @@ export async function addDsLibTokens(args) {
|
|
|
124
124
|
const fontResult = installLocalFonts(fontsPath, projectDir);
|
|
125
125
|
if (fontResult.success) {
|
|
126
126
|
fontSpinner.stop(`${fontResult.fileCount} font files installed`);
|
|
127
|
+
if (fontResult.preload.patched) {
|
|
128
|
+
p.log.info('Preloading Book, Medium and Mono Book in src/app.html');
|
|
129
|
+
} else {
|
|
130
|
+
p.log.warn(
|
|
131
|
+
`Could not add font preloads (${fontResult.preload.reason}). Add these to your <head>:\n` +
|
|
132
|
+
preloadLinkTags().join('\n')
|
|
133
|
+
);
|
|
134
|
+
}
|
|
127
135
|
} else {
|
|
128
136
|
fontSpinner.stop('Failed to install fonts');
|
|
129
137
|
p.log.warn(fontResult.error);
|
package/lib/add-ds-ui-svelte.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import * as p from '@clack/prompts';
|
|
7
7
|
import { validateRegistryUrl } from './validate-url.js';
|
|
8
8
|
import { detectPm, getPm } from './use-package-manager.js';
|
|
9
|
-
import { validateFontsPath, installLocalFonts } from './install-fonts-local.js';
|
|
9
|
+
import { validateFontsPath, installLocalFonts, preloadLinkTags } from './install-fonts-local.js';
|
|
10
10
|
import { isInteractive } from './is-interactive.js';
|
|
11
11
|
import {
|
|
12
12
|
prependCss,
|
|
@@ -177,6 +177,14 @@ export async function addDsUiSvelte(args) {
|
|
|
177
177
|
const fontResult = installLocalFonts(fontsPath, projectDir);
|
|
178
178
|
if (fontResult.success) {
|
|
179
179
|
fontSpinner.stop(`${fontResult.fileCount} font files installed`);
|
|
180
|
+
if (fontResult.preload.patched) {
|
|
181
|
+
p.log.info('Preloading Book, Medium and Mono Book in src/app.html');
|
|
182
|
+
} else {
|
|
183
|
+
p.log.warn(
|
|
184
|
+
`Could not add font preloads (${fontResult.preload.reason}). Add these to your <head>:\n` +
|
|
185
|
+
preloadLinkTags().join('\n')
|
|
186
|
+
);
|
|
187
|
+
}
|
|
180
188
|
} else {
|
|
181
189
|
fontSpinner.stop('Failed to install fonts');
|
|
182
190
|
p.log.warn(fontResult.error);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, copyFileSync, mkdirSync } from 'fs';
|
|
1
|
+
import { existsSync, readdirSync, copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join, resolve } from 'path';
|
|
3
3
|
|
|
4
4
|
// strip surrounding quotes that some terminals (VS Code, Cursor) add on drag-and-drop
|
|
@@ -13,25 +13,59 @@ function sanitizePath(p) {
|
|
|
13
13
|
return trimmed;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// all
|
|
16
|
+
// all 13 required font files
|
|
17
17
|
export const REQUIRED_FONTS = [
|
|
18
|
-
// PP Neue Montreal Sans
|
|
19
|
-
'PPNeueMontreal-Thin.
|
|
20
|
-
'PPNeueMontreal-ThinItalic.
|
|
21
|
-
'PPNeueMontreal-Light.
|
|
22
|
-
'PPNeueMontreal-Book.
|
|
23
|
-
'PPNeueMontreal-Italic.
|
|
24
|
-
'PPNeueMontreal-Medium.
|
|
25
|
-
'PPNeueMontreal-Bold.
|
|
26
|
-
'PPNeueMontreal-BoldItalic.
|
|
27
|
-
// PP Neue Montreal Mono
|
|
28
|
-
'PPNeueMontrealMono-Thin.
|
|
29
|
-
'PPNeueMontrealMono-Book.
|
|
30
|
-
'PPNeueMontrealMono-RegularItalic.
|
|
31
|
-
'PPNeueMontrealMono-Medium.
|
|
32
|
-
'PPNeueMontrealMono-Bold.
|
|
18
|
+
// PP Neue Montreal Sans
|
|
19
|
+
'PPNeueMontreal-Thin.woff2',
|
|
20
|
+
'PPNeueMontreal-ThinItalic.woff2',
|
|
21
|
+
'PPNeueMontreal-Light.woff2',
|
|
22
|
+
'PPNeueMontreal-Book.woff2',
|
|
23
|
+
'PPNeueMontreal-Italic.woff2',
|
|
24
|
+
'PPNeueMontreal-Medium.woff2',
|
|
25
|
+
'PPNeueMontreal-Bold.woff2',
|
|
26
|
+
'PPNeueMontreal-BoldItalic.woff2',
|
|
27
|
+
// PP Neue Montreal Mono
|
|
28
|
+
'PPNeueMontrealMono-Thin.woff2',
|
|
29
|
+
'PPNeueMontrealMono-Book.woff2',
|
|
30
|
+
'PPNeueMontrealMono-RegularItalic.woff2',
|
|
31
|
+
'PPNeueMontrealMono-Medium.woff2',
|
|
32
|
+
'PPNeueMontrealMono-Bold.woff2'
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
+
// the faces the first paint uses: 400 sans (body), 500 sans (emphasis), 400 mono (labels)
|
|
36
|
+
export const FONT_PRELOADS = [
|
|
37
|
+
'PPNeueMontreal-Book.woff2',
|
|
38
|
+
'PPNeueMontreal-Medium.woff2',
|
|
39
|
+
'PPNeueMontrealMono-Book.woff2'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// `crossorigin` is required on font preloads even same-origin; without it the
|
|
43
|
+
// browser fetches the file twice (once anonymous for the preload, once CORS for @font-face)
|
|
44
|
+
export function preloadLinkTags() {
|
|
45
|
+
return FONT_PRELOADS.map(
|
|
46
|
+
(file) => `<link rel="preload" as="font" type="font/woff2" crossorigin href="/fonts/${file}" />`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// insert the preload tags above %sveltekit.head% in src/app.html (idempotent)
|
|
51
|
+
export function patchAppHtmlPreload(projectPath) {
|
|
52
|
+
const htmlPath = join(projectPath, 'src', 'app.html');
|
|
53
|
+
if (!existsSync(htmlPath)) return { patched: false, reason: 'src/app.html not found' };
|
|
54
|
+
const content = readFileSync(htmlPath, 'utf-8');
|
|
55
|
+
if (content.includes(`/fonts/${FONT_PRELOADS[0]}`)) {
|
|
56
|
+
return { patched: false, reason: 'preload tags already present' };
|
|
57
|
+
}
|
|
58
|
+
const marker = /^([ \t]*)%sveltekit\.head%/m;
|
|
59
|
+
const match = content.match(marker);
|
|
60
|
+
if (!match) return { patched: false, reason: '%sveltekit.head% placeholder not found' };
|
|
61
|
+
const indent = match[1];
|
|
62
|
+
const tags = preloadLinkTags()
|
|
63
|
+
.map((tag) => `${indent}${tag}\n`)
|
|
64
|
+
.join('');
|
|
65
|
+
writeFileSync(htmlPath, content.replace(marker, `${tags}${indent}%sveltekit.head%`));
|
|
66
|
+
return { patched: true };
|
|
67
|
+
}
|
|
68
|
+
|
|
35
69
|
// validate that a directory contains all required font files
|
|
36
70
|
export function validateFontsPath(fontsPath) {
|
|
37
71
|
const resolved = resolve(sanitizePath(fontsPath));
|
|
@@ -72,7 +106,7 @@ export function copyFontsToStatic(fontsPath, projectPath) {
|
|
|
72
106
|
return REQUIRED_FONTS.length;
|
|
73
107
|
}
|
|
74
108
|
|
|
75
|
-
// orchestrate: validate
|
|
109
|
+
// orchestrate: validate, copy font files to static/fonts/, and preload the first-paint faces
|
|
76
110
|
export function installLocalFonts(fontsPath, projectPath) {
|
|
77
111
|
const validation = validateFontsPath(fontsPath);
|
|
78
112
|
if (!validation.valid) {
|
|
@@ -80,6 +114,7 @@ export function installLocalFonts(fontsPath, projectPath) {
|
|
|
80
114
|
}
|
|
81
115
|
|
|
82
116
|
const fileCount = copyFontsToStatic(fontsPath, projectPath);
|
|
117
|
+
const preload = patchAppHtmlPreload(projectPath);
|
|
83
118
|
|
|
84
|
-
return { success: true, fileCount };
|
|
119
|
+
return { success: true, fileCount, preload };
|
|
85
120
|
}
|