create-obsidian-arrow 0.5.1 → 0.5.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 (61) hide show
  1. package/README.md +7 -7
  2. package/cli/create.mjs +65 -0
  3. package/cli/detect-pm.mjs +20 -0
  4. package/cli/lib.mjs +117 -0
  5. package/cli/refresh.mjs +65 -0
  6. package/index.mjs +47 -204
  7. package/package.json +11 -2
  8. package/template/.husky/pre-commit +3 -2
  9. package/template/AGENTS.md +57 -12
  10. package/template/README.md +66 -31
  11. package/template/_gitignore +4 -1
  12. package/template/biome.json +7 -1
  13. package/template/docs/prompts/agent-setup.md +22 -20
  14. package/template/docs/prompts/update-existing.md +3 -3
  15. package/template/docs/workflow.md +11 -7
  16. package/template/package.json +15 -14
  17. package/template/src/components/DiffViewer/DiffViewer.css +41 -0
  18. package/template/src/components/DiffViewer/DiffViewer.ts +55 -0
  19. package/template/src/components/EmptyState/EmptyState.css +5 -5
  20. package/template/src/components/EmptyState/EmptyState.ts +5 -9
  21. package/template/src/utilities.css +158 -0
  22. package/template/src/views/DiffViewer/DiffViewerView.css +42 -0
  23. package/template/src/views/DiffViewer/DiffViewerView.ts +53 -0
  24. package/template/src/views/ExampleView/ExampleView.ts +92 -0
  25. package/template/stories/components/ComponentShell.stories.ts +28 -0
  26. package/template/stories/components/EmptyState.stories.ts +1 -0
  27. package/template/stories/components/LoadingState.stories.ts +1 -0
  28. package/template/stories/components/Toggle.stories.ts +50 -0
  29. package/template/stories/views/DiffViewer/DiffViewer.stories.ts +94 -0
  30. package/template/stories/views/EditorView.stories.ts +55 -0
  31. package/template/stories/views/ExampleView/ExampleView.stories.ts +15 -0
  32. package/template/stories/views/PanelView.stories.ts +61 -0
  33. package/template/stories/views/SettingsPanel/SettingsPanel.stories.ts +14 -0
  34. package/template/test/css-structure.test.mjs +112 -0
  35. package/template/test/template-footguns.test.mjs +85 -6
  36. package/template/test/viewer-stories.test.mjs +12 -0
  37. package/template/tools/router/client.ts +26 -4
  38. package/template/tools/router/routeToPage.ts +29 -13
  39. package/template/tools/sandbox/frame.ts +7 -27
  40. package/template/tools/sandbox/home.ts +6 -11
  41. package/template/tools/sandbox/layout.ts +24 -2
  42. package/template/tools/sandbox/sandbox.css +188 -226
  43. package/template/tools/sandbox/shell.ts +2 -2
  44. package/template/tools/sandbox/toolbar.ts +20 -9
  45. package/template/tools/viewer/ClassesPage.ts +7 -7
  46. package/template/tools/viewer/ComponentsIndex.ts +3 -3
  47. package/template/tools/viewer/StoryPage.ts +53 -40
  48. package/template/tools/viewer/TokensPage.ts +10 -10
  49. package/template/tools/viewer/ViewsIndex.ts +66 -0
  50. package/template/tools/viewer/discovery.ts +2 -0
  51. package/template/tools/viewer/obsidian-classes.ts +1 -1
  52. package/template/tools/viewer/sidebar.ts +27 -38
  53. package/template/tools/viewer/stories.ts +16 -2
  54. package/template/.github/workflows/ci.yml +0 -36
  55. package/template/pnpm-lock.yaml +0 -1608
  56. package/template/scripts/create-component.mjs +0 -101
  57. package/template/scripts/create-view.mjs +0 -75
  58. package/template/src/components/DiffViewer.ts +0 -42
  59. package/template/stories/DiffViewer.stories.ts +0 -75
  60. package/template/stories/SettingsPanel.stories.ts +0 -11
  61. package/template/stories/Toggle.stories.ts +0 -28
@@ -20,6 +20,9 @@ This file is the hub — everything else is linked from here:
20
20
  - `obsidian-arrow-maintenance` — updating an existing project.
21
21
  - [`docs/prompts/`](docs/prompts/) — copy-paste agent prompts: `agent-setup.md`
22
22
  (scaffold + orient) and `update-existing.md` (update tooling + skills, keep src).
23
+ - [`CONTRIBUTING.md`](CONTRIBUTING.md) — source-repo and tool development (monorepo layout,
24
+ `oasbox`/`create-obsidian-arrow` package dev, `pnpm create:sync`). **Project-level work
25
+ does not require reading this.**
23
26
 
24
27
  Design rationale (why `core`+`framework`, no SSR, how `app.css` is sourced) is
25
28
  summarized in "What this is (and isn't)" below and in the README.
@@ -38,23 +41,64 @@ summarized in "What this is (and isn't)" below and in the README.
38
41
  ## Run it
39
42
 
40
43
  ```sh
41
- pnpm install
42
- pnpm pull-css # macOS-only auto-detect; else --path <asar|css> / OBSIDIAN_ASAR=<path>
43
- pnpm dev # Vite + HMR
44
+ <pm> install
45
+ <pm> run pull-css # macOS-only auto-detect; else --path <asar|css> / OBSIDIAN_ASAR=<path>
46
+ <pm> run dev # Vite + HMR
44
47
  ```
45
48
 
49
+ (`<pm>` = `pnpm` / `npm` / `bun` — any package manager works.)
50
+
46
51
  `public/app.css` is **git-ignored** (Obsidian's proprietary CSS — not
47
- redistributed); run `pnpm pull-css` once before `pnpm dev`.
52
+ redistributed); run `pull-css` once before `dev`.
48
53
 
49
54
  Install the skills (pulled from the published repo, not vendored):
50
- `pnpm skills:install --yes` (non-interactive, all skills) or `pnpm skills:install`
51
- (TUI); update with `pnpm skills:update`. Scope
55
+ `<pm> run skills:install --yes` (non-interactive, all skills) or `<pm> run skills:install`
56
+ (TUI); update with `<pm> run skills:update`. Scope
52
57
  flags: `--agent <name>`, `--project-dir=<path>`, `--global`. **Nested inside
53
58
  another repo?** Skills install cwd-relative — use `--project-dir=<outer-repo>` so
54
59
  they land where an agent at the outer repo looks. To refresh an existing
55
- project's tooling, run `npx create-obsidian-arrow update` (see the
60
+ project's tooling, run `npx create-obsidian-arrow refresh` (see the
56
61
  obsidian-arrow-maintenance skill).
57
62
 
63
+ ## oasbox — in-project CLI
64
+
65
+ `oasbox` is installed as a local devDependency by the scaffold. Run it via
66
+ `npx oasbox …`, `pnpm oasbox …`, or `bunx oasbox …` — works under any package manager.
67
+
68
+ ### generate
69
+
70
+ ```sh
71
+ oasbox generate component <Name> [--css] # flat or Parent/Child (view-scoped)
72
+ oasbox generate view <Name> [--editor] # --editor → surface:"editor", readable line width
73
+ oasbox generate story <Name> [--kind view|component]
74
+ ```
75
+
76
+ `pnpm create:view <Name>` and `pnpm create:component <Name>` are convenience aliases
77
+ that delegate to `oasbox generate` — use whichever you prefer.
78
+
79
+ ### validate & test
80
+
81
+ ```sh
82
+ oasbox validate # check:css + check:scope + check:imports + typecheck
83
+ oasbox validate --json # machine-readable output
84
+ oasbox test # pass-through to project test runner
85
+ oasbox dev # pass-through to project dev server
86
+ ```
87
+
88
+ ## create-obsidian-arrow lifecycle
89
+
90
+ `create-obsidian-arrow` is the **project lifecycle** CLI — run it ephemerally (no
91
+ local install needed):
92
+
93
+ ```sh
94
+ npx create-obsidian-arrow create <dir> # scaffold a new project
95
+ npx create-obsidian-arrow refresh [dir] # refresh managed tooling files from the current template
96
+ ```
97
+
98
+ `refresh` (formerly `update`) pulls fresh managed files; `--dry-run` shows what
99
+ would change without writing. Genuine version bumps (skills, dependencies) are
100
+ handled separately. Works with any PM: `npx` / `pnpm dlx` / `bunx`.
101
+
58
102
  ## Arrow v1.0.6 footguns — READ BEFORE WRITING TEMPLATES
59
103
 
60
104
  These are hard runtime errors, not style nits. They are encoded in CI
@@ -107,8 +151,9 @@ in `boundary()`.
107
151
  - `stories/components/` — stories for primitives (`kind: "component"`)
108
152
  - Mirror the `src/` structure. `kind` auto-detected from path; set explicitly to override.
109
153
  - `kind` — specifies story type (`"view"` or `"component"`).
154
+ - `surface?: "panel" | "editor"` — which Obsidian surface a view replicates (orthogonal to `kind`). `"panel"` (default) is full-bleed (workspace leaf); `"editor"` is readable line width for note/document views. Scaffold with `pnpm create:view <Name> --editor` — it wraps the body in the portable `oas-readable-width` utility (ports 1:1) and sets the flag; the flag only widens the sandbox pane so the centering shows. Use that utility — don't write your own `max-width: var(--file-line-width)` container.
110
155
  - `decorator?: (content: ArrowExpression) => ArrowExpression` — wraps the rendered variant in ancestor context when a component requires a parent class to render correctly.
111
- - Scaffold: `pnpm create:view <Name>` / `pnpm create:component <Name>`
156
+ - Scaffold: `pnpm create:view <Name>` (add `--editor` for readable-width) / `pnpm create:component <Name>`
112
157
 
113
158
  ### Composition analysis (extracting shared primitives)
114
159
 
@@ -132,10 +177,10 @@ When multiple views share a pattern, extract a primitive — but do it in this o
132
177
  ## Verify before claiming done
133
178
 
134
179
  ```sh
135
- pnpm typecheck # tsc --noEmit
136
- pnpm test # node:test
137
- pnpm lint # biome
138
- pnpm check # all of the above + CSS orphan + import boundary + scope checks
180
+ <pm> run typecheck # tsc --noEmit
181
+ <pm> run test # node:test
182
+ <pm> run lint # biome
183
+ <pm> run check # all of the above + CSS orphan + import boundary + scope checks
139
184
  ```
140
185
 
141
186
  Then confirm the actual render:
@@ -7,31 +7,33 @@ styled entirely by Obsidian's real `app.css`, so what you see here is what you
7
7
  get inside a plugin view.
8
8
 
9
9
  New machine? See [`docs/workflow.md`](docs/workflow.md) for the full
10
- fresh-checkout-to-running workflow.
10
+ fresh-checkout-to-running workflow. **Working on the tooling itself?** See
11
+ [`CONTRIBUTING.md`](CONTRIBUTING.md) for the monorepo dev workflow.
11
12
 
12
13
  ## Scaffold a new project
13
14
 
14
- Scaffold a fresh sandbox with the published initializer
15
- ([`create-obsidian-arrow`](create-obsidian-arrow/)):
15
+ Scaffold a fresh sandbox with the published initializer:
16
16
 
17
17
  ```sh
18
18
  npm create obsidian-arrow@latest my-app
19
- # or: pnpm create obsidian-arrow my-app
20
- # or: npx create-obsidian-arrow my-app
19
+ # or: npx create-obsidian-arrow create my-app
20
+ # or: pnpm dlx create-obsidian-arrow create my-app
21
+ # or: bunx create-obsidian-arrow create my-app
21
22
  ```
22
23
 
23
- Then `cd my-app && pnpm install && pnpm pull-css && pnpm dev`. A freshly
24
- scaffolded project passes `pnpm run ci` out of the box. The initializer's
25
- template is generated from this repo (`pnpm create:sync`), so it never drifts.
24
+ Then `cd my-app && <pm> install && <pm> run pull-css && <pm> run dev`. A freshly
25
+ scaffolded project passes the full `check` suite out of the box.
26
26
 
27
- **Update an existing project's tooling** (refreshes managed files — scripts,
27
+ **Refresh an existing project's tooling** (pulls fresh managed files — scripts,
28
28
  docs, CI, `biome.json`, agent guides, and the viewer/router/sandbox
29
29
  infrastructure in `src/` — merges new `package.json` scripts/deps; never
30
30
  touches `src/components/`, `stories/`, `public/`, `index.html`, or build configs):
31
31
 
32
32
  ```sh
33
- npx create-obsidian-arrow update # in the project (or: update <dir>)
34
- npx create-obsidian-arrow update --dry-run # preview first
33
+ npx create-obsidian-arrow refresh # in the project (or: refresh <dir>)
34
+ npx create-obsidian-arrow refresh --dry-run # preview first
35
+ # also: pnpm dlx create-obsidian-arrow refresh
36
+ # also: bunx create-obsidian-arrow refresh
35
37
  ```
36
38
 
37
39
  > **Nested in another repo?** If you scaffold inside an existing repo, skills
@@ -43,51 +45,79 @@ npx create-obsidian-arrow update --dry-run # preview first
43
45
  > the session**. The scaffolder prints this hint when it detects nesting.
44
46
 
45
47
  > This repo (the full sandbox) is **not** published to npm — only the
46
- > `create-obsidian-arrow/` initializer is. Copy-paste agent prompts live in
48
+ > `create-obsidian-arrow` initializer is. Copy-paste agent prompts live in
47
49
  > [`docs/prompts/`](docs/prompts/): `agent-setup.md` (scaffold + orient) and
48
- > `update-existing.md` (update tooling + skills, keeping `src/` intact).
50
+ > `update-existing.md` (refresh tooling + skills, keeping `src/` intact).
49
51
 
50
52
  ## Quick start
51
53
 
52
54
  ```sh
53
- pnpm install
54
- pnpm pull-css # extract Obsidian's app.css from your local install (required)
55
- pnpm dev # Vite dev server with HMR
55
+ <pm> install
56
+ <pm> run pull-css # extract Obsidian's app.css from your local install (required)
57
+ <pm> run dev # Vite dev server with HMR
56
58
  ```
57
59
 
60
+ (`<pm>` = `npm`, `pnpm`, `bun`, or `yarn` — whichever you prefer.)
61
+
58
62
  `pull-css` reads `app.css` out of `Obsidian.app/.../obsidian.asar` (macOS) and
59
63
  writes `public/app.css`. Override the location with `--path <obsidian.asar|app.css>`
60
- or `OBSIDIAN_ASAR=<path>`. **Run it once before `pnpm dev`** — it needs a local
64
+ or `OBSIDIAN_ASAR=<path>`. **Run it once before `dev`** — it needs a local
61
65
  Obsidian install.
62
66
 
63
67
  > **Why it isn't committed:** `public/app.css` is **git-ignored**. It's
64
68
  > Obsidian's proprietary stylesheet, so we don't redistribute it — each developer
65
- > extracts it from their own licensed Obsidian install via `pnpm pull-css`.
69
+ > extracts it from their own licensed Obsidian install via `pull-css`.
66
70
 
67
71
  > **Platform note:** automatic Obsidian discovery is currently **macOS-only** —
68
72
  > `pull-css` knows where `Obsidian.app` lives on macOS. Windows and WSL paths are
69
73
  > not auto-detected yet (planned). On those platforms, point the script at the
70
74
  > file explicitly via `--path <obsidian.asar|app.css>` or `OBSIDIAN_ASAR=<path>`.
71
75
 
76
+ ## Daily driver: `oasbox`
77
+
78
+ `oasbox` is the in-project CLI — installed as a local devDependency by the scaffold.
79
+ Run it via your package manager or `npx`:
80
+
81
+ ```sh
82
+ # Generate scaffolding
83
+ oasbox generate view ChatView # new view (full-pane)
84
+ oasbox generate view ChatView --editor # readable line-width (surface: "editor")
85
+ oasbox generate component Composer # primitive → src/components/Composer/
86
+ oasbox generate component ChatView/Widget # view-scoped → src/views/ChatView/Widget.ts
87
+ oasbox generate story MyWidget --kind component
88
+
89
+ # Validate (runs CSS orphan/scope/import checks + typecheck)
90
+ oasbox validate
91
+ oasbox validate --json # machine-readable output
92
+
93
+ # Pass-throughs
94
+ oasbox test
95
+ oasbox dev
96
+ ```
97
+
98
+ Invoke via your PM if `oasbox` isn't on your PATH: `pnpm oasbox …` / `npx oasbox …` / `bunx oasbox …`.
99
+
100
+ The `create:view` / `create:component` scripts delegate to `oasbox generate` — both forms work.
101
+
72
102
  ## Scripts
73
103
 
74
104
  | Script | What it does |
75
105
  |---|---|
76
- | `pnpm dev` | Vite dev server (client-only, HMR) |
77
- | `pnpm build` / `pnpm preview` | Production build / preview |
78
- | `pnpm typecheck` | `tsc --noEmit` |
79
- | `pnpm test` | Node built-in test runner (`test/*.test.mjs`) |
80
- | `pnpm lint` / `pnpm format` | Biome check / check-and-write |
81
- | `pnpm check` | format + typecheck + test (local pre-flight) |
82
- | `pnpm ci` | Biome CI + typecheck + test + build (what CI runs) |
83
- | `pnpm pull-css` | refresh `public/app.css` from the local Obsidian install |
106
+ | `dev` | Vite dev server (client-only, HMR) |
107
+ | `build` / `preview` | Production build / preview |
108
+ | `typecheck` | `tsc --noEmit` |
109
+ | `test` | Node built-in test runner (`test/*.test.mjs`) |
110
+ | `lint` / `format` | Biome check / check-and-write |
111
+ | `check` | Biome + typecheck + test + CSS-orphan/scope/import checks (local pre-flight) |
112
+ | `ci` | Biome CI + typecheck + test + CSS-orphan/scope/import checks + build (what CI runs) |
113
+ | `pull-css` | refresh `public/app.css` from the local Obsidian install |
84
114
 
85
115
  A husky `pre-commit` hook runs `lint-staged` (Biome on staged files) + a full
86
116
  typecheck. CI (`.github/workflows/ci.yml`) runs the `ci` script on push/PR.
87
117
 
88
118
  ## Agent skills
89
119
 
