@orbytes/astrolab 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +37 -0
- package/README.md +410 -0
- package/bin/lab-cull.mjs +401 -0
- package/bin/pin-gallery.mjs +121 -0
- package/defaults.mjs +120 -0
- package/dist/core/astro-integration.js +130 -0
- package/dist/core/index.js +6 -0
- package/dist/core/lib-paths.js +29 -0
- package/dist/core/options.js +173 -0
- package/dist/core/utils/get-exports.js +52 -0
- package/dist/core/utils/invariant.js +12 -0
- package/dist/core/utils/kebab-case.js +15 -0
- package/dist/core/utils/path-builder.js +22 -0
- package/dist/core/utils/path.js +53 -0
- package/dist/core/virtual-module/get-story-modules.js +60 -0
- package/dist/core/virtual-module/story-modules.js +8 -0
- package/dist/core/virtual-module/virtual-module-ids.js +22 -0
- package/dist/core/virtual-module/virtual-routes.js +83 -0
- package/dist/core/virtual-module/vite-plugin.js +98 -0
- package/docs/PIN-CONTRACT.md +263 -0
- package/docs/PIN.md +429 -0
- package/index.d.ts +279 -0
- package/index.mjs +347 -0
- package/package.json +93 -0
- package/src/Empty.astro +4 -0
- package/src/Home.astro +298 -0
- package/src/LabHead.astro +1102 -0
- package/src/core/LICENSE-astrobook +166 -0
- package/src/core/astro-integration.ts +166 -0
- package/src/core/client.ts +89 -0
- package/src/core/index.ts +7 -0
- package/src/core/lib/components/empty.astro +1 -0
- package/src/core/lib/components/head.astro +1 -0
- package/src/core/lib/components/home.astro +8 -0
- package/src/core/lib/components/with-decorators.astro +22 -0
- package/src/core/lib/pages/app.astro +19 -0
- package/src/core/lib/pages/preview.astro +17 -0
- package/src/core/lib/pages/story.astro +16 -0
- package/src/core/lib-paths.ts +72 -0
- package/src/core/options.ts +262 -0
- package/src/core/utils/get-exports.ts +59 -0
- package/src/core/utils/invariant.ts +13 -0
- package/src/core/utils/kebab-case.ts +30 -0
- package/src/core/utils/path-builder.ts +45 -0
- package/src/core/utils/path.ts +80 -0
- package/src/core/virtual-module/get-story-modules.ts +110 -0
- package/src/core/virtual-module/story-modules.ts +9 -0
- package/src/core/virtual-module/virtual-module-ids.ts +17 -0
- package/src/core/virtual-module/virtual-routes.ts +130 -0
- package/src/core/virtual-module/vite-plugin.ts +125 -0
- package/src/pin/board.mjs +1521 -0
- package/src/pin/index.mjs +666 -0
- package/src/pin/shot.mjs +427 -0
- package/src/pin/source-stamp.mjs +159 -0
- package/src/pin/tickets.mjs +697 -0
- package/src/pin/toolbar.js +3181 -0
- package/src/shell/Browse.astro +371 -0
- package/src/shell/CardGrid.astro +297 -0
- package/src/shell/Viewport.astro +1330 -0
- package/src/shell/index.json.ts +12 -0
- package/src/shell/lab-index.ts +344 -0
- package/src/shell/lab-params.ts +245 -0
- package/src/shell/live-files.mjs +164 -0
- package/src/shell/marks.mjs +136 -0
- package/src/types/index.ts +6 -0
- package/src/types/types.ts +239 -0
- package/src/types/virtual.d.ts +29 -0
- package/src/ui/components/app.astro +13 -0
- package/src/ui/components/build-path.ts +13 -0
- package/src/ui/components/build-tree.ts +108 -0
- package/src/ui/components/collapse-duration.ts +28 -0
- package/src/ui/components/compress-terms.ts +10 -0
- package/src/ui/components/dashboard-layout.astro +39 -0
- package/src/ui/components/home.astro +65 -0
- package/src/ui/components/layout.astro +110 -0
- package/src/ui/components/preview-layout.astro +109 -0
- package/src/ui/components/sidebar-button-fullscreen.astro +38 -0
- package/src/ui/components/sidebar-button-search.astro +23 -0
- package/src/ui/components/sidebar-button-theme.astro +9 -0
- package/src/ui/components/sidebar-button.astro +24 -0
- package/src/ui/components/sidebar-resize-handle.astro +74 -0
- package/src/ui/components/sidebar-search-panel.astro +41 -0
- package/src/ui/components/sidebar-search-script.ts +103 -0
- package/src/ui/components/sidebar-title.astro +17 -0
- package/src/ui/components/sidebar-tree-node.astro +143 -0
- package/src/ui/components/sidebar-tree.astro +84 -0
- package/src/ui/components/sidebar.astro +29 -0
- package/src/ui/components/theme-message.ts +26 -0
- package/src/ui/components/theme-script.astro +71 -0
- package/src/ui/components/theme-toggle.astro +63 -0
- package/src/ui/components/theme.ts +32 -0
- package/src/ui/index.ts +4 -0
- package/src/ui/lab.css +549 -0
- package/virtual.d.ts +42 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/build-tree.ts) — see
|
|
2
|
+
// ../../core/LICENSE-astrobook.
|
|
3
|
+
// Divergence: relative imports carry the .ts extension, and the StoryModule type comes from the
|
|
4
|
+
// vendored types (../../types/) rather than the `@astrobook/types` package.
|
|
5
|
+
import type { StoryModule } from '../../types/types.ts'
|
|
6
|
+
|
|
7
|
+
import { compressTerms } from './compress-terms.ts'
|
|
8
|
+
|
|
9
|
+
export interface DirectoryNode {
|
|
10
|
+
type: 'directory'
|
|
11
|
+
name: string
|
|
12
|
+
searchText: string
|
|
13
|
+
children: TreeNode[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ModuleNode {
|
|
17
|
+
type: 'module'
|
|
18
|
+
id: string
|
|
19
|
+
name: string
|
|
20
|
+
searchText: string
|
|
21
|
+
children: StoryNode[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface StoryNode {
|
|
25
|
+
type: 'story'
|
|
26
|
+
id: string
|
|
27
|
+
name: string
|
|
28
|
+
searchText: string
|
|
29
|
+
children?: undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type TreeNode = DirectoryNode | ModuleNode | StoryNode
|
|
33
|
+
|
|
34
|
+
export function isSingleStory(module: StoryModule): boolean {
|
|
35
|
+
return module.stories.length === 1 && module.stories[0].name === module.name
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findOrCreateDirectory(
|
|
39
|
+
children: TreeNode[],
|
|
40
|
+
segments: string[],
|
|
41
|
+
): TreeNode[] {
|
|
42
|
+
let current = children
|
|
43
|
+
for (const segment of segments) {
|
|
44
|
+
let dir: DirectoryNode | undefined
|
|
45
|
+
for (const node of current) {
|
|
46
|
+
if (node.type === 'directory' && node.name === segment) {
|
|
47
|
+
dir = node
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!dir) {
|
|
52
|
+
dir = { type: 'directory', name: segment, children: [], searchText: '' }
|
|
53
|
+
current.push(dir)
|
|
54
|
+
}
|
|
55
|
+
current = dir.children
|
|
56
|
+
}
|
|
57
|
+
return current
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildTree(modules: StoryModule[]): TreeNode[] {
|
|
61
|
+
const root: TreeNode[] = []
|
|
62
|
+
|
|
63
|
+
for (const module of modules) {
|
|
64
|
+
const segments = module.directory ? module.directory.split('/') : []
|
|
65
|
+
const children = findOrCreateDirectory(root, segments)
|
|
66
|
+
|
|
67
|
+
const storyNodes: StoryNode[] = module.stories.map((story) => ({
|
|
68
|
+
type: 'story',
|
|
69
|
+
id: story.id,
|
|
70
|
+
name: story.name,
|
|
71
|
+
searchText: '',
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
if (isSingleStory(module)) {
|
|
75
|
+
children.push(storyNodes[0])
|
|
76
|
+
} else {
|
|
77
|
+
const moduleNode: ModuleNode = {
|
|
78
|
+
type: 'module',
|
|
79
|
+
id: module.id,
|
|
80
|
+
name: module.name,
|
|
81
|
+
searchText: '',
|
|
82
|
+
children: storyNodes,
|
|
83
|
+
}
|
|
84
|
+
children.push(moduleNode)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const node of root) {
|
|
89
|
+
assignSearchText(node, [])
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return root
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function assignSearchText(
|
|
96
|
+
node: TreeNode,
|
|
97
|
+
ancestorTerms: readonly string[],
|
|
98
|
+
): readonly string[] {
|
|
99
|
+
const currTerms = [...ancestorTerms, node.name]
|
|
100
|
+
const subTreeTerms: Array<readonly string[]> = Array.from(
|
|
101
|
+
node.children || [],
|
|
102
|
+
(child) => assignSearchText(child, currTerms),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const mergedTerms = compressTerms([...currTerms, ...subTreeTerms.flat()])
|
|
106
|
+
node.searchText = mergedTerms.join(' ')
|
|
107
|
+
return mergedTerms
|
|
108
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/collapse-duration.ts) — see
|
|
2
|
+
// ../../core/LICENSE-astrobook.
|
|
3
|
+
// Divergence: the relative import carries the .ts extension.
|
|
4
|
+
import type { TreeNode } from './build-tree.ts'
|
|
5
|
+
|
|
6
|
+
function countDescendantRows(node: TreeNode): number {
|
|
7
|
+
if (node.type === 'story') return 0
|
|
8
|
+
let rows = node.children.length
|
|
9
|
+
for (const child of node.children) {
|
|
10
|
+
rows += countDescendantRows(child)
|
|
11
|
+
}
|
|
12
|
+
return rows
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getCollapseDuration(node: TreeNode): number {
|
|
16
|
+
if (node.type === 'story') return 0
|
|
17
|
+
return computeCollapseDuration(countDescendantRows(node))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function computeCollapseDuration(rows: number): number {
|
|
21
|
+
const minMs = 100
|
|
22
|
+
const maxMs = 500
|
|
23
|
+
const scale = 35
|
|
24
|
+
return Math.min(
|
|
25
|
+
maxMs,
|
|
26
|
+
Math.round(minMs + scale * Math.log(Math.max(1, rows))),
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/compress-terms.ts) — see
|
|
2
|
+
// ../../core/LICENSE-astrobook. Unchanged.
|
|
3
|
+
export function compressTerms(terms: readonly string[]): readonly string[] {
|
|
4
|
+
const unique = Array.from(new Set(terms.map((k) => k.toLowerCase())))
|
|
5
|
+
return unique
|
|
6
|
+
.filter(
|
|
7
|
+
(term) => !unique.some((other) => other !== term && other.includes(term)),
|
|
8
|
+
)
|
|
9
|
+
.sort()
|
|
10
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The DASHBOARD document: the lab's chrome with the story rendered inside it. New in this
|
|
3
|
+
// package — upstream had one layout for the dashboard, the bare story and the home page alike,
|
|
4
|
+
// and gave all three the consumer's CSS.
|
|
5
|
+
//
|
|
6
|
+
// This is the one document that legitimately carries BOTH stylesheets, because it is both things
|
|
7
|
+
// at once (ruled 2026-09-22):
|
|
8
|
+
//
|
|
9
|
+
// · the chrome — sidebar, search, toolbar — styled by ../lab.css and by nothing else;
|
|
10
|
+
// · the preview — the story in <main> — which must be rendered exactly as the real site
|
|
11
|
+
// renders it, so it gets the consumer's `css` list and the consumer's head component.
|
|
12
|
+
//
|
|
13
|
+
// Nothing crosses. The chrome reads only `--lab-*` tokens and is anchored on chrome classes and
|
|
14
|
+
// ids, so the consumer's `body`/`a`/`*` rules cannot reach it; the chrome sets no font, colour or
|
|
15
|
+
// size on `body`, so nothing of ours inherits down into the story. The full argument, and what it
|
|
16
|
+
// costs, is at the top of ../lab.css.
|
|
17
|
+
//
|
|
18
|
+
// The consumer's head arrives via `virtual:orbytes-lab/user-head.mjs` — the lab shell's own
|
|
19
|
+
// virtual module (../../../index.mjs), which is how it reached this package before too: the
|
|
20
|
+
// package's LabHead used to mount it on EVERY lab page, which is what carried the site's fonts
|
|
21
|
+
// into the chrome.
|
|
22
|
+
import 'virtual:astrobook/user-css.mjs'
|
|
23
|
+
|
|
24
|
+
import UserHead from 'virtual:orbytes-lab/user-head.mjs'
|
|
25
|
+
|
|
26
|
+
import Layout from './layout.astro'
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
story: string | undefined
|
|
30
|
+
hasSidebar: boolean
|
|
31
|
+
}
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<Layout story={Astro.props.story} hasSidebar={Astro.props.hasSidebar}>
|
|
35
|
+
<Fragment slot="head">
|
|
36
|
+
<UserHead />
|
|
37
|
+
</Fragment>
|
|
38
|
+
<slot />
|
|
39
|
+
</Layout>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/home.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS utility strings (including the `@container` queries, which had no
|
|
5
|
+
// container to query once the chrome stopped shipping Uno's preflight) are the `.lab-splash*`
|
|
6
|
+
// classes in ../lab.css, and the star glyph is an inline SVG.
|
|
7
|
+
//
|
|
8
|
+
// This is Astrobook's BUILT-IN splash. The orbytes lab always passes its own `home`
|
|
9
|
+
// (src/Home.astro — the tier cards and live thumbnails), so this renders only for a consumer
|
|
10
|
+
// that leaves the option at its default. Kept working, not designed.
|
|
11
|
+
import config from 'virtual:astrobook/global-config.mjs'
|
|
12
|
+
|
|
13
|
+
const content = config.homeContent
|
|
14
|
+
const showBadges = !!(content.version || content.repo)
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<div class="lab-splash lab-chrome">
|
|
18
|
+
{
|
|
19
|
+
content.title == null ? null : (
|
|
20
|
+
<h1 class="lab-splash__title">{content.title}</h1>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
{
|
|
24
|
+
content.subtitle == null ? null : (
|
|
25
|
+
<p class="lab-splash__subtitle">{content.subtitle}</p>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
showBadges && (
|
|
31
|
+
<div class="lab-splash__badges">
|
|
32
|
+
{content.version && (
|
|
33
|
+
<a
|
|
34
|
+
href={content.version.href}
|
|
35
|
+
target="_blank"
|
|
36
|
+
rel="noopener noreferrer"
|
|
37
|
+
class="lab-splash__badge"
|
|
38
|
+
>
|
|
39
|
+
{content.version.label}
|
|
40
|
+
</a>
|
|
41
|
+
)}
|
|
42
|
+
{content.repo && (
|
|
43
|
+
<a
|
|
44
|
+
href={content.repo.href}
|
|
45
|
+
target="_blank"
|
|
46
|
+
rel="noopener noreferrer"
|
|
47
|
+
class="lab-splash__badge"
|
|
48
|
+
>
|
|
49
|
+
<svg
|
|
50
|
+
viewBox="0 0 16 16"
|
|
51
|
+
fill="none"
|
|
52
|
+
stroke="currentColor"
|
|
53
|
+
stroke-width="1.5"
|
|
54
|
+
stroke-linejoin="round"
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
>
|
|
57
|
+
<path d="M8 1.75 10 6l4.25.5-3.1 2.9.85 4.35L8 11.6l-4 2.15.85-4.35L1.75 6.5 6 6z" />
|
|
58
|
+
</svg>
|
|
59
|
+
<span>{content.repo.label}</span>
|
|
60
|
+
</a>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
</div>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/layout.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
//
|
|
5
|
+
// THE DIVERGENCE THAT MATTERS (ruled 2026-09-22): this layout no longer imports
|
|
6
|
+
// `virtual:astrobook/user-css.mjs`. Upstream imported the consumer's stylesheets here, last, "so
|
|
7
|
+
// it can override built-in styles" — which is exactly how the lab's chrome ended up wearing the
|
|
8
|
+
// website's brand. The consumer's CSS now reaches the PREVIEW only, through
|
|
9
|
+
// ./dashboard-layout.astro (the story rendered beside the chrome) and ./preview-layout.astro (the
|
|
10
|
+
// bare story route). This layout is pure chrome and is what the `/lab` home page renders through.
|
|
11
|
+
// Full reasoning: ../lab.css.
|
|
12
|
+
//
|
|
13
|
+
// Other divergences: the UnoCSS utility strings are the `.lab-shell` / `.lab-main` classes in
|
|
14
|
+
// ../lab.css; `astro-theme-toggle`'s ThemeScript is ./theme-script.astro, vendored; and a
|
|
15
|
+
// `<slot name="head" />` was added so a wrapping layout can put the consumer's head and CSS into
|
|
16
|
+
// this document without this file importing either.
|
|
17
|
+
//
|
|
18
|
+
// DOM contracts LabHead.astro depends on, all unchanged: `<html data-active-story>`, the `.dark`
|
|
19
|
+
// class (set by the theme script), `#astrobook-sidebar`, the resize-handle element, and
|
|
20
|
+
// `astro:after-swap` / `astro:page-load` from <ClientRouter>.
|
|
21
|
+
import '../lab.css'
|
|
22
|
+
|
|
23
|
+
import { ClientRouter } from 'astro:transitions'
|
|
24
|
+
|
|
25
|
+
import CustomHead from 'virtual:astrobook/components/head.mjs'
|
|
26
|
+
import config from 'virtual:astrobook/global-config.mjs'
|
|
27
|
+
|
|
28
|
+
import Sidebar from './sidebar.astro'
|
|
29
|
+
import SidebarResizeHandle from './sidebar-resize-handle.astro'
|
|
30
|
+
|
|
31
|
+
interface Props {
|
|
32
|
+
story: string | undefined
|
|
33
|
+
hasSidebar: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const hasSidebar = Astro.props.hasSidebar ?? true
|
|
37
|
+
const DEFAULT_SIDEBAR_WIDTH = 300
|
|
38
|
+
const DEFAULT_SIDEBAR_RESIZING = false
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
<html
|
|
42
|
+
lang="en"
|
|
43
|
+
class="lab-shell"
|
|
44
|
+
data-astrobook-root
|
|
45
|
+
data-active-story={Astro.props.story ?? ''}
|
|
46
|
+
data-sidebar-width={DEFAULT_SIDEBAR_WIDTH}
|
|
47
|
+
data-sidebar-resizing={DEFAULT_SIDEBAR_RESIZING ? '' : undefined}
|
|
48
|
+
>
|
|
49
|
+
<head>
|
|
50
|
+
<meta charset="utf-8" />
|
|
51
|
+
<meta name="viewport" content="width=device-width" />
|
|
52
|
+
<meta name="generator" content={Astro.generator} />
|
|
53
|
+
<title>{config.title}</title>
|
|
54
|
+
<script>
|
|
55
|
+
import { initThemeMessage } from './theme-message.ts'
|
|
56
|
+
initThemeMessage()
|
|
57
|
+
</script>
|
|
58
|
+
{/* CustomHead is the lab's own head (src/LabHead.astro), and the THEME SCRIPT comes with it.
|
|
59
|
+
Upstream rendered <ThemeScript /> here instead. It moved because the lab's folder pages and
|
|
60
|
+
viewport configurator are their own documents — they mount LabHead and never this layout —
|
|
61
|
+
so a theme script that lived here left `.dark` unset on them. That was invisible while the
|
|
62
|
+
chrome wore the consumer's dark-only tokens; now that the chrome has a light theme and a
|
|
63
|
+
dark one of its own (../lab.css), those pages rendered light while the dashboard rendered
|
|
64
|
+
dark. Measured 2026-09-22 on a real consumer site. One script per document either way:
|
|
65
|
+
LabHead carries it on every chrome page, and the bare story
|
|
66
|
+
page mounts no LabHead and carries its own (./preview-layout.astro). */}
|
|
67
|
+
<CustomHead />
|
|
68
|
+
<slot name="head" />
|
|
69
|
+
<ClientRouter fallback="none" />
|
|
70
|
+
<script is:inline>
|
|
71
|
+
;(() => {
|
|
72
|
+
const STORAGE_KEY = 'astrobook-sidebar-width'
|
|
73
|
+
const restoreWidth = () => {
|
|
74
|
+
const width = parseFloat(localStorage.getItem(STORAGE_KEY) || '')
|
|
75
|
+
if (width) {
|
|
76
|
+
document.documentElement.dataset.sidebarWidth = String(width)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
restoreWidth()
|
|
81
|
+
document.addEventListener('astro:after-swap', restoreWidth)
|
|
82
|
+
})()
|
|
83
|
+
</script>
|
|
84
|
+
</head>
|
|
85
|
+
<body class="lab-shell">
|
|
86
|
+
{
|
|
87
|
+
hasSidebar && (
|
|
88
|
+
<>
|
|
89
|
+
<div id="astrobook-sidebar" class="lab-chrome">
|
|
90
|
+
<Sidebar story={Astro.props.story} />
|
|
91
|
+
</div>
|
|
92
|
+
<SidebarResizeHandle />
|
|
93
|
+
</>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
{
|
|
97
|
+
/*
|
|
98
|
+
Use `transition:animate="none"` here because we don't want the fade animation
|
|
99
|
+
when switching between stories. Sadly this also removes the animation when toggling the light/dark theme.
|
|
100
|
+
*/
|
|
101
|
+
}
|
|
102
|
+
<main
|
|
103
|
+
class="lab-main"
|
|
104
|
+
transition:name="astrobook-main"
|
|
105
|
+
transition:animate="none"
|
|
106
|
+
>
|
|
107
|
+
<slot />
|
|
108
|
+
</main>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The PREVIEW document: the bare story at <subpath>/stories/<id>, with no chrome at all. New in
|
|
3
|
+
// this package — upstream rendered the bare story through the same layout as the dashboard, so
|
|
4
|
+
// every story page also carried the sidebar's stylesheet, its scripts and the lab's head.
|
|
5
|
+
//
|
|
6
|
+
// THE RULING (2026-09-22) runs both ways, and this file is the second half of it: the
|
|
7
|
+
// story keeps the consumer's `css` list and `head` component, because seeing a component as the
|
|
8
|
+
// real site renders it is the point of the lab — and it gets NOTHING of the lab's own styling.
|
|
9
|
+
// ../lab.css is not imported here, LabHead.astro is not mounted here, and a component styled by
|
|
10
|
+
// lab CSS the real site has never heard of is a lab that lies.
|
|
11
|
+
//
|
|
12
|
+
// What IS here, and why:
|
|
13
|
+
// · the document frame — the same `html { overflow: hidden }` / flex `body` / scrollable
|
|
14
|
+
// `<main>` the story has always rendered inside. It is structure, not styling: the thumbnail
|
|
15
|
+
// cards and the viewport configurator measure stories in fixed-size iframes, and changing
|
|
16
|
+
// which box scrolls would change what they show. The three rules live inline, on
|
|
17
|
+
// `.lab-preview-*` classes a story cannot collide with, rather than in the chrome stylesheet.
|
|
18
|
+
// · the theme script — it sets `.dark` on <html> and answers the `astrobook:set-theme`
|
|
19
|
+
// postMessage the embedding pages send. Behaviour, not chrome styling; a site with no `.dark`
|
|
20
|
+
// rules sees nothing.
|
|
21
|
+
// · the UNCONDITIONAL noindex. The lab is staging-only and never indexable (ruled 2026-09-02)
|
|
22
|
+
// and this is one of the two mechanisms that enforce it — the other is the consumer's build
|
|
23
|
+
// gate. `npm run check:lab-gate` walks every built lab page and fails on any without it, so
|
|
24
|
+
// this meta has to be here as well as in LabHead.astro. Do not make it conditional.
|
|
25
|
+
// · Feedbucket, when the consumer configured a key — a bare story page is a lab page you can
|
|
26
|
+
// open full screen, and it carried the widget before this split.
|
|
27
|
+
import 'virtual:astrobook/user-css.mjs'
|
|
28
|
+
|
|
29
|
+
import { ClientRouter } from 'astro:transitions'
|
|
30
|
+
|
|
31
|
+
import config from 'virtual:astrobook/global-config.mjs'
|
|
32
|
+
import labConfig from 'virtual:orbytes-lab/config.mjs'
|
|
33
|
+
import UserHead from 'virtual:orbytes-lab/user-head.mjs'
|
|
34
|
+
|
|
35
|
+
import ThemeScript from './theme-script.astro'
|
|
36
|
+
|
|
37
|
+
interface Props {
|
|
38
|
+
story: string | undefined
|
|
39
|
+
hasSidebar: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const feedbucketKey = labConfig.feedbucketKey
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
<html
|
|
46
|
+
lang="en"
|
|
47
|
+
class="lab-preview-shell"
|
|
48
|
+
data-astrobook-root
|
|
49
|
+
data-astrobook-preview
|
|
50
|
+
data-active-story={Astro.props.story ?? ''}
|
|
51
|
+
>
|
|
52
|
+
<head>
|
|
53
|
+
<meta charset="utf-8" />
|
|
54
|
+
<meta name="viewport" content="width=device-width" />
|
|
55
|
+
<meta name="generator" content={Astro.generator} />
|
|
56
|
+
<title>{config.title}</title>
|
|
57
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
58
|
+
<ThemeScript />
|
|
59
|
+
<script>
|
|
60
|
+
import { initThemeMessage } from './theme-message.ts'
|
|
61
|
+
initThemeMessage()
|
|
62
|
+
</script>
|
|
63
|
+
<UserHead />
|
|
64
|
+
{
|
|
65
|
+
feedbucketKey && (
|
|
66
|
+
<script
|
|
67
|
+
is:inline
|
|
68
|
+
defer
|
|
69
|
+
src="https://cdn.feedbucket.app/assets/feedbucket.js"
|
|
70
|
+
data-feedbucket={feedbucketKey}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
<ClientRouter fallback="none" />
|
|
75
|
+
<style is:global>
|
|
76
|
+
html.lab-preview-shell {
|
|
77
|
+
height: 100%;
|
|
78
|
+
max-height: 100vh;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
body.lab-preview-shell {
|
|
83
|
+
margin: 0;
|
|
84
|
+
padding: 0;
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: row;
|
|
87
|
+
align-items: stretch;
|
|
88
|
+
justify-content: flex-start;
|
|
89
|
+
min-height: 100%;
|
|
90
|
+
max-height: 100%;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.lab-preview-main {
|
|
94
|
+
flex: 1;
|
|
95
|
+
min-width: 0;
|
|
96
|
+
overflow: auto;
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body class="lab-preview-shell">
|
|
101
|
+
<main
|
|
102
|
+
class="lab-preview-main"
|
|
103
|
+
transition:name="astrobook-main"
|
|
104
|
+
transition:animate="none"
|
|
105
|
+
>
|
|
106
|
+
<slot />
|
|
107
|
+
</main>
|
|
108
|
+
</body>
|
|
109
|
+
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-button-fullscreen.astro) —
|
|
3
|
+
// see ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS icon class is an inline SVG (no build step here), and the
|
|
5
|
+
// `import '@astrobook/types'` side-effect line is gone — the ambient declarations it pulled in
|
|
6
|
+
// live in ../../types/virtual.d.ts now.
|
|
7
|
+
import config from 'virtual:astrobook/global-config.mjs'
|
|
8
|
+
|
|
9
|
+
import SidebarButton from './sidebar-button.astro'
|
|
10
|
+
import { buildPath } from './build-path.ts'
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
story?: string
|
|
14
|
+
}
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
Astro.props.story ? (
|
|
19
|
+
<SidebarButton
|
|
20
|
+
href={buildPath(config.storyBase, Astro.props.story)}
|
|
21
|
+
title="Go full screen"
|
|
22
|
+
>
|
|
23
|
+
<svg
|
|
24
|
+
viewBox="0 0 16 16"
|
|
25
|
+
fill="none"
|
|
26
|
+
stroke="currentColor"
|
|
27
|
+
stroke-width="1.5"
|
|
28
|
+
stroke-linecap="round"
|
|
29
|
+
stroke-linejoin="round"
|
|
30
|
+
aria-hidden="true"
|
|
31
|
+
>
|
|
32
|
+
<path d="M9.5 2.5H13.5V6.5" />
|
|
33
|
+
<path d="M13.5 2.5 8 8" />
|
|
34
|
+
<path d="M12 9.5v3a1 1 0 0 1-1 1H3.5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3" />
|
|
35
|
+
</svg>
|
|
36
|
+
</SidebarButton>
|
|
37
|
+
) : null
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-button-search.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS icon class (`i-fluent-search-16-regular`, which presetIcons turned into
|
|
5
|
+
// a background-image at build time) is an inline SVG — this package has no build step.
|
|
6
|
+
// `id="astrobook-search-toggle"` is a DOM contract: sidebar-search-script.ts binds the panel to
|
|
7
|
+
// it, and LabHead.astro clicks it to open the panel on ⌘K.
|
|
8
|
+
import SidebarButton from './sidebar-button.astro'
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<SidebarButton id="astrobook-search-toggle" title="Search">
|
|
12
|
+
<svg
|
|
13
|
+
viewBox="0 0 16 16"
|
|
14
|
+
fill="none"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
stroke-width="1.5"
|
|
17
|
+
stroke-linecap="round"
|
|
18
|
+
aria-hidden="true"
|
|
19
|
+
>
|
|
20
|
+
<circle cx="7" cy="7" r="4.5"></circle>
|
|
21
|
+
<path d="m10.5 10.5 3 3"></path>
|
|
22
|
+
</svg>
|
|
23
|
+
</SidebarButton>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-button-theme.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: `astro-theme-toggle`'s Toggle is ./theme-toggle.astro, vendored into this package
|
|
5
|
+
// (why: ./theme-script.astro).
|
|
6
|
+
import Toggle from './theme-toggle.astro'
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<Toggle class="astrobook-sidebar-button lab-focus-ring" />
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-button.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS `astrobook-sidebar-button` shortcut is now a real class in
|
|
5
|
+
// ../lab.css. The CLASS NAME is unchanged on purpose — LabHead.astro creates the collapse, cull,
|
|
6
|
+
// viewport, staging and code buttons in JavaScript and sets exactly this className on each.
|
|
7
|
+
interface Props {
|
|
8
|
+
id?: string
|
|
9
|
+
href?: string
|
|
10
|
+
title: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Element = Astro.props.href ? 'a' : 'button'
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<Element
|
|
17
|
+
id={Astro.props.id}
|
|
18
|
+
href={Astro.props.href}
|
|
19
|
+
class:list={['astrobook-sidebar-button', 'lab-focus-ring']}
|
|
20
|
+
title={Astro.props.title}
|
|
21
|
+
>
|
|
22
|
+
<span class="lab-sr-only">{Astro.props.title}</span>
|
|
23
|
+
<slot />
|
|
24
|
+
</Element>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-resize-handle.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS utility string is a real rule on the `astrobook-sidebar-resize-handle`
|
|
5
|
+
// element selector in ../lab.css. The ELEMENT NAME is unchanged — LabHead.astro's collapse styles
|
|
6
|
+
// hide it by that name.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<astrobook-sidebar-resize-handle role="separator"></astrobook-sidebar-resize-handle>
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
const STORAGE_KEY = 'astrobook-sidebar-width'
|
|
13
|
+
const DEFAULT_WIDTH = 300
|
|
14
|
+
let previousWidth = ''
|
|
15
|
+
|
|
16
|
+
function setSidebarWidth(width: number): void {
|
|
17
|
+
const widthString = width.toFixed(0)
|
|
18
|
+
if (widthString === previousWidth) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
previousWidth = widthString
|
|
22
|
+
document.documentElement.dataset.sidebarWidth = widthString
|
|
23
|
+
localStorage.setItem(STORAGE_KEY, widthString)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class AstrobookSidebarResizeHandle extends HTMLElement {
|
|
27
|
+
#handleCapture = (event: PointerEvent) => {
|
|
28
|
+
event.preventDefault()
|
|
29
|
+
this.setPointerCapture(event.pointerId)
|
|
30
|
+
document.documentElement.dataset.sidebarResizing = ''
|
|
31
|
+
|
|
32
|
+
document.body.addEventListener('pointermove', this.#handleMove)
|
|
33
|
+
document.body.addEventListener('pointerup', this.#handleRelease, {
|
|
34
|
+
once: true,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#handleRelease = (event?: PointerEvent) => {
|
|
39
|
+
if (event) {
|
|
40
|
+
event.preventDefault()
|
|
41
|
+
this.releasePointerCapture(event.pointerId)
|
|
42
|
+
}
|
|
43
|
+
delete document.documentElement.dataset.sidebarResizing
|
|
44
|
+
document.body.removeEventListener('pointermove', this.#handleMove)
|
|
45
|
+
document.body.removeEventListener('pointerup', this.#handleRelease)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#handleMove = (event: PointerEvent) => {
|
|
49
|
+
setSidebarWidth(event.clientX)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#handleReset = () => {
|
|
53
|
+
setSidebarWidth(DEFAULT_WIDTH)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
connectedCallback() {
|
|
57
|
+
this.addEventListener('pointerdown', this.#handleCapture)
|
|
58
|
+
this.addEventListener('dblclick', this.#handleReset)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
disconnectedCallback() {
|
|
62
|
+
this.removeEventListener('pointerdown', this.#handleCapture)
|
|
63
|
+
this.removeEventListener('dblclick', this.#handleReset)
|
|
64
|
+
this.#handleRelease()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!customElements.get('astrobook-sidebar-resize-handle')) {
|
|
69
|
+
customElements.define(
|
|
70
|
+
'astrobook-sidebar-resize-handle',
|
|
71
|
+
AstrobookSidebarResizeHandle,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
</script>
|