pi-weave 0.1.12 → 0.1.13
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 +8 -37
- package/package.json +1 -2
- package/src/core/concurrency.ts +3 -6
- package/src/core/frontmatter.ts +0 -53
- package/src/core/graph/build.ts +6 -7
- package/src/core/graph/current.ts +2 -4
- package/src/core/graph/model.ts +1 -1
- package/src/core/graph/wikilinks.ts +3 -3
- package/src/core/index.ts +26 -27
- package/src/core/paths.ts +0 -7
- package/src/core/vault.ts +16 -681
- package/src/core/view/detail.ts +1 -1
- package/src/core/view/health.ts +1 -1
- package/src/core/view/tree.ts +1 -1
- package/src/pi/index.ts +6 -85
- package/src/pi/summarize.ts +2 -2
- package/src/pi/viewer/tui/bodyStore.ts +4 -7
- package/src/pi/viewer/tui/branding.ts +7 -148
- package/src/pi/viewer/tui/run.ts +3 -17
- package/src/pi/viewer/tui/surface/base.ts +24 -3
- package/src/pi/viewer/tui/surface/explore.ts +41 -6
- package/src/pi/viewer/tui/workspace.ts +23 -351
- package/src/pi/viewer/tui/workspaceRoot.ts +31 -172
- package/src/pi/viewer/web/run.ts +7 -117
- package/src/web/client/api.dom.ts +2 -2
- package/src/web/client/api.ts +14 -223
- package/src/web/client/bootstrap.ts +5 -14
- package/src/web/client/context/context.model.ts +9 -11
- package/src/web/client/dist/app.js +93 -219
- package/src/web/client/graph/dynamics.ts +5 -65
- package/src/web/client/graph/renderer.dom.ts +7 -8
- package/src/web/client/graph/renderer.ts +9 -35
- package/src/web/client/main.tsx +1 -1
- package/src/web/client/note/Note.tsx +21 -63
- package/src/web/client/search/SearchPalette.tsx +45 -36
- package/src/web/client/search/search.model.ts +33 -454
- package/src/web/client/shell/Columns.tsx +13 -83
- package/src/web/client/shell/Header.tsx +2 -10
- package/src/web/client/shell/Shell.tsx +50 -125
- package/src/web/client/shell/StatusBar.tsx +1 -4
- package/src/web/client/shell/icons.model.ts +4 -7
- package/src/web/client/shell/keys.model.ts +5 -42
- package/src/web/client/shell/keys.ts +2 -2
- package/src/web/client/shell/shell.model.ts +10 -133
- package/src/web/client/shell/theme.model.ts +2 -2
- package/src/web/client/shell/theme.ts +33 -157
- package/src/web/client/state.ts +9 -89
- package/src/web/client/tree/Tree.tsx +25 -575
- package/src/web/client/tree/tree.model.ts +8 -162
- package/src/web/client/workspace.ts +72 -242
- package/src/web/server/page.ts +8 -10
- package/src/web/server/routes.ts +30 -563
- package/src/web/server/server.ts +6 -145
- package/src/web/shared/layout.ts +72 -624
- package/src/web/shared/wire.ts +10 -196
- package/src/core/sessions.ts +0 -929
- package/src/pi/sessionScan.ts +0 -104
- package/src/pi/viewer/tui/explorer.ts +0 -586
- package/src/web/client/live.model.ts +0 -275
- package/src/web/client/live.ts +0 -151
- package/src/web/client/note/Editor.tsx +0 -109
- package/src/web/client/note/editor.controller.ts +0 -151
- package/src/web/client/note/editor.model.ts +0 -686
- package/src/web/client/search/search.ts +0 -107
- package/src/web/client/shell/Divider.tsx +0 -44
- package/src/web/client/shell/cssvars.ts +0 -70
- package/src/web/client/shell/drag.model.ts +0 -170
- package/src/web/client/shell/layout.model.ts +0 -500
- package/src/web/client/shell/viewport.ts +0 -29
- package/src/web/server/sse.ts +0 -321
- package/src/web/server/watcher.ts +0 -507
|
@@ -25,19 +25,9 @@
|
|
|
25
25
|
* | --- | --- |
|
|
26
26
|
* | `⌘K` / `Ctrl K` | always — a modifier shortcut is unambiguous even mid-word |
|
|
27
27
|
* | `⌘1` `⌘2` `⌘3` | always |
|
|
28
|
-
* | `⌘E` `⌘S` | always — the editor's two keys (§11 P5.4) |
|
|
29
28
|
* | `Esc` | an overlay is open, or something is selected. Never otherwise |
|
|
30
29
|
* | `/` `g` `?` `t` | **only** when focus is not in a text field and no overlay is open |
|
|
31
30
|
*
|
|
32
|
-
* `⌘S` is claimed **even while typing**, which is the one place this map
|
|
33
|
-
* deliberately overrides the browser. It has to be: the browser's `⌘S` is
|
|
34
|
-
* "save this page as HTML", which in a note editor is never what the user
|
|
35
|
-
* meant, and the moment they most want to save is the moment their cursor is
|
|
36
|
-
* in the textarea. `⌘E` is claimed on the same grounds — the browser has no
|
|
37
|
-
* default for it, and toggling out of the editor is a thing to do *from*
|
|
38
|
-
* inside the editor. Neither fires when an overlay is open, because the
|
|
39
|
-
* palette owns every key but `Escape`.
|
|
40
|
-
*
|
|
41
31
|
* `j` / `k` are not here at all: they are tree-scoped, so they live in
|
|
42
32
|
* `tree.model.ts`'s `normalizeTreeKey` where the tree's own handler applies
|
|
43
33
|
* them — a global `j` would move the tree selection while the user is looking
|
|
@@ -51,9 +41,7 @@
|
|
|
51
41
|
* `⌘K`, which would otherwise re-open the palette on top of itself.
|
|
52
42
|
*/
|
|
53
43
|
|
|
54
|
-
import { COLUMNS,
|
|
55
|
-
import type { ColumnId } from "./layout.model";
|
|
56
|
-
import type { OverlayId } from "./shell.model";
|
|
44
|
+
import { COLUMNS, type ColumnId, type OverlayId } from "./shell.model";
|
|
57
45
|
|
|
58
46
|
// --- the event, without the DOM ---------------------------------------------------
|
|
59
47
|
|
|
@@ -102,25 +90,17 @@ export type ShellAction =
|
|
|
102
90
|
| { readonly type: "filterTree" }
|
|
103
91
|
| { readonly type: "fitGraph" }
|
|
104
92
|
| { readonly type: "clearSelection" }
|
|
105
|
-
/** `⌘E` — toggle the note column between read and edit (§11 P5.4). */
|
|
106
|
-
| { readonly type: "toggleEdit" }
|
|
107
|
-
/** `⌘S` — save the open draft. */
|
|
108
|
-
| { readonly type: "saveNote" }
|
|
109
93
|
/** `t` — cycle the colour theme: system → light → dark → system. */
|
|
110
94
|
| { readonly type: "cycleTheme" };
|
|
111
95
|
|
|
112
96
|
/**
|
|
113
97
|
* The command-key letters, as data.
|
|
114
98
|
*
|
|
115
|
-
* A table for the same reason {@link COLUMN_DIGITS} is one:
|
|
116
|
-
*
|
|
117
|
-
* hand-written cases, and the help sheet is generated from it so it cannot
|
|
118
|
-
* document a key the code does not implement.
|
|
99
|
+
* A table for the same reason {@link COLUMN_DIGITS} is one: the help sheet is
|
|
100
|
+
* generated from it so it cannot document a key the code does not implement.
|
|
119
101
|
*/
|
|
120
102
|
export const COMMAND_KEYS: Readonly<Record<string, ShellAction>> = {
|
|
121
103
|
k: { type: "openSearch" },
|
|
122
|
-
e: { type: "toggleEdit" },
|
|
123
|
-
s: { type: "saveNote" },
|
|
124
104
|
};
|
|
125
105
|
|
|
126
106
|
/**
|
|
@@ -140,9 +120,7 @@ export const COLUMN_DIGITS: Readonly<Record<string, ColumnId>> = { "1": "tree",
|
|
|
140
120
|
* search button once was. Derived from `columnsAt`, not listed, so the caveat
|
|
141
121
|
* cannot drift from the breakpoints the sheet is attached to.
|
|
142
122
|
*/
|
|
143
|
-
const COLLAPSIBLE_COLUMNS: ReadonlySet<ColumnId> = new Set(
|
|
144
|
-
COLUMNS.filter((c) => columnsAt("medium").includes(c) === false || columnsAt("narrow").includes(c) === false),
|
|
145
|
-
);
|
|
123
|
+
const COLLAPSIBLE_COLUMNS: ReadonlySet<ColumnId> = new Set(["tree", "graph"]);
|
|
146
124
|
|
|
147
125
|
/**
|
|
148
126
|
* Whether a modifier combination counts as "the platform's command key".
|
|
@@ -249,7 +227,7 @@ export interface KeyTarget {
|
|
|
249
227
|
* The slice of `KeyboardEvent` the subscription reads.
|
|
250
228
|
*
|
|
251
229
|
* Structural, so the platform's satisfies it without a cast and a fake is an
|
|
252
|
-
* object literal — the same port shape
|
|
230
|
+
* object literal — the same port shape as `HttpResponse`
|
|
253
231
|
* use, for the same reason: there is no DOM test environment (§10).
|
|
254
232
|
*/
|
|
255
233
|
export interface KeyboardEventLike {
|
|
@@ -358,10 +336,6 @@ export interface ShellEffects {
|
|
|
358
336
|
fitGraph(): void;
|
|
359
337
|
/** Write `null` to §1.3's `selectedId`. */
|
|
360
338
|
clearSelection(): void;
|
|
361
|
-
/** `⌘E` — dispatch `toggle` into the note editor. */
|
|
362
|
-
toggleEdit(): void;
|
|
363
|
-
/** `⌘S` — dispatch `save` into the note editor. */
|
|
364
|
-
saveNote(): void;
|
|
365
339
|
/** `t` — advance the user's theme choice by one step in its cycle. */
|
|
366
340
|
cycleTheme(): void;
|
|
367
341
|
}
|
|
@@ -385,10 +359,6 @@ export function runShellAction(action: ShellAction, fx: ShellEffects): void {
|
|
|
385
359
|
return fx.fitGraph();
|
|
386
360
|
case "clearSelection":
|
|
387
361
|
return fx.clearSelection();
|
|
388
|
-
case "toggleEdit":
|
|
389
|
-
return fx.toggleEdit();
|
|
390
|
-
case "saveNote":
|
|
391
|
-
return fx.saveNote();
|
|
392
362
|
case "cycleTheme":
|
|
393
363
|
return fx.cycleTheme();
|
|
394
364
|
}
|
|
@@ -448,13 +418,6 @@ export function keyHelp(cmd: string): readonly KeyHelpGroup[] {
|
|
|
448
418
|
{ combo: "Home / End", what: "First / last row" },
|
|
449
419
|
],
|
|
450
420
|
},
|
|
451
|
-
{
|
|
452
|
-
title: "Note",
|
|
453
|
-
entries: [
|
|
454
|
-
{ combo: `${cmd}E`, what: "Toggle read / edit" },
|
|
455
|
-
{ combo: `${cmd}S`, what: "Save the open draft" },
|
|
456
|
-
],
|
|
457
|
-
},
|
|
458
421
|
{
|
|
459
422
|
title: "Graph",
|
|
460
423
|
entries: [{ combo: "g", what: "Fit the whole graph" }],
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `keys.model.ts` decides what a keystroke means; this attaches one listener
|
|
5
5
|
* and performs the answer. Four lines of it are the listener and the rest is
|
|
6
|
-
* dispatch, which is deliberate —
|
|
7
|
-
*
|
|
6
|
+
* dispatch, which is deliberate — a subscription written inline in a
|
|
7
|
+
* `useEffect` is one no
|
|
8
8
|
* test can reach.
|
|
9
9
|
*
|
|
10
10
|
* ## One listener, on the document, at the bubble phase
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Everything the shell *decides*, as pure functions (weave-workspace §1.2).
|
|
3
3
|
*
|
|
4
|
-
* The header's status summary, the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The header's status summary, the per-column empty-state copy and the status
|
|
5
|
+
* bar's text all live here rather than inside the components that render them.
|
|
6
|
+
* That is not a stylistic
|
|
7
7
|
* preference: §10 forbids adding a DOM test environment, so a conditional
|
|
8
8
|
* inside a `.tsx` is a conditional that can never be covered, and §14 lists
|
|
9
9
|
* "coverage gate blocks the UI work" as a live risk whose stated mitigation
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { GraphPayload, WireNodeKind, WireStalenessState } from "../../shared/wire";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/** The three fixed workspace surfaces. */
|
|
19
|
+
export type ColumnId = "tree" | "note" | "graph";
|
|
20
|
+
|
|
21
|
+
/** Columns in keyboard and visual order. */
|
|
22
|
+
export const COLUMNS: readonly ColumnId[] = ["tree", "note", "graph"];
|
|
21
23
|
|
|
22
24
|
// --- the header summary --------------------------------------------------------
|
|
23
25
|
|
|
@@ -77,93 +79,22 @@ export function summaryParts(summary: HeaderSummary): readonly string[] {
|
|
|
77
79
|
return [`vault:${summary.notes}`, `repo:${repoLabel(summary.repo)}`, `${summary.nodes} nodes`];
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
// --- the connection indicator ----------------------------------------------------
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* How a connection state is presented.
|
|
84
|
-
*
|
|
85
|
-
* `tone` is a class suffix rather than a colour, so the palette stays in the
|
|
86
|
-
* stylesheet where §5.2's nonce'd CSS can own it and this module stays free
|
|
87
|
-
* of presentation constants it cannot test the appearance of.
|
|
88
|
-
*/
|
|
89
|
-
export interface ConnectionView {
|
|
90
|
-
readonly label: string;
|
|
91
|
-
readonly tone: "ok" | "warn" | "bad";
|
|
92
|
-
/** The `title=` tooltip. Says what the user should expect to happen next. */
|
|
93
|
-
readonly hint: string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const CONNECTION_VIEWS: Readonly<Record<ConnectionState, ConnectionView>> = {
|
|
97
|
-
live: { label: "live", tone: "ok", hint: "watching the vault and the repository for changes" },
|
|
98
|
-
reconnecting: {
|
|
99
|
-
label: "reconnecting",
|
|
100
|
-
tone: "warn",
|
|
101
|
-
// Naming the recovery matters: §6 has the client refetch everything on
|
|
102
|
-
// reopen, so the user's screen will catch up on its own and they should
|
|
103
|
-
// not go looking for a reload button.
|
|
104
|
-
hint: "the event stream dropped — retrying, and everything refetches when it returns",
|
|
105
|
-
},
|
|
106
|
-
offline: {
|
|
107
|
-
label: "offline",
|
|
108
|
-
tone: "bad",
|
|
109
|
-
hint: "the workspace server is gone — the header's refresh control retries",
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/** Present a connection state. Total over the three states of §1.3. */
|
|
114
|
-
export function connectionView(state: ConnectionState): ConnectionView {
|
|
115
|
-
return CONNECTION_VIEWS[state];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
82
|
// --- empty states ------------------------------------------------------------------
|
|
119
83
|
|
|
120
|
-
/**
|
|
121
|
-
* A column's title, and the placeholder shown while its content is someone
|
|
122
|
-
* else's phase.
|
|
123
|
-
*
|
|
124
|
-
* P1's deliverable for the three columns was an *honest* empty state naming
|
|
125
|
-
* the phase that would fill it — more useful than a spinner implying a load,
|
|
126
|
-
* and far more useful than a blank pane that reads as a bug.
|
|
127
|
-
*
|
|
128
|
-
* **As of P3 no column uses one.** All three are built, so `Columns.tsx`
|
|
129
|
-
* renders their real surfaces and each has its *own* empty states —
|
|
130
|
-
* `treeEmptyMessage`, `noteEmptyMessage` and `graphEmptyMessage`, which
|
|
131
|
-
* distinguish "loading" from "filtered to nothing" from "genuinely empty" in a
|
|
132
|
-
* way one static sentence cannot.
|
|
133
|
-
*
|
|
134
|
-
* The table stays because {@link EmptyStateCopy.title} is still every column's
|
|
135
|
-
* heading and its `aria-label`. {@link EmptyStateCopy.body} and `phase` are
|
|
136
|
-
* now dead for all three, and are kept accurate rather than deleted for one
|
|
137
|
-
* reason: `tests/web/client-shell.test.ts` asserts the `phase` values against
|
|
138
|
-
* §11, so a column whose phase silently disagreed with the doc would be the
|
|
139
|
-
* first sign that this table had stopped tracking reality. `EmptyState.tsx`
|
|
140
|
-
* itself now has no caller, and is deleted rather than kept warm — an unused
|
|
141
|
-
* component is a thing the next reader has to work out is unused.
|
|
142
|
-
*/
|
|
84
|
+
/** The title used by a column heading and its `aria-label`. */
|
|
143
85
|
export interface EmptyStateCopy {
|
|
144
86
|
readonly title: string;
|
|
145
|
-
readonly body: string;
|
|
146
|
-
/** The phase that fills this column in, e.g. `"P2"`. */
|
|
147
|
-
readonly phase: string;
|
|
148
87
|
}
|
|
149
88
|
|
|
150
89
|
const EMPTY_STATES: Readonly<Record<ColumnId, EmptyStateCopy>> = {
|
|
151
90
|
tree: {
|
|
152
91
|
title: "Tree",
|
|
153
|
-
// Built in P2; body and phase are no longer rendered. See the header.
|
|
154
|
-
body: "The vault and repository outline, expandable, filterable, with provenance markers.",
|
|
155
|
-
phase: "P2",
|
|
156
92
|
},
|
|
157
93
|
note: {
|
|
158
94
|
title: "Note",
|
|
159
|
-
body: "Select anything to read it here — rendered Markdown, front matter, tags and wikilinks.",
|
|
160
|
-
phase: "P2",
|
|
161
95
|
},
|
|
162
96
|
graph: {
|
|
163
97
|
title: "Graph",
|
|
164
|
-
// Built in P3; body and phase are no longer rendered. See the header.
|
|
165
|
-
body: "The knowledge graph, laid out and navigable, with the selection's neighbourhood highlighted.",
|
|
166
|
-
phase: "P3",
|
|
167
98
|
},
|
|
168
99
|
};
|
|
169
100
|
|
|
@@ -172,18 +103,9 @@ export function emptyStateFor(column: ColumnId): EmptyStateCopy {
|
|
|
172
103
|
return EMPTY_STATES[column];
|
|
173
104
|
}
|
|
174
105
|
|
|
175
|
-
/**
|
|
176
|
-
* The context rail's title.
|
|
177
|
-
*
|
|
178
|
-
* Built in P2.5, so `body`/`phase` are no longer rendered either — the rail's
|
|
179
|
-
* real empty states are `RAIL_EMPTY` in `context/context.model.ts`, which
|
|
180
|
-
* separates "loading" from "nothing selected" from "this node is isolated".
|
|
181
|
-
* `title` is still the rail's heading and `aria-label`.
|
|
182
|
-
*/
|
|
106
|
+
/** The context rail's heading and `aria-label`. */
|
|
183
107
|
export const CONTEXT_EMPTY: EmptyStateCopy = {
|
|
184
108
|
title: "Context",
|
|
185
|
-
body: "Links, backlinks and mentions for whatever is selected.",
|
|
186
|
-
phase: "P2",
|
|
187
109
|
};
|
|
188
110
|
|
|
189
111
|
// --- the status bar -------------------------------------------------------------------
|
|
@@ -200,7 +122,6 @@ export const CONTEXT_EMPTY: EmptyStateCopy = {
|
|
|
200
122
|
export interface StatusBarModel {
|
|
201
123
|
readonly cwd: string;
|
|
202
124
|
readonly selection: string;
|
|
203
|
-
readonly connection: ConnectionView;
|
|
204
125
|
/** `null` before the first successful graph fetch. */
|
|
205
126
|
readonly stamp: string | null;
|
|
206
127
|
}
|
|
@@ -212,13 +133,11 @@ export const NO_VALUE = "—";
|
|
|
212
133
|
export function statusBarModel(
|
|
213
134
|
cwd: string,
|
|
214
135
|
selectedId: string | null,
|
|
215
|
-
connection: ConnectionState,
|
|
216
136
|
stamp: string | null,
|
|
217
137
|
): StatusBarModel {
|
|
218
138
|
return {
|
|
219
139
|
cwd: cwd === "" ? NO_VALUE : cwd,
|
|
220
140
|
selection: selectedId ?? "nothing selected",
|
|
221
|
-
connection: connectionView(connection),
|
|
222
141
|
stamp,
|
|
223
142
|
};
|
|
224
143
|
}
|
|
@@ -239,48 +158,6 @@ export function shortStamp(stamp: string | null): string {
|
|
|
239
158
|
return match?.[1] ?? stamp;
|
|
240
159
|
}
|
|
241
160
|
|
|
242
|
-
// --- pairing columns with dividers ---------------------------------------------------
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* One rendered column, plus the divider that follows it (if any).
|
|
246
|
-
*
|
|
247
|
-
* `divider` is `null` for the last column: there are three columns and two
|
|
248
|
-
* dividers, and the trailing edge of the grid is the window, not a handle.
|
|
249
|
-
*/
|
|
250
|
-
export interface ColumnSlot {
|
|
251
|
-
readonly column: ResolvedColumn;
|
|
252
|
-
readonly divider: DividerId | null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Interleave resolved columns with the dividers between them.
|
|
257
|
-
*
|
|
258
|
-
* This exists as a model function rather than an `index + 1 < length` check
|
|
259
|
-
* inside the JSX for two reasons. The first is coverage: that check is a
|
|
260
|
-
* branch, and a branch in a `.tsx` is a branch no test can reach (§10). The
|
|
261
|
-
* second is that the naive version is *wrong at a breakpoint* — at `"medium"`
|
|
262
|
-
* only tree and note render, so the divider after `note` must not appear even
|
|
263
|
-
* though `DIVIDERS` contains one. Deriving the pairing from the columns
|
|
264
|
-
* actually being rendered, rather than from the static divider list, makes
|
|
265
|
-
* that impossible to get wrong.
|
|
266
|
-
*/
|
|
267
|
-
export function columnSlots(resolved: readonly ResolvedColumn[]): readonly ColumnSlot[] {
|
|
268
|
-
return resolved.map((column, index) => ({
|
|
269
|
-
column,
|
|
270
|
-
// A divider is *named by the column to its left* (`layout.model.ts`'s
|
|
271
|
-
// `dividerPair`), so the id is the column's own — no index arithmetic into
|
|
272
|
-
// `DIVIDERS`, which would need an unreachable `?? null` to satisfy
|
|
273
|
-
// `noUncheckedIndexedAccess` and would leave a branch no test can cover.
|
|
274
|
-
// It exists only where a next column follows to resize against.
|
|
275
|
-
divider: index < resolved.length - 1 && isDivider(column.id) ? column.id : null,
|
|
276
|
-
}));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/** Whether a column has a divider named after it — i.e. is not the last one. */
|
|
280
|
-
function isDivider(column: ColumnId): column is DividerId {
|
|
281
|
-
return (DIVIDERS as readonly ColumnId[]).includes(column);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
161
|
// --- the refresh button ------------------------------------------------------------
|
|
285
162
|
|
|
286
163
|
/**
|
|
@@ -45,7 +45,7 @@ export type ThemeChoice = "system" | "light" | "dark";
|
|
|
45
45
|
/** The choices in cycle order. */
|
|
46
46
|
export const THEME_CHOICES: readonly ThemeChoice[] = ["system", "light", "dark"];
|
|
47
47
|
|
|
48
|
-
/** The `localStorage` key. Namespaced and versioned
|
|
48
|
+
/** The `localStorage` key. Namespaced and versioned. */
|
|
49
49
|
export const THEME_STORAGE_KEY = "pi-weave.theme.v1";
|
|
50
50
|
|
|
51
51
|
/** The slice of `Storage` this module needs. */
|
|
@@ -144,4 +144,4 @@ export function themeButton(choice: ThemeChoice): ThemeButtonView {
|
|
|
144
144
|
glyph: THEME_GLYPHS[choice],
|
|
145
145
|
hint: `Colour theme: ${choice} — click for ${cycleTheme(choice)}`,
|
|
146
146
|
};
|
|
147
|
-
}
|
|
147
|
+
}
|