@orbytes/astrolab 0.3.0 → 0.4.0-next.2
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 +261 -116
- package/bin/pin-gallery.mjs +53 -19
- package/defaults.mjs +72 -20
- package/dist/core/virtual-module/virtual-routes.js +12 -1
- package/docs/PIN-CONTRACT.md +84 -10
- package/docs/PIN.md +117 -37
- package/index.d.ts +41 -23
- package/index.mjs +46 -91
- package/package.json +7 -3
- package/src/Home.astro +166 -264
- package/src/LabHead.astro +37 -1047
- package/src/chrome/ActionsMenu.astro +97 -0
- package/src/chrome/ComponentCard.astro +76 -0
- package/src/chrome/Icon.astro +21 -0
- package/src/chrome/LICENSE-icons +43 -0
- package/src/chrome/Nav.astro +131 -0
- package/src/chrome/Panel.astro +117 -0
- package/src/chrome/Properties.astro +104 -0
- package/src/chrome/SectionsTree.astro +128 -0
- package/src/chrome/Shell.astro +120 -0
- package/src/chrome/Sprite.astro +23 -0
- package/src/chrome/StoryView.astro +192 -0
- package/src/chrome/Tree.astro +86 -0
- package/src/chrome/ViewportControls.astro +173 -0
- package/src/chrome/ViewportStage.astro +55 -0
- package/src/chrome/fonts/OFL.txt +93 -0
- package/src/chrome/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/src/chrome/icons.ts +68 -0
- package/src/chrome/marks-client.ts +75 -0
- package/src/chrome/model.ts +156 -0
- package/src/chrome/navbar-client.ts +324 -0
- package/src/chrome/params-client.ts +434 -0
- package/src/chrome/pins-data.ts +63 -0
- package/src/chrome/shell-client.ts +468 -0
- package/src/chrome/site-data.ts +230 -0
- package/src/chrome/trees.ts +257 -0
- package/src/chrome/viewport-client.ts +405 -0
- package/src/chrome/views/Assets.astro +125 -0
- package/src/chrome/views/Pages.astro +214 -0
- package/src/chrome/views/Placeholder.astro +37 -0
- package/src/chrome/views/Tasks.astro +79 -0
- package/src/core/LICENSE-astrobook +21 -0
- package/src/core/lib/components/home.astro +4 -2
- package/src/core/lib/pages/story.astro +12 -10
- package/src/core/utils/kebab-case.ts +2 -2
- package/src/core/virtual-module/virtual-routes.ts +20 -4
- package/src/pin/board.mjs +414 -190
- package/src/pin/index.mjs +67 -22
- package/src/pin/tickets.mjs +6 -5
- package/src/pin/toolbar.js +82 -4
- package/src/shell/Browse.astro +131 -353
- package/src/shell/Viewport.astro +22 -1315
- package/src/shell/lab-index.ts +28 -18
- package/src/shell/lab-params.ts +113 -6
- package/src/shell/live-files.mjs +212 -10
- package/src/shell/marks.mjs +17 -41
- package/src/ui/components/app.astro +5 -7
- package/src/ui/components/preview-layout.astro +17 -0
- package/src/ui/components/theme-script.astro +17 -5
- package/src/ui/lab.css +3754 -371
- package/virtual.d.ts +13 -4
- package/bin/lab-cull.mjs +0 -401
- package/src/shell/CardGrid.astro +0 -297
- package/src/ui/components/build-path.ts +0 -13
- package/src/ui/components/build-tree.ts +0 -108
- package/src/ui/components/collapse-duration.ts +0 -28
- package/src/ui/components/compress-terms.ts +0 -10
- package/src/ui/components/dashboard-layout.astro +0 -39
- package/src/ui/components/home.astro +0 -65
- package/src/ui/components/layout.astro +0 -110
- package/src/ui/components/sidebar-button-fullscreen.astro +0 -38
- package/src/ui/components/sidebar-button-search.astro +0 -23
- package/src/ui/components/sidebar-button-theme.astro +0 -9
- package/src/ui/components/sidebar-button.astro +0 -24
- package/src/ui/components/sidebar-resize-handle.astro +0 -74
- package/src/ui/components/sidebar-search-panel.astro +0 -41
- package/src/ui/components/sidebar-search-script.ts +0 -103
- package/src/ui/components/sidebar-title.astro +0 -17
- package/src/ui/components/sidebar-tree-node.astro +0 -143
- package/src/ui/components/sidebar-tree.astro +0 -84
- package/src/ui/components/sidebar.astro +0 -29
- package/src/ui/components/theme-toggle.astro +0 -63
package/README.md
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# @orbytes/astrolab
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
playground normally cannot tell you
|
|
8
|
-
from the pages rather than declared.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
A local dev tool for an Astro site, in one package and one integration call. Everything runs on
|
|
4
|
+
the site's own dev server at `/lab`; nothing leaves the machine.
|
|
5
|
+
|
|
6
|
+
**The lab** — every page, section and component in the build, rendered live on a canvas you size,
|
|
7
|
+
with the one thing a component playground normally cannot tell you: **which of these is actually on
|
|
8
|
+
the site right now**, derived from the pages rather than declared. Its engine began as
|
|
9
|
+
[Astrobook](https://github.com/ocavue/astrobook), vendored; its chrome is this package's own.
|
|
10
|
+
|
|
11
|
+
**Tasks — the pin board** at `/lab/tasks` — click a rendered element on `astro dev`, leave a
|
|
12
|
+
comment, and a markdown ticket plus a screenshot land on local disk for an agent to pick up
|
|
13
|
+
([docs/PIN.md](./docs/PIN.md), contract at [docs/PIN-CONTRACT.md](./docs/PIN-CONTRACT.md)). Kanban and
|
|
14
|
+
table views in the lab, and a **Comment** button in its navbar that pins an element of the page it
|
|
15
|
+
is previewing. Dev only, and only in a site that is inside a git repository (see
|
|
16
|
+
[Tasks](#tasks--the-pin-board-at-labtasks)); switchable off with `pin: false`. `/pin`, its old
|
|
17
|
+
address, redirects there.
|
|
18
|
+
|
|
19
|
+
It ships mostly **source**. The `.astro` files, and everything under `src/chrome/`, `src/shell/`,
|
|
20
|
+
`src/ui/` and `src/types/`, are compiled by the consumer's own Vite, the way Starlight ships its components —
|
|
21
|
+
which is why the lab picks up the consumer's tokens, fonts and breakpoints in a variant's preview: it
|
|
19
22
|
renders inside the consumer's pipeline. The one exception is `src/core/`, transpiled to `dist/` by
|
|
20
23
|
`scripts/build-core.mjs` at `prepack` and `prepare`, because `astro.config.mjs` is loaded by Node's
|
|
21
24
|
own ESM loader and Node refuses to strip TypeScript under `node_modules`. Without that step an
|
|
@@ -24,9 +27,12 @@ installed copy cannot be loaded at all. The script's header comment is the full
|
|
|
24
27
|
## Install
|
|
25
28
|
|
|
26
29
|
```sh
|
|
27
|
-
npm i -D @orbytes/astrolab
|
|
30
|
+
npm i -D -E @orbytes/astrolab
|
|
28
31
|
```
|
|
29
32
|
|
|
33
|
+
`-E` (`--save-exact`) pins the version: a lab upgrade is a deliberate change to a site, never a
|
|
34
|
+
side effect of `npm install`.
|
|
35
|
+
|
|
30
36
|
Then add the integration:
|
|
31
37
|
|
|
32
38
|
```js
|
|
@@ -37,18 +43,26 @@ import orbytesLab from "@orbytes/astrolab";
|
|
|
37
43
|
export default defineConfig({
|
|
38
44
|
integrations: [
|
|
39
45
|
orbytesLab({
|
|
40
|
-
css: ["./src/styles/global.css"], // your
|
|
46
|
+
css: ["./src/styles/global.css"], // optional: your site's own stylesheet(s), for the previews
|
|
41
47
|
}),
|
|
42
48
|
],
|
|
43
49
|
});
|
|
44
50
|
```
|
|
45
51
|
|
|
52
|
+
`css` is optional, and `./src/styles/global.css` is only an example: name whatever stylesheet your
|
|
53
|
+
site's layout imports. A new Astro project has no such file, so either point `css` at the one you
|
|
54
|
+
have or leave the option out. Left out, it is `[]` and the variant previews get no site stylesheet
|
|
55
|
+
at all, only each component's own scoped styles; the lab's chrome never gets it either way. Each
|
|
56
|
+
entry becomes a plain `import` in the preview's layout (a path starting with `.` resolves against
|
|
57
|
+
the project root, anything else as a package), so name only files that exist.
|
|
58
|
+
|
|
46
59
|
That is the whole configuration. In particular, **do not add an `ssr.noExternal` entry for this
|
|
47
60
|
package** — the integration adds its own, because the package ships `.astro` files that Vite would
|
|
48
61
|
otherwise externalise and hand to Node, which cannot read them. A consumer needs nothing. (Two
|
|
49
62
|
older orbytes repos set it by hand; it is redundant, and copying it from them is copying noise.)
|
|
50
63
|
|
|
51
|
-
|
|
64
|
+
Each named export in a stories file is a story; the lab lists them as **Variants**. Now write a
|
|
65
|
+
story. Stories live under `src/lab/`, in one directory per tier:
|
|
52
66
|
|
|
53
67
|
```
|
|
54
68
|
src/lab/
|
|
@@ -56,12 +70,11 @@ src/lab/
|
|
|
56
70
|
components/Button.astro + Button.stories.ts
|
|
57
71
|
explorations/KeycapButton.astro + KeycapButton.stories.ts
|
|
58
72
|
decorators/ ← yours, built against YOUR page shell (see below)
|
|
59
|
-
cull.json ← written by the lab, commit it
|
|
60
73
|
responsive.json ← written by the lab, commit it
|
|
61
74
|
```
|
|
62
75
|
|
|
63
76
|
A stories file has a default export naming the component, and one named export per story. The
|
|
64
|
-
export's name is
|
|
77
|
+
export's name is the variant's label in the lab; its `args` are the component's props.
|
|
65
78
|
|
|
66
79
|
```ts
|
|
67
80
|
// src/lab/components/Button.stories.ts
|
|
@@ -76,8 +89,17 @@ export const Subtle = { args: { label: "See pricing", variant: "subtle" } };
|
|
|
76
89
|
export const Strong = { args: { label: "Book a call", variant: "strong" } };
|
|
77
90
|
```
|
|
78
91
|
|
|
79
|
-
Then `npm run dev` and open **`http://localhost:4321/lab
|
|
80
|
-
|
|
92
|
+
Then `npm run dev` and open **`http://localhost:4321/lab`**. `subpath` moves it; `directory` moves
|
|
93
|
+
the stories.
|
|
94
|
+
|
|
95
|
+
**Test versions** are published under the `next` tag while the redesign settles:
|
|
96
|
+
`npm i -D -E @orbytes/astrolab@next`. `latest` stays on the last stable release until 1.0.
|
|
97
|
+
|
|
98
|
+
**Upgrading from 0.3.** The mark-for-deletion feature (the cull) is removed, decided 2026-09-24.
|
|
99
|
+
The `orbytes-lab-cull` command is gone, so delete any `lab:cull` script that calls it, and the
|
|
100
|
+
`src/lab/cull.json` it kept. The `cullTier`, `cullDir` and `cullFile` options and a tier's
|
|
101
|
+
`cullable` flag are no longer read: a config that still sets them builds, but they do nothing, and
|
|
102
|
+
a typed config will flag them. Nothing else a 0.3 site configured changes meaning.
|
|
81
103
|
|
|
82
104
|
**Peers.** Astro **7 or newer**, narrowed from `>=5.2.0` on 2026-09-22 when the pin board moved in:
|
|
83
105
|
the board's dev-toolbar app imports `astro/client/dev-toolbar/apps/utils/highlight.js` and
|
|
@@ -167,10 +189,15 @@ than silent.
|
|
|
167
189
|
|
|
168
190
|
### What is vendored
|
|
169
191
|
|
|
170
|
-
**Astrobook is not a dependency**, and neither is anything it used to pull in.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
**Astrobook is not a dependency**, and neither is anything it used to pull in. Its core is vendored
|
|
193
|
+
at version `0.13.3` into [`src/core/`](./src/core/), its types into [`src/types/`](./src/types/), and
|
|
194
|
+
what the preview still needs of its UI into [`src/ui/`](./src/ui/) — the rest of that UI was retired
|
|
195
|
+
by the redesign (2026-09-24), whose chrome is this package's own, in [`src/chrome/`](./src/chrome/).
|
|
196
|
+
Nothing outside Astro is installed, and there is nothing to keep in step but this folder.
|
|
197
|
+
|
|
198
|
+
The chrome bundles two third-party assets, each with its licence beside it: **Inter** (SIL OFL,
|
|
199
|
+
[`src/chrome/fonts/`](./src/chrome/fonts/)) and **Lucide** icons (ISC,
|
|
200
|
+
[`src/chrome/LICENSE-icons`](./src/chrome/LICENSE-icons)).
|
|
174
201
|
|
|
175
202
|
Attribution, the upstream commit, and every deliberate divergence from it are recorded in
|
|
176
203
|
[`src/core/LICENSE-astrobook`](./src/core/LICENSE-astrobook) (MIT, Copyright (c) 2024 ocavue; the
|
|
@@ -183,19 +210,19 @@ This package is MIT too; see [LICENSE](./LICENSE).
|
|
|
183
210
|
### The chrome is not styled by the website
|
|
184
211
|
|
|
185
212
|
**Decided 2026-09-22.** The lab's own furniture looks the same in every site it is installed in,
|
|
186
|
-
and only the rendered
|
|
213
|
+
and only the rendered variant takes the site's styling. A site that wants a different-looking lab
|
|
187
214
|
edits this package's source: there is no theme API and no token override, on purpose.
|
|
188
215
|
|
|
189
|
-
So the chrome —
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
216
|
+
So the chrome — both sidebar levels, the navbar, every view — is drawn by one stylesheet that ships
|
|
217
|
+
with this package, [`src/ui/lab.css`](./src/ui/lab.css), in `--lab-*` tokens that cannot collide with
|
|
218
|
+
a site's own, in Inter, with light and dark themes of its own. It never receives the `css` list or
|
|
219
|
+
the `head` component.
|
|
193
220
|
|
|
194
|
-
The **preview** — the
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
page
|
|
198
|
-
The
|
|
221
|
+
The **preview** — the bare variant at `/lab/stories/<id>`, and the site's own pages — gets both,
|
|
222
|
+
because seeing a component as the real site renders it is the whole point. Since the redesign the
|
|
223
|
+
chrome only ever shows a preview **inside an `<iframe>`**, so the two never share a document: no
|
|
224
|
+
chrome page carries a byte of the site's CSS, and the chrome's stylesheet never reaches a variant.
|
|
225
|
+
The preview also keeps its **own** light/dark, separate from the chrome's.
|
|
199
226
|
|
|
200
227
|
## Options
|
|
201
228
|
|
|
@@ -205,13 +232,14 @@ Every option is optional.
|
|
|
205
232
|
| --- | --- | --- |
|
|
206
233
|
| `directory` | `"src/lab"` | Where the stories live, relative to the project root. |
|
|
207
234
|
| `subpath` | `"/lab"` | Where the lab is served. `""` serves it at the site root. |
|
|
208
|
-
| `css` | `[]` | The site's token and base stylesheets
|
|
209
|
-
| `head` | — | A path to the site's own head component, rendered
|
|
210
|
-
| `title` | `"Component lab"` | The
|
|
211
|
-
| `stagingUrl` | — | The deployed lab's base URL — the real minified build, which a dev server is not. Every card and
|
|
235
|
+
| `css` | `[]` | The site's own token and base stylesheets, if you want the previews styled like the site (see [Install](#install)). They reach the variant **previews** only, never the lab's own chrome. |
|
|
236
|
+
| `head` | — | A path to the site's own head component, rendered inside every variant preview's head. This is how fonts arrive: variants never render through the site's layout, so whatever that layout registers has to be repeated here. |
|
|
237
|
+
| `title` | `"Component lab"` | The site's name as the lab shows it — the tab title and the home page. |
|
|
238
|
+
| `stagingUrl` | — | The deployed lab's base URL — the real minified build, which a dev server is not. Every card and every component page link to the same variant there. Omit it and those links do not appear. |
|
|
212
239
|
| `previewUrlTemplate` | — | A branch-preview URL with a `{branch}` placeholder, e.g. `"https://{branch}-my-site.workers.dev"`. The home page then lists the local git branches with a link each. Omit it and there is no branch list. It is a string, not a function, because the config crosses a virtual module and has to stay JSON-serialisable. |
|
|
213
240
|
| `feedbucketKey` | — | Set it and every lab page carries the [Feedbucket](https://feedbucket.app) tag. The package takes a key or nothing: the consumer keeps its own staging gate, so pass the key only where the widget belongs. |
|
|
214
|
-
| `tiers` | `["sections", "components", "explorations"]` | The tiers, in reading order. A tier is the first path segment under `directory`. |
|
|
241
|
+
| `tiers` | `["sections", "components", "explorations"]` | The tiers, in reading order. A tier is the first path segment under `directory`. A tier with no stories files is left out of the menu. |
|
|
242
|
+
| `viewports` | the orbytes scale | The viewport's screen sizes: `design` widths (the device switch), `breakpoints` (the band the Viewport field names, and the presets) and named `devices`. See [Viewports](#viewports). |
|
|
215
243
|
| `pin` | `true` | The pin board. `false` omits it entirely; an object is the board's own options — see below. |
|
|
216
244
|
|
|
217
245
|
### Tiers
|
|
@@ -223,39 +251,120 @@ what each one *is*:
|
|
|
223
251
|
tiers: [
|
|
224
252
|
{ id: "pages", label: "Page sections", responsive: true },
|
|
225
253
|
"atoms",
|
|
226
|
-
{ id: "sketches", label: "Sketches"
|
|
254
|
+
{ id: "sketches", label: "Sketches" },
|
|
227
255
|
]
|
|
228
256
|
```
|
|
229
257
|
|
|
230
|
-
- **`responsive`** — this tier holds page sections
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
`
|
|
235
|
-
a browser. Defaults to true for a tier named `explorations`.
|
|
258
|
+
- **`responsive`** — this tier holds page sections, laid out `<tier>/<Section>/<Version>/`; the lab
|
|
259
|
+
reads the section and version from those folders whatever the tier is called. Its variants carry
|
|
260
|
+
the responsive mark, and they render at page width (1440×900) in thumbnails and as the viewport's
|
|
261
|
+
default frame; everything else renders on a component stage (960×540). Defaults to true for a
|
|
262
|
+
tier named `sections`.
|
|
236
263
|
|
|
237
|
-
At most one tier holds
|
|
264
|
+
At most one tier holds the role.
|
|
238
265
|
|
|
239
|
-
|
|
266
|
+
### Viewports
|
|
240
267
|
|
|
241
|
-
|
|
268
|
+
```js
|
|
269
|
+
viewports: {
|
|
270
|
+
design: [
|
|
271
|
+
{ label: "Desktop", width: 1440, icon: "monitor" },
|
|
272
|
+
{ label: "Phone", width: 390, icon: "phone" }, // icons: monitor, tablet, phone-landscape, phone
|
|
273
|
+
],
|
|
274
|
+
breakpoints: [{ name: "desktop", min: 1024 }, { name: "mobile", min: 0 }],
|
|
275
|
+
devices: [{ label: "Pixel 8", width: 412, height: 915 }],
|
|
276
|
+
}
|
|
277
|
+
```
|
|
242
278
|
|
|
243
|
-
|
|
279
|
+
Each key is optional and replaces its default whole. `breakpoints` are lower edges, widest first,
|
|
280
|
+
the last one `0`; the presets offer both sides of every edge (1024 and 1023).
|
|
244
281
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
282
|
+
## What you get
|
|
283
|
+
|
|
284
|
+
Two sidebar levels — the menu (Home; Site: Pages, one entry per tier, Assets; Tasks; Support and
|
|
285
|
+
Settings), with the lab's own light/dark switch (a moon) beside the logo, and a panel with a
|
|
286
|
+
breadcrumb, a search box and a tree — and a main area with a navbar over the content. The menu
|
|
287
|
+
shrinks to a 64px icon rail, where the moon sits under the logomark and each group opens as a
|
|
288
|
+
flyout under its own name. Kanban Board and All Tasks have no panel. The panel's tree has no icons
|
|
289
|
+
and reads at 12px.
|
|
290
|
+
|
|
291
|
+
**`<subpath>/`** — Home: counts for pages, each tier, assets and open tasks, and every page's
|
|
292
|
+
mounted components in slot order — what is on the site right now.
|
|
293
|
+
|
|
294
|
+
**`<subpath>/pages/<route>`** — the site's own pages, in a tree nested the way the routes nest, each
|
|
295
|
+
shown on the viewport canvas at its real URL. The navbar is the component page's, less the
|
|
296
|
+
variants: the page's name and route, the eye (below), a menu of the components the page mounts, in
|
|
297
|
+
slot order, and **Reload** and **Open page** beside the viewport controls. `<subpath>/pages` lists
|
|
298
|
+
them all.
|
|
299
|
+
|
|
300
|
+
**`<subpath>/browse/<tier>`** — a tier (Sections, Components, …): its components as cards with a live
|
|
301
|
+
thumbnail, under a title bar with filter chips (All · Live · Used · Unused). A live component's card
|
|
302
|
+
carries a green badge with its slot number; the others carry a *used by N* pill when something live
|
|
303
|
+
uses them, and a section carries its responsive state. On the sections tier the panel's filter
|
|
304
|
+
menu adds responsive state.
|
|
305
|
+
|
|
306
|
+
On the sections tier the panel is the **Sections tree**: one row per section with a slot badge —
|
|
307
|
+
green with its slot number, a grey dash when no page mounts it, violet on the row you are on. The
|
|
308
|
+
mounted sections come first; the rest sit under *Unslotted · on no page*. **Sort by** switches
|
|
309
|
+
between slot number and name (A–Z) and is remembered in this browser.
|
|
310
|
+
|
|
311
|
+
**`<subpath>/dashboard/<story>`** — a component's page. The tree stops at the component, and the
|
|
312
|
+
navbar carries the rest:
|
|
313
|
+
|
|
314
|
+
- **The name, the version and an eye.** Hovering the eye opens the **Properties** panel: where the
|
|
315
|
+
component is live and in which slot (or what uses it, or *Not on any page*), its open and resolved
|
|
316
|
+
pins, the two responsive marks as checkboxes (sections only, see [Marks](#marks)) and its source
|
|
317
|
+
file. Clicking the eye keeps the panel open; Escape or a click elsewhere closes it.
|
|
318
|
+
- **Variants** — always a dropdown ("Default 1 of 3"), whatever the count — and previous / next
|
|
319
|
+
buttons, with `j`/`k` stepping through every variant of the tier.
|
|
320
|
+
- **The device switch and the Viewport field.** The switch picks a design width. The field is the
|
|
321
|
+
frame's width × height, typed in place, and a button naming the band and the zoom ("tablet Fit")
|
|
322
|
+
that opens the presets: Rotate, both sides of every breakpoint edge, the named devices and a zoom
|
|
323
|
+
row (Fit, 25–200%). Drag the frame's right edge, bottom edge or corner to resize it. Fit is the
|
|
324
|
+
default: it scales the frame to fit the stage in both directions, and never past 100%.
|
|
325
|
+
- **Comment**, where the pin board runs — see [Pinning from the lab](#pinning-from-the-lab).
|
|
326
|
+
- **Parameters**, only for a variant that registers some — see [Parameters panel](#parameters-panel).
|
|
327
|
+
- **⋯** — everything that leaves the lab (Open in VS Code, open the bare variant full screen, view
|
|
328
|
+
on staging, copy a link to this exact size), then two sets of switches: **Pins** (Show pins, on;
|
|
329
|
+
Show resolved, off) and **Preview › Dark mode**, which appears only when the framed site has a
|
|
330
|
+
dark mode of its own and switches the preview, not the lab.
|
|
331
|
+
|
|
332
|
+
The navbar is one row when it is 1040px wide or more, and two rows below that, the viewport row
|
|
333
|
+
last. The viewport is in the URL (`?w=390&h=844&z=fit`), so a link names the exact frame, and it
|
|
334
|
+
rides along when you step between variants. `[` `]` step the widths, `r` rotates, `f` fits, `0` is
|
|
335
|
+
100%.
|
|
336
|
+
|
|
337
|
+
**`<subpath>/assets`** — every image under `src/` and `public/`, its size and dimensions, and which
|
|
338
|
+
files reference it, so "what does nothing use?" is a filter.
|
|
339
|
+
|
|
340
|
+
**`<subpath>/tasks`**, **`<subpath>/tasks/all`** — the pin board, dev only. See below.
|
|
249
341
|
|
|
250
342
|
**`<subpath>/index.json`** — the whole index as a static file: every story with its id, URLs,
|
|
251
343
|
files, tier/section/version, live slot, used-by list, tags and summary. Scripts and agent briefs
|
|
252
344
|
read this instead of globbing.
|
|
253
345
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
346
|
+
**`<subpath>/viewport/<story>`** — the old viewport configurator's address, now a redirect to the
|
|
347
|
+
component page, kept so old links still land.
|
|
348
|
+
|
|
349
|
+
**Keys.** `⌘B` closes and opens the panel, `⌘⇧B` shrinks the menu to icons, `⌘.` does both,
|
|
350
|
+
`⌘K` searches. With the panel closed, point at the canvas's left edge and the panel slides out
|
|
351
|
+
under the navbar, over the canvas, without resizing the preview.
|
|
352
|
+
|
|
353
|
+
**Astro's dev toolbar is hidden inside the lab's frames**, so a lab page shows one toolbar: its
|
|
354
|
+
own. The site's pages, and a bare variant opened in its own tab, keep theirs.
|
|
355
|
+
|
|
356
|
+
### Pinning from the lab
|
|
357
|
+
|
|
358
|
+
Where the pin board runs, **Comment** pins an element of the framed preview: click it, click the
|
|
359
|
+
element, and the pin app on Astro's dev toolbar opens its composer. The pick is confined to the
|
|
360
|
+
canvas, so it can never land on the lab's own chrome; Escape stops it. The button stays disabled
|
|
361
|
+
until that toolbar app has started, because it is the one picker and composer the lab uses.
|
|
362
|
+
|
|
363
|
+
Pins already left on the framed page are drawn over the frame at their elements, blue while open and
|
|
364
|
+
grey once resolved; a pin whose selector finds nothing on the page, or more than one thing, is not
|
|
365
|
+
drawn. Clicking a pin, or the pin row in the Properties panel, opens All Tasks. ⋯ › Pins decides
|
|
366
|
+
what is drawn — Show pins (on) and Show resolved (off) — and the choice is remembered in this
|
|
367
|
+
browser. On the site's own pages, outside the lab, pinning goes through the dev toolbar as before.
|
|
259
368
|
|
|
260
369
|
**An unconditional `noindex`** on every lab page. Lab pages do not render through the site's
|
|
261
370
|
layout, so they never inherit its staging `noindex` — this one is not gated on the environment, on
|
|
@@ -265,47 +374,26 @@ purpose. Keep your own build gate as well: two mechanisms, because the rule is a
|
|
|
265
374
|
|
|
266
375
|
A component is live when a page under `src/pages/` imports it **and** mounts it as a tag. Every
|
|
267
376
|
page is walked, the layout is excluded, and the slot is the component's position in that page's
|
|
268
|
-
document order. Nothing is configured, so the
|
|
377
|
+
document order. Nothing is configured, so the slot badge cannot go stale.
|
|
269
378
|
|
|
270
379
|
## Marks
|
|
271
380
|
|
|
272
|
-
|
|
273
|
-
`PUT /__lab/responsive`) and
|
|
274
|
-
|
|
275
|
-
-
|
|
276
|
-
approval-gated and neither fact is readable from the code, so both are ticked by hand in the
|
|
277
|
-
sidebar.
|
|
278
|
-
- **`cull.json`** — `{ marked }`: stories in the cullable tier, marked for deletion.
|
|
279
|
-
|
|
280
|
-
The API refuses anything outside the right tier, anything that is not a stories file on disk, and
|
|
281
|
-
anything live or used by a live section — with the offender and the reason. In a build there is no
|
|
282
|
-
dev server, so there is no write path at all and the switches hide themselves.
|
|
381
|
+
One JSON file under `directory`, **`responsive.json`**, written by a **dev-only** API
|
|
382
|
+
(`PUT /__lab/responsive`) and meant to be committed. It holds `{ done, approved }` per section
|
|
383
|
+
version, ticked in the component's **Properties** panel (the eye beside its name). Responsive work
|
|
384
|
+
is approval-gated and neither fact is readable from the code, so both are ticked by hand.
|
|
283
385
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
```sh
|
|
289
|
-
orbytes-lab-cull # dry run — print the plan, remove nothing
|
|
290
|
-
orbytes-lab-cull --json # the same plan as JSON
|
|
291
|
-
orbytes-lab-cull --yes # perform it: `git rm` (staged, not committed)
|
|
292
|
-
orbytes-lab-cull --dir=… # when the lab is not at src/lab
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
It runs in the current working directory, so wire it as a script in the consumer:
|
|
296
|
-
|
|
297
|
-
```json
|
|
298
|
-
"scripts": { "lab:cull": "orbytes-lab-cull" }
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
It removes each marked stories file, the component it renders, and anything under the lab
|
|
302
|
-
directory that only the removal set imports — then refuses the whole run if any entry is blocked.
|
|
386
|
+
The API refuses anything outside the responsive tier and anything that is not a stories file on
|
|
387
|
+
disk — with the offender and the reason. In a build there is no dev server, so there is no write
|
|
388
|
+
path at all: the boxes stay disabled and show the state the page was built with.
|
|
303
389
|
|
|
304
390
|
## Parameters panel
|
|
305
391
|
|
|
306
|
-
A component with something to tune declares it and renders no UI of its own. The panel is
|
|
307
|
-
the
|
|
308
|
-
page
|
|
392
|
+
A component with something to tune declares it and renders no UI of its own. The panel is a drawer
|
|
393
|
+
over the right edge of the component page's canvas, opened from the navbar's **Parameters** button,
|
|
394
|
+
which exists only for a variant that registered something — the bare variant page shows nothing.
|
|
395
|
+
The drawer never resizes the canvas, because that would change the preview's width and trip its
|
|
396
|
+
breakpoints; whether it is open, and which groups are collapsed, is remembered in this browser.
|
|
309
397
|
|
|
310
398
|
```ts
|
|
311
399
|
import { registerLabParams } from "@orbytes/astrolab/params";
|
|
@@ -315,6 +403,7 @@ const panel = registerLabParams({
|
|
|
315
403
|
title: "hero shader",
|
|
316
404
|
controls: [
|
|
317
405
|
{ kind: "range", id: "speed", label: "Speed", min: 0, max: 4, step: 0.05, value: 1, unit: "×" },
|
|
406
|
+
{ kind: "number", id: "headlineSize", label: "Headline size", value: 72, unit: "px" },
|
|
318
407
|
{ kind: "toggle", id: "grain", label: "Grain", value: true },
|
|
319
408
|
],
|
|
320
409
|
apply(values) { /* push them into whatever they configure */ },
|
|
@@ -324,47 +413,102 @@ const values = panel.values; // LIVE — mutated in place, safe to hold
|
|
|
324
413
|
panel.status("WebGL2 · 0.8 ms/frame");
|
|
325
414
|
```
|
|
326
415
|
|
|
416
|
+
The control kinds are `range` (a slider), `number` (below), `toggle`, `select` and `color`.
|
|
417
|
+
|
|
327
418
|
`apply` is called once at registration with the restored values, so it must tolerate running before
|
|
328
419
|
the thing it configures exists. Values persist per group in `localStorage`. Reset, Copy settings,
|
|
329
420
|
the readouts and the collapse are the panel's job, not the component's.
|
|
330
421
|
|
|
331
|
-
|
|
332
|
-
`LabParamValues`, `LabParamEntry`.
|
|
422
|
+
### Sizes are never sliders
|
|
333
423
|
|
|
334
|
-
|
|
424
|
+
**Decided 2026-09-24.** A parameter that is a size — a font size, a tracking, a line height, a width,
|
|
425
|
+
an offset, a radius — is `kind: "number"`: a field to type the value into, joined to a select of the
|
|
426
|
+
units it can be expressed in. Sliders stay for unitless tuning values (speed, grain, scale,
|
|
427
|
+
opacity).
|
|
335
428
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
429
|
+
```ts
|
|
430
|
+
{ kind: "number", id, label, value, unit, units?, step?, min?, max?, note? }
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- `value` is the bare default number (`72`) and `unit` its default unit (`"px"`, or `""` for none).
|
|
434
|
+
- `units` is what the select offers. It defaults to `LAB_UNITS.size` — px · rem · em · vw · vh · %.
|
|
435
|
+
`LAB_UNITS.tracking` (em · px) and `LAB_UNITS.lineHeight` (none · px) are there too, or pass a
|
|
436
|
+
list of your own; the default `unit` is added if the list lacks it.
|
|
437
|
+
- `step` is what ↑ / ↓ add (1 by default); `min` and `max` bound typed and restored values alike.
|
|
438
|
+
|
|
439
|
+
`apply` receives **one CSS-ready string** per number control, the number and the unit joined —
|
|
440
|
+
`"72px"`, `"12.5rem"`, or `"1.05"` when the unit is `""` — so it drops straight into a style:
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
el.style.setProperty("--headline-size", String(values.headlineSize)); // "72px"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
`parseLabLength("72px")` gives `{ value: 72, unit: "px" }` back when the number itself is wanted,
|
|
447
|
+
and `null` for anything that is not one number and an optional unit. Changing the unit keeps the
|
|
448
|
+
number and changes what it means: the panel never converts between units, because px → rem
|
|
449
|
+
depends on a root size it cannot know. A size that used to be a `range` can become a `number`
|
|
450
|
+
without losing its saved tuning — a stored bare number is read in the default unit.
|
|
451
|
+
|
|
452
|
+
Exported from `@orbytes/astrolab/params`: `registerLabParams`, `LAB_UNITS`, `parseLabLength`,
|
|
453
|
+
`formatLabLength` (its inverse), `labUnitsOf`, and the types `LabParamControl`, `LabParamGroup`,
|
|
454
|
+
`LabParamHandle`, `LabParamValue`, `LabParamValues`, `LabParamEntry`.
|
|
455
|
+
|
|
456
|
+
## Tasks — the pin board at `/lab/tasks`
|
|
457
|
+
|
|
458
|
+
Dev only and on by default. Inside the lab it is two views in the lab's own chrome — **Kanban
|
|
459
|
+
Board** at `<subpath>/tasks` and **All Tasks**, a sortable table, at `<subpath>/tasks/all` — with no
|
|
460
|
+
panel beside them: the board's own chips filter by status. `/pin`, its old address, redirects there.
|
|
461
|
+
Pinning from inside the lab is the navbar's Comment button ([Pinning from the
|
|
462
|
+
lab](#pinning-from-the-lab)). Everything the board does is in [docs/PIN.md](./docs/PIN.md); what
|
|
463
|
+
matters at the integration boundary is here.
|
|
464
|
+
|
|
465
|
+
**It needs the site to be inside a git repository.** Every ticket path is stored relative to the
|
|
466
|
+
repository root: the Astro project's folder, or the nearest one above it, that holds `.git`.
|
|
467
|
+
With none, the board switches itself off for that run and says so only as a warning in the dev
|
|
468
|
+
server's log; the lab is unaffected. Nothing on screen says why: `<subpath>/tasks`,
|
|
469
|
+
`<subpath>/tasks/all` and `/pin` answer 404, the Tasks group and the Comment button are absent,
|
|
470
|
+
there is no pin app on the dev toolbar and no source stamp. Run `git init` (then restart the dev
|
|
471
|
+
server) to turn it on, or pass `pin: false` to silence the warning.
|
|
339
472
|
|
|
340
473
|
```js
|
|
341
474
|
orbytesLab({
|
|
342
475
|
css: ["./src/styles/global.css"],
|
|
343
476
|
pin: {
|
|
344
|
-
|
|
477
|
+
// site-relative; this adds the lab's components tier to the default
|
|
478
|
+
stamp: ["src/lab/sections", "src/lab/components", "src/components"],
|
|
345
479
|
project: "acme-website", // the screenshot archive folder
|
|
346
480
|
},
|
|
347
481
|
})
|
|
348
482
|
```
|
|
349
483
|
|
|
484
|
+
**Which files a pin can name.** A pin names the source file of the nearest element that carries a
|
|
485
|
+
`data-orbytes-src` attribute, and `stamp` decides which `.astro` files get one. The default is
|
|
486
|
+
`src/lab/sections` and `src/components` — **not** `src/lab/components` or `src/lab/explorations`,
|
|
487
|
+
where the layout above puts components — so a pin on one of those, in its own variant, records
|
|
488
|
+
its source as `unresolved` (its selector, screenshot and HTML are still in the ticket). Rendered
|
|
489
|
+
inside a stamped section, the same component's pin names the section's file. Add the directories you
|
|
490
|
+
want, as in the example: `stamp` replaces the default rather than adding to it, and it does not
|
|
491
|
+
follow `directory` if you moved the stories. Before widening it, read the last bullet below: a
|
|
492
|
+
template in a stamped folder with no element to stamp (its first top-level tag a component, say)
|
|
493
|
+
throws, and needs a `stampSkip` entry.
|
|
494
|
+
|
|
350
495
|
| Option | Default | What it does |
|
|
351
496
|
| --- | --- | --- |
|
|
352
|
-
| `stamp` | `["src/lab/sections", "src/components"]` | **Site-relative** directories whose `.astro` files get `data-orbytes-src`, so a pin can name the file it came from. |
|
|
497
|
+
| `stamp` | `["src/lab/sections", "src/components"]` | **Site-relative** directories whose `.astro` files get `data-orbytes-src`, so a pin can name the file it came from. Replaces the default whole; see above for `src/lab/components`. |
|
|
353
498
|
| `stampSkip` | `[]` | **Site-relative** `.astro` paths exempt from the stamp's hard failure. A repo-relative path is accepted too, for configs written before 2026-09-22. |
|
|
354
499
|
| `backlogDir` | `"backlog"` | Repo-relative board directory: tickets in `tasks/`, PNGs in `assets/`. |
|
|
355
500
|
| `archiveDir` | `~/.orbytes/feedback-archive` | Canonical screenshot home; the repo copy is a hardlink into it. |
|
|
356
501
|
| `project` | the checkout's folder name | The folder under `archiveDir` this project's screenshots live in. |
|
|
357
502
|
| `shots` | `true` | `false` writes tickets and never loads playwright. |
|
|
358
|
-
| `route` | `"
|
|
359
|
-
| `links` | the lab's resolved subpath | The board's corner links. `[]` draws none. |
|
|
503
|
+
| `route` | `"<subpath>/tasks"` | Where the board is served. On its own, without the lab, `"/pin"`. |
|
|
504
|
+
| `links` | the lab's resolved subpath | The standalone board's corner links. `[]` draws none. |
|
|
360
505
|
| `appId` / `appName` / `icon` | `"orbytes-pin"` / `"Pin"` / `"bug"` | The dev-toolbar app's identity. |
|
|
361
506
|
|
|
362
507
|
Three things about it are worth knowing before they surprise you:
|
|
363
508
|
|
|
364
|
-
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
address every document here names; a site that owns that path sets `pin: { route: "/__pin" }`.
|
|
509
|
+
- **`/pin` still answers, in dev, with a redirect.** It is middleware installed ahead of Astro's own
|
|
510
|
+
request handler, so a site with its own `src/pages/pin.astro` gets the redirect instead of its
|
|
511
|
+
page — in dev only. Build output is unaffected.
|
|
368
512
|
- **`project` defaults to the checkout's directory name**, which two clones of *different* repos
|
|
369
513
|
under the same folder name silently share. One's `pin-007.png` overwrites the other's and both
|
|
370
514
|
boards keep rendering. Set it explicitly where that is possible.
|
|
@@ -375,8 +519,8 @@ Three things about it are worth knowing before they surprise you:
|
|
|
375
519
|
|
|
376
520
|
### `orbytes-pin-gallery`
|
|
377
521
|
|
|
378
|
-
The board is live at `/
|
|
379
|
-
snapshot that opens with no dev server running.
|
|
522
|
+
The board is live at `/lab/tasks` and read fresh on every request, so this binary is only for the
|
|
523
|
+
file: a snapshot that opens with no dev server running.
|
|
380
524
|
|
|
381
525
|
```sh
|
|
382
526
|
orbytes-pin-gallery # → backlog/gallery.html
|
|
@@ -385,8 +529,8 @@ orbytes-pin-gallery --backlog-dir feedback # when the site moved its board
|
|
|
385
529
|
```
|
|
386
530
|
|
|
387
531
|
It renders from the same module the live board does and is read-only — a `file://` page has nothing
|
|
388
|
-
listening behind it. It runs in the current working directory,
|
|
389
|
-
|
|
532
|
+
listening behind it. It runs in the current working directory, so wire it as a script in the
|
|
533
|
+
consumer:
|
|
390
534
|
|
|
391
535
|
```json
|
|
392
536
|
"scripts": { "pin:gallery": "orbytes-pin-gallery" }
|
|
@@ -397,7 +541,7 @@ as a script in the consumer:
|
|
|
397
541
|
- **`decorators/`** — a decorator wraps a story in the page shell it will really live in. That
|
|
398
542
|
shell is different in every build, so the package ships none: write your own against your own
|
|
399
543
|
layout.
|
|
400
|
-
- **`
|
|
544
|
+
- **`responsive.json`** — data about your components, committed with them.
|
|
401
545
|
- **The build gate** — whether the lab is in this build is the consumer's decision, in the
|
|
402
546
|
consumer's `astro.config.mjs`, and worth asserting with a check of your own.
|
|
403
547
|
- **The head component** — fonts and anything else the site's layout registers.
|
|
@@ -405,6 +549,7 @@ as a script in the consumer:
|
|
|
405
549
|
## Notes
|
|
406
550
|
|
|
407
551
|
- The `tags` a story declares (`export default { component, meta: { tags: [...] } }`) are read
|
|
408
|
-
|
|
552
|
+
from the stories file's text, not imported, so write them as string literals: a tag built at
|
|
553
|
+
runtime is not seen.
|
|
409
554
|
- Editing the integration or its `.mjs` helpers is not picked up by the dev server's config-change
|
|
410
555
|
restart — they stay in Node's ESM cache. Stop and start the server.
|