@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.
Files changed (82) hide show
  1. package/README.md +261 -116
  2. package/bin/pin-gallery.mjs +53 -19
  3. package/defaults.mjs +72 -20
  4. package/dist/core/virtual-module/virtual-routes.js +12 -1
  5. package/docs/PIN-CONTRACT.md +84 -10
  6. package/docs/PIN.md +117 -37
  7. package/index.d.ts +41 -23
  8. package/index.mjs +46 -91
  9. package/package.json +7 -3
  10. package/src/Home.astro +166 -264
  11. package/src/LabHead.astro +37 -1047
  12. package/src/chrome/ActionsMenu.astro +97 -0
  13. package/src/chrome/ComponentCard.astro +76 -0
  14. package/src/chrome/Icon.astro +21 -0
  15. package/src/chrome/LICENSE-icons +43 -0
  16. package/src/chrome/Nav.astro +131 -0
  17. package/src/chrome/Panel.astro +117 -0
  18. package/src/chrome/Properties.astro +104 -0
  19. package/src/chrome/SectionsTree.astro +128 -0
  20. package/src/chrome/Shell.astro +120 -0
  21. package/src/chrome/Sprite.astro +23 -0
  22. package/src/chrome/StoryView.astro +192 -0
  23. package/src/chrome/Tree.astro +86 -0
  24. package/src/chrome/ViewportControls.astro +173 -0
  25. package/src/chrome/ViewportStage.astro +55 -0
  26. package/src/chrome/fonts/OFL.txt +93 -0
  27. package/src/chrome/fonts/inter-latin-wght-normal.woff2 +0 -0
  28. package/src/chrome/icons.ts +68 -0
  29. package/src/chrome/marks-client.ts +75 -0
  30. package/src/chrome/model.ts +156 -0
  31. package/src/chrome/navbar-client.ts +324 -0
  32. package/src/chrome/params-client.ts +434 -0
  33. package/src/chrome/pins-data.ts +63 -0
  34. package/src/chrome/shell-client.ts +468 -0
  35. package/src/chrome/site-data.ts +230 -0
  36. package/src/chrome/trees.ts +257 -0
  37. package/src/chrome/viewport-client.ts +405 -0
  38. package/src/chrome/views/Assets.astro +125 -0
  39. package/src/chrome/views/Pages.astro +214 -0
  40. package/src/chrome/views/Placeholder.astro +37 -0
  41. package/src/chrome/views/Tasks.astro +79 -0
  42. package/src/core/LICENSE-astrobook +21 -0
  43. package/src/core/lib/components/home.astro +4 -2
  44. package/src/core/lib/pages/story.astro +12 -10
  45. package/src/core/utils/kebab-case.ts +2 -2
  46. package/src/core/virtual-module/virtual-routes.ts +20 -4
  47. package/src/pin/board.mjs +414 -190
  48. package/src/pin/index.mjs +67 -22
  49. package/src/pin/tickets.mjs +6 -5
  50. package/src/pin/toolbar.js +82 -4
  51. package/src/shell/Browse.astro +131 -353
  52. package/src/shell/Viewport.astro +22 -1315
  53. package/src/shell/lab-index.ts +28 -18
  54. package/src/shell/lab-params.ts +113 -6
  55. package/src/shell/live-files.mjs +212 -10
  56. package/src/shell/marks.mjs +17 -41
  57. package/src/ui/components/app.astro +5 -7
  58. package/src/ui/components/preview-layout.astro +17 -0
  59. package/src/ui/components/theme-script.astro +17 -5
  60. package/src/ui/lab.css +3754 -371
  61. package/virtual.d.ts +13 -4
  62. package/bin/lab-cull.mjs +0 -401
  63. package/src/shell/CardGrid.astro +0 -297
  64. package/src/ui/components/build-path.ts +0 -13
  65. package/src/ui/components/build-tree.ts +0 -108
  66. package/src/ui/components/collapse-duration.ts +0 -28
  67. package/src/ui/components/compress-terms.ts +0 -10
  68. package/src/ui/components/dashboard-layout.astro +0 -39
  69. package/src/ui/components/home.astro +0 -65
  70. package/src/ui/components/layout.astro +0 -110
  71. package/src/ui/components/sidebar-button-fullscreen.astro +0 -38
  72. package/src/ui/components/sidebar-button-search.astro +0 -23
  73. package/src/ui/components/sidebar-button-theme.astro +0 -9
  74. package/src/ui/components/sidebar-button.astro +0 -24
  75. package/src/ui/components/sidebar-resize-handle.astro +0 -74
  76. package/src/ui/components/sidebar-search-panel.astro +0 -41
  77. package/src/ui/components/sidebar-search-script.ts +0 -103
  78. package/src/ui/components/sidebar-title.astro +0 -17
  79. package/src/ui/components/sidebar-tree-node.astro +0 -143
  80. package/src/ui/components/sidebar-tree.astro +0 -84
  81. package/src/ui/components/sidebar.astro +0 -29
  82. package/src/ui/components/theme-toggle.astro +0 -63
package/src/Home.astro CHANGED
@@ -1,47 +1,69 @@
1
1
  ---
2
- // Lab home — Astrobook's `home` component, given to it by the integration (../index.mjs).
3
- // Replaces the stock splash with the lab index: a row of tier cards (each opening its folder page
4
- // at <subpath>/browse/<tier>), then every story as a live thumbnail card grouped by tier, each
5
- // card carrying its derived pill — live slot, used-by, or nothing.
2
+ // Lab home — Astrobook's `home` component, given to it by the integration (../index.mjs) and
3
+ // rendered as the whole `/lab` document (./ui/components/app.astro).
6
4
  //
7
- // Everything drawn here comes from ./shell/lab-index.ts, which reads Astrobook's own story list
8
- // (so ids and URLs never drift from what it routes) and derives "live" from what the pages under
9
- // src/pages/ actually mount. The cards themselves are ./shell/CardGrid.astro, shared with the
10
- // folder pages.
5
+ // A summary, not a gallery (redesigned 2026-09-24). The previous home drew every story in the lab
6
+ // as a live <iframe> thumbnail — forty-odd full renders of the site, each loading its fonts and
7
+ // stylesheets, before you could click anything. Those cards now live one tier at a time on the
8
+ // tier pages, lazily; the home page is the counts and the one thing only this lab can tell you —
9
+ // what is mounted on which page, in which slot, right now.
11
10
  import { execSync } from "node:child_process";
12
- import labConfig from "virtual:orbytes-lab/config.mjs";
13
- import CardGrid from "./shell/CardGrid.astro";
14
- import {
15
- buildLabIndex,
16
- labBase,
17
- liveCountOf,
18
- sortTiers,
19
- STAGING_URL,
20
- tierLabel,
21
- } from "./shell/lab-index";
11
+ import Shell from "./chrome/Shell.astro";
12
+ import Icon from "./chrome/Icon.astro";
13
+ import { hrefs, labConfig, tasksBase } from "./chrome/model";
14
+ import { componentsOf } from "./chrome/trees";
15
+ import { rootDir, siteAssetsModel, sitePagesModel } from "./chrome/site-data";
16
+ import { buildLabIndex, STAGING_URL, sortTiers, tierLabel } from "./shell/lab-index";
22
17
 
23
18
  const index = buildLabIndex();
