@mhmo91/schmancy 0.9.14 → 0.9.15

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.
@@ -0,0 +1,247 @@
1
+ # Follow-ups: schmancy agent runtime
2
+
3
+ **Status:** v0.9.13 of `@mhmo91/schmancy/agent` is live. The items below are improvements that did not ship in v1. They're ordered by impact, not urgency — v0.9.13 already satisfies the Claude Design handover's acceptance criteria.
4
+
5
+ **Parent:** `handover/schmancy-agent-runtime.md` (original ask), `handover/agent-runtime-v1.md` (response + live URLs).
6
+
7
+ ---
8
+
9
+ ## 1. Lazy vendor chunks (biggest performance win, biggest risk)
10
+
11
+ **Problem.** `highlight.js`, `jsqr`, and `@material/material-color-utilities` are `manualChunks`-split into `dist/agent/vendor-*.js` siblings, but they're **eagerly fetched** on first load because the owning components use module-top static imports. A blank page with only `<schmancy-theme>` + `<schmancy-button>` still pulls 22 KB (material-color) + 53 KB (jsqr) + 15 KB gzipped (highlight.js) — 90 KB of deps for components the page never renders.
12
+
13
+ **Fix.** Per-component dynamic imports inside the owning elements. Pattern (worked out in [agent-runtime-v1.md](./agent-runtime-v1.md)'s "lazy caveat" section):
14
+
15
+ ```ts
16
+ // BEFORE — src/code-highlight/code-highlight.ts
17
+ import hljs from 'highlight.js/lib/core'
18
+ import javascript from 'highlight.js/lib/languages/javascript'
19
+ // ... 5 more language imports
20
+ hljs.registerLanguage('javascript', javascript)
21
+ // ...
22
+
23
+ // AFTER — module-scope memoised Promise, async render
24
+ let hljsPromise: Promise<typeof import('highlight.js/lib/core').default> | null = null
25
+ function loadHljs() {
26
+ if (hljsPromise) return hljsPromise
27
+ hljsPromise = Promise.all([
28
+ import('highlight.js/lib/core'),
29
+ import('highlight.js/lib/languages/bash'),
30
+ import('highlight.js/lib/languages/javascript'),
31
+ // ... etc
32
+ ]).then(([core, bash, js, /*…*/]) => {
33
+ const hljs = core.default
34
+ hljs.registerLanguage('bash', bash.default)
35
+ hljs.registerLanguage('javascript', js.default)
36
+ // ...
37
+ return hljs
38
+ })
39
+ return hljsPromise
40
+ }
41
+
42
+ @customElement('schmancy-code')
43
+ export class SchmancyCode extends TailwindElement(/*…*/) {
44
+ @state() private _highlighted = ''
45
+ protected updated(changed: Map<string, unknown>) {
46
+ if (changed.has('code') || changed.has('language')) {
47
+ loadHljs().then(hljs => {
48
+ this._highlighted = this.language
49
+ ? hljs.highlight(this.code.trim(), { language: this.language }).value
50
+ : hljs.highlightAuto(this.code.trim()).value
51
+ })
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ Rollup auto-splits `await import(...)` — no more `manualChunks` entry needed.
58
+
59
+ **Per-component effort** (from [agent-runtime-v1.md](./agent-runtime-v1.md)):
60
+
61
+ | Component | File | Effort | Risk |
62
+ |---|---|---|---|
63
+ | `<schmancy-code>` | `src/code-highlight/code-highlight.ts` | ~1.5h | Low |
64
+ | `<schmancy-qr-scanner>` | `src/qr-scanner/qr-scanner.ts` | ~2.5h | Medium — camera lifecycle + jsqr timing |
65
+ | `<schmancy-typewriter>` | `src/typewriter/typewriter.ts` | ~1h | Low |
66
+ | `@material/material-color-utilities` in theme | `src/theme/theme.service.ts` + `theme.format.ts` + `theme.component.ts` | **~3–4h** | **High** — theme reads are currently synchronous; downstream subscribers expect `theme.value` immediately. Needs `Theme | Pending` Observable contract + audit of every consumer. |
67
+
68
+ **Total: ~8–10h** focused work. **Gate with a CI test** that loads the bundle on a theme-only page and asserts `vendor-highlight`, `vendor-jsqr`, and `vendor-material-color` are NOT in the network log. Without that gate, an innocent top-level `import` added later will silently re-eagerize them.
69
+
70
+ **Owner:** needs an ADR before touching the theme service. Stub at [`docs/adr/0009-lazy-vendor-chunks-in-schmancy-agent.md`](../../../docs/adr/0009-lazy-vendor-chunks-in-schmancy-agent.md) in the parent monorepo.
71
+
72
+ ---
73
+
74
+ ## 2. CI smoke-test gate (highest value per minute of work)
75
+
76
+ **Problem.** `src/agent/agent-bundle.test.ts` exists and passes locally (6/6 tests, verified in v0.9.13). It's not yet wired into `.github/workflows/publish-to-npm.yml` as a blocking step. A regression that breaks `window.schmancy.help('schmancy-button')` would publish silently.
77
+
78
+ **Fix.** One line in the workflow, before `yarn build`:
79
+
80
+ ```yaml
81
+ - name: Agent bundle smoke test
82
+ run: yarn vitest run src/agent/
83
+ ```
84
+
85
+ Requires Playwright in CI (already declared in `devDependencies` via `@vitest/browser-playwright`, but `yarn install` doesn't download Chromium by default). Add a step:
86
+
87
+ ```yaml
88
+ - name: Install Playwright Chromium
89
+ run: yarn playwright install chromium --with-deps
90
+ ```
91
+
92
+ **Effort:** ~15 min. Lowest effort, prevents every future regression.
93
+
94
+ ---
95
+
96
+ ## 3. JSDoc backfill campaign
97
+
98
+ **Problem.** The manifest plugin fails open on missing JSDoc. v0.9.13 ships:
99
+ - **25 / 111** components with `@example` (22% coverage)
100
+ - **0 / 111** with `@platform` (0%)
101
+ - **0 / 111** with `@summary` on the class (the plugin falls back to the class's JSDoc description, which works but is verbose)
102
+ - **6 / 6** services with `@service` (100% — done in v0.9.13)
103
+
104
+ The missing tags aren't a blocker — the fields just don't appear in the manifest. Backfill is mechanical, high-value, low-risk.
105
+
106
+ **Fix.** Per-component JSDoc additions in `src/**/*.ts`:
107
+
108
+ ```ts
109
+ /**
110
+ * @element schmancy-foo
111
+ * @summary One-line "when to use" — goes into manifest `summary` field.
112
+ * @example
113
+ * <schmancy-foo variant="filled">Hello</schmancy-foo>
114
+ * @platform button click - Schmancy-skinned native <button>; degrade to
115
+ * `<button class="..."` if the tag fails to register.
116
+ */
117
+ @customElement('schmancy-foo')
118
+ ```
119
+
120
+ **Effort.** ~5 min per component × ~86 remaining = **~7h**, easily split across PRs. Good "pair programming with Claude" task — each component takes one read + one edit.
121
+
122
+ **Gate:** add a lint rule (or a build warning) that fails the build if a `@customElement('schmancy-*')` class has no `@summary` tag after the backfill campaign is complete.
123
+
124
+ ---
125
+
126
+ ## 4. Semver bump to 0.10.0
127
+
128
+ **Problem.** CI's `yarn version patch` bumped `0.9.12 → 0.9.13` for a **new subpath**, which is arguably a MINOR version per semver. The handover author pinned `@0.9.13` in the response doc — that works, but consumers reading the package version may underestimate the surface change.
129
+
130
+ **Fix.** Once this is merged and the JSDoc backfill has had a few iterations, manually bump `package.json` version to `0.10.0` and push. CI will auto-patch it to `0.10.1` on the next source change, but the minor bump signals "new public subpath."
131
+
132
+ **Effort:** ~2 min.
133
+
134
+ **Alternative:** switch the CI workflow to use [`release-please`](https://github.com/googleapis/release-please) or [Changesets](https://github.com/changesets/changesets) for Conventional-Commit-driven semver. Higher upfront cost, eliminates the manual step forever.
135
+
136
+ ---
137
+
138
+ ## 5. wca → CEM analyzer migration
139
+
140
+ **Problem.** `yarn manifest` still runs `web-component-analyzer` (wca v2.0.0, last updated 2023-10). Lit officially endorses `@custom-elements-manifest/analyzer` ([lit-starter-ts](https://github.com/lit/lit/tree/main/packages/lit-starter-ts)). wca's output format differs from the current CEM v1 schema — our agent plugin already emits CEM v1 shape, so the `custom-elements.json` at the package root is the odd one out.
141
+
142
+ **Fix.** Replace:
143
+ ```json
144
+ "manifest": "wca 'src/**/*.ts' --outFile custom-elements.json --format json"
145
+ ```
146
+ with:
147
+ ```json
148
+ "manifest": "cem analyze --litelement --globs \"src/**/*.ts\" --outfile custom-elements.json"
149
+ ```
150
+
151
+ CEM analyzer's output is a different shape (`modules[].declarations[]` vs wca's `tags[]`). Downstream consumers of `custom-elements.json` (IDE tooling, `ts-lit-plugin`) may need adjustments.
152
+
153
+ **Effort:** ~1h to swap tools + verify IDE hover still works + update any script that reads the old shape.
154
+
155
+ **Alternative:** drop `custom-elements.json` entirely. The agent plugin already emits a richer CEM v1 manifest at `dist/agent/schmancy.manifest.json` on every build. The root `custom-elements.json` is a duplicate with a different shape. Eliminating it simplifies the build chain.
156
+
157
+ ---
158
+
159
+ ## 6. `<schmancy-devtools>` visible dev overlay
160
+
161
+ **Problem.** `<schmancy-skill>` renders nothing — by design, per handover §7 Q3. An agent (or a human debugging a prototype) has no visual feedback that the runtime is installed. They have to open DevTools and check `window.schmancy`.
162
+
163
+ **Fix.** A separate, OPT-IN element: `<schmancy-devtools>`. Renders a small floating badge showing:
164
+ - schmancy version (from `manifest.schemaVersion` + package version)
165
+ - count: 111 tags, 6 services, 130 tokens
166
+ - "Open console: `window.schmancy.help(…)`"
167
+ - Collapsible panel with search over the manifest
168
+
169
+ **Keep `<schmancy-skill>` silent.** This is additive.
170
+
171
+ **Effort:** ~3h for a minimal surface, ~6h for a polished one.
172
+
173
+ ---
174
+
175
+ ## 7. Real MCP adapter (optional, speculative)
176
+
177
+ **Problem.** [agent-runtime-v1.md](./agent-runtime-v1.md) caveats that the runtime is explicitly **NOT** MCP-branded because postMessage isn't an MCP transport in the current spec (2025-11-25). If an external agent framework (Claude Code, another MCP host) wants to drive the library through the MCP protocol, someone has to write an adapter.
178
+
179
+ **Shape.** A separate `@mhmo91/schmancy-mcp-server` npm package (not a subpath of schmancy — different product). Stdio transport. Reads `@mhmo91/schmancy/agent/manifest` as its source of truth. Exposes:
180
+ - `resources/list` — every schmancy tag as `schmancy://components/<tag>`
181
+ - `resources/read` — returns the manifest entry for one tag
182
+ - `completion/complete` — returns the `values: string[]` array for an enum attribute
183
+
184
+ The manifest already has everything needed; the package is just the JSON-RPC wrapper.
185
+
186
+ **Effort:** ~1 day for a minimal server, ~3 days with integration tests against the Claude Code MCP client. Do this only when a concrete consumer asks for it.
187
+
188
+ ---
189
+
190
+ ## 8. Publish-pipeline hardening
191
+
192
+ **Problem.** The workflow at `.github/workflows/publish-to-npm.yml` has a few known-fragile spots:
193
+
194
+ - **Workflow file changes don't retrigger the workflow.** To ship a fix to the workflow itself, you have to touch a file in the `paths:` filter. This session needed `chore(agent): add keywords` commits to force a retrigger. Fix: add `.github/workflows/**` to the filter.
195
+ - **`yarn version patch` can race** against a concurrent push. Two simultaneous commits would both try to bump and publish. Fix: wrap the publish step in a mutex (GitHub Actions concurrency group).
196
+ - **`npm publish` uses `NPM_TOKEN` secret**, fine today; if the token rotates, the workflow fails silently without telling the maintainer where to regenerate. Fix: add a step that checks `npm whoami` before publish and fails loudly if the token is invalid.
197
+
198
+ **Effort:** ~1h to wire all three.
199
+
200
+ ---
201
+
202
+ ## 9. Automate `<PENDING>` substitution in handover docs
203
+
204
+ **Problem.** `handover/agent-runtime-v1.md` had `<PENDING>` placeholders that we manually replaced with `0.9.13` after the first publish. Future handover docs will have the same issue.
205
+
206
+ **Fix.** A build step that substitutes `0.9.15` in `handover/**/*.md` against `package.json`'s `version` field on every build. `dist/handover/**/*.md` gets the rendered version; the source stays templated.
207
+
208
+ **Effort:** ~30 min (one sed step or a tiny script).
209
+
210
+ ---
211
+
212
+ ## 10. esm.sh health monitoring
213
+
214
+ **Problem.** We rely on esm.sh to serve the bundle. If esm.sh goes down, every consumer of `https://esm.sh/@mhmo91/schmancy/agent@0.9.13` breaks. No monitoring.
215
+
216
+ **Fix.** A lightweight GitHub Action that runs daily:
217
+ - `curl -sI` the esm.sh URL
218
+ - Verifies `content-type: application/javascript`, `access-control-allow-origin: *`
219
+ - On failure: open a GitHub issue with the response headers
220
+
221
+ **Alternative CDNs** (drop-in):
222
+ - `https://cdn.jsdelivr.net/npm/@mhmo91/schmancy@<version>/dist/agent/schmancy.agent.js`
223
+ - `https://unpkg.com/@mhmo91/schmancy@<version>/dist/agent/schmancy.agent.js`
224
+
225
+ Document both as fallbacks in `handover/agent-runtime-v1.md` (already listed there).
226
+
227
+ **Effort:** ~30 min for the monitoring action.
228
+
229
+ ---
230
+
231
+ ## Priority matrix (if you only have one week)
232
+
233
+ | # | Title | Impact | Effort | Risk | Ship order |
234
+ |---|---|---|---|---|---|
235
+ | 2 | CI smoke-test gate | High | 15m | Low | **1st** |
236
+ | 1 | Lazy vendor chunks (code + qr + typewriter) | High | 5h | Low | 2nd |
237
+ | 3 | JSDoc `@example` backfill (first 30 components) | Medium | 3h | None | 3rd |
238
+ | 9 | `<PENDING>` substitution | Low | 30m | None | 4th |
239
+ | 8 | Publish-pipeline hardening | Medium | 1h | Low | 5th |
240
+ | 4 | Semver bump to 0.10.0 | Low | 2m | None | any time |
241
+ | 1 | Lazy theme (material-color-utilities) | High | 4h | **High** | separate ADR, not this week |
242
+ | 5 | wca → CEM analyzer migration | Medium | 1h | Medium | after lazy chunks |
243
+ | 6 | `<schmancy-devtools>` visible panel | Medium | 3h | None | when someone asks |
244
+ | 7 | Real MCP adapter | Low today | 1d | Low | only on concrete request |
245
+ | 10 | esm.sh monitoring | Low | 30m | None | when burnt once |
246
+
247
+ **In one week:** 1 + 2 + 3 partial + 9 = meaningful delta, no architectural risk, keeps the CI gate as the safety net for everything after.
@@ -0,0 +1,109 @@
1
+ # Handover response: schmancy agent runtime v1
2
+
3
+ **From:** schmancy maintainers
4
+ **To:** Claude Design agent (ref: `handover/schmancy-agent-runtime.md`)
5
+ **Status:** shipped. Pinned URLs below.
6
+
7
+ ## The URLs you asked for
8
+
9
+ ```
10
+ https://esm.sh/@mhmo91/schmancy/agent@0.9.15
11
+ https://esm.sh/@mhmo91/schmancy/agent/manifest@0.9.15
12
+ ```
13
+
14
+ `0.9.13` is the first release containing `/agent`; every subsequent publish serves the same subpath. `npm view @mhmo91/schmancy version` always returns the current pin if you want to float forward.
15
+
16
+ ## Minimum consumption
17
+
18
+ One script tag. No bundler, no bare specifiers, no npm install.
19
+
20
+ ```html
21
+ <!doctype html>
22
+ <script type="module">
23
+ import { $dialog, theme } from 'https://esm.sh/@mhmo91/schmancy/agent@0.9.15';
24
+ </script>
25
+ <schmancy-theme root scheme="dark">
26
+ <schmancy-surface type="solid" fill="all">
27
+ <schmancy-button variant="filled">Hi</schmancy-button>
28
+ <schmancy-skill></schmancy-skill>
29
+ </schmancy-surface>
30
+ </schmancy-theme>
31
+ ```
32
+
33
+ Importing the URL side-effect registers every `<schmancy-*>` tag (111 of them, verified live) plus `<schmancy-skill>`. Named re-exports cover the full imperative surface: `$dialog`, `$notify`, `sheet`, `SchmancySheetPosition`, `schmancyContentDrawer`, `theme`, `area`, `lazy`, `createContext`, `select`, `selectItem`, `$LitElement`.
34
+
35
+ ## Discovery API (installed by `<schmancy-skill>`)
36
+
37
+ Drop `<schmancy-skill>` anywhere on the page once; `window.schmancy` appears on connect. Every helper is synchronous and pure — the manifest is inlined into the bundle, no second fetch required.
38
+
39
+ | Call | Returns |
40
+ |---|---|
41
+ | `window.schmancy.help()` | `{ elements: Array<{ tag, summary }>, services: Array<{ name, summary }> }` — overview list |
42
+ | `window.schmancy.help('schmancy-button')` | Full CEM-v1 declaration: `attributes[]`, `events[]`, `slots[]`, `cssParts[]`, `cssProperties[]`, `examples[]`, `contexts[]` |
43
+ | `window.schmancy.help('$dialog')` | Service entry with `methods[]` signatures |
44
+ | `window.schmancy.tokens()` | Flat array of `schmancy-sys-color-*` custom-property names, extracted at build time from `theme.interface.ts` + `theme.style.css` |
45
+ | `window.schmancy.platformPrimitive('schmancy-dialog')` | `{ tag, mode?, note? }` when the component has an `@platform` JSDoc hint — tells the agent what native element the component wraps for graceful degradation |
46
+ | `window.schmancy.registeredTags()` | String list of every schmancy tag currently registered in `customElements` |
47
+ | `window.schmancy.a11yAudit()` | Walks the live DOM and reports `{ tag, role, ariaLabel, hasShadowRoot, formAssociated }` per instance |
48
+ | `window.schmancy.manifestUrl` | Blob URL pointing at the inlined manifest JSON — `fetch()` it if you want the raw bytes |
49
+ | `window.schmancy.manifest` | The inlined manifest object itself |
50
+ | `window.schmancy.capabilities()` | Runtime feature probe (see below) |
51
+
52
+ ## Enum values for attribute types
53
+
54
+ Every attribute whose TypeScript type is a string-literal union (inline `'a' \| 'b'` or named alias like `ButtonVariant`) ships a resolved `values: string[]` field in its manifest entry. No re-parsing of TS type strings needed. Example:
55
+
56
+ ```js
57
+ window.schmancy.help('schmancy-button').attributes
58
+ .find(a => a.name === 'variant')
59
+ .values
60
+ // ["elevated", "filled", "filled tonal", "tonal", "outlined", "text"]
61
+ ```
62
+
63
+ ## Runtime capability probe
64
+
65
+ Sandboxes differ. `capabilities()` feature-detects platform APIs at runtime so an agent can branch on what's actually available:
66
+
67
+ ```ts
68
+ type Capabilities = {
69
+ popover: boolean // HTML popover attribute
70
+ declarativeShadowDom: boolean // <template shadowrootmode>
71
+ scopedRegistries: boolean // native `new CustomElementRegistry()`
72
+ trustedTypes: boolean // require-trusted-types-for
73
+ cssRegisteredProperties: boolean // CSS.registerProperty()
74
+ elementInternalsAria: boolean // ElementInternals.role etc.
75
+ formAssociated: boolean // ElementInternals (any)
76
+ adoptedStyleSheets: boolean // Document.adoptedStyleSheets
77
+ }
78
+ ```
79
+
80
+ Use it to pick dialog implementation, fall back gracefully, or refuse to render when a required feature is missing.
81
+
82
+ ## Acceptance test
83
+
84
+ Port `Procurement Lifecycle.html` (or any prototype) using only the two URLs above. The bundle handles everything — registration, theme tokens, service wiring. If the port works, the runtime is verified.
85
+
86
+ ## Honest caveats
87
+
88
+ 1. **Vendor chunks (`highlight.js`, `jsqr`, `@material/material-color-utilities`) are `manualChunks`-split but still eagerly fetched** on first load, because the owning components use module-top static imports. Total wire cost on HTTP/2: 373 KB gzipped across the primary bundle + 3 vendor chunks, fetched in parallel. Primary bundle is 284 KB gzipped (under handover's 300 KB target). Per-component dynamic imports would make the vendor chunks truly lazy; that refactor is tracked separately (see `docs/adr/` in the parent monorepo).
89
+
90
+ 2. **Claude Artifacts CSP is observed, not officially published.** Current bundle works under the observed CSP (`'unsafe-inline'`, `'unsafe-eval'`, `cdnjs.cloudflare.com`, `blob:` in `worker-src`). It contains no `eval`, no string-concatenated HTML, no Trusted-Types-hostile patterns — if Anthropic tightens the CSP later it should still work. If Trusted Types becomes enforced, the runtime avoids all sinks (Lit templates only; Blob URL for `fetch` not `<script src>`).
91
+
92
+ 3. **esm.sh availability.** We rely on esm.sh to serve the npm-published tarball. If esm.sh is down or slow, `https://cdn.jsdelivr.net/npm/@mhmo91/schmancy/agent/+esm` and `https://unpkg.com/@mhmo91/schmancy/agent` are drop-in alternatives.
93
+
94
+ 4. **`services[]` and `examples[]` fill in as JSDoc backfills land.** The plugin that produces the manifest fails open — missing `@service` / `@example` JSDoc just means fewer entries, not a broken build. Expect this to grow with each release.
95
+
96
+ 5. **`<schmancy-skill>` renders nothing.** The handover's §7 Q3 defaulted to invisible. A visible `<schmancy-devtools>` dev-overlay element is not shipped; it's a separate concept if you ever need one.
97
+
98
+ ## Reporting issues
99
+
100
+ Open an issue at `github.com/mhmo91/schmancy` with:
101
+ - The `window.schmancy.capabilities()` output from your sandbox
102
+ - The minimum HTML that fails
103
+ - The manifest version (`window.schmancy.manifest.schemaVersion`)
104
+
105
+ ## Follow-up tracker
106
+
107
+ - [ ] Per-component dynamic imports for `highlight.js`, `jsqr`, `@material/material-color-utilities` — cuts first-paint load for agents that don't use the heavy components. ADR pending in `docs/adr/NNNN-lazy-vendor-chunks-in-schmancy-agent.md`.
108
+ - [ ] JSDoc backfill pass — add `@example`, `@platform`, `@summary` tags to the remaining component set so the manifest's `examples[]` and `platformPrimitive` fields populate fully.
109
+ - [ ] CI smoke test that loads the built bundle in headless Chromium and asserts the handover's §4.4 acceptance criteria on every push (shipped as `src/agent/agent-bundle.test.ts` but not yet wired into the `publish-to-npm.yml` workflow as a blocking gate).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhmo91/schmancy",
3
- "version": "0.9.14",
3
+ "version": "0.9.15",
4
4
  "description": "UI library build with web components",
5
5
  "main": "./dist/index.js",
6
6
  "customElements": "custom-elements.json",
@@ -58,8 +58,9 @@
58
58
  },
59
59
  "scripts": {
60
60
  "dev": "vite build --config vite.config.ts --watch",
61
- "build": "tsc && vite build --config vite.config.ts && yarn manifest && yarn build:agent",
61
+ "build": "tsc && vite build --config vite.config.ts && yarn manifest && yarn build:agent && yarn build:handover",
62
62
  "build:agent": "vite build --config vite.config.agent.ts",
63
+ "build:handover": "node scripts/render-handover.mjs",
63
64
  "manifest": "wca 'src/**/*.ts' --outFile custom-elements.json --format json",
64
65
  "ncu": "ncu -u && npm i",
65
66
  "test": "vitest run",