@wildwinter/app-shell 0.4.1 → 0.15.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 (81) 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 +104 -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 +134 -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 +1020 -6
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +429 -3
  25. package/dist/index.d.ts +429 -3
  26. package/dist/index.js +987 -8
  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 +7 -0
  37. package/dist/menu.cjs.map +1 -1
  38. package/dist/menu.d.cts +20 -1
  39. package/dist/menu.d.ts +20 -1
  40. package/dist/menu.js +1 -1
  41. package/dist/notes-editor.css +107 -0
  42. package/dist/notes-editor.css.map +1 -0
  43. package/dist/session.cjs +52 -0
  44. package/dist/session.cjs.map +1 -0
  45. package/dist/session.d.cts +79 -0
  46. package/dist/session.d.ts +79 -0
  47. package/dist/session.js +50 -0
  48. package/dist/session.js.map +1 -0
  49. package/dist/tool-window.cjs +79 -0
  50. package/dist/tool-window.cjs.map +1 -0
  51. package/dist/tool-window.css +60 -0
  52. package/dist/tool-window.css.map +1 -0
  53. package/dist/tool-window.d.cts +63 -0
  54. package/dist/tool-window.d.ts +63 -0
  55. package/dist/tool-window.js +73 -0
  56. package/dist/tool-window.js.map +1 -0
  57. package/dist/tooltip.css +22 -0
  58. package/dist/tooltip.css.map +1 -0
  59. package/dist/updater.cjs +315 -0
  60. package/dist/updater.cjs.map +1 -0
  61. package/dist/updater.d.cts +53 -0
  62. package/dist/updater.d.ts +53 -0
  63. package/dist/updater.js +306 -0
  64. package/dist/updater.js.map +1 -0
  65. package/dist/vc-status.cjs +77 -0
  66. package/dist/vc-status.cjs.map +1 -0
  67. package/dist/vc-status.d.cts +40 -0
  68. package/dist/vc-status.d.ts +40 -0
  69. package/dist/vc-status.js +71 -0
  70. package/dist/vc-status.js.map +1 -0
  71. package/dist/vc.css +41 -0
  72. package/dist/vc.css.map +1 -0
  73. package/package.json +90 -1
  74. package/dist/chunk-KQT3XYRS.js +0 -10
  75. package/dist/chunk-KQT3XYRS.js.map +0 -1
  76. package/dist/pane-shell.d.cts +0 -2
  77. package/dist/pane-shell.d.ts +0 -2
  78. package/dist/settings.d.cts +0 -2
  79. package/dist/settings.d.ts +0 -2
  80. package/dist/tokens.d.cts +0 -2
  81. package/dist/tokens.d.ts +0 -2
package/dist/index.d.ts CHANGED
@@ -1,14 +1,23 @@
1
- export { MenuLabel, PANE_MENU } from './menu.js';
1
+ export { EDIT_MENU, MenuLabel, PANE_MENU } from './menu.js';
2
+ export { JOB_PROGRESS, JobProgress } from './job.js';
2
3
 
3
4
  type Child = Node | string | null | undefined;
4
5
  interface ElProps {
5
6
  className?: string;
6
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. */
7
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;
8
15
  onClick?: (event: MouseEvent) => void;
9
16
  }
10
17
  declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, classNameOrProps?: string | ElProps, ...children: Child[]): HTMLElementTagNameMap[K];
11
- /** A small square glyph button (the move/delete controls list editors share). */
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. */
12
21
  declare function iconBtn(glyph: string, title: string, onClick: () => void, disabled?: boolean, danger?: boolean): HTMLButtonElement;
13
22
  /** A captioned field: `<label class="shell-labelled"><span class="shell-fieldcap">…</span>control</label>`. */
14
23
  declare function labelled(label: string, control: HTMLElement): HTMLElement;
@@ -31,6 +40,72 @@ declare function colourIndex(name: string): number;
31
40
  /** The theme-aware CSS variable reference for a name's slot. */
32
41
  declare function colourFor(name: string): string;
33
42
 
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
+
34
109
  type PaneSide = "nav" | "inspector";
