create-obsidian-arrow 0.2.2 → 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/README.md +5 -4
- package/package.json +1 -1
- package/template/AGENTS.md +27 -3
- package/template/README.md +40 -1
- package/template/_gitignore +3 -0
- package/template/docs/prompts/agent-setup.md +22 -11
- package/template/docs/prompts/update-existing.md +1 -1
- package/template/docs/workflow.md +18 -6
- package/template/package.json +1 -1
- package/template/src/components/SettingsPanel.stories.ts +11 -0
- package/template/src/components/SettingsPanel.ts +1 -1
- package/template/src/components/Toggle.stories.ts +28 -0
- package/template/src/main.ts +1 -0
- package/template/src/router/client.ts +15 -2
- package/template/src/router/routeToPage.ts +74 -27
- package/template/src/sandbox/home.ts +55 -26
- package/template/src/sandbox/sandbox.css +302 -0
- package/template/src/utilities.css +205 -0
- package/template/src/viewer/ClassesPage.ts +37 -0
- package/template/src/viewer/ComponentsIndex.ts +56 -0
- package/template/src/viewer/StoryPage.ts +73 -0
- package/template/src/viewer/TokensPage.ts +82 -0
- package/template/src/viewer/derive.ts +81 -0
- package/template/src/viewer/discovery.ts +63 -0
- package/template/src/viewer/obsidian-classes.ts +269 -0
- package/template/src/viewer/sidebar.ts +55 -0
- package/template/src/viewer/stories.ts +83 -0
- package/template/src/viewer/token-utils.ts +84 -0
- package/template/src/viewer/tokens.ts +30 -0
- package/template/test/token-utils.test.mjs +65 -0
- package/template/test/viewer-derive.test.mjs +65 -0
- package/template/test/viewer-stories.test.mjs +44 -0
- package/template/src/examples/ExamplesIndex.ts +0 -36
- package/template/src/examples/registry.ts +0 -26
package/README.md
CHANGED
|
@@ -68,10 +68,11 @@ node create-obsidian-arrow/index.mjs update ../my-app # update
|
|
|
68
68
|
## What you get
|
|
69
69
|
|
|
70
70
|
A full sandbox: client-only Vite + TS, `@arrow-js/core` + `@arrow-js/framework`
|
|
71
|
-
(no SSR), `routeToPage` + Navigation-API router
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
(no SSR), `routeToPage` + Navigation-API router, a Storybook-style component
|
|
72
|
+
viewer at `/components` (co-locate `*.stories.ts` to add stories), a live token
|
|
73
|
+
and class reference at `/reference`, Biome + husky pre-commit + `node:test` +
|
|
74
|
+
GitHub Actions CI, a `skills:install` that pulls the agent skills from the
|
|
75
|
+
published repo, and the `pull-css` script that extracts Obsidian's `app.css`.
|
|
75
76
|
|
|
76
77
|
## Maintaining the template
|
|
77
78
|
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -10,9 +10,13 @@ This file is the hub — everything else is linked from here:
|
|
|
10
10
|
|
|
11
11
|
- [`docs/workflow.md`](docs/workflow.md) — fresh-machine → running workflow.
|
|
12
12
|
- [`skills/`](skills/) — installable domain skills (`pnpm skills:install`):
|
|
13
|
-
obsidian-arrow-sandbox,
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
- `obsidian-arrow-sandbox` — running the sandbox, CSS scoping, porting basics.
|
|
14
|
+
- `obsidian-arrow-stories` — **component + story authoring workflow**: `defineStories` API, variants, children, status flag, DRY patterns, utilities.
|
|
15
|
+
- `obsidian-arrow-css` — **CSS decision hierarchy**: Obsidian classes → oas-* utilities → custom CSS; token reference; specificity scoping; overrides via variables; auditing for excess CSS.
|
|
16
|
+
- `arrow-js-obsidian-templates` — Arrow v1.0.6 template syntax + footguns.
|
|
17
|
+
- `arrow-js-obsidian-patterns` — icons, CSS scoping, lifecycle, reactive state.
|
|
18
|
+
- `arrow-js-obsidian-porting` — sandbox→plugin parity check (`component-hash`).
|
|
19
|
+
- `obsidian-arrow-maintenance` — updating an existing project.
|
|
16
20
|
- [`docs/prompts/`](docs/prompts/) — copy-paste agent prompts: `agent-setup.md`
|
|
17
21
|
(scaffold + orient) and `update-existing.md` (update tooling + skills, keep src).
|
|
18
22
|
|
|
@@ -78,6 +82,20 @@ events via `@event` (`@click="${fn}"`), keyed lists via
|
|
|
78
82
|
`html\`…\`.key(id)`, async sections via `component(asyncFn, { fallback })` wrapped
|
|
79
83
|
in `boundary()`.
|
|
80
84
|
|
|
85
|
+
## Conventions
|
|
86
|
+
|
|
87
|
+
- **Check `/reference/classes` first** — Obsidian has semantic classes for most
|
|
88
|
+
common patterns. Use them before writing any custom CSS.
|
|
89
|
+
- **Use `oas-*` utilities second** (`src/utilities.css`) — flex, gap, padding,
|
|
90
|
+
margin, typography, border, overflow helpers built on Obsidian's token scale.
|
|
91
|
+
Prefer `class="oas-flex oas-gap-2"` over `style="display:flex;gap:8px"`.
|
|
92
|
+
- **Custom CSS last** — only when Obsidian has no class and utilities don't cover
|
|
93
|
+
it. Scope under container + element type, use `var(--…)` tokens for values.
|
|
94
|
+
- Add a story by creating a co-located `*.stories.ts` next to the component —
|
|
95
|
+
it appears at `/components/<slug>` automatically. See the `obsidian-arrow-stories`
|
|
96
|
+
skill for the full `defineStories` API (variants, children, status, notes).
|
|
97
|
+
Browse live tokens at `/reference`, curated classes at `/reference/classes`.
|
|
98
|
+
|
|
81
99
|
## CSS scoping
|
|
82
100
|
|
|
83
101
|
- Use Obsidian's own classes (`.setting-item`, `.clickable-icon`,
|
|
@@ -109,3 +127,9 @@ mount it from `ItemView.onOpen()` via `template(this.contentEl)`. If it uses
|
|
|
109
127
|
`boundary()`/async components, add `@arrow-js/framework` to the plugin and the
|
|
110
128
|
`import '@arrow-js/framework'` side-effect import. Strip any sandbox chrome
|
|
111
129
|
(`src/sandbox/*`) — that stays here.
|
|
130
|
+
|
|
131
|
+
**Bring `src/utilities.css` along.** Components may use `oas-`-prefixed utility
|
|
132
|
+
classes (flex, gap, padding, typography, border — all built on Obsidian's token
|
|
133
|
+
scale). Copy `src/utilities.css` once into the plugin's styles directory and
|
|
134
|
+
import it there. The `oas-` prefix guarantees no conflict with Obsidian selectors.
|
|
135
|
+
Re-copy when the sandbox file changes (the porting-parity skill covers this).
|
package/template/README.md
CHANGED
|
@@ -91,7 +91,9 @@ skills under [`skills/`](skills/) — it's a skill marketplace. Scaffolds **don'
|
|
|
91
91
|
vendor copies**; they pull from this published repo, so installs are always
|
|
92
92
|
current.
|
|
93
93
|
|
|
94
|
-
- `obsidian-arrow-sandbox` — running and using this sandbox,
|
|
94
|
+
- `obsidian-arrow-sandbox` — running and using this sandbox, CSS scoping, porting basics.
|
|
95
|
+
- `obsidian-arrow-stories` — **component + story authoring workflow**: the complete `defineStories` API (variants, children, status flag, notes), DRY patterns, utility classes, story viewing, and how to structure sub-components.
|
|
96
|
+
- `obsidian-arrow-css` — **CSS decision hierarchy**: Obsidian classes first, `oas-*` utilities second, custom CSS last; the live token reference (`/reference`), class catalog (`/reference/classes`), specificity scoping, overrides via CSS variables, and auditing components to minimize hand-written CSS.
|
|
95
97
|
- `arrow-js-obsidian-templates` — Arrow v1.0.6 template rules + footguns.
|
|
96
98
|
- `arrow-js-obsidian-patterns` — integration patterns: icons (Lucide / data-icon
|
|
97
99
|
sweep), CSS scoping vs Obsidian globals, mount/unmount lifecycle, reactive state.
|
|
@@ -125,6 +127,38 @@ different project root, e.g. an outer repo a scaffold is nested in), `--global`
|
|
|
125
127
|
forms `SKILLS_AGENT` / `SKILLS_PROJECT_DIR` / `SKILLS_GLOBAL` (and
|
|
126
128
|
`SKIP_SKILLS_INSTALL=1`) influence *that* path.
|
|
127
129
|
|
|
130
|
+
## Component viewer & reference
|
|
131
|
+
|
|
132
|
+
**Component viewer (`/components`):** a Storybook-style browser for sandbox
|
|
133
|
+
components. Co-locate a `*.stories.ts` file next to any component and it appears
|
|
134
|
+
in the sidebar and on Home automatically. Stories support named variants,
|
|
135
|
+
drill-in via `children` slugs, and a derived src path shown in the viewer — all
|
|
136
|
+
discovered at build time via `import.meta.glob`.
|
|
137
|
+
|
|
138
|
+
**Reference index (`/reference`):** all `var(--)` tokens parsed live from
|
|
139
|
+
`app.css`, grouped by category (Size & spacing, Radius, Colors, …) with color
|
|
140
|
+
swatches, size bars, a filter input, theme-aware resolved values, and a copy
|
|
141
|
+
button. `/reference/classes` is a curated catalog of Obsidian pattern classes
|
|
142
|
+
with live previews.
|
|
143
|
+
|
|
144
|
+
### Add a story
|
|
145
|
+
|
|
146
|
+
Create `src/components/MyThing.stories.ts` next to the component:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { defineStories } from "../viewer/stories";
|
|
150
|
+
import { MyThing } from "./MyThing";
|
|
151
|
+
|
|
152
|
+
export default defineStories({
|
|
153
|
+
description: "What it demonstrates.",
|
|
154
|
+
variants: { default: () => MyThing() },
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
It appears in the sidebar and on Home automatically; the src path shown in the
|
|
159
|
+
viewer is derived from the file location. Stories are sandbox-only — they never
|
|
160
|
+
port to the plugin.
|
|
161
|
+
|
|
128
162
|
## Porting a component into the plugin
|
|
129
163
|
|
|
130
164
|
Components use only Obsidian classes + `var(--…)` tokens and mount via
|
|
@@ -133,6 +167,11 @@ copy the component file into the plugin's view directory and mount it from
|
|
|
133
167
|
`onOpen()`. If it uses `boundary()`/async components, add `@arrow-js/framework`
|
|
134
168
|
to the plugin and the `import '@arrow-js/framework'` side-effect import.
|
|
135
169
|
|
|
170
|
+
Also copy `src/utilities.css` into the plugin once — components may use `oas-`
|
|
171
|
+
utility classes (flex, gap, padding, typography, border helpers built on
|
|
172
|
+
Obsidian's token scale). The `oas-` prefix means no conflicts with Obsidian's
|
|
173
|
+
own selectors. All ported components in a plugin share one copy of this file.
|
|
174
|
+
|
|
136
175
|
## Arrow v1.0.6 footguns (learned the hard way)
|
|
137
176
|
|
|
138
177
|
These are enforced/encoded so they don't regress:
|
package/template/_gitignore
CHANGED
|
@@ -30,7 +30,8 @@ Then:
|
|
|
30
30
|
# committed; it's Obsidian's proprietary CSS). Auto-detect is
|
|
31
31
|
# macOS-only; on Windows/WSL pass --path <obsidian.asar|app.css>
|
|
32
32
|
# or set OBSIDIAN_ASAR=<path>.
|
|
33
|
-
pnpm dev # open the printed URL: / is
|
|
33
|
+
pnpm dev # open the printed URL: / is home, /components the story viewer,
|
|
34
|
+
# /reference the Obsidian token/class index.
|
|
34
35
|
# The toolbar slider/presets + edge drag handle resize the panel.
|
|
35
36
|
pnpm skills:install --yes # install all agent skills non-interactively, pulled
|
|
36
37
|
# from the published repo (not vendored) — this loads
|
|
@@ -42,11 +43,17 @@ Then:
|
|
|
42
43
|
READ FIRST
|
|
43
44
|
- AGENTS.md (root) — operating guide + docs map (links everything below).
|
|
44
45
|
- docs/workflow.md — fresh-machine → running workflow.
|
|
45
|
-
- skills/*/SKILL.md —
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
- skills/*/SKILL.md — seven installable skills:
|
|
47
|
+
obsidian-arrow-sandbox running the sandbox, CSS scoping, porting basics
|
|
48
|
+
obsidian-arrow-stories component + story authoring: defineStories API,
|
|
49
|
+
variants, children, status, DRY patterns, utilities
|
|
50
|
+
obsidian-arrow-css CSS decision hierarchy: Obsidian classes → oas-*
|
|
51
|
+
utilities → custom CSS; token/class reference;
|
|
52
|
+
scoping rules; overrides via variables; CSS audit
|
|
53
|
+
arrow-js-obsidian-templates template syntax + hard footguns
|
|
54
|
+
arrow-js-obsidian-patterns icons, CSS scoping, lifecycle, reactive state
|
|
55
|
+
arrow-js-obsidian-porting sandbox→plugin parity check
|
|
56
|
+
obsidian-arrow-maintenance updating an existing project
|
|
50
57
|
|
|
51
58
|
ARROW v1.0.6 FOOTGUNS — do not relearn these the hard way:
|
|
52
59
|
1. NO literal HTML comments inside html`` templates — Arrow treats HTML comments
|
|
@@ -68,8 +75,9 @@ CONVENTIONS
|
|
|
68
75
|
only when Obsidian has no class, scoped under a container class + element type
|
|
69
76
|
(e.g. `.my-panel button.my-action`) so it beats Obsidian's global button rule.
|
|
70
77
|
- Sandbox-only chrome lives in src/sandbox/* — it does NOT port to a plugin.
|
|
71
|
-
- Add a demo by
|
|
72
|
-
|
|
78
|
+
- Add a demo by creating a co-located `*.stories.ts` next to the component (see
|
|
79
|
+
README "Add a story"); it appears at `/components/<slug>` automatically.
|
|
80
|
+
Browse Obsidian tokens/classes at `/reference`.
|
|
73
81
|
|
|
74
82
|
VERIFY BEFORE CLAIMING DONE
|
|
75
83
|
- `pnpm typecheck && pnpm test && pnpm lint` (or `pnpm run ci` for the full chain).
|
|
@@ -81,15 +89,18 @@ PORTING TO A PLUGIN
|
|
|
81
89
|
Copy the component file into the plugin's view dir and mount from
|
|
82
90
|
ItemView.onOpen() via `template(this.contentEl)`. If it uses boundary()/async
|
|
83
91
|
components, add @arrow-js/framework to the plugin and the side-effect
|
|
84
|
-
`import '@arrow-js/framework'`.
|
|
85
|
-
|
|
92
|
+
`import '@arrow-js/framework'`. Also copy src/utilities.css into the plugin once
|
|
93
|
+
(all ported components share it) — components may use oas-* utility classes
|
|
94
|
+
(flex, gap, padding, text, border helpers built on Obsidian's token scale).
|
|
95
|
+
Leave src/sandbox/* behind. Guard against drift with the porting-parity check
|
|
96
|
+
(see the arrow-js-obsidian-porting skill).
|
|
86
97
|
|
|
87
98
|
MAINTENANCE (existing project)
|
|
88
99
|
Refresh tooling later with `npx create-obsidian-arrow update` (preserves src/),
|
|
89
100
|
update skills with `pnpm skills:update`. See the obsidian-arrow-maintenance skill.
|
|
90
101
|
|
|
91
102
|
Start by scaffolding, running setup steps, then read AGENTS.md and confirm
|
|
92
|
-
`pnpm dev` renders /
|
|
103
|
+
`pnpm dev` renders correctly at /components and /reference. Report what you see.
|
|
93
104
|
```
|
|
94
105
|
|
|
95
106
|
---
|
|
@@ -60,7 +60,7 @@ STEPS (in order)
|
|
|
60
60
|
|
|
61
61
|
7. Verify
|
|
62
62
|
- pnpm run ci (biome + typecheck + tests + build)
|
|
63
|
-
- pnpm dev and confirm /
|
|
63
|
+
- pnpm dev and confirm /components and /reference render with a clean console
|
|
64
64
|
|
|
65
65
|
REPORT
|
|
66
66
|
- What `update` changed, which skills are now installed and where, the pull-css
|
|
@@ -25,7 +25,8 @@ pnpm pull-css # macOS: auto-detects /Applications/Obs
|
|
|
25
25
|
# or OBSIDIAN_ASAR=<path> pnpm pull-css
|
|
26
26
|
|
|
27
27
|
# 4. Run it
|
|
28
|
-
pnpm dev # open the printed URL — / is
|
|
28
|
+
pnpm dev # open the printed URL — / is home, /components the story
|
|
29
|
+
# viewer, /reference the token/class index
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
`public/app.css` is **git-ignored and never shipped** (it's Obsidian's proprietary
|
|
@@ -54,8 +55,15 @@ Then point the agent at [`AGENTS.md`](../AGENTS.md), or brief a fresh agent with
|
|
|
54
55
|
## Build → verify → port loop
|
|
55
56
|
|
|
56
57
|
```sh
|
|
57
|
-
#
|
|
58
|
+
# 1. Check /reference/classes in the running sandbox — does Obsidian have a class
|
|
59
|
+
# for your pattern? Use it before writing custom CSS.
|
|
60
|
+
# 2. Add src/components/MyThing.ts (Arrow component)
|
|
61
|
+
# 3. Add src/components/MyThing.stories.ts (co-located story file)
|
|
58
62
|
pnpm dev # iterate with HMR
|
|
63
|
+
# /components → index of all discovered stories
|
|
64
|
+
# /components/my-thing → story for MyThing
|
|
65
|
+
# /reference → live Obsidian token reference
|
|
66
|
+
# /reference/classes → curated class catalog with live previews
|
|
59
67
|
pnpm run ci # biome + typecheck + tests + build before trusting it
|
|
60
68
|
```
|
|
61
69
|
|
|
@@ -63,10 +71,14 @@ Always confirm the actual render in the browser — Arrow's footguns surface onl
|
|
|
63
71
|
at render, so a passing `tsc` is not proof a component works. See the footguns in
|
|
64
72
|
[`AGENTS.md`](../AGENTS.md) and the `arrow-js-obsidian-templates` skill.
|
|
65
73
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
For the complete story authoring workflow (`defineStories` API, variants, children,
|
|
75
|
+
status flag, DRY patterns) see the `obsidian-arrow-stories` skill.
|
|
76
|
+
|
|
77
|
+
**Port a component into a plugin:** copy the component file and `src/utilities.css`
|
|
78
|
+
(once, shared by all ports) into the plugin. Mount from `ItemView.onOpen()` via
|
|
79
|
+
`template(this.contentEl)`. If it uses `boundary()`/async, add
|
|
80
|
+
`@arrow-js/framework` and `import '@arrow-js/framework'`. Leave `src/sandbox/*`
|
|
81
|
+
behind.
|
|
70
82
|
|
|
71
83
|
## Scaffold vs. clone
|
|
72
84
|
|
package/template/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"create:sync": "node create-obsidian-arrow/scripts/sync-template.mjs",
|
|
15
15
|
"lint": "biome check .",
|
|
16
16
|
"format": "biome check --write .",
|
|
17
|
-
"test": "node --test test/*.test.mjs",
|
|
17
|
+
"test": "node --experimental-strip-types --test test/*.test.mjs",
|
|
18
18
|
"check": "biome check --write . && pnpm typecheck && pnpm test",
|
|
19
19
|
"ci": "biome ci . && pnpm typecheck && pnpm test && pnpm build",
|
|
20
20
|
"skills:install": "node scripts/install-skills.mjs --force",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineStories } from "../viewer/stories";
|
|
2
|
+
import { SettingsPanel } from "./SettingsPanel";
|
|
3
|
+
|
|
4
|
+
export default defineStories({
|
|
5
|
+
description: "Vertical tabs, toggles, a keyed list, and an async boundary() section.",
|
|
6
|
+
variants: {
|
|
7
|
+
default: () => SettingsPanel(),
|
|
8
|
+
},
|
|
9
|
+
children: ["toggle"],
|
|
10
|
+
status: "live",
|
|
11
|
+
});
|
|
@@ -70,7 +70,7 @@ function rebuildIndex(): void {
|
|
|
70
70
|
* live state; clicking flips it in place (deep reactivity re-runs only the
|
|
71
71
|
* tracked expressions below — no list re-render).
|
|
72
72
|
*/
|
|
73
|
-
const Toggle = (enabled: () => boolean, onToggle: () => void): ArrowTemplate => html`<div
|
|
73
|
+
export const Toggle = (enabled: () => boolean, onToggle: () => void): ArrowTemplate => html`<div
|
|
74
74
|
class="${() => `checkbox-container${enabled() ? " is-enabled" : ""}`}"
|
|
75
75
|
@click="${onToggle}"
|
|
76
76
|
>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { reactive } from "@arrow-js/core";
|
|
2
|
+
import { defineStories } from "../viewer/stories";
|
|
3
|
+
import { Toggle } from "./SettingsPanel";
|
|
4
|
+
|
|
5
|
+
export default defineStories({
|
|
6
|
+
description: "Obsidian checkbox-container toggle used by SettingsPanel.",
|
|
7
|
+
componentPath: "src/components/SettingsPanel.ts",
|
|
8
|
+
variants: {
|
|
9
|
+
interactive: () => {
|
|
10
|
+
const state = reactive({ on: true });
|
|
11
|
+
return Toggle(
|
|
12
|
+
() => state.on,
|
|
13
|
+
() => {
|
|
14
|
+
state.on = !state.on;
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
off: {
|
|
19
|
+
render: () =>
|
|
20
|
+
Toggle(
|
|
21
|
+
() => false,
|
|
22
|
+
() => {}
|
|
23
|
+
),
|
|
24
|
+
notes: "Static off state (click does nothing).",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
status: "live",
|
|
28
|
+
});
|
package/template/src/main.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { html } from "@arrow-js/core";
|
|
1
2
|
import { Frame } from "../sandbox/frame";
|
|
2
3
|
import { Shell } from "../sandbox/shell";
|
|
4
|
+
import type { Page } from "./routeToPage";
|
|
3
5
|
import { routeToPage } from "./routeToPage";
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -29,10 +31,21 @@ function getNavigation(): NavigationLike | undefined {
|
|
|
29
31
|
|
|
30
32
|
export function startRouter(root: HTMLElement): void {
|
|
31
33
|
const render = (url: string): void => {
|
|
32
|
-
|
|
34
|
+
let resolved = routeToPage(url);
|
|
35
|
+
for (let hops = 0; "redirect" in resolved && hops < 3; hops++) {
|
|
36
|
+
window.history.replaceState({}, "", resolved.redirect);
|
|
37
|
+
resolved = routeToPage(resolved.redirect);
|
|
38
|
+
}
|
|
39
|
+
if ("redirect" in resolved) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const page: Page = resolved;
|
|
33
43
|
document.title = page.title;
|
|
34
44
|
root.replaceChildren();
|
|
35
|
-
|
|
45
|
+
const content = page.sidebar
|
|
46
|
+
? html`${page.sidebar}${Frame(page.title, page.view)}`
|
|
47
|
+
: Frame(page.title, page.view);
|
|
48
|
+
Shell(content)(root);
|
|
36
49
|
};
|
|
37
50
|
|
|
38
51
|
render(window.location.href);
|
|
@@ -1,43 +1,33 @@
|
|
|
1
1
|
import { html } from "@arrow-js/core";
|
|
2
2
|
import type { ArrowExpression } from "@arrow-js/core";
|
|
3
|
-
import { findExample } from "../examples/registry";
|
|
4
3
|
import { Home } from "../sandbox/home";
|
|
4
|
+
import { ClassesPage } from "../viewer/ClassesPage";
|
|
5
|
+
import { ComponentsIndex } from "../viewer/ComponentsIndex";
|
|
6
|
+
import { StoryPage } from "../viewer/StoryPage";
|
|
7
|
+
import { TokensPage } from "../viewer/TokensPage";
|
|
8
|
+
import { findStory } from "../viewer/discovery";
|
|
9
|
+
import { ViewerSidebar } from "../viewer/sidebar";
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
|
-
* Single route resolver, shared by every entry point
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* sandbox Frame and sets document.title from this.
|
|
12
|
+
* Single route resolver, shared by every entry point (Arrow scaffold shape, so
|
|
13
|
+
* a future SSR lane could call it identically). Pages may carry a sidebar
|
|
14
|
+
* (rendered outside the pane) and routes may resolve to a redirect, which the
|
|
15
|
+
* client router applies via history.replaceState.
|
|
12
16
|
*/
|
|
13
17
|
export interface Page {
|
|
14
18
|
status: number;
|
|
15
19
|
title: string;
|
|
16
20
|
view: ArrowExpression;
|
|
21
|
+
sidebar?: ArrowExpression;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const { pathname } = new URL(url, window.location.origin);
|
|
23
|
-
|
|
24
|
-
if (pathname === "/" || pathname === "") {
|
|
25
|
-
return {
|
|
26
|
-
status: 200,
|
|
27
|
-
title: APP_NAME,
|
|
28
|
-
view: Home(),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
24
|
+
export interface Redirect {
|
|
25
|
+
redirect: string;
|
|
26
|
+
}
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
if (match) {
|
|
34
|
-
return {
|
|
35
|
-
status: 200,
|
|
36
|
-
title: `${match.label} · ${APP_NAME}`,
|
|
37
|
-
view: match.view(),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
28
|
+
const APP_NAME = "Arrow Sandbox";
|
|
40
29
|
|
|
30
|
+
function notFound(pathname: string): Page {
|
|
41
31
|
return {
|
|
42
32
|
status: 404,
|
|
43
33
|
title: `Not found · ${APP_NAME}`,
|
|
@@ -47,7 +37,7 @@ export function routeToPage(url: string): Page {
|
|
|
47
37
|
<div class="setting-item-info">
|
|
48
38
|
<div class="setting-item-name">Not found</div>
|
|
49
39
|
<div class="setting-item-description">
|
|
50
|
-
No route for <code>${pathname}</code>. <a href="/">Back
|
|
40
|
+
No route for <code>${pathname}</code>. <a href="/">Back home</a>.
|
|
51
41
|
</div>
|
|
52
42
|
</div>
|
|
53
43
|
</div>
|
|
@@ -55,3 +45,60 @@ export function routeToPage(url: string): Page {
|
|
|
55
45
|
`,
|
|
56
46
|
};
|
|
57
47
|
}
|
|
48
|
+
|
|
49
|
+
export function routeToPage(url: string): Page | Redirect {
|
|
50
|
+
const { pathname, searchParams } = new URL(url, window.location.origin);
|
|
51
|
+
|
|
52
|
+
if (pathname === "/" || pathname === "") {
|
|
53
|
+
return { status: 200, title: APP_NAME, view: Home() };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (pathname === "/example") {
|
|
57
|
+
return { redirect: "/components/settings-panel" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (pathname === "/components" || pathname === "/components/") {
|
|
61
|
+
return {
|
|
62
|
+
status: 200,
|
|
63
|
+
title: `Components · ${APP_NAME}`,
|
|
64
|
+
view: ComponentsIndex(),
|
|
65
|
+
sidebar: ViewerSidebar(pathname),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const storyMatch = pathname.match(/^\/components\/([^/]+)$/);
|
|
70
|
+
if (storyMatch) {
|
|
71
|
+
const story = findStory(storyMatch[1]);
|
|
72
|
+
if (!story) {
|
|
73
|
+
return { ...notFound(pathname), sidebar: ViewerSidebar(pathname) };
|
|
74
|
+
}
|
|
75
|
+
const requested = searchParams.get("variant");
|
|
76
|
+
const variantName = requested ?? Object.keys(story.variants)[0];
|
|
77
|
+
return {
|
|
78
|
+
status: story.variants[variantName] ? 200 : 404,
|
|
79
|
+
title: `${story.title} · ${APP_NAME}`,
|
|
80
|
+
view: StoryPage(story, variantName),
|
|
81
|
+
sidebar: ViewerSidebar(`/components/${story.slug}`),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (pathname === "/reference") {
|
|
86
|
+
return {
|
|
87
|
+
status: 200,
|
|
88
|
+
title: `Tokens · ${APP_NAME}`,
|
|
89
|
+
view: TokensPage(),
|
|
90
|
+
sidebar: ViewerSidebar(pathname),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (pathname === "/reference/classes") {
|
|
95
|
+
return {
|
|
96
|
+
status: 200,
|
|
97
|
+
title: `Classes · ${APP_NAME}`,
|
|
98
|
+
view: ClassesPage(),
|
|
99
|
+
sidebar: ViewerSidebar(pathname),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return notFound(pathname);
|
|
104
|
+
}
|
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
import { component, html, reactive } from "@arrow-js/core";
|
|
2
2
|
import type { ArrowTemplate } from "@arrow-js/core";
|
|
3
|
-
import { ExamplesIndex } from "../examples/ExamplesIndex";
|
|
4
|
-
import { examples } from "../examples/registry";
|
|
5
3
|
import { layoutState } from "./layout";
|
|
6
4
|
import { themeState } from "./theme";
|
|
7
5
|
|
|
8
|
-
/**
|
|
9
|
-
* Sandbox landing page at "/": a readiness check + getting-started commands +
|
|
10
|
-
* the examples list. Sandbox chrome — does not port to a plugin.
|
|
11
|
-
*
|
|
12
|
-
* The readiness probe catches the #1 fresh-machine gotcha: running `pnpm dev`
|
|
13
|
-
* before `pnpm pull-css` leaves app.css unloaded, so every `var(--…)` token is
|
|
14
|
-
* empty. We detect that by reading the computed value of a known token.
|
|
15
|
-
*/
|
|
16
6
|
const probe = reactive({ tick: 0 });
|
|
17
7
|
|
|
18
8
|
function stylingLoaded(): boolean {
|
|
19
|
-
const generation = probe.tick;
|
|
9
|
+
const generation = probe.tick;
|
|
20
10
|
const style = getComputedStyle(document.body);
|
|
21
11
|
return (
|
|
22
12
|
generation >= 0 &&
|
|
@@ -29,6 +19,8 @@ function recheck(): void {
|
|
|
29
19
|
probe.tick++;
|
|
30
20
|
}
|
|
31
21
|
|
|
22
|
+
const gettingStarted = reactive({ expanded: false });
|
|
23
|
+
|
|
32
24
|
const GETTING_STARTED = [
|
|
33
25
|
{ cmd: "pnpm pull-css", note: "extract Obsidian's app.css — run once (macOS auto-detect)" },
|
|
34
26
|
{ cmd: "pnpm dev", note: "this dev server (Vite + HMR)" },
|
|
@@ -36,9 +28,13 @@ const GETTING_STARTED = [
|
|
|
36
28
|
{ cmd: "pnpm run ci", note: "biome + typecheck + tests + build" },
|
|
37
29
|
];
|
|
38
30
|
|
|
31
|
+
const VIEWS = [
|
|
32
|
+
{ label: "Components", path: "/components", note: "Component story viewer" },
|
|
33
|
+
{ label: "Tokens", path: "/reference", note: "CSS custom property reference" },
|
|
34
|
+
{ label: "Classes", path: "/reference/classes", note: "Obsidian class catalog" },
|
|
35
|
+
];
|
|
36
|
+
|
|
39
37
|
export const Home = component((): ArrowTemplate => {
|
|
40
|
-
// Re-probe shortly after mount, in case app.css finished loading after the
|
|
41
|
-
// first paint (stylesheets load async).
|
|
42
38
|
setTimeout(recheck, 250);
|
|
43
39
|
|
|
44
40
|
return html`
|
|
@@ -79,28 +75,61 @@ export const Home = component((): ArrowTemplate => {
|
|
|
79
75
|
</div>
|
|
80
76
|
</div>
|
|
81
77
|
</div>
|
|
78
|
+
</div>
|
|
82
79
|
|
|
80
|
+
<div class="${() => (gettingStarted.expanded ? "oas-card is-expanded" : "oas-card")}">
|
|
81
|
+
<div
|
|
82
|
+
class="oas-card-header"
|
|
83
|
+
@click="${() => {
|
|
84
|
+
gettingStarted.expanded = !gettingStarted.expanded;
|
|
85
|
+
}}"
|
|
86
|
+
>
|
|
87
|
+
<span class="oas-card-title">Getting started</span>
|
|
88
|
+
<span class="oas-card-chevron">›</span>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="oas-card-body">
|
|
91
|
+
<div class="oas-settings">
|
|
92
|
+
${GETTING_STARTED.map((step) =>
|
|
93
|
+
html`
|
|
94
|
+
<div class="setting-item">
|
|
95
|
+
<div class="setting-item-info">
|
|
96
|
+
<div class="setting-item-name" style="font-family: var(--font-monospace);">
|
|
97
|
+
${step.cmd}
|
|
98
|
+
</div>
|
|
99
|
+
<div class="setting-item-description">${step.note}</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
`.key(step.cmd)
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
<p class="oas-card-note">
|
|
106
|
+
See AGENTS.md + docs/ for the full flow; agent prompts in docs/prompts/.
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="oas-settings">
|
|
83
112
|
<div class="setting-item setting-item-heading">
|
|
84
113
|
<div class="setting-item-info">
|
|
85
|
-
<div class="setting-item-name">
|
|
86
|
-
<div class="setting-item-description">
|
|
87
|
-
See AGENTS.md + docs/ for the full flow; agent prompts in docs/prompts/.
|
|
88
|
-
</div>
|
|
114
|
+
<div class="setting-item-name">Views</div>
|
|
115
|
+
<div class="setting-item-description">Main pages in this sandbox.</div>
|
|
89
116
|
</div>
|
|
90
117
|
</div>
|
|
91
|
-
${
|
|
118
|
+
${VIEWS.map((view) =>
|
|
92
119
|
html`
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
</div>
|
|
98
|
-
<div class="setting-item-description">${step.note}</div>
|
|
120
|
+
<div class="setting-item">
|
|
121
|
+
<div class="setting-item-info">
|
|
122
|
+
<div class="setting-item-name">
|
|
123
|
+
<a href="${view.path}">${view.label}</a>
|
|
99
124
|
</div>
|
|
125
|
+
<div class="setting-item-description">${view.note}</div>
|
|
100
126
|
</div>
|
|
101
|
-
|
|
127
|
+
<div class="setting-item-control">
|
|
128
|
+
<a class="mod-cta oas-open-link" href="${view.path}">Open</a>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
`.key(view.label)
|
|
102
132
|
)}
|
|
103
133
|
</div>
|
|
104
|
-
${ExamplesIndex(examples)}
|
|
105
134
|
`;
|
|
106
135
|
});
|