@orbytes/astrolab 0.3.0 → 0.4.0-next.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/README.md +109 -64
- package/defaults.mjs +65 -0
- package/dist/core/virtual-module/virtual-routes.js +12 -1
- package/docs/PIN-CONTRACT.md +8 -0
- package/docs/PIN.md +29 -19
- package/index.d.ts +40 -16
- package/index.mjs +32 -10
- package/package.json +6 -2
- package/src/Home.astro +167 -264
- package/src/LabHead.astro +37 -1047
- package/src/chrome/ComponentCard.astro +69 -0
- package/src/chrome/Icon.astro +21 -0
- package/src/chrome/LICENSE-icons +43 -0
- package/src/chrome/Nav.astro +105 -0
- package/src/chrome/Panel.astro +104 -0
- package/src/chrome/Shell.astro +106 -0
- package/src/chrome/Sprite.astro +23 -0
- package/src/chrome/StoryView.astro +251 -0
- package/src/chrome/Tree.astro +83 -0
- package/src/chrome/ViewportControls.astro +98 -0
- package/src/chrome/ViewportStage.astro +32 -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 +59 -0
- package/src/chrome/marks-client.ts +102 -0
- package/src/chrome/model.ts +142 -0
- package/src/chrome/pins-data.ts +30 -0
- package/src/chrome/shell-client.ts +372 -0
- package/src/chrome/site-data.ts +230 -0
- package/src/chrome/trees.ts +152 -0
- package/src/chrome/viewport-client.ts +579 -0
- package/src/chrome/views/Assets.astro +110 -0
- package/src/chrome/views/Pages.astro +178 -0
- package/src/chrome/views/Placeholder.astro +37 -0
- package/src/chrome/views/Tasks.astro +107 -0
- package/src/core/LICENSE-astrobook +16 -0
- package/src/core/lib/components/home.astro +4 -2
- package/src/core/lib/pages/story.astro +12 -10
- package/src/core/virtual-module/virtual-routes.ts +20 -4
- package/src/pin/board.mjs +389 -175
- package/src/pin/index.mjs +33 -2
- package/src/pin/toolbar.js +1 -1
- package/src/shell/Browse.astro +106 -353
- package/src/shell/Viewport.astro +22 -1315
- package/src/shell/lab-index.ts +23 -14
- package/src/ui/components/app.astro +5 -7
- package/src/ui/components/theme-script.astro +13 -2
- package/src/ui/lab.css +2152 -370
- package/virtual.d.ts +13 -0
- 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,20 +1,21 @@
|
|
|
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
|
-
only, switchable off with `pin: false
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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's own sidebar. Dev only, switchable off with `pin: false`; `/pin`, its old
|
|
15
|
+
address, redirects there.
|
|
16
|
+
|
|
17
|
+
It ships mostly **source**. The `.astro` files, and everything under `src/chrome/`, `src/shell/`,
|
|
18
|
+
`src/ui/` and `src/types/`, are compiled by the consumer's own Vite, the way Starlight ships its components —
|
|
18
19
|
which is why the lab picks up the consumer's tokens, fonts and breakpoints in a story preview: it
|
|
19
20
|
renders inside the consumer's pipeline. The one exception is `src/core/`, transpiled to `dist/` by
|
|
20
21
|
`scripts/build-core.mjs` at `prepack` and `prepare`, because `astro.config.mjs` is loaded by Node's
|
|
@@ -76,8 +77,11 @@ export const Subtle = { args: { label: "See pricing", variant: "subtle" } };
|
|
|
76
77
|
export const Strong = { args: { label: "Book a call", variant: "strong" } };
|
|
77
78
|
```
|
|
78
79
|
|
|
79
|
-
Then `npm run dev` and open **`http://localhost:4321/lab
|
|
80
|
-
|
|
80
|
+
Then `npm run dev` and open **`http://localhost:4321/lab`**. `subpath` moves it; `directory` moves
|
|
81
|
+
the stories.
|
|
82
|
+
|
|
83
|
+
**Test versions** are published under the `next` tag while the redesign settles:
|
|
84
|
+
`npm i -D @orbytes/astrolab@next`. `latest` stays on the last stable release until 1.0.
|
|
81
85
|
|
|
82
86
|
**Peers.** Astro **7 or newer**, narrowed from `>=5.2.0` on 2026-09-22 when the pin board moved in:
|
|
83
87
|
the board's dev-toolbar app imports `astro/client/dev-toolbar/apps/utils/highlight.js` and
|
|
@@ -167,10 +171,15 @@ than silent.
|
|
|
167
171
|
|
|
168
172
|
### What is vendored
|
|
169
173
|
|
|
170
|
-
**Astrobook is not a dependency**, and neither is anything it used to pull in.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
**Astrobook is not a dependency**, and neither is anything it used to pull in. Its core is vendored
|
|
175
|
+
at version `0.13.3` into [`src/core/`](./src/core/), its types into [`src/types/`](./src/types/), and
|
|
176
|
+
what the preview still needs of its UI into [`src/ui/`](./src/ui/) — the rest of that UI was retired
|
|
177
|
+
by the redesign (2026-09-24), whose chrome is this package's own, in [`src/chrome/`](./src/chrome/).
|
|
178
|
+
Nothing outside Astro is installed, and there is nothing to keep in step but this folder.
|
|
179
|
+
|
|
180
|
+
The chrome bundles two third-party assets, each with its licence beside it: **Inter** (SIL OFL,
|
|
181
|
+
[`src/chrome/fonts/`](./src/chrome/fonts/)) and **Lucide** icons (ISC,
|
|
182
|
+
[`src/chrome/LICENSE-icons`](./src/chrome/LICENSE-icons)).
|
|
174
183
|
|
|
175
184
|
Attribution, the upstream commit, and every deliberate divergence from it are recorded in
|
|
176
185
|
[`src/core/LICENSE-astrobook`](./src/core/LICENSE-astrobook) (MIT, Copyright (c) 2024 ocavue; the
|
|
@@ -186,16 +195,16 @@ This package is MIT too; see [LICENSE](./LICENSE).
|
|
|
186
195
|
and only the rendered story takes the site's styling. A site that wants a different-looking lab
|
|
187
196
|
edits this package's source: there is no theme API and no token override, on purpose.
|
|
188
197
|
|
|
189
|
-
So the chrome —
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
So the chrome — both sidebar levels, the navbar, every view — is drawn by one stylesheet that ships
|
|
199
|
+
with this package, [`src/ui/lab.css`](./src/ui/lab.css), in `--lab-*` tokens that cannot collide with
|
|
200
|
+
a site's own, in Inter, with light and dark themes of its own. It never receives the `css` list or
|
|
201
|
+
the `head` component.
|
|
193
202
|
|
|
194
|
-
The **preview** — the story at `/lab/stories/<id>`, and the
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
page
|
|
198
|
-
The
|
|
203
|
+
The **preview** — the bare story at `/lab/stories/<id>`, and the site's own pages — gets both,
|
|
204
|
+
because seeing a component as the real site renders it is the whole point. Since the redesign the
|
|
205
|
+
chrome only ever shows a preview **inside an `<iframe>`**, so the two never share a document: no
|
|
206
|
+
chrome page carries a byte of the site's CSS, and the chrome's stylesheet never reaches a story.
|
|
207
|
+
The preview also keeps its **own** light/dark, separate from the chrome's.
|
|
199
208
|
|
|
200
209
|
## Options
|
|
201
210
|
|
|
@@ -205,13 +214,14 @@ Every option is optional.
|
|
|
205
214
|
| --- | --- | --- |
|
|
206
215
|
| `directory` | `"src/lab"` | Where the stories live, relative to the project root. |
|
|
207
216
|
| `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
|
|
217
|
+
| `css` | `[]` | The site's token and base stylesheets. They reach the story **previews** only, never the lab's own chrome. |
|
|
218
|
+
| `head` | — | A path to the site's own head component, rendered inside every story preview's head. This is how fonts arrive: stories never render through the site's layout, so whatever that layout registers has to be repeated here. |
|
|
219
|
+
| `title` | `"Component lab"` | The site's name as the lab shows it — the tab title and the home page. |
|
|
211
220
|
| `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
221
|
| `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
222
|
| `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`. |
|
|
223
|
+
| `tiers` | `["sections", "components", "explorations"]` | The tiers, in reading order. A tier is the first path segment under `directory`. A tier with no stories is left out of the menu. |
|
|
224
|
+
| `viewports` | the orbytes scale | The viewport's screen sizes: `design` widths (the device switch), `breakpoints` (the band readout and the presets) and named `devices`. See [Viewports](#viewports). |
|
|
215
225
|
| `pin` | `true` | The pin board. `false` omits it entirely; an object is the board's own options — see below. |
|
|
216
226
|
|
|
217
227
|
### Tiers
|
|
@@ -236,26 +246,60 @@ tiers: [
|
|
|
236
246
|
|
|
237
247
|
At most one tier holds each role.
|
|
238
248
|
|
|
249
|
+
### Viewports
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
viewports: {
|
|
253
|
+
design: [
|
|
254
|
+
{ label: "Desktop", width: 1440, icon: "monitor" },
|
|
255
|
+
{ label: "Phone", width: 390, icon: "phone" }, // icons: monitor, tablet, phone-landscape, phone
|
|
256
|
+
],
|
|
257
|
+
breakpoints: [{ name: "desktop", min: 1024 }, { name: "mobile", min: 0 }],
|
|
258
|
+
devices: [{ label: "Pixel 8", width: 412, height: 915 }],
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Each key is optional and replaces its default whole. `breakpoints` are lower edges, widest first,
|
|
263
|
+
the last one `0`; the presets offer both sides of every edge (1024 and 1023).
|
|
264
|
+
|
|
239
265
|
## What you get
|
|
240
266
|
|
|
241
|
-
|
|
267
|
+
Two sidebar levels — the menu (Home; Site: Pages, one entry per tier, Assets; Tasks; Support and
|
|
268
|
+
Settings) and a panel with a breadcrumb, a search box and a tree — and a main area with a navbar
|
|
269
|
+
over the content.
|
|
270
|
+
|
|
271
|
+
**`<subpath>/`** — Home: counts for pages, each tier, assets and open tasks, and every page's
|
|
272
|
+
mounted components in slot order — what is on the site right now.
|
|
242
273
|
|
|
243
|
-
**`<subpath>/
|
|
274
|
+
**`<subpath>/pages/<route>`** — the site's own pages, in a tree nested the way the routes nest, each
|
|
275
|
+
shown on the viewport canvas at its real URL. `<subpath>/pages` lists them all.
|
|
244
276
|
|
|
245
|
-
**`<subpath>/
|
|
246
|
-
and
|
|
247
|
-
|
|
248
|
-
|
|
277
|
+
**`<subpath>/browse/<tier>`** — a tier (Sections, Components, …): its components as cards with a live
|
|
278
|
+
thumbnail and their pills, filterable by live / used / unused / responsive.
|
|
279
|
+
|
|
280
|
+
**`<subpath>/dashboard/<story>`** — a component's page. The tree stops at the component; its stories
|
|
281
|
+
are tabs (a dropdown past four), with previous/next buttons and `j`/`k` stepping through every story
|
|
282
|
+
of the tier. The story renders on a **canvas**: pick a design width, type or drag the frame's size,
|
|
283
|
+
rotate, pick a breakpoint edge or a device, zoom (Fit by default), and read the band. The viewport
|
|
284
|
+
is in the URL (`?w=390&h=844&z=fit`), so a link names the exact frame. `[` `]` step the widths, `r`
|
|
285
|
+
rotates, `f` fits, `0` is 100%. The pills open a **status menu** (where it is live, and the marks
|
|
286
|
+
below); everything that leaves the lab (VS Code, full screen, staging, a link) is under ⋯.
|
|
287
|
+
|
|
288
|
+
**`<subpath>/assets`** — every image under `src/` and `public/`, its size and dimensions, and which
|
|
289
|
+
files reference it, so "what does nothing use?" is a filter.
|
|
290
|
+
|
|
291
|
+
**`<subpath>/tasks`**, **`<subpath>/tasks/all`** — the pin board, dev only. See below.
|
|
249
292
|
|
|
250
293
|
**`<subpath>/index.json`** — the whole index as a static file: every story with its id, URLs,
|
|
251
294
|
files, tier/section/version, live slot, used-by list, tags and summary. Scripts and agent briefs
|
|
252
295
|
read this instead of globbing.
|
|
253
296
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
297
|
+
**`<subpath>/viewport/<story>`** — the old viewport configurator's address, now a redirect to the
|
|
298
|
+
component page, kept so old links still land.
|
|
299
|
+
|
|
300
|
+
**Keys.** `⌘B` closes and opens the panel, `⌘⇧B` shrinks the menu to icons, `⌘.` does both,
|
|
301
|
+
`⌘K` searches. With the panel closed, point at the canvas's left edge and the panel slides out over
|
|
302
|
+
it without resizing the preview.
|
|
259
303
|
|
|
260
304
|
**An unconditional `noindex`** on every lab page. Lab pages do not render through the site's
|
|
261
305
|
layout, so they never inherit its staging `noindex` — this one is not gated on the environment, on
|
|
@@ -270,16 +314,16 @@ document order. Nothing is configured, so the pill cannot go stale.
|
|
|
270
314
|
## Marks
|
|
271
315
|
|
|
272
316
|
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
|
|
317
|
+
`PUT /__lab/responsive`) and both meant to be committed. Both are ticked in a component's **status
|
|
318
|
+
menu** (its pills, in the navbar):
|
|
274
319
|
|
|
275
320
|
- **`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
|
|
277
|
-
sidebar.
|
|
321
|
+
approval-gated and neither fact is readable from the code, so both are ticked by hand.
|
|
278
322
|
- **`cull.json`** — `{ marked }`: stories in the cullable tier, marked for deletion.
|
|
279
323
|
|
|
280
324
|
The API refuses anything outside the right tier, anything that is not a stories file on disk, and
|
|
281
325
|
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
|
|
326
|
+
dev server, so there is no write path at all and the boxes stay disabled.
|
|
283
327
|
|
|
284
328
|
### `orbytes-lab-cull`
|
|
285
329
|
|
|
@@ -303,9 +347,9 @@ directory that only the removal set imports — then refuses the whole run if an
|
|
|
303
347
|
|
|
304
348
|
## Parameters panel
|
|
305
349
|
|
|
306
|
-
A component with something to tune declares it and renders no UI of its own. The panel is
|
|
307
|
-
the
|
|
308
|
-
page
|
|
350
|
+
A component with something to tune declares it and renders no UI of its own. The panel is a drawer
|
|
351
|
+
on the right of the component page's canvas, opened from the navbar's **Parameters** button, which
|
|
352
|
+
exists only for a story that registered something — the bare story page shows nothing.
|
|
309
353
|
|
|
310
354
|
```ts
|
|
311
355
|
import { registerLabParams } from "@orbytes/astrolab/params";
|
|
@@ -331,11 +375,13 @@ the readouts and the collapse are the panel's job, not the component's.
|
|
|
331
375
|
Exported types: `LabParamControl`, `LabParamGroup`, `LabParamHandle`, `LabParamValue`,
|
|
332
376
|
`LabParamValues`, `LabParamEntry`.
|
|
333
377
|
|
|
334
|
-
##
|
|
378
|
+
## Tasks — the pin board at `/lab/tasks`
|
|
335
379
|
|
|
336
|
-
Dev only, on by default, and it needs no configuration
|
|
337
|
-
|
|
338
|
-
|
|
380
|
+
Dev only, on by default, and it needs no configuration. Inside the lab it is two views in the lab's
|
|
381
|
+
own chrome — **Kanban Board** at `<subpath>/tasks` and **All Tasks**, a sortable table, at
|
|
382
|
+
`<subpath>/tasks/all` — with the statuses and their counts in the panel. `/pin`, its old address,
|
|
383
|
+
redirects there. Everything it does is in [docs/PIN.md](./docs/PIN.md); what matters at the
|
|
384
|
+
integration boundary is here.
|
|
339
385
|
|
|
340
386
|
```js
|
|
341
387
|
orbytesLab({
|
|
@@ -355,16 +401,15 @@ orbytesLab({
|
|
|
355
401
|
| `archiveDir` | `~/.orbytes/feedback-archive` | Canonical screenshot home; the repo copy is a hardlink into it. |
|
|
356
402
|
| `project` | the checkout's folder name | The folder under `archiveDir` this project's screenshots live in. |
|
|
357
403
|
| `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. |
|
|
404
|
+
| `route` | `"<subpath>/tasks"` | Where the board is served. On its own, without the lab, `"/pin"`. |
|
|
405
|
+
| `links` | the lab's resolved subpath | The standalone board's corner links. `[]` draws none. |
|
|
360
406
|
| `appId` / `appName` / `icon` | `"orbytes-pin"` / `"Pin"` / `"bug"` | The dev-toolbar app's identity. |
|
|
361
407
|
|
|
362
408
|
Three things about it are worth knowing before they surprise you:
|
|
363
409
|
|
|
364
|
-
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
address every document here names; a site that owns that path sets `pin: { route: "/__pin" }`.
|
|
410
|
+
- **`/pin` still answers, in dev, with a redirect.** It is middleware installed ahead of Astro's own
|
|
411
|
+
request handler, so a site with its own `src/pages/pin.astro` gets the redirect instead of its
|
|
412
|
+
page — in dev only. Build output is unaffected.
|
|
368
413
|
- **`project` defaults to the checkout's directory name**, which two clones of *different* repos
|
|
369
414
|
under the same folder name silently share. One's `pin-007.png` overwrites the other's and both
|
|
370
415
|
boards keep rendering. Set it explicitly where that is possible.
|
|
@@ -375,8 +420,8 @@ Three things about it are worth knowing before they surprise you:
|
|
|
375
420
|
|
|
376
421
|
### `orbytes-pin-gallery`
|
|
377
422
|
|
|
378
|
-
The board is live at `/
|
|
379
|
-
snapshot that opens with no dev server running.
|
|
423
|
+
The board is live at `/lab/tasks` and read fresh on every request, so this binary is only for the
|
|
424
|
+
file: a snapshot that opens with no dev server running.
|
|
380
425
|
|
|
381
426
|
```sh
|
|
382
427
|
orbytes-pin-gallery # → backlog/gallery.html
|
package/defaults.mjs
CHANGED
|
@@ -38,6 +38,67 @@ export const TIER_ROLES = {
|
|
|
38
38
|
explorations: { cullable: true },
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The screen sizes the viewport offers — a per-site setting (decided 2026-09-24), defaulting to the
|
|
43
|
+
* orbytes scale.
|
|
44
|
+
*
|
|
45
|
+
* - `design` — the widths the design is drawn at: the navbar's device switch, in this order. The
|
|
46
|
+
* `icon` names one of the chrome's icons (monitor, tablet, phone-landscape, phone).
|
|
47
|
+
* - `breakpoints` — the lower edge of each CSS band, widest first; the last is 0. The readout
|
|
48
|
+
* names the band the frame is in, and the presets offer both sides of every edge (992 and 991).
|
|
49
|
+
* - `devices` — named sizes that set width AND height.
|
|
50
|
+
*
|
|
51
|
+
* @typedef {{ label: string; width: number; icon: string }} DesignWidth
|
|
52
|
+
* @typedef {{ name: string; min: number }} Breakpoint
|
|
53
|
+
* @typedef {{ label: string; width: number; height: number }} Device
|
|
54
|
+
* @typedef {{ design: DesignWidth[]; breakpoints: Breakpoint[]; devices: Device[] }} Viewports
|
|
55
|
+
*/
|
|
56
|
+
/** @type {Viewports} */
|
|
57
|
+
export const DEFAULT_VIEWPORTS = {
|
|
58
|
+
design: [
|
|
59
|
+
{ label: "Desktop", width: 1440, icon: "monitor" },
|
|
60
|
+
{ label: "Tablet", width: 834, icon: "tablet" },
|
|
61
|
+
{ label: "Phone landscape", width: 550, icon: "phone-landscape" },
|
|
62
|
+
{ label: "Phone", width: 390, icon: "phone" },
|
|
63
|
+
],
|
|
64
|
+
breakpoints: [
|
|
65
|
+
{ name: "desktop", min: 992 },
|
|
66
|
+
{ name: "tablet", min: 768 },
|
|
67
|
+
{ name: "phone landscape", min: 480 },
|
|
68
|
+
{ name: "phone", min: 0 },
|
|
69
|
+
],
|
|
70
|
+
devices: [
|
|
71
|
+
{ label: "iPhone 15", width: 390, height: 844 },
|
|
72
|
+
{ label: "iPhone 15 Pro Max", width: 430, height: 932 },
|
|
73
|
+
{ label: "iPad", width: 834, height: 1194 },
|
|
74
|
+
{ label: "iPad landscape", width: 1194, height: 834 },
|
|
75
|
+
{ label: "MacBook", width: 1440, height: 900 },
|
|
76
|
+
{ label: "Wide", width: 1920, height: 1080 },
|
|
77
|
+
{ label: "Ultra-wide", width: 2560, height: 1080 },
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** @param {unknown} value @returns {Viewports} */
|
|
82
|
+
const resolveViewports = (value) => {
|
|
83
|
+
const given = value && typeof value === "object" ? /** @type {Partial<Viewports>} */ (value) : {};
|
|
84
|
+
const list = (v, fallback) => (Array.isArray(v) && v.length ? v : fallback);
|
|
85
|
+
return {
|
|
86
|
+
design: list(given.design, DEFAULT_VIEWPORTS.design).map((d) => ({
|
|
87
|
+
label: String(d.label ?? `${d.width}`),
|
|
88
|
+
width: Number(d.width),
|
|
89
|
+
icon: String(d.icon ?? "monitor"),
|
|
90
|
+
})),
|
|
91
|
+
breakpoints: list(given.breakpoints, DEFAULT_VIEWPORTS.breakpoints)
|
|
92
|
+
.map((b) => ({ name: String(b.name), min: Number(b.min) || 0 }))
|
|
93
|
+
.sort((a, b) => b.min - a.min),
|
|
94
|
+
devices: list(given.devices, DEFAULT_VIEWPORTS.devices).map((d) => ({
|
|
95
|
+
label: String(d.label),
|
|
96
|
+
width: Number(d.width),
|
|
97
|
+
height: Number(d.height),
|
|
98
|
+
})),
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
41
102
|
/** `./src/lab/` and `src/lab` both mean `src/lab`. @param {string} value */
|
|
42
103
|
export const normaliseDirectory = (value) =>
|
|
43
104
|
String(value)
|
|
@@ -89,6 +150,8 @@ const resolveTier = (tier) => {
|
|
|
89
150
|
* @property {string | null} cullDir `<directory>/<cullTier>/`, or null
|
|
90
151
|
* @property {string} responsiveFile `<directory>/responsive.json`
|
|
91
152
|
* @property {string} cullFile `<directory>/cull.json`
|
|
153
|
+
* @property {Viewports} viewports the viewport's screen sizes (DEFAULT_VIEWPORTS)
|
|
154
|
+
* @property {null} tasks filled in by the integration when the pin board runs
|
|
92
155
|
*/
|
|
93
156
|
|
|
94
157
|
/**
|
|
@@ -116,5 +179,7 @@ export function resolveLabOptions(options = {}) {
|
|
|
116
179
|
cullDir: cull ? `${directory}/${cull.id}/` : null,
|
|
117
180
|
responsiveFile: `${directory}/responsive.json`,
|
|
118
181
|
cullFile: `${directory}/cull.json`,
|
|
182
|
+
viewports: resolveViewports(options.viewports),
|
|
183
|
+
tasks: null,
|
|
119
184
|
};
|
|
120
185
|
}
|
|
@@ -51,7 +51,18 @@ async function getVirtualRoutes(rootDir, codegenDir, logger, dashboardSubpath, p
|
|
|
51
51
|
}
|
|
52
52
|
function createVirtualRouteComponent(route) {
|
|
53
53
|
const modName = `astrobook_module_${route.storyModule.id}`.replaceAll(/\W+/g, "_").replaceAll(/_+/g, "_").replace(/_+$/, "");
|
|
54
|
-
|
|
54
|
+
if (route.props.hasSidebar) {
|
|
55
|
+
return `
|
|
56
|
+
---
|
|
57
|
+
// Automatically generated by Astrobook
|
|
58
|
+
|
|
59
|
+
import StoryPage from '${STORY_PAGE}';
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
<StoryPage story={'${route.props.story}'} hasSidebar={true} />
|
|
63
|
+
`.trim();
|
|
64
|
+
}
|
|
65
|
+
const page = PREVIEW_PAGE;
|
|
55
66
|
return `
|
|
56
67
|
---
|
|
57
68
|
// Automatically generated by Astrobook
|
package/docs/PIN-CONTRACT.md
CHANGED
|
@@ -182,6 +182,14 @@ site setting `pin: { route: "/__pin" }` got a panel that fetched a path the midd
|
|
|
182
182
|
serve and showed an empty list — fixed 2026-09-22. One function normalises the trailing
|
|
183
183
|
slash, `boardRoute`, and both the middleware and the toolbar read it, so they cannot disagree.
|
|
184
184
|
|
|
185
|
+
**The board page moved into the lab on 2026-09-24.** Through `orbytesLab()` the route is
|
|
186
|
+
`<subpath>/tasks` and the page there is an Astro page in the lab's chrome
|
|
187
|
+
(`../src/chrome/views/Tasks.astro`), not this middleware's standalone document. The panel's contract
|
|
188
|
+
with it is unchanged, because the page renders the SAME markup from the same function
|
|
189
|
+
(`boardParts`): it still reads `[data-board]`, its `data-api`, every `[data-col][data-status]`, the
|
|
190
|
+
`script.card-data` payloads and `.broken li code` off the page at `route`. A change to those
|
|
191
|
+
selectors in `board.mjs` is a change to this contract.
|
|
192
|
+
|
|
185
193
|
## The status line — one line, not a log
|
|
186
194
|
|
|
187
195
|
Decided 2026-09-22, replacing a running four-entry list that narrated every step. The log was
|
package/docs/PIN.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# The pin board
|
|
2
2
|
|
|
3
3
|
Click a rendered element on `astro dev`, leave a comment, and get a markdown ticket plus a PNG on
|
|
4
|
-
local disk for a Claude Code agent to pick up. Then work the tickets on a kanban at `/
|
|
5
|
-
same dev server. No cloud, no API key, no MCP, no second process.
|
|
4
|
+
local disk for a Claude Code agent to pick up. Then work the tickets on a kanban at `/lab/tasks`,
|
|
5
|
+
in the lab, on the same dev server. No cloud, no API key, no MCP, no second process.
|
|
6
6
|
|
|
7
7
|
It is for a **solo pass** — your own review of a site in dev, before anyone else sees it. It does
|
|
8
8
|
not sync with whatever tool you use for review rounds with other people, and is not meant to
|
|
@@ -24,7 +24,7 @@ own port (`backlog browser`, 6420). That is the whole reason it is gone — the
|
|
|
24
24
|
|
|
25
25
|
| Stop | Do instead |
|
|
26
26
|
|---|---|
|
|
27
|
-
| `backlog browser` | `npm run dev`, then `/
|
|
27
|
+
| `backlog browser` | `npm run dev`, then `/lab/tasks` |
|
|
28
28
|
| `backlog task edit <n> …` on a pin ticket | drag the card, or use the status menu in its detail panel |
|
|
29
29
|
|
|
30
30
|
`backlog task edit` is the original defect, not an alternative. Any write through backlog.md
|
|
@@ -78,12 +78,23 @@ isCancelled(t.status); // the archive test
|
|
|
78
78
|
|
|
79
79
|
`isOpen()` is *not `Resolved` and not `Cancelled`* — one definition, shared by this board's chips
|
|
80
80
|
and the in-page panel's Open tab, so the two cannot disagree by one ticket. `Cancelled` appears in
|
|
81
|
-
neither tab: it is reachable on
|
|
81
|
+
neither tab: it is reachable on the board only.
|
|
82
82
|
|
|
83
|
-
## The board at `/
|
|
83
|
+
## The board at `/lab/tasks`
|
|
84
|
+
|
|
85
|
+
**In the lab since 2026-09-24** (decided that day: one URL, everything under `/lab`). The board is
|
|
86
|
+
two views in the lab's own chrome — **Kanban Board** at `/lab/tasks` and **All Tasks**, a sortable
|
|
87
|
+
table of every ticket, newest first, at `/lab/tasks/all` — with the statuses and their counts in
|
|
88
|
+
the lab's panel; `/lab/tasks?status=Ready%20for%20review` opens the board filtered to one column.
|
|
89
|
+
`/pin`, the board's address until then, redirects there. Both views are Astro pages the pin half
|
|
90
|
+
injects in `astro dev` only, rendered from the same markup, stylesheet and script as the standalone
|
|
91
|
+
file (`boardParts` in [`src/pin/board.mjs`](../src/pin/board.mjs)); the stylesheet is scoped with
|
|
92
|
+
`@scope (.pin-board)` so it cannot restyle the chrome around it, and it takes the lab's palette and
|
|
93
|
+
dark mode inside the lab.
|
|
84
94
|
|
|
85
95
|
Six columns in the order above, read fresh from `backlog/tasks/` on every request. No build step,
|
|
86
|
-
no regeneration; a refresh is the reload.
|
|
96
|
+
no regeneration; a refresh is the reload. In the lab the columns keep a fixed width and the row
|
|
97
|
+
scrolls sideways, rather than wrapping into a stack beside two sidebars.
|
|
87
98
|
|
|
88
99
|
- **Ready for review is drawn to be seen**, because it is the human review queue and the whole
|
|
89
100
|
reason the model changed — violet rule, violet dot, *waiting on you* under the name, a tinted panel
|
|
@@ -110,21 +121,19 @@ printed `0 ticket(s)` until 2026-09-22). It renders from the same module and is
|
|
|
110
121
|
board is writable exactly when `renderBoard` is given an endpoint, and a `file://` page has nothing
|
|
111
122
|
listening behind it.
|
|
112
123
|
|
|
113
|
-
**The board's corner links** are an option (`links`), not the hardcoded `/` and `/lab`
|
|
114
|
-
until 2026-09-22.
|
|
115
|
-
so the "Lab" link is derived from the value the routes are injected at rather than agreeing with it
|
|
116
|
-
by luck.
|
|
124
|
+
**The standalone board's corner links** are an option (`links`), not the hardcoded `/` and `/lab`
|
|
125
|
+
they were until 2026-09-22. Inside the lab there are none — the lab's own menu is right there.
|
|
117
126
|
|
|
118
127
|
## The write path
|
|
119
128
|
|
|
120
129
|
One endpoint, `POST <route>/api/ticket`, registered in `astro:server:setup` — which does not exist
|
|
121
130
|
in a build, so it cannot reach `dist/`.
|
|
122
131
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
the
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
**The endpoint, the screenshots and the `/pin` redirect are middleware**, installed ahead of
|
|
133
|
+
Astro's own request handler, so they shadow a host page at the same path — in dev only. Through
|
|
134
|
+
`orbytesLab()` the route is `<subpath>/tasks`, which no site page is likely to be; the one exposed
|
|
135
|
+
address is `/pin`, which redirects. On its own, without the lab, the pin half still serves its
|
|
136
|
+
standalone board at `route`, default `/pin`.
|
|
128
137
|
|
|
129
138
|
```json
|
|
130
139
|
{ "id": "PIN-004", "status": "Ready for review", "expect": { "status": "In Progress" } }
|
|
@@ -205,8 +214,8 @@ Pass these as `orbytesLab({ pin: { … } })`.
|
|
|
205
214
|
| `archiveDir` | `~/.orbytes/feedback-archive` | Canonical screenshot home; the repo copy is a hardlink into it. |
|
|
206
215
|
| `project` | the checkout's folder name | The folder under `archiveDir` this project's screenshots live in. Two clones of **different** repos under one folder name share an archive silently, one overwriting the other's `pin-007.png` — set it where that is possible. |
|
|
207
216
|
| `shots` | `true` | `false` writes tickets and takes no screenshots. |
|
|
208
|
-
| `route` | `"/pin"` | Where the board is served. `<route>/assets/*` serves its screenshots, `<route>/api/ticket` is the write endpoint
|
|
209
|
-
| `links` | the lab's resolved subpath | The board's corner links, `[{ href, label }]`. `[]` draws none. |
|
|
217
|
+
| `route` | `"<subpath>/tasks"` (`"/pin"` without the lab) | Where the board is served. `<route>/assets/*` serves its screenshots, `<route>/api/ticket` is the write endpoint; in the lab, `<route>` and `<route>/all` are the two views. Trailing slashes are stripped. The toolbar app reads the resolved value too (it fetches the ticket list from it and its **Board ↗** button opens it), through the virtual module below — until 2026-09-22 it carried a literal `/pin`, so setting this left the panel fetching a path nothing serves. |
|
|
218
|
+
| `links` | the lab's resolved subpath | The standalone board's corner links, `[{ href, label }]`. `[]` draws none. |
|
|
210
219
|
| `appId` / `appName` / `icon` | `"orbytes-pin"` / `"Pin"` / `"bug"` | The dev-toolbar app's identity. |
|
|
211
220
|
|
|
212
221
|
## What a ticket looks like
|
|
@@ -412,8 +421,9 @@ window, so clicking it again focuses that tab instead of opening another.
|
|
|
412
421
|
app, no stamp.
|
|
413
422
|
2. Both Vite plugins carry `apply: "serve"`, so they cannot load in a build even if they were
|
|
414
423
|
registered.
|
|
415
|
-
3. `astro:server:setup`, where every write lives — the ticket writer, the
|
|
416
|
-
|
|
424
|
+
3. `astro:server:setup`, where every write lives — the ticket writer, the screenshots and the
|
|
425
|
+
`<route>/api/ticket` endpoint — does not run in a build at all. The lab's two Tasks views are
|
|
426
|
+
injected past assertion 1, so they exist in `astro dev` and in no build.
|
|
417
427
|
4. That hook returns early anyway when `astro:config:setup` stood down, so a host that runs the
|
|
418
428
|
hooks in an order this package did not choose still gets nothing.
|
|
419
429
|
|
package/index.d.ts
CHANGED
|
@@ -23,14 +23,17 @@ export interface OrbytesLabOptions {
|
|
|
23
23
|
directory?: string;
|
|
24
24
|
/** Where the lab is served. Default `"/lab"`. */
|
|
25
25
|
subpath?: string;
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The consumer's token/stylesheet files. They reach the PREVIEW only — the bare story each
|
|
28
|
+
* component page shows in its canvas — never the lab's own chrome. Default `[]`.
|
|
29
|
+
*/
|
|
27
30
|
css?: string[];
|
|
28
31
|
/**
|
|
29
|
-
* A path to the consumer's own head component, rendered inside
|
|
30
|
-
* anything else site-specific reach
|
|
32
|
+
* A path to the consumer's own head component, rendered inside every story preview's head — how
|
|
33
|
+
* fonts and anything else site-specific reach the previews. Relative to the project root.
|
|
31
34
|
*/
|
|
32
35
|
head?: string;
|
|
33
|
-
/** The
|
|
36
|
+
/** The site's name as the lab shows it (the tab title, the home page). Default `"Component lab"`. */
|
|
34
37
|
title?: string;
|
|
35
38
|
/**
|
|
36
39
|
* The deployed lab's base URL — the real minified build, which a dev server is not. Every card
|
|
@@ -53,13 +56,32 @@ export interface OrbytesLabOptions {
|
|
|
53
56
|
* the entry says otherwise.
|
|
54
57
|
*/
|
|
55
58
|
tiers?: (string | LabTierOption)[];
|
|
59
|
+
/**
|
|
60
|
+
* The screen sizes the viewport offers. Every key is optional; an omitted one keeps the default
|
|
61
|
+
* (the orbytes scale).
|
|
62
|
+
*/
|
|
63
|
+
viewports?: {
|
|
64
|
+
/**
|
|
65
|
+
* The widths the design is drawn at — the navbar's device switch, in order. `icon` is one of
|
|
66
|
+
* `monitor`, `tablet`, `phone-landscape`, `phone`.
|
|
67
|
+
* @default 1440 desktop · 834 tablet · 550 phone landscape · 390 phone
|
|
68
|
+
*/
|
|
69
|
+
design?: { label: string; width: number; icon?: string }[];
|
|
70
|
+
/**
|
|
71
|
+
* The lower edge of each CSS band; the last is 0. The readout names the band the frame is in,
|
|
72
|
+
* and the presets offer both sides of every edge.
|
|
73
|
+
* @default desktop 992 · tablet 768 · phone landscape 480 · phone 0
|
|
74
|
+
*/
|
|
75
|
+
breakpoints?: { name: string; min: number }[];
|
|
76
|
+
/** Named sizes that set width AND height. @default iPhone 15, iPad, MacBook, … */
|
|
77
|
+
devices?: { label: string; width: number; height: number }[];
|
|
78
|
+
};
|
|
56
79
|
/**
|
|
57
80
|
* The pin board — the dev-only click-to-ticket feedback half of this package (`docs/PIN.md`).
|
|
58
81
|
*
|
|
59
|
-
* `true` or omitted ships it at
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* than guessed — pass your own `links` to override that.
|
|
82
|
+
* `true` or omitted ships it at `<subpath>/tasks` (the lab's Kanban Board and All Tasks views),
|
|
83
|
+
* with `/pin` redirecting there; `false` omits it entirely, for a site that wants the lab alone.
|
|
84
|
+
* An object is the pin integration's own options.
|
|
63
85
|
*
|
|
64
86
|
* @default true
|
|
65
87
|
*/
|
|
@@ -95,11 +117,12 @@ export interface OrbytesPinOptions {
|
|
|
95
117
|
shots?: boolean;
|
|
96
118
|
/**
|
|
97
119
|
* Where the board is served, dev only. `<route>/assets/*` serves its screenshots and
|
|
98
|
-
* `<route>/api/ticket` is the one write endpoint
|
|
120
|
+
* `<route>/api/ticket` is the one write endpoint; through `orbytesLab()`, `<route>` and
|
|
121
|
+
* `<route>/all` are the lab's Kanban Board and All Tasks views.
|
|
99
122
|
*
|
|
100
|
-
*
|
|
101
|
-
* the same path — in dev, silently.
|
|
102
|
-
* @default "/pin"
|
|
123
|
+
* Its asset and API paths are MIDDLEWARE, installed ahead of Astro's own request handler, so they
|
|
124
|
+
* shadow a host page at the same path — in dev, silently.
|
|
125
|
+
* @default "<subpath>/tasks" through orbytesLab(), "/pin" on its own
|
|
103
126
|
*/
|
|
104
127
|
route?: string;
|
|
105
128
|
/**
|
|
@@ -117,9 +140,10 @@ export interface OrbytesPinOptions {
|
|
|
117
140
|
}
|
|
118
141
|
|
|
119
142
|
/**
|
|
120
|
-
* The orbytes
|
|
121
|
-
* (`src/core/`, 0.13.3), the lab
|
|
122
|
-
* flattens the returned array, so one entry in `integrations` configures
|
|
143
|
+
* The orbytes lab AND the pin board: the Astrobook core vendored into this package
|
|
144
|
+
* (`src/core/`, 0.13.3), the lab's chrome around it, and the dev-only feedback board at
|
|
145
|
+
* `<subpath>/tasks`. Astro flattens the returned array, so one entry in `integrations` configures
|
|
146
|
+
* all three.
|
|
123
147
|
*
|
|
124
148
|
* `pin: false` returns the lab alone.
|
|
125
149
|
*/
|
|
@@ -206,7 +230,7 @@ export const OPEN_STATUSES: readonly PinStatus[];
|
|
|
206
230
|
export function normaliseStatus(status: unknown): string;
|
|
207
231
|
/** Open — neither accepted nor cancelled. `isOpen("Done")` is `false`. */
|
|
208
232
|
export function isOpen(status: unknown): boolean;
|
|
209
|
-
/** Archived. Every agent-facing read drops these;
|
|
233
|
+
/** Archived. Every agent-facing read drops these; the board is the one place they are visible. */
|
|
210
234
|
export function isCancelled(status: unknown): boolean;
|
|
211
235
|
/** May an agent START work on this ticket? Exactly one status says yes. */
|
|
212
236
|
export function isAgentReady(status: unknown): boolean;
|