@orbytes/astrolab 0.3.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/LICENSE +37 -0
- package/README.md +410 -0
- package/bin/lab-cull.mjs +401 -0
- package/bin/pin-gallery.mjs +121 -0
- package/defaults.mjs +120 -0
- package/dist/core/astro-integration.js +130 -0
- package/dist/core/index.js +6 -0
- package/dist/core/lib-paths.js +29 -0
- package/dist/core/options.js +173 -0
- package/dist/core/utils/get-exports.js +52 -0
- package/dist/core/utils/invariant.js +12 -0
- package/dist/core/utils/kebab-case.js +15 -0
- package/dist/core/utils/path-builder.js +22 -0
- package/dist/core/utils/path.js +53 -0
- package/dist/core/virtual-module/get-story-modules.js +60 -0
- package/dist/core/virtual-module/story-modules.js +8 -0
- package/dist/core/virtual-module/virtual-module-ids.js +22 -0
- package/dist/core/virtual-module/virtual-routes.js +83 -0
- package/dist/core/virtual-module/vite-plugin.js +98 -0
- package/docs/PIN-CONTRACT.md +263 -0
- package/docs/PIN.md +429 -0
- package/index.d.ts +279 -0
- package/index.mjs +347 -0
- package/package.json +93 -0
- package/src/Empty.astro +4 -0
- package/src/Home.astro +298 -0
- package/src/LabHead.astro +1102 -0
- package/src/core/LICENSE-astrobook +166 -0
- package/src/core/astro-integration.ts +166 -0
- package/src/core/client.ts +89 -0
- package/src/core/index.ts +7 -0
- package/src/core/lib/components/empty.astro +1 -0
- package/src/core/lib/components/head.astro +1 -0
- package/src/core/lib/components/home.astro +8 -0
- package/src/core/lib/components/with-decorators.astro +22 -0
- package/src/core/lib/pages/app.astro +19 -0
- package/src/core/lib/pages/preview.astro +17 -0
- package/src/core/lib/pages/story.astro +16 -0
- package/src/core/lib-paths.ts +72 -0
- package/src/core/options.ts +262 -0
- package/src/core/utils/get-exports.ts +59 -0
- package/src/core/utils/invariant.ts +13 -0
- package/src/core/utils/kebab-case.ts +30 -0
- package/src/core/utils/path-builder.ts +45 -0
- package/src/core/utils/path.ts +80 -0
- package/src/core/virtual-module/get-story-modules.ts +110 -0
- package/src/core/virtual-module/story-modules.ts +9 -0
- package/src/core/virtual-module/virtual-module-ids.ts +17 -0
- package/src/core/virtual-module/virtual-routes.ts +130 -0
- package/src/core/virtual-module/vite-plugin.ts +125 -0
- package/src/pin/board.mjs +1521 -0
- package/src/pin/index.mjs +666 -0
- package/src/pin/shot.mjs +427 -0
- package/src/pin/source-stamp.mjs +159 -0
- package/src/pin/tickets.mjs +697 -0
- package/src/pin/toolbar.js +3181 -0
- package/src/shell/Browse.astro +371 -0
- package/src/shell/CardGrid.astro +297 -0
- package/src/shell/Viewport.astro +1330 -0
- package/src/shell/index.json.ts +12 -0
- package/src/shell/lab-index.ts +344 -0
- package/src/shell/lab-params.ts +245 -0
- package/src/shell/live-files.mjs +164 -0
- package/src/shell/marks.mjs +136 -0
- package/src/types/index.ts +6 -0
- package/src/types/types.ts +239 -0
- package/src/types/virtual.d.ts +29 -0
- package/src/ui/components/app.astro +13 -0
- package/src/ui/components/build-path.ts +13 -0
- package/src/ui/components/build-tree.ts +108 -0
- package/src/ui/components/collapse-duration.ts +28 -0
- package/src/ui/components/compress-terms.ts +10 -0
- package/src/ui/components/dashboard-layout.astro +39 -0
- package/src/ui/components/home.astro +65 -0
- package/src/ui/components/layout.astro +110 -0
- package/src/ui/components/preview-layout.astro +109 -0
- package/src/ui/components/sidebar-button-fullscreen.astro +38 -0
- package/src/ui/components/sidebar-button-search.astro +23 -0
- package/src/ui/components/sidebar-button-theme.astro +9 -0
- package/src/ui/components/sidebar-button.astro +24 -0
- package/src/ui/components/sidebar-resize-handle.astro +74 -0
- package/src/ui/components/sidebar-search-panel.astro +41 -0
- package/src/ui/components/sidebar-search-script.ts +103 -0
- package/src/ui/components/sidebar-title.astro +17 -0
- package/src/ui/components/sidebar-tree-node.astro +143 -0
- package/src/ui/components/sidebar-tree.astro +84 -0
- package/src/ui/components/sidebar.astro +29 -0
- package/src/ui/components/theme-message.ts +26 -0
- package/src/ui/components/theme-script.astro +71 -0
- package/src/ui/components/theme-toggle.astro +63 -0
- package/src/ui/components/theme.ts +32 -0
- package/src/ui/index.ts +4 -0
- package/src/ui/lab.css +549 -0
- package/virtual.d.ts +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 William Teig (orbytes.io)
|
|
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.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
PARTS OF THIS PACKAGE ARE VENDORED FROM ASTROBOOK, ALSO UNDER MIT.
|
|
26
|
+
|
|
27
|
+
`src/core/`, `src/ui/` and `src/types/` are vendored from Astrobook
|
|
28
|
+
(https://github.com/ocavue/astrobook), version 0.13.3, commit
|
|
29
|
+
a79f03cdfd32eb08330b33fa7d5830ac506f9272 — Copyright (c) 2024, 2025 ocavue.
|
|
30
|
+
|
|
31
|
+
Their upstream copyright and permission notices are reproduced in full in
|
|
32
|
+
`src/core/LICENSE-astrobook`, which also records exactly which files diverge
|
|
33
|
+
from upstream and how. That file ships inside the published package, which is
|
|
34
|
+
what MIT redistribution requires; do not remove it from the `files` list in
|
|
35
|
+
package.json, and do not move it without checking that it still ships.
|
|
36
|
+
|
|
37
|
+
Everything outside those three directories is original to this package.
|
package/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# @orbytes/astrolab
|
|
2
|
+
|
|
3
|
+
Two dev surfaces for an Astro site, in one package and one integration call.
|
|
4
|
+
|
|
5
|
+
**The component lab** at `/lab` — an [Astrobook](https://github.com/ocavue/astrobook) shell. Every
|
|
6
|
+
section, component and experiment in a build, rendered live, with the one thing a component
|
|
7
|
+
playground normally cannot tell you — **which of these is actually on the site right now**, derived
|
|
8
|
+
from the pages rather than declared.
|
|
9
|
+
|
|
10
|
+
**The pin board** at `/pin` — click a rendered element on `astro dev`, leave a comment, and a
|
|
11
|
+
markdown ticket plus a screenshot land on local disk for an agent to pick up
|
|
12
|
+
([docs/PIN.md](./docs/PIN.md), contract at [docs/PIN-CONTRACT.md](./docs/PIN-CONTRACT.md)). It moved
|
|
13
|
+
in from its own package on 2026-09-22, so the two install, configure and version as one thing. Dev
|
|
14
|
+
only, switchable off with `pin: false`.
|
|
15
|
+
|
|
16
|
+
It ships mostly **source**. The `.astro` files, and everything under `src/shell/`, `src/ui/` and
|
|
17
|
+
`src/types/`, are compiled by the consumer's own Vite, the way Starlight ships its components —
|
|
18
|
+
which is why the lab picks up the consumer's tokens, fonts and breakpoints in a story preview: it
|
|
19
|
+
renders inside the consumer's pipeline. The one exception is `src/core/`, transpiled to `dist/` by
|
|
20
|
+
`scripts/build-core.mjs` at `prepack` and `prepare`, because `astro.config.mjs` is loaded by Node's
|
|
21
|
+
own ESM loader and Node refuses to strip TypeScript under `node_modules`. Without that step an
|
|
22
|
+
installed copy cannot be loaded at all. The script's header comment is the full account.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm i -D @orbytes/astrolab
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then add the integration:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// astro.config.mjs
|
|
34
|
+
import { defineConfig } from "astro/config";
|
|
35
|
+
import orbytesLab from "@orbytes/astrolab";
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
integrations: [
|
|
39
|
+
orbytesLab({
|
|
40
|
+
css: ["./src/styles/global.css"], // your token and base stylesheets
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
That is the whole configuration. In particular, **do not add an `ssr.noExternal` entry for this
|
|
47
|
+
package** — the integration adds its own, because the package ships `.astro` files that Vite would
|
|
48
|
+
otherwise externalise and hand to Node, which cannot read them. A consumer needs nothing. (Two
|
|
49
|
+
older orbytes repos set it by hand; it is redundant, and copying it from them is copying noise.)
|
|
50
|
+
|
|
51
|
+
Now write a story. Stories live under `src/lab/`, in one directory per tier:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
src/lab/
|
|
55
|
+
sections/Section01Hero/V1/Section01Hero.astro + Section01Hero.stories.ts
|
|
56
|
+
components/Button.astro + Button.stories.ts
|
|
57
|
+
explorations/KeycapButton.astro + KeycapButton.stories.ts
|
|
58
|
+
decorators/ ← yours, built against YOUR page shell (see below)
|
|
59
|
+
cull.json ← written by the lab, commit it
|
|
60
|
+
responsive.json ← written by the lab, commit it
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A stories file has a default export naming the component, and one named export per story. The
|
|
64
|
+
export's name is what the sidebar shows; its `args` are the component's props.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// src/lab/components/Button.stories.ts
|
|
68
|
+
import Button from "./Button.astro";
|
|
69
|
+
|
|
70
|
+
export default {
|
|
71
|
+
component: Button,
|
|
72
|
+
meta: { tags: ["cta", "v2"] }, // optional — the sidebar search matches tags
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const Subtle = { args: { label: "See pricing", variant: "subtle" } };
|
|
76
|
+
export const Strong = { args: { label: "Book a call", variant: "strong" } };
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then `npm run dev` and open **`http://localhost:4321/lab`** — the home page lists every tier and
|
|
80
|
+
renders every story live. `subpath` moves it; `directory` moves the stories.
|
|
81
|
+
|
|
82
|
+
**Peers.** Astro **7 or newer**, narrowed from `>=5.2.0` on 2026-09-22 when the pin board moved in:
|
|
83
|
+
the board's dev-toolbar app imports `astro/client/dev-toolbar/apps/utils/highlight.js` and
|
|
84
|
+
`.../window.js`, which are Astro 7 internals and part of no published API, so the old range was a
|
|
85
|
+
promise this package cannot keep. `playwright` is a second peer and an **optional** one — it is
|
|
86
|
+
loaded by dynamic import, only when a screenshot is actually taken, so a site without it runs the
|
|
87
|
+
lab and writes pin tickets without pictures rather than failing to load its config. Add it with
|
|
88
|
+
`npm i -D playwright && npx playwright install chromium` if you want the screenshots.
|
|
89
|
+
|
|
90
|
+
### Keeping the lab out of production
|
|
91
|
+
|
|
92
|
+
The integration does not decide this; you do. Wrap it in whatever condition your deployment gives
|
|
93
|
+
you, and the lab is simply absent from any build where the condition is false:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// The lab is a staging surface. Ship it to dev and staging, never to production.
|
|
97
|
+
const includeLab = process.argv.includes("dev") || process.env.MY_DEPLOY_ENV === "staging";
|
|
98
|
+
|
|
99
|
+
export default defineConfig({
|
|
100
|
+
integrations: [includeLab ? orbytesLab({ css: ["./src/styles/global.css"] }) : null],
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The environment variable is **yours**. orbytes' own sites use `PUBLIC_DEPLOY_ENV=staging`; that is
|
|
105
|
+
a deployment convention and no part of this package, which never reads any environment variable to
|
|
106
|
+
decide whether it loads. Every lab page also carries an unconditional `noindex`, so the two
|
|
107
|
+
mechanisms are independent — see *What you get*.
|
|
108
|
+
|
|
109
|
+
### One integration, not three
|
|
110
|
+
|
|
111
|
+
`orbytesLab()` returns the vendored Astrobook integration, the lab shell *and* the pin board as an
|
|
112
|
+
array; Astro flattens integration arrays, so that single entry configures all of them. Do not
|
|
113
|
+
install and add the `astrobook` package yourself — a second copy would fight this one over the same
|
|
114
|
+
routes and the same `virtual:astrobook/*` module ids.
|
|
115
|
+
|
|
116
|
+
The lab alone, for a site that does not want the board:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
orbytesLab({ css: ["./src/styles/global.css"], pin: false })
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Editing this package from a git worktree
|
|
123
|
+
|
|
124
|
+
Only relevant if you are developing **the lab itself** from a git worktree of a consuming repo. A
|
|
125
|
+
worktree has no `node_modules` of its own, so node walks up and resolves a bare `@orbytes/astrolab`
|
|
126
|
+
to the *main* clone's copy — you edit the package and the running site shows no change at all. The
|
|
127
|
+
whole shell follows that first resolution, because the integration anchors its route entrypoints
|
|
128
|
+
with `new URL(…, import.meta.url)`.
|
|
129
|
+
|
|
130
|
+
The fix has two halves. Import the package by **relative path** rather than by the bare specifier,
|
|
131
|
+
and add a `vite.resolve.alias` entry per **subpath** (`@orbytes/astrolab/head`, `/home`, `/params`
|
|
132
|
+
and friends), because those are resolved by Vite rather than by that import.
|
|
133
|
+
`testbeds/orbytes-v2/astro.config.mjs` builds the alias map from this package's own `name` and
|
|
134
|
+
`exports` so neither half can drift from it — copy that file.
|
|
135
|
+
|
|
136
|
+
**An installed copy needs none of this.** Measured 2026-09-22 against a packed tarball in a clean
|
|
137
|
+
Astro project: the bare specifier resolved, and `/head`, `/home` and `/params` resolved through
|
|
138
|
+
`node_modules` with no alias map at all.
|
|
139
|
+
|
|
140
|
+
### Installing from a folder copy — the legacy path
|
|
141
|
+
|
|
142
|
+
Before this package was published, a site got it by **copying the folder in** and linking it as a
|
|
143
|
+
workspace. That path still works and at least one orbytes site is still on it, so it is documented
|
|
144
|
+
rather than removed; a new install should use npm.
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
rsync -a --delete --exclude='.git' --exclude='.DS_Store' --exclude='ops/' --exclude='node_modules' \
|
|
148
|
+
--exclude='testbeds/' --exclude='.claude/' --exclude='package-lock.json' \
|
|
149
|
+
<this repo>/ <site>/packages/astro-lab/
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The excludes matter: `testbeds/` is this package's own consuming site, not part of what a site
|
|
153
|
+
installs, and a plain `cp -R` would carry it in along with this repo's `workspaces` and `scripts`
|
|
154
|
+
keys. Then, in the site's `package.json`:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"workspaces": ["packages/*"],
|
|
159
|
+
"devDependencies": { "@orbytes/astrolab": "*" }
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`npm install` links it from the site's own repo, so a CI build needs no token and no registry. On
|
|
164
|
+
this path **this repo is canonical**: fix it here first, bump the `version`, then refresh each
|
|
165
|
+
site's copy — that version is the stamp saying which copy a site is on, so drift is visible rather
|
|
166
|
+
than silent.
|
|
167
|
+
|
|
168
|
+
### What is vendored
|
|
169
|
+
|
|
170
|
+
**Astrobook is not a dependency**, and neither is anything it used to pull in. All three of its
|
|
171
|
+
packages are vendored at version `0.13.3` — the core into [`src/core/`](./src/core/), the UI into
|
|
172
|
+
[`src/ui/`](./src/ui/), the types into [`src/types/`](./src/types/) — and `astro-theme-toggle` with
|
|
173
|
+
them. Nothing outside Astro is installed, and there is nothing to keep in step but this folder.
|
|
174
|
+
|
|
175
|
+
Attribution, the upstream commit, and every deliberate divergence from it are recorded in
|
|
176
|
+
[`src/core/LICENSE-astrobook`](./src/core/LICENSE-astrobook) (MIT, Copyright (c) 2024 ocavue; the
|
|
177
|
+
theme toggle's own notice is at the end of the same file). Read it before touching anything under
|
|
178
|
+
`src/core/`, `src/ui/` or `src/types/` — each mirrors its upstream layout on purpose, so a future
|
|
179
|
+
version can still be diffed in.
|
|
180
|
+
|
|
181
|
+
This package is MIT too; see [LICENSE](./LICENSE).
|
|
182
|
+
|
|
183
|
+
### The chrome is not styled by the website
|
|
184
|
+
|
|
185
|
+
**Decided 2026-09-22.** The lab's own furniture looks the same in every site it is installed in,
|
|
186
|
+
and only the rendered story takes the site's styling. A site that wants a different-looking lab
|
|
187
|
+
edits this package's source: there is no theme API and no token override, on purpose.
|
|
188
|
+
|
|
189
|
+
So the chrome — layout, sidebar, search, the home and folder pages, the viewport configurator — is
|
|
190
|
+
drawn by one stylesheet that ships with this package, [`src/ui/lab.css`](./src/ui/lab.css), in
|
|
191
|
+
`--lab-*` tokens that cannot collide with a site's own, on a system font stack, with light and dark
|
|
192
|
+
themes of its own. It never receives the `css` list or the `head` component.
|
|
193
|
+
|
|
194
|
+
The **preview** — the story at `/lab/stories/<id>`, and the story rendered inside
|
|
195
|
+
`/lab/dashboard/<id>` — still gets both, because seeing a component as the real site renders it is
|
|
196
|
+
the whole point. Isolation runs both ways: the chrome's stylesheet is not loaded on a bare story
|
|
197
|
+
page, and a component styled by lab CSS the real site has never heard of would be a lab that lies.
|
|
198
|
+
The mechanism, and what it costs, is documented at the top of `lab.css`.
|
|
199
|
+
|
|
200
|
+
## Options
|
|
201
|
+
|
|
202
|
+
Every option is optional.
|
|
203
|
+
|
|
204
|
+
| Option | Default | What it does |
|
|
205
|
+
| --- | --- | --- |
|
|
206
|
+
| `directory` | `"src/lab"` | Where the stories live, relative to the project root. |
|
|
207
|
+
| `subpath` | `"/lab"` | Where the lab is served. `""` serves it at the site root. |
|
|
208
|
+
| `css` | `[]` | The site's token and base stylesheets. Astrobook injects them into its own pages; the lab's own pages (folder pages, viewport) pull the same list. |
|
|
209
|
+
| `head` | — | A path to the site's own head component, rendered **inside** the lab's head. This is how fonts arrive: lab stories never render through the site's layout, so whatever that layout registers has to be repeated here. |
|
|
210
|
+
| `title` | `"Component lab"` | The lab's name, on the home page and in the dashboard. |
|
|
211
|
+
| `stagingUrl` | — | The deployed lab's base URL — the real minified build, which a dev server is not. Every card and the story header link to the same story there. Omit it and those links do not appear. |
|
|
212
|
+
| `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
|
+
| `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`. |
|
|
215
|
+
| `pin` | `true` | The pin board. `false` omits it entirely; an object is the board's own options — see below. |
|
|
216
|
+
|
|
217
|
+
### Tiers
|
|
218
|
+
|
|
219
|
+
Tier names are a convention, not a law. Pass strings to rename or reorder them, or objects to say
|
|
220
|
+
what each one *is*:
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
tiers: [
|
|
224
|
+
{ id: "pages", label: "Page sections", responsive: true },
|
|
225
|
+
"atoms",
|
|
226
|
+
{ id: "sketches", label: "Sketches", cullable: true },
|
|
227
|
+
]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
- **`responsive`** — this tier holds page sections. Its stories carry the responsive mark, and they
|
|
231
|
+
render at page width (1440×900) in thumbnails and as the viewport's default frame; everything
|
|
232
|
+
else renders on a component stage (960×540). Defaults to true for a tier named `sections`.
|
|
233
|
+
- **`cullable`** — this tier's stories may be marked for deletion in the sidebar and removed by
|
|
234
|
+
`orbytes-lab-cull`. Nothing else can be: version history and shared chrome are never deleted from
|
|
235
|
+
a browser. Defaults to true for a tier named `explorations`.
|
|
236
|
+
|
|
237
|
+
At most one tier holds each role.
|
|
238
|
+
|
|
239
|
+
## What you get
|
|
240
|
+
|
|
241
|
+
**`<subpath>/`** — the home page: a card per tier, then every story as a scaled-down live render.
|
|
242
|
+
|
|
243
|
+
**`<subpath>/browse/<path>`** — a folder page per folder in the tree, with a filter box.
|
|
244
|
+
|
|
245
|
+
**`<subpath>/viewport/<story>`** — the viewport configurator. Drag, type or pick the frame's width
|
|
246
|
+
and height, zoom independently of it, and read the breakpoint band. The viewport is in the URL
|
|
247
|
+
(`?w=390&h=844&z=0.6`), so a link — or a screenshot tool — names the exact frame. `[` `]` step the
|
|
248
|
+
width presets, `r` rotates, `f` fits, `0` is 1:1.
|
|
249
|
+
|
|
250
|
+
**`<subpath>/index.json`** — the whole index as a static file: every story with its id, URLs,
|
|
251
|
+
files, tier/section/version, live slot, used-by list, tags and summary. Scripts and agent briefs
|
|
252
|
+
read this instead of globbing.
|
|
253
|
+
|
|
254
|
+
**The sidebar**, decorated: a **live** pill on every story a page actually mounts (`live · slot 3
|
|
255
|
+
of 12`, or `live · /about, slot 3` when the page is not the home page), a **used by** pill on
|
|
256
|
+
shared chrome, a **responsive** pill and two checkboxes on section versions, a cull switch in the
|
|
257
|
+
header, and the search widened so `live`, `v1`, `responsive` or a tag all match. `⌘B` collapses the
|
|
258
|
+
sidebar, `⌘K` focuses search, `j`/`k` walk the stories.
|
|
259
|
+
|
|
260
|
+
**An unconditional `noindex`** on every lab page. Lab pages do not render through the site's
|
|
261
|
+
layout, so they never inherit its staging `noindex` — this one is not gated on the environment, on
|
|
262
|
+
purpose. Keep your own build gate as well: two mechanisms, because the rule is absolute.
|
|
263
|
+
|
|
264
|
+
### "Live" is derived, never declared
|
|
265
|
+
|
|
266
|
+
A component is live when a page under `src/pages/` imports it **and** mounts it as a tag. Every
|
|
267
|
+
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 pill cannot go stale.
|
|
269
|
+
|
|
270
|
+
## Marks
|
|
271
|
+
|
|
272
|
+
Two JSON files under `directory`, both written by a **dev-only** API (`PUT /__lab/cull`,
|
|
273
|
+
`PUT /__lab/responsive`) and both meant to be committed:
|
|
274
|
+
|
|
275
|
+
- **`responsive.json`** — `{ done, approved }` per section version. Responsive work is
|
|
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.
|
|
283
|
+
|
|
284
|
+
### `orbytes-lab-cull`
|
|
285
|
+
|
|
286
|
+
The marks delete nothing. This binary does, and only when told to:
|
|
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.
|
|
303
|
+
|
|
304
|
+
## Parameters panel
|
|
305
|
+
|
|
306
|
+
A component with something to tune declares it and renders no UI of its own. The panel is drawn by
|
|
307
|
+
the viewport configurator, under the top bar, and only there — the dashboard and the bare story
|
|
308
|
+
page show nothing.
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { registerLabParams } from "@orbytes/astrolab/params";
|
|
312
|
+
|
|
313
|
+
const panel = registerLabParams({
|
|
314
|
+
id: "hero-shader", // stable — it is the storage key
|
|
315
|
+
title: "hero shader",
|
|
316
|
+
controls: [
|
|
317
|
+
{ kind: "range", id: "speed", label: "Speed", min: 0, max: 4, step: 0.05, value: 1, unit: "×" },
|
|
318
|
+
{ kind: "toggle", id: "grain", label: "Grain", value: true },
|
|
319
|
+
],
|
|
320
|
+
apply(values) { /* push them into whatever they configure */ },
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const values = panel.values; // LIVE — mutated in place, safe to hold in a render loop
|
|
324
|
+
panel.status("WebGL2 · 0.8 ms/frame");
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
`apply` is called once at registration with the restored values, so it must tolerate running before
|
|
328
|
+
the thing it configures exists. Values persist per group in `localStorage`. Reset, Copy settings,
|
|
329
|
+
the readouts and the collapse are the panel's job, not the component's.
|
|
330
|
+
|
|
331
|
+
Exported types: `LabParamControl`, `LabParamGroup`, `LabParamHandle`, `LabParamValue`,
|
|
332
|
+
`LabParamValues`, `LabParamEntry`.
|
|
333
|
+
|
|
334
|
+
## The pin board at `/pin`
|
|
335
|
+
|
|
336
|
+
Dev only, on by default, and it needs no configuration: `orbytesLab()` already gives it the lab's
|
|
337
|
+
resolved `subpath` so the board's own "Lab" link points wherever the lab actually is. Everything it
|
|
338
|
+
does is in [docs/PIN.md](./docs/PIN.md); what matters at the integration boundary is here.
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
orbytesLab({
|
|
342
|
+
css: ["./src/styles/global.css"],
|
|
343
|
+
pin: {
|
|
344
|
+
stamp: ["src/lab/sections", "src/components"], // site-relative
|
|
345
|
+
project: "acme-website", // the screenshot archive folder
|
|
346
|
+
},
|
|
347
|
+
})
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
| Option | Default | What it does |
|
|
351
|
+
| --- | --- | --- |
|
|
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. |
|
|
353
|
+
| `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
|
+
| `backlogDir` | `"backlog"` | Repo-relative board directory: tickets in `tasks/`, PNGs in `assets/`. |
|
|
355
|
+
| `archiveDir` | `~/.orbytes/feedback-archive` | Canonical screenshot home; the repo copy is a hardlink into it. |
|
|
356
|
+
| `project` | the checkout's folder name | The folder under `archiveDir` this project's screenshots live in. |
|
|
357
|
+
| `shots` | `true` | `false` writes tickets and never loads playwright. |
|
|
358
|
+
| `route` | `"/pin"` | Where the board is served. |
|
|
359
|
+
| `links` | the lab's resolved subpath | The board's corner links. `[]` draws none. |
|
|
360
|
+
| `appId` / `appName` / `icon` | `"orbytes-pin"` / `"Pin"` / `"bug"` | The dev-toolbar app's identity. |
|
|
361
|
+
|
|
362
|
+
Three things about it are worth knowing before they surprise you:
|
|
363
|
+
|
|
364
|
+
- **`route` shadows a host page at the same path.** The board is middleware installed ahead of
|
|
365
|
+
Astro's own request handler, so a site with its own `src/pages/pin.astro` serves the board
|
|
366
|
+
instead — in dev, with nothing on screen to say why. The default stays `/pin` because it is the
|
|
367
|
+
address every document here names; a site that owns that path sets `pin: { route: "/__pin" }`.
|
|
368
|
+
- **`project` defaults to the checkout's directory name**, which two clones of *different* repos
|
|
369
|
+
under the same folder name silently share. One's `pin-007.png` overwrites the other's and both
|
|
370
|
+
boards keep rendering. Set it explicitly where that is possible.
|
|
371
|
+
- **`stamp` throws on a matched `.astro` file with no element to stamp** — a template whose first
|
|
372
|
+
top-level tag is a component, say. That is deliberate (a warned-past file produces tickets with no
|
|
373
|
+
source), and `stampSkip` is the way past it. Point `stamp` at directories you know, not at
|
|
374
|
+
`src/`.
|
|
375
|
+
|
|
376
|
+
### `orbytes-pin-gallery`
|
|
377
|
+
|
|
378
|
+
The board is live at `/pin` and read fresh on every request, so this binary is only for the file: a
|
|
379
|
+
snapshot that opens with no dev server running.
|
|
380
|
+
|
|
381
|
+
```sh
|
|
382
|
+
orbytes-pin-gallery # → backlog/gallery.html
|
|
383
|
+
orbytes-pin-gallery --out somewhere.html
|
|
384
|
+
orbytes-pin-gallery --backlog-dir feedback # when the site moved its board
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
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, like `orbytes-lab-cull`, so wire it
|
|
389
|
+
as a script in the consumer:
|
|
390
|
+
|
|
391
|
+
```json
|
|
392
|
+
"scripts": { "pin:gallery": "orbytes-pin-gallery" }
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## What stays yours
|
|
396
|
+
|
|
397
|
+
- **`decorators/`** — a decorator wraps a story in the page shell it will really live in. That
|
|
398
|
+
shell is different in every build, so the package ships none: write your own against your own
|
|
399
|
+
layout.
|
|
400
|
+
- **`cull.json` / `responsive.json`** — data about your components, committed with them.
|
|
401
|
+
- **The build gate** — whether the lab is in this build is the consumer's decision, in the
|
|
402
|
+
consumer's `astro.config.mjs`, and worth asserting with a check of your own.
|
|
403
|
+
- **The head component** — fonts and anything else the site's layout registers.
|
|
404
|
+
|
|
405
|
+
## Notes
|
|
406
|
+
|
|
407
|
+
- The `tags` a story declares (`export default { component, meta: { tags: [...] } }`) are read
|
|
408
|
+
through `import.meta.glob("/src/**/*.stories.ts")`, so a `directory` outside `src/` gets no tags.
|
|
409
|
+
- Editing the integration or its `.mjs` helpers is not picked up by the dev server's config-change
|
|
410
|
+
restart — they stay in Node's ESM cache. Stop and start the server.
|