@wildwinter/app-shell 0.37.0 → 0.39.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.
Files changed (144) hide show
  1. package/LICENSES.md +62 -0
  2. package/README.md +21 -0
  3. package/dist/about.css +13 -34
  4. package/dist/about.css.map +1 -1
  5. package/dist/anchored.css +5 -5
  6. package/dist/anchored.css.map +1 -1
  7. package/dist/app-store.cjs +15 -0
  8. package/dist/app-store.cjs.map +1 -1
  9. package/dist/app-store.d.cts +25 -1
  10. package/dist/app-store.d.ts +25 -1
  11. package/dist/app-store.js +14 -1
  12. package/dist/app-store.js.map +1 -1
  13. package/dist/chunk-2ECJUTSX.js +97 -0
  14. package/dist/chunk-2ECJUTSX.js.map +1 -0
  15. package/dist/chunk-2NMVIM54.js +55 -0
  16. package/dist/chunk-2NMVIM54.js.map +1 -0
  17. package/dist/chunk-C6UVQYKI.js +70 -0
  18. package/dist/chunk-C6UVQYKI.js.map +1 -0
  19. package/dist/chunk-IU7EY6BI.js +26 -0
  20. package/dist/chunk-IU7EY6BI.js.map +1 -0
  21. package/dist/chunk-K2AOLQW4.js +82 -0
  22. package/dist/chunk-K2AOLQW4.js.map +1 -0
  23. package/dist/chunk-KGAMP5LW.js +87 -0
  24. package/dist/chunk-KGAMP5LW.js.map +1 -0
  25. package/dist/chunk-L5HOBEKL.js +534 -0
  26. package/dist/chunk-L5HOBEKL.js.map +1 -0
  27. package/dist/chunk-P4G4ERG6.js +60 -0
  28. package/dist/chunk-P4G4ERG6.js.map +1 -0
  29. package/dist/chunk-P6WLAEKZ.js +71 -0
  30. package/dist/chunk-P6WLAEKZ.js.map +1 -0
  31. package/dist/chunk-S4KWQLYN.js +66 -0
  32. package/dist/chunk-S4KWQLYN.js.map +1 -0
  33. package/dist/chunk-XZYZXY7I.js +554 -0
  34. package/dist/chunk-XZYZXY7I.js.map +1 -0
  35. package/dist/comments.css +5 -3
  36. package/dist/comments.css.map +1 -1
  37. package/dist/confirm.css +1 -54
  38. package/dist/confirm.css.map +1 -1
  39. package/dist/context-menu.cjs +250 -0
  40. package/dist/context-menu.cjs.map +1 -0
  41. package/dist/context-menu.css +64 -0
  42. package/dist/context-menu.css.map +1 -0
  43. package/dist/context-menu.d.cts +25 -0
  44. package/dist/context-menu.d.ts +25 -0
  45. package/dist/context-menu.js +5 -0
  46. package/dist/context-menu.js.map +1 -0
  47. package/dist/controls.css +155 -0
  48. package/dist/controls.css.map +1 -0
  49. package/dist/dialog.cjs +222 -0
  50. package/dist/dialog.cjs.map +1 -0
  51. package/dist/dialog.css +55 -0
  52. package/dist/dialog.css.map +1 -0
  53. package/dist/dialog.d.cts +28 -0
  54. package/dist/dialog.d.ts +28 -0
  55. package/dist/dialog.js +5 -0
  56. package/dist/dialog.js.map +1 -0
  57. package/dist/floating.cjs +83 -0
  58. package/dist/floating.cjs.map +1 -0
  59. package/dist/floating.d.cts +25 -0
  60. package/dist/floating.d.ts +25 -0
  61. package/dist/floating.js +4 -0
  62. package/dist/floating.js.map +1 -0
  63. package/dist/identity.css +15 -50
  64. package/dist/identity.css.map +1 -1
  65. package/dist/index.cjs +1322 -258
  66. package/dist/index.cjs.map +1 -1
  67. package/dist/index.d.cts +349 -131
  68. package/dist/index.d.ts +349 -131
  69. package/dist/index.js +259 -808
  70. package/dist/index.js.map +1 -1
  71. package/dist/job.css +23 -0
  72. package/dist/job.css.map +1 -1
  73. package/dist/link-status.cjs +430 -0
  74. package/dist/link-status.cjs.map +1 -0
  75. package/dist/link-status.css +76 -0
  76. package/dist/link-status.css.map +1 -0
  77. package/dist/link-status.d.cts +53 -0
  78. package/dist/link-status.d.ts +53 -0
  79. package/dist/link-status.js +5 -0
  80. package/dist/link-status.js.map +1 -0
  81. package/dist/menu.cjs +62 -1
  82. package/dist/menu.cjs.map +1 -1
  83. package/dist/menu.d.cts +88 -1
  84. package/dist/menu.d.ts +88 -1
  85. package/dist/menu.js +1 -1
  86. package/dist/notes-editor.css +3 -46
  87. package/dist/notes-editor.css.map +1 -1
  88. package/dist/pane-shell.css +16 -3
  89. package/dist/pane-shell.css.map +1 -1
  90. package/dist/property-list-D3VZCJzi.d.cts +162 -0
  91. package/dist/property-list-D3VZCJzi.d.ts +162 -0
  92. package/dist/property-list.cjs +784 -0
  93. package/dist/property-list.cjs.map +1 -0
  94. package/dist/property-list.d.cts +1 -0
  95. package/dist/property-list.d.ts +1 -0
  96. package/dist/property-list.js +4 -0
  97. package/dist/property-list.js.map +1 -0
  98. package/dist/settings.css +63 -8
  99. package/dist/settings.css.map +1 -1
  100. package/dist/stale.css +3 -0
  101. package/dist/stale.css.map +1 -1
  102. package/dist/stepper.css +35 -0
  103. package/dist/stepper.css.map +1 -1
  104. package/dist/tokens.css +19 -0
  105. package/dist/tokens.css.map +1 -1
  106. package/dist/tool-window.cjs +63 -0
  107. package/dist/tool-window.cjs.map +1 -1
  108. package/dist/tool-window.css +6 -3
  109. package/dist/tool-window.css.map +1 -1
  110. package/dist/tool-window.d.cts +67 -1
  111. package/dist/tool-window.d.ts +67 -1
  112. package/dist/tool-window.js +63 -1
  113. package/dist/tool-window.js.map +1 -1
  114. package/dist/updater-view.cjs +283 -0
  115. package/dist/updater-view.cjs.map +1 -0
  116. package/dist/updater-view.d.cts +23 -0
  117. package/dist/updater-view.d.ts +23 -0
  118. package/dist/updater-view.js +6 -0
  119. package/dist/updater-view.js.map +1 -0
  120. package/dist/updater.cjs +12 -16
  121. package/dist/updater.cjs.map +1 -1
  122. package/dist/updater.css +44 -0
  123. package/dist/updater.css.map +1 -0
  124. package/dist/updater.js +12 -16
  125. package/dist/updater.js.map +1 -1
  126. package/dist/util.cjs +78 -0
  127. package/dist/util.cjs.map +1 -0
  128. package/dist/util.d.cts +37 -0
  129. package/dist/util.d.ts +37 -0
  130. package/dist/util.js +3 -0
  131. package/dist/util.js.map +1 -0
  132. package/dist/vc.css +3 -2
  133. package/dist/vc.css.map +1 -1
  134. package/dist/welcome.cjs +215 -0
  135. package/dist/welcome.cjs.map +1 -0
  136. package/dist/welcome.css +112 -0
  137. package/dist/welcome.css.map +1 -0
  138. package/dist/welcome.d.cts +55 -0
  139. package/dist/welcome.d.ts +55 -0
  140. package/dist/welcome.js +4 -0
  141. package/dist/welcome.js.map +1 -0
  142. package/package.json +58 -2
  143. package/dist/chunk-QDPFU4WX.js +0 -43
  144. package/dist/chunk-QDPFU4WX.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,61 +1,26 @@
1
- export { EDIT_MENU, GO_MENU, MenuLabel, PANE_MENU } from './menu.js';
1
+ export { EDIT_MENU, FILE_MENU, GO_MENU, MenuLabel, PANE_MENU, PLAY_MENU, PUBLISH_MENU, REVIEW_MENU, VIEW_MENU } from './menu.js';
2
+ export { D as DefaultControlOptions, a as DupGuard, P as PROPERTY_TYPES, b as PropertyDeclLike, c as PropertyListHandle, d as PropertyListOptions, e as PropertyListType, S as SettingsDialog, f as SettingsDialogOptions, g as SettingsSection, h as SettingsSectionHandle, i as defaultControl, j as dupGuard, k as expandableRow, l as focusNewRow, m as mountPropertyList, n as mountSettingsDialog, r as revealRow, o as revealRowWhenReady } from './property-list-D3VZCJzi.js';
3
+ export { DialogFrame, DialogFrameOptions, dialogFrame } from './dialog.js';
4
+ export { Floating, FloatingOptions, createFloating } from './floating.js';
5
+ export { ContextItem, PopoverOptions, openContextMenu, openPopover } from './context-menu.js';
2
6
  export { JOB_PROGRESS, JobProgress } from './job.js';
7
+ export { UpdaterLink, UpdaterViewOptions, feedUpdaterDownloadProgress, showUpdaterDialog } from './updater-view.js';
8
+ export { UpdaterDownloadProgress, UpdaterPromptOptions } from './updater.js';
9
+ export { Welcome, WelcomeAction, WelcomeGroup, WelcomeGroupItem, WelcomeOptions, WelcomeRecent, mountWelcome } from './welcome.js';
10
+ export { LinkStatus, LinkStatusChip, LinkStatusClass, LinkStatusOptions, linkAddress, linkStatusClass, linkStatusTip, mountLinkStatus } from './link-status.js';
11
+ export { Debounced, copyWithFeedback, debounce, formatCount, isEditableTarget, plural, relativeTime } from './util.js';
12
+ import './app-store.js';
13
+ import 'electron';
3
14
 
4
- type Child = Node | string | null | undefined;
5
- interface ElProps {
6
- className?: string;
7
- text?: string;
8
- /** A NATIVE rollover. Prefer `tip`: `title` is OS chrome on the platform's own
9
- * slow delay, which is the seam the themed tooltip exists to close. */
10
- title?: string;
11
- /** A themed rollover (`data-tip`, picked up by the delegated controller that
12
- * `initTooltips()` wires). Also the accessible name, since the elements that
13
- * want one are usually icon buttons with no text of their own. */
14
- tip?: string;
15
- onClick?: (event: MouseEvent) => void;
16
- }
17
- declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, classNameOrProps?: string | ElProps, ...children: Child[]): HTMLElementTagNameMap[K];
18
- /** A small square glyph button (the move/delete controls list editors share).
19
- * Its rollover is the THEMED one (`data-tip`), so an app that has called
20
- * `initTooltips()` gets our bubble rather than the platform's. */
21
- declare function iconBtn(glyph: string, title: string, onClick: () => void, disabled?: boolean, danger?: boolean): HTMLButtonElement;
22
15
  /**
23
- * A captioned field: `<label class="shell-labelled"><span class="shell-fieldcap">…</span>control</label>`.
16
+ * The typed glyphs the vocabulary used to draw.
24
17
  *
25
- * The label is pointed at the field it captions, EXPLICITLY, and never at a button. A `<label>` with no
26
- * `for` forwards a click to its first labelable descendant, and buttons are labelable - so a caption
27
- * wrapped around a control containing buttons (`tagChips`, whose every chip carries a remove button)
28
- * turns every click on the row's dead space into a press of the FIRST button in it. That shipped:
29
- * patterkit/patter#44, where clicking a Game Data list value anywhere but its ✕ deleted the first value
30
- * in the list. Reported as "clicking a value removes the wrong one", which is not a wrong-index bug at
31
- * all - the click never reached the chip.
32
- *
33
- * When the control holds no labelable field (a chips editor with the add input removed, say), this is a
34
- * plain `<div>`: no caption behaviour is better than a caption that presses something.
35
- */
36
- declare function labelled(label: string, control: HTMLElement): HTMLElement;
37
- /** Swap item `i` with its neighbour `i + delta` IN PLACE (the up/down reorder list editors share);
38
- * a no-op when the target is out of range. Returns whether anything moved. */
39
- declare function moveItem<T>(arr: T[], i: number, delta: number): boolean;
40
- /** A tag-style editor for a string-list field (an enum's allowed values / flags): removable chips + an
41
- * add input (Enter or "," commits; blank / duplicate ignored). Mutates `holder.values` in place (read
42
- * back on save). `onChange` fires after any add / remove so callers can refresh a dependent control. */
43
- declare function tagChips(holder: {
44
- values?: string[];
45
- }, onChange?: () => void): HTMLElement;
46
-
47
- /** Curated colour slots; must match the `--char-*` set defined per theme. */
48
- declare const PALETTE_SIZE = 12;
49
- declare const PALETTE: readonly string[];
50
- /** Hash a name to a palette slot: FNV-1a + an fmix32 avalanche, well
51
- * distributed even for short, similar names. */
52
- declare function colourIndex(name: string): number;
53
- /** The theme-aware CSS variable reference for a name's slot. */
54
- declare function colourFor(name: string): string;
55
-
56
- /**
57
- * Named by what it MEANS. Grouped by the job, because that is how a caller
58
- * looks for one.
18
+ * @deprecated Since the move to drawn icons (design-language.md, "Icons are
19
+ * drawn"). Kept byte-for-byte so a consumer still doing
20
+ * `textContent = icon.close` keeps working while each app moves; a new call
21
+ * site uses `iconNode(name)` (a node to append) or `iconHtml(name)` (for an
22
+ * innerHTML build). The names are the same in all three. Removed once both
23
+ * apps have moved.
59
24
  */
60
25
  declare const icon: {
61
26
  /** Close a panel, a dialog, a popover. Patterpad's, twelve times over. */
@@ -117,24 +82,187 @@ declare const icon: {
117
82
  * comments are the conversation, and the two must never look alike. */
118
83
  readonly comment: "❝";
119
84
  };
120
- type IconName = keyof typeof icon;
121
85
  /**
122
- * The few shapes no glyph says honestly, as inline SVG.
123
- *
124
- * `currentColor` throughout, so they take the colour of the button they sit in
125
- * exactly as the glyphs do, and sized in the markup for the same reason: an icon
126
- * that needs a CSS rule to be the right size is an icon that will be the wrong
127
- * size somewhere.
86
+ * Every word in the vocabulary. The 26 the glyph table had, plus the seven the
87
+ * 2026-09 review found the apps drawing by hand with nothing to check against.
88
+ */
89
+ type IconName = keyof typeof icon
90
+ /** History back, the topbar's quiet arrow pair (nav-history.ts). An ARROW,
91
+ * not a chevron: a chevron says structure, an arrow says time (the author's
92
+ * ruling, 2026-08-28). Added beside `arrowRight` so the pair share a set. */
93
+ | "arrowLeft"
94
+ /** History forward; also the one honest "go there" arrow where a link used
95
+ * to carry a typed one. */
96
+ | "arrowRight"
97
+ /** Connect to a server, a live link, a running game. */
98
+ | "connect"
99
+ /** Search. */
100
+ | "search"
101
+ /** Keep this window on top (tool-window-web.ts). */
102
+ | "pin"
103
+ /** Settings. */
104
+ | "settings"
105
+ /** Record. The icon is the DISC; painting it in the danger colour while
106
+ * recording is the caller's job, through the button's own colour. */
107
+ | "record";
108
+ /**
109
+ * The drawings as strings, by word, for a caller that builds markup itself.
110
+ * Prefer `iconHtml(name, size)`, which sizes it; these carry no width/height.
128
111
  *
129
- * Lifted from Patterpad, which drew them first.
112
+ * Two legacy entries ride along unchanged: `noteFilled` and `noteOutline`, the
113
+ * has-notes / no-notes pair Patterpad drew on a 16-unit grid before the set
114
+ * existed. They are sized in the markup (13px) and on their own grid, so they
115
+ * are NOT part of the vocabulary above and `iconNode` does not know them; no
116
+ * app uses them today. They stay until a 24-grid pair replaces them, because
117
+ * their two-state meaning (there are notes / there are none) has no Lucide
118
+ * shape, and re-weighting the 16-grid drawing to the family stroke merges its
119
+ * text lines.
130
120
  */
131
121
  declare const iconSvg: {
132
- /** A document with lines: notes exist on this thing. */
122
+ /** A document with lines: notes exist on this thing. (legacy, 16 grid) */
133
123
  readonly noteFilled: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"currentColor\" fill-rule=\"evenodd\" aria-hidden=\"true\"><path d=\"M3.4 1.4h5.3L12.6 5.3v8.1a1.2 1.2 0 0 1-1.2 1.2H3.4a1.2 1.2 0 0 1-1.2-1.2V2.6A1.2 1.2 0 0 1 3.4 1.4ZM4.9 6.8h6.2v1.1H4.9Zm0 2.1h6.2v1.1H4.9Zm0 2.1h3.7v1.1H4.9Z\"/></svg>";
134
- /** The same document, empty: nothing written here yet. */
124
+ /** The same document, empty: nothing written here yet. (legacy, 16 grid) */
135
125
  readonly noteOutline: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.1\" stroke-linejoin=\"round\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M3.4 2h4.9l3.9 3.9v7.5a1 1 0 0 1-1 1H3.4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1Z\"/><path d=\"M8.3 2.2v3.8h3.8\"/><path d=\"M4.9 8h6.2M4.9 10h6.2M4.9 12h3.7\" stroke-width=\"0.95\"/></svg>";
126
+ readonly search: string;
127
+ readonly close: string;
128
+ readonly play: string;
129
+ readonly more: string;
130
+ readonly collapsed: string;
131
+ readonly expanded: string;
132
+ readonly dropdown: string;
133
+ readonly back: string;
134
+ readonly forward: string;
135
+ readonly up: string;
136
+ readonly down: string;
137
+ readonly tick: string;
138
+ readonly locked: string;
139
+ readonly readOnly: string;
140
+ readonly checkedOut: string;
141
+ readonly modified: string;
142
+ readonly untracked: string;
143
+ readonly warning: string;
144
+ readonly dot: string;
145
+ readonly restart: string;
146
+ readonly viewCards: string;
147
+ readonly viewTable: string;
148
+ readonly viewNode: string;
149
+ readonly grip: string;
150
+ readonly add: string;
151
+ readonly note: string;
152
+ readonly comment: string;
153
+ readonly arrowLeft: string;
154
+ readonly arrowRight: string;
155
+ readonly connect: string;
156
+ readonly pin: string;
157
+ readonly settings: string;
158
+ readonly record: string;
136
159
  };
137
160
  type IconSvgName = keyof typeof iconSvg;
161
+ /** Every word, in table order, for a test or a gallery to walk. */
162
+ declare const ICON_NAMES: IconName[];
163
+ /** The default size: 14px, the family's icon inside its small square button. */
164
+ declare const ICON_SIZE = 14;
165
+ /**
166
+ * The drawing as markup with its size set, for a caller building innerHTML.
167
+ * `data-icon` carries the word, so a stylesheet or a test can find it.
168
+ */
169
+ declare function iconHtml(name: IconName, size?: number): string;
170
+ /**
171
+ * The drawing as a fresh SVG node, sized. Append it where the glyph used to be
172
+ * set as text; it is `aria-hidden`, so the button's accessible name stays the
173
+ * button's (`aria-label` / `data-tip`), as it always had to.
174
+ */
175
+ declare function iconNode(name: IconName, size?: number): SVGSVGElement;
176
+ /** The word a deprecated glyph stood for, or undefined for any other string. */
177
+ declare function iconNameOfGlyph(glyph: string): IconName | undefined;
178
+ /** Whether a string is a word in the vocabulary. */
179
+ declare function isIconName(s: string): s is IconName;
180
+
181
+ type Child = Node | string | null | undefined;
182
+ interface ElProps {
183
+ className?: string;
184
+ text?: string;
185
+ /** A NATIVE rollover. Prefer `tip`: `title` is OS chrome on the platform's own
186
+ * slow delay, which is the seam the themed tooltip exists to close. */
187
+ title?: string;
188
+ /** A themed rollover (`data-tip`, picked up by the delegated controller that
189
+ * `initTooltips()` wires). Also the accessible name, since the elements that
190
+ * want one are usually icon buttons with no text of their own. */
191
+ tip?: string;
192
+ onClick?: (event: MouseEvent) => void;
193
+ }
194
+ declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, classNameOrProps?: string | ElProps, ...children: Child[]): HTMLElementTagNameMap[K];
195
+ /** A small square icon button (the move/delete controls list editors share).
196
+ * Its rollover is the THEMED one (`data-tip`), so an app that has called
197
+ * `initTooltips()` gets our bubble rather than the platform's.
198
+ *
199
+ * `name` is a word from the vocabulary ("up", "close"), drawn at 14px. A
200
+ * typed glyph from the deprecated `icon` table ("↑", "✕") is accepted for
201
+ * now and drawn as the word it stood for, so an app that has not moved its
202
+ * call sites yet still gets the drawn set; any other string is set as text,
203
+ * which is the old behaviour and on its way out. */
204
+ declare function iconBtn(name: IconName | (string & {}), title: string, onClick: () => void, disabled?: boolean, danger?: boolean): HTMLButtonElement;
205
+ /**
206
+ * A captioned field: `<label class="shell-labelled"><span class="shell-fieldcap">…</span>control</label>`.
207
+ *
208
+ * The label is pointed at the field it captions, EXPLICITLY, and never at a button. A `<label>` with no
209
+ * `for` forwards a click to its first labelable descendant, and buttons are labelable - so a caption
210
+ * wrapped around a control containing buttons (`tagChips`, whose every chip carries a remove button)
211
+ * turns every click on the row's dead space into a press of the FIRST button in it. That shipped:
212
+ * patterkit/patter#44, where clicking a Game Data list value anywhere but its ✕ deleted the first value
213
+ * in the list. Reported as "clicking a value removes the wrong one", which is not a wrong-index bug at
214
+ * all - the click never reached the chip.
215
+ *
216
+ * When the control holds no labelable field (a chips editor with the add input removed, say), this is a
217
+ * plain `<div>`: no caption behaviour is better than a caption that presses something.
218
+ */
219
+ declare function labelled(label: string, control: HTMLElement): HTMLElement;
220
+ /** Swap item `i` with its neighbour `i + delta` IN PLACE (the up/down reorder list editors share);
221
+ * a no-op when the target is out of range. Returns whether anything moved. */
222
+ declare function moveItem<T>(arr: T[], i: number, delta: number): boolean;
223
+ /** A tag-style editor for a string-list field (an enum's allowed values / flags): removable chips + an
224
+ * add input (Enter or "," commits; blank / duplicate ignored). Mutates `holder.values` in place (read
225
+ * back on save). `onChange` fires after any add / remove so callers can refresh a dependent control. */
226
+ declare function tagChips(holder: {
227
+ values?: string[];
228
+ }, onChange?: () => void): HTMLElement;
229
+ /**
230
+ * An ORDERED chips editor for a string-list field whose order means something
231
+ * (a quality's stages: `advance()` walks them, so 2 comes after 1): numbered
232
+ * chips, each with move-earlier / move-later / remove, plus an add input
233
+ * (Enter or "," commits; blank / duplicate ignored). Mutates `holder.stages`
234
+ * in place; `onChange` fires after any add / move / remove.
235
+ *
236
+ * Patterpad's, lifted (ui-review-2026-09, finding 3); Storyletter had been
237
+ * faking it with `tagChips` over a `values` holder, which loses the order
238
+ * controls. Same class family as `tagChips` (`.shell-tags`), so it is styled by
239
+ * settings.css already.
240
+ */
241
+ declare function stageChips(holder: {
242
+ stages?: string[];
243
+ }, onChange?: () => void): HTMLElement;
244
+ /**
245
+ * Make `el` a drag source and a drop target for HTML5 drag-to-reorder among
246
+ * its siblings. While carried it wears `.dragging`; a hovered target wears
247
+ * `.drop-before` or `.drop-after` by the midpoint test on `axis` ("y" for
248
+ * rows, "x" for a wrapping grid); a drop calls `onMove(draggedId, before,
249
+ * targetId)` and the caller reorders its model and re-renders.
250
+ *
251
+ * Storyletter's `wireCardDrag`, lifted (ui-review-2026-09, finding 30);
252
+ * Patterpad's `wireSceneDrag` is the same idea fixed on "y". The marks are
253
+ * classes only: the app draws them (Patterpad's `box-shadow: 0 -2px 0 0
254
+ * var(--accent)` pair is the family's look).
255
+ */
256
+ declare function wireReorder(el: HTMLElement, id: string, axis: "x" | "y", onMove: (id: string, before: boolean, targetId: string) => void): void;
257
+
258
+ /** Curated colour slots; must match the `--char-*` set defined per theme. */
259
+ declare const PALETTE_SIZE = 12;
260
+ declare const PALETTE: readonly string[];
261
+ /** Hash a name to a palette slot: FNV-1a + an fmix32 avalanche, well
262
+ * distributed even for short, similar names. */
263
+ declare function colourIndex(name: string): number;
264
+ /** The theme-aware CSS variable reference for a name's slot. */
265
+ declare function colourFor(name: string): string;
138
266
 
139
267
  type PaneSide = "nav" | "inspector";
140
268
  interface PaneSideConfig {
@@ -168,6 +296,17 @@ interface PaneShellOptions {
168
296
  initial?: Partial<PaneShellState>;
169
297
  /** Persist state after any toggle or resize (host wires it to its store). */
170
298
  onChange?: (state: PaneShellState) => void;
299
+ /** The toggle's rollover for a side in a given state. Default: "Show
300
+ * navigator" / "Hide navigator" from the side's `label`, with the
301
+ * `shortcutHint` in brackets. Written as `data-tip` (the themed tooltip)
302
+ * and as the accessible name; never as `title`. */
303
+ tipFor?: (side: PaneSide, open: boolean) => string;
304
+ /** An extra class per side, toggled on the `.panes` grid whenever that side
305
+ * is collapsed, beside the shell's own `no-nav` / `no-inspector`. For an
306
+ * app whose stylesheet already keys on its own name for the state
307
+ * (Patterpad's `props-doc-open`), so its rules ride the shell's collapse
308
+ * rather than a second copy of it. */
309
+ collapseAlso?: Partial<Record<PaneSide, string>>;
171
310
  }
172
311
  interface PaneShell {
173
312
  /** The frame element (top bar + panes); mount it into the app container. */
@@ -182,7 +321,13 @@ interface PaneShell {
182
321
  readonly inspector: HTMLElement;
183
322
  togglePane(side: PaneSide): void;
184
323
  setPaneOpen(side: PaneSide, open: boolean): void;
324
+ /** Whether the pane is showing: open, and not held closed. */
185
325
  isOpen(side: PaneSide): boolean;
326
+ /** Fold a side away WITHOUT changing what the person chose: a mode that has
327
+ * no use for the inspector (Patterpad's properties page) holds it closed,
328
+ * and releasing the hold restores whatever was remembered. Not persisted;
329
+ * the toggle keeps reporting the remembered state. */
330
+ holdClosed(side: PaneSide, held: boolean): void;
186
331
  /** Clear dragged widths back to the config defaults. */
187
332
  resetWidths(): void;
188
333
  /** A snapshot of the current state (open + widths). */
@@ -191,60 +336,6 @@ interface PaneShell {
191
336
  }
192
337
  declare function mountPaneShell(host: HTMLElement, opts: PaneShellOptions): PaneShell;
193
338
 
194
- /** A compact list row: a single `.set-rowline` (inline controls) plus, when
195
- * `details` are given, a disclosure that reveals a `.set-details` panel below,
196
- * so a list scans like a table with secondary fields tucked away. */
197
- declare function expandableRow(opts: {
198
- line: HTMLElement[];
199
- details?: HTMLElement[];
200
- }): HTMLElement;
201
- /** After a "+ Add" re-renders a list, bring the new (last) row into view and
202
- * focus its name input (`.set-name`, else the first input). */
203
- declare function focusNewRow(listEl: HTMLElement | null | undefined): void;
204
- interface DupGuard {
205
- /** Forget the previous render's inputs (call at the top of render). */
206
- reset(): void;
207
- /** Register a name input; `key` returns the comparison string (default the
208
- * input's value). Re-checks live on input. `also` = extra inputs that change the key. */
209
- track(input: HTMLInputElement, key?: () => string, also?: HTMLInputElement[]): void;
210
- /** Re-paint duplicates; true if any two non-blank keys collide. */
211
- check(): boolean;
212
- /** The first input currently flagged (after a fresh check), or null. */
213
- firstDuplicate(): HTMLInputElement | null;
214
- }
215
- /** Case-insensitive duplicate-name detection: paints clashing inputs `.invalid`
216
- * and lets a Save gate block + jump via `firstDuplicate()`. */
217
- declare function dupGuard(): DupGuard;
218
- interface SettingsSectionHandle {
219
- /** The first invalid control to jump to, blocking Save (e.g. a duplicate name). */
220
- firstInvalid?(): HTMLElement | null;
221
- }
222
- interface SettingsSection {
223
- id: string;
224
- label: string;
225
- /** Optional left-rail group heading above this tab. */
226
- group?: string;
227
- /** Build the section body into `host`; called fresh on each open so it reads
228
- * the current data. Return a handle (for the Save gate). */
229
- mount(host: HTMLElement): SettingsSectionHandle;
230
- }
231
- interface SettingsDialogOptions {
232
- title: string;
233
- sections: SettingsSection[];
234
- /** Fired when Save is clicked and every section validates. The app reads its
235
- * own section state and persists. May be async; the dialog stays open until
236
- * it resolves, then closes. */
237
- onSave: () => void | Promise<void>;
238
- }
239
- interface SettingsDialog {
240
- /** Open (optionally on a given tab id). Sections re-mount from current data. */
241
- open(tabId?: string): void;
242
- destroy(): void;
243
- }
244
- /** Build a settings dialog. The frame owns the modal, the tab rail, save-on-Save
245
- * (with a validation gate), and Cancel; the sections own their content + state. */
246
- declare function mountSettingsDialog(opts: SettingsDialogOptions): SettingsDialog;
247
-
248
339
  interface PinButtonOptions {
249
340
  pinned: boolean;
250
341
  /** Persist the choice. The button reflects the click itself. */
@@ -266,11 +357,93 @@ interface PinButton {
266
357
  /** The always-on-top pin: `.swin-pin`, aria-pressed, and a tooltip that says
267
358
  * what a click will do (the richest of the family's variants, kept). */
268
359
  declare function pinButton(opts: PinButtonOptions): PinButton;
360
+ interface ToolWindowHeadOptions {
361
+ /** The window's own name. Constant: it says which window this is, so it must
362
+ * not change as projects open and close. Omitted by a window whose mode
363
+ * tabs stand where the title would be (Find): pass them as `tabs`. */
364
+ title?: string;
365
+ /** Mode tabs in the title's place (Find's bar: modes left, pin and close right). */
366
+ tabs?: HTMLElement;
367
+ /** A pin BUILT ALREADY, for a window that mounts its chrome once and drives
368
+ * the button with `pin.set(on)` rather than rebuilding the bar. That is the
369
+ * better shape (no re-entrancy question, survives a partial repaint), so the
370
+ * helper takes either. */
371
+ pin?: {
372
+ el: HTMLElement;
373
+ };
374
+ pinned?: boolean;
375
+ onPin?: (on: boolean) => void;
376
+ onClose: () => void;
377
+ /** Between the title and the spacer: a subtitle, the project name. */
378
+ lead?: (Node | string | null)[];
379
+ /** Between the spacer and the pin: this window's own controls. */
380
+ trail?: (Node | string | null)[];
381
+ /** An extra class beside `swin-head`, for a window that styles its own bar. */
382
+ className?: string;
383
+ /**
384
+ * Escape closes the window (default true): the family's rule, written once
385
+ * here rather than per window.
386
+ *
387
+ * The listener stands aside on its own for a field's Escape (an input,
388
+ * textarea, select or contenteditable target: clearing, cancelling an edit)
389
+ * and for an Escape a deeper handler has already `preventDefault()`ed.
390
+ * `"always"` drops the first of those: a window whose focus LIVES in a
391
+ * field (Find, whose query box is where the typing is) still closes on
392
+ * Escape from it. `false` turns the listener off for a window that owns
393
+ * Escape entirely; it calls `onClose` itself when nothing smaller is left.
394
+ */
395
+ esc?: boolean | "always";
396
+ /**
397
+ * A window that layers Escape (a selection to clear, a pending outcome to
398
+ * drop, a sweep that must not be closed out from under) says so here:
399
+ * return true when the key was consumed and the window stays open, false
400
+ * to let it close. Innermost first; the window is last.
401
+ */
402
+ onEscape?: () => boolean;
403
+ }
404
+ /** The `.swin-head` bar every tool window wears: title (or tabs), lead, spacer,
405
+ * trail, pin, close. The close button says "Close (Esc)" once, here, rather
406
+ * than in every window. */
407
+ declare function toolWindowHead(opts: ToolWindowHeadOptions): HTMLElement;
408
+ interface FollowButtonOptions {
409
+ on: boolean;
410
+ /** Persist the choice. The button reflects the click itself. */
411
+ onToggle: (on: boolean) => void;
412
+ /** What the editor opens as the run goes: "line" (Patterpad), "card"
413
+ * (Storyletter). Default "line". */
414
+ what?: string;
415
+ }
416
+ /** A toggle and the handle to drive it, the same shape `pinButton` returns. */
417
+ interface FollowButton {
418
+ el: HTMLButtonElement;
419
+ /** Reflect a state decided elsewhere, without calling `onToggle`. */
420
+ set: (on: boolean) => void;
421
+ }
422
+ /**
423
+ * "Follow in the editor": the play surface's toggle that makes the EDITOR
424
+ * follow the run, opening each line / card as it plays. Both play surfaces
425
+ * built one (patterpad play.ts, studio table.ts) with the same label and
426
+ * paraphrased tips; this is the one wording.
427
+ *
428
+ * The LABEL is the careful part. "Follow" alone would match the vocabulary of
429
+ * a window that follows the editor; this is the other direction, the editor
430
+ * following the run. Off by default and remembered by the host: following is
431
+ * the author asking for it.
432
+ */
433
+ declare function followButton(opts: FollowButtonOptions): FollowButton;
269
434
 
270
435
  interface ConfirmOptions {
271
436
  title: string;
272
- body: string;
273
- /** The destructive button's label ("Delete box", "Restart"). */
437
+ /** One plain sentence, ending with what undo does. */
438
+ body?: string;
439
+ /**
440
+ * Content that is evidence rather than a sentence: a list of the scenes that
441
+ * refer to the thing, a warning line. Rendered after `body` when both are
442
+ * given. The caller styles it; the frame's body is a column with a gap.
443
+ */
444
+ bodyNode?: HTMLElement;
445
+ /** The destructive button's label, which names its object ("Delete scene",
446
+ * "Remove language"). */
274
447
  confirmLabel: string;
275
448
  }
276
449
  /** Resolves true on confirm; false on Cancel / Esc / backdrop. */
@@ -335,6 +508,11 @@ interface AnchoredPanelOptions {
335
508
  ignoreDown?: string;
336
509
  /** While something matching this exists, Escape belongs to it, not to us. */
337
510
  deferEscape?: string;
511
+ /** Where the panel is appended. Default <body>. A panel opened from inside a
512
+ * modal <dialog> passes the dialog: a body-level node sits behind the scrim,
513
+ * inert, while one inside the dialog paints in its top layer. Placement is
514
+ * viewport-relative either way (`position: fixed`). */
515
+ host?: HTMLElement;
338
516
  onClose?: () => void;
339
517
  }
340
518
  declare function openAnchoredPanel(opts: AnchoredPanelOptions): AnchoredPanel | null;
@@ -363,7 +541,7 @@ declare function isValidGameId(gameId: string): boolean;
363
541
  * app probes the real parser to prove the list is still true. */
364
542
  declare const RESERVED_PROPERTY_NAMES: readonly string[];
365
543
  /** What an editor should tell an author, in one line. */
366
- declare const PROPERTY_NAME_HINT = "Lower case letters, digits and underscores. Cannot start with a digit. No hyphens or spaces.";
544
+ declare const PROPERTY_NAME_HINT = "Lower case letters, digits, and underscores. It can't start with a digit. No hyphens or spaces.";
367
545
  /**
368
546
  * Coerce a label into a legal property name: lower case, apostrophes dropped,
369
547
  * runs of anything else to a single underscore, no trailing underscore, an
@@ -505,14 +683,20 @@ type VcMap = ReadonlyMap<string, ShardVc>;
505
683
  * box + tags + hands shards), most actionable winning: any holder locks it,
506
684
  * any stale shard makes it stale, any read-only shard makes it read-only. */
507
685
  declare function foldVc(shards: VcMap, keys: string | undefined): ShardVc | undefined;
508
- /** The one badge an item flies, by priority, or null when there is nothing to
509
- * say. Monochrome typographic glyphs (never colour emoji), so each inherits
510
- * its themed colour and reads the same in Linen and Baize. */
511
- declare function vcBadgeFor(s: ShardVc | undefined): {
686
+ /** What a row flies: the word to draw (`name`), the class that colours it,
687
+ * and the rollover. `glyph` is the deprecated typed character for a caller
688
+ * still setting text; it goes when `icon` does. */
689
+ interface VcBadge {
690
+ name: IconName;
691
+ /** @deprecated Draw `name` with `iconNode` instead. */
512
692
  glyph: string;
513
693
  cls: string;
514
694
  title: string;
515
- } | null;
695
+ }
696
+ /** The one badge an item flies, by priority, or null when there is nothing to
697
+ * say. Drawn monochrome (never colour emoji), so each inherits its themed
698
+ * colour and reads the same in Linen and Baize. */
699
+ declare function vcBadgeFor(s: ShardVc | undefined): VcBadge | null;
516
700
  /** Repaint the badge on every item under `root` that names a shard (its
517
701
  * `data-vc`). In place: no re-render, so nothing loses focus or scroll. */
518
702
  declare function paintVcBadges(root: ParentNode, shards: VcMap): void;
@@ -532,6 +716,21 @@ interface IdentityOptions {
532
716
  current?: Identity;
533
717
  /** A name to offer when there is none: the VCS's, one day. */
534
718
  suggested?: Identity;
719
+ /**
720
+ * `"welcome"` is the first run ("Welcome to <appName>", Continue); `"edit"`
721
+ * is the menu item ("User information", Save). Absent, the title is the
722
+ * shell's "Who is working here?" and the button is Save.
723
+ */
724
+ mode?: "welcome" | "edit";
725
+ /** Names the app in the welcome title. Without it the title is "Welcome". */
726
+ appName?: string;
727
+ /**
728
+ * By default a skip resolves `undefined` and the app asks again next launch.
729
+ * With this on, a skip resolves `{ name: "" }`: the app stores the blank and
730
+ * never asks again (Patterpad's first run, where main fills in a default).
731
+ * A Save with an empty name counts as a skip either way.
732
+ */
733
+ neverAskAgainOnSkip?: boolean;
535
734
  }
536
735
  /** Ask, and record the answer. Resolves when the dialog closes, either way. */
537
736
  declare function askIdentity(opts?: IdentityOptions): Promise<Identity | undefined>;
@@ -633,23 +832,42 @@ interface CommentsOptions {
633
832
  }
634
833
  declare function openComments(opts: CommentsOptions): void;
635
834
 
835
+ /** Per-run overrides for a strip that serves several kinds of job (the main
836
+ * window's: a countable export beside an uncountable pull). Each begin()
837
+ * starts from the mount options and applies these on top. */
838
+ interface JobBeginOptions {
839
+ /** Pin the band regardless of what the job reports. */
840
+ indeterminate?: boolean;
841
+ /** Show Cancel for this run. */
842
+ cancellable?: boolean;
843
+ }
636
844
  interface JobProgressView {
637
845
  /** The strip; mount it where the job belongs. */
638
846
  readonly element: HTMLElement;
639
847
  /** Show the strip, reset to zero, and (optionally) name what is running. */
640
- begin(label?: string): void;
641
- /** Feed it a progress message from the main-side host. */
848
+ begin(label?: string, run?: JobBeginOptions): void;
849
+ /** Feed it a progress message from the main-side host. A `total` of 0
850
+ * (the host's "no idea yet") draws the indeterminate band; the first
851
+ * positive total flips the strip to the determinate bar. */
642
852
  update(done: number, total: number, elapsedMs: number): void;
643
853
  /** Hide the strip (the job finished, failed, or was cancelled). */
644
854
  end(): void;
645
855
  readonly visible: boolean;
856
+ /** True while the strip is drawing the band rather than the bar. */
857
+ readonly indeterminate: boolean;
646
858
  }
647
859
  interface JobProgressOptions {
648
860
  /** Called when Cancel is clicked. The button disables itself and says so;
649
- * the job stops at its next yield, and the host then calls end(). */
650
- onCancel: () => void;
861
+ * the job stops at its next yield, and the host then calls end(). Omit it
862
+ * for a strip whose jobs cannot be stopped: Cancel is then not drawn. */
863
+ onCancel?: () => void;
651
864
  /** The word for the units, for the count readout (default "runs"). */
652
865
  units?: string;
866
+ /** Always draw the band and never the count, whatever the job reports.
867
+ * Default: follow the data (band until a total arrives). */
868
+ indeterminate?: boolean;
869
+ /** Draw Cancel. Default: only when `onCancel` was given. */
870
+ cancellable?: boolean;
653
871
  }
654
872
  declare function mountJobProgress(host: HTMLElement, opts: JobProgressOptions): JobProgressView;
655
873
 
@@ -900,4 +1118,4 @@ declare function historyNav(onBack: () => void, onForward: () => void): {
900
1118
  set(canBack: boolean, canForward: boolean): void;
901
1119
  };
902
1120
 
903
- export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type NavHistory, type NavHistoryOptions, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, type PropertyRefOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type ToastKind, type ToastOptions, type TooltipOptions, type VcMap, askIdentity, bindPropertyName, bindPropertyRef, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createNavHistory, createSaveController, dismissToast, dupGuard, el, expandableRow, firstIllegalPropertyName, focusNewRow, foldVc, gameIdify, hideTip, historyNav, icon, iconBtn, iconSvg, initTooltips, isCaseOnlyPropertyName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, staleBar, tagChips, tipAt, tipBold, toast, vcBadgeFor };
1121
+ export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type ElProps, type FollowButton, type FollowButtonOptions, type GameIdEditorOptions, ICON_NAMES, ICON_SIZE, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobBeginOptions, type JobProgressOptions, type JobProgressView, type NavHistory, type NavHistoryOptions, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, type PropertyRefOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type ShardVc, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type ToastKind, type ToastOptions, type ToolWindowHeadOptions, type TooltipOptions, type VcBadge, type VcMap, askIdentity, bindPropertyName, bindPropertyRef, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createNavHistory, createSaveController, dismissToast, el, firstIllegalPropertyName, foldVc, followButton, gameIdify, hideTip, historyNav, icon, iconBtn, iconHtml, iconNameOfGlyph, iconNode, iconSvg, initTooltips, isCaseOnlyPropertyName, isIconName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, stageChips, staleBar, tagChips, tipAt, tipBold, toast, toolWindowHead, vcBadgeFor, wireReorder };