@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.
Files changed (69) hide show
  1. package/README.md +109 -64
  2. package/defaults.mjs +65 -0
  3. package/dist/core/virtual-module/virtual-routes.js +12 -1
  4. package/docs/PIN-CONTRACT.md +8 -0
  5. package/docs/PIN.md +29 -19
  6. package/index.d.ts +40 -16
  7. package/index.mjs +32 -10
  8. package/package.json +6 -2
  9. package/src/Home.astro +167 -264
  10. package/src/LabHead.astro +37 -1047
  11. package/src/chrome/ComponentCard.astro +69 -0
  12. package/src/chrome/Icon.astro +21 -0
  13. package/src/chrome/LICENSE-icons +43 -0
  14. package/src/chrome/Nav.astro +105 -0
  15. package/src/chrome/Panel.astro +104 -0
  16. package/src/chrome/Shell.astro +106 -0
  17. package/src/chrome/Sprite.astro +23 -0
  18. package/src/chrome/StoryView.astro +251 -0
  19. package/src/chrome/Tree.astro +83 -0
  20. package/src/chrome/ViewportControls.astro +98 -0
  21. package/src/chrome/ViewportStage.astro +32 -0
  22. package/src/chrome/fonts/OFL.txt +93 -0
  23. package/src/chrome/fonts/inter-latin-wght-normal.woff2 +0 -0
  24. package/src/chrome/icons.ts +59 -0
  25. package/src/chrome/marks-client.ts +102 -0
  26. package/src/chrome/model.ts +142 -0
  27. package/src/chrome/pins-data.ts +30 -0
  28. package/src/chrome/shell-client.ts +372 -0
  29. package/src/chrome/site-data.ts +230 -0
  30. package/src/chrome/trees.ts +152 -0
  31. package/src/chrome/viewport-client.ts +579 -0
  32. package/src/chrome/views/Assets.astro +110 -0
  33. package/src/chrome/views/Pages.astro +178 -0
  34. package/src/chrome/views/Placeholder.astro +37 -0
  35. package/src/chrome/views/Tasks.astro +107 -0
  36. package/src/core/LICENSE-astrobook +16 -0
  37. package/src/core/lib/components/home.astro +4 -2
  38. package/src/core/lib/pages/story.astro +12 -10
  39. package/src/core/virtual-module/virtual-routes.ts +20 -4
  40. package/src/pin/board.mjs +389 -175
  41. package/src/pin/index.mjs +33 -2
  42. package/src/pin/toolbar.js +1 -1
  43. package/src/shell/Browse.astro +106 -353
  44. package/src/shell/Viewport.astro +22 -1315
  45. package/src/shell/lab-index.ts +23 -14
  46. package/src/ui/components/app.astro +5 -7
  47. package/src/ui/components/theme-script.astro +13 -2
  48. package/src/ui/lab.css +2152 -370
  49. package/virtual.d.ts +13 -0
  50. package/src/shell/CardGrid.astro +0 -297
  51. package/src/ui/components/build-path.ts +0 -13
  52. package/src/ui/components/build-tree.ts +0 -108
  53. package/src/ui/components/collapse-duration.ts +0 -28
  54. package/src/ui/components/compress-terms.ts +0 -10
  55. package/src/ui/components/dashboard-layout.astro +0 -39
  56. package/src/ui/components/home.astro +0 -65
  57. package/src/ui/components/layout.astro +0 -110
  58. package/src/ui/components/sidebar-button-fullscreen.astro +0 -38
  59. package/src/ui/components/sidebar-button-search.astro +0 -23
  60. package/src/ui/components/sidebar-button-theme.astro +0 -9
  61. package/src/ui/components/sidebar-button.astro +0 -24
  62. package/src/ui/components/sidebar-resize-handle.astro +0 -74
  63. package/src/ui/components/sidebar-search-panel.astro +0 -41
  64. package/src/ui/components/sidebar-search-script.ts +0 -103
  65. package/src/ui/components/sidebar-title.astro +0 -17
  66. package/src/ui/components/sidebar-tree-node.astro +0 -143
  67. package/src/ui/components/sidebar-tree.astro +0 -84
  68. package/src/ui/components/sidebar.astro +0 -29
  69. package/src/ui/components/theme-toggle.astro +0 -63
@@ -0,0 +1,178 @@
1
+ ---
2
+ // Pages — <subpath>/pages/[...path], injected by ../../../index.mjs. The site's own pages, walked
3
+ // from src/pages/ the same way the live derivation walks them (../../shell/live-files.mjs), in a
4
+ // tree nested the way the routes nest.
5
+ //
6
+ // <subpath>/pages every page as a card, with a live thumbnail and what it mounts
7
+ // <subpath>/pages/<route> one page on the viewport canvas — the REAL page, at its real URL, in
8
+ // an <iframe> you size; `index` is the home page
9
+ //
10
+ // A dynamic route ([slug]) is listed but cannot be shown: the lab would need one of its generated
11
+ // paths, and a static site only knows those at build time.
12
+ import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
13
+ import Shell from "../Shell.astro";
14
+ import Tree from "../Tree.astro";
15
+ import Icon from "../Icon.astro";
16
+ import ViewportControls from "../ViewportControls.astro";
17
+ import ViewportStage from "../ViewportStage.astro";
18
+ import { hrefs, type Crumb, type FilterOption } from "../model";
19
+ import { componentsOf } from "../trees";
20
+ import { pagesTree, rootDir, sitePagesModel, type SitePage } from "../site-data";
21
+ import { pinCounts } from "../pins-data";
22
+ import { buildLabIndex } from "../../shell/lab-index";
23
+ import { pageRoute, sitePages } from "../../shell/live-files.mjs";
24
+
25
+ export const getStaticPaths = (() => [
26
+ { params: { path: undefined }, props: { route: null as string | null } },
27
+ ...sitePages(rootDir)
28
+ .map((file) => pageRoute(file))
29
+ .filter((route) => !route.includes("["))
30
+ .map((route) => ({
31
+ params: { path: route === "/" ? "index" : route.replace(/^\/+/, "") },
32
+ props: { route: route as string | null },
33
+ })),
34
+ ]) satisfies GetStaticPaths;
35
+
36
+ type Props = InferGetStaticPropsType<typeof getStaticPaths>;
37
+
38
+ const { route } = Astro.props as Props;
39
+ const pages = sitePagesModel();
40
+ const page: SitePage | null = route ? (pages.find((p) => p.route === route) ?? null) : null;
41
+ const components = componentsOf(buildLabIndex());
42
+ const byFile = new Map(components.filter((c) => c.componentFile).map((c) => [c.componentFile!, c]));
43
+ const pins = await pinCounts();
44
+
45
+ const crumbs: Crumb[] = [{ label: "Site" }, { label: "Pages", href: hrefs.pages }];
46
+ const filters: FilterOption[] = [
47
+ { value: "mounts", label: "Mounts components" },
48
+ { value: "static", label: "No components" },
49
+ { value: "dynamic", label: "Dynamic routes" },
50
+ ];
51
+ // The iframe's address: the site's own route. Astro serves both `/about` and `/about/` in dev and
52
+ // the build writes `/about/index.html`, so the bare route resolves either way.
53
+ const base = import.meta.env.BASE_URL.replace(/\/+$/, "");
54
+ const src = page ? `${base}${page.route}` : "";
55
+ ---
56
+
57
+ <Shell
58
+ title={page ? page.label : "Pages"}
59
+ active="pages"
60
+ panel={{ label: "Pages", crumbs, filters, filterLabel: "Show pages", searchPlaceholder: "Search pages" }}
61
+ >
62
+ <Tree slot="panel" nodes={pagesTree(pages, page?.route)} />
63
+
64
+ {
65
+ page ? (
66
+ <Fragment slot="navbar">
67
+ <div class="lab-navbar__id">
68
+ <h1 class="lab-navbar__title" title={page.title ?? undefined}>
69
+ {page.label}
70
+ <small>{page.route}</small>
71
+ </h1>
72
+ <button class="lab-status-btn" type="button" popovertarget="lab-mounts-menu" title="What this page mounts">
73
+ <span class="lab-pill">{page.mounts.length} component{page.mounts.length === 1 ? "" : "s"}</span>
74
+ {pins.openFor(page.file) > 0 && <span class="lab-pill lab-pill--brand">{pins.openFor(page.file)} open pins</span>}
75
+ <Icon name="chevron-down" size="sm" />
76
+ </button>
77
+ <div class="lab-menu" id="lab-mounts-menu" popover role="menu">
78
+ <p class="lab-menu__label">On this page, in order</p>
79
+ {page.mounts.length === 0 && <p class="lab-status-menu__fact">It mounts no components of its own.</p>}
80
+ {page.mounts.map((file, i) => {
81
+ const mod = byFile.get(file);
82
+ return mod ? (
83
+ <a class="lab-menu__item" role="menuitem" href={mod.stories[0]!.dashboardUrl}>
84
+ <span class="lab-slots__n">{i + 1}</span>
85
+ {mod.moduleName}
86
+ {mod.version && <span class="lab-menu__hint">{mod.version}</span>}
87
+ </a>
88
+ ) : (
89
+ <span class="lab-menu__item" aria-disabled="true" title="Not in the lab — no stories file for it">
90
+ <span class="lab-slots__n">{i + 1}</span>
91
+ {file.split("/").pop()}
92
+ </span>
93
+ );
94
+ })}
95
+ <hr />
96
+ <p class="lab-status-menu__fact lab-mono">{page.file}</p>
97
+ </div>
98
+ </div>
99
+ <div class="lab-navbar__tools">
100
+ <button class="lab-btn lab-btn--sm" type="button" data-lab-reload title="Reload the page in the frame">
101
+ <Icon name="reload" />
102
+ Reload
103
+ </button>
104
+ <a class="lab-btn lab-btn--sm" href={src} target="_blank" rel="noopener" title="Open the real page in a new tab">
105
+ <Icon name="external" />
106
+ Open
107
+ </a>
108
+ </div>
109
+ <ViewportControls defaultW={1440} defaultH={900} kind="page" />
110
+ <div class="lab-navbar__tools">
111
+ <button class="lab-btn lab-btn--sm lab-btn--icon" type="button" popovertarget="lab-actions-menu" title="More">
112
+ <Icon name="more" />
113
+ <span class="lab-visually-hidden">More actions</span>
114
+ </button>
115
+ </div>
116
+ <div class="lab-menu" id="lab-actions-menu" popover role="menu" data-lab-align="end">
117
+ {import.meta.env.DEV && (
118
+ <a class="lab-menu__item" role="menuitem" href={`vscode://file${rootDir.startsWith("/") ? "" : "/"}${rootDir}/${page.file}`}>
119
+ <Icon name="code" />
120
+ Open in VS Code
121
+ </a>
122
+ )}
123
+ <button class="lab-menu__item" type="button" role="menuitem" data-lab-copy-link>
124
+ <Icon name="link" />
125
+ <span data-lab-copy-label>Copy link to this size</span>
126
+ </button>
127
+ </div>
128
+ </Fragment>
129
+ ) : (
130
+ <Fragment slot="navbar">
131
+ <div class="lab-navbar__id">
132
+ <h1 class="lab-navbar__title">Pages</h1>
133
+ <div class="lab-navbar__meta">
134
+ <span class="lab-pill">{pages.length} page{pages.length === 1 ? "" : "s"}</span>
135
+ </div>
136
+ </div>
137
+ </Fragment>
138
+ )
139
+ }
140
+
141
+ {
142
+ page ? (
143
+ <ViewportStage src={src} title={page.title ?? page.label} />
144
+ ) : (
145
+ <div class="lab-page">
146
+ <div class="lab-cards">
147
+ {pages.map((p) => (
148
+ <article class="lab-card" data-lab-search={`${p.label} ${p.route} ${p.title ?? ""}`.toLowerCase()} data-lab-facets={p.dynamic ? "dynamic" : p.mounts.length ? "mounts" : "static"}>
149
+ {p.dynamic ? (
150
+ <div class="lab-card__main">
151
+ <span class="lab-card__thumb lab-card__thumb--note">A dynamic route — no single page to show</span>
152
+ <span class="lab-card__body">
153
+ <span class="lab-card__name"><span>{p.label}</span></span>
154
+ <span class="lab-card__sub lab-mono">{p.route}</span>
155
+ </span>
156
+ </div>
157
+ ) : (
158
+ <a class="lab-card__main" href={p.href}>
159
+ <span class="lab-card__thumb" data-frame-w="1440" data-frame-h="900">
160
+ <iframe src={`${base}${p.route}`} title={p.label} loading="lazy" tabindex="-1" />
161
+ </span>
162
+ <span class="lab-card__body">
163
+ <span class="lab-card__name"><span>{p.label}</span></span>
164
+ <span class="lab-card__sub">{p.title ?? p.route}</span>
165
+ <span class="lab-card__pills">
166
+ <span class="lab-pill lab-pill--mono">{p.route}</span>
167
+ <span class="lab-pill">{p.mounts.length} component{p.mounts.length === 1 ? "" : "s"}</span>
168
+ </span>
169
+ </span>
170
+ </a>
171
+ )}
172
+ </article>
173
+ ))}
174
+ </div>
175
+ </div>
176
+ )
177
+ }
178
+ </Shell>
@@ -0,0 +1,37 @@
1
+ ---
2
+ // Support and Settings — <subpath>/support and <subpath>/settings, injected by ../../../index.mjs.
3
+ // In the design, with nothing decided to go under either yet, so each page says exactly that.
4
+ // Both are open design questions in the project's work list.
5
+ import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
6
+ import Shell from "../Shell.astro";
7
+ import Icon from "../Icon.astro";
8
+
9
+ export const getStaticPaths = (() => [
10
+ { params: { section: "support" }, props: { key: "support" as const, title: "Support", icon: "support" as const } },
11
+ { params: { section: "settings" }, props: { key: "settings" as const, title: "Settings", icon: "settings" as const } },
12
+ ]) satisfies GetStaticPaths;
13
+
14
+ type Props = InferGetStaticPropsType<typeof getStaticPaths>;
15
+ const { key, title, icon } = Astro.props as Props;
16
+ ---
17
+
18
+ <Shell title={title} active={key}>
19
+ <Fragment slot="navbar">
20
+ <div class="lab-navbar__id">
21
+ <h1 class="lab-navbar__title">{title}</h1>
22
+ </div>
23
+ </Fragment>
24
+ <div class="lab-placeholder">
25
+ <div class="lab-placeholder__box">
26
+ <span class="lab-placeholder__icon"><Icon name={icon} /></span>
27
+ <h1>{title} is still to be designed</h1>
28
+ <p>
29
+ {
30
+ key === "settings"
31
+ ? "The lab has no settings screen yet. Everything it does is configured in astro.config.mjs, in the orbytesLab() call."
32
+ : "There is no support page yet. The package's README and docs/ folder are the documentation for now."
33
+ }
34
+ </p>
35
+ </div>
36
+ </div>
37
+ </Shell>
@@ -0,0 +1,107 @@
1
+ ---
2
+ // Tasks — <route>/[...view]: the pin board's kanban at <route> and the All Tasks table at
3
+ // <route>/all, where <route> is <subpath>/tasks by default. Injected by the PIN half
4
+ // (../../pin/index.mjs), in `astro dev` only — past its first dev-only assertion — so no build ever
5
+ // contains it. Every ticket is read off disk on each request.
6
+ //
7
+ // The board's markup, stylesheet and script are the pin half's own (../../pin/board.mjs ›
8
+ // boardParts), the same ones the standalone gallery file uses; this page only lays them out in the
9
+ // lab's chrome and maps their palette onto the lab's tokens (`.pin-board--lab`). The Astro dev
10
+ // toolbar's ticket panel reads this page the way it read /pin: by the `[data-board]` and
11
+ // `script.card-data` payloads it carries, so the two cannot disagree about what a ticket is.
12
+ import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
13
+ import Shell from "../Shell.astro";
14
+ import Tree from "../Tree.astro";
15
+ import { labConfig, type TreeNode } from "../model";
16
+ import { BOARD_CSS, BOARD_SCRIPT, boardParts, collectTickets, READY_FOR_REVIEW, STATUSES } from "../../pin/board.mjs";
17
+
18
+ export const getStaticPaths = (() => [
19
+ { params: { view: undefined }, props: { view: "board" as const } },
20
+ { params: { view: "all" }, props: { view: "table" as const } },
21
+ ]) satisfies GetStaticPaths;
22
+
23
+ type Props = InferGetStaticPropsType<typeof getStaticPaths>;
24
+ const { view } = Astro.props as Props;
25
+ const tasks = labConfig.tasks;
26
+
27
+ type Ticket = { status: string; shotName?: string };
28
+ let tickets: Ticket[] = [];
29
+ let broken: { file: string; reason: string }[] = [];
30
+ let error: string | null = null;
31
+ if (tasks) {
32
+ try {
33
+ ({ tickets, broken } = collectTickets(tasks.repoRoot, { backlogDir: tasks.backlogDir, includeCancelled: true }));
34
+ } catch (e) {
35
+ error = String((e as Error)?.message ?? e).split("\n")[0]!;
36
+ }
37
+ }
38
+ const assetHref = (t: Ticket) =>
39
+ tasks && t.shotName ? tasks.assets + t.shotName.split("/").map(encodeURIComponent).join("/") : null;
40
+ const parts = boardParts(tickets, broken, { assetHref, apiHref: tasks?.api ?? null, links: [], view });
41
+
42
+ const base = tasks?.base ?? "";
43
+ const here = view === "table" ? `${base}/all` : base;
44
+ let wanted: string | null = null;
45
+ try {
46
+ wanted = Astro.url.searchParams.get("status");
47
+ } catch {
48
+ wanted = null;
49
+ }
50
+ const statusNodes: TreeNode[] = [
51
+ { id: "status:all", label: "All tickets", href: here, icon: "tasks", count: tickets.length, current: !wanted },
52
+ ...[...STATUSES, ...new Set(tickets.map((t) => t.status).filter((s) => !STATUSES.includes(s)))].map((status) => {
53
+ const n = tickets.filter((t) => t.status === status).length;
54
+ return {
55
+ id: `status:${status}`,
56
+ label: status,
57
+ href: `${here}?status=${encodeURIComponent(status)}`,
58
+ icon: "check" as const,
59
+ count: n,
60
+ dot: status === READY_FOR_REVIEW && n > 0 ? ("open" as const) : undefined,
61
+ current: wanted === status,
62
+ };
63
+ }),
64
+ ];
65
+ const title = view === "table" ? "All Tasks" : "Kanban Board";
66
+ ---
67
+
68
+ <Shell
69
+ title={title}
70
+ active={view === "table" ? "tasks-all" : "tasks-board"}
71
+ panel={{ label: "Tasks", crumbs: [{ label: "Tasks" }, { label: title }], search: false }}
72
+ >
73
+ <Tree slot="panel" nodes={statusNodes} />
74
+
75
+ <Fragment slot="navbar">
76
+ <div class="lab-navbar__id">
77
+ <h1 class="lab-navbar__title">{title}</h1>
78
+ <div class="lab-navbar__meta">
79
+ {parts.counts.waiting > 0 && <span class="lab-pill lab-pill--brand">{parts.counts.waiting} ready for your review</span>}
80
+ <span class="lab-pill">{parts.counts.total} ticket{parts.counts.total === 1 ? "" : "s"}</span>
81
+ {parts.counts.needsHuman > 0 && <span class="lab-pill lab-pill--error">{parts.counts.needsHuman} needing a human</span>}
82
+ </div>
83
+ </div>
84
+ </Fragment>
85
+
86
+ <style is:inline set:html={BOARD_CSS}></style>
87
+ <div class="pin-board pin-board--lab">
88
+ {
89
+ !tasks || error ? (
90
+ <div class="lab-page">
91
+ <div class="lab-empty">
92
+ <strong>The pin board is not running</strong>
93
+ {error ?? "Tasks exist in `astro dev` only, with the pin half on and a git repository to write tickets into."}
94
+ </div>
95
+ </div>
96
+ ) : (
97
+ <>
98
+ <header class="top" set:html={parts.tools} />
99
+ <Fragment set:html={parts.broken} />
100
+ <Fragment set:html={parts.main} />
101
+ <Fragment set:html={parts.dialog} />
102
+ </>
103
+ )
104
+ }
105
+ </div>
106
+ {tasks && !error && <script is:inline set:html={BOARD_SCRIPT}></script>}
107
+ </Shell>
@@ -65,6 +65,12 @@ Every deliberate divergence in src/core/
65
65
  `story.astro` per route (2026-09-22). Upstream routes the dashboard and the bare story through
66
66
  one page, so a bare story also received the lab's chrome. Splitting them is what lets the
67
67
  lab's own styling stop at the preview's edge — see src/ui/lab.css.
68
+ 8a. The dashboard route renders NO story (2026-09-24, the redesign). `virtual-routes.ts` emits a
69
+ dashboard route that imports neither the story module nor the decorators, and
70
+ `lib/pages/story.astro` renders the package's own component view (src/chrome/StoryView.astro),
71
+ which shows the story in an <iframe> of its bare route. That is what keeps every section's CSS
72
+ off every chrome page. `lib/components/home.astro` — upstream's default home — mounts the
73
+ package's own home (src/Home.astro), since the splash it used to mount is gone (see 16).
68
74
 
69
75
  --------------------------------------------------------------------------------
70
76
  Every deliberate divergence in src/ui/ (all of it added 2026-09-22)
@@ -100,6 +106,16 @@ Every deliberate divergence in src/ui/ (all of it added 2026-09-22)
100
106
  than from `@astrobook/core/client`, and the `import '@astrobook/types'` side-effect lines are
101
107
  gone — the ambient declarations they pulled in live in src/types/virtual.d.ts.
102
108
 
109
+ 16. THE VENDORED SIDEBAR UI IS RETIRED (2026-09-24). The lab's chrome was redesigned and is
110
+ orbytes-authored now, in src/chrome/ — it is not upstream's and carries no vendoring header.
111
+ Deleted from src/ui/components/: layout.astro, dashboard-layout.astro, home.astro (the
112
+ splash), theme-toggle.astro, the eleven sidebar*.astro / sidebar-search-script.ts files,
113
+ build-tree.ts, build-path.ts, collapse-duration.ts and compress-terms.ts. What remains of
114
+ upstream's UI is what the bare story page and the home route still need: app.astro,
115
+ preview-layout.astro, theme-script.astro, theme-message.ts, theme.ts and index.ts. lab.css
116
+ was never upstream's (9) and is the redesign's stylesheet. `theme-script.astro` gained a
117
+ `storageKey` prop, so the chrome and the preview each keep their own light/dark.
118
+
103
119
  --------------------------------------------------------------------------------
104
120
  Every deliberate divergence in src/types/ (all of it added 2026-09-22)
105
121
  --------------------------------------------------------------------------------
@@ -1,8 +1,10 @@
1
1
  ---
2
2
  // Vendored from astrobook 0.13.3 (packages/astrobook/lib/components/home.astro) — see
3
3
  // ../../LICENSE-astrobook.
4
- // Divergence: `@astrobook/ui/components/home.astro` → the vendored UI, by relative path.
5
- import Home from '../../../ui/components/home.astro'
4
+ // Divergence: upstream's default home is the UI package's splash. The redesign (2026-09-24)
5
+ // retired that splash with the rest of the vendored sidebar UI, so the default is the package's
6
+ // own home page (../../../Home.astro) — the one `orbytesLab()` always passes anyway.
7
+ import Home from '../../../Home.astro'
6
8
  ---
7
9
 
8
10
  <Home />
@@ -1,16 +1,18 @@
1
1
  ---
2
2
  // Vendored from astrobook 0.13.3 (packages/astrobook/lib/pages/story.astro) — see
3
3
  // ../../LICENSE-astrobook.
4
- // Divergence: `@astrobook/ui/components/layout.astro` → the vendored UI's DASHBOARD layout, by
5
- // relative path. Upstream used one layout for the dashboard and the bare story alike; this page
6
- // is now the dashboard only (chrome + the story inside it), and the bare story route renders
7
- // through ./preview.astro. Why the two split: ../../../ui/lab.css.
8
- import LayoutComponent from '../../../ui/components/dashboard-layout.astro'
9
- import type { ComponentProps } from 'astro/types'
4
+ // Divergence: this is the DASHBOARD page only, and since the redesign (2026-09-24) it renders the
5
+ // package's own component view (../../../chrome/StoryView.astro) — the chrome with the story in an
6
+ // <iframe> on a sizable canvas — rather than a layout with the story rendered inline. So it takes no
7
+ // slot: ../../virtual-module/virtual-routes.ts no longer imports the story module into the
8
+ // dashboard route at all, which is what keeps every section's CSS off every chrome page. The bare
9
+ // story route renders through ./preview.astro. Why the two split: ../../../ui/lab.css.
10
+ import StoryView from '../../../chrome/StoryView.astro'
10
11
 
11
- type Props = ComponentProps<typeof LayoutComponent>
12
+ interface Props {
13
+ story: string
14
+ hasSidebar: boolean
15
+ }
12
16
  ---
13
17
 
14
- <LayoutComponent {...Astro.props}>
15
- <slot />
16
- </LayoutComponent>
18
+ <StoryView story={Astro.props.story} />
@@ -3,6 +3,8 @@
3
3
  // (upstream is bundled before it ships; this package is not).
4
4
  // Divergence: the specifiers this file emits into every generated route are absolute paths into ./lib-paths.ts rather
5
5
  // than bare `astrobook/...` ids — see that file for why.
6
+ // Divergence: the dashboard route imports no story module (2026-09-24) — see
7
+ // createVirtualRouteComponent.
6
8
  import path from 'node:path'
7
9
 
8
10
  import type { AstroIntegrationLogger } from 'astro'
@@ -98,10 +100,24 @@ export function createVirtualRouteComponent(route: VirtualRoute): string {
98
100
  .replaceAll(/_+/g, '_')
99
101
  .replace(/_+$/, '')
100
102
 
101
- // Divergence: upstream emits STORY_PAGE for both routes. The dashboard route (hasSidebar) is
102
- // chrome plus preview and keeps it; the bare story route gets PREVIEW_PAGE, which carries the
103
- // consumer's CSS and none of the lab's — decided 2026-09-22, see ../../ui/lab.css.
104
- const page = route.props.hasSidebar ? STORY_PAGE : PREVIEW_PAGE
103
+ // Divergence: upstream emits STORY_PAGE for both routes, each rendering the story inline. Here
104
+ // the bare story route gets PREVIEW_PAGE, which carries the consumer's CSS and none of the lab's
105
+ // (decided 2026-09-22, see ../../ui/lab.css) — and the dashboard route (hasSidebar) renders the
106
+ // story NOT AT ALL (2026-09-24): the component view shows it in an <iframe> of the bare route, so
107
+ // the dashboard imports neither the story module nor anything it pulls in. That is what keeps
108
+ // every section's scoped CSS off every chrome page.
109
+ if (route.props.hasSidebar) {
110
+ return `
111
+ ---
112
+ // Automatically generated by Astrobook
113
+
114
+ import StoryPage from '${STORY_PAGE}';
115
+ ---
116
+
117
+ <StoryPage story={'${route.props.story}'} hasSidebar={true} />
118
+ `.trim()
119
+ }
120
+ const page = PREVIEW_PAGE
105
121
 
106
122
  return `
107
123
  ---