@stnd/core 0.1.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +236 -0
- package/package.json +89 -12
- package/src/dev-toolbar/debug-toggle.js +21 -0
- package/src/dev-toolbar/lab-toggle.js +153 -0
- package/src/loader.js +648 -0
- package/src/virtual.js +425 -0
- package/src/virtual.test.js +37 -0
- package/standard.js +573 -0
- package/CHANGELOG.md +0 -13
- package/src/StandardModel.ts +0 -55
- package/src/StandardRoot.ts +0 -73
- package/src/Visitor.ts +0 -50
- package/src/index.ts +0 -4
- package/tsconfig.json +0 -20
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Francis Fontaine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "@stnd/core"
|
|
3
|
+
aliases: []
|
|
4
|
+
created: 2026-06-20 13:56
|
|
5
|
+
modified: 2026-07-15 10:20
|
|
6
|
+
last_audited: 2026-07-14
|
|
7
|
+
audit_interval_days: 90
|
|
8
|
+
next_audit: 2026-10-12
|
|
9
|
+
audit_priority: 3
|
|
10
|
+
maturity: tree
|
|
11
|
+
mode: read
|
|
12
|
+
publish: false
|
|
13
|
+
status: active
|
|
14
|
+
tags:
|
|
15
|
+
- package
|
|
16
|
+
- stnd
|
|
17
|
+
theme: kernel
|
|
18
|
+
type: package
|
|
19
|
+
visibility: private
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# @[stnd](../README)/core
|
|
23
|
+
|
|
24
|
+
The **Standard** core integration is the technological foundation of the **Utopie** ecosystem. It provides a coherent framework for design, content rendering, module management, and server utilities.
|
|
25
|
+
|
|
26
|
+
This document serves as a technical reference for developers and AI agents working on the project.
|
|
27
|
+
|
|
28
|
+
## ELI5
|
|
29
|
+
|
|
30
|
+
This is the one line you put in every Standard site’s `astro.config.mjs`. It does the heavy lifting so nothing else has to: finds your feature folders (`modules/`), wires up styles/fonts/SEO automatically (the *“Gold Standard”*), and makes every other `@stnd/*` package available without extra setup. Think of it as the light socket every appliance plugs into.
|
|
31
|
+
|
|
32
|
+
**Install:** `pnpm add @stnd/core` (or use `stnd new` — see `packages/cli`).
|
|
33
|
+
|
|
34
|
+
**Use it:**
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// astro.config.mjs
|
|
38
|
+
import { defineConfig } from "astro/config";
|
|
39
|
+
import standard from "@stnd/core";
|
|
40
|
+
|
|
41
|
+
export default defineConfig({
|
|
42
|
+
integrations: [standard({ title: "My Site" })],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
That’s the whole setup. Styling, fonts, SEO basics, and the command palette are already on. Add your own feature folders under `modules/` and they’re picked up automatically.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Architecture
|
|
51
|
+
|
|
52
|
+
The concept relies on a strict separation between the **Core** (static, tested) and **Modules/Spores** (dynamic, project-specific).
|
|
53
|
+
|
|
54
|
+
- **Standard Integration (`standard.js`)**: The orchestrator. It manages the auto-discovery of modules, injects routes and styles, and configures virtual modules.
|
|
55
|
+
- **Core Helpers (`core/`)**: Libraries of pure functions for authentication, formatting, logging, and data manipulation.
|
|
56
|
+
- **Press Engine (`@stnd/press`)**: High-end Markdown rendering engine (based on `markdown-it`) with support for runes, Obsidian callouts, and fine typography.
|
|
57
|
+
- **Design System (`@stnd/styles`)**: Mathematical design tokens, typographic scales, and the OKLCH color system.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 🔌 Module System (Spores)
|
|
62
|
+
|
|
63
|
+
Modules are auto-discovered and orchestrate both logic and interface via a **Unified Hooks** system.
|
|
64
|
+
|
|
65
|
+
### Anatomy of a module (`index.module.js`):
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
export default {
|
|
69
|
+
id: "module-name",
|
|
70
|
+
|
|
71
|
+
// Hooks: The brain of the module
|
|
72
|
+
// - .js, .ts -> LOGIC (e.g., astro hooks, app events)
|
|
73
|
+
// - .astro, .svelte, .md -> INTERFACE (e.g., header, footer)
|
|
74
|
+
hooks: {
|
|
75
|
+
"astro:config:setup": "./hooks/setup.js", // Astro logic
|
|
76
|
+
"header:top": "./components/Banner.astro", // UI injection point
|
|
77
|
+
"app:init": ["./init.ts", "./Widget.astro"], // Mixed is possible
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
routes: [{ path: "/my-route", entrypoint: "./routes/index.astro" }],
|
|
81
|
+
styles: ["./styles.scss"],
|
|
82
|
+
scripts: ["./client-side.js"],
|
|
83
|
+
middleware: "./middleware.js",
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Virtual Modules & Rendering:
|
|
88
|
+
|
|
89
|
+
- `virtual:stnd/hooks`: To execute **logic hooks** — `.js`/`.ts` entries not prefixed `launcher:` and not suffixed `:action` (those two patterns always classify as UI/action zone contributions instead — see `virtual:stnd/components` below — mismatched classification of the same hook name across registrations is a build-time fatal error). Two calling conventions over the same `hooks:` registrations: `runHook(name, ...args)` fans a call out to every registered handler and collects their results into an array (e.g. `astro:build:done`, `stnd:rss:register`); `runPipeline(name, initialValue, ...args)` threads one value through every registered handler in sequence, each required to explicitly return the (possibly transformed) value — for "let each interested module inspect and optionally override the same thing" (e.g. `apps/stnd.gd`'s `note:render`, where password-gate overrides how a locked note renders without the route naming it). Both isolate handler failures — a throwing handler is logged and skipped, not left to abort the rest. Handlers registered with `server: true` are excluded from the client build of this virtual module — required for any handler touching server-only data (DB calls, secrets), since client-bundled consumers like `packages/launcher/launcher.ts` import from the same module; setting `server: true` on a `launcher:`/`:action`-classified entry is a fatal error rather than a silent no-op, since that pool has no SSR/client split.
|
|
90
|
+
- `virtual:stnd/components`: Used by the `<Hook />` component for UI rendering, and read directly (`extensions[zone]`) by client code that needs callable functions, not just components — e.g. `launcher:action`/`graft:action` entries.
|
|
91
|
+
|
|
92
|
+
**UI Consumption:**
|
|
93
|
+
|
|
94
|
+
```astro
|
|
95
|
+
import Hook from "@stnd/core/Hook.astro";
|
|
96
|
+
<Hook id="header:top" />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🛠 Helpers Reference (Core & Server)
|
|
102
|
+
|
|
103
|
+
Importable via `@stnd/server/[filename]` or `@stnd/utils/[filename]`.
|
|
104
|
+
|
|
105
|
+
### Authentication (`@stnd/server/auth.js`)
|
|
106
|
+
|
|
107
|
+
| Function | Signature | Description |
|
|
108
|
+
| :--- | :--- | :--- |
|
|
109
|
+
| `signJWT` | `signJWT(payload, secret)` | Signs a JWT token via the Web Crypto API. |
|
|
110
|
+
| `verifyJWT` | `verifyJWT(token, secret)` | Verifies and decodes a JWT. |
|
|
111
|
+
| `getSession` | `getSession(request, secret)` | Extracts the session from cookies. |
|
|
112
|
+
| `createSession` | `createSession(payload, secret)` | Generates the `Set-Cookie` header. |
|
|
113
|
+
|
|
114
|
+
### Browser & Client (`@stnd/client`)
|
|
115
|
+
|
|
116
|
+
| Function | Signature | Description |
|
|
117
|
+
| :--- | :--- | :--- |
|
|
118
|
+
| `copyToClipboard` | `copyToClipboard(text)` | Copies to clipboard (with fallback). |
|
|
119
|
+
| `emit` | `emit(name, detail)` | Global event bus (`window.dispatchEvent`). |
|
|
120
|
+
| `on` | `on(name, callback)` | Global event listener. |
|
|
121
|
+
| `standardGateView` | `standardGateView(view)` | Opens a specific view in the Gate. |
|
|
122
|
+
| `standardGatePrompt` | `standardGatePrompt(text)` | Opens the Gate with pre-filled text. |
|
|
123
|
+
|
|
124
|
+
### Document Converter (`@stnd/ingest`)
|
|
125
|
+
|
|
126
|
+
| Function | Signature | Description |
|
|
127
|
+
| :--- | :--- | :--- |
|
|
128
|
+
| `convertDocumentToMarkdown` | `convertDocumentToMarkdown(file)` | Converts DOCX, PDF, HTML, or RTF into Markdown. |
|
|
129
|
+
| `isSupportedDocument` | `isSupportedDocument(file)` | Checks if the file is convertible. |
|
|
130
|
+
|
|
131
|
+
### E-Ink Optimization (`@stnd/client/eink.js`)
|
|
132
|
+
|
|
133
|
+
| Function | Signature | Description |
|
|
134
|
+
| :--- | :--- | :--- |
|
|
135
|
+
| `initEInk` | `initEInk(options)` | Initializes E-Ink detection and styles. |
|
|
136
|
+
| `toggleEInkMode` | `toggleEInkMode()` | Manually toggles E-Ink mode. |
|
|
137
|
+
|
|
138
|
+
### Utils & Data (`@stnd/utils/index.js` & `@stnd/utils/slugify.js`)
|
|
139
|
+
|
|
140
|
+
| Function | Signature | Description |
|
|
141
|
+
| :--- | :--- | :--- |
|
|
142
|
+
| `slugify` | `slugify(text)` | Robust and universal slugifier. |
|
|
143
|
+
| `deepMerge` | `deepMerge(target, source)` | Deeply merges objects. |
|
|
144
|
+
| `getExcerpt` | `getExcerpt(content, opts)` | Extracts a summary (HTML/MD). |
|
|
145
|
+
| `formatDate` | `formatDate(date, format)` | Multilingual date formatting. |
|
|
146
|
+
| `getReadingTime` | `getReadingTime(text)` | Estimated reading time. |
|
|
147
|
+
| `validateEmail` | `validateEmail(email)` | Simplified email validation. |
|
|
148
|
+
|
|
149
|
+
### Other Helpers:
|
|
150
|
+
|
|
151
|
+
- `@stnd/server/cors.js`: `getCorsHeaders`, `handleCorsPreflight`.
|
|
152
|
+
- `@stnd/server/errors.js`: Standardized error classes (`StandardError`, `AuthError`, etc.).
|
|
153
|
+
- `@stnd/server/filename.js`: `sanitizeFilename`, `makeUniqueFilename`.
|
|
154
|
+
- `@stnd/log`: Scoped color logging system (`log.info`, `log.success`, `log.banner`).
|
|
155
|
+
- `@stnd/core/src/collections.js`: `defineStandardCollection` (Astro Content Layer helper).
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 🎨 Design Tokens (CSS Variables)
|
|
160
|
+
|
|
161
|
+
The system uses CSS variables (`--stnd-*` or root variables `:root`) for consistency.
|
|
162
|
+
|
|
163
|
+
### 📐 Geometry & Rhythm (`packages/styles/_standard-01-token.scss`)
|
|
164
|
+
|
|
165
|
+
| Variable | Default Value | Description |
|
|
166
|
+
| :--- | :--- | :--- |
|
|
167
|
+
| `--ratio-golden` | `1.618` | Golden Ratio for layouts. |
|
|
168
|
+
| `--ratio-silver` | `1.414` | Silver Ratio for scales. |
|
|
169
|
+
| `--baseline` | `1rlh` | Base unit of vertical rhythm. |
|
|
170
|
+
| `--space` | `var(--baseline)` | Base spacing. |
|
|
171
|
+
| `--space-[1-12]` | `var(--space) * n` | Modular spacing scale. |
|
|
172
|
+
| `--scale-[2-8]` | `modular scale` | Exponential typographic scale. |
|
|
173
|
+
| `--line-width` | `~42rem` | Optimal reading width (measure). |
|
|
174
|
+
| `--radius` | `--leading` | Border radius consistent with rhythm. |
|
|
175
|
+
|
|
176
|
+
### 🎨 Colors (`packages/styles/_standard-02-color.scss`)
|
|
177
|
+
|
|
178
|
+
OKLCH-based system generating harmony from `--color-accent`.
|
|
179
|
+
|
|
180
|
+
| Semantic Token | Description |
|
|
181
|
+
| :--- | :--- |
|
|
182
|
+
| `--color-background` | Canvas background (paper in light, retro black in dark). |
|
|
183
|
+
| `--color-foreground` | Primary text color. |
|
|
184
|
+
| `--color-accent` | Brand/action color (generates all other colors). |
|
|
185
|
+
| `--color-surface` | Card/container background. |
|
|
186
|
+
| `--color-border` | Subtle borders. |
|
|
187
|
+
| `--color-[green\|red\|yellow\|blue]` | Pigment colors derived from accent. |
|
|
188
|
+
| `--color-success\|error\|warning` | Semantic states. |
|
|
189
|
+
|
|
190
|
+
### 🛠 SCSS Breakpoints
|
|
191
|
+
|
|
192
|
+
- `$mobile`: 600px
|
|
193
|
+
- `$small`: 768px
|
|
194
|
+
- `$large`: 1024px
|
|
195
|
+
- `$wide`: 1440px
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 💡 Notes for AI
|
|
200
|
+
|
|
201
|
+
- **Module Integration**: Standardized on *“Module”* terminology (formerly Folios/Spores).
|
|
202
|
+
- **Actions**: Core actions are now fully decanted into their respective modules and aggregated via `virtual:stnd/actions`.
|
|
203
|
+
- **Data Model**: Business logic belongs in models (`model/`) or `core/` helpers. Astro components are reserved for rendering.
|
|
204
|
+
- **Typography**: Respect vertical rhythm by using `var(--space-*)` for margins and `var(--scale-*)` for font sizes.
|
|
205
|
+
|
|
206
|
+
## Notes / Observations
|
|
207
|
+
|
|
208
|
+
*(jot down anything noticed here — quirks, gotchas, ideas)*
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Todo <!-- was "What's next — open" -->
|
|
213
|
+
|
|
214
|
+
Part of the [Road to Public Release](../README.md#-road-to-public-release) (Phases 1–2). These roll up into [the project board](../../README.md#the-project-board).
|
|
215
|
+
|
|
216
|
+
- [ ] **Purge dead dependencies** — `astro-icon`, `pdfjs-dist`, `html-to-md`, [priority:: 3] [token_scale:: 3] [created:: 2026-07-14] [area:: framework]
|
|
217
|
+
`dotenv`, `unist-util-visit`, `toposort`, `modern-normalize`, `dompurify`, `linkedom`, `markdown-it*` are declared in `package.json` but not referenced by core’s own code (press owns its own copies). Run `pnpm audit:depedencies`.
|
|
218
|
+
- [ ] **`wrangler` is declared here, in press, and in bin** — one owner. [priority:: 3] [token_scale:: 3] [created:: 2026-07-14] [area:: framework]
|
|
219
|
+
- [ ] **Silent no-op** — `prefetch` and `experimental.clientPrerender` set on the [priority:: 3] [token_scale:: 3] [created:: 2026-07-14] [area:: framework]
|
|
220
|
+
integration object at [standard.js:63](standard.js:63) do nothing (Astro only reads `name` + `hooks` on an integration). Route through `updateConfig` or remove.
|
|
221
|
+
- [x] **Alias list is now dynamic** — derived from core’s declared `@stnd/*`
|
|
222
|
+
deps in `standard.js`, so it can never drift. `@stnd/icon` added to deps.
|
|
223
|
+
- [x] **Delete `src/___collections.___js`** — done (disabled dead file removed).
|
|
224
|
+
- [x] **Layering paradox resolved** — core is documented as the sanctioned
|
|
225
|
+
meta-package (its code imports only log + utils; it declares the rest). See the Import Hierarchy note in [packages/README](../README.md#-import-hierarchy-strict).
|
|
226
|
+
- [ ] **`adeSsrDeps` → `@stnd/cloudflare`** (not core) — decided during Phase 2: [priority:: 3] [token_scale:: 3] [created:: 2026-07-14] [area:: framework]
|
|
227
|
+
the SSR/workerd optimizeDeps fix belongs with the Cloudflare package, split from the app-specific deps that stay in the app.
|
|
228
|
+
|
|
229
|
+
## 🔗 Liens & Normes
|
|
230
|
+
|
|
231
|
+
Ce paquet implémente les principes directeurs de l’écosystème Standard. Pour plus de détails sur le fonctionnement global, consultez les documents suivants :
|
|
232
|
+
- **Architecture Système** : [4.1 Architecture](../../apps/stnd.build/modules/manual/content/4-system/4.1-architecture.md)
|
|
233
|
+
- **Principes de Code** : [4.2 Code Standards](../../apps/stnd.build/modules/manual/content/4-system/4.2-code-standards.md)
|
|
234
|
+
- **Normes de Documentation** : [4.9 Normes README](../../apps/stnd.build/modules/manual/content/4-system/4.9-readme-standard.md)
|
|
235
|
+
- **Le Standard** : [packages/README.md](../README.md)
|
|
236
|
+
- **Entrée Monorepo** : [README.md](../../README.md)
|
package/package.json
CHANGED
|
@@ -1,24 +1,101 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stnd/core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.22.1",
|
|
4
|
+
"description": "A fine-art typography framework implementing classical design principles, mathematical precision with the golden ratio, and micro-typography rules inspired by the masters of print design.",
|
|
5
|
+
"author": "Francis Fontaine",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/ZeFish/Standard#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ZeFish/Standard.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/ZeFish/Standard/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"standard",
|
|
17
|
+
"astro-integration"
|
|
18
|
+
],
|
|
19
|
+
"main": "standard.js",
|
|
4
20
|
"type": "module",
|
|
5
|
-
"
|
|
6
|
-
"types": "./src/index.ts",
|
|
21
|
+
"svelte": "./Hook.svelte",
|
|
7
22
|
"exports": {
|
|
8
|
-
".": "./
|
|
23
|
+
".": "./standard.js",
|
|
24
|
+
"./Hook": {
|
|
25
|
+
"astro": "./Hook.astro",
|
|
26
|
+
"svelte": "./Hook.svelte",
|
|
27
|
+
"default": "./Hook.astro"
|
|
28
|
+
},
|
|
29
|
+
"./loader": "./src/loader.js",
|
|
30
|
+
"./virtual": "./src/virtual.js",
|
|
31
|
+
"./src/dev-toolbar/*": "./src/dev-toolbar/*",
|
|
32
|
+
"./types/*": "./types/*"
|
|
9
33
|
},
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
34
|
+
"files": [
|
|
35
|
+
"standard.js",
|
|
36
|
+
"types",
|
|
37
|
+
"src"
|
|
38
|
+
],
|
|
39
|
+
"browserslist": [
|
|
40
|
+
"> 1%",
|
|
41
|
+
"last 2 versions",
|
|
42
|
+
"not dead"
|
|
43
|
+
],
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=14.0.0"
|
|
17
46
|
},
|
|
18
47
|
"publishConfig": {
|
|
19
48
|
"access": "public"
|
|
20
49
|
},
|
|
50
|
+
"directories": {
|
|
51
|
+
"example": "example"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@astrojs/sitemap": "^3.7.3",
|
|
55
|
+
"@astrojs/svelte": "9.0.1",
|
|
56
|
+
"astro": "7.1.3",
|
|
57
|
+
"glob": "^13.0.6",
|
|
58
|
+
"svelte": "5.56.4",
|
|
59
|
+
"zod": "^4.4.3",
|
|
60
|
+
"@stnd/client": "0.5.0",
|
|
61
|
+
"@stnd/cloudflare": "0.5.0",
|
|
62
|
+
"@stnd/fonts": "0.5.0",
|
|
63
|
+
"@stnd/icon": "0.2.0",
|
|
64
|
+
"@stnd/lab": "1.0.0",
|
|
65
|
+
"@stnd/launcher": "1.0.0",
|
|
66
|
+
"@stnd/layout": "0.5.0",
|
|
67
|
+
"@stnd/log": "0.5.0",
|
|
68
|
+
"@stnd/modules": "0.5.1",
|
|
69
|
+
"@stnd/press": "0.7.0",
|
|
70
|
+
"@stnd/store": "0.5.0",
|
|
71
|
+
"@stnd/styles": "0.5.2",
|
|
72
|
+
"@stnd/themes": "0.5.1",
|
|
73
|
+
"@stnd/ui": "0.5.0",
|
|
74
|
+
"@stnd/utils": "0.5.0"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@playwright/test": "^1.61.1",
|
|
78
|
+
"@types/node": "^26.0.1",
|
|
79
|
+
"auto-changelog": "^2.6.0",
|
|
80
|
+
"happy-dom": "^20.10.6",
|
|
81
|
+
"knip": "^6.23.0",
|
|
82
|
+
"prettier": "^3.9.1",
|
|
83
|
+
"prettier-plugin-astro": "^0.14.1",
|
|
84
|
+
"prettier-plugin-svelte": "^4.1.1",
|
|
85
|
+
"typescript": "^6.0.3",
|
|
86
|
+
"vitest": "^4.1.9"
|
|
87
|
+
},
|
|
88
|
+
"types": "./types/standard.d.ts",
|
|
21
89
|
"scripts": {
|
|
22
|
-
"
|
|
90
|
+
"build": "echo 'No build step required for core integration library'",
|
|
91
|
+
"dev": "pnpm run clean:ports && astro dev",
|
|
92
|
+
"start": "astro dev",
|
|
93
|
+
"kill-node": "killall node || taskkill /F /IM node.exe",
|
|
94
|
+
"commit": "git add . && git commit",
|
|
95
|
+
"test": "pnpm run test:lib",
|
|
96
|
+
"test:lib": "vitest run",
|
|
97
|
+
"audit:depedencies": "knip && ncu",
|
|
98
|
+
"clean:depedencies": "knip --fix --include dependencies,devDependencies",
|
|
99
|
+
"clean:ports": "lsof -ti:8083 | xargs kill -9 2>/dev/null || true"
|
|
23
100
|
}
|
|
24
101
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineToolbarApp } from "astro/toolbar";
|
|
2
|
+
|
|
3
|
+
export default defineToolbarApp({
|
|
4
|
+
init(canvas, app) {
|
|
5
|
+
// Restore previous debug state
|
|
6
|
+
const wasActive = sessionStorage.getItem("stnd-debug-mode") === "true";
|
|
7
|
+
if (wasActive) {
|
|
8
|
+
document.documentElement.classList.add("stnd-debug");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
app.onToggled(({ state }) => {
|
|
12
|
+
if (state) {
|
|
13
|
+
document.documentElement.classList.add("stnd-debug");
|
|
14
|
+
sessionStorage.setItem("stnd-debug-mode", "true");
|
|
15
|
+
} else {
|
|
16
|
+
document.documentElement.classList.remove("stnd-debug");
|
|
17
|
+
sessionStorage.setItem("stnd-debug-mode", "false");
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { defineToolbarApp } from "astro/toolbar";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resilient Lab toggle
|
|
5
|
+
*
|
|
6
|
+
* Behavior:
|
|
7
|
+
* - If `window.StandardLab` is present, open the quick panel immediately.
|
|
8
|
+
* - Otherwise, attempt a dynamic import of the lab client (best-effort),
|
|
9
|
+
* and wait briefly for the global to become available before opening.
|
|
10
|
+
* - Always toggle the toolbar state back off (momentary action).
|
|
11
|
+
*/
|
|
12
|
+
function waitForStandardLab(timeout = 3000, interval = 100) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const start = Date.now();
|
|
15
|
+
const check = () => {
|
|
16
|
+
if (window.StandardLab) return resolve(window.StandardLab);
|
|
17
|
+
if (Date.now() - start >= timeout)
|
|
18
|
+
return reject(new Error("StandardLab not available within timeout"));
|
|
19
|
+
setTimeout(check, interval);
|
|
20
|
+
};
|
|
21
|
+
check();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default defineToolbarApp({
|
|
26
|
+
init(canvas, app) {
|
|
27
|
+
// Poller handle (fallback if StandardLab doesn't emit events)
|
|
28
|
+
let stateWatcher = null;
|
|
29
|
+
// When toggles are triggered programmatically we should suppress
|
|
30
|
+
// the onToggled handler to avoid re-entrancy loops.
|
|
31
|
+
let suppressToggleHandler = false;
|
|
32
|
+
|
|
33
|
+
function startPoller() {
|
|
34
|
+
if (stateWatcher) return;
|
|
35
|
+
// Skip the first sample to avoid a race with initialization events
|
|
36
|
+
let grace = true;
|
|
37
|
+
stateWatcher = setInterval(() => {
|
|
38
|
+
if (!window.StandardLab) return;
|
|
39
|
+
if (grace) {
|
|
40
|
+
// Give the client a moment to initialize and emit its state event
|
|
41
|
+
grace = false;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (window.StandardLab.state === "dormant") {
|
|
45
|
+
clearInterval(stateWatcher);
|
|
46
|
+
stateWatcher = null;
|
|
47
|
+
// Ensure programmatic toggle doesn't re-enter the handler
|
|
48
|
+
suppressToggleHandler = true;
|
|
49
|
+
app.toggleState({ state: false });
|
|
50
|
+
setTimeout(() => (suppressToggleHandler = false), 0);
|
|
51
|
+
} else {
|
|
52
|
+
// Keep toolbar toggled while lab is open
|
|
53
|
+
suppressToggleHandler = true;
|
|
54
|
+
app.toggleState({ state: true });
|
|
55
|
+
setTimeout(() => (suppressToggleHandler = false), 0);
|
|
56
|
+
}
|
|
57
|
+
}, 250);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function clearPoller() {
|
|
61
|
+
if (stateWatcher) {
|
|
62
|
+
clearInterval(stateWatcher);
|
|
63
|
+
stateWatcher = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Listen for StandardLab state events (if the client emits them)
|
|
68
|
+
const globalStateListener = (e) => {
|
|
69
|
+
const s = e?.detail?.state;
|
|
70
|
+
if (s === "dormant") {
|
|
71
|
+
clearPoller();
|
|
72
|
+
// Suppress the onToggled handler while we programmatically toggle the button
|
|
73
|
+
suppressToggleHandler = true;
|
|
74
|
+
app.toggleState({ state: false });
|
|
75
|
+
setTimeout(() => (suppressToggleHandler = false), 0);
|
|
76
|
+
} else if (s === "quick" || s === "full") {
|
|
77
|
+
// Suppress the onToggled handler while we programmatically toggle the button
|
|
78
|
+
suppressToggleHandler = true;
|
|
79
|
+
app.toggleState({ state: true });
|
|
80
|
+
setTimeout(() => (suppressToggleHandler = false), 0);
|
|
81
|
+
startPoller();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
window.addEventListener("standardlab:state", globalStateListener);
|
|
85
|
+
|
|
86
|
+
// Initial sync: if the lab is already open, reflect that in the toolbar
|
|
87
|
+
if (
|
|
88
|
+
window.StandardLab &&
|
|
89
|
+
window.StandardLab.state &&
|
|
90
|
+
window.StandardLab.state !== "dormant"
|
|
91
|
+
) {
|
|
92
|
+
app.toggleState({ state: true });
|
|
93
|
+
startPoller();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
app.onToggled(({ state }) => {
|
|
97
|
+
// If this toggle came from our own programmatic updates, ignore it
|
|
98
|
+
if (suppressToggleHandler) return;
|
|
99
|
+
if (state) {
|
|
100
|
+
(async () => {
|
|
101
|
+
try {
|
|
102
|
+
// The Svelte Lab island (<Lab client:load />) provides
|
|
103
|
+
// window.StandardLab automatically. If it hasn't hydrated
|
|
104
|
+
// yet, wait briefly for it.
|
|
105
|
+
if (
|
|
106
|
+
!window.StandardLab ||
|
|
107
|
+
typeof window.StandardLab.openQuick !== "function"
|
|
108
|
+
) {
|
|
109
|
+
await waitForStandardLab(3000, 100);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
window.StandardLab &&
|
|
114
|
+
typeof window.StandardLab.openQuick === "function"
|
|
115
|
+
) {
|
|
116
|
+
window.StandardLab.openQuick();
|
|
117
|
+
} else {
|
|
118
|
+
console.warn(
|
|
119
|
+
"[Standard Lab] Instance not available. Ensure <Lab client:load /> is mounted.",
|
|
120
|
+
);
|
|
121
|
+
suppressToggleHandler = true;
|
|
122
|
+
app.toggleState({ state: false });
|
|
123
|
+
setTimeout(() => (suppressToggleHandler = false), 0);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Ensure we keep the toolbar toggled while the lab is open
|
|
128
|
+
startPoller();
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.warn("[Standard Lab] Error while trying to open:", err);
|
|
131
|
+
suppressToggleHandler = true;
|
|
132
|
+
app.toggleState({ state: false });
|
|
133
|
+
setTimeout(() => (suppressToggleHandler = false), 0);
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
} else {
|
|
137
|
+
// User toggled the toolbar off -> close the lab (if open) and stop syncing
|
|
138
|
+
if (
|
|
139
|
+
window.StandardLab &&
|
|
140
|
+
typeof window.StandardLab.close === "function" &&
|
|
141
|
+
window.StandardLab.state !== "dormant"
|
|
142
|
+
) {
|
|
143
|
+
try {
|
|
144
|
+
window.StandardLab.close();
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.warn("[Standard Lab] Error while trying to close:", err);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
clearPoller();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
});
|