35
110
  interface PaneSideConfig {
36
111
  /** The open width as a CSS length, e.g. "14rem" or "224px". */
@@ -135,4 +210,355 @@ interface SettingsDialog {
135
210
  * (with a validation gate), and Cancel; the sections own their content + state. */
136
211
  declare function mountSettingsDialog(opts: SettingsDialogOptions): SettingsDialog;
137
212
 
138
- export { type Child, type DupGuard, type ElProps, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, colourFor, colourIndex, dupGuard, el, expandableRow, focusNewRow, iconBtn, labelled, mountPaneShell, mountSettingsDialog, moveItem, tagChips };
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" | "centre";
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" | "centre", keepClear: string[]): void;
288
+
289
+ /**
290
+ * Slugify a name into hyphen form: lower case, apostrophes dropped, runs of
291
+ * anything else to a single hyphen, no leading or trailing hyphen.
292
+ *
293
+ * May return "", when there was nothing usable in the input. Callers treat that
294
+ * as "no pinned address" rather than as an address.
295
+ */
296
+ declare function gameIdify(text: string): string;
297
+ /** Is this a legal address? Lower case, digits and hyphens, starting and ending
298
+ * with a letter or a digit. Note "" is NOT legal: an empty pinned address means
299
+ * "derive one", which is a different thing from an address. */
300
+ declare function isValidGameId(gameId: string): boolean;
301
+
302
+ interface GameIdEditorOptions {
303
+ anchor: HTMLElement;
304
+ /** The pinned address, or "" when it is being derived. */
305
+ value: string;
306
+ /** What it derives to, shown as the placeholder. */
307
+ derived: string;
308
+ /** Persist. "" means "stop pinning, go back to deriving". */
309
+ onCommit: (gameId: string) => void;
310
+ /** The panel's title. Both apps say "Game ID"; a third may not. */
311
+ title?: string;
312
+ /** Override the rules. Defaults are the shell's, which is what a new app
313
+ * should use; an app whose domain layer owns them can pass its own. */
314
+ slugify?: (text: string) => string;
315
+ validate?: (gameId: string) => boolean;
316
+ /** Where the panel goes, for hosts that anchor it somewhere tight. */
317
+ prefer?: "left" | "below" | "centre";
318
+ }
319
+ /**
320
+ * Open it. Returns false when that click TOGGLED the panel shut, which is the
321
+ * anchored panel's contract and worth passing on: a caller that repaints on open
322
+ * needs to know it did not.
323
+ */
324
+ declare function openGameIdEditor(opts: GameIdEditorOptions): boolean;
325
+
326
+ /** One shard's state as the renderer sees it. The app's own DTO will be this
327
+ * shape (it crosses its own bridge); this is what the view needs. */
328
+ interface ShardVc {
329
+ key: string;
330
+ writable: boolean;
331
+ lockedBy?: string[];
332
+ outOfDate?: boolean;
333
+ }
334
+ /** A snapshot, keyed by shard key. Absent = clean, writable, up to date. */
335
+ type VcMap = ReadonlyMap<string, ShardVc>;
336
+ /** Fold the state of the shard key(s) an item stands for (a box row spans its
337
+ * box + tags + hands shards), most actionable winning: any holder locks it,
338
+ * any stale shard makes it stale, any read-only shard makes it read-only. */
339
+ declare function foldVc(shards: VcMap, keys: string | undefined): ShardVc | undefined;
340
+ /** The one badge an item flies, by priority, or null when there is nothing to
341
+ * say. Monochrome typographic glyphs (never colour emoji), so each inherits
342
+ * its themed colour and reads the same in Linen and Baize. */
343
+ declare function vcBadgeFor(s: ShardVc | undefined): {
344
+ glyph: string;
345
+ cls: string;
346
+ title: string;
347
+ } | null;
348
+ /** Repaint the badge on every item under `root` that names a shard (its
349
+ * `data-vc`). In place: no re-render, so nothing loses focus or scroll. */
350
+ declare function paintVcBadges(root: ParentNode, shards: VcMap): void;
351
+ /** Turn a region's editing controls off - or back on, but only the ones WE
352
+ * turned off, so a control disabled for its own reasons (the first card's
353
+ * "previous", a redraw-turns box on "always") stays disabled. */
354
+ declare function lockControls(host: HTMLElement, off: boolean, staysLive: string): void;
355
+ /** The notice a locked document opens with: who holds it, and why nothing types. */
356
+ declare function lockNotice(holders: string[]): HTMLElement;
357
+
358
+ interface Identity {
359
+ name: string;
360
+ email?: string;
361
+ }
362
+ interface IdentityOptions {
363
+ /** What it already says, if anything. */
364
+ current?: Identity;
365
+ /** A name to offer when there is none: the VCS's, one day. */
366
+ suggested?: Identity;
367
+ }
368
+ /** Ask, and record the answer. Resolves when the dialog closes, either way. */
369
+ declare function askIdentity(opts?: IdentityOptions): Promise<Identity | undefined>;
370
+
371
+ /** One line of documentation. `type` names its class; absent = editor-only. */
372
+ interface DocLine {
373
+ type?: string;
374
+ text: string;
375
+ }
376
+ /** What applies to one thing: its own notes, and the ancestors' that reach it.
377
+ * Inherited notes are READ-ONLY - they are edited where they live, which is
378
+ * what stops a note being copied down and then disagreeing with itself. */
379
+ interface Notes {
380
+ own: DocLine[];
381
+ inherited: {
382
+ id: string;
383
+ label: string;
384
+ lines: DocLine[];
385
+ }[];
386
+ /** The classes this app offers, in the order they should appear. */
387
+ classes: string[];
388
+ }
389
+ interface NotesEditorOptions {
390
+ /** What this is about, for the dialog's title ("Notes: Arrive at the gate"). */
391
+ subject: string;
392
+ notes: Notes;
393
+ /** Commit. Called once, on close, and only when something changed. */
394
+ save: (lines: DocLine[]) => void;
395
+ }
396
+ /** Open the notes modal. Resolves when it closes. */
397
+ declare function openNotesEditor(opts: NotesEditorOptions): void;
398
+
399
+ /** One message. The author is a NAME stamped at posting time, not a reference:
400
+ * people leave projects and their words stay. */
401
+ interface CommentMessage {
402
+ author: string;
403
+ ts: string;
404
+ body: string;
405
+ /** A TOMBSTONE: the words are gone, the turn in the conversation is not.
406
+ * Deleting a reply out of a thread would renumber the argument around it, so
407
+ * what is left says who spoke and when and that they withdrew it. The body is
408
+ * emptied on the way out, so "deleted" means deleted from the file too. */
409
+ deleted?: boolean;
410
+ }
411
+ /** A thread, anchored to the id of whatever it is about. */
412
+ interface Comment {
413
+ id: string;
414
+ anchor: string;
415
+ resolved?: boolean;
416
+ messages: CommentMessage[];
417
+ }
418
+ interface CommentsOptions {
419
+ anchor: HTMLElement;
420
+ /** What is being discussed, for the panel's title. */
421
+ subject: string;
422
+ /** Every thread on this thing, resolved ones included. */
423
+ threads: Comment[];
424
+ /** Review ▸ Show Resolved Comments. */
425
+ showResolved: boolean;
426
+ /** A fresh thread id, minted by the caller so main never has to. */
427
+ newThreadId: () => string;
428
+ post: (threadId: string, body: string) => void;
429
+ setResolved: (threadId: string, resolved: boolean) => void;
430
+ /**
431
+ * Withdraw one message. Absent = the host does not offer deletion.
432
+ *
433
+ * By INDEX, because a message has no id of its own and giving every message
434
+ * one would be a format change to every stored thread for the sake of one
435
+ * verb. The index is read off the list the reader is looking at, and these
436
+ * files are edited by one person at a time with a VCS underneath.
437
+ *
438
+ * The host decides what deleting MEANS: the agreed rule is that the message
439
+ * becomes a tombstone, and the whole thread goes when nothing readable would
440
+ * be left (which covers the common case of withdrawing a comment nobody had
441
+ * replied to).
442
+ */
443
+ deleteMessage?: (threadId: string, index: number) => void;
444
+ /** Where the panel sits: "centre" for a point on a canvas (see anchored.ts). */
445
+ prefer?: "left" | "below" | "centre";
446
+ }
447
+ declare function openComments(opts: CommentsOptions): void;
448
+
449
+ interface JobProgressView {
450
+ /** The strip; mount it where the job belongs. */
451
+ readonly element: HTMLElement;
452
+ /** Show the strip, reset to zero, and (optionally) name what is running. */
453
+ begin(label?: string): void;
454
+ /** Feed it a progress message from the main-side host. */
455
+ update(done: number, total: number, elapsedMs: number): void;
456
+ /** Hide the strip (the job finished, failed, or was cancelled). */
457
+ end(): void;
458
+ readonly visible: boolean;
459
+ }
460
+ interface JobProgressOptions {
461
+ /** Called when Cancel is clicked. The button disables itself and says so;
462
+ * the job stops at its next yield, and the host then calls end(). */
463
+ onCancel: () => void;
464
+ /** The word for the units, for the count readout (default "runs"). */
465
+ units?: string;
466
+ }
467
+ declare function mountJobProgress(host: HTMLElement, opts: JobProgressOptions): JobProgressView;
468
+
469
+ /** Wrap `s` so it renders bold inside a tooltip (see the marker note above). */
470
+ declare function tipBold(s: string): string;
471
+ interface TooltipOptions {
472
+ /** Called before every show: return true to keep the bubble down. For a mode
473
+ * that wants no chrome at all (Patterpad's Writing View). */
474
+ suppressed?: () => boolean;
475
+ }
476
+ /** A rectangle in VIEWPORT coordinates, for anchoring a tip to something that is
477
+ * not a DOM element: a shape on a canvas, a run of text inside one. */
478
+ interface TipRect {
479
+ left: number;
480
+ top: number;
481
+ width: number;
482
+ height: number;
483
+ }
484
+ /**
485
+ * Show a tip for something that has no DOM node of its own: a card on a canvas,
486
+ * a zone, a pin. The caller says WHERE (a viewport rectangle) and WHAT, and
487
+ * identifies the subject with `key`.
488
+ *
489
+ * The key is what makes this usable from a canvas. Re-calling with the same key
490
+ * re-places the bubble WITHOUT restarting the delay, so a tip can follow its
491
+ * shape while the camera moves; calling with a different key starts the wait
492
+ * again, so sweeping the pointer across a dense board does not strobe.
493
+ *
494
+ * The bubble is ordinary DOM, so it holds its size whatever the canvas is
495
+ * zoomed to. That is the point of it: the label it stands in for is the one
496
+ * thing on a canvas that must not shrink with everything else.
497
+ */
498
+ declare function tipAt(key: string, rect: TipRect, text: string): void;
499
+ /** Take any tip down: the pointer has left, or a gesture has started. */
500
+ declare function hideTip(): void;
501
+ /** Wire the one delegated controller. Idempotent; later options are ignored. */
502
+ declare function initTooltips(opts?: TooltipOptions): void;
503
+
504
+ /**
505
+ * Three states, not a boolean.
506
+ *
507
+ * "saving" earns its place: on a slow disk or a slow VC hook a save takes long
508
+ * enough to see, and a status that flips straight from unsaved to saved leaves
509
+ * the author wondering whether the click did anything.
510
+ */
511
+ type SaveStatus = "saved" | "unsaved" | "saving";
512
+ interface SaveControllerOptions {
513
+ /**
514
+ * Write whatever is pending. Return false if it failed: the controller stays
515
+ * "unsaved" and will try again on the next touch or flush, rather than
516
+ * quietly reporting success over a write that did not happen.
517
+ *
518
+ * Called only when something is pending, and never twice at once.
519
+ */
520
+ write: () => Promise<boolean> | boolean;
521
+ /** Told every time the status changes, and only when it changes. */
522
+ onStatus?: (status: SaveStatus) => void;
523
+ /** How long after the last edit to write. Storyletter's 700ms by default. */
524
+ delayMs?: number;
525
+ /**
526
+ * The longest an edit may go unwritten while more keep arriving.
527
+ *
528
+ * This is the interval half of the two apps put back in: without it, steady
529
+ * typing defers the write for as long as the typing lasts.
530
+ */
531
+ maxWaitMs?: number;
532
+ }
533
+ interface SaveController {
534
+ /** What the indicator should say. */
535
+ readonly status: SaveStatus;
536
+ /** Is anything unwritten? For a close guard, or an updater asking whether it
537
+ * may restart. True while a write is in flight, too: it is not on disk yet. */
538
+ readonly pending: boolean;
539
+ /** Something changed. Starts (or extends) the countdown. */
540
+ touch(): void;
541
+ /**
542
+ * Write now, and wait for it.
543
+ *
544
+ * Call on every transition that must see current bytes: switching what is
545
+ * being edited, opening a tool window that reads the disk, closing the
546
+ * window, Cmd+S. Safe to call when nothing is pending (it does nothing) and
547
+ * safe to call during a write (it waits for it, then writes again if more
548
+ * arrived while it ran).
549
+ */
550
+ flush(): Promise<void>;
551
+ /** Give up on what is pending without writing it. For a project close after
552
+ * the author has said to discard, and for teardown. */
553
+ cancel(): void;
554
+ /**
555
+ * Autosave on or off (Patterpad's per-project setting).
556
+ *
557
+ * Off does not mean "never write": touch() still marks the work unsaved, and
558
+ * flush() still writes on transitions and on Cmd+S. It only stops the clock.
559
+ */
560
+ setAuto(on: boolean): void;
561
+ }
562
+ declare function createSaveController(opts: SaveControllerOptions): SaveController;
563
+
564
+ 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 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, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isValidGameId, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };