@wildwinter/app-shell 0.1.0 → 0.14.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 (80) hide show
  1. package/README.md +35 -0
  2. package/dist/about.css +76 -0
  3. package/dist/about.css.map +1 -0
  4. package/dist/anchored.css +50 -0
  5. package/dist/anchored.css.map +1 -0
  6. package/dist/app-store.cjs +74 -0
  7. package/dist/app-store.cjs.map +1 -0
  8. package/dist/app-store.d.cts +75 -0
  9. package/dist/app-store.d.ts +75 -0
  10. package/dist/app-store.js +72 -0
  11. package/dist/app-store.js.map +1 -0
  12. package/dist/chunk-MDGOIT3O.js +58 -0
  13. package/dist/chunk-MDGOIT3O.js.map +1 -0
  14. package/dist/chunk-R6RO5TXB.js +16 -0
  15. package/dist/chunk-R6RO5TXB.js.map +1 -0
  16. package/dist/comments.css +109 -0
  17. package/dist/comments.css.map +1 -0
  18. package/dist/confirm.css +62 -0
  19. package/dist/confirm.css.map +1 -0
  20. package/dist/identity.css +62 -0
  21. package/dist/identity.css.map +1 -0
  22. package/dist/index.cjs +1287 -0
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +488 -1
  25. package/dist/index.d.ts +488 -1
  26. package/dist/index.js +1241 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/job.cjs +61 -0
  29. package/dist/job.cjs.map +1 -0
  30. package/dist/job.css +70 -0
  31. package/dist/job.css.map +1 -0
  32. package/dist/job.d.cts +56 -0
  33. package/dist/job.d.ts +56 -0
  34. package/dist/job.js +3 -0
  35. package/dist/job.js.map +1 -0
  36. package/dist/menu.cjs +19 -0
  37. package/dist/menu.cjs.map +1 -0
  38. package/dist/menu.d.cts +32 -0
  39. package/dist/menu.d.ts +32 -0
  40. package/dist/menu.js +3 -0
  41. package/dist/menu.js.map +1 -0
  42. package/dist/notes-editor.css +107 -0
  43. package/dist/notes-editor.css.map +1 -0
  44. package/dist/pane-shell.css +114 -0
  45. package/dist/pane-shell.css.map +1 -0
  46. package/dist/session.cjs +52 -0
  47. package/dist/session.cjs.map +1 -0
  48. package/dist/session.d.cts +79 -0
  49. package/dist/session.d.ts +79 -0
  50. package/dist/session.js +50 -0
  51. package/dist/session.js.map +1 -0
  52. package/dist/settings.css +266 -0
  53. package/dist/settings.css.map +1 -0
  54. package/dist/tool-window.cjs +79 -0
  55. package/dist/tool-window.cjs.map +1 -0
  56. package/dist/tool-window.css +60 -0
  57. package/dist/tool-window.css.map +1 -0
  58. package/dist/tool-window.d.cts +63 -0
  59. package/dist/tool-window.d.ts +63 -0
  60. package/dist/tool-window.js +73 -0
  61. package/dist/tool-window.js.map +1 -0
  62. package/dist/tooltip.css +22 -0
  63. package/dist/tooltip.css.map +1 -0
  64. package/dist/updater.cjs +315 -0
  65. package/dist/updater.cjs.map +1 -0
  66. package/dist/updater.d.cts +53 -0
  67. package/dist/updater.d.ts +53 -0
  68. package/dist/updater.js +306 -0
  69. package/dist/updater.js.map +1 -0
  70. package/dist/vc-status.cjs +77 -0
  71. package/dist/vc-status.cjs.map +1 -0
  72. package/dist/vc-status.d.cts +40 -0
  73. package/dist/vc-status.d.ts +40 -0
  74. package/dist/vc-status.js +71 -0
  75. package/dist/vc-status.js.map +1 -0
  76. package/dist/vc.css +41 -0
  77. package/dist/vc.css.map +1 -0
  78. package/package.json +101 -2
  79. package/dist/tokens.d.cts +0 -2
  80. package/dist/tokens.d.ts +0 -2
