create-obsidian-arrow 0.1.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 +47 -0
- package/index.mjs +76 -0
- package/package.json +40 -0
- package/template/.github/workflows/ci.yml +36 -0
- package/template/.husky/pre-commit +2 -0
- package/template/AGENTS.md +90 -0
- package/template/LICENSE +21 -0
- package/template/README.md +116 -0
- package/template/_gitignore +8 -0
- package/template/biome.json +30 -0
- package/template/docs/prompts/agent-setup.md +94 -0
- package/template/docs/superpowers/specs/2026-06-29-obsidian-arrow-sandbox-design.md +206 -0
- package/template/index.html +19 -0
- package/template/package.json +43 -0
- package/template/pnpm-lock.yaml +1408 -0
- package/template/scripts/install-skills.mjs +47 -0
- package/template/scripts/lib/extract-app-css.mjs +60 -0
- package/template/scripts/pull-app-css.mjs +90 -0
- package/template/skills/arrow-js-obsidian-patterns/SKILL.md +94 -0
- package/template/skills/arrow-js-obsidian-templates/SKILL.md +101 -0
- package/template/skills/obsidian-arrow-sandbox/SKILL.md +64 -0
- package/template/src/components/SettingsPanel.ts +232 -0
- package/template/src/data/loadStatus.ts +17 -0
- package/template/src/examples/ExamplesIndex.ts +36 -0
- package/template/src/examples/registry.ts +26 -0
- package/template/src/main.ts +18 -0
- package/template/src/router/client.ts +85 -0
- package/template/src/router/routeToPage.ts +57 -0
- package/template/src/sandbox/frame.ts +35 -0
- package/template/src/sandbox/layout.ts +40 -0
- package/template/src/sandbox/sandbox.css +125 -0
- package/template/src/sandbox/shell.ts +15 -0
- package/template/src/sandbox/theme.ts +22 -0
- package/template/src/sandbox/toolbar.ts +32 -0
- package/template/test/extract-app-css.test.mjs +70 -0
- package/template/test/template-footguns.test.mjs +58 -0
- package/template/tsconfig.json +13 -0
- package/template/vite.config.ts +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# create-obsidian-arrow
|
|
2
|
+
|
|
3
|
+
Scaffold a new [Obsidian](https://obsidian.md/)-styled [Arrow.js](https://arrow-js.com/)
|
|
4
|
+
UI sandbox — a client-only Vite + TypeScript project that renders Arrow
|
|
5
|
+
components against Obsidian's real `app.css`, ready to port into a plugin.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm create obsidian-arrow@latest my-app
|
|
11
|
+
# or: pnpm create obsidian-arrow my-app
|
|
12
|
+
# or: npx create-obsidian-arrow my-app
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
cd my-app
|
|
19
|
+
pnpm install
|
|
20
|
+
pnpm pull-css # required — extract Obsidian's app.css from your local install (macOS auto-detect)
|
|
21
|
+
pnpm dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> `public/app.css` is git-ignored and never bundled — it's Obsidian's proprietary
|
|
25
|
+
> CSS, so each developer extracts it from their own install via `pnpm pull-css`.
|
|
26
|
+
|
|
27
|
+
Local dev of the initializer itself (from the sandbox repo, before publishing):
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
node create-obsidian-arrow/index.mjs ../my-app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What you get
|
|
34
|
+
|
|
35
|
+
A full sandbox: client-only Vite + TS, `@arrow-js/core` + `@arrow-js/framework`
|
|
36
|
+
(no SSR), `routeToPage` + Navigation-API router with an `/example` demo, Biome +
|
|
37
|
+
husky pre-commit + `node:test` + GitHub Actions CI, bundled agent skills, and the
|
|
38
|
+
`pull-css` script that extracts Obsidian's `app.css`.
|
|
39
|
+
|
|
40
|
+
## Maintaining the template
|
|
41
|
+
|
|
42
|
+
`template/` is **generated** from the sandbox repo so it never drifts. After
|
|
43
|
+
changing the sandbox, regenerate it from the repo root:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pnpm create:sync
|
|
47
|
+
```
|
package/index.mjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-obsidian-arrow — scaffold a new Obsidian-styled Arrow.js UI sandbox.
|
|
4
|
+
*
|
|
5
|
+
* pnpm create obsidian-arrow my-app # once published
|
|
6
|
+
* node create-obsidian-arrow/index.mjs my-app # locally, before publishing
|
|
7
|
+
*
|
|
8
|
+
* Copies the vendored template/ into <dir>, restores .gitignore (npm strips it
|
|
9
|
+
* from packages, so it's vendored as _gitignore), rewrites the project name,
|
|
10
|
+
* and runs `git init`. The template is a full, verified sandbox — see template/.
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const templateDir = path.join(here, "template");
|
|
19
|
+
|
|
20
|
+
function fail(message) {
|
|
21
|
+
console.error(`create-obsidian-arrow: ${message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const targetArg = process.argv[2];
|
|
26
|
+
if (!targetArg) {
|
|
27
|
+
fail("usage: create-obsidian-arrow <directory>");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const destRoot = path.resolve(process.cwd(), targetArg);
|
|
31
|
+
const appName = path.basename(destRoot);
|
|
32
|
+
|
|
33
|
+
if (fs.existsSync(destRoot) && fs.readdirSync(destRoot).length > 0) {
|
|
34
|
+
fail(`target "${targetArg}" already exists and is not empty.`);
|
|
35
|
+
}
|
|
36
|
+
if (!fs.existsSync(templateDir)) {
|
|
37
|
+
fail("template/ is missing — run scripts/sync-template.mjs to build it.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function copyDir(src, dest) {
|
|
41
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
42
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
43
|
+
const srcPath = path.join(src, entry.name);
|
|
44
|
+
// Vendored as _gitignore because npm omits .gitignore from published tarballs.
|
|
45
|
+
const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
|
|
46
|
+
const destPath = path.join(dest, destName);
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
copyDir(srcPath, destPath);
|
|
49
|
+
} else {
|
|
50
|
+
fs.copyFileSync(srcPath, destPath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
copyDir(templateDir, destRoot);
|
|
56
|
+
|
|
57
|
+
// Personalize the project name. Use a targeted replace (not JSON.parse +
|
|
58
|
+
// stringify) so the template's existing Biome formatting stays byte-identical
|
|
59
|
+
// and the fresh project passes `pnpm lint` out of the box.
|
|
60
|
+
const pkgPath = path.join(destRoot, "package.json");
|
|
61
|
+
const pkgText = fs.readFileSync(pkgPath, "utf8");
|
|
62
|
+
const renamed = pkgText.replace(
|
|
63
|
+
/("name":\s*)"[^"]*"/,
|
|
64
|
+
(_match, prefix) => `${prefix}${JSON.stringify(appName)}`
|
|
65
|
+
);
|
|
66
|
+
fs.writeFileSync(pkgPath, renamed);
|
|
67
|
+
|
|
68
|
+
// Initialize a git repo (best-effort; ignore if git is unavailable).
|
|
69
|
+
spawnSync("git", ["init", "-q"], { cwd: destRoot, stdio: "ignore" });
|
|
70
|
+
|
|
71
|
+
console.log(`\nScaffolded ${appName} in ${path.relative(process.cwd(), destRoot) || "."}\n`);
|
|
72
|
+
console.log("Next steps:");
|
|
73
|
+
console.log(` cd ${targetArg}`);
|
|
74
|
+
console.log(" pnpm install");
|
|
75
|
+
console.log(" pnpm pull-css # extract Obsidian's app.css (macOS auto-detect)");
|
|
76
|
+
console.log(" pnpm dev\n");
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-obsidian-arrow",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold an Obsidian-styled Arrow.js UI sandbox (pnpm create obsidian-arrow <dir>).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-obsidian-arrow": "index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.mjs",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"obsidian",
|
|
21
|
+
"obsidian-plugin",
|
|
22
|
+
"arrow-js",
|
|
23
|
+
"arrowjs",
|
|
24
|
+
"create",
|
|
25
|
+
"scaffold",
|
|
26
|
+
"template",
|
|
27
|
+
"ui",
|
|
28
|
+
"prototyping"
|
|
29
|
+
],
|
|
30
|
+
"author": "Kyle Brodeur",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"homepage": "https://github.com/kylebrodeur/obsidian-arrow-sandbox#readme",
|
|
33
|
+
"bugs": "https://github.com/kylebrodeur/obsidian-arrow-sandbox/issues",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/kylebrodeur/obsidian-arrow-sandbox.git",
|
|
37
|
+
"directory": "create-obsidian-arrow"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {}
|
|
40
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
check:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- uses: pnpm/action-setup@v4
|
|
15
|
+
with:
|
|
16
|
+
version: 10.14.0
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: 22
|
|
21
|
+
cache: pnpm
|
|
22
|
+
|
|
23
|
+
- name: Install
|
|
24
|
+
run: pnpm install --frozen-lockfile
|
|
25
|
+
|
|
26
|
+
- name: Lint + format check (Biome)
|
|
27
|
+
run: pnpm biome ci .
|
|
28
|
+
|
|
29
|
+
- name: Typecheck
|
|
30
|
+
run: pnpm typecheck
|
|
31
|
+
|
|
32
|
+
- name: Test
|
|
33
|
+
run: pnpm test
|
|
34
|
+
|
|
35
|
+
- name: Build
|
|
36
|
+
run: pnpm build
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Operating guide for AI agents working in **obsidian-arrow-sandbox** — a
|
|
4
|
+
client-only environment for prototyping [Arrow.js](https://arrow-js.com/) UI that
|
|
5
|
+
ports into an Obsidian plugin with near-zero refactoring.
|
|
6
|
+
|
|
7
|
+
Full design + rationale: [`docs/superpowers/specs`](docs/superpowers/specs/2026-06-29-obsidian-arrow-sandbox-design.md).
|
|
8
|
+
Deeper how-to skills: [`skills/`](skills/) (install via `npx skills`).
|
|
9
|
+
|
|
10
|
+
## What this is (and isn't)
|
|
11
|
+
|
|
12
|
+
- **Is:** a Vite + TypeScript sandbox. Components use `@arrow-js/core`
|
|
13
|
+
(`reactive`, `html`, `component`, `watch`) + `@arrow-js/framework` (`boundary`),
|
|
14
|
+
mounted with `template(container)` — the exact call an Obsidian
|
|
15
|
+
`ItemView.onOpen()` makes. Styling comes entirely from Obsidian's real
|
|
16
|
+
`app.css`, loaded under Obsidian body classes.
|
|
17
|
+
- **Isn't:** an SSR app. There is **no server and no hydration** — an Obsidian
|
|
18
|
+
plugin renders only in the Electron renderer, so `@arrow-js/ssr` and
|
|
19
|
+
`@arrow-js/hydrate` are intentionally absent. Do not add them.
|
|
20
|
+
|
|
21
|
+
## Run it
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pnpm install
|
|
25
|
+
pnpm pull-css # macOS-only auto-detect; else --path <asar|css> / OBSIDIAN_ASAR=<path>
|
|
26
|
+
pnpm dev # Vite + HMR
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`public/app.css` is **git-ignored** (Obsidian's proprietary CSS — not
|
|
30
|
+
redistributed); run `pnpm pull-css` once before `pnpm dev`.
|
|
31
|
+
|
|
32
|
+
## Arrow v1.0.6 footguns — READ BEFORE WRITING TEMPLATES
|
|
33
|
+
|
|
34
|
+
These are hard runtime errors, not style nits. They are encoded in CI
|
|
35
|
+
(`test/template-footguns.test.mjs`) where possible.
|
|
36
|
+
|
|
37
|
+
1. **No literal HTML comments inside `html\`\`` templates.** Arrow uses HTML
|
|
38
|
+
comments as expression-slot markers; a literal `<!-- … -->` makes the slot
|
|
39
|
+
count mismatch and throws `Invalid HTML position`. Use JS `//` comments
|
|
40
|
+
*outside* the template literal.
|
|
41
|
+
2. **An attribute expression must be the *entire* value.** Good:
|
|
42
|
+
`class="${() => '…'}"`. Broken: `class="static ${() => '…'}"` (partial values
|
|
43
|
+
aren't registered as placeholders → throws). Build the full string in one
|
|
44
|
+
expression.
|
|
45
|
+
3. **Reactive vs static.** `${data.x}` renders once at mount; `${() => data.x}`
|
|
46
|
+
is tracked and updates only that slot. Forgetting the `() =>` is the #1
|
|
47
|
+
"why isn't it updating" bug. Returning `false` from an attribute expression
|
|
48
|
+
**removes** the attribute (vs `""` which keeps it empty).
|
|
49
|
+
4. **`@event` handlers must type the param `Event`, not a narrowed subtype.**
|
|
50
|
+
`(e: MouseEvent) => …` fails to assign to Arrow's handler type (parameter
|
|
51
|
+
contravariance) → `TS2345`. Use `(e: Event) => …` and narrow inside; no-arg
|
|
52
|
+
handlers are fine. (Caught by `tsc`; footguns 1–2 are caught by
|
|
53
|
+
`test/template-footguns.test.mjs`, which also flags this for inline handlers.)
|
|
54
|
+
|
|
55
|
+
Other conventions: property binding via `.prop` (`.checked="${() => …}"`),
|
|
56
|
+
events via `@event` (`@click="${fn}"`), keyed lists via
|
|
57
|
+
`html\`…\`.key(id)`, async sections via `component(asyncFn, { fallback })` wrapped
|
|
58
|
+
in `boundary()`.
|
|
59
|
+
|
|
60
|
+
## CSS scoping
|
|
61
|
+
|
|
62
|
+
- Use Obsidian's own classes (`.setting-item`, `.clickable-icon`,
|
|
63
|
+
`.workspace-leaf`, `.vertical-tab-*`) and `var(--…)` tokens **first**. Add
|
|
64
|
+
custom CSS only where Obsidian has no class.
|
|
65
|
+
- Scope any custom rule under a container class + element type (e.g.
|
|
66
|
+
`.oas-frame button.oas-theme-toggle`) so it beats Obsidian's global
|
|
67
|
+
`button:not(.clickable-icon)` rule and never leaks. Sandbox-only chrome lives
|
|
68
|
+
in `src/sandbox/sandbox.css`; component styling stays on Obsidian classes.
|
|
69
|
+
|
|
70
|
+
## Verify before claiming done
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
pnpm typecheck # tsc --noEmit
|
|
74
|
+
pnpm test # node:test
|
|
75
|
+
pnpm lint # biome
|
|
76
|
+
pnpm check # all of the above (format + typecheck + test)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then confirm the actual render in the browser at the `pnpm dev` URL — check the
|
|
80
|
+
console is clean and the component looks like a real Obsidian pane. Do not claim
|
|
81
|
+
a component works on typecheck alone; Arrow's footguns only surface at render.
|
|
82
|
+
|
|
83
|
+
## Porting a component into the plugin
|
|
84
|
+
|
|
85
|
+
Components are framework-light and use only Obsidian classes/tokens, so porting
|
|
86
|
+
is mechanical: copy the component file into the plugin's view directory and
|
|
87
|
+
mount it from `ItemView.onOpen()` via `template(this.contentEl)`. If it uses
|
|
88
|
+
`boundary()`/async components, add `@arrow-js/framework` to the plugin and the
|
|
89
|
+
`import '@arrow-js/framework'` side-effect import. Strip any sandbox chrome
|
|
90
|
+
(`src/sandbox/*`) — that stays here.
|
package/template/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kyle Brodeur
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Obsidian Arrow Sandbox
|
|
2
|
+
|
|
3
|
+
A client-only prototyping environment for building [Arrow.js](https://arrow-js.com/)
|
|
4
|
+
UI that drops into an Obsidian plugin with near-zero refactoring. Components are
|
|
5
|
+
written with `@arrow-js/core` (+ `@arrow-js/framework` for async boundaries) and
|
|
6
|
+
styled entirely by Obsidian's real `app.css`, so what you see here is what you
|
|
7
|
+
get inside a plugin view.
|
|
8
|
+
|
|
9
|
+
See the design + decision record in
|
|
10
|
+
[`docs/superpowers/specs`](docs/superpowers/specs/2026-06-29-obsidian-arrow-sandbox-design.md).
|
|
11
|
+
|
|
12
|
+
## Scaffold a new project
|
|
13
|
+
|
|
14
|
+
Scaffold a fresh sandbox with the published initializer
|
|
15
|
+
([`create-obsidian-arrow`](create-obsidian-arrow/)):
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm create obsidian-arrow@latest my-app
|
|
19
|
+
# or: pnpm create obsidian-arrow my-app
|
|
20
|
+
# or: npx create-obsidian-arrow my-app
|
|
21
|
+
```
|
|
22
|
+
|
|
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.
|
|
26
|
+
|
|
27
|
+
> This repo (the full sandbox) is **not** published to npm — only the
|
|
28
|
+
> `create-obsidian-arrow/` initializer is. An agent-onboarding prompt lives in
|
|
29
|
+
> [`docs/prompts/agent-setup.md`](docs/prompts/agent-setup.md).
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pnpm install
|
|
35
|
+
pnpm pull-css # extract Obsidian's app.css from your local install (required)
|
|
36
|
+
pnpm dev # Vite dev server with HMR
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`pull-css` reads `app.css` out of `Obsidian.app/.../obsidian.asar` (macOS) and
|
|
40
|
+
writes `public/app.css`. Override the location with `--path <obsidian.asar|app.css>`
|
|
41
|
+
or `OBSIDIAN_ASAR=<path>`. **Run it once before `pnpm dev`** — it needs a local
|
|
42
|
+
Obsidian install.
|
|
43
|
+
|
|
44
|
+
> **Why it isn't committed:** `public/app.css` is **git-ignored**. It's
|
|
45
|
+
> Obsidian's proprietary stylesheet, so we don't redistribute it — each developer
|
|
46
|
+
> extracts it from their own licensed Obsidian install via `pnpm pull-css`.
|
|
47
|
+
|
|
48
|
+
> **Platform note:** automatic Obsidian discovery is currently **macOS-only** —
|
|
49
|
+
> `pull-css` knows where `Obsidian.app` lives on macOS. Windows and WSL paths are
|
|
50
|
+
> not auto-detected yet (planned). On those platforms, point the script at the
|
|
51
|
+
> file explicitly via `--path <obsidian.asar|app.css>` or `OBSIDIAN_ASAR=<path>`.
|
|
52
|
+
|
|
53
|
+
## Scripts
|
|
54
|
+
|
|
55
|
+
| Script | What it does |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `pnpm dev` | Vite dev server (client-only, HMR) |
|
|
58
|
+
| `pnpm build` / `pnpm preview` | Production build / preview |
|
|
59
|
+
| `pnpm typecheck` | `tsc --noEmit` |
|
|
60
|
+
| `pnpm test` | Node built-in test runner (`test/*.test.mjs`) |
|
|
61
|
+
| `pnpm lint` / `pnpm format` | Biome check / check-and-write |
|
|
62
|
+
| `pnpm check` | format + typecheck + test (local pre-flight) |
|
|
63
|
+
| `pnpm ci` | Biome CI + typecheck + test + build (what CI runs) |
|
|
64
|
+
| `pnpm pull-css` | refresh `public/app.css` from the local Obsidian install |
|
|
65
|
+
|
|
66
|
+
A husky `pre-commit` hook runs `lint-staged` (Biome on staged files) + a full
|
|
67
|
+
typecheck. CI (`.github/workflows/ci.yml`) runs the `ci` script on push/PR.
|
|
68
|
+
|
|
69
|
+
## Bundled agent skills
|
|
70
|
+
|
|
71
|
+
This repo ships [`skills`](https://github.com/vercel-labs/skills)-compatible skills
|
|
72
|
+
under [`skills/`](skills/) — it doubles as a local skill marketplace:
|
|
73
|
+
|
|
74
|
+
- `obsidian-arrow-sandbox` — running and using this sandbox, and porting to a plugin.
|
|
75
|
+
- `arrow-js-obsidian-templates` — Arrow v1.0.6 template rules + footguns.
|
|
76
|
+
- `arrow-js-obsidian-patterns` — integration patterns: icons (Lucide / data-icon
|
|
77
|
+
sweep), CSS scoping vs Obsidian globals, mount/unmount lifecycle, reactive state.
|
|
78
|
+
|
|
79
|
+
Install them into your agent via the interactive TUI:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
pnpm skills:install # = npx skills add . (pick skills in the TUI)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`postinstall` offers this automatically after `pnpm install`, but only in an
|
|
86
|
+
interactive terminal — it skips in CI / non-TTY (set `SKIP_SKILLS_INSTALL=1` to
|
|
87
|
+
opt out entirely). You can also add this repo from anywhere with
|
|
88
|
+
`npx skills add <git-url-or-path>`.
|
|
89
|
+
|
|
90
|
+
## Porting a component into the plugin
|
|
91
|
+
|
|
92
|
+
Components use only Obsidian classes + `var(--…)` tokens and mount via
|
|
93
|
+
`template(container)` — the same call an `ItemView.onOpen()` makes. To port:
|
|
94
|
+
copy the component file into the plugin's view directory and mount it from
|
|
95
|
+
`onOpen()`. If it uses `boundary()`/async components, add `@arrow-js/framework`
|
|
96
|
+
to the plugin and the `import '@arrow-js/framework'` side-effect import.
|
|
97
|
+
|
|
98
|
+
## Arrow v1.0.6 footguns (learned the hard way)
|
|
99
|
+
|
|
100
|
+
These are enforced/encoded so they don't regress:
|
|
101
|
+
|
|
102
|
+
1. **No literal HTML comments inside `html\`\`` templates.** Arrow uses HTML
|
|
103
|
+
comments as expression-slot markers; a literal `<!-- … -->` inflates the slot
|
|
104
|
+
count and throws `Invalid HTML position`. Use JS `//` comments outside the
|
|
105
|
+
template. Guarded by `test/template-footguns.test.mjs`.
|
|
106
|
+
2. **An attribute expression must be the *entire* value.** `class="${() => '…'}"`
|
|
107
|
+
works; `class="static ${() => '…'}"` (partial) does not register as a
|
|
108
|
+
placeholder and throws. Build the full string inside one expression.
|
|
109
|
+
3. **Reactive vs static:** `${data.x}` renders once; `${() => data.x}` is tracked
|
|
110
|
+
and updates only that slot. Returning `false` from an attribute expression
|
|
111
|
+
removes the attribute.
|
|
112
|
+
4. **`@event` handlers must type the param `Event`, not a narrowed subtype**
|
|
113
|
+
(`MouseEvent`, …). Parameter contravariance makes `(e: MouseEvent) => void`
|
|
114
|
+
fail to assign to Arrow's handler type (`TS2345`); use `(e: Event) => …` and
|
|
115
|
+
narrow inside. Caught by `tsc`, and the inline form is also guarded by
|
|
116
|
+
`test/template-footguns.test.mjs`.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
+
"organizeImports": {
|
|
4
|
+
"enabled": true
|
|
5
|
+
},
|
|
6
|
+
"linter": {
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"rules": {
|
|
9
|
+
"recommended": true,
|
|
10
|
+
"suspicious": {
|
|
11
|
+
"noExplicitAny": "off"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"formatter": {
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"indentStyle": "tab",
|
|
18
|
+
"lineWidth": 100
|
|
19
|
+
},
|
|
20
|
+
"javascript": {
|
|
21
|
+
"formatter": {
|
|
22
|
+
"quoteStyle": "double",
|
|
23
|
+
"trailingCommas": "es5",
|
|
24
|
+
"semicolons": "always"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": {
|
|
28
|
+
"ignore": ["dist", "node_modules", "public", "pnpm-lock.yaml", "create-obsidian-arrow/template"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Agent setup prompt
|
|
2
|
+
|
|
3
|
+
Copy everything in the block below and give it to a coding agent (Claude Code or
|
|
4
|
+
similar) to scaffold a new Obsidian Arrow sandbox and orient itself.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
Scaffold and set up an Obsidian Arrow Sandbox, then orient yourself on it.
|
|
10
|
+
|
|
11
|
+
WHAT IT IS
|
|
12
|
+
A client-only Vite + TypeScript sandbox for prototyping Obsidian plugin UI with
|
|
13
|
+
Arrow.js (@arrow-js/core + @arrow-js/framework), rendered against Obsidian's real
|
|
14
|
+
app.css so components look exactly as they will inside a plugin. Components mount
|
|
15
|
+
via `template(container)` — the same call an Obsidian ItemView.onOpen() makes —
|
|
16
|
+
so a finished component copy-pastes into a plugin with near-zero refactoring.
|
|
17
|
+
There is NO SSR/hydration (an Obsidian plugin renders entirely client-side); do
|
|
18
|
+
not add @arrow-js/ssr or @arrow-js/hydrate.
|
|
19
|
+
|
|
20
|
+
SCAFFOLD IT (use the published tool — pick one)
|
|
21
|
+
npm create obsidian-arrow@latest my-app
|
|
22
|
+
pnpm create obsidian-arrow my-app
|
|
23
|
+
npx create-obsidian-arrow my-app
|
|
24
|
+
|
|
25
|
+
Then:
|
|
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 the examples index, /example the demo.
|
|
34
|
+
# The toolbar slider/presets + edge drag handle resize the panel.
|
|
35
|
+
pnpm skills:install # install the bundled agent skills (npx skills add .) — pick
|
|
36
|
+
# them in the TUI; this is how you load the domain knowledge.
|
|
37
|
+
|
|
38
|
+
READ FIRST
|
|
39
|
+
- AGENTS.md (root) — operating guide: run, footguns, CSS scoping, verify, port.
|
|
40
|
+
- skills/*/SKILL.md — obsidian-arrow-sandbox (workflow), arrow-js-obsidian-
|
|
41
|
+
templates (template syntax + footguns), arrow-js-obsidian-patterns (icons via
|
|
42
|
+
Lucide/data-icon sweep, CSS scoping, mount/unmount lifecycle, reactive state).
|
|
43
|
+
- docs/superpowers/specs/ — design + decision record (why core+framework, no SSR).
|
|
44
|
+
|
|
45
|
+
ARROW v1.0.6 FOOTGUNS — do not relearn these the hard way:
|
|
46
|
+
1. NO literal HTML comments inside html`` templates — Arrow treats HTML comments
|
|
47
|
+
as expression-slot markers, so `<!-- … -->` throws "Invalid HTML position" at
|
|
48
|
+
render. Use JS // comments outside the template.
|
|
49
|
+
2. An attribute expression must be the ENTIRE value. `class="${() => '…'}"` works;
|
|
50
|
+
`class="static ${() => '…'}"` (partial) throws. Build the full string in one
|
|
51
|
+
expression. Returning `false` from a whole-value attribute expr removes the attr.
|
|
52
|
+
3. Reactive vs static: `${x}` renders once; `${() => x}` is tracked and updates
|
|
53
|
+
only that slot. Forgetting the `() =>` is the #1 "not updating" bug.
|
|
54
|
+
4. @event handlers must type the param as `Event`, not a narrowed subtype
|
|
55
|
+
(MouseEvent, …) — contravariance makes it fail to assign (TS2345). Use
|
|
56
|
+
`(e: Event) => …` and narrow inside; no-arg handlers are fine.
|
|
57
|
+
Footguns 1, 2, 4 are guarded by test/template-footguns.test.mjs + tsc.
|
|
58
|
+
|
|
59
|
+
CONVENTIONS
|
|
60
|
+
- Use Obsidian's own classes (.setting-item, .clickable-icon, .workspace-leaf,
|
|
61
|
+
.vertical-tab-*, .modal, .mod-cta) and var(--…) tokens first; add custom CSS
|
|
62
|
+
only when Obsidian has no class, scoped under a container class + element type
|
|
63
|
+
(e.g. `.my-panel button.my-action`) so it beats Obsidian's global button rule.
|
|
64
|
+
- Sandbox-only chrome lives in src/sandbox/* — it does NOT port to a plugin.
|
|
65
|
+
- Add a demo by exporting an Arrow component and registering it in
|
|
66
|
+
src/examples/registry.ts (it shows on the index and at its own route).
|
|
67
|
+
|
|
68
|
+
VERIFY BEFORE CLAIMING DONE
|
|
69
|
+
- `pnpm typecheck && pnpm test && pnpm lint` (or `pnpm run ci` for the full chain).
|
|
70
|
+
- Then open the `pnpm dev` URL and confirm the component renders like a real
|
|
71
|
+
Obsidian pane with a clean console — Arrow's footguns only surface at render,
|
|
72
|
+
so typecheck passing is not proof a component works.
|
|
73
|
+
|
|
74
|
+
PORTING TO A PLUGIN
|
|
75
|
+
Copy the component file into the plugin's view dir and mount from
|
|
76
|
+
ItemView.onOpen() via `template(this.contentEl)`. If it uses boundary()/async
|
|
77
|
+
components, add @arrow-js/framework to the plugin and the side-effect
|
|
78
|
+
`import '@arrow-js/framework'`. Leave src/sandbox/* behind.
|
|
79
|
+
|
|
80
|
+
Start by scaffolding, running setup steps, then read AGENTS.md and confirm
|
|
81
|
+
`pnpm dev` renders /example correctly. Report what you see.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Notes
|
|
87
|
+
|
|
88
|
+
- The scaffolder is published to npm as **`create-obsidian-arrow`**, which is why
|
|
89
|
+
all three of `npm create obsidian-arrow`, `pnpm create obsidian-arrow`, and
|
|
90
|
+
`npx create-obsidian-arrow` work (the `create-` prefix is what `*/create`
|
|
91
|
+
resolves to).
|
|
92
|
+
- `pnpm pull-css` is the one step that won't "just work" on a fresh machine —
|
|
93
|
+
it needs a local Obsidian install (macOS auto-detected). That's intentional:
|
|
94
|
+
Obsidian's `app.css` is proprietary and never committed/redistributed.
|