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
@@ -12,18 +12,14 @@ export interface EmptyStateOptions {
12
12
 
13
13
  export function EmptyState(options: EmptyStateOptions): ArrowExpression {
14
14
  return html`
15
- <div class="empty-state">
16
- ${options.icon ? html`<div class="empty-state-icon">${icon(options.icon)}</div>` : ""}
17
- <div class="empty-state-title">${options.title}</div>
18
- ${
19
- options.description
20
- ? html`<div class="empty-state-description">${options.description}</div>`
21
- : ""
22
- }
15
+ <div class="oas-empty-state">
16
+ ${options.icon ? html`<div class="oas-empty-icon">${icon(options.icon)}</div>` : ""}
17
+ <div class="oas-empty-title">${options.title}</div>
18
+ ${options.description ? html`<div class="oas-empty-desc">${options.description}</div>` : ""}
23
19
  ${
24
20
  options.action
25
21
  ? html`<button
26
- class="mod-cta empty-state-action"
22
+ class="mod-cta oas-empty-action"
27
23
  @click="${options.action.onClick}"
28
24
  >
29
25
  ${options.action.label}
@@ -303,3 +303,161 @@
303
303
  .oas-select-none {
304
304
  user-select: none;
305
305
  }
306
+
307
+ /* ── Badges ──────────────────────────────────────────────────── */
308
+ /* Status and label badges. Portable — copy with components into plugin. */
309
+ .oas-badge {
310
+ display: inline-flex;
311
+ align-items: center;
312
+ padding: 1px var(--size-4-1);
313
+ border-radius: var(--radius-s);
314
+ font-size: var(--font-ui-smaller);
315
+ font-weight: var(--font-medium);
316
+ vertical-align: middle;
317
+ margin-left: var(--size-4-1);
318
+ }
319
+ .oas-badge.is-live {
320
+ background: color-mix(in srgb, var(--text-success) 15%, var(--background-primary));
321
+ color: var(--text-success);
322
+ }
323
+ .oas-badge.is-draft {
324
+ background: var(--background-modifier-hover);
325
+ color: var(--text-faint);
326
+ }
327
+
328
+ /* ── Collapsible card ────────────────────────────────────────── */
329
+ /* Expandable section with left-border accent. Portable — copy with components. */
330
+ .oas-card {
331
+ border-bottom: 1px solid var(--background-modifier-border);
332
+ border-left: 3px solid var(--background-modifier-border-hover);
333
+ }
334
+ .oas-card.is-expanded {
335
+ border-left-color: var(--interactive-accent);
336
+ }
337
+ .oas-card-header {
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: space-between;
341
+ gap: var(--size-4-2);
342
+ padding: var(--size-4-3);
343
+ cursor: pointer;
344
+ font-size: var(--font-ui-small);
345
+ font-weight: var(--font-semibold);
346
+ color: var(--text-normal);
347
+ user-select: none;
348
+ }
349
+ .oas-card-header:hover {
350
+ background: var(--background-modifier-hover);
351
+ }
352
+ .oas-card-title {
353
+ flex: 1;
354
+ min-width: 0;
355
+ overflow: hidden;
356
+ text-overflow: ellipsis;
357
+ white-space: nowrap;
358
+ }
359
+ .oas-card-chevron {
360
+ flex-shrink: 0;
361
+ color: var(--text-faint);
362
+ transition: transform 0.15s;
363
+ font-size: var(--font-ui-medium);
364
+ line-height: 1;
365
+ }
366
+ .oas-card.is-expanded .oas-card-chevron {
367
+ transform: rotate(90deg);
368
+ }
369
+ .oas-card-body {
370
+ display: none;
371
+ border-top: 1px solid var(--background-modifier-border);
372
+ }
373
+ .oas-card.is-expanded .oas-card-body {
374
+ display: block;
375
+ }
376
+ .oas-card-desc {
377
+ margin: 0;
378
+ padding: var(--size-4-2) var(--size-4-3);
379
+ color: var(--text-muted);
380
+ font-size: var(--font-ui-smaller);
381
+ }
382
+ .oas-card-note {
383
+ margin: 0;
384
+ padding: 0 var(--size-4-3) var(--size-4-2);
385
+ color: var(--text-muted);
386
+ font-size: var(--font-ui-smaller);
387
+ }
388
+ .oas-card-children {
389
+ display: flex;
390
+ align-items: baseline;
391
+ flex-wrap: wrap;
392
+ gap: var(--size-4-1);
393
+ padding: var(--size-4-1) var(--size-4-3);
394
+ }
395
+ .oas-card-children-label {
396
+ color: var(--text-faint);
397
+ font-size: var(--font-ui-smaller);
398
+ }
399
+ .oas-card-actions {
400
+ padding: var(--size-4-2) var(--size-4-3);
401
+ }
402
+
403
+ /* ── View & component shells ─────────────────────────────────── */
404
+ /*
405
+ * Portable structural shells. Copy these with your view/component into the
406
+ * plugin — they rely only on Obsidian tokens and need no sandbox-specific CSS.
407
+ *
408
+ * View shell: apply oas-shell-view to the root element your view returns.
409
+ * The view-content div (Obsidian) is the scroll container; oas-shell-view
410
+ * fills it as a flex column so the body can grow and the footer stays pinned.
411
+ *
412
+ * Component shell: apply oas-shell-panel to a standalone component root for a
413
+ * card-style container with border, radius, and padding.
414
+ */
415
+ .oas-shell-view {
416
+ display: flex;
417
+ flex-direction: column;
418
+ height: 100%;
419
+ min-height: 0;
420
+ }
421
+ /* Header: pinned to the top, never shrinks, has a min-height floor but grows
422
+ * to fit taller content (e.g. a wrapping toolbar). */
423
+ .oas-shell-view-header {
424
+ flex: 0 0 auto;
425
+ min-height: var(--header-height);
426
+ }
427
+ /* Body: fills all space between header and footer; scrolls its own overflow. */
428
+ .oas-shell-view-body {
429
+ flex: 1 1 auto;
430
+ min-height: 0;
431
+ overflow-y: auto;
432
+ }
433
+ /* Footer: pinned to the bottom, never shrinks, has a min-height floor but
434
+ * expands to fit contained components (e.g. a composer that grows with input). */
435
+ .oas-shell-view-footer {
436
+ flex: 0 0 auto;
437
+ min-height: var(--header-height);
438
+ border-top: 1px solid var(--background-modifier-border);
439
+ }
440
+ .oas-shell-panel {
441
+ display: flex;
442
+ flex-direction: column;
443
+ gap: var(--size-4-2);
444
+ padding: var(--size-4-3);
445
+ background: var(--background-primary);
446
+ border: 1px solid var(--background-modifier-border);
447
+ border-radius: var(--radius-m);
448
+ }
449
+
450
+ /* ── Readable width (editor-style views) ─────────────────────────
451
+ * Reproduces Obsidian's "readable line length" for a *custom* view: caps
452
+ * content at the same --file-line-width token the markdown editor uses and
453
+ * centers it, with the editor's horizontal --file-margins. A custom ItemView
454
+ * does not inherit Obsidian's markdown editor chrome, so an editor-style
455
+ * (note/document) view wraps its content in this class to match. Portable: the
456
+ * tokens resolve against the active theme in both the sandbox and the plugin.
457
+ */
458
+ .oas-readable-width {
459
+ width: 100%;
460
+ max-width: var(--file-line-width);
461
+ margin-inline: auto;
462
+ padding-inline: var(--file-margins-x);
463
+ }
@@ -0,0 +1,42 @@
1
+ /* DiffViewer as a full-pane editor view. */
2
+
3
+ /* Label row above each editor pane. */
4
+ .oas-diff-view-labels {
5
+ display: flex;
6
+ border-bottom: 1px solid var(--background-modifier-border);
7
+ flex-shrink: 0;
8
+ }
9
+
10
+ .oas-diff-view-label {
11
+ flex: 1;
12
+ padding: var(--size-2-1) var(--size-4-3);
13
+ font-size: var(--font-ui-smaller);
14
+ color: var(--text-muted);
15
+ overflow: hidden;
16
+ text-overflow: ellipsis;
17
+ white-space: nowrap;
18
+ border-right: 1px solid var(--background-modifier-border);
19
+ }
20
+
21
+ .oas-diff-view-label:last-child {
22
+ border-right: none;
23
+ }
24
+
25
+ /* Body: fills remaining height. position: relative lets the diff viewer use inset: 0. */
26
+ .oas-diff-view-body {
27
+ flex: 1;
28
+ min-height: 0;
29
+ overflow: hidden;
30
+ position: relative;
31
+ }
32
+
33
+ /* Absolute positioning gives CM6 an explicit pixel height so height: 100% resolves
34
+ correctly on .cm-mergeView. Flex-resolved heights (flex: 1 + height: auto) don't
35
+ propagate to percentage-height grandchildren in all browsers. */
36
+ .oas-diff-view-body .oas-diff-viewer {
37
+ position: absolute;
38
+ inset: 0;
39
+ height: auto;
40
+ border: none;
41
+ border-radius: 0;
42
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * DiffViewerView — full-pane editor view wrapping the DiffViewer component.
3
+ *
4
+ * Shell anatomy:
5
+ * oas-shell-view-header pinned header: file labels + accept/reject actions
6
+ * oas-diff-view-labels per-pane filename labels (original | modified)
7
+ * oas-diff-view-body fills remaining height; CM6 handles internal scroll
8
+ *
9
+ * Duplicate this folder, rename DiffViewerView → YourDiffView, and wire in
10
+ * your real file content. The oas-shell-* classes handle the flex layout.
11
+ */
12
+
13
+ import { html } from "@arrow-js/core";
14
+ import type { ArrowExpression } from "@arrow-js/core";
15
+ import { DiffViewer } from "../../components/DiffViewer/DiffViewer";
16
+ import "./DiffViewerView.css";
17
+
18
+ export interface DiffViewerViewOptions {
19
+ original: string;
20
+ modified: string;
21
+ originalLabel?: string;
22
+ modifiedLabel?: string;
23
+ }
24
+
25
+ export function DiffViewerView({
26
+ original,
27
+ modified,
28
+ originalLabel = "Original",
29
+ modifiedLabel = "Modified",
30
+ }: DiffViewerViewOptions): ArrowExpression {
31
+ return html`
32
+ <div class="oas-shell-view">
33
+ <div class="oas-shell-view-header">
34
+ <div class="oas-flex oas-items-center oas-justify-between oas-px-3 oas-py-2-1">
35
+ <span class="oas-font-semibold oas-text-sm">Changes</span>
36
+ <div class="oas-flex oas-gap-2">
37
+ <button class="mod-cta" style="font-size: var(--font-ui-smaller);">Accept all</button>
38
+ <button class="mod-destructive" style="font-size: var(--font-ui-smaller);">Reject all</button>
39
+ </div>
40
+ </div>
41
+ </div>
42
+
43
+ <div class="oas-diff-view-labels">
44
+ <div class="oas-diff-view-label">${originalLabel}</div>
45
+ <div class="oas-diff-view-label">${modifiedLabel}</div>
46
+ </div>
47
+
48
+ <div class="oas-diff-view-body">
49
+ ${DiffViewer({ original, modified })}
50
+ </div>
51
+ </div>
52
+ `;
53
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * ExampleView — a fuller reference for a new Obsidian plugin view.
3
+ *
4
+ * Shows header + scrollable list body + pinned footer input, matching the
5
+ * ChatView shell pattern from pi-vault-mind. Duplicate this folder, rename
6
+ * ExampleView → YourView everywhere, and replace the stub content.
7
+ *
8
+ * Shell anatomy (oas-shell-* classes handle all layout — no flex CSS to write):
9
+ * oas-shell-view-header pinned top bar: title + actions
10
+ * oas-shell-view-body scrollable content area
11
+ * oas-shell-view-footer pinned bottom bar: input + action button
12
+ */
13
+
14
+ import { html } from "@arrow-js/core";
15
+ import type { ArrowExpression, ArrowTemplate } from "@arrow-js/core";
16
+ import { EmptyState } from "../../components/EmptyState/EmptyState";
17
+
18
+ /** Stub item type — replace with your real data model. */
19
+ interface ExampleItem {
20
+ id: string;
21
+ label: string;
22
+ note?: string;
23
+ }
24
+
25
+ const STUB_ITEMS: ExampleItem[] = [
26
+ { id: "1", label: "First item", note: "This is an example note." },
27
+ { id: "2", label: "Second item" },
28
+ { id: "3", label: "Third item", note: "Another note." },
29
+ { id: "4", label: "Fourth item" },
30
+ { id: "5", label: "Fifth item", note: "More content here." },
31
+ { id: "6", label: "Sixth item" },
32
+ { id: "7", label: "Seventh item", note: "Scroll to see this." },
33
+ { id: "8", label: "Eighth item" },
34
+ ];
35
+
36
+ function ItemRow(item: ExampleItem): ArrowTemplate {
37
+ return html`
38
+ <div class="setting-item">
39
+ <div class="setting-item-info">
40
+ <div class="setting-item-name">${item.label}</div>
41
+ ${item.note ? html`<div class="setting-item-description">${item.note}</div>` : ""}
42
+ </div>
43
+ <div class="setting-item-control">
44
+ <button class="clickable-icon" aria-label="Options">⋯</button>
45
+ </div>
46
+ </div>
47
+ `;
48
+ }
49
+
50
+ export function ExampleView(items: ExampleItem[] = STUB_ITEMS): ArrowExpression {
51
+ return html`
52
+ <div class="oas-shell-view">
53
+
54
+ <div class="oas-shell-view-header">
55
+ <div class="oas-flex oas-items-center oas-justify-between oas-px-3 oas-py-2-1">
56
+ <span class="oas-font-semibold oas-text-sm">Example View</span>
57
+ <div class="oas-flex oas-gap-1">
58
+ <button class="clickable-icon" aria-label="Filter">⊟</button>
59
+ <button class="mod-cta" style="font-size: var(--font-ui-smaller);">New item</button>
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="oas-shell-view-body">
65
+ ${
66
+ items.length === 0
67
+ ? EmptyState({
68
+ icon: "file",
69
+ title: "No items yet",
70
+ description: "Create your first item to get started.",
71
+ action: { label: "New item", onClick: () => {} },
72
+ })
73
+ : ""
74
+ }
75
+ ${items.map((item) => ItemRow(item))}
76
+ </div>
77
+
78
+ <div class="oas-shell-view-footer">
79
+ <div class="oas-flex oas-items-center oas-gap-2 oas-px-3 oas-py-2">
80
+ <input
81
+ type="text"
82
+ class="search-input"
83
+ placeholder="Quick add…"
84
+ style="flex: 1;"
85
+ />
86
+ <button class="mod-cta" aria-label="Add">+</button>
87
+ </div>
88
+ </div>
89
+
90
+ </div>
91
+ `;
92
+ }
@@ -0,0 +1,28 @@
1
+ import { html } from "@arrow-js/core";
2
+ import { defineStories } from "../../tools/viewer/stories";
3
+
4
+ export default defineStories({
5
+ title: "Structural / Card",
6
+ description:
7
+ "Standard panel container for standalone components. Use oas-shell-panel as the root wrapper when a component needs its own card frame.",
8
+ status: "live",
9
+ componentPath: "src/utilities.css",
10
+ variants: {
11
+ default: () => html`
12
+ <div class="oas-shell-panel" style="max-width: 320px;">
13
+ <div class="setting-item-name">Panel heading</div>
14
+ <div class="setting-item-description">Panel description or sub-content goes here.</div>
15
+ <div><button class="mod-cta">Primary action</button></div>
16
+ </div>
17
+ `,
18
+ with_badge: () => html`
19
+ <div class="oas-shell-panel" style="max-width: 320px;">
20
+ <div class="oas-flex oas-items-center oas-justify-between">
21
+ <div class="setting-item-name">Panel with badge</div>
22
+ <span class="oas-badge is-live">live</span>
23
+ </div>
24
+ <div class="setting-item-description">Panel using oas-badge alongside shell.</div>
25
+ </div>
26
+ `,
27
+ },
28
+ });
@@ -4,6 +4,7 @@ import { defineStories } from "../../tools/viewer/stories";
4
4
  export default defineStories({
5
5
  description: "Reusable empty state for any view — icon, title, description, optional action.",
6
6
  status: "live",
7
+ componentPath: "src/components/EmptyState/EmptyState.ts",
7
8
  variants: {
8
9
  default: () => EmptyState({ title: "Nothing here yet" }),
9
10
  "with description": () => {
@@ -4,6 +4,7 @@ import { defineStories } from "../../tools/viewer/stories";
4
4
  export default defineStories({
5
5
  description: "Loading indicator for async view content.",
6
6
  status: "live",
7
+ componentPath: "src/components/LoadingState.ts",
7
8
  variants: {
8
9
  default: () => LoadingState(),
9
10
  "with message": () => LoadingState("Loading sessions…"),
@@ -0,0 +1,50 @@
1
+ import { html, reactive } from "@arrow-js/core";
2
+ import { Toggle } from "../../src/components/SettingsPanel";
3
+ import { defineStories } from "../../tools/viewer/stories";
4
+
5
+ export default defineStories({
6
+ title: "Settings / Toggle",
7
+ description:
8
+ "Obsidian-native checkbox toggle used in settings rows. Pass a reactive getter and an onToggle handler.",
9
+ status: "live",
10
+ componentPath: "src/components/SettingsPanel.ts",
11
+ variants: {
12
+ on: () => {
13
+ const state = reactive({ enabled: true });
14
+ return Toggle(
15
+ () => state.enabled,
16
+ () => {
17
+ state.enabled = !state.enabled;
18
+ }
19
+ );
20
+ },
21
+ off: () => {
22
+ const state = reactive({ enabled: false });
23
+ return Toggle(
24
+ () => state.enabled,
25
+ () => {
26
+ state.enabled = !state.enabled;
27
+ }
28
+ );
29
+ },
30
+ in_setting_row: () => {
31
+ const state = reactive({ enabled: true });
32
+ return html`
33
+ <div class="setting-item">
34
+ <div class="setting-item-info">
35
+ <div class="setting-item-name">Enable feature</div>
36
+ <div class="setting-item-description">Toggles the feature on or off.</div>
37
+ </div>
38
+ <div class="setting-item-control">
39
+ ${Toggle(
40
+ () => state.enabled,
41
+ () => {
42
+ state.enabled = !state.enabled;
43
+ }
44
+ )}
45
+ </div>
46
+ </div>
47
+ `;
48
+ },
49
+ },
50
+ });
@@ -0,0 +1,94 @@
1
+ import { DiffViewerView } from "../../../src/views/DiffViewer/DiffViewerView";
2
+ import { defineStories } from "../../../tools/viewer/stories";
3
+
4
+ const ORIGINAL = `---
5
+ title: Meeting Notes
6
+ status: draft
7
+ ---
8
+
9
+ # Team Standup
10
+
11
+ Quick notes from today's standup.
12
+
13
+ ## Done
14
+
15
+ - Reviewed the PR for the search panel
16
+ - Fixed the token filter in the reference viewer
17
+
18
+ ## In Progress
19
+
20
+ - Arrow component for the diff viewer
21
+ - Documentation updates
22
+
23
+ ## Notes
24
+
25
+ See the project board for full task breakdown.
26
+ `;
27
+
28
+ const MODIFIED = `---
29
+ title: Meeting Notes
30
+ status: complete
31
+ tags: [standup, team]
32
+ ---
33
+
34
+ # Team Standup — 2026-07-04
35
+
36
+ Notes from today's standup.
37
+
38
+ ## Done
39
+
40
+ - Reviewed and merged the PR for the search panel
41
+ - Fixed the token filter in the reference viewer
42
+ - Added DiffViewer view to the sandbox
43
+
44
+ ## In Progress
45
+
46
+ - Documentation updates
47
+ - Editor pane integration
48
+
49
+ ## Notes
50
+
51
+ See the project board for full task breakdown.
52
+ Next standup: Thursday.
53
+ `;
54
+
55
+ const CODE_ORIGINAL = `function greet(name: string): string {
56
+ \treturn "Hello, " + name;
57
+ }
58
+
59
+ const result = greet("world");
60
+ console.log(result);
61
+ `;
62
+
63
+ const CODE_MODIFIED = `function greet(name: string, greeting = "Hello"): string {
64
+ \treturn \`\${greeting}, \${name}!\`;
65
+ }
66
+
67
+ const result = greet("world", "Hi");
68
+ console.log(result);
69
+ `;
70
+
71
+ export default defineStories({
72
+ title: "Editor / DiffViewer",
73
+ description:
74
+ "Full-pane diff editor using CodeMirror 6 MergeView. Header shows file labels and accept/reject actions. Body fills the remaining height — CM6 manages internal scrolling.",
75
+ status: "live",
76
+ kind: "view",
77
+ componentPath: "src/views/DiffViewer/DiffViewerView.ts",
78
+ variants: {
79
+ markdown: () =>
80
+ DiffViewerView({
81
+ original: ORIGINAL,
82
+ modified: MODIFIED,
83
+ originalLabel: "notes-draft.md",
84
+ modifiedLabel: "notes-revised.md",
85
+ }),
86
+ code: () =>
87
+ DiffViewerView({
88
+ original: CODE_ORIGINAL,
89
+ modified: CODE_MODIFIED,
90
+ originalLabel: "utils.ts (original)",
91
+ modifiedLabel: "utils.ts (modified)",
92
+ }),
93
+ },
94
+ });
@@ -0,0 +1,55 @@
1
+ import { html } from "@arrow-js/core";
2
+ import { defineStories } from "../../tools/viewer/stories";
3
+
4
+ export default defineStories({
5
+ title: "Views / Editor View",
6
+ description:
7
+ "Note/document view at readable line width. Full-bleed header + footer with the body content capped at --file-line-width via oas-readable-width. Copy this for any editor- or reader-style view; scaffold with `pnpm create:view <Name> --editor`.",
8
+ status: "live",
9
+ kind: "view",
10
+ surface: "editor",
11
+ componentPath: "src/utilities.css",
12
+ variants: {
13
+ default: () => html`
14
+ <div class="oas-shell-view" style="height: 420px; border: 1px dashed var(--background-modifier-border);">
15
+ <div class="oas-shell-view-header" style="padding: var(--size-4-2) var(--size-4-3); background: var(--background-secondary); border-bottom: 1px solid var(--background-modifier-border);">
16
+ Editor header (title, actions) — spans the full pane
17
+ </div>
18
+ <div class="oas-shell-view-body">
19
+ <div class="oas-readable-width">
20
+ <h1>Document title</h1>
21
+ <p>
22
+ The body content is capped at Obsidian's readable line width
23
+ (<code>--file-line-width</code>, 700px) and centered — the same
24
+ measure the markdown editor uses. Header and footer stay full-bleed.
25
+ </p>
26
+ ${Array.from({ length: 4 }, (_, i) =>
27
+ html`<p>
28
+ Paragraph ${i + 1}. Long-form prose stays comfortable to read
29
+ because the line length never exceeds the readable measure, no
30
+ matter how wide the pane is dragged. Resize the panel to see the
31
+ column stay put while the chrome fills the width.
32
+ </p>`.key(i)
33
+ )}
34
+ </div>
35
+ </div>
36
+ <div class="oas-shell-view-footer" style="padding: var(--size-4-2) var(--size-4-3);">
37
+ Editor footer (word count, status) — spans the full pane
38
+ </div>
39
+ </div>
40
+ `,
41
+ body_only: () => html`
42
+ <div class="oas-shell-view" style="height: 420px; border: 1px dashed var(--background-modifier-border);">
43
+ <div class="oas-shell-view-body">
44
+ <div class="oas-readable-width">
45
+ <h1>Reader view</h1>
46
+ <p>
47
+ Body only — no header or footer. Content is centered at readable
48
+ line width, ideal for a distraction-free reader or preview pane.
49
+ </p>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ `,
54
+ },
55
+ });
@@ -0,0 +1,15 @@
1
+ import { ExampleView } from "../../../src/views/ExampleView/ExampleView";
2
+ import { defineStories } from "../../../tools/viewer/stories";
3
+
4
+ export default defineStories({
5
+ title: "Example / ExampleView",
6
+ description:
7
+ "Reference shell for a new view. Duplicate this folder, rename ExampleView → YourView, and replace the stub content. The oas-shell-* classes handle all layout.",
8
+ status: "live",
9
+ kind: "view",
10
+ componentPath: "src/views/ExampleView/ExampleView.ts",
11
+ variants: {
12
+ with_items: () => ExampleView(),
13
+ empty: () => ExampleView([]),
14
+ },
15
+ });