90
- This repo is the source of truth for seven [`skills`](https://github.com/vercel-labs/skills)-compatible
120
+ This repo is the source of truth for eight [`skills`](https://github.com/vercel-labs/skills)-compatible
91
121
  skills under [`skills/`](skills/) — it's a skill marketplace. Scaffolds **don't
92
122
  vendor copies**; they pull from this published repo, so installs are always
93
123
  current.
@@ -102,8 +132,8 @@ current.
102
132
  - `arrow-js-obsidian-porting` — content-addressed porting parity: the
103
133
  `component-hash` tool + a husky/CI check that the plugin copy hasn't drifted
104
134
  from the sandbox source.
105
- - `obsidian-arrow-maintenance` — updating an existing project: `create-obsidian-arrow
106
- update`, `skills:update`, nesting/`--project-dir`, re-pull styling.
135
+ - `obsidian-arrow-maintenance` — refreshing an existing project: `create-obsidian-arrow
136
+ refresh`, `skills:update`, nesting/`--project-dir`, re-pull styling.
107
137
 
108
138
  Install them into your agent (pulls from the published repo; the sandbox repo
109
139
  itself uses its local `skills/`):
@@ -119,7 +149,7 @@ pnpm skills:update # update an existing setup t
119
149
  Anywhere, with no project at all:
120
150
  `npx skills add kylebrodeur/obsidian-arrow-sandbox --all --yes`.
121
151
 
122
- `postinstall` offers the picker automatically after `pnpm install`, but only in
152
+ `postinstall` offers the picker automatically after `<pm> install`, but only in
123
153
  an interactive terminal — in CI / non-TTY it just prints how to install (never
124
154
  hangs). For agents/CI, use `--yes`.
125
155
  Scope flags: `--agent <name>` (one agent), `--project-dir=<path>` (install into a
@@ -147,6 +177,7 @@ with live previews.
147
177
 
148
178
  ```sh
149
179
  pnpm create:view ChatView
180
+ # or: oasbox generate view ChatView
150
181
  ```
151
182
 
152
183
  Creates `src/views/ChatView/` (ts + css + state.ts) and `stories/views/ChatView.stories.ts`.
@@ -156,9 +187,13 @@ Creates `src/views/ChatView/` (ts + css + state.ts) and `stories/views/ChatView.
156
187
  ```sh
157
188
  pnpm create:component Composer # primitive → src/components/Composer/
158
189
  pnpm create:component ChatView/Widget # view-specific → src/views/ChatView/Widget.ts
190
+ # or: oasbox generate component Composer
159
191
  ```
160
192
 
161
- View stories use `kind: "view"` — full pane frame. Component stories use `kind: "component"` centered canvas. Both auto-detected from the `stories/views/` vs `stories/components/` path.
193
+ View stories use `kind: "view"` — full pane frame with `surface: "panel"` (default) for
194
+ full-bleed or `surface: "editor"` for readable line width. Component stories use
195
+ `kind: "component"` — centered canvas. Both auto-detected from the `stories/views/` vs
196
+ `stories/components/` path.
162
197
 
163
198
  ## Porting a component into the plugin
164
199
 
@@ -9,7 +9,10 @@ dist
9
9
 
10
10
  # Obsidian's app.css is proprietary — extract it locally with `pnpm pull-css`,
11
11
  # don't commit/redistribute it.
12
- public/app.css
12
+ **/public/app.css
13
+
14
+ # CodeGraph index (per-machine, regenerated by `codegraph index`).
15
+ .codegraph/
13
16
 
14
17
  # subagent-driven-development scratch (briefs, reports, ledger)
15
18
  .superpowers/
@@ -25,6 +25,12 @@
25
25
  }
26
26
  },
27
27
  "files": {
28
- "ignore": ["dist", "node_modules", "public", "pnpm-lock.yaml", "create-obsidian-arrow/template"]
28
+ "ignore": [
29
+ "dist",
30
+ "node_modules",
31
+ "public",
32
+ "pnpm-lock.yaml",
33
+ "packages/create-obsidian-arrow/template"
34
+ ]
29
35
  }
30
36
  }
@@ -24,26 +24,27 @@ SCAFFOLD IT (use the published tool — pick one)
24
24
 
25
25
  Then:
26
26
  cd my-app
27
- pnpm install
28
- pnpm pull-css # REQUIRED before dev — extracts Obsidian's app.css from your
29
- # LOCAL install into public/app.css (git-ignored, never
30
- # committed; it's Obsidian's proprietary CSS). Auto-detect is
31
- # macOS-only; on Windows/WSL pass --path <obsidian.asar|app.css>
32
- # or set OBSIDIAN_ASAR=<path>.
33
- pnpm dev # open the printed URL: / is home, /components the story viewer,
34
- # /reference the Obsidian token/class index.
35
- # The toolbar slider/presets + edge drag handle resize the panel.
36
- pnpm skills:install --yes # install all agent skills non-interactively, pulled
37
- # from the published repo (not vendored) this loads
38
- # the domain knowledge. Drop --yes for an interactive picker.
39
- # NESTED inside another repo? Skills install cwd-relative,
40
- # so add --project-dir=<outer-repo> (or --global) to put them
41
- # where an agent at the outer repo will find them.
27
+ <pm> install # npm install, pnpm install, or bun install
28
+ <pm> run pull-css # REQUIRED before dev — extracts Obsidian's app.css from your
29
+ # LOCAL install into public/app.css (git-ignored, never
30
+ # committed; it's Obsidian's proprietary CSS). Auto-detect is
31
+ # macOS-only; on Windows/WSL pass --path <obsidian.asar|app.css>
32
+ # or set OBSIDIAN_ASAR=<path>.
33
+ <pm> run dev # open the printed URL: / is home, /components the story viewer,
34
+ # /reference the Obsidian token/class index.
35
+ # The toolbar slider/presets + edge drag handle resize the panel.
36
+ oasbox generate view MyView # scaffold a new view (or component/story)
37
+ pnpm skills:install --yes # install all agent skills non-interactively, pulled
38
+ # from the published repo (not vendored) this loads
39
+ # the domain knowledge. Drop --yes for an interactive picker.
40
+ # NESTED inside another repo? Skills install cwd-relative,
41
+ # so add --project-dir=<outer-repo> (or --global) to put them
42
+ # where an agent at the outer repo will find them.
42
43
 
43
44
  READ FIRST
44
45
  - AGENTS.md (root) — operating guide + docs map (links everything below).
45
46
  - docs/workflow.md — fresh-machine → running workflow.
46
- - skills/*/SKILL.md — seven installable skills:
47
+ - skills/*/SKILL.md — eight installable skills:
47
48
  obsidian-arrow-composition design component hierarchy: surveys codebase, shows file trees,
48
49
  asks questions, checks DRY/primitives, produces locked plan
49
50
  obsidian-arrow-sandbox running the sandbox, CSS scoping, porting basics
@@ -77,9 +78,10 @@ CONVENTIONS
77
78
  only when Obsidian has no class, scoped under a container class + element type
78
79
  (e.g. `.my-panel button.my-action`) so it beats Obsidian's global button rule.
79
80
  - Sandbox-only chrome lives in tools/sandbox/* — it does NOT port to a plugin.
80
- - Add a story by creating `stories/MyThing.stories.ts` (top-level `stories/`
81
- dir, NOT in `src/`). Import: `"../tools/viewer/stories"` and
82
- `"../src/components/MyThing"`. It appears at `/components/<slug>` automatically.
81
+ - Add a story by creating `stories/components/MyThing.stories.ts` (or
82
+ `stories/views/MyView/MyView.stories.ts` for a view) under `stories/`, NOT in
83
+ `src/`. Import (depth follows nesting): `"../../tools/viewer/stories"` and
84
+ `"../../src/components/MyThing"`. It appears at `/components/<slug>` automatically.
83
85
  Browse tokens at `/reference`, curated classes at `/reference/classes`.
84
86
 
85
87
  VERIFY BEFORE CLAIMING DONE
@@ -99,7 +101,7 @@ Leave tools/sandbox/* behind. Guard against drift with the porting-parity check
99
101
  (see the arrow-js-obsidian-porting skill).
100
102
 
101
103
  MAINTENANCE (existing project)
102
- Refresh tooling with `npx create-obsidian-arrow update` (preserves src/components/
104
+ Refresh tooling with `npx create-obsidian-arrow refresh` (preserves src/components/
103
105
  and stories/ — updates viewer, router, sandbox, utilities, and tooling files),
104
106
  update skills with `pnpm skills:update`. See the obsidian-arrow-maintenance skill.
105
107
 
@@ -32,8 +32,8 @@ STEPS (in order)
32
32
  so the session's skill:// registry (repo-root + global only) can find them.
33
33
 
34
34
  2. Refresh tooling — preserves src/components/ only
35
- - Preview: npx create-obsidian-arrow update --dry-run
36
- - Apply: npx create-obsidian-arrow update
35
+ - Preview: npx create-obsidian-arrow refresh --dry-run
36
+ - Apply: npx create-obsidian-arrow refresh
37
37
  - Then: pnpm install
38
38
  Refreshes scripts/, docs/, .github/, .husky/, biome.json, AGENTS.md, CLAUDE.md,
39
39
  tools/, src/main.ts, src/utilities.css, test/,
@@ -77,6 +77,6 @@ REPORT
77
77
  - Skills are pulled from the published repo, so step 3 is location-independent for
78
78
  the *source* — only the *install location* matters (run it at the repo root the
79
79
  agent uses; reload after).
80
- - `npx create-obsidian-arrow update` refreshes managed files and merges
80
+ - `npx create-obsidian-arrow refresh` refreshes managed files and merges
81
81
  `package.json`, but never overwrites `src/components/`, `stories/`, `public/`,
82
82
  `index.html`, or build configs. See the `obsidian-arrow-maintenance` skill.
@@ -51,25 +51,29 @@ Then point the agent at [`AGENTS.md`](../AGENTS.md), or brief a fresh agent with
51
51
 
52
52
  **Refresh an existing project's tooling** (scripts, docs, CI, viewer/router/sandbox
53
53
  in `src/` — never `src/components/` or `stories/`):
54
- `npx create-obsidian-arrow update` (add `--dry-run` to preview).
54
+ `npx create-obsidian-arrow refresh` (add `--dry-run` to preview).
55
55
 
56
56
  ## Build → verify → port loop
57
57
 
58
58
  ```sh
59
59
  # 1. Check /reference/classes in the running sandbox — does Obsidian have a class
60
60
  # for your pattern? Use it before writing custom CSS.
61
- # 2. Scaffold (or add manually):
62
- pnpm create:view ChatView # full-pane view → src/views/ChatView/ + stories/views/
63
- pnpm create:component Composer # reusable primitive → src/components/Composer/ + stories/components/
64
- pnpm create:component ChatView/Widget # view-specific sub-component (not promoted yet)
61
+ # 2. Generate components, views, and stories (using oasbox or the npm scripts):
62
+ oasbox generate view ChatView # full-pane view → src/views/ChatView/ + stories/views/
63
+ oasbox generate component Composer # reusable primitive → src/components/Composer/ + stories/components/
64
+ oasbox generate component ChatView/Widget # view-specific sub-component (not promoted yet)
65
+ # Alternatively, the npm scripts delegate to oasbox:
66
+ <pm> run create:view ChatView
67
+ <pm> run create:component Composer
65
68
  # 3. Iterate with HMR
66
- pnpm dev
69
+ <pm> run dev
67
70
  # /components → index of all discovered stories
68
71
  # /components/my-thing → story for MyThing (kind: "component" → centered canvas)
69
72
  # /components/chat-view → story for ChatView (kind: "view" → full pane)
70
73
  # /reference → live Obsidian token reference
71
74
  # /reference/classes → curated class catalog with live previews
72
- pnpm run ci # biome + typecheck + tests + build before trusting it
75
+ oasbox validate # CSS check + scope + imports + typecheck
76
+ <pm> run ci # biome + typecheck + tests + build before trusting it
73
77
  ```
74
78
 
75
79
  Always confirm the actual render in the browser — Arrow's footguns and CSS scoping bugs
@@ -2,9 +2,9 @@
2
2
  "name": "obsidian-arrow-sandbox",
3
3
  "private": true,
4
4
  "type": "module",
5
+ "version": "0.1.0",
5
6
  "description": "Client-only sandbox for prototyping Obsidian plugin UI with Arrow.js against Obsidian's real styling.",
6
7
  "license": "MIT",
7
- "packageManager": "pnpm@10.14.0",
8
8
  "scripts": {
9
9
  "dev": "vite",
10
10
  "build": "vite build",
@@ -12,21 +12,20 @@
12
12
  "typecheck": "tsc -p tsconfig.json --noEmit",
13
13
  "pull-css": "node scripts/pull-app-css.mjs",
14
14
  "port:css": "node scripts/port-css.mjs",
15
- "create:sync": "node create-obsidian-arrow/scripts/sync-template.mjs",
16
- "create:component": "node scripts/create-component.mjs",
17
- "create:view": "node scripts/create-view.mjs",
15
+ "create:component": "oasbox generate component",
16
+ "create:view": "oasbox generate view",
18
17
  "lint": "biome check .",
19
18
  "format": "biome check --write .",
20
19
  "test": "node --experimental-strip-types --test test/*.test.mjs",
21
20
  "check:css": "node scripts/check-orphaned-css.mjs",
22
21
  "check:scope": "node scripts/check-scope-classes.mjs",
23
22
  "check:imports": "node scripts/check-view-imports.mjs",
24
- "check": "biome check . && pnpm typecheck && pnpm test && pnpm check:css && pnpm check:scope && pnpm check:imports",
25
- "ci": "biome ci . && pnpm typecheck && pnpm test && pnpm check:css && pnpm check:scope && pnpm check:imports && pnpm build",
23
+ "check": "biome check . && tsc -p tsconfig.json --noEmit && node --experimental-strip-types --test test/*.test.mjs && node scripts/check-orphaned-css.mjs && node scripts/check-scope-classes.mjs && node scripts/check-view-imports.mjs",
24
+ "ci": "biome ci . && tsc -p tsconfig.json --noEmit && node --experimental-strip-types --test test/*.test.mjs && node scripts/check-orphaned-css.mjs && node scripts/check-scope-classes.mjs && node scripts/check-view-imports.mjs && vite build",
25
+ "prepare": "husky",
26
26
  "skills:install": "node scripts/install-skills.mjs --force",
27
27
  "skills:update": "node scripts/install-skills.mjs --update",
28
- "postinstall": "node scripts/install-skills.mjs",
29
- "prepare": "husky"
28
+ "postinstall": "node scripts/install-skills.mjs"
30
29
  },
31
30
  "lint-staged": {
32
31
  "*.{ts,mjs,js,json,jsonc}": "biome check --write --no-errors-on-unmatched"
@@ -42,14 +41,16 @@
42
41
  "devDependencies": {
43
42
  "@biomejs/biome": "^1.9.4",
44
43
  "@types/node": "^22.16.5",
45
- "husky": "^9.1.6",
46
- "lint-staged": "^15.2.10",
44
+ "oasbox": "^0.1.0",
47
45
  "postcss": "^8.5.16",
48
46
  "typescript": "^5.9.3",
49
- "vite": "^8.0.0"
47
+ "vite": "^8.0.0",
48
+ "husky": "^9.1.6",
49
+ "lint-staged": "^15.2.10"
50
50
  },
51
51
  "pnpm": {
52
- "onlyBuiltDependencies": ["@biomejs/biome"]
53
- },
54
- "version": "0.1.0"
52
+ "onlyBuiltDependencies": [
53
+ "@biomejs/biome"
54
+ ]
55
+ }
55
56
  }
@@ -0,0 +1,41 @@
1
+ /* DiffViewer — CodeMirror 6 MergeView container. Portable. */
2
+ .oas-diff-viewer {
3
+ height: 480px;
4
+ overflow: hidden;
5
+ border: 1px solid var(--background-modifier-border);
6
+ border-radius: var(--radius-m);
7
+ font-family: var(--font-monospace);
8
+ font-size: var(--font-ui-small);
9
+ }
10
+
11
+ .oas-diff-viewer .cm-mergeView {
12
+ height: 100%;
13
+ }
14
+
15
+ .oas-diff-viewer .cm-editor {
16
+ height: 100%;
17
+ background: var(--background-primary);
18
+ color: var(--text-normal);
19
+ }
20
+
21
+ .oas-diff-viewer .cm-scroller {
22
+ overflow: auto;
23
+ font-family: var(--font-monospace);
24
+ }
25
+
26
+ .oas-diff-viewer .cm-mergeViewEditor {
27
+ border-right: 1px solid var(--background-modifier-border);
28
+ }
29
+
30
+ .oas-diff-viewer .cm-mergeViewEditor:last-child {
31
+ border-right: none;
32
+ }
33
+
34
+ .oas-diff-viewer .cm-deletedChunk {
35
+ background: color-mix(in srgb, var(--color-red) 15%, transparent);
36
+ }
37
+
38
+ .oas-diff-viewer .cm-insertedChunk,
39
+ .oas-diff-viewer .cm-changedLine {
40
+ background: color-mix(in srgb, var(--color-green) 12%, transparent);
41
+ }
@@ -0,0 +1,55 @@
1
+ import { html } from "@arrow-js/core";
2
+ import type { ArrowTemplate } from "@arrow-js/core";
3
+ import { markdown } from "@codemirror/lang-markdown";
4
+ import { MergeView } from "@codemirror/merge";
5
+ import { EditorState } from "@codemirror/state";
6
+ import "./DiffViewer.css";
7
+
8
+ export interface DiffViewerOptions {
9
+ original: string;
10
+ modified: string;
11
+ /** Which side shows original (a) and which shows modified (b). Default: a-b */
12
+ orientation?: "a-b" | "b-a";
13
+ }
14
+
15
+ /**
16
+ * CodeMirror 6 MergeView — side-by-side diff using the same CM6 engine as
17
+ * Obsidian's editor. Obsidian's app.css styles .cm-* classes automatically,
18
+ * so this looks native without additional theming.
19
+ *
20
+ * Arrow.js 1.x only handles ArrowTemplate/component/array in expressions —
21
+ * raw Node insertion isn't supported. We render a placeholder via html`` and
22
+ * mount the CM6 MergeView into it after Arrow commits the DOM.
23
+ *
24
+ * requestAnimationFrame (not queueMicrotask) is required here: CSS flex layout
25
+ * is calculated by the rendering engine, not during microtask execution. CM6
26
+ * reads the container's offsetHeight at mount time — if flex hasn't painted yet
27
+ * it gets 0, and the editor renders at a fixed fallback height instead of
28
+ * filling its container.
29
+ */
30
+ export function DiffViewer(options: DiffViewerOptions): ArrowTemplate {
31
+ // Unique attribute so we can locate the container after Arrow mounts it.
32
+ const key = Math.random().toString(36).slice(2, 10);
33
+
34
+ requestAnimationFrame(() => {
35
+ const container = document.querySelector(`[data-diff="${key}"]`);
36
+ if (!container) return;
37
+ container.removeAttribute("data-diff");
38
+ new MergeView({
39
+ a: {
40
+ doc: options.original,
41
+ extensions: [markdown(), EditorState.readOnly.of(true)],
42
+ },
43
+ b: {
44
+ doc: options.modified,
45
+ extensions: [markdown(), EditorState.readOnly.of(true)],
46
+ },
47
+ parent: container,
48
+ orientation: options.orientation ?? "a-b",
49
+ highlightChanges: true,
50
+ collapseUnchanged: { margin: 3, minSize: 4 },
51
+ });
52
+ });
53
+
54
+ return html`<div class="oas-diff-viewer" data-diff="${key}"></div>`;
55
+ }
@@ -1,4 +1,4 @@
1
- .empty-state {
1
+ .oas-empty-state {
2
2
  display: flex;
3
3
  flex-direction: column;
4
4
  align-items: center;
@@ -8,23 +8,23 @@
8
8
  color: var(--text-muted);
9
9
  }
10
10
 
11
- .empty-state-icon {
11
+ .oas-empty-icon {
12
12
  font-size: var(--font-ui-larger, 2rem);
13
13
  line-height: 1;
14
14
  color: var(--text-faint);
15
15
  }
16
16
 
17
- .empty-state-title {
17
+ .oas-empty-title {
18
18
  font-size: var(--font-ui-medium);
19
19
  font-weight: var(--font-semibold);
20
20
  color: var(--text-normal);
21
21
  }
22
22
 
23
- .empty-state-description {
23
+ .oas-empty-desc {
24
24
  font-size: var(--font-ui-small);
25
25
  max-width: 280px;
26
26
  }
27
27
 
28
- .empty-state-action {
28
+ .oas-empty-action {
29
29
  margin-top: var(--size-4-2);
30
30
  }