package/dist/index.d.cts CHANGED
@@ -1,11 +1,35 @@
1
+ export { EDIT_MENU, MenuLabel, PANE_MENU } from './menu.cjs';
2
+ export { JOB_PROGRESS, JobProgress } from './job.cjs';
3
+
1
4
  type Child = Node | string | null | undefined;
2
5
  interface ElProps {
3
6
  className?: string;
4
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. */
5
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;
6
15
  onClick?: (event: MouseEvent) => void;
7
16
  }
8
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
+ /** A captioned field: `<label class="shell-labelled"><span class="shell-fieldcap">…</span>control</label>`. */
23
+ declare function labelled(label: string, control: HTMLElement): HTMLElement;
24
+ /** Swap item `i` with its neighbour `i + delta` IN PLACE (the up/down reorder list editors share);
25
+ * a no-op when the target is out of range. Returns whether anything moved. */
26
+ declare function moveItem<T>(arr: T[], i: number, delta: number): boolean;
27
+ /** A tag-style editor for a string-list field (an enum's allowed values / flags): removable chips + an
28
+ * add input (Enter or "," commits; blank / duplicate ignored). Mutates `holder.values` in place (read
29
+ * back on save). `onChange` fires after any add / remove so callers can refresh a dependent control. */
30
+ declare function tagChips(holder: {
31
+ values?: string[];
32
+ }, onChange?: () => void): HTMLElement;
9
33
 
10
34
  /** Curated colour slots; must match the `--char-*` set defined per theme. */
11
35
  declare const PALETTE_SIZE = 12;
@@ -16,4 +40,467 @@ declare function colourIndex(name: string): number;
16
40
  /** The theme-aware CSS variable reference for a name's slot. */
17
41
  declare function colourFor(name: string): string;
18
42
 
