@statewalker/wiki.view.react 0.1.1 → 0.1.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.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @statewalker/wiki.view.react
2
+
3
+ ## What it is
4
+
5
+ A React renderer fragment for wikis built with `@statewalker/wiki.core`. It adds one dock panel, the wiki site browser: it opens a generated thematic site (`<project>/sites/<slug>/`) and shows a table-of-contents sidebar, breadcrumbs, previous/next links, and the selected page rendered as markdown. It handles the `wiki:open-site` command, so any logic code can open a site by dispatching `OpenWikiSiteCommand`.
6
+
7
+ ## Why it exists
8
+
9
+ `@statewalker/wiki.core` generates sites (`WikiSite`) but contains no React, so it can run in Node, in a CLI, or in a worker. This package holds the wiki's React side and plugs it into the shell the same way other renderer fragments do: a json-render catalog, a command handler, and slot contributions. Apps that do not show wiki sites do not pay for React code in the wiki core.
10
+
11
+ ## How to use
12
+
13
+ ### Install
14
+
15
+ ```sh
16
+ pnpm add @statewalker/wiki.view.react @statewalker/wiki.core
17
+ ```
18
+
19
+ Peer dependencies: `react` and `react-dom` (^19.3.0).
20
+
21
+ ### Entry points
22
+
23
+ | Subpath | What it gives |
24
+ | --- | --- |
25
+ | `@statewalker/wiki.view.react` | Catalog and id helpers: `wikiSiteCatalog`, `WIKI_SITE_CATALOG_ID` (`"wiki-site"`), `makeWikiSiteSpec`, `wikiSitePanelId`, `wikiSiteSpecId`, plus `OpenWikiSiteCommand` / `OpenWikiSitePayload` re-exported from `@statewalker/wiki.core`. |
26
+ | `@statewalker/wiki.view.react/fragment` | Default export: the renderer-fragment init `(ctx) => cleanup`. |
27
+ | `@statewalker/wiki.view.react/styles` | `src/styles.css`: Tailwind v4 `@source` directives so the host's Tailwind build picks up this package's classes. |
28
+
29
+ The code runs in the browser, inside a shell that has booted the workbench substrate.
30
+
31
+ ### Register the fragment
32
+
33
+ Run the fragment init as a renderer, after the logic fragments, and import the styles once:
34
+
35
+ ```ts
36
+ import initWikiReact from "@statewalker/wiki.view.react/fragment";
37
+ import "@statewalker/wiki.view.react/styles";
38
+
39
+ // e.g. with @statewalker/app-shell:
40
+ bootShell({ renderers: [initWikiReact] });
41
+ ```
42
+
43
+ The init reads these adapters from the workspace in `ctx`: `Slots`, `SpecStore`, `Commands`, `LayoutStore`. If the substrate has not registered one of them, it fails with `No adapter registered for …`.
44
+
45
+ ## Examples
46
+
47
+ ### Open a generated site from logic code
48
+
49
+ ```ts
50
+ import { Commands } from "@statewalker/shared-commands";
51
+ import { OpenWikiSiteCommand } from "@statewalker/wiki.core";
52
+
53
+ await workspace
54
+ .requireAdapter(Commands)
55
+ .call(OpenWikiSiteCommand, { project: "notes", slug: "overview" }).promise;
56
+ ```
57
+
58
+ The handler creates the spec `spec:wiki-site:notes/overview` if needed and shows it in the dock panel `wiki-site:notes/overview` (via `ShowDockPanelCommand`), titled with the slug.
59
+
60
+ ### Build the ids and spec yourself
61
+
62
+ ```ts
63
+ import {
64
+ makeWikiSiteSpec,
65
+ wikiSitePanelId,
66
+ wikiSiteSpecId,
67
+ } from "@statewalker/wiki.view.react";
68
+
69
+ wikiSitePanelId("notes", "overview"); // "wiki-site:notes/overview"
70
+ wikiSiteSpecId("notes", "overview"); // "spec:wiki-site:notes/overview"
71
+ makeWikiSiteSpec("notes", "overview"); // one WikiSiteView element with { project, slug }
72
+ ```
73
+
74
+ ## Internals
75
+
76
+ ### Why ids are deterministic
77
+
78
+ The panel id and spec id are derived from `(project, slug)`. Opening the same site twice focuses the existing tab instead of opening a second one. On workspace load, the fragment walks the saved dock layout for panels with the `wiki-site:` prefix and recreates their specs from the id alone, so restored tabs render without any extra stored state.
79
+
80
+ ### What the panel reads, and what it shows when something is missing
81
+
82
+ The view loads `/<project>/sites/<slug>/site.json` and the page files next to it through the `files:load-file` command (`LoadFileCommand`), so it works on any `FilesApi` the workspace uses. The states a user sees:
83
+
84
+ - `Loading site…` / `Loading…` while files load.
85
+ - `No site generated yet.` when `site.json` is missing or lists no pages. Generate the site first (`WikiSite.generate`, or the `generate_site` tool from `createWikiSiteTools`).
86
+ - `Failed to load site: <message>` when `site.json` is not valid JSON.
87
+ - `Failed to load page: <message>` when a page file cannot be read.
88
+
89
+ ### What it depends on, and why
90
+
91
+ - `@statewalker/wiki.core`: the `OpenWikiSiteCommand` contract and the `SiteManifest` type.
92
+ - `@json-render/core`, `zod`, `@statewalker/render.core`, `@statewalker/render.view.react`: the catalog, spec store, layout restore, and React registry.
93
+ - `@statewalker/shell.core`, `@statewalker/shell.view.react`: `ShowDockPanelCommand` and the dock tab icon slot.
94
+ - `@statewalker/shared-commands`, `@statewalker/shared-registry`, `@statewalker/shared-slots`, `@statewalker/workspace.core`: command bus, cleanup registry, slots, workspace access.
95
+ - `@statewalker/mime.view.markdown`, `@statewalker/ui.view.react`, `@statewalker/ui.view.shadcn`, `lucide-react`: markdown rendering, workspace hook, buttons, the tab icon.
96
+
97
+ ## License
98
+
99
+ MIT. See [LICENSE](../../LICENSE).
@@ -1 +1 @@
1
- {"version":3,"file":"site-data.js","names":[],"sources":["../../src/internal/site-data.ts"],"sourcesContent":["import { Commands } from \"@statewalker/shared-commands\";\nimport { useAppWorkspace } from \"@statewalker/ui.view.react\";\nimport type { SiteManifest } from \"@statewalker/wiki.core\";\nimport { LoadFileCommand } from \"@statewalker/workspace.core\";\nimport { useEffect, useState } from \"react\";\n\n/** Workspace-absolute path of a site's `site.json` manifest. */\nexport function siteJsonPath(project: string, slug: string): string {\n return `/${project}/sites/${slug}/site.json`;\n}\n\n/** Workspace-absolute path of a generated page (manifest `page.path` is a file name). */\nexport function pageUri(project: string, slug: string, pagePath: string): string {\n return `/${project}/sites/${slug}/${pagePath}`;\n}\n\nexport type ManifestState =\n | { status: \"loading\" }\n | { status: \"ready\"; manifest: SiteManifest }\n | { status: \"empty\" }\n | { status: \"error\"; message: string };\n\n/** Load a site's manifest via `files:load-file`. A missing file resolves to `empty`. */\nexport function useSiteManifest(project: string, slug: string): ManifestState {\n const workspace = useAppWorkspace();\n const commands = workspace.requireAdapter(Commands);\n const [state, setState] = useState<ManifestState>({ status: \"loading\" });\n\n useEffect(() => {\n let cancelled = false;\n setState({ status: \"loading\" });\n commands\n .call(LoadFileCommand, { path: siteJsonPath(project, slug) })\n .promise.then((loaded) => {\n if (cancelled) return;\n try {\n const manifest = JSON.parse(new TextDecoder().decode(loaded.bytes)) as SiteManifest;\n setState({ status: \"ready\", manifest });\n } catch (error) {\n setState({\n status: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n })\n .catch(() => {\n if (!cancelled) setState({ status: \"empty\" });\n });\n return () => {\n cancelled = true;\n };\n }, [project, slug, commands]);\n\n return state;\n}\n\nexport type FileTextState =\n | { status: \"loading\" }\n | { status: \"ready\"; text: string }\n | { status: \"error\"; message: string };\n\n/** Load a markdown page's text via `files:load-file`. */\nexport function useFileText(uri: string): FileTextState {\n const workspace = useAppWorkspace();\n const commands = workspace.requireAdapter(Commands);\n const [state, setState] = useState<FileTextState>({ status: \"loading\" });\n\n useEffect(() => {\n let cancelled = false;\n setState({ status: \"loading\" });\n commands\n .call(LoadFileCommand, { path: uri.replace(/^file:\\/\\//, \"\") })\n .promise.then((loaded) => {\n if (cancelled) return;\n setState({ status: \"ready\", text: new TextDecoder().decode(loaded.bytes) });\n })\n .catch((error: unknown) => {\n if (cancelled) return;\n setState({\n status: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n });\n return () => {\n cancelled = true;\n };\n }, [uri, commands]);\n\n return state;\n}\n"],"mappings":";;;;;;AAOA,SAAgB,aAAa,SAAiB,MAAsB;CAClE,OAAO,IAAI,QAAQ,SAAS,KAAK;AACnC;;AAGA,SAAgB,QAAQ,SAAiB,MAAc,UAA0B;CAC/E,OAAO,IAAI,QAAQ,SAAS,KAAK,GAAG;AACtC;;AASA,SAAgB,gBAAgB,SAAiB,MAA6B;CAE5E,MAAM,WADY,gBACO,CAAC,CAAC,eAAe,QAAQ;CAClD,MAAM,CAAC,OAAO,YAAY,SAAwB,EAAE,QAAQ,UAAU,CAAC;CAEvE,gBAAgB;EACd,IAAI,YAAY;EAChB,SAAS,EAAE,QAAQ,UAAU,CAAC;EAC9B,SACG,KAAK,iBAAiB,EAAE,MAAM,aAAa,SAAS,IAAI,EAAE,CAAC,CAAC,CAC5D,QAAQ,MAAM,WAAW;GACxB,IAAI,WAAW;GACf,IAAI;IACF,MAAM,WAAW,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC;IAClE,SAAS;KAAE,QAAQ;KAAS;IAAS,CAAC;GACxC,SAAS,OAAO;IACd,SAAS;KACP,QAAQ;KACR,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAChE,CAAC;GACH;EACF,CAAC,CAAC,CACD,YAAY;GACX,IAAI,CAAC,WAAW,SAAS,EAAE,QAAQ,QAAQ,CAAC;EAC9C,CAAC;EACH,aAAa;GACX,YAAY;EACd;CACF,GAAG;EAAC;EAAS;EAAM;CAAQ,CAAC;CAE5B,OAAO;AACT;;AAQA,SAAgB,YAAY,KAA4B;CAEtD,MAAM,WADY,gBACO,CAAC,CAAC,eAAe,QAAQ;CAClD,MAAM,CAAC,OAAO,YAAY,SAAwB,EAAE,QAAQ,UAAU,CAAC;CAEvE,gBAAgB;EACd,IAAI,YAAY;EAChB,SAAS,EAAE,QAAQ,UAAU,CAAC;EAC9B,SACG,KAAK,iBAAiB,EAAE,MAAM,IAAI,QAAQ,cAAc,EAAE,EAAE,CAAC,CAAC,CAC9D,QAAQ,MAAM,WAAW;GACxB,IAAI,WAAW;GACf,SAAS;IAAE,QAAQ;IAAS,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,KAAK;GAAE,CAAC;EAC5E,CAAC,CAAC,CACD,OAAO,UAAmB;GACzB,IAAI,WAAW;GACf,SAAS;IACP,QAAQ;IACR,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAChE,CAAC;EACH,CAAC;EACH,aAAa;GACX,YAAY;EACd;CACF,GAAG,CAAC,KAAK,QAAQ,CAAC;CAElB,OAAO;AACT"}
1
+ {"version":3,"file":"site-data.js","names":[],"sources":["../../src/internal/site-data.ts"],"sourcesContent":["import { Commands } from \"@statewalker/shared-commands\";\nimport { useAppWorkspace } from \"@statewalker/ui.view.react\";\nimport type { SiteManifest } from \"@statewalker/wiki.core\";\nimport { LoadFileCommand } from \"@statewalker/workspace.core\";\nimport { useEffect, useState } from \"react\";\n\n/** Workspace-absolute path of a site's `site.json` manifest. */\nexport function siteJsonPath(project: string, slug: string): string {\n return `/${project}/sites/${slug}/site.json`;\n}\n\n/** Workspace-absolute path of a generated page (manifest `page.path` is a file name). */\nexport function pageUri(project: string, slug: string, pagePath: string): string {\n return `/${project}/sites/${slug}/${pagePath}`;\n}\n\nexport type ManifestState =\n | { status: \"loading\" }\n | { status: \"ready\"; manifest: SiteManifest }\n | { status: \"empty\" }\n | { status: \"error\"; message: string };\n\n/** Load a site's manifest via `files:load-file`. A missing file resolves to `empty`. */\nexport function useSiteManifest(project: string, slug: string): ManifestState {\n const workspace = useAppWorkspace();\n const commands = workspace.requireAdapter(Commands);\n const [state, setState] = useState<ManifestState>({ status: \"loading\" });\n\n useEffect(() => {\n let cancelled = false;\n setState({ status: \"loading\" });\n commands\n .call(LoadFileCommand, { path: siteJsonPath(project, slug) })\n .promise.then((loaded) => {\n if (cancelled) return;\n try {\n const manifest = JSON.parse(new TextDecoder().decode(loaded.bytes)) as SiteManifest;\n setState({ status: \"ready\", manifest });\n } catch (error) {\n setState({\n status: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n })\n .catch(() => {\n if (!cancelled) setState({ status: \"empty\" });\n });\n return () => {\n cancelled = true;\n };\n }, [project, slug, commands]);\n\n return state;\n}\n\nexport type FileTextState =\n | { status: \"loading\" }\n | { status: \"ready\"; text: string }\n | { status: \"error\"; message: string };\n\n/** Load a markdown page's text via `files:load-file`. */\nexport function useFileText(uri: string): FileTextState {\n const workspace = useAppWorkspace();\n const commands = workspace.requireAdapter(Commands);\n const [state, setState] = useState<FileTextState>({ status: \"loading\" });\n\n useEffect(() => {\n let cancelled = false;\n setState({ status: \"loading\" });\n commands\n .call(LoadFileCommand, { path: uri.replace(/^file:\\/\\//, \"\") })\n .promise.then((loaded) => {\n if (cancelled) return;\n setState({\n status: \"ready\",\n text: new TextDecoder().decode(loaded.bytes),\n });\n })\n .catch((error: unknown) => {\n if (cancelled) return;\n setState({\n status: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n });\n return () => {\n cancelled = true;\n };\n }, [uri, commands]);\n\n return state;\n}\n"],"mappings":";;;;;;AAOA,SAAgB,aAAa,SAAiB,MAAsB;CAClE,OAAO,IAAI,QAAQ,SAAS,KAAK;AACnC;;AAGA,SAAgB,QAAQ,SAAiB,MAAc,UAA0B;CAC/E,OAAO,IAAI,QAAQ,SAAS,KAAK,GAAG;AACtC;;AASA,SAAgB,gBAAgB,SAAiB,MAA6B;CAE5E,MAAM,WADY,gBACO,CAAC,CAAC,eAAe,QAAQ;CAClD,MAAM,CAAC,OAAO,YAAY,SAAwB,EAAE,QAAQ,UAAU,CAAC;CAEvE,gBAAgB;EACd,IAAI,YAAY;EAChB,SAAS,EAAE,QAAQ,UAAU,CAAC;EAC9B,SACG,KAAK,iBAAiB,EAAE,MAAM,aAAa,SAAS,IAAI,EAAE,CAAC,CAAC,CAC5D,QAAQ,MAAM,WAAW;GACxB,IAAI,WAAW;GACf,IAAI;IACF,MAAM,WAAW,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC;IAClE,SAAS;KAAE,QAAQ;KAAS;IAAS,CAAC;GACxC,SAAS,OAAO;IACd,SAAS;KACP,QAAQ;KACR,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAChE,CAAC;GACH;EACF,CAAC,CAAC,CACD,YAAY;GACX,IAAI,CAAC,WAAW,SAAS,EAAE,QAAQ,QAAQ,CAAC;EAC9C,CAAC;EACH,aAAa;GACX,YAAY;EACd;CACF,GAAG;EAAC;EAAS;EAAM;CAAQ,CAAC;CAE5B,OAAO;AACT;;AAQA,SAAgB,YAAY,KAA4B;CAEtD,MAAM,WADY,gBACO,CAAC,CAAC,eAAe,QAAQ;CAClD,MAAM,CAAC,OAAO,YAAY,SAAwB,EAAE,QAAQ,UAAU,CAAC;CAEvE,gBAAgB;EACd,IAAI,YAAY;EAChB,SAAS,EAAE,QAAQ,UAAU,CAAC;EAC9B,SACG,KAAK,iBAAiB,EAAE,MAAM,IAAI,QAAQ,cAAc,EAAE,EAAE,CAAC,CAAC,CAC9D,QAAQ,MAAM,WAAW;GACxB,IAAI,WAAW;GACf,SAAS;IACP,QAAQ;IACR,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,KAAK;GAC7C,CAAC;EACH,CAAC,CAAC,CACD,OAAO,UAAmB;GACzB,IAAI,WAAW;GACf,SAAS;IACP,QAAQ;IACR,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAChE,CAAC;EACH,CAAC;EACH,aAAa;GACX,YAAY;EACd;CACF,GAAG,CAAC,KAAK,QAAQ,CAAC;CAElB,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","names":[],"sources":["../../src/public/catalog.ts"],"mappings":";;;;;;;;qBASa,6CAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAExB;MAAgB,OAAK,EAAA;;;;;;;;qBAKZ;;wBAGG,iBAAiB,iBAAiB,eAAe;;wBAcjD,gBAAgB,iBAAiB;;wBAKjC,eAAe,iBAAiB"}
1
+ {"version":3,"file":"catalog.d.ts","names":[],"sources":["../../src/public/catalog.ts"],"mappings":";;;;;;;;qBASa,6CAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAExB;MACE,OAAK,EAAA;;;;;;;;qBAME;;wBAGG,iBAAiB,iBAAiB,eAAe;;wBAcjD,gBAAgB,iBAAiB;;wBAKjC,eAAe,iBAAiB"}
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.js","names":[],"sources":["../../src/public/catalog.ts"],"sourcesContent":["import { defineCatalog, type Spec } from \"@json-render/core\";\nimport { schema } from \"@statewalker/render.view.react\";\nimport { z } from \"zod\";\n\n/**\n * Catalog declaration for the wiki site-browser panel — typed schema only. The\n * `WikiSiteView` React binding lives in `init.tsx` (ADR-0002 logic/renderer split).\n * The single element carries `{ project, slug }`; the component reads `site.json`.\n */\nexport const wikiSiteCatalog = defineCatalog(schema, {\n components: {\n WikiSiteView: { props: z.object({ project: z.string(), slug: z.string() }) },\n },\n actions: {},\n});\n\nexport const WIKI_SITE_CATALOG_ID = \"wiki-site\";\n\n/** Build the one-element spec for a (project, slug) site. */\nexport function makeWikiSiteSpec(project: string, slug: string): Spec {\n return {\n root: \"panel\",\n elements: {\n panel: {\n type: \"WikiSiteView\",\n props: { project, slug },\n children: [],\n },\n },\n } as Spec;\n}\n\n/** Deterministic panel id so reopening the same site focuses the tab. */\nexport function wikiSitePanelId(project: string, slug: string): string {\n return `wiki-site:${project}/${slug}`;\n}\n\n/** Deterministic spec id; pairs with `wikiSitePanelId`. */\nexport function wikiSiteSpecId(project: string, slug: string): string {\n return `spec:wiki-site:${project}/${slug}`;\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,kBAAkB,cAAc,QAAQ;CACnD,YAAY,EACV,cAAc,EAAE,OAAO,EAAE,OAAO;EAAE,SAAS,EAAE,OAAO;EAAG,MAAM,EAAE,OAAO;CAAE,CAAC,EAAE,EAC7E;CACA,SAAS,CAAC;AACZ,CAAC;AAED,MAAa,uBAAuB;;AAGpC,SAAgB,iBAAiB,SAAiB,MAAoB;CACpE,OAAO;EACL,MAAM;EACN,UAAU,EACR,OAAO;GACL,MAAM;GACN,OAAO;IAAE;IAAS;GAAK;GACvB,UAAU,CAAC;EACb,EACF;CACF;AACF;;AAGA,SAAgB,gBAAgB,SAAiB,MAAsB;CACrE,OAAO,aAAa,QAAQ,GAAG;AACjC;;AAGA,SAAgB,eAAe,SAAiB,MAAsB;CACpE,OAAO,kBAAkB,QAAQ,GAAG;AACtC"}
1
+ {"version":3,"file":"catalog.js","names":[],"sources":["../../src/public/catalog.ts"],"sourcesContent":["import { defineCatalog, type Spec } from \"@json-render/core\";\nimport { schema } from \"@statewalker/render.view.react\";\nimport { z } from \"zod\";\n\n/**\n * Catalog declaration for the wiki site-browser panel — typed schema only. The\n * `WikiSiteView` React binding lives in `init.tsx` (ADR-0002 logic/renderer split).\n * The single element carries `{ project, slug }`; the component reads `site.json`.\n */\nexport const wikiSiteCatalog = defineCatalog(schema, {\n components: {\n WikiSiteView: {\n props: z.object({ project: z.string(), slug: z.string() }),\n },\n },\n actions: {},\n});\n\nexport const WIKI_SITE_CATALOG_ID = \"wiki-site\";\n\n/** Build the one-element spec for a (project, slug) site. */\nexport function makeWikiSiteSpec(project: string, slug: string): Spec {\n return {\n root: \"panel\",\n elements: {\n panel: {\n type: \"WikiSiteView\",\n props: { project, slug },\n children: [],\n },\n },\n } as Spec;\n}\n\n/** Deterministic panel id so reopening the same site focuses the tab. */\nexport function wikiSitePanelId(project: string, slug: string): string {\n return `wiki-site:${project}/${slug}`;\n}\n\n/** Deterministic spec id; pairs with `wikiSitePanelId`. */\nexport function wikiSiteSpecId(project: string, slug: string): string {\n return `spec:wiki-site:${project}/${slug}`;\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,kBAAkB,cAAc,QAAQ;CACnD,YAAY,EACV,cAAc,EACZ,OAAO,EAAE,OAAO;EAAE,SAAS,EAAE,OAAO;EAAG,MAAM,EAAE,OAAO;CAAE,CAAC,EAC3D,EACF;CACA,SAAS,CAAC;AACZ,CAAC;AAED,MAAa,uBAAuB;;AAGpC,SAAgB,iBAAiB,SAAiB,MAAoB;CACpE,OAAO;EACL,MAAM;EACN,UAAU,EACR,OAAO;GACL,MAAM;GACN,OAAO;IAAE;IAAS;GAAK;GACvB,UAAU,CAAC;EACb,EACF;CACF;AACF;;AAGA,SAAgB,gBAAgB,SAAiB,MAAsB;CACrE,OAAO,aAAa,QAAQ,GAAG;AACjC;;AAGA,SAAgB,eAAe,SAAiB,MAAsB;CACpE,OAAO,kBAAkB,QAAQ,GAAG;AACtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","names":[],"sources":["../../src/public/init.tsx"],"sourcesContent":["import {\n catalogsSlot,\n LayoutStore,\n restorePanelSpecsFromLayout,\n SpecStore,\n} from \"@statewalker/render.core\";\nimport { defineRegistry } from \"@statewalker/render.view.react\";\nimport { Commands } from \"@statewalker/shared-commands\";\nimport { newRegistry } from \"@statewalker/shared-registry\";\nimport { Slots } from \"@statewalker/shared-slots\";\nimport { ShowDockPanelCommand } from \"@statewalker/shell.core\";\nimport { dockTabIconSlot } from \"@statewalker/shell.view.react\";\nimport { OpenWikiSiteCommand } from \"@statewalker/wiki.core\";\nimport { getWorkspace } from \"@statewalker/workspace.core\";\nimport { BookText } from \"lucide-react\";\nimport { WikiSiteView } from \"../internal/wiki-site-view.js\";\nimport {\n makeWikiSiteSpec,\n WIKI_SITE_CATALOG_ID,\n wikiSiteCatalog,\n wikiSitePanelId,\n wikiSiteSpecId,\n} from \"./catalog.js\";\n\n/** Split a restored panel id suffix (`<project>/<slug>`) back into its parts. */\nfunction splitSiteId(id: string): { project: string; slug: string } {\n const slash = id.indexOf(\"/\");\n return slash < 0\n ? { project: id, slug: \"\" }\n : { project: id.slice(0, slash), slug: id.slice(slash + 1) };\n}\n\n/**\n * Renderer-fragment init for `@statewalker/wiki.view.react`. Registers the\n * `wiki-site` json-render catalog (binding `WikiSiteView`) and the `wiki:open-site`\n * command that opens a generated thematic site as a dock panel. ADR-0002: all wiki\n * React lives here, never in `@statewalker/wiki`.\n */\nexport default function initWikiReact(ctx: Record<string, unknown>): () => Promise<void> {\n const [register, cleanup] = newRegistry();\n const workspace = getWorkspace(ctx);\n const slots = workspace.requireAdapter(Slots);\n const store = workspace.requireAdapter(SpecStore);\n const commands = workspace.requireAdapter(Commands);\n const layoutStore = workspace.requireAdapter(LayoutStore);\n\n // Pre-allocate specs for restored wiki-site tabs on workspace-connect,\n // reading the layout held by the LayoutStore adapter (see mime.view.pdf).\n register(\n workspace.onLoad(() => {\n restorePanelSpecsFromLayout({\n store,\n layout: layoutStore.get(),\n panelIdPrefix: \"wiki-site:\",\n catalogId: WIKI_SITE_CATALOG_ID,\n buildSpec: (id) => {\n const { project, slug } = splitSiteId(id);\n return makeWikiSiteSpec(project, slug);\n },\n buildSpecId: (id) => {\n const { project, slug } = splitSiteId(id);\n return wikiSiteSpecId(project, slug);\n },\n });\n }),\n );\n\n const { registry } = defineRegistry(wikiSiteCatalog, {\n components: {\n WikiSiteView: ({ props }) => <WikiSiteView project={props.project} slug={props.slug} />,\n },\n actions: {},\n });\n register(slots.register(catalogsSlot, WIKI_SITE_CATALOG_ID, registry));\n\n register(\n commands.listen(OpenWikiSiteCommand, (command) => {\n const { project, slug } = command.payload;\n const specId = wikiSiteSpecId(project, slug);\n const panelId = wikiSitePanelId(project, slug);\n if (!store.get(specId)) {\n store.create({\n id: specId,\n catalogId: WIKI_SITE_CATALOG_ID,\n spec: makeWikiSiteSpec(project, slug),\n });\n }\n void commands\n .call(ShowDockPanelCommand, { panelId, specId, title: slug })\n .promise.then(() => command.resolve())\n .catch((error) => command.reject(error));\n return true;\n }),\n );\n\n register(slots.provide(dockTabIconSlot, { panelIdPrefix: \"wiki-site:\", Icon: BookText }));\n\n return cleanup;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyBA,SAAS,YAAY,IAA+C;CAClE,MAAM,QAAQ,GAAG,QAAQ,GAAG;CAC5B,OAAO,QAAQ,IACX;EAAE,SAAS;EAAI,MAAM;CAAG,IACxB;EAAE,SAAS,GAAG,MAAM,GAAG,KAAK;EAAG,MAAM,GAAG,MAAM,QAAQ,CAAC;CAAE;AAC/D;;;;;;;AAQA,SAAwB,cAAc,KAAmD;CACvF,MAAM,CAAC,UAAU,WAAW,YAAY;CACxC,MAAM,YAAY,aAAa,GAAG;CAClC,MAAM,QAAQ,UAAU,eAAe,KAAK;CAC5C,MAAM,QAAQ,UAAU,eAAe,SAAS;CAChD,MAAM,WAAW,UAAU,eAAe,QAAQ;CAClD,MAAM,cAAc,UAAU,eAAe,WAAW;CAIxD,SACE,UAAU,aAAa;EACrB,4BAA4B;GAC1B;GACA,QAAQ,YAAY,IAAI;GACxB,eAAe;GACf,WAAW;GACX,YAAY,OAAO;IACjB,MAAM,EAAE,SAAS,SAAS,YAAY,EAAE;IACxC,OAAO,iBAAiB,SAAS,IAAI;GACvC;GACA,cAAc,OAAO;IACnB,MAAM,EAAE,SAAS,SAAS,YAAY,EAAE;IACxC,OAAO,eAAe,SAAS,IAAI;GACrC;EACF,CAAC;CACH,CAAC,CACH;CAEA,MAAM,EAAE,aAAa,eAAe,iBAAiB;EACnD,YAAY,EACV,eAAe,EAAE,YAAY,oBAAC,cAAD;GAAc,SAAS,MAAM;GAAS,MAAM,MAAM;EAAO,CAAA,EACxF;EACA,SAAS,CAAC;CACZ,CAAC;CACD,SAAS,MAAM,SAAS,cAAc,sBAAsB,QAAQ,CAAC;CAErE,SACE,SAAS,OAAO,sBAAsB,YAAY;EAChD,MAAM,EAAE,SAAS,SAAS,QAAQ;EAClC,MAAM,SAAS,eAAe,SAAS,IAAI;EAC3C,MAAM,UAAU,gBAAgB,SAAS,IAAI;EAC7C,IAAI,CAAC,MAAM,IAAI,MAAM,GACnB,MAAM,OAAO;GACX,IAAI;GACJ,WAAW;GACX,MAAM,iBAAiB,SAAS,IAAI;EACtC,CAAC;EAEH,SACG,KAAK,sBAAsB;GAAE;GAAS;GAAQ,OAAO;EAAK,CAAC,CAAC,CAC5D,QAAQ,WAAW,QAAQ,QAAQ,CAAC,CAAC,CACrC,OAAO,UAAU,QAAQ,OAAO,KAAK,CAAC;EACzC,OAAO;CACT,CAAC,CACH;CAEA,SAAS,MAAM,QAAQ,iBAAiB;EAAE,eAAe;EAAc,MAAM;CAAS,CAAC,CAAC;CAExF,OAAO;AACT"}
1
+ {"version":3,"file":"init.js","names":[],"sources":["../../src/public/init.tsx"],"sourcesContent":["import {\n catalogsSlot,\n LayoutStore,\n restorePanelSpecsFromLayout,\n SpecStore,\n} from \"@statewalker/render.core\";\nimport { defineRegistry } from \"@statewalker/render.view.react\";\nimport { Commands } from \"@statewalker/shared-commands\";\nimport { newRegistry } from \"@statewalker/shared-registry\";\nimport { Slots } from \"@statewalker/shared-slots\";\nimport { ShowDockPanelCommand } from \"@statewalker/shell.core\";\nimport { dockTabIconSlot } from \"@statewalker/shell.view.react\";\nimport { OpenWikiSiteCommand } from \"@statewalker/wiki.core\";\nimport { getWorkspace } from \"@statewalker/workspace.core\";\nimport { BookText } from \"lucide-react\";\nimport { WikiSiteView } from \"../internal/wiki-site-view.js\";\nimport {\n makeWikiSiteSpec,\n WIKI_SITE_CATALOG_ID,\n wikiSiteCatalog,\n wikiSitePanelId,\n wikiSiteSpecId,\n} from \"./catalog.js\";\n\n/** Split a restored panel id suffix (`<project>/<slug>`) back into its parts. */\nfunction splitSiteId(id: string): { project: string; slug: string } {\n const slash = id.indexOf(\"/\");\n return slash < 0\n ? { project: id, slug: \"\" }\n : { project: id.slice(0, slash), slug: id.slice(slash + 1) };\n}\n\n/**\n * Renderer-fragment init for `@statewalker/wiki.view.react`. Registers the\n * `wiki-site` json-render catalog (binding `WikiSiteView`) and the `wiki:open-site`\n * command that opens a generated thematic site as a dock panel. ADR-0002: all wiki\n * React lives here, never in `@statewalker/wiki`.\n */\nexport default function initWikiReact(ctx: Record<string, unknown>): () => Promise<void> {\n const [register, cleanup] = newRegistry();\n const workspace = getWorkspace(ctx);\n const slots = workspace.requireAdapter(Slots);\n const store = workspace.requireAdapter(SpecStore);\n const commands = workspace.requireAdapter(Commands);\n const layoutStore = workspace.requireAdapter(LayoutStore);\n\n // Pre-allocate specs for restored wiki-site tabs on workspace-connect,\n // reading the layout held by the LayoutStore adapter (see mime.view.pdf).\n register(\n workspace.onLoad(() => {\n restorePanelSpecsFromLayout({\n store,\n layout: layoutStore.get(),\n panelIdPrefix: \"wiki-site:\",\n catalogId: WIKI_SITE_CATALOG_ID,\n buildSpec: (id) => {\n const { project, slug } = splitSiteId(id);\n return makeWikiSiteSpec(project, slug);\n },\n buildSpecId: (id) => {\n const { project, slug } = splitSiteId(id);\n return wikiSiteSpecId(project, slug);\n },\n });\n }),\n );\n\n const { registry } = defineRegistry(wikiSiteCatalog, {\n components: {\n WikiSiteView: ({ props }) => <WikiSiteView project={props.project} slug={props.slug} />,\n },\n actions: {},\n });\n register(slots.register(catalogsSlot, WIKI_SITE_CATALOG_ID, registry));\n\n register(\n commands.listen(OpenWikiSiteCommand, (command) => {\n const { project, slug } = command.payload;\n const specId = wikiSiteSpecId(project, slug);\n const panelId = wikiSitePanelId(project, slug);\n if (!store.get(specId)) {\n store.create({\n id: specId,\n catalogId: WIKI_SITE_CATALOG_ID,\n spec: makeWikiSiteSpec(project, slug),\n });\n }\n void commands\n .call(ShowDockPanelCommand, { panelId, specId, title: slug })\n .promise.then(() => command.resolve())\n .catch((error) => command.reject(error));\n return true;\n }),\n );\n\n register(\n slots.provide(dockTabIconSlot, {\n panelIdPrefix: \"wiki-site:\",\n Icon: BookText,\n }),\n );\n\n return cleanup;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyBA,SAAS,YAAY,IAA+C;CAClE,MAAM,QAAQ,GAAG,QAAQ,GAAG;CAC5B,OAAO,QAAQ,IACX;EAAE,SAAS;EAAI,MAAM;CAAG,IACxB;EAAE,SAAS,GAAG,MAAM,GAAG,KAAK;EAAG,MAAM,GAAG,MAAM,QAAQ,CAAC;CAAE;AAC/D;;;;;;;AAQA,SAAwB,cAAc,KAAmD;CACvF,MAAM,CAAC,UAAU,WAAW,YAAY;CACxC,MAAM,YAAY,aAAa,GAAG;CAClC,MAAM,QAAQ,UAAU,eAAe,KAAK;CAC5C,MAAM,QAAQ,UAAU,eAAe,SAAS;CAChD,MAAM,WAAW,UAAU,eAAe,QAAQ;CAClD,MAAM,cAAc,UAAU,eAAe,WAAW;CAIxD,SACE,UAAU,aAAa;EACrB,4BAA4B;GAC1B;GACA,QAAQ,YAAY,IAAI;GACxB,eAAe;GACf,WAAW;GACX,YAAY,OAAO;IACjB,MAAM,EAAE,SAAS,SAAS,YAAY,EAAE;IACxC,OAAO,iBAAiB,SAAS,IAAI;GACvC;GACA,cAAc,OAAO;IACnB,MAAM,EAAE,SAAS,SAAS,YAAY,EAAE;IACxC,OAAO,eAAe,SAAS,IAAI;GACrC;EACF,CAAC;CACH,CAAC,CACH;CAEA,MAAM,EAAE,aAAa,eAAe,iBAAiB;EACnD,YAAY,EACV,eAAe,EAAE,YAAY,oBAAC,cAAD;GAAc,SAAS,MAAM;GAAS,MAAM,MAAM;EAAO,CAAA,EACxF;EACA,SAAS,CAAC;CACZ,CAAC;CACD,SAAS,MAAM,SAAS,cAAc,sBAAsB,QAAQ,CAAC;CAErE,SACE,SAAS,OAAO,sBAAsB,YAAY;EAChD,MAAM,EAAE,SAAS,SAAS,QAAQ;EAClC,MAAM,SAAS,eAAe,SAAS,IAAI;EAC3C,MAAM,UAAU,gBAAgB,SAAS,IAAI;EAC7C,IAAI,CAAC,MAAM,IAAI,MAAM,GACnB,MAAM,OAAO;GACX,IAAI;GACJ,WAAW;GACX,MAAM,iBAAiB,SAAS,IAAI;EACtC,CAAC;EAEH,SACG,KAAK,sBAAsB;GAAE;GAAS;GAAQ,OAAO;EAAK,CAAC,CAAC,CAC5D,QAAQ,WAAW,QAAQ,QAAQ,CAAC,CAAC,CACrC,OAAO,UAAU,QAAQ,OAAO,KAAK,CAAC;EACzC,OAAO;CACT,CAAC,CACH;CAEA,SACE,MAAM,QAAQ,iBAAiB;EAC7B,eAAe;EACf,MAAM;CACR,CAAC,CACH;CAEA,OAAO;AACT"}
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "@statewalker/wiki.view.react",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Wiki renderer fragment: search panel, indexer panel, and browsing/citation views (DockView specs / slot contributions) consuming the React-free @statewalker/wiki adapters.",
7
- "homepage": "https://github.com/statewalker/statewalker-workbench",
6
+ "description": "Wiki renderer fragment: a dock panel that browses sites generated by @statewalker/wiki.core, opened with the wiki:open-site command.",
7
+ "bugs": {
8
+ "url": "https://github.com/statewalker/sandclaw/issues"
9
+ },
10
+ "homepage": "https://github.com/statewalker/sandclaw/tree/main/packages/wiki.view.react#readme",
8
11
  "license": "MIT",
9
12
  "repository": {
10
13
  "type": "git",
11
- "url": "git+ssh://git@github.com/statewalker/statewalker-workbench.git"
14
+ "url": "git+https://github.com/statewalker/sandclaw.git",
15
+ "directory": "packages/wiki.view.react"
12
16
  },
13
17
  "exports": {
14
18
  ".": {
@@ -45,7 +49,7 @@
45
49
  "@statewalker/workspace.core": "^0.5.0",
46
50
  "lucide-react": "^1.51.0",
47
51
  "zod": "^4.6.5",
48
- "@statewalker/wiki.core": "^0.1.1"
52
+ "@statewalker/wiki.core": "^0.1.2"
49
53
  },
50
54
  "peerDependencies": {
51
55
  "react": "^19.3.0",
@@ -72,7 +72,10 @@ export function useFileText(uri: string): FileTextState {
72
72
  .call(LoadFileCommand, { path: uri.replace(/^file:\/\//, "") })
73
73
  .promise.then((loaded) => {
74
74
  if (cancelled) return;
75
- setState({ status: "ready", text: new TextDecoder().decode(loaded.bytes) });
75
+ setState({
76
+ status: "ready",
77
+ text: new TextDecoder().decode(loaded.bytes),
78
+ });
76
79
  })
77
80
  .catch((error: unknown) => {
78
81
  if (cancelled) return;
@@ -12,8 +12,20 @@ const MANIFEST = {
12
12
  title: "Acme themes",
13
13
  generated: "",
14
14
  pages: [
15
- { id: "founders", title: "Founders", path: "founders.md", order: 0, promptHash: "a" },
16
- { id: "products", title: "Products", path: "products.md", order: 1, promptHash: "b" },
15
+ {
16
+ id: "founders",
17
+ title: "Founders",
18
+ path: "founders.md",
19
+ order: 0,
20
+ promptHash: "a",
21
+ },
22
+ {
23
+ id: "products",
24
+ title: "Products",
25
+ path: "products.md",
26
+ order: 1,
27
+ promptHash: "b",
28
+ },
17
29
  ],
18
30
  };
19
31
 
@@ -9,7 +9,9 @@ import { z } from "zod";
9
9
  */
10
10
  export const wikiSiteCatalog = defineCatalog(schema, {
11
11
  components: {
12
- WikiSiteView: { props: z.object({ project: z.string(), slug: z.string() }) },
12
+ WikiSiteView: {
13
+ props: z.object({ project: z.string(), slug: z.string() }),
14
+ },
13
15
  },
14
16
  actions: {},
15
17
  });
@@ -2,7 +2,10 @@
2
2
 
3
3
  // The open-site command contract lives in `@statewalker/wiki` (React-free) so logic
4
4
  // can dispatch it; re-exported here for renderer consumers.
5
- export { OpenWikiSiteCommand, type OpenWikiSitePayload } from "@statewalker/wiki.core";
5
+ export {
6
+ OpenWikiSiteCommand,
7
+ type OpenWikiSitePayload,
8
+ } from "@statewalker/wiki.core";
6
9
  export {
7
10
  makeWikiSiteSpec,
8
11
  WIKI_SITE_CATALOG_ID,
@@ -25,13 +25,19 @@ describe("initWikiReact — wiki:open-site", () => {
25
25
 
26
26
  const cleanup = initWikiReact({ "workspace:workspace": ws });
27
27
 
28
- await commands.call(OpenWikiSiteCommand, { project: "proj", slug: "themes" }).promise;
28
+ await commands.call(OpenWikiSiteCommand, {
29
+ project: "proj",
30
+ slug: "themes",
31
+ }).promise;
29
32
  expect(store.get(wikiSiteSpecId("proj", "themes"))).toBeTruthy();
30
33
  expect(shown).toEqual(["wiki-site:proj/themes"]);
31
34
 
32
35
  // Re-opening the same site does not duplicate the spec (the guard prevents a
33
36
  // throwing re-create) and targets the same deterministic panel id (focus).
34
- await commands.call(OpenWikiSiteCommand, { project: "proj", slug: "themes" }).promise;
37
+ await commands.call(OpenWikiSiteCommand, {
38
+ project: "proj",
39
+ slug: "themes",
40
+ }).promise;
35
41
  expect(shown).toEqual(["wiki-site:proj/themes", "wiki-site:proj/themes"]);
36
42
 
37
43
  await cleanup();
@@ -93,7 +93,12 @@ export default function initWikiReact(ctx: Record<string, unknown>): () => Promi
93
93
  }),
94
94
  );
95
95
 
96
- register(slots.provide(dockTabIconSlot, { panelIdPrefix: "wiki-site:", Icon: BookText }));
96
+ register(
97
+ slots.provide(dockTabIconSlot, {
98
+ panelIdPrefix: "wiki-site:",
99
+ Icon: BookText,
100
+ }),
101
+ );
97
102
 
98
103
  return cleanup;
99
104
  }