create-obsidian-arrow 0.2.2 → 0.4.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 +10 -7
- package/index.mjs +29 -10
- package/package.json +1 -1
- package/template/AGENTS.md +31 -5
- package/template/README.md +47 -5
- package/template/_gitignore +3 -0
- package/template/docs/prompts/agent-setup.md +26 -13
- package/template/docs/prompts/update-existing.md +13 -9
- package/template/docs/workflow.md +21 -8
- package/template/package.json +6 -2
- package/template/pnpm-lock.yaml +197 -0
- package/template/src/components/DiffViewer.ts +42 -0
- package/template/src/components/SettingsPanel.ts +1 -1
- package/template/src/main.ts +4 -3
- package/template/src/utilities.css +205 -0
- package/template/stories/DiffViewer.stories.ts +75 -0
- package/template/stories/SettingsPanel.stories.ts +11 -0
- package/template/stories/Toggle.stories.ts +28 -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 → tools}/router/client.ts +15 -2
- package/template/tools/router/routeToPage.ts +104 -0
- package/template/{src → tools}/sandbox/home.ts +55 -26
- package/template/tools/sandbox/sandbox.css +474 -0
- package/template/tools/viewer/ClassesPage.ts +37 -0
- package/template/tools/viewer/ComponentsIndex.ts +56 -0
- package/template/tools/viewer/StoryPage.ts +73 -0
- package/template/tools/viewer/TokensPage.ts +82 -0
- package/template/tools/viewer/derive.ts +91 -0
- package/template/tools/viewer/discovery.ts +64 -0
- package/template/tools/viewer/obsidian-classes.ts +269 -0
- package/template/tools/viewer/sidebar.ts +55 -0
- package/template/tools/viewer/stories.ts +83 -0
- package/template/tools/viewer/token-utils.ts +84 -0
- package/template/tools/viewer/tokens.ts +30 -0
- package/template/src/examples/ExamplesIndex.ts +0 -36
- package/template/src/examples/registry.ts +0 -26
- package/template/src/router/routeToPage.ts +0 -57
- package/template/src/sandbox/sandbox.css +0 -130
- /package/template/{src → tools}/sandbox/frame.ts +0 -0
- /package/template/{src → tools}/sandbox/layout.ts +0 -0
- /package/template/{src → tools}/sandbox/shell.ts +0 -0
- /package/template/{src → tools}/sandbox/theme.ts +0 -0
- /package/template/{src → tools}/sandbox/toolbar.ts +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
classifyValue,
|
|
5
|
+
filterTokens,
|
|
6
|
+
groupTokens,
|
|
7
|
+
parseCustomProps,
|
|
8
|
+
} from "../tools/viewer/token-utils.ts";
|
|
9
|
+
|
|
10
|
+
test("parseCustomProps extracts custom property declarations from rule text", () => {
|
|
11
|
+
const css = "body.theme-dark { --text-accent: #a288ff; --size-4-2: 8px; color: red; }";
|
|
12
|
+
assert.deepEqual(parseCustomProps(css), [
|
|
13
|
+
{ name: "--text-accent", value: "#a288ff" },
|
|
14
|
+
{ name: "--size-4-2", value: "8px" },
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("parseCustomProps does not false-match var() references in values", () => {
|
|
19
|
+
const css = ".x { --a: var(--b); background: var(--c); }";
|
|
20
|
+
assert.deepEqual(parseCustomProps(css), [{ name: "--a", value: "var(--b)" }]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("groupTokens groups by prefix in stable order, dedupes last-wins, sorts names", () => {
|
|
24
|
+
const groups = groupTokens([
|
|
25
|
+
{ name: "--zeta-thing", value: "1" },
|
|
26
|
+
{ name: "--size-4-4", value: "16px" },
|
|
27
|
+
{ name: "--size-4-2", value: "8px" },
|
|
28
|
+
{ name: "--size-4-2", value: "9px" },
|
|
29
|
+
{ name: "--color-red", value: "#e11" },
|
|
30
|
+
]);
|
|
31
|
+
assert.deepEqual(
|
|
32
|
+
groups.map((g) => g.label),
|
|
33
|
+
["Size & spacing", "Colors", "Other"]
|
|
34
|
+
);
|
|
35
|
+
const size = groups[0];
|
|
36
|
+
assert.deepEqual(size.tokens, [
|
|
37
|
+
{ name: "--size-4-2", value: "9px" },
|
|
38
|
+
{ name: "--size-4-4", value: "16px" },
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("classifyValue detects colors, lengths, other", () => {
|
|
43
|
+
assert.equal(classifyValue("#fff"), "color");
|
|
44
|
+
assert.equal(classifyValue("#a288ffcc"), "color");
|
|
45
|
+
assert.equal(classifyValue("rgba(0, 0, 0, 0.3)"), "color");
|
|
46
|
+
assert.equal(classifyValue("hsl(254, 80%, 68%)"), "color");
|
|
47
|
+
assert.equal(classifyValue("16px"), "length");
|
|
48
|
+
assert.equal(classifyValue("0.875em"), "length");
|
|
49
|
+
assert.equal(classifyValue("inherit"), "other");
|
|
50
|
+
assert.equal(classifyValue("var(--x)"), "other");
|
|
51
|
+
assert.equal(classifyValue("100%"), "length");
|
|
52
|
+
assert.equal(classifyValue("100vw"), "length");
|
|
53
|
+
assert.equal(classifyValue("0"), "length");
|
|
54
|
+
assert.equal(classifyValue("2pt"), "length");
|
|
55
|
+
assert.equal(classifyValue("#abc12"), "other");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("filterTokens is a case-insensitive substring match; blank query passes all", () => {
|
|
59
|
+
const decls = [
|
|
60
|
+
{ name: "--text-accent", value: "x" },
|
|
61
|
+
{ name: "--size-4-2", value: "y" },
|
|
62
|
+
];
|
|
63
|
+
assert.deepEqual(filterTokens(decls, "ACCENT"), [decls[0]]);
|
|
64
|
+
assert.deepEqual(filterTokens(decls, " "), decls);
|
|
65
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
buildStoryTree,
|
|
5
|
+
kebabCase,
|
|
6
|
+
storyMetaFromGlobKey,
|
|
7
|
+
titleFromSlug,
|
|
8
|
+
} from "../tools/viewer/derive.ts";
|
|
9
|
+
|
|
10
|
+
test("kebabCase converts PascalCase and spaces/underscores", () => {
|
|
11
|
+
assert.equal(kebabCase("SettingsPanel"), "settings-panel");
|
|
12
|
+
assert.equal(kebabCase("Toggle"), "toggle");
|
|
13
|
+
assert.equal(kebabCase("message_feed thing"), "message-feed-thing");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("titleFromSlug start-cases", () => {
|
|
17
|
+
assert.equal(titleFromSlug("settings-panel"), "Settings Panel");
|
|
18
|
+
assert.equal(titleFromSlug("toggle"), "Toggle");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("storyMetaFromGlobKey derives slug + repo-relative paths", () => {
|
|
22
|
+
const meta = storyMetaFromGlobKey("../../stories/SettingsPanel.stories.ts");
|
|
23
|
+
assert.equal(meta.slug, "settings-panel");
|
|
24
|
+
assert.equal(meta.storiesPath, "stories/SettingsPanel.stories.ts");
|
|
25
|
+
assert.equal(meta.componentPath, "src/components/SettingsPanel.ts");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("storyMetaFromGlobKey handles nested directories", () => {
|
|
29
|
+
const meta = storyMetaFromGlobKey("../../stories/chat/MessageFeed.stories.ts");
|
|
30
|
+
assert.equal(meta.slug, "message-feed");
|
|
31
|
+
assert.equal(meta.storiesPath, "stories/chat/MessageFeed.stories.ts");
|
|
32
|
+
assert.equal(meta.componentPath, "src/components/chat/MessageFeed.ts");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("buildStoryTree nests children under parents", () => {
|
|
36
|
+
const { roots, unknownChildren } = buildStoryTree([
|
|
37
|
+
{ slug: "settings-panel", children: ["toggle"] },
|
|
38
|
+
{ slug: "toggle" },
|
|
39
|
+
]);
|
|
40
|
+
assert.equal(unknownChildren.length, 0);
|
|
41
|
+
assert.equal(roots.length, 1);
|
|
42
|
+
assert.equal(roots[0].slug, "settings-panel");
|
|
43
|
+
assert.equal(roots[0].children[0].slug, "toggle");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("buildStoryTree reports unknown children and keeps them out of the tree", () => {
|
|
47
|
+
const { roots, unknownChildren } = buildStoryTree([{ slug: "a", children: ["ghost"] }]);
|
|
48
|
+
assert.deepEqual(unknownChildren, [{ parent: "a", child: "ghost" }]);
|
|
49
|
+
assert.equal(roots[0].children.length, 0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("buildStoryTree guards cycles: mutual refs fall back to flat roots", () => {
|
|
53
|
+
const { roots } = buildStoryTree([
|
|
54
|
+
{ slug: "a", children: ["b"] },
|
|
55
|
+
{ slug: "b", children: ["a"] },
|
|
56
|
+
]);
|
|
57
|
+
// both are referenced, so no natural roots — fall back to all items flat
|
|
58
|
+
assert.deepEqual(
|
|
59
|
+
roots.map((r) => r.slug),
|
|
60
|
+
["a", "b"]
|
|
61
|
+
);
|
|
62
|
+
// and recursion must not loop forever: nested child stops at the cycle
|
|
63
|
+
assert.equal(roots[0].children[0].slug, "b");
|
|
64
|
+
assert.equal(roots[0].children[0].children.length, 0);
|
|
65
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { defineStories, normalizeVariants, validateStoryDef } from "../tools/viewer/stories.ts";
|
|
4
|
+
|
|
5
|
+
test("defineStories is an identity that preserves the def", () => {
|
|
6
|
+
const def = { variants: { default: () => "x" } };
|
|
7
|
+
assert.equal(defineStories(def), def);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("validateStoryDef accepts a minimal valid def", () => {
|
|
11
|
+
assert.deepEqual(validateStoryDef({ variants: { default: () => "x" } }), { ok: true });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("validateStoryDef accepts full metadata incl. componentPath override", () => {
|
|
15
|
+
const def = {
|
|
16
|
+
title: "Toggle",
|
|
17
|
+
description: "d",
|
|
18
|
+
componentPath: "src/components/SettingsPanel.ts",
|
|
19
|
+
variants: { on: { render: () => "x", notes: "n" } },
|
|
20
|
+
children: ["other"],
|
|
21
|
+
};
|
|
22
|
+
assert.deepEqual(validateStoryDef(def), { ok: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("validateStoryDef rejects non-objects and missing/empty variants", () => {
|
|
26
|
+
assert.equal(validateStoryDef(undefined).ok, false);
|
|
27
|
+
assert.equal(validateStoryDef(null).ok, false);
|
|
28
|
+
assert.equal(validateStoryDef({}).ok, false);
|
|
29
|
+
assert.equal(validateStoryDef({ variants: {} }).ok, false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("validateStoryDef rejects bad variant values and bad children", () => {
|
|
33
|
+
assert.equal(validateStoryDef({ variants: { a: 42 } }).ok, false);
|
|
34
|
+
assert.equal(validateStoryDef({ variants: { a: { notes: "no render" } } }).ok, false);
|
|
35
|
+
assert.equal(validateStoryDef({ variants: { a: () => "x" }, children: [1] }).ok, false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("normalizeVariants wraps bare functions and passes objects through", () => {
|
|
39
|
+
const fn = () => "x";
|
|
40
|
+
const out = normalizeVariants({ bare: fn, full: { render: fn, notes: "n" } });
|
|
41
|
+
assert.deepEqual(out.bare, { render: fn });
|
|
42
|
+
assert.equal(out.full.render, fn);
|
|
43
|
+
assert.equal(out.full.notes, "n");
|
|
44
|
+
});
|
|
@@ -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);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { html } from "@arrow-js/core";
|
|
2
|
+
import type { ArrowExpression } from "@arrow-js/core";
|
|
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";
|
|
10
|
+
|
|
11
|
+
/**
|
|
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.
|
|
16
|
+
*/
|
|
17
|
+
export interface Page {
|
|
18
|
+
status: number;
|
|
19
|
+
title: string;
|
|
20
|
+
view: ArrowExpression;
|
|
21
|
+
sidebar?: ArrowExpression;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Redirect {
|
|
25
|
+
redirect: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const APP_NAME = "Arrow Sandbox";
|
|
29
|
+
|
|
30
|
+
function notFound(pathname: string): Page {
|
|
31
|
+
return {
|
|
32
|
+
status: 404,
|
|
33
|
+
title: `Not found · ${APP_NAME}`,
|
|
34
|
+
view: html`
|
|
35
|
+
<div class="oas-settings">
|
|
36
|
+
<div class="setting-item setting-item-heading">
|
|
37
|
+
<div class="setting-item-info">
|
|
38
|
+
<div class="setting-item-name">Not found</div>
|
|
39
|
+
<div class="setting-item-description">
|
|
40
|
+
No route for <code>${pathname}</code>. <a href="/">Back home</a>.
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
`,
|
|
46
|
+
};
|
|
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
|
});
|