@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/index.mjs
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
//
|
|
12
12
|
// ── the pin board (./src/pin/, moved in 2026-09-22) ─────────────────────────────────────────────
|
|
13
13
|
// Decided 2026-09-22: the lab and the pin board are ONE app, bundled and working as one — not two
|
|
14
|
-
// packages a site installs separately. So
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// packages a site installs separately. So the lab (`<subpath>`) and its Tasks board
|
|
15
|
+
// (`<subpath>/tasks`) come from one call, one install, one dev server. `pin: false` omits it
|
|
16
|
+
// entirely — a site may want the lab alone — and `pin: {…}` passes options straight through
|
|
17
|
+
// (ops/standalone-plan.md › The pin board merges in; docs/PIN.md).
|
|
17
18
|
//
|
|
18
19
|
// The pin half is DEV ONLY and gates itself four times over; it is registered in staging builds
|
|
19
20
|
// along with the lab and does nothing there, because its first assertion is `command === "dev"`.
|
|
@@ -25,22 +26,21 @@
|
|
|
25
26
|
// components (which render the consumer's head inside them).
|
|
26
27
|
// 2. inject the shell's routes, prerendered like every Astrobook route:
|
|
27
28
|
// <subpath>/browse/[...path] folder pages → ./src/shell/Browse.astro
|
|
28
|
-
// <subpath>/
|
|
29
|
+
// <subpath>/pages/[...path] the site's pages → ./src/chrome/views/Pages.astro
|
|
30
|
+
// <subpath>/assets the site's images → ./src/chrome/views/Assets.astro
|
|
31
|
+
// <subpath>/[section] support, settings → ./src/chrome/views/Placeholder.astro
|
|
32
|
+
// <subpath>/viewport/[...id] a redirect, kept for old links → ./src/shell/Viewport.astro
|
|
29
33
|
// <subpath>/index.json the lab index → ./src/shell/index.json.ts
|
|
34
|
+
// (the task views, <subpath>/tasks and /tasks/all, are the pin half's, dev only)
|
|
30
35
|
// and define `virtual:orbytes-lab/*` so those pages can read the resolved options, the
|
|
31
36
|
// consumer's head component and the consumer's CSS without importing anything by path.
|
|
32
|
-
// 3. serve the
|
|
33
|
-
// build, so a deployed lab has no write path and the switches hide themselves):
|
|
34
|
-
// GET /__lab/cull { marked: string[], updated: string | null }
|
|
35
|
-
// PUT /__lab/cull body { marked: string[] } — every entry must be an existing
|
|
36
|
-
// stories file in the CULLABLE tier AND not live on any page;
|
|
37
|
-
// otherwise 400 with the offenders. Writes <directory>/cull.json.
|
|
37
|
+
// 3. serve the responsive marks API on the Vite dev server only (astro:server:setup never runs
|
|
38
|
+
// in a build, so a deployed lab has no write path and the switches hide themselves):
|
|
38
39
|
// GET /__lab/responsive { done: string[], approved: string[], updated: string | null }
|
|
39
40
|
// PUT /__lab/responsive body { done: string[], approved?: string[] } — every entry must
|
|
40
41
|
// be an existing stories file in the RESPONSIVE tier; otherwise 400
|
|
41
42
|
// with the offenders. Writes <directory>/responsive.json.
|
|
42
|
-
// Marks only. NOTHING HERE DELETES A FILE
|
|
43
|
-
// run by hand and with a dry run first.
|
|
43
|
+
// Marks only. NOTHING HERE DELETES A FILE.
|
|
44
44
|
//
|
|
45
45
|
// Gotcha (2026-09-06): Astro re-imports astro.config.mjs on a config change, but this module and
|
|
46
46
|
// its ./src/shell/*.mjs imports stay in Node's ESM cache for the life of the process — so an edit
|
|
@@ -55,15 +55,7 @@ import { fileURLToPath } from "node:url";
|
|
|
55
55
|
import { createAstrobookIntegration } from "./dist/core/index.js";
|
|
56
56
|
import { resolveLabOptions } from "./defaults.mjs";
|
|
57
57
|
import orbytesPin from "./src/pin/index.mjs";
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
60
|
-
cullOffence,
|
|
61
|
-
readCull,
|
|
62
|
-
readResponsive,
|
|
63
|
-
responsiveOffence,
|
|
64
|
-
writeCull,
|
|
65
|
-
writeResponsive,
|
|
66
|
-
} from "./src/shell/marks.mjs";
|
|
58
|
+
import { readResponsive, responsiveOffence, writeResponsive } from "./src/shell/marks.mjs";
|
|
67
59
|
|
|
68
60
|
const file = (relative) => fileURLToPath(new URL(relative, import.meta.url));
|
|
69
61
|
|
|
@@ -80,22 +72,6 @@ const RESOLVED = {
|
|
|
80
72
|
[VIRTUAL.css]: "__virtual_orbytes_lab_user_css__.mjs",
|
|
81
73
|
};
|
|
82
74
|
|
|
83
|
-
/**
|
|
84
|
-
* Why an entry may not be marked for deletion, or null when it may. `importedByLive` maps a
|
|
85
|
-
* component file to the live section that imports it, so an exploration a live section uses is
|
|
86
|
-
* refused here with the same reason the cull script would give later.
|
|
87
|
-
*/
|
|
88
|
-
const cullEntryOffence = (rootDir, config, entry, liveByFile, importedByLive) => {
|
|
89
|
-
const bad = cullOffence(rootDir, config, entry);
|
|
90
|
-
if (bad) return bad;
|
|
91
|
-
const component = storyComponentFile(rootDir, entry);
|
|
92
|
-
const mount = component ? liveByFile.get(component) : undefined;
|
|
93
|
-
if (mount) return `live on ${mount.page} (slot ${mount.slot}: ${component})`;
|
|
94
|
-
const user = component ? importedByLive.get(component) : undefined;
|
|
95
|
-
if (user) return `used by live section ${user}`;
|
|
96
|
-
return null;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
75
|
const readBody = (req) =>
|
|
100
76
|
new Promise((resolve, reject) => {
|
|
101
77
|
let body = "";
|
|
@@ -117,14 +93,23 @@ const send = (res, status, payload) => {
|
|
|
117
93
|
res.end(JSON.stringify(payload));
|
|
118
94
|
};
|
|
119
95
|
|
|
96
|
+
/**
|
|
97
|
+
* What the lab and the pin half tell each other at setup. One object, created per `orbytesLab()`
|
|
98
|
+
* call and handed to both. The pin half fills `tasks` in its own `astro:config:setup` — only in dev,
|
|
99
|
+
* only when it found a repository — and the lab reads it when its config module LOADS, which Vite
|
|
100
|
+
* does on first request, after every integration's setup hook has run. So the lab's Tasks group
|
|
101
|
+
* exists exactly when the board does, and neither half decides for the other.
|
|
102
|
+
* @typedef {{ tasks: import("./defaults.mjs").LabConfig["tasks"] | { base: string; api: string; assets: string; repoRoot: string; backlogDir: string } }} Shared
|
|
103
|
+
*/
|
|
104
|
+
|
|
120
105
|
/** The virtual modules the package's own pages read. Everything in them is JSON-serialisable. */
|
|
121
|
-
const virtualModulesPlugin = (config, headEntrypoint) => ({
|
|
106
|
+
const virtualModulesPlugin = (config, headEntrypoint, shared) => ({
|
|
122
107
|
name: "orbytes-lab/virtual",
|
|
123
108
|
resolveId: (id) => RESOLVED[id],
|
|
124
109
|
load(id) {
|
|
125
110
|
switch (id) {
|
|
126
111
|
case RESOLVED[VIRTUAL.config]:
|
|
127
|
-
return `export default ${JSON.stringify(config)};`;
|
|
112
|
+
return `export default ${JSON.stringify({ ...config, tasks: shared.tasks ?? null })};`;
|
|
128
113
|
case RESOLVED[VIRTUAL.head]:
|
|
129
114
|
return `export { default } from ${JSON.stringify(headEntrypoint)};`;
|
|
130
115
|
case RESOLVED[VIRTUAL.css]:
|
|
@@ -133,8 +118,8 @@ const virtualModulesPlugin = (config, headEntrypoint) => ({
|
|
|
133
118
|
},
|
|
134
119
|
});
|
|
135
120
|
|
|
136
|
-
/** @param {Record<string, unknown>} [options] */
|
|
137
|
-
function labShell(options) {
|
|
121
|
+
/** @param {Record<string, unknown>} [options] @param {Shared} [shared] */
|
|
122
|
+
function labShell(options, shared = { tasks: null }) {
|
|
138
123
|
const config = resolveLabOptions(options);
|
|
139
124
|
let rootDir = process.cwd();
|
|
140
125
|
return {
|
|
@@ -153,6 +138,9 @@ function labShell(options) {
|
|
|
153
138
|
|
|
154
139
|
for (const [pattern, entrypoint] of [
|
|
155
140
|
[`${config.subpath}/browse/[...path]`, "./src/shell/Browse.astro"],
|
|
141
|
+
[`${config.subpath}/pages/[...path]`, "./src/chrome/views/Pages.astro"],
|
|
142
|
+
[`${config.subpath}/assets`, "./src/chrome/views/Assets.astro"],
|
|
143
|
+
[`${config.subpath}/[section]`, "./src/chrome/views/Placeholder.astro"],
|
|
156
144
|
[`${config.subpath}/viewport/[...id]`, "./src/shell/Viewport.astro"],
|
|
157
145
|
[`${config.subpath}/index.json`, "./src/shell/index.json.ts"],
|
|
158
146
|
]) {
|
|
@@ -161,66 +149,27 @@ function labShell(options) {
|
|
|
161
149
|
|
|
162
150
|
updateConfig({
|
|
163
151
|
vite: {
|
|
164
|
-
plugins: [virtualModulesPlugin(config, head)],
|
|
152
|
+
plugins: [virtualModulesPlugin(config, head, shared)],
|
|
165
153
|
// This package ships SOURCE — .astro and .ts compiled by the consumer's Vite, the way
|
|
166
154
|
// Starlight ships its components. Vite externalises node_modules for SSR by default,
|
|
167
155
|
// and an externalised .astro file is handed to Node, which cannot read it. The
|
|
168
156
|
// consumer should need one line in its config and nothing more, so the exemption is
|
|
169
157
|
// declared here rather than there.
|
|
170
158
|
ssr: { noExternal: ["@orbytes/astrolab"] },
|
|
171
|
-
// Marking writes JSON
|
|
172
|
-
//
|
|
159
|
+
// Marking writes a JSON file under src/; keep Vite from treating it as a source
|
|
160
|
+
// change and reloading the lab mid-triage.
|
|
173
161
|
server: {
|
|
174
|
-
watch: { ignored: [`**/${config.
|
|
162
|
+
watch: { ignored: [`**/${config.responsiveFile}`] },
|
|
175
163
|
},
|
|
176
164
|
},
|
|
177
165
|
});
|
|
178
166
|
logger.info(
|
|
179
|
-
`
|
|
167
|
+
`pages at ${config.subpath}/pages, folders at ${config.subpath}/browse, assets at ${config.subpath}/assets, index at ${config.subpath}/index.json`,
|
|
180
168
|
);
|
|
181
169
|
},
|
|
182
170
|
"astro:server:setup": ({ server, logger }) => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (url.pathname !== "/__lab/cull") return next();
|
|
186
|
-
try {
|
|
187
|
-
if (req.method === "GET") return send(res, 200, readCull(rootDir, config));
|
|
188
|
-
if (req.method === "PUT") {
|
|
189
|
-
const body = await readBody(req);
|
|
190
|
-
if (!Array.isArray(body.marked))
|
|
191
|
-
return send(res, 400, { error: "body must be { marked: string[] }" });
|
|
192
|
-
const live = liveComponentFiles(rootDir);
|
|
193
|
-
// First mount wins, so the refusal names the page a reader would look at first.
|
|
194
|
-
const liveByFile = new Map();
|
|
195
|
-
for (const l of live) if (!liveByFile.has(l.file)) liveByFile.set(l.file, l);
|
|
196
|
-
const importedByLive = new Map();
|
|
197
|
-
for (const l of live) {
|
|
198
|
-
for (const dep of defaultImports(rootDir, l.file)) {
|
|
199
|
-
if (!importedByLive.has(dep.file)) importedByLive.set(dep.file, l.file);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const offenders = body.marked
|
|
203
|
-
.map((entry) => ({
|
|
204
|
-
path: entry,
|
|
205
|
-
reason: cullEntryOffence(rootDir, config, entry, liveByFile, importedByLive),
|
|
206
|
-
}))
|
|
207
|
-
.filter((o) => o.reason !== null);
|
|
208
|
-
if (offenders.length > 0) {
|
|
209
|
-
return send(res, 400, { error: "some entries cannot be marked", offenders });
|
|
210
|
-
}
|
|
211
|
-
const data = writeCull(rootDir, config, body.marked);
|
|
212
|
-
logger.info(`cull marks saved: ${data.marked.length} → ${config.cullFile}`);
|
|
213
|
-
return send(res, 200, { ok: true, file: config.cullFile, ...data });
|
|
214
|
-
}
|
|
215
|
-
return send(res, 405, { error: "GET or PUT" });
|
|
216
|
-
} catch (e) {
|
|
217
|
-
return send(res, 500, { error: String(e?.message ?? e) });
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// The responsive marks — same shape of API as the cull one above, and deliberately its
|
|
222
|
-
// sibling rather than a second mechanism: a section version is marked responsive (and,
|
|
223
|
-
// separately, approved for that work) by hand, because neither fact is in the code.
|
|
171
|
+
// The responsive marks: a section version is marked responsive (and, separately, approved
|
|
172
|
+
// for that work) by hand, because neither fact is in the code.
|
|
224
173
|
server.middlewares.use(async (req, res, next) => {
|
|
225
174
|
const url = new URL(req.url ?? "/", "http://x");
|
|
226
175
|
if (url.pathname !== "/__lab/responsive") return next();
|
|
@@ -253,7 +202,7 @@ function labShell(options) {
|
|
|
253
202
|
}
|
|
254
203
|
});
|
|
255
204
|
|
|
256
|
-
logger.info("mark
|
|
205
|
+
logger.info("mark API at /__lab/responsive (dev only)");
|
|
257
206
|
},
|
|
258
207
|
},
|
|
259
208
|
};
|
|
@@ -277,7 +226,7 @@ function labShell(options) {
|
|
|
277
226
|
* @param {Record<string, unknown>} [options] the lab's options
|
|
278
227
|
* @param {import("./defaults.mjs").LabConfig} config the RESOLVED lab config
|
|
279
228
|
*/
|
|
280
|
-
const pinHalf = (options, config) => {
|
|
229
|
+
const pinHalf = (options, config, shared) => {
|
|
281
230
|
const pin = options?.pin;
|
|
282
231
|
if (pin === false || pin === null) return [];
|
|
283
232
|
const given = typeof pin === "object" ? pin : {};
|
|
@@ -289,7 +238,11 @@ const pinHalf = (options, config) => {
|
|
|
289
238
|
{ href: "/", label: "Site" },
|
|
290
239
|
{ href: config.subpath || "/", label: "Lab" },
|
|
291
240
|
],
|
|
241
|
+
// ONE URL (decided 2026-09-24): the board lives under the lab, at <subpath>/tasks, drawn in
|
|
242
|
+
// the lab's own chrome. `/pin` still answers, with a redirect. A consumer's `route` wins.
|
|
243
|
+
route: `${config.subpath}/tasks`,
|
|
292
244
|
...given,
|
|
245
|
+
lab: { shared },
|
|
293
246
|
}),
|
|
294
247
|
];
|
|
295
248
|
};
|
|
@@ -302,6 +255,8 @@ const pinHalf = (options, config) => {
|
|
|
302
255
|
*/
|
|
303
256
|
export default function orbytesLab(options = {}) {
|
|
304
257
|
const config = resolveLabOptions(options);
|
|
258
|
+
/** @type {Shared} */
|
|
259
|
+
const shared = { tasks: null };
|
|
305
260
|
return [
|
|
306
261
|
createAstrobookIntegration({
|
|
307
262
|
directory: config.directory,
|
|
@@ -313,8 +268,8 @@ export default function orbytesLab(options = {}) {
|
|
|
313
268
|
head: "@orbytes/astrolab/head",
|
|
314
269
|
home: "@orbytes/astrolab/home",
|
|
315
270
|
}),
|
|
316
|
-
labShell(options),
|
|
317
|
-
...pinHalf(options, config),
|
|
271
|
+
labShell(options, shared),
|
|
272
|
+
...pinHalf(options, config, shared),
|
|
318
273
|
];
|
|
319
274
|
}
|
|
320
275
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbytes/astrolab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-next.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "The orbytes
|
|
5
|
+
"description": "The orbytes lab for Astro sites — every page, section and component rendered live, which of them the site actually mounts, a viewport canvas to size them on, and a dev-only click-to-ticket pin board — in one integration, all local.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"astro",
|
|
8
8
|
"astro-integration",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"preview": "npm --workspace testbed-orbytes-v2 run preview",
|
|
29
29
|
"build:staging": "npm --workspace testbed-orbytes-v2 run build:staging",
|
|
30
30
|
"check:isolation": "npm --workspace testbed-orbytes-v2 run check:isolation",
|
|
31
|
+
"check:lab-index": "npm --workspace testbed-orbytes-v2 run check:lab-index",
|
|
31
32
|
"verify": "npm --workspace testbed-orbytes-v2 run verify"
|
|
32
33
|
},
|
|
33
34
|
"bin": {
|
|
34
|
-
"orbytes-lab-cull": "bin/lab-cull.mjs",
|
|
35
35
|
"orbytes-pin-gallery": "bin/pin-gallery.mjs"
|
|
36
36
|
},
|
|
37
37
|
"main": "./index.mjs",
|
|
@@ -70,6 +70,10 @@
|
|
|
70
70
|
"src",
|
|
71
71
|
"virtual.d.ts"
|
|
72
72
|
],
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public",
|
|
75
|
+
"tag": "next"
|
|
76
|
+
},
|
|
73
77
|
"dependencies": {
|
|
74
78
|
"@sveltejs/acorn-typescript": "^1.0.12",
|
|
75
79
|
"acorn": "^8.18.0",
|