24
- const tiers = sortTiers([...new Set(index.items.map((item) => item.tier))]).map((tier) => {
25
- const items = index.items.filter((item) => item.tier === tier);
19
+ const components = componentsOf(index);
20
+ const pages = sitePagesModel();
21
+ const assets = siteAssetsModel();
22
+
23
+ const tiers = sortTiers([...new Set(components.map((c) => c.tier))]).map((tier) => {
24
+ const mods = components.filter((c) => c.tier === tier);
26
25
  return {
27
26
  tier,
28
27
  label: tierLabel(tier),
29
- href: tier ? `${labBase}/browse/${tier}` : `${labBase}/browse`,
30
- count: items.length,
31
- live: liveCountOf(items),
32
- items,
28
+ href: hrefs.tier(tier),
29
+ components: mods.length,
30
+ stories: mods.reduce((n, m) => n + m.stories.length, 0),
31
+ live: mods.filter((m) => m.live).length,
32
+ used: mods.filter((m) => !m.live && m.usedBy.length).length,
33
+ unused: mods.filter((m) => !m.live && !m.usedBy.length).length,
33
34
  };
34
35
  });
35
36
 
37
+ // The pin board's counts, in dev only (tasksBase is null in every build).
38
+ let tasks: { open: number; review: number } | null = null;
39
+ if (tasksBase && labConfig.tasks) {
40
+ const { collectTickets, OPEN_STATUSES, READY_FOR_REVIEW } = await import("./pin/board.mjs");
41
+ try {
42
+ const { tickets } = collectTickets(labConfig.tasks.repoRoot, { backlogDir: labConfig.tasks.backlogDir });
43
+ tasks = {
44
+ open: tickets.filter((t: { status: string }) => OPEN_STATUSES.includes(t.status)).length,
45
+ review: tickets.filter((t: { status: string }) => t.status === READY_FOR_REVIEW).length,
46
+ };
47
+ } catch {
48
+ tasks = null;
49
+ }
50
+ }
51
+
52
+ // What is on the site: every page, and in slot order what it mounts, with the lab component it is.
53
+ const byComponentFile = new Map(components.filter((c) => c.componentFile).map((c) => [c.componentFile!, c]));
54
+ const onSite = pages
55
+ .filter((page) => page.mounts.length > 0)
56
+ .map((page) => ({
57
+ page,
58
+ slots: page.mounts.map((file, i) => ({ slot: i + 1, file, component: byComponentFile.get(file) ?? null })),
59
+ }));
60
+
36
61
  // Local branches → per-branch preview builds, when the consumer configured a URL template.
37
- // `previewUrlTemplate` is a string with a {branch} placeholder rather than a function because the
38
- // config crosses a virtual module and has to stay JSON-serialisable. No template, no branch list:
39
- // a host that does not build branches has nothing to link to.
40
62
  const previewTemplate = labConfig.previewUrlTemplate;
41
63
  let branches: string[] = [];
42
64
  if (previewTemplate) {
43
65
  try {
44
- branches = execSync('git branch --format="%(refname:short)"', { encoding: "utf8" })
66
+ branches = execSync('git branch --format="%(refname:short)"', { encoding: "utf8", cwd: rootDir })
45
67
  .split("\n")
46
68
  .map((line) => line.trim())
47
69
  .filter(Boolean);
@@ -52,247 +74,127 @@ if (previewTemplate) {
52
74
  const previewAlias = (branch: string) =>
53
75
  (previewTemplate ?? "").replace(
54
76
  /\{branch\}/g,
55
- branch
56
- .toLowerCase()
57
- .replace(/[^a-z0-9]+/g, "-")
58
- .replace(/^-+|-+$/g, ""),
77
+ branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""),
59
78
  );
79
+ const unusedAssets = assets.filter((a) => a.usedBy.length === 0).length;
60
80
  ---
61
81
 
62
- <div class="lab-home lab-chrome">
63
- <header class="lab-home__header">
64
- <p class="lab-home__eyebrow">// component lab</p>
65
- <h1 class="lab-home__title">{labConfig.title}</h1>
66
- <p class="lab-home__intro">
67
- Every section, component and exploration in the build, rendered live. Cards open the story
68
- in the dashboard; thumbnails are the bare renders themselves, scaled down. A white pill means
69
- a page mounts that component right now, and names the page when it is not the home page. On
70
- section versions a second pill says whether that version has been made responsive; tick it in
71
- the sidebar. Where a deployed lab is configured, each card also links to the same story
72
- there — the real minified build, which this dev server is not — and, locally, to the
73
- component in VS Code.
74
- </p>
75
- {
76
- STAGING_URL && (
77
- <a class="lab-home__staging" href={STAGING_URL} target="_blank" rel="noopener">
78
- Staging site
79
- <span aria-hidden="true">&nbsp;&#8599;</span>
80
- </a>
81
- )
82
- }
83
- </header>
82
+ <Shell title="Home" active="home" navbarClass="lab-navbar--listing">
83
+ <Fragment slot="navbar">
84
+ <div class="lab-listing-bar">
85
+ <h1 class="lab-listing-bar__title">Home</h1>
86
+ <p class="lab-listing-bar__counts">{labConfig.title}</p>
87
+ {
88
+ STAGING_URL && (
89
+ <a class="lab-btn lab-btn--sm lab-listing-bar__end" href={STAGING_URL} target="_blank" rel="noopener">
90
+ <Icon name="external" />
91
+ Staging
92
+ </a>
93
+ )
94
+ }
95
+ </div>
96
+ </Fragment>
97
+
98
+ <div class="lab-page">
99
+ <header class="lab-page__head">
100
+ <h2 class="lab-page__title">What is on this site</h2>
101
+ <p class="lab-page__lede">
102
+ Every page, section and component in the build, rendered live — and which of them the site
103
+ actually mounts right now, read from <code>src/pages/</code> rather than declared.
104
+ </p>
105
+ </header>
106
+
107
+ <div class="lab-stats">
108
+ <a class="lab-stat" href={hrefs.pages}>
109
+ <span class="lab-stat__head"><Icon name="pages" />Pages</span>
110
+ <span class="lab-stat__value">{pages.length}</span>
111
+ <span class="lab-stat__foot">
112
+ <span class="lab-pill">{onSite.length} with components</span>
113
+ {pages.some((p) => p.dynamic) && <span class="lab-pill">{pages.filter((p) => p.dynamic).length} dynamic</span>}
114
+ </span>
115
+ </a>
116
+ {
117
+ tiers.map((tier) => (
118
+ <a class="lab-stat" href={tier.href}>
119
+ <span class="lab-stat__head">
120
+ <Icon name={tier.tier === labConfig.sectionsTier ? "sections" : tier.tier === "components" ? "components" : "component"} />
121
+ {tier.label}
122
+ </span>
123
+ <span class="lab-stat__value">{tier.components}</span>
124
+ <span class="lab-stat__foot">
125
+ {tier.live > 0 && <span class="lab-pill lab-pill--live">{tier.live} live</span>}
126
+ {tier.used > 0 && <span class="lab-pill lab-pill--used">{tier.used} used</span>}
127
+ {tier.unused > 0 && <span class="lab-pill">{tier.unused} unused</span>}
128
+ <span class="lab-pill">{tier.stories} variant{tier.stories === 1 ? "" : "s"}</span>
129
+ </span>
130
+ </a>
131
+ ))
132
+ }
133
+ <a class="lab-stat" href={hrefs.assets}>
134
+ <span class="lab-stat__head"><Icon name="assets" />Assets</span>
135
+ <span class="lab-stat__value">{assets.length}</span>
136
+ <span class="lab-stat__foot">
137
+ {unusedAssets > 0 ? <span class="lab-pill lab-pill--unresponsive">{unusedAssets} unused</span> : <span class="lab-pill">all referenced</span>}
138
+ </span>
139
+ </a>
140
+ {
141
+ tasks && tasksBase && (
142
+ <a class="lab-stat" href={tasksBase}>
143
+ <span class="lab-stat__head"><Icon name="tasks" />Tasks</span>
144
+ <span class="lab-stat__value">{tasks.open}</span>
145
+ <span class="lab-stat__foot">
146
+ {tasks.review > 0 ? <span class="lab-pill lab-pill--brand">{tasks.review} ready for review</span> : <span class="lab-pill">open</span>}
147
+ </span>
148
+ </a>
149
+ )
150
+ }
151
+ </div>
84
152
 
85
- <section class="lab-home__tiers">
86
153
  {
87
- tiers.map((tier) => (
88
- <a class="tier-card" href={tier.href}>
89
- <span class="tier-card__eyebrow">// {tier.tier || "root"}</span>
90
- <span class="tier-card__name">{tier.label}</span>
91
- <span class="tier-card__meta">
92
- <span>
93
- {tier.count} stor{tier.count === 1 ? "y" : "ies"}
94
- </span>
95
- {tier.live > 0 && <span class="tier-card__live">{tier.live} live</span>}
96
- </span>
97
- </a>
154
+ onSite.map(({ page, slots }) => (
155
+ <section>
156
+ <h3 class="lab-section-title">
157
+ <a href={page.href}>{page.label}</a>
158
+ <small>{page.route} · {slots.length} mounted</small>
159
+ </h3>
160
+ <ol class="lab-slots">
161
+ {slots.map(({ slot, file, component }) => (
162
+ <li class="lab-slots__row">
163
+ <span class="lab-slots__n">{slot}</span>
164
+ {component ? (
165
+ <a class="lab-slots__name" href={component.stories[0]!.dashboardUrl}>
166
+ {component.moduleName}
167
+ {component.version && <span class="lab-card__version">{component.version}</span>}
168
+ </a>
169
+ ) : (
170
+ <span class="lab-slots__name lab-slots__name--bare" title="Not in the lab — no *.stories.ts file for it">
171
+ {file.split("/").pop()}
172
+ </span>
173
+ )}
174
+ <span class="lab-slots__file lab-mono">{file}</span>
175
+ </li>
176
+ ))}
177
+ </ol>
178
+ </section>
98
179
  ))
99
180
  }
100
- </section>
101
-
102
- {
103
- branches.length > 0 && (
104
- <section class="lab-home__branches">
105
- <h2 class="lab-home__group-title">Branches</h2>
106
- <ul>
107
- {branches.map((branch) => (
108
- <li>
109
- <code>{branch}</code>
110
- <a href={previewAlias(branch)} target="_blank" rel="noopener">
111
- branch preview
112
- </a>
113
- </li>
114
- ))}
115
- </ul>
116
- </section>
117
- )
118
- }
119
-
120
- {
121
- tiers.map((tier) => (
122
- <section class="lab-home__group">
123
- <h2 class="lab-home__group-title">
124
- <a href={tier.href}>{tier.label}</a>
125
- </h2>
126
- <CardGrid items={tier.items} />
127
- </section>
128
- ))
129
- }
130
- </div>
131
-
132
- <style>
133
- .lab-home {
134
- background-color: var(--lab-color-bg);
135
- color: var(--lab-color-text);
136
- font-family: var(--lab-font-body);
137
- min-height: 100%;
138
- padding: 3em 2.5em 5em;
139
- }
140
-
141
- .lab-home__eyebrow {
142
- font-family: var(--lab-font-mono);
143
- font-weight: 500;
144
- color: var(--lab-color-text-muted);
145
- margin-bottom: 0.75em;
146
- }
147
-
148
- .lab-home__title {
149
- font-family: var(--lab-font-heading);
150
- color: var(--lab-color-text-strong);
151
- font-size: 2.25em;
152
- font-weight: 500;
153
- letter-spacing: -0.5px;
154
- margin-bottom: 0.4em;
155
- }
156
-
157
- .lab-home__intro {
158
- max-width: 42em;
159
- color: var(--lab-color-text-faint);
160
- line-height: 1.55;
161
- margin-bottom: 1.5em;
162
- }
163
-
164
- .lab-home__staging {
165
- display: inline-block;
166
- font-family: var(--lab-font-mono);
167
- font-weight: 500;
168
- color: var(--lab-color-text-strong);
169
- text-decoration: none;
170
- border: 1px solid var(--lab-color-border);
171
- border-left: 1px solid var(--lab-color-text-strong);
172
- padding: 0.75em 1.25em;
173
- transition: background-color var(--lab-transition), color var(--lab-transition);
174
- }
175
-
176
- .lab-home__staging:hover,
177
- .lab-home__staging:focus-visible {
178
- background-color: var(--lab-color-text-strong);
179
- color: var(--lab-color-accent-text);
180
- }
181
-
182
- .lab-home__tiers {
183
- display: grid;
184
- grid-template-columns: repeat(auto-fit, minmax(14em, 1fr));
185
- gap: 1em;
186
- margin-top: 2.5em;
187
- }
188
-
189
- .tier-card {
190
- display: flex;
191
- flex-direction: column;
192
- gap: 0.5em;
193
- text-decoration: none;
194
- border: 1px solid var(--lab-color-border);
195
- background-color: var(--lab-color-surface);
196
- padding: 1.25em 1.5em;
197
- transition: border-color var(--lab-transition);
198
- }
199
-
200
- .tier-card:hover,
201
- .tier-card:focus-visible {
202
- border-color: var(--lab-color-border-strong);
203
- }
204
-
205
- .tier-card__eyebrow {
206
- font-family: var(--lab-font-mono);
207
- font-weight: 500;
208
- font-size: 0.75em;
209
- color: var(--lab-color-text-faint);
210
- }
211
-
212
- .tier-card__name {
213
- font-family: var(--lab-font-heading);
214
- font-size: 1.4em;
215
- font-weight: 500;
216
- color: var(--lab-color-text-strong);
217
- }
218
-
219
- .tier-card__meta {
220
- display: flex;
221
- align-items: baseline;
222
- justify-content: space-between;
223
- gap: 1em;
224
- font-family: var(--lab-font-mono);
225
- font-size: 0.8em;
226
- color: var(--lab-color-text-faint);
227
- }
228
-
229
- .tier-card__live {
230
- font-weight: 500;
231
- line-height: 1;
232
- padding: 0.45em 0.85em;
233
- border-radius: var(--lab-radius-pill);
234
- background-color: var(--lab-color-accent);
235
- color: var(--lab-color-accent-text);
236
- }
237
-
238
- .lab-home__branches {
239
- margin-top: 2.5em;
240
- }
241
-
242
- .lab-home__branches ul {
243
- list-style: none;
244
- padding: 0;
245
- margin: 0;
246
- }
247
-
248
- .lab-home__branches li {
249
- display: flex;
250
- align-items: baseline;
251
- gap: 1em;
252
- padding: 0.5em 0;
253
- border-bottom: 1px solid var(--lab-color-border);
254
- }
255
181
 
256
- .lab-home__branches code {
257
- font-family: var(--lab-font-mono);
258
- color: var(--lab-color-text-strong);
259
- }
260
-
261
- .lab-home__branches a {
262
- font-family: var(--lab-font-mono);
263
- font-size: 0.85em;
264
- color: var(--lab-color-text-faint);
265
- text-decoration: none;
266
- }
267
-
268
- .lab-home__branches a:hover,
269
- .lab-home__branches a:focus-visible {
270
- color: var(--lab-color-text-strong);
271
- text-decoration: underline;
272
- }
273
-
274
- .lab-home__group {
275
- margin-top: 3em;
276
- }
277
-
278
- .lab-home__group-title {
279
- font-family: var(--lab-font-mono);
280
- font-weight: 500;
281
- font-size: 1em;
282
- color: var(--lab-color-text-muted);
283
- margin-bottom: 1em;
284
- padding-bottom: 0.5em;
285
- border-bottom: 1px solid var(--lab-color-border);
286
- }
287
-
288
- .lab-home__group-title a {
289
- color: inherit;
290
- text-decoration: none;
291
- }
292
-
293
- .lab-home__group-title a:hover,
294
- .lab-home__group-title a:focus-visible {
295
- color: var(--lab-color-text-strong);
296
- text-decoration: underline;
297
- }
298
- </style>
182
+ {
183
+ branches.length > 0 && (
184
+ <section>
185
+ <h3 class="lab-section-title">Branches</h3>
186
+ <ol class="lab-slots">
187
+ {branches.map((branch) => (
188
+ <li class="lab-slots__row">
189
+ <span class="lab-slots__name lab-mono">{branch}</span>
190
+ <a class="lab-slots__file" href={previewAlias(branch)} target="_blank" rel="noopener">
191
+ branch preview ↗
192
+ </a>
193
+ </li>
194
+ ))}
195
+ </ol>
196
+ </section>
197
+ )
198
+ }
199
+ </div>
200
+ </Shell>