19
- export { type Child, type ElProps, PALETTE, PALETTE_SIZE, colourFor, colourIndex, el };
43
+ /**
44
+ * Named by what it MEANS. Grouped by the job, because that is how a caller
45
+ * looks for one.
46
+ */
47
+ declare const icon: {
48
+ /** Close a panel, a dialog, a popover. Patterpad's, twelve times over. */
49
+ readonly close: "✕";
50
+ /** The overflow menu on a document or a row: "more about this". */
51
+ readonly more: "⋯";
52
+ /** Collapsed disclosure, pointing at what would open. */
53
+ readonly collapsed: "▸";
54
+ /** Expanded disclosure. */
55
+ readonly expanded: "▾";
56
+ /** A dropdown's affordance, where it is not a real <select>. */
57
+ readonly dropdown: "▾";
58
+ /** Back / previous, in a breadcrumb or a stepper. */
59
+ readonly back: "‹";
60
+ /** Forward / next. */
61
+ readonly forward: "›";
62
+ /** Up one, in a list. */
63
+ readonly up: "↑";
64
+ /** Down one. */
65
+ readonly down: "↓";
66
+ /** Done, applied, agreed. */
67
+ readonly tick: "✓";
68
+ /** Somebody else holds this: the version-control lock. */
69
+ readonly locked: "⊘";
70
+ /** Read-only on disk with no other holder: the resting state of everything
71
+ * in a lock-based working copy that nobody has checked out yet. */
72
+ readonly readOnly: "○";
73
+ /** Something needs attention but is not fatal. */
74
+ readonly warning: "‼";
75
+ /** A live thing: the running position, an active marker. */
76
+ readonly dot: "●";
77
+ /** Play, in the sense of "run this". */
78
+ readonly play: "▶";
79
+ /** The drag grip on a reorderable row. */
80
+ readonly grip: "⠿";
81
+ /** Add one of whatever this list holds. */
82
+ readonly add: "+";
83
+ /** Documentation notes: the pencil. Both apps had chosen it independently,
84
+ * which is the happiest way for a vocabulary entry to arrive. */
85
+ readonly note: "✎";
86
+ /** A comment thread. Speech, not a pencil: documentation states the reason,
87
+ * comments are the conversation, and the two must never look alike. */
88
+ readonly comment: "❝";
89
+ };
90
+ type IconName = keyof typeof icon;
91
+ /**
92
+ * The few shapes no glyph says honestly, as inline SVG.
93
+ *
94
+ * `currentColor` throughout, so they take the colour of the button they sit in
95
+ * exactly as the glyphs do, and sized in the markup for the same reason: an icon
96
+ * that needs a CSS rule to be the right size is an icon that will be the wrong
97
+ * size somewhere.
98
+ *
99
+ * Lifted from Patterpad, which drew them first.
100
+ */
101
+ declare const iconSvg: {
102
+ /** A document with lines: notes exist on this thing. */
103
+ 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>";
104
+ /** The same document, empty: nothing written here yet. */
105
+ 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>";
106
+ };
107
+ type IconSvgName = keyof typeof iconSvg;
108
+
109
+ type PaneSide = "nav" | "inspector";
110
+ interface PaneSideConfig {
111
+ /** The open width as a CSS length, e.g. "14rem" or "224px". */
112
+ defaultWidth: string;
113
+ /** Drag floor in px (default 160). */
114
+ minWidth?: number;
115
+ /** Drag ceiling in px from the window width (default w => min(640, w - 420)). */
116
+ maxWidth?: (innerWidth: number) => number;
117
+ /** Whether a drag seam is offered (default true). */
118
+ resizable?: boolean;
119
+ /** Noun for the toggle's tooltip/aria, e.g. "navigator" / "inspector". */
120
+ label?: string;
121
+ /** Shortcut hint appended to the tooltip, e.g. "Cmd+1". */
122
+ shortcutHint?: string;
123
+ }
124
+ interface PaneShellState {
125
+ open: Record<PaneSide, boolean>;
126
+ /** Dragged widths in px; absent = the config default. */
127
+ width: Partial<Record<PaneSide, number>>;
128
+ }
129
+ interface PaneShellOptions {
130
+ nav: PaneSideConfig;
131
+ inspector: PaneSideConfig;
132
+ /** Initial open + width state (from the host's store). */
133
+ initial?: Partial<PaneShellState>;
134
+ /** Persist state after any toggle or resize (host wires it to its store). */
135
+ onChange?: (state: PaneShellState) => void;
136
+ }
137
+ interface PaneShell {
138
+ /** The frame element (top bar + panes); mount it into the app container. */
139
+ readonly root: HTMLElement;
140
+ readonly topbar: HTMLElement;
141
+ /** Top-bar host slots: leading (title) and trailing (status), the host fills. */
142
+ readonly topbarLead: HTMLElement;
143
+ readonly topbarTrail: HTMLElement;
144
+ /** Pane bodies the host fills (the fixed-width inner that clips on collapse). */
145
+ readonly nav: HTMLElement;
146
+ readonly centre: HTMLElement;
147
+ readonly inspector: HTMLElement;
148
+ togglePane(side: PaneSide): void;
149
+ setPaneOpen(side: PaneSide, open: boolean): void;
150
+ isOpen(side: PaneSide): boolean;
151
+ /** Clear dragged widths back to the config defaults. */
152
+ resetWidths(): void;
153
+ /** A snapshot of the current state (open + widths). */
154
+ state(): PaneShellState;
155
+ destroy(): void;
156
+ }
157
+ declare function mountPaneShell(host: HTMLElement, opts: PaneShellOptions): PaneShell;
158
+
159
+ /** A compact list row: a single `.set-rowline` (inline controls) plus, when
160
+ * `details` are given, a disclosure that reveals a `.set-details` panel below,
161
+ * so a list scans like a table with secondary fields tucked away. */
162
+ declare function expandableRow(opts: {
163
+ line: HTMLElement[];
164
+ details?: HTMLElement[];
165
+ }): HTMLElement;
166
+ /** After a "+ Add" re-renders a list, bring the new (last) row into view and
167
+ * focus its name input (`.set-name`, else the first input). */
168
+ declare function focusNewRow(listEl: HTMLElement | null | undefined): void;
169
+ interface DupGuard {
170
+ /** Forget the previous render's inputs (call at the top of render). */
171
+ reset(): void;
172
+ /** Register a name input; `key` returns the comparison string (default the
173
+ * input's value). Re-checks live on input. `also` = extra inputs that change the key. */
174
+ track(input: HTMLInputElement, key?: () => string, also?: HTMLInputElement[]): void;
175
+ /** Re-paint duplicates; true if any two non-blank keys collide. */
176
+ check(): boolean;
177
+ /** The first input currently flagged (after a fresh check), or null. */
178
+ firstDuplicate(): HTMLInputElement | null;
179
+ }
180
+ /** Case-insensitive duplicate-name detection: paints clashing inputs `.invalid`
181
+ * and lets a Save gate block + jump via `firstDuplicate()`. */
182
+ declare function dupGuard(): DupGuard;
183
+ interface SettingsSectionHandle {
184
+ /** The first invalid control to jump to, blocking Save (e.g. a duplicate name). */
185
+ firstInvalid?(): HTMLElement | null;
186
+ }
187
+ interface SettingsSection {
188
+ id: string;
189
+ label: string;
190
+ /** Optional left-rail group heading above this tab. */
191
+ group?: string;
192
+ /** Build the section body into `host`; called fresh on each open so it reads
193
+ * the current data. Return a handle (for the Save gate). */
194
+ mount(host: HTMLElement): SettingsSectionHandle;
195
+ }
196
+ interface SettingsDialogOptions {
197
+ title: string;
198
+ sections: SettingsSection[];
199
+ /** Fired when Save is clicked and every section validates. The app reads its
200
+ * own section state and persists. May be async; the dialog stays open until
201
+ * it resolves, then closes. */
202
+ onSave: () => void | Promise<void>;
203
+ }
204
+ interface SettingsDialog {
205
+ /** Open (optionally on a given tab id). Sections re-mount from current data. */
206
+ open(tabId?: string): void;
207
+ destroy(): void;
208
+ }
209
+ /** Build a settings dialog. The frame owns the modal, the tab rail, save-on-Save
210
+ * (with a validation gate), and Cancel; the sections own their content + state. */
211
+ declare function mountSettingsDialog(opts: SettingsDialogOptions): SettingsDialog;
212
+
213
+ interface PinButtonOptions {
214
+ pinned: boolean;
215
+ /** Persist the choice (the button keeps its own visual state). */
216
+ onToggle: (on: boolean) => void;
217
+ }
218
+ /** The always-on-top pin: `.swin-pin`, aria-pressed, and a tooltip that says
219
+ * what a click will do (the richest of the family's variants, kept). */
220
+ declare function pinButton(opts: PinButtonOptions): HTMLButtonElement;
221
+
222
+ interface ConfirmOptions {
223
+ title: string;
224
+ body: string;
225
+ /** The destructive button's label ("Delete box", "Restart"). */
226
+ confirmLabel: string;
227
+ }
228
+ /** Resolves true on confirm; false on Cancel / Esc / backdrop. */
229
+ declare function confirmDialog(opts: ConfirmOptions): Promise<boolean>;
230
+
231
+ interface AboutOptions {
232
+ /** The product's name, large. */
233
+ appName: string;
234
+ /** "1.4.2" - shown as "Version 1.4.2". */
235
+ version: string;
236
+ /** One or two sentences: what this app is for. */
237
+ blurb?: string;
238
+ /** Licence, authorship, the suite it belongs to. Newlines are respected. */
239
+ credits?: string;
240
+ links?: {
241
+ label: string;
242
+ url: string;
243
+ }[];
244
+ /** Inline SVG for the product wordmark, if it has one. */
245
+ wordmark?: string;
246
+ /** Open a URL in the real browser. Omit and the links render as text. */
247
+ onOpenLink?: (url: string) => void;
248
+ }
249
+ /** Show the About dialog. Resolves when it closes. */
250
+ declare function showAbout(opts: AboutOptions): Promise<void>;
251
+
252
+ interface AnchoredPanel {
253
+ panel: HTMLElement;
254
+ /** The content area to populate: the header is built for you. */
255
+ body: HTMLElement;
256
+ /** Idempotent; fires the caller's `onClose` exactly once. */
257
+ close: () => void;
258
+ }
259
+ interface AnchoredPanelOptions {
260
+ anchor: HTMLElement;
261
+ /** Extra class(es) on the panel, for app-specific sizing or colour. */
262
+ className?: string;
263
+ title: string;
264
+ width: number;
265
+ /**
266
+ * Which side to try first.
267
+ *
268
+ * `"left"` suits a panel hung off a row in a right-hand pane (Patterpad's
269
+ * inspector); `"below"` suits one hung off a button in a document's header
270
+ * (Storyletter's topline). Either way it flips when the room is not there.
271
+ */
272
+ prefer?: "left" | "below";
273
+ /** Selectors for chrome the panel must not cover - a problems bar, a review
274
+ * bar - so those stay usable while it is open. */
275
+ keepClear?: string[];
276
+ /** A pointerdown inside something matching this does NOT close the panel. */
277
+ ignoreDown?: string;
278
+ /** While something matching this exists, Escape belongs to it, not to us. */
279
+ deferEscape?: string;
280
+ onClose?: () => void;
281
+ }
282
+ declare function openAnchoredPanel(opts: AnchoredPanelOptions): AnchoredPanel | null;
283
+ /** Close whatever is open. For a navigation or a re-render: a panel anchored to
284
+ * an element that no longer exists would hang in the air. */
285
+ declare function closeAnchoredPanel(): void;
286
+ /** Patterpad's placement, with the side as a parameter. */
287
+ declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below", keepClear: string[]): void;
288
+
289
+ /** One shard's state as the renderer sees it. The app's own DTO will be this
290
+ * shape (it crosses its own bridge); this is what the view needs. */
291
+ interface ShardVc {
292
+ key: string;
293
+ writable: boolean;
294
+ lockedBy?: string[];
295
+ outOfDate?: boolean;
296
+ }
297
+ /** A snapshot, keyed by shard key. Absent = clean, writable, up to date. */
298
+ type VcMap = ReadonlyMap<string, ShardVc>;
299
+ /** Fold the state of the shard key(s) an item stands for (a box row spans its
300
+ * box + tags + hands shards), most actionable winning: any holder locks it,
301
+ * any stale shard makes it stale, any read-only shard makes it read-only. */
302
+ declare function foldVc(shards: VcMap, keys: string | undefined): ShardVc | undefined;
303
+ /** The one badge an item flies, by priority, or null when there is nothing to
304
+ * say. Monochrome typographic glyphs (never colour emoji), so each inherits
305
+ * its themed colour and reads the same in Linen and Baize. */
306
+ declare function vcBadgeFor(s: ShardVc | undefined): {
307
+ glyph: string;
308
+ cls: string;
309
+ title: string;
310
+ } | null;
311
+ /** Repaint the badge on every item under `root` that names a shard (its
312
+ * `data-vc`). In place: no re-render, so nothing loses focus or scroll. */
313
+ declare function paintVcBadges(root: ParentNode, shards: VcMap): void;
314
+ /** Turn a region's editing controls off - or back on, but only the ones WE
315
+ * turned off, so a control disabled for its own reasons (the first card's
316
+ * "previous", a redraw-turns box on "always") stays disabled. */
317
+ declare function lockControls(host: HTMLElement, off: boolean, staysLive: string): void;
318
+ /** The notice a locked document opens with: who holds it, and why nothing types. */
319
+ declare function lockNotice(holders: string[]): HTMLElement;
320
+
321
+ interface Identity {
322
+ name: string;
323
+ email?: string;
324
+ }
325
+ interface IdentityOptions {
326
+ /** What it already says, if anything. */
327
+ current?: Identity;
328
+ /** A name to offer when there is none: the VCS's, one day. */
329
+ suggested?: Identity;
330
+ }
331
+ /** Ask, and record the answer. Resolves when the dialog closes, either way. */
332
+ declare function askIdentity(opts?: IdentityOptions): Promise<Identity | undefined>;
333
+
334
+ /** One line of documentation. `type` names its class; absent = editor-only. */
335
+ interface DocLine {
336
+ type?: string;
337
+ text: string;
338
+ }
339
+ /** What applies to one thing: its own notes, and the ancestors' that reach it.
340
+ * Inherited notes are READ-ONLY - they are edited where they live, which is
341
+ * what stops a note being copied down and then disagreeing with itself. */
342
+ interface Notes {
343
+ own: DocLine[];
344
+ inherited: {
345
+ id: string;
346
+ label: string;
347
+ lines: DocLine[];
348
+ }[];
349
+ /** The classes this app offers, in the order they should appear. */
350
+ classes: string[];
351
+ }
352
+ interface NotesEditorOptions {
353
+ /** What this is about, for the dialog's title ("Notes: Arrive at the gate"). */
354
+ subject: string;
355
+ notes: Notes;
356
+ /** Commit. Called once, on close, and only when something changed. */
357
+ save: (lines: DocLine[]) => void;
358
+ }
359
+ /** Open the notes modal. Resolves when it closes. */
360
+ declare function openNotesEditor(opts: NotesEditorOptions): void;
361
+
362
+ /** One message. The author is a NAME stamped at posting time, not a reference:
363
+ * people leave projects and their words stay. */
364
+ interface CommentMessage {
365
+ author: string;
366
+ ts: string;
367
+ body: string;
368
+ }
369
+ /** A thread, anchored to the id of whatever it is about. */
370
+ interface Comment {
371
+ id: string;
372
+ anchor: string;
373
+ resolved?: boolean;
374
+ messages: CommentMessage[];
375
+ }
376
+ interface CommentsOptions {
377
+ anchor: HTMLElement;
378
+ /** What is being discussed, for the panel's title. */
379
+ subject: string;
380
+ /** Every thread on this thing, resolved ones included. */
381
+ threads: Comment[];
382
+ /** Review ▸ Show Resolved Comments. */
383
+ showResolved: boolean;
384
+ /** A fresh thread id, minted by the caller so main never has to. */
385
+ newThreadId: () => string;
386
+ post: (threadId: string, body: string) => void;
387
+ setResolved: (threadId: string, resolved: boolean) => void;
388
+ }
389
+ declare function openComments(opts: CommentsOptions): void;
390
+
391
+ interface JobProgressView {
392
+ /** The strip; mount it where the job belongs. */
393
+ readonly element: HTMLElement;
394
+ /** Show the strip, reset to zero, and (optionally) name what is running. */
395
+ begin(label?: string): void;
396
+ /** Feed it a progress message from the main-side host. */
397
+ update(done: number, total: number, elapsedMs: number): void;
398
+ /** Hide the strip (the job finished, failed, or was cancelled). */
399
+ end(): void;
400
+ readonly visible: boolean;
401
+ }
402
+ interface JobProgressOptions {
403
+ /** Called when Cancel is clicked. The button disables itself and says so;
404
+ * the job stops at its next yield, and the host then calls end(). */
405
+ onCancel: () => void;
406
+ /** The word for the units, for the count readout (default "runs"). */
407
+ units?: string;
408
+ }
409
+ declare function mountJobProgress(host: HTMLElement, opts: JobProgressOptions): JobProgressView;
410
+
411
+ /** Wrap `s` so it renders bold inside a tooltip (see the marker note above). */
412
+ declare function tipBold(s: string): string;
413
+ interface TooltipOptions {
414
+ /** Called before every show: return true to keep the bubble down. For a mode
415
+ * that wants no chrome at all (Patterpad's Writing View). */
416
+ suppressed?: () => boolean;
417
+ }
418
+ /** A rectangle in VIEWPORT coordinates, for anchoring a tip to something that is
419
+ * not a DOM element: a shape on a canvas, a run of text inside one. */
420
+ interface TipRect {
421
+ left: number;
422
+ top: number;
423
+ width: number;
424
+ height: number;
425
+ }
426
+ /**
427
+ * Show a tip for something that has no DOM node of its own: a card on a canvas,
428
+ * a zone, a pin. The caller says WHERE (a viewport rectangle) and WHAT, and
429
+ * identifies the subject with `key`.
430
+ *
431
+ * The key is what makes this usable from a canvas. Re-calling with the same key
432
+ * re-places the bubble WITHOUT restarting the delay, so a tip can follow its
433
+ * shape while the camera moves; calling with a different key starts the wait
434
+ * again, so sweeping the pointer across a dense board does not strobe.
435
+ *
436
+ * The bubble is ordinary DOM, so it holds its size whatever the canvas is
437
+ * zoomed to. That is the point of it: the label it stands in for is the one
438
+ * thing on a canvas that must not shrink with everything else.
439
+ */
440
+ declare function tipAt(key: string, rect: TipRect, text: string): void;
441
+ /** Take any tip down: the pointer has left, or a gesture has started. */
442
+ declare function hideTip(): void;
443
+ /** Wire the one delegated controller. Idempotent; later options are ignored. */
444
+ declare function initTooltips(opts?: TooltipOptions): void;
445
+
446
+ /**
447
+ * Three states, not a boolean.
448
+ *
449
+ * "saving" earns its place: on a slow disk or a slow VC hook a save takes long
450
+ * enough to see, and a status that flips straight from unsaved to saved leaves
451
+ * the author wondering whether the click did anything.
452
+ */
453
+ type SaveStatus = "saved" | "unsaved" | "saving";
454
+ interface SaveControllerOptions {
455
+ /**
456
+ * Write whatever is pending. Return false if it failed: the controller stays
457
+ * "unsaved" and will try again on the next touch or flush, rather than
458
+ * quietly reporting success over a write that did not happen.
459
+ *
460
+ * Called only when something is pending, and never twice at once.
461
+ */
462
+ write: () => Promise<boolean> | boolean;
463
+ /** Told every time the status changes, and only when it changes. */
464
+ onStatus?: (status: SaveStatus) => void;
465
+ /** How long after the last edit to write. Storyletter's 700ms by default. */
466
+ delayMs?: number;
467
+ /**
468
+ * The longest an edit may go unwritten while more keep arriving.
469
+ *
470
+ * This is the interval half of the two apps put back in: without it, steady
471
+ * typing defers the write for as long as the typing lasts.
472
+ */
473
+ maxWaitMs?: number;
474
+ }
475
+ interface SaveController {
476
+ /** What the indicator should say. */
477
+ readonly status: SaveStatus;
478
+ /** Is anything unwritten? For a close guard, or an updater asking whether it
479
+ * may restart. True while a write is in flight, too: it is not on disk yet. */
480
+ readonly pending: boolean;
481
+ /** Something changed. Starts (or extends) the countdown. */
482
+ touch(): void;
483
+ /**
484
+ * Write now, and wait for it.
485
+ *
486
+ * Call on every transition that must see current bytes: switching what is
487
+ * being edited, opening a tool window that reads the disk, closing the
488
+ * window, Cmd+S. Safe to call when nothing is pending (it does nothing) and
489
+ * safe to call during a write (it waits for it, then writes again if more
490
+ * arrived while it ran).
491
+ */
492
+ flush(): Promise<void>;
493
+ /** Give up on what is pending without writing it. For a project close after
494
+ * the author has said to discard, and for teardown. */
495
+ cancel(): void;
496
+ /**
497
+ * Autosave on or off (Patterpad's per-project setting).
498
+ *
499
+ * Off does not mean "never write": touch() still marks the work unsaved, and
500
+ * flush() still writes on transitions and on Cmd+S. It only stops the clock.
501
+ */
502
+ setAuto(on: boolean): void;
503
+ }
504
+ declare function createSaveController(opts: SaveControllerOptions): SaveController;
505
+
506
+ export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, hideTip, icon, iconBtn, iconSvg, initTooltips, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };