@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.
- package/README.md +35 -0
- package/dist/about.css +76 -0
- package/dist/about.css.map +1 -0
- package/dist/anchored.css +50 -0
- package/dist/anchored.css.map +1 -0
- package/dist/app-store.cjs +74 -0
- package/dist/app-store.cjs.map +1 -0
- package/dist/app-store.d.cts +75 -0
- package/dist/app-store.d.ts +75 -0
- package/dist/app-store.js +72 -0
- package/dist/app-store.js.map +1 -0
- package/dist/chunk-MDGOIT3O.js +58 -0
- package/dist/chunk-MDGOIT3O.js.map +1 -0
- package/dist/chunk-R6RO5TXB.js +16 -0
- package/dist/chunk-R6RO5TXB.js.map +1 -0
- package/dist/comments.css +109 -0
- package/dist/comments.css.map +1 -0
- package/dist/confirm.css +62 -0
- package/dist/confirm.css.map +1 -0
- package/dist/identity.css +62 -0
- package/dist/identity.css.map +1 -0
- package/dist/index.cjs +1287 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +488 -1
- package/dist/index.d.ts +488 -1
- package/dist/index.js +1241 -1
- package/dist/index.js.map +1 -1
- package/dist/job.cjs +61 -0
- package/dist/job.cjs.map +1 -0
- package/dist/job.css +70 -0
- package/dist/job.css.map +1 -0
- package/dist/job.d.cts +56 -0
- package/dist/job.d.ts +56 -0
- package/dist/job.js +3 -0
- package/dist/job.js.map +1 -0
- package/dist/menu.cjs +19 -0
- package/dist/menu.cjs.map +1 -0
- package/dist/menu.d.cts +32 -0
- package/dist/menu.d.ts +32 -0
- package/dist/menu.js +3 -0
- package/dist/menu.js.map +1 -0
- package/dist/notes-editor.css +107 -0
- package/dist/notes-editor.css.map +1 -0
- package/dist/pane-shell.css +114 -0
- package/dist/pane-shell.css.map +1 -0
- package/dist/session.cjs +52 -0
- package/dist/session.cjs.map +1 -0
- package/dist/session.d.cts +79 -0
- package/dist/session.d.ts +79 -0
- package/dist/session.js +50 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.css +266 -0
- package/dist/settings.css.map +1 -0
- package/dist/tool-window.cjs +79 -0
- package/dist/tool-window.cjs.map +1 -0
- package/dist/tool-window.css +60 -0
- package/dist/tool-window.css.map +1 -0
- package/dist/tool-window.d.cts +63 -0
- package/dist/tool-window.d.ts +63 -0
- package/dist/tool-window.js +73 -0
- package/dist/tool-window.js.map +1 -0
- package/dist/tooltip.css +22 -0
- package/dist/tooltip.css.map +1 -0
- package/dist/updater.cjs +315 -0
- package/dist/updater.cjs.map +1 -0
- package/dist/updater.d.cts +53 -0
- package/dist/updater.d.ts +53 -0
- package/dist/updater.js +306 -0
- package/dist/updater.js.map +1 -0
- package/dist/vc-status.cjs +77 -0
- package/dist/vc-status.cjs.map +1 -0
- package/dist/vc-status.d.cts +40 -0
- package/dist/vc-status.d.ts +40 -0
- package/dist/vc-status.js +71 -0
- package/dist/vc-status.js.map +1 -0
- package/dist/vc.css +41 -0
- package/dist/vc.css.map +1 -0
- package/package.json +101 -2
- package/dist/tokens.d.cts +0 -2
- package/dist/tokens.d.ts +0 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dom.ts","../src/colour.ts"],"names":[],"mappings":";;;AAqBO,SAAS,EAAA,CACd,GAAA,EACA,gBAAA,EAAA,GACG,QAAA,EACuB;AAC1B,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACvC,EAAA,MAAM,KAAA,GAAiB,OAAO,gBAAA,KAAqB,QAAA,GAC/C,EAAE,SAAA,EAAW,gBAAA,EAAiB,GAC9B,gBAAA,IAAoB,EAAC;AACzB,EAAA,IAAI,KAAA,CAAM,SAAA,EAAW,IAAA,CAAK,SAAA,GAAY,KAAA,CAAM,SAAA;AAC5C,EAAA,IAAI,KAAA,CAAM,SAAS,MAAA,IAAa,KAAA,CAAM,SAAS,IAAA,EAAM,IAAA,CAAK,cAAc,KAAA,CAAM,IAAA;AAC9E,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,QAAQ,KAAA,CAAM,KAAA;AAClD,EAAA,IAAI,MAAM,OAAA,EAAS,IAAA,CAAK,gBAAA,CAAiB,OAAA,EAAS,MAAM,OAAwB,CAAA;AAChF,EAAA,KAAA,MAAW,SAAS,QAAA,EAAU;AAC5B,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW;AAC3C,IAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,EACnB;AACA,EAAA,OAAO,IAAA;AACT;;;AC3BO,IAAM,YAAA,GAAe;AAErB,IAAM,OAAA,GAAU,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,YAAA,EAAa,EAAG,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,WAAA,EAAc,CAAC,CAAA,CAAA,CAAG;AAIjF,SAAS,YAAY,IAAA,EAAsB;AAChD,EAAA,IAAI,CAAA,GAAI,UAAA;AACR,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AAAE,IAAA,CAAA,IAAK,IAAA,CAAK,WAAW,CAAC,CAAA;AAAG,IAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,QAAU,CAAA;AAAA,EAAG;AAC/F,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AAAI,EAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,UAAU,CAAA;AAAG,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AAAI,EAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,UAAU,CAAA;AAAG,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AACrG,EAAA,OAAA,CAAQ,MAAM,CAAA,IAAK,YAAA;AACrB;AAGO,SAAS,UAAU,IAAA,EAAsB;AAC9C,EAAA,OAAO,OAAA,CAAQ,WAAA,CAAY,IAAI,CAAC,CAAA;AAClC","file":"index.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// `el`: the tiny tag-typed element factory both apps build their imperative,\n// idempotent views with.\n//\n// A SUPERSET of the two apps' prior signatures, so neither rewrites call sites:\n// - Patterpad: el(tag, cls?, text?) (positional)\n// - Storylet Studio: el(tag, { className, text, ... }, ...kids) (options bag)\n// A string second argument is the className; an object is the props bag; string\n// children become text nodes. So `el(\"div\", \"c\", \"t\")` and\n// `el(\"div\", { className: \"c\" }, \"t\")` produce identical DOM.\n// ---------------------------------------------------------------------------\n\nexport type Child = Node | string | null | undefined;\n\nexport interface ElProps {\n className?: string;\n text?: string;\n title?: string;\n onClick?: (event: MouseEvent) => void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n classNameOrProps?: string | ElProps,\n ...children: Child[]\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n const props: ElProps = typeof classNameOrProps === \"string\"\n ? { className: classNameOrProps }\n : classNameOrProps ?? {};\n if (props.className) node.className = props.className;\n if (props.text !== undefined && props.text !== null) node.textContent = props.text;\n if (props.title !== undefined) node.title = props.title;\n if (props.onClick) node.addEventListener(\"click\", props.onClick as EventListener);\n for (const child of children) {\n if (child === null || child === undefined) continue;\n node.append(child);\n }\n return node;\n}\n","// ---------------------------------------------------------------------------\n// The per-entity colour model (design-language.md section 4): a stable hash of\n// a name selects an INDEX into the curated palette - repeatable, storage-free.\n// The hues live in the token layer as `--char-0 .. --char-(N-1)` per theme, so\n// a colour reference adapts to light/dark with zero JS. Each app defines the\n// `--char-*` values in its own palette; this module only picks the slot.\n//\n// Storylet Studio uses it for dimension-value / zone chips; Patterpad for\n// character cues.\n// ---------------------------------------------------------------------------\n\n/** Curated colour slots; must match the `--char-*` set defined per theme. */\nexport const PALETTE_SIZE = 12;\n\nexport const PALETTE = Array.from({ length: PALETTE_SIZE }, (_, i) => `var(--char-${i})`) as readonly string[];\n\n/** Hash a name to a palette slot: FNV-1a + an fmix32 avalanche, well\n * distributed even for short, similar names. */\nexport function colourIndex(name: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < name.length; i++) { h ^= name.charCodeAt(i); h = Math.imul(h, 0x01000193); }\n h ^= h >>> 16; h = Math.imul(h, 0x85ebca6b); h ^= h >>> 13; h = Math.imul(h, 0xc2b2ae35); h ^= h >>> 16;\n return (h >>> 0) % PALETTE_SIZE;\n}\n\n/** The theme-aware CSS variable reference for a name's slot. */\nexport function colourFor(name: string): string {\n return PALETTE[colourIndex(name)]!;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/dom.ts","../src/colour.ts","../src/icons.ts","../src/pane-shell.ts","../src/menu.ts","../src/settings.ts","../src/tool-window-web.ts","../src/confirm.ts","../src/about.ts","../src/anchored.ts","../src/vc-view.ts","../src/identity.ts","../src/notes-editor.ts","../src/comments.ts","../src/job-view.ts","../src/job.ts","../src/tooltip.ts","../src/save.ts"],"names":["label","el","active","timer","GAP","thread"],"mappings":"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","file":"index.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// `el`: the tiny tag-typed element factory both apps build their imperative,\n// idempotent views with.\n//\n// A SUPERSET of the two apps' prior signatures, so neither rewrites call sites:\n// - Patterpad: el(tag, cls?, text?) (positional)\n// - Storylet Studio: el(tag, { className, text, ... }, ...kids) (options bag)\n// A string second argument is the className; an object is the props bag; string\n// children become text nodes. So `el(\"div\", \"c\", \"t\")` and\n// `el(\"div\", { className: \"c\" }, \"t\")` produce identical DOM.\n// ---------------------------------------------------------------------------\n\nexport type Child = Node | string | null | undefined;\n\nexport interface ElProps {\n className?: string;\n text?: string;\n /** A NATIVE rollover. Prefer `tip`: `title` is OS chrome on the platform's own\n * slow delay, which is the seam the themed tooltip exists to close. */\n title?: string;\n /** A themed rollover (`data-tip`, picked up by the delegated controller that\n * `initTooltips()` wires). Also the accessible name, since the elements that\n * want one are usually icon buttons with no text of their own. */\n tip?: string;\n onClick?: (event: MouseEvent) => void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n classNameOrProps?: string | ElProps,\n ...children: Child[]\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n const props: ElProps = typeof classNameOrProps === \"string\"\n ? { className: classNameOrProps }\n : classNameOrProps ?? {};\n if (props.className) node.className = props.className;\n if (props.text !== undefined && props.text !== null) node.textContent = props.text;\n if (props.title !== undefined) node.title = props.title;\n if (props.tip !== undefined) { node.dataset[\"tip\"] = props.tip; node.setAttribute(\"aria-label\", props.tip); }\n if (props.onClick) node.addEventListener(\"click\", props.onClick as EventListener);\n for (const child of children) {\n if (child === null || child === undefined) continue;\n node.append(child);\n }\n return node;\n}\n\n/** A small square glyph button (the move/delete controls list editors share).\n * Its rollover is the THEMED one (`data-tip`), so an app that has called\n * `initTooltips()` gets our bubble rather than the platform's. */\nexport function iconBtn(glyph: string, title: string, onClick: () => void, disabled = false, danger = false): HTMLButtonElement {\n const b = el(\"button\", `shell-icon${danger ? \" danger\" : \"\"}`);\n b.type = \"button\"; b.textContent = glyph; b.dataset[\"tip\"] = title; b.setAttribute(\"aria-label\", title);\n b.disabled = disabled;\n b.addEventListener(\"click\", onClick);\n return b;\n}\n\n/** A captioned field: `<label class=\"shell-labelled\"><span class=\"shell-fieldcap\">…</span>control</label>`. */\nexport function labelled(label: string, control: HTMLElement): HTMLElement {\n const w = el(\"label\", \"shell-labelled\");\n w.append(el(\"span\", \"shell-fieldcap\", label), control);\n return w;\n}\n\n/** Swap item `i` with its neighbour `i + delta` IN PLACE (the up/down reorder list editors share);\n * a no-op when the target is out of range. Returns whether anything moved. */\nexport function moveItem<T>(arr: T[], i: number, delta: number): boolean {\n const j = i + delta;\n if (j < 0 || j >= arr.length) return false;\n [arr[i], arr[j]] = [arr[j]!, arr[i]!];\n return true;\n}\n\n/** A tag-style editor for a string-list field (an enum's allowed values / flags): removable chips + an\n * add input (Enter or \",\" commits; blank / duplicate ignored). Mutates `holder.values` in place (read\n * back on save). `onChange` fires after any add / remove so callers can refresh a dependent control. */\nexport function tagChips(holder: { values?: string[] }, onChange?: () => void): HTMLElement {\n const wrap = el(\"div\", \"shell-tags\");\n const input = el(\"input\", \"shell-tag-input\");\n input.type = \"text\"; input.placeholder = \"add value\"; input.spellcheck = false;\n const makeChip = (v: string): HTMLElement => {\n const chip = el(\"span\", \"shell-tag\", v);\n const x = el(\"button\", \"shell-tag-x\", \"✕\");\n x.type = \"button\"; x.title = `remove ${v}`; x.setAttribute(\"aria-label\", `remove ${v}`);\n x.addEventListener(\"click\", () => { holder.values = (holder.values ?? []).filter((o) => o !== v); chip.remove(); onChange?.(); });\n chip.append(x);\n return chip;\n };\n const commit = (): void => {\n const v = input.value.trim();\n if (v && !(holder.values ?? []).includes(v)) { (holder.values ??= []).push(v); wrap.insertBefore(makeChip(v), input); onChange?.(); }\n input.value = \"\";\n };\n input.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\" || e.key === \",\") { e.preventDefault(); commit(); } });\n input.addEventListener(\"blur\", commit);\n for (const v of holder.values ?? []) wrap.append(makeChip(v));\n wrap.append(input);\n return wrap;\n}\n","// ---------------------------------------------------------------------------\n// The per-entity colour model (design-language.md section 4): a stable hash of\n// a name selects an INDEX into the curated palette - repeatable, storage-free.\n// The hues live in the token layer as `--char-0 .. --char-(N-1)` per theme, so\n// a colour reference adapts to light/dark with zero JS. Each app defines the\n// `--char-*` values in its own palette; this module only picks the slot.\n//\n// Storylet Studio uses it for dimension-value / zone chips; Patterpad for\n// character cues.\n// ---------------------------------------------------------------------------\n\n/** Curated colour slots; must match the `--char-*` set defined per theme. */\nexport const PALETTE_SIZE = 12;\n\nexport const PALETTE = Array.from({ length: PALETTE_SIZE }, (_, i) => `var(--char-${i})`) as readonly string[];\n\n/** Hash a name to a palette slot: FNV-1a + an fmix32 avalanche, well\n * distributed even for short, similar names. */\nexport function colourIndex(name: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < name.length; i++) { h ^= name.charCodeAt(i); h = Math.imul(h, 0x01000193); }\n h ^= h >>> 16; h = Math.imul(h, 0x85ebca6b); h ^= h >>> 13; h = Math.imul(h, 0xc2b2ae35); h ^= h >>> 16;\n return (h >>> 0) % PALETTE_SIZE;\n}\n\n/** The theme-aware CSS variable reference for a name's slot. */\nexport function colourFor(name: string): string {\n return PALETTE[colourIndex(name)]!;\n}\n","// ---------------------------------------------------------------------------\n// The family's icon vocabulary.\n//\n// An icon is a WORD. Two apps in one suite spelling \"close\" differently is the\n// same fault as calling one thing a scene here and a chapter there, and it is\n// harder to notice because nobody reads glyphs, they just feel wrong. This\n// module exists so that no app ever types a codepoint again: it asks for\n// `icon.close` and gets whatever the family has agreed that means.\n//\n// The evidence that it was needed, gathered 2026-08-09 across both apps:\n// Patterpad spelled close as `✕` twelve times, Storyletter used `✕` once and\n// `×` twice - in one window - and a comment bubble had just been invented from\n// scratch with nothing to check it against.\n//\n// GLYPHS, mostly, rather than SVG. That is a deliberate limit: a text glyph\n// inherits colour, size and weight from its button for free, costs nothing to\n// ship, and is legible at the small sizes this chrome lives at. SVG is here for\n// the two or three shapes Unicode has no honest character for (a document with\n// lines on it), lifted from Patterpad where it already drew them.\n//\n// Adding one: it needs a MEANING, not a picture. \"A downward chevron\" is not an\n// entry; \"expand this\" is. If two callers want the same picture for two\n// meanings, that is two entries, and one of them will be wrong later.\n// ---------------------------------------------------------------------------\n\n/**\n * Named by what it MEANS. Grouped by the job, because that is how a caller\n * looks for one.\n */\nexport const icon = {\n // --- dismissing and revealing ---------------------------------------------\n /** Close a panel, a dialog, a popover. Patterpad's, twelve times over. */\n close: \"✕\",\n /** The overflow menu on a document or a row: \"more about this\". */\n more: \"⋯\",\n /** Collapsed disclosure, pointing at what would open. */\n collapsed: \"▸\",\n /** Expanded disclosure. */\n expanded: \"▾\",\n /** A dropdown's affordance, where it is not a real <select>. */\n dropdown: \"▾\",\n\n // --- moving through things ------------------------------------------------\n /** Back / previous, in a breadcrumb or a stepper. */\n back: \"‹\",\n /** Forward / next. */\n forward: \"›\",\n /** Up one, in a list. */\n up: \"↑\",\n /** Down one. */\n down: \"↓\",\n\n // --- state ----------------------------------------------------------------\n /** Done, applied, agreed. */\n tick: \"✓\",\n /** Somebody else holds this: the version-control lock. */\n locked: \"⊘\",\n /** Read-only on disk with no other holder: the resting state of everything\n * in a lock-based working copy that nobody has checked out yet. */\n readOnly: \"○\",\n /** Something needs attention but is not fatal. */\n warning: \"‼\",\n /** A live thing: the running position, an active marker. */\n dot: \"●\",\n /** Play, in the sense of \"run this\". */\n play: \"▶\",\n\n // --- handling -------------------------------------------------------------\n /** The drag grip on a reorderable row. */\n grip: \"⠿\",\n /** Add one of whatever this list holds. */\n add: \"+\",\n\n // --- annotation -----------------------------------------------------------\n /** Documentation notes: the pencil. Both apps had chosen it independently,\n * which is the happiest way for a vocabulary entry to arrive. */\n note: \"✎\",\n /** A comment thread. Speech, not a pencil: documentation states the reason,\n * comments are the conversation, and the two must never look alike. */\n comment: \"❝\",\n} as const;\n\nexport type IconName = keyof typeof icon;\n\n/**\n * The few shapes no glyph says honestly, as inline SVG.\n *\n * `currentColor` throughout, so they take the colour of the button they sit in\n * exactly as the glyphs do, and sized in the markup for the same reason: an icon\n * that needs a CSS rule to be the right size is an icon that will be the wrong\n * size somewhere.\n *\n * Lifted from Patterpad, which drew them first.\n */\nexport const iconSvg = {\n /** A document with lines: notes exist on this thing. */\n 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>',\n /** The same document, empty: nothing written here yet. */\n 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>',\n} as const;\n\nexport type IconSvgName = keyof typeof iconSvg;\n","// ---------------------------------------------------------------------------\n// mountPaneShell: the app-frame both editors share - a top bar with compact\n// pane toggles, a nav | centre | inspector grid whose side panes collapse and\n// resize, and width/open-state persistence via a host callback. The host fills\n// the pane bodies + top-bar slots and owns its own keyboard accelerators\n// (call togglePane); the frame owns layout, collapse, drag-resize, and state.\n//\n// The frame CSS ships alongside as pane-shell.css (import it once at the app\n// root). It reads the shared token grammar (tokens.css).\n// ---------------------------------------------------------------------------\n\nexport type PaneSide = \"nav\" | \"inspector\";\n\nexport interface PaneSideConfig {\n /** The open width as a CSS length, e.g. \"14rem\" or \"224px\". */\n defaultWidth: string;\n /** Drag floor in px (default 160). */\n minWidth?: number;\n /** Drag ceiling in px from the window width (default w => min(640, w - 420)). */\n maxWidth?: (innerWidth: number) => number;\n /** Whether a drag seam is offered (default true). */\n resizable?: boolean;\n /** Noun for the toggle's tooltip/aria, e.g. \"navigator\" / \"inspector\". */\n label?: string;\n /** Shortcut hint appended to the tooltip, e.g. \"Cmd+1\". */\n shortcutHint?: string;\n}\n\nexport interface PaneShellState {\n open: Record<PaneSide, boolean>;\n /** Dragged widths in px; absent = the config default. */\n width: Partial<Record<PaneSide, number>>;\n}\n\nexport interface PaneShellOptions {\n nav: PaneSideConfig;\n inspector: PaneSideConfig;\n /** Initial open + width state (from the host's store). */\n initial?: Partial<PaneShellState>;\n /** Persist state after any toggle or resize (host wires it to its store). */\n onChange?: (state: PaneShellState) => void;\n}\n\nexport interface PaneShell {\n /** The frame element (top bar + panes); mount it into the app container. */\n readonly root: HTMLElement;\n readonly topbar: HTMLElement;\n /** Top-bar host slots: leading (title) and trailing (status), the host fills. */\n readonly topbarLead: HTMLElement;\n readonly topbarTrail: HTMLElement;\n /** Pane bodies the host fills (the fixed-width inner that clips on collapse). */\n readonly nav: HTMLElement;\n readonly centre: HTMLElement;\n readonly inspector: HTMLElement;\n togglePane(side: PaneSide): void;\n setPaneOpen(side: PaneSide, open: boolean): void;\n isOpen(side: PaneSide): boolean;\n /** Clear dragged widths back to the config defaults. */\n resetWidths(): void;\n /** A snapshot of the current state (open + widths). */\n state(): PaneShellState;\n destroy(): void;\n}\n\nconst DEFAULT_MAX = (w: number): number => Math.min(640, w - 420);\nconst openVar: Record<PaneSide, string> = { nav: \"--nav-open-w\", inspector: \"--insp-open-w\" };\nconst collapseClass: Record<PaneSide, string> = { nav: \"no-nav\", inspector: \"no-inspector\" };\n\nexport function mountPaneShell(host: HTMLElement, opts: PaneShellOptions): PaneShell {\n const cfg: Record<PaneSide, PaneSideConfig> = { nav: opts.nav, inspector: opts.inspector };\n const open: Record<PaneSide, boolean> = { nav: opts.initial?.open?.nav ?? true, inspector: opts.initial?.open?.inspector ?? true };\n const width: Partial<Record<PaneSide, number>> = { ...(opts.initial?.width ?? {}) };\n\n const el = <K extends keyof HTMLElementTagNameMap>(tag: K, cls?: string): HTMLElementTagNameMap[K] => {\n const n = document.createElement(tag);\n if (cls) n.className = cls;\n return n;\n };\n\n // --- top bar ---------------------------------------------------------------\n const navToggle = el(\"button\", \"pane-toggle\");\n navToggle.type = \"button\";\n const inspToggle = el(\"button\", \"pane-toggle\");\n inspToggle.type = \"button\";\n const topbarLead = el(\"div\", \"topbar-lead\");\n const topbarTrail = el(\"div\", \"topbar-trail\");\n const spacer = el(\"span\", \"topbar-spacer\");\n const topbar = el(\"header\", \"topbar\");\n topbar.append(navToggle, topbarLead, spacer, topbarTrail, inspToggle);\n\n // --- panes -----------------------------------------------------------------\n const panes = el(\"main\", \"panes\");\n const navPane = el(\"nav\", \"pane pane-nav\");\n const navInner = el(\"div\", \"pane-inner\");\n navPane.append(navInner);\n const centre = el(\"section\", \"pane-centre\");\n const inspPane = el(\"aside\", \"pane pane-inspector\");\n const inspInner = el(\"div\", \"pane-inner\");\n inspPane.append(inspInner);\n const navResizer = el(\"div\", \"pane-resizer pane-resizer-nav\");\n const inspResizer = el(\"div\", \"pane-resizer pane-resizer-insp\");\n panes.append(navPane, centre, inspPane, navResizer, inspResizer);\n\n const root = el(\"div\", \"pane-shell\");\n root.append(topbar, panes);\n host.append(root);\n\n // --- state application -----------------------------------------------------\n function applyToggleGlyph(side: PaneSide, button: HTMLButtonElement): void {\n const isOpen = open[side];\n // Chevron points toward where the pane collapses to.\n button.textContent = side === \"nav\" ? (isOpen ? \"‹\" : \"›\") : (isOpen ? \"›\" : \"‹\");\n const noun = cfg[side].label ?? side;\n const hint = cfg[side].shortcutHint ? ` (${cfg[side].shortcutHint})` : \"\";\n button.title = `${isOpen ? \"Hide\" : \"Show\"} ${noun}${hint}`;\n button.setAttribute(\"aria-label\", button.title);\n button.setAttribute(\"aria-pressed\", String(isOpen));\n }\n\n function apply(): void {\n panes.classList.toggle(collapseClass.nav, !open.nav);\n panes.classList.toggle(collapseClass.inspector, !open.inspector);\n // The open widths live on the grid: the track and each .pane-inner read them.\n panes.style.setProperty(openVar.nav, width.nav !== undefined ? `${width.nav}px` : cfg.nav.defaultWidth);\n panes.style.setProperty(openVar.inspector, width.inspector !== undefined ? `${width.inspector}px` : cfg.inspector.defaultWidth);\n applyToggleGlyph(\"nav\", navToggle);\n applyToggleGlyph(\"inspector\", inspToggle);\n navResizer.style.display = cfg.nav.resizable === false ? \"none\" : \"\";\n inspResizer.style.display = cfg.inspector.resizable === false ? \"none\" : \"\";\n }\n\n const snapshot = (): PaneShellState => ({ open: { ...open }, width: { ...width } });\n const persist = (): void => opts.onChange?.(snapshot());\n\n function setPaneOpen(side: PaneSide, next: boolean): void {\n if (open[side] === next) return;\n open[side] = next;\n apply();\n persist();\n }\n function togglePane(side: PaneSide): void { setPaneOpen(side, !open[side]); }\n function resetWidths(): void { delete width.nav; delete width.inspector; apply(); persist(); }\n\n navToggle.addEventListener(\"click\", () => togglePane(\"nav\"));\n inspToggle.addEventListener(\"click\", () => togglePane(\"inspector\"));\n\n // --- drag resize -----------------------------------------------------------\n function beginResize(side: PaneSide, startEvent: PointerEvent): void {\n if (cfg[side].resizable === false || !open[side]) return;\n startEvent.preventDefault();\n const pane = side === \"nav\" ? navPane : inspPane;\n const min = cfg[side].minWidth ?? 160;\n const max = cfg[side].maxWidth ?? DEFAULT_MAX;\n const startX = startEvent.clientX;\n const startW = pane.getBoundingClientRect().width;\n document.body.classList.add(\"pane-resizing\");\n let latest = startW;\n const onMove = (e: PointerEvent): void => {\n const delta = side === \"nav\" ? e.clientX - startX : startX - e.clientX;\n latest = Math.max(min, Math.min(max(window.innerWidth), startW + delta));\n panes.style.setProperty(openVar[side], `${latest}px`);\n };\n const onUp = (): void => {\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", onUp);\n document.body.classList.remove(\"pane-resizing\");\n width[side] = latest;\n persist();\n };\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", onUp);\n }\n navResizer.addEventListener(\"pointerdown\", (e) => beginResize(\"nav\", e));\n inspResizer.addEventListener(\"pointerdown\", (e) => beginResize(\"inspector\", e));\n navResizer.addEventListener(\"dblclick\", () => { delete width.nav; apply(); persist(); });\n inspResizer.addEventListener(\"dblclick\", () => { delete width.inspector; apply(); persist(); });\n\n apply();\n\n return {\n root, topbar, topbarLead, topbarTrail,\n nav: navInner, centre, inspector: inspInner,\n togglePane, setPaneOpen, isOpen: (s) => open[s], resetWidths, state: snapshot,\n destroy: () => { root.remove(); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Shell-owned menu naming: the labels + accelerators for the pane-frame menu\n// items, single-sourced so every app in the family names them identically\n// (and a rename here flows to all of them). Pure data - no electron, no DOM -\n// so a main-process menu builder can import it. Each app builds its own\n// MenuItem from these and wires the click to its shell controller.\n// ---------------------------------------------------------------------------\n\nexport interface MenuLabel {\n label: string;\n /** Electron accelerator string, when the item has one. */\n accelerator?: string;\n}\n\n/**\n * The Edit menu items the family shares.\n *\n * Undo and Redo are here and the MECHANISM is not, which is the whole finding\n * about undo: Patterpad's is ProseMirror's document history, Storyletter's is a\n * replay of file bytes, and those have nothing in common but this. See\n * `design/shared-shell.md`.\n *\n * The rule both apps discovered separately, and the reason these are spelled\n * out rather than left as Electron's `role: \"undo\"`: the native role runs a DOM\n * text-field undo, which knows nothing about either app's model and will\n * silently undo the wrong thing.\n */\nexport const EDIT_MENU: {\n undo: MenuLabel;\n redo: MenuLabel;\n duplicate: MenuLabel;\n find: MenuLabel;\n} = {\n undo: { label: \"Undo\", accelerator: \"CmdOrCtrl+Z\" },\n redo: { label: \"Redo\", accelerator: \"Shift+CmdOrCtrl+Z\" },\n duplicate: { label: \"Duplicate\", accelerator: \"CmdOrCtrl+D\" },\n find: { label: \"Find…\", accelerator: \"CmdOrCtrl+F\" },\n};\n\n/** The pane-frame menu items (a View menu, conventionally). */\nexport const PANE_MENU: {\n showNav: MenuLabel;\n showInspector: MenuLabel;\n resetView: MenuLabel;\n} = {\n showNav: { label: \"Show Navigator\", accelerator: \"CmdOrCtrl+1\" },\n showInspector: { label: \"Show Inspector\", accelerator: \"CmdOrCtrl+2\" },\n resetView: { label: \"Reset View\" },\n};\n","// ---------------------------------------------------------------------------\n// The shared settings surface: a native <dialog> with a left-rail of grouped\n// tabs and one panel per section (mountSettingsDialog), plus the list-editing\n// core those sections lean on (expandableRow, focusNewRow, dupGuard). The app\n// supplies the sections; each holds its own state and reports the first invalid\n// control so the Save gate can block + jump. Framework here, content in the app.\n//\n// Lifted from Patterpad's Project Settings (settings-list.ts + the inlined\n// dialog logic in its renderer), with neutral class names. Styling ships as\n// settings.css.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\n\n// --- list-editing core --------------------------------------------------------\n\n/** A compact list row: a single `.set-rowline` (inline controls) plus, when\n * `details` are given, a disclosure that reveals a `.set-details` panel below,\n * so a list scans like a table with secondary fields tucked away. */\nexport function expandableRow(opts: { line: HTMLElement[]; details?: HTMLElement[] }): HTMLElement {\n const row = el(\"div\", \"set-row\");\n const line = el(\"div\", \"set-rowline\");\n if (opts.details && opts.details.length) {\n const details = el(\"div\", \"set-details\"); details.hidden = true;\n for (const d of opts.details) details.append(d);\n // The triangle is drawn by CSS off `.open` (a text glyph stays hairline-thin).\n const toggle = el(\"button\", \"set-expand\"); toggle.type = \"button\";\n toggle.title = \"More\"; toggle.setAttribute(\"aria-label\", \"More\");\n toggle.setAttribute(\"aria-expanded\", \"false\");\n toggle.addEventListener(\"click\", () => {\n const open = details.hidden;\n details.hidden = !open;\n toggle.setAttribute(\"aria-expanded\", String(open));\n toggle.classList.toggle(\"open\", open);\n });\n line.append(toggle);\n opts.line.forEach((c) => line.append(c));\n row.append(line, details);\n } else {\n opts.line.forEach((c) => line.append(c));\n row.append(line);\n }\n return row;\n}\n\n/** After a \"+ Add\" re-renders a list, bring the new (last) row into view and\n * focus its name input (`.set-name`, else the first input). */\nexport function focusNewRow(listEl: HTMLElement | null | undefined): void {\n const last = listEl?.lastElementChild;\n if (!(last instanceof HTMLElement)) return;\n last.scrollIntoView({ block: \"nearest\" });\n const name = last.querySelector<HTMLInputElement>(\"input.set-name\") ?? last.querySelector<HTMLInputElement>(\"input\");\n name?.focus();\n}\n\nexport interface DupGuard {\n /** Forget the previous render's inputs (call at the top of render). */\n reset(): void;\n /** Register a name input; `key` returns the comparison string (default the\n * input's value). Re-checks live on input. `also` = extra inputs that change the key. */\n track(input: HTMLInputElement, key?: () => string, also?: HTMLInputElement[]): void;\n /** Re-paint duplicates; true if any two non-blank keys collide. */\n check(): boolean;\n /** The first input currently flagged (after a fresh check), or null. */\n firstDuplicate(): HTMLInputElement | null;\n}\n\n/** Case-insensitive duplicate-name detection: paints clashing inputs `.invalid`\n * and lets a Save gate block + jump via `firstDuplicate()`. */\nexport function dupGuard(): DupGuard {\n const items: Array<{ input: HTMLInputElement; key: () => string }> = [];\n const check = (): boolean => {\n const counts = new Map<string, number>();\n for (const it of items) { const k = it.key().trim().toLowerCase(); if (k) counts.set(k, (counts.get(k) ?? 0) + 1); }\n let any = false;\n for (const it of items) {\n const k = it.key().trim().toLowerCase();\n const dup = !!k && (counts.get(k) ?? 0) > 1;\n it.input.classList.toggle(\"invalid\", dup);\n if (dup) { it.input.title = \"Duplicate name. Names must be unique.\"; any = true; }\n else if (it.input.title === \"Duplicate name. Names must be unique.\") it.input.removeAttribute(\"title\");\n }\n return any;\n };\n return {\n reset() { items.length = 0; },\n track(input, key = () => input.value, also) {\n items.push({ input, key });\n input.addEventListener(\"input\", check);\n for (const a of also ?? []) a.addEventListener(\"input\", check);\n },\n check,\n firstDuplicate() { check(); return items.find((it) => it.input.classList.contains(\"invalid\"))?.input ?? null; },\n };\n}\n\n// --- the settings dialog ------------------------------------------------------\n\nexport interface SettingsSectionHandle {\n /** The first invalid control to jump to, blocking Save (e.g. a duplicate name). */\n firstInvalid?(): HTMLElement | null;\n}\n\nexport interface SettingsSection {\n id: string;\n label: string;\n /** Optional left-rail group heading above this tab. */\n group?: string;\n /** Build the section body into `host`; called fresh on each open so it reads\n * the current data. Return a handle (for the Save gate). */\n mount(host: HTMLElement): SettingsSectionHandle;\n}\n\nexport interface SettingsDialogOptions {\n title: string;\n sections: SettingsSection[];\n /** Fired when Save is clicked and every section validates. The app reads its\n * own section state and persists. May be async; the dialog stays open until\n * it resolves, then closes. */\n onSave: () => void | Promise<void>;\n}\n\nexport interface SettingsDialog {\n /** Open (optionally on a given tab id). Sections re-mount from current data. */\n open(tabId?: string): void;\n destroy(): void;\n}\n\n/** Build a settings dialog. The frame owns the modal, the tab rail, save-on-Save\n * (with a validation gate), and Cancel; the sections own their content + state. */\nexport function mountSettingsDialog(opts: SettingsDialogOptions): SettingsDialog {\n const dialog = el(\"dialog\", \"settings-dialog\");\n const form = el(\"form\", \"settings-form\"); form.method = \"dialog\";\n const tabs = el(\"nav\", \"settings-tabs\");\n const panels = el(\"div\", \"settings-panels\");\n const error = el(\"p\", \"settings-error\"); error.hidden = true;\n const cancel = el(\"button\", \"settings-cancel\", \"Cancel\"); cancel.type = \"button\"; cancel.value = \"cancel\";\n const save = el(\"button\", \"settings-save primary\", \"Save\"); save.value = \"save\";\n\n const panelFor = new Map<string, HTMLElement>();\n const tabFor = new Map<string, HTMLButtonElement>();\n const handles = new Map<string, SettingsSectionHandle>();\n let active = opts.sections[0]?.id ?? \"\";\n\n let lastGroup: string | undefined;\n for (const s of opts.sections) {\n if (s.group && s.group !== lastGroup) { tabs.append(el(\"div\", \"settings-group\", s.group)); lastGroup = s.group; }\n const tab = el(\"button\", \"settings-tab\", s.label); tab.type = \"button\"; tab.dataset[\"tab\"] = s.id;\n tab.addEventListener(\"click\", () => showTab(s.id));\n tabs.append(tab); tabFor.set(s.id, tab);\n const panel = el(\"section\", \"settings-panel\"); panel.dataset[\"panel\"] = s.id; panel.hidden = true;\n panels.append(panel); panelFor.set(s.id, panel);\n }\n\n function showTab(id: string): void {\n active = id;\n for (const [sid, t] of tabFor) t.classList.toggle(\"active\", sid === id);\n for (const [sid, p] of panelFor) p.hidden = sid !== id;\n }\n\n form.append(\n el(\"h2\", \"settings-title\", opts.title),\n el(\"div\", \"settings-body\", tabs, panels),\n el(\"div\", \"settings-actions\", error, cancel, save),\n );\n dialog.append(form);\n document.body.append(dialog);\n\n // Enter should not submit while editing a field; only the Save button does.\n form.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && (e.target as HTMLElement)?.tagName === \"INPUT\") e.preventDefault();\n });\n\n cancel.addEventListener(\"click\", () => dialog.close(\"cancel\"));\n save.addEventListener(\"click\", (e) => {\n e.preventDefault();\n error.hidden = true;\n for (const s of opts.sections) {\n const bad = handles.get(s.id)?.firstInvalid?.();\n if (bad) { showTab(s.id); bad.focus(); error.textContent = \"Fix the highlighted fields first.\"; error.hidden = false; return; }\n }\n void (async () => { await opts.onSave(); dialog.close(\"save\"); })();\n });\n\n return {\n open(tabId) {\n handles.clear();\n for (const s of opts.sections) {\n const panel = panelFor.get(s.id)!;\n panel.replaceChildren();\n handles.set(s.id, s.mount(panel));\n }\n error.hidden = true;\n showTab(tabId ?? active ?? opts.sections[0]?.id ?? \"\");\n dialog.showModal();\n },\n destroy() { dialog.remove(); },\n };\n}\n","// ---------------------------------------------------------------------------\n// The tool-window kit, RENDERER half: the always-on-top pin button every\n// helper window carries (previously pasted verbatim in five places across\n// the two apps). The matching chrome CSS - .swin-head drag bar, .swin-pin,\n// .swin-close - ships as tool-window.css.\n//\n// The frameless drag bar itself needs no JS at all: it is markup + the\n// -webkit-app-region rules in tool-window.css.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\n\n/** The pin glyph (shared across the family; byte-identical everywhere). */\nconst PIN_ICON = `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"22\"/><path d=\"M9 10.8a2 2 0 0 1-1.1 1.8l-1.8.9A2 2 0 0 0 5 15.3V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.7a2 2 0 0 0-1.1-1.8l-1.8-.9A2 2 0 0 1 15 10.8V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>`;\n\nexport interface PinButtonOptions {\n pinned: boolean;\n /** Persist the choice (the button keeps its own visual state). */\n onToggle: (on: boolean) => void;\n}\n\n/** The always-on-top pin: `.swin-pin`, aria-pressed, and a tooltip that says\n * what a click will do (the richest of the family's variants, kept). */\nexport function pinButton(opts: PinButtonOptions): HTMLButtonElement {\n const b = el(\"button\", \"swin-pin\");\n b.type = \"button\";\n b.innerHTML = PIN_ICON;\n let pinned = opts.pinned;\n const reflect = (): void => {\n b.classList.toggle(\"on\", pinned);\n b.setAttribute(\"aria-pressed\", String(pinned));\n b.title = pinned ? \"Pinned on top: click to unpin\" : \"Click to keep on top\";\n };\n reflect();\n b.addEventListener(\"click\", () => { pinned = !pinned; reflect(); opts.onToggle(pinned); });\n return b;\n}\n","// ---------------------------------------------------------------------------\n// A themed in-app confirmation modal (design-language \"coherent to the\n// edges\": never a stock OS dialog). The promise API is Patterpad's surface\n// confirm; the build is native <dialog> + showModal() so the focus trap and\n// Esc handling come free. Two deliberate corrections over that original,\n// kept when Patterpad migrates here: the destructive button wears --danger\n// (a required host token - the surface one mistakenly used --accent), and\n// initial focus lands on Cancel so a stray Enter cannot destroy anything.\n//\n// Styles ship as confirm.css (import it beside tokens.css). Callers keep the\n// elide-the-dialog rule: when nothing is at stake, don't ask.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\n\nexport interface ConfirmOptions {\n title: string;\n body: string;\n /** The destructive button's label (\"Delete box\", \"Restart\"). */\n confirmLabel: string;\n}\n\n/** Resolves true on confirm; false on Cancel / Esc / backdrop. */\nexport function confirmDialog(opts: ConfirmOptions): Promise<boolean> {\n return new Promise((resolve) => {\n const cancel = el(\"button\", \"confirm-btn cancel\", \"Cancel\");\n const danger = el(\"button\", \"confirm-btn danger\", opts.confirmLabel);\n cancel.type = \"button\";\n danger.type = \"button\";\n const dlg = el(\n \"dialog\",\n \"confirm-dialog\",\n el(\"div\", \"confirm-title\", opts.title),\n el(\"div\", \"confirm-body\", opts.body),\n el(\"div\", \"confirm-actions\", cancel, danger),\n );\n dlg.setAttribute(\"aria-label\", opts.title);\n document.body.append(dlg);\n\n let done = false;\n const finish = (ok: boolean): void => {\n if (done) return;\n done = true;\n resolve(ok);\n // Exit motion: .closing swaps the enter keyframes for their reverse\n // (zero-duration under prefers-reduced-motion, so teardown is instant).\n dlg.classList.add(\"closing\");\n const dur = parseFloat(getComputedStyle(dlg).animationDuration) || 0;\n const teardown = (): void => { dlg.close(); dlg.remove(); };\n if (dur === 0) { teardown(); return; }\n const timer = setTimeout(teardown, dur * 1000 + 120);\n dlg.addEventListener(\"animationend\", (e) => {\n if (e.target !== dlg) return;\n clearTimeout(timer);\n teardown();\n });\n };\n\n cancel.addEventListener(\"click\", () => finish(false));\n danger.addEventListener(\"click\", () => finish(true));\n // Esc arrives as the dialog's cancel event; route it through the exit motion.\n dlg.addEventListener(\"cancel\", (e) => { e.preventDefault(); finish(false); });\n // A mousedown that lands on the dialog element itself is the backdrop.\n dlg.addEventListener(\"mousedown\", (e) => { if (e.target === dlg) finish(false); });\n\n dlg.showModal();\n cancel.focus();\n });\n}\n","// ---------------------------------------------------------------------------\n// The About surface: every app in the family has one, and it should never be\n// the grey OS panel (design-language, \"coherent to the edges\").\n//\n// Patterpad's, generalised. Theirs reuses its updater-dialog chrome and hard\n// codes the PatterKit wordmark; the shell takes the wordmark as markup from the\n// app, because that is the one part that is genuinely per-product. Everything\n// else - the layout, the type, the link handling, the single Close - is the\n// family's, so two apps' About boxes are recognisably siblings.\n//\n// The host opens links, because a renderer cannot: `onOpenLink` goes through\n// the app's own IPC to `shell.openExternal`. Without it the links are shown as\n// plain text rather than as buttons that do nothing.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\n\nexport interface AboutOptions {\n /** The product's name, large. */\n appName: string;\n /** \"1.4.2\" - shown as \"Version 1.4.2\". */\n version: string;\n /** One or two sentences: what this app is for. */\n blurb?: string;\n /** Licence, authorship, the suite it belongs to. Newlines are respected. */\n credits?: string;\n links?: { label: string; url: string }[];\n /** Inline SVG for the product wordmark, if it has one. */\n wordmark?: string;\n /** Open a URL in the real browser. Omit and the links render as text. */\n onOpenLink?: (url: string) => void;\n}\n\n/** Show the About dialog. Resolves when it closes. */\nexport function showAbout(opts: AboutOptions): Promise<void> {\n return new Promise((resolve) => {\n const dialog = el(\"dialog\", \"shell-about\");\n if (opts.wordmark !== undefined) {\n const mark = el(\"div\", \"shell-about-mark\");\n mark.innerHTML = opts.wordmark;\n dialog.append(mark);\n }\n dialog.append(\n el(\"div\", \"shell-about-name\", opts.appName),\n el(\"div\", \"shell-about-version\", `Version ${opts.version}`),\n );\n if (opts.blurb !== undefined) dialog.append(el(\"p\", \"shell-about-blurb\", opts.blurb));\n if (opts.credits !== undefined) dialog.append(el(\"p\", \"shell-about-credits\", opts.credits));\n\n if (opts.links?.length) {\n const row = el(\"div\", \"shell-about-links\");\n for (const link of opts.links) {\n if (!opts.onOpenLink) { row.append(el(\"span\", \"shell-about-link\", link.label)); continue; }\n const a = el(\"button\", \"shell-about-link\", link.label);\n a.type = \"button\";\n a.addEventListener(\"click\", () => opts.onOpenLink?.(link.url));\n row.append(a);\n }\n dialog.append(row);\n }\n\n const close = el(\"button\", \"shell-about-close\", \"Close\");\n close.type = \"button\";\n dialog.append(el(\"div\", \"shell-about-actions\", close));\n dialog.setAttribute(\"aria-label\", `About ${opts.appName}`);\n document.body.append(dialog);\n\n let done = false;\n const finish = (): void => {\n if (done) return;\n done = true;\n dialog.close();\n dialog.remove();\n resolve();\n };\n close.addEventListener(\"click\", finish);\n dialog.addEventListener(\"close\", finish);\n dialog.showModal();\n close.focus();\n });\n}\n","// ---------------------------------------------------------------------------\n// A floating panel anchored to the thing it is about: Patterpad's\n// `openAnchoredPanel`, lifted and generalised.\n//\n// Patterpad's condition, effects, jump and gameId editors all use one, and so do\n// Storyletter's comment threads. Two implementations of one idea existed for\n// about a day, which is exactly the drift the shell is for - and Patterpad's was\n// the mature one, so this is that, not the one written second.\n//\n// The behaviours are all learnt from use, and each is a bug somebody hit:\n//\n// - Re-clicking the SAME anchor toggles the panel off and returns null,\n// rather than closing and reopening with a flicker. Callers must handle the\n// null.\n// - A DIFFERENT anchor swaps rather than stacking: only one is ever open.\n// - The outside-click listener is armed a tick late, or the very click that\n// opened the panel would close it again.\n// - `ignoreDown` spares body-level inner popovers, which are outside the\n// panel in the DOM but inside it as far as the author is concerned.\n// - `deferEscape` leaves Escape to an inner popover when one is open, so one\n// key does not close two things.\n//\n// Placement is the part worth reading. It flips to whichever side of the anchor\n// has MORE room, keeps clear of any bottom bars the app names, caps at 70% of\n// the window and scrolls its body. A panel that opens cramped against a status\n// bar with empty space above it is the failure this avoids.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\n\nexport interface AnchoredPanel {\n panel: HTMLElement;\n /** The content area to populate: the header is built for you. */\n body: HTMLElement;\n /** Idempotent; fires the caller's `onClose` exactly once. */\n close: () => void;\n}\n\nexport interface AnchoredPanelOptions {\n anchor: HTMLElement;\n /** Extra class(es) on the panel, for app-specific sizing or colour. */\n className?: string;\n title: string;\n width: number;\n /**\n * Which side to try first.\n *\n * `\"left\"` suits a panel hung off a row in a right-hand pane (Patterpad's\n * inspector); `\"below\"` suits one hung off a button in a document's header\n * (Storyletter's topline). Either way it flips when the room is not there.\n */\n prefer?: \"left\" | \"below\";\n /** Selectors for chrome the panel must not cover - a problems bar, a review\n * bar - so those stay usable while it is open. */\n keepClear?: string[];\n /** A pointerdown inside something matching this does NOT close the panel. */\n ignoreDown?: string;\n /** While something matching this exists, Escape belongs to it, not to us. */\n deferEscape?: string;\n onClose?: () => void;\n}\n\n/** The one open panel. Only ever one, which is what makes toggling meaningful. */\nlet current: { anchor: HTMLElement; panel: HTMLElement; close: () => void } | null = null;\n\nexport function openAnchoredPanel(opts: AnchoredPanelOptions): AnchoredPanel | null {\n if (current) {\n const prev = current;\n current = null;\n prev.close();\n if (prev.anchor === opts.anchor) return null; // a toggle, not a reopen\n }\n\n const panel = el(\"div\", `shell-anchored ${opts.className ?? \"\"}`.trim());\n const closeBtn = el(\"button\", \"shell-anchored-close\", \"✕\");\n closeBtn.type = \"button\";\n closeBtn.setAttribute(\"aria-label\", \"Close\");\n const body = el(\"div\", \"shell-anchored-body\");\n panel.append(\n el(\"div\", \"shell-anchored-head\", el(\"span\", \"shell-anchored-title\", opts.title), closeBtn),\n body,\n );\n document.body.append(panel);\n placeAnchored(panel, opts.anchor, opts.width, opts.prefer ?? \"below\", opts.keepClear ?? []);\n\n let closed = false;\n const close = (): void => {\n if (closed) return;\n closed = true;\n if (current?.panel === panel) current = null;\n document.removeEventListener(\"pointerdown\", onDown, true);\n document.removeEventListener(\"keydown\", onKey, true);\n panel.remove();\n opts.onClose?.();\n };\n closeBtn.addEventListener(\"click\", close);\n\n const onDown = (e: PointerEvent): void => {\n const target = e.target as Node;\n if (panel.contains(target) || opts.anchor.contains(target)) return;\n if (opts.ignoreDown !== undefined && target instanceof Element && target.closest(opts.ignoreDown)) return;\n close();\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key !== \"Escape\") return;\n if (opts.deferEscape !== undefined && document.querySelector(opts.deferEscape)) return;\n close();\n };\n // Next tick: the click that opened this is still travelling.\n setTimeout(() => {\n document.addEventListener(\"pointerdown\", onDown, true);\n document.addEventListener(\"keydown\", onKey, true);\n }, 0);\n\n current = { anchor: opts.anchor, panel, close };\n return { panel, body, close };\n}\n\n/** Close whatever is open. For a navigation or a re-render: a panel anchored to\n * an element that no longer exists would hang in the air. */\nexport function closeAnchoredPanel(): void {\n const open = current;\n current = null;\n open?.close();\n}\n\n/** Patterpad's placement, with the side as a parameter. */\nexport function placeAnchored(\n panel: HTMLElement, anchor: HTMLElement, width: number,\n prefer: \"left\" | \"below\", keepClear: string[],\n): void {\n const a = anchor.getBoundingClientRect();\n const GAP = 10;\n const CAP = window.innerHeight * 0.7;\n const MIN_H = 140;\n panel.style.width = `${width}px`;\n\n // Horizontally: left of the anchor when asked and there is room, otherwise\n // aligned to it and nudged back inside the window.\n const leftOf = a.left - width - GAP;\n const alignedRight = Math.min(a.right - width, window.innerWidth - width - 8);\n panel.style.left = `${Math.round(prefer === \"left\" && leftOf >= 8\n ? leftOf\n : Math.max(8, prefer === \"left\" ? a.left : alignedRight))}px`;\n\n // Vertically: whichever side has more room, staying off the app's bottom bars.\n let bottomLimit = window.innerHeight - 8;\n for (const selector of keepClear) {\n const bar = document.querySelector(selector);\n if (!bar) continue;\n const r = bar.getBoundingClientRect();\n if (r.height > 0) bottomLimit = Math.min(bottomLimit, r.top - 6);\n }\n const roomBelow = bottomLimit - (a.bottom + GAP);\n const roomAbove = (a.top - GAP) - 8;\n if (roomAbove > roomBelow) {\n panel.style.top = \"auto\";\n panel.style.bottom = `${Math.round(window.innerHeight - (a.top - GAP))}px`;\n panel.style.maxHeight = `${Math.round(Math.max(MIN_H, Math.min(CAP, roomAbove)))}px`;\n } else {\n panel.style.bottom = \"auto\";\n panel.style.top = `${Math.round(Math.max(8, a.bottom + GAP))}px`;\n panel.style.maxHeight = `${Math.round(Math.max(MIN_H, Math.min(CAP, roomBelow)))}px`;\n }\n}\n","// ---------------------------------------------------------------------------\n// The version-control surface's pure DOM half: folding a snapshot over the\n// shard key(s) an item stands for, the one badge that item flies, painting\n// those badges onto rows that already exist, and turning a document's editing\n// controls off when somebody else holds its shard.\n//\n// The family's, for the same reason the main half is: it follows from the app\n// SHAPE. Patterpad's #145 grammar, by way of Storyletter's port.\n//\n// Render-FREE by design, and that is load-bearing: a poll repaints badges\n// without rebuilding anything, so a mid-edit document is never disturbed.\n//\n// The one thing an app must supply is WHAT STAYS LIVE in a locked document\n// (`staysLive`): reading a shard somebody else holds must remain fully\n// possible, so navigation, disclosure and view switches keep working while the\n// writing stops - and only the app knows what those look like in its own\n// markup.\n// ---------------------------------------------------------------------------\n\nimport { icon } from \"./icons.js\";\nimport { el } from \"./dom.js\";\n\n/** One shard's state as the renderer sees it. The app's own DTO will be this\n * shape (it crosses its own bridge); this is what the view needs. */\nexport interface ShardVc {\n key: string;\n writable: boolean;\n lockedBy?: string[];\n outOfDate?: boolean;\n}\n\n/** A snapshot, keyed by shard key. Absent = clean, writable, up to date. */\nexport type VcMap = ReadonlyMap<string, ShardVc>;\n\n/** Fold the state of the shard key(s) an item stands for (a box row spans its\n * box + tags + hands shards), most actionable winning: any holder locks it,\n * any stale shard makes it stale, any read-only shard makes it read-only. */\nexport function foldVc(shards: VcMap, keys: string | undefined): ShardVc | undefined {\n if (!keys) return undefined;\n let out: ShardVc | undefined;\n for (const key of keys.split(\" \")) {\n const s = shards.get(key);\n if (!s) continue;\n out ??= { key, writable: true };\n if (!s.writable) out.writable = false;\n if (s.lockedBy?.length) out.lockedBy = [...new Set([...(out.lockedBy ?? []), ...s.lockedBy])];\n if (s.outOfDate) out.outOfDate = true;\n }\n return out;\n}\n\n/** The one badge an item flies, by priority, or null when there is nothing to\n * say. Monochrome typographic glyphs (never colour emoji), so each inherits\n * its themed colour and reads the same in Linen and Baize. */\nexport function vcBadgeFor(s: ShardVc | undefined): { glyph: string; cls: string; title: string } | null {\n if (s?.lockedBy?.length) return { glyph: icon.locked, cls: \"vc-locked\", title: `Locked by ${s.lockedBy.join(\", \")}` };\n if (s?.outOfDate) return { glyph: icon.down, cls: \"vc-stale\", title: \"Out of date - a newer version is on the server\" };\n // Read-only on disk with NO other holder is still editable: the save checks\n // it out. Muted, and last, because under a lock-based VCS this is the\n // resting state of everything the author has not touched yet.\n if (s && !s.writable) return { glyph: icon.readOnly, cls: \"vc-frozen\", title: \"Read-only on disk - saving checks it out\" };\n return null;\n}\n\n/** Repaint the badge on every item under `root` that names a shard (its\n * `data-vc`). In place: no re-render, so nothing loses focus or scroll. */\nexport function paintVcBadges(root: ParentNode, shards: VcMap): void {\n root.querySelectorAll<HTMLElement>(\"[data-vc]\").forEach((host) => {\n host.querySelector(\":scope > .vc-badge\")?.remove();\n const badge = vcBadgeFor(foldVc(shards, host.dataset[\"vc\"]));\n if (!badge) return;\n // `tip` carries the accessible name too, so no separate aria-label.\n const span = el(\"span\", { className: `vc-badge ${badge.cls}`, text: badge.glyph, tip: badge.title });\n host.append(span);\n });\n}\n\ntype Disableable = HTMLButtonElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\n\n/** Turn a region's editing controls off - or back on, but only the ones WE\n * turned off, so a control disabled for its own reasons (the first card's\n * \"previous\", a redraw-turns box on \"always\") stays disabled. */\nexport function lockControls(host: HTMLElement, off: boolean, staysLive: string): void {\n host.classList.toggle(\"vc-readonly\", off);\n if (off) {\n host.querySelectorAll<Disableable>(\"input, textarea, select, button\").forEach((c) => {\n if (c.disabled || (staysLive !== \"\" && c.matches(staysLive))) return;\n c.disabled = true;\n c.classList.add(\"vc-off\");\n });\n } else {\n host.querySelectorAll<Disableable>(\".vc-off\").forEach((c) => { c.disabled = false; c.classList.remove(\"vc-off\"); });\n }\n}\n\n/** The notice a locked document opens with: who holds it, and why nothing types. */\nexport function lockNotice(holders: string[]): HTMLElement {\n return el(\"div\", { className: \"vc-lock\" },\n el(\"span\", { className: \"vc-lock-glyph\", text: icon.locked }),\n el(\"span\", { text: `Locked by ${holders.join(\", \")} - read-only until they release it.` }));\n}\n","// ---------------------------------------------------------------------------\n// \"Who is working here?\" - Patterpad's first-run identity, as the family's.\n//\n// `{ name, email? }`, asked ONCE at first startup and skippable, editable later\n// from User Information… in the menu. It belongs to the person at the keyboard,\n// never to the project, which is why the app stores it in ITS OWN state and not\n// in a shard: a project carrying a name would hand it to whoever opened the file\n// next.\n//\n// The shell asks and returns; PERSISTING is the app's, because each app has its\n// own state file. `suggested` is there for the day simple-vc-lib can say who the\n// version control thinks you are (see patterkit from-storylets/vc-current-user),\n// so the box opens with a sensible answer rather than empty.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\n\nexport interface Identity { name: string; email?: string }\n\nexport interface IdentityOptions {\n /** What it already says, if anything. */\n current?: Identity;\n /** A name to offer when there is none: the VCS's, one day. */\n suggested?: Identity;\n}\n\n\n/** Ask, and record the answer. Resolves when the dialog closes, either way. */\nexport function askIdentity(opts: IdentityOptions = {}): Promise<Identity | undefined> {\n return new Promise<Identity | undefined>((resolve) => {\n const seed = opts.current ?? opts.suggested;\n const name = el(\"input\", \"shell-ident-input\") as HTMLInputElement;\n name.placeholder = \"Your name\";\n name.value = seed?.name ?? \"\";\n const email = el(\"input\", \"shell-ident-input\") as HTMLInputElement;\n email.placeholder = \"Email (optional)\";\n email.type = \"email\";\n email.value = seed?.email ?? \"\";\n\n const skip = el(\"button\", \"shell-ident-btn\", \"Skip\");\n const save = el(\"button\", \"shell-ident-btn primary\", \"Save\");\n skip.type = \"button\"; save.type = \"button\";\n\n const dialog = el(\"dialog\", \"shell-ident\",\n el(\"div\", \"shell-ident-title\", \"Who is working here?\"),\n el(\"p\", \"shell-ident-hint\",\n \"Stamped on the comments you write. Kept in this app, never in the project, and changeable later.\"),\n el(\"div\", \"shell-ident-fields\", name, email),\n el(\"div\", \"shell-ident-actions\", skip, save),\n );\n document.body.append(dialog);\n\n let done = false;\n const finish = (keep: boolean): void => {\n if (done) return;\n done = true;\n const typed = name.value.trim();\n const value = email.value.trim();\n const answer: Identity | undefined = keep && typed !== \"\"\n ? (value === \"\" ? { name: typed } : { name: typed, email: value })\n : undefined;\n dialog.close();\n dialog.remove();\n resolve(answer);\n };\n skip.addEventListener(\"click\", () => finish(false));\n save.addEventListener(\"click\", () => finish(true));\n // Enter saves, Escape skips: the two answers the dialog has.\n name.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\") finish(true); });\n email.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\") finish(true); });\n dialog.addEventListener(\"close\", () => finish(false));\n\n dialog.showModal();\n name.focus();\n });\n}\n","// ---------------------------------------------------------------------------\n// The documentation-notes editor: Patterpad's, as the family's (Patter spec 18).\n//\n// Documentation states the reason; comments are the conversation. A note says\n// why a card is here, who it is for, what a translator needs to know. It has no\n// author, no timestamp, and nobody replies to it.\n//\n// A MODAL, and that is a decision somebody already paid for: Patterpad tried an\n// anchored popover first and removed it as too cramped. Notes are prose, and\n// prose needs room.\n//\n// One text area PER CLASS, each non-blank line becoming a note of that class.\n// The alternative - a list of lines each with its own class dropdown - is more\n// granular and, as their proposal puts it, fiddlier: you would fill in a form to\n// write a sentence.\n//\n// THE SEAM: this knows nothing about what a note is attached to. The app hands\n// it the notes that apply, the ancestors they came from and the classes on\n// offer, and takes back a list. Whether the levels are box -> deck -> card or\n// scene -> block -> line never reaches here.\n//\n// Showing INHERITED notes read-only is Storyletter's addition, and it belongs in\n// the family: Patterpad deferred it because its stacked inspector already shows\n// each ancestor's Notes row, but an app whose editor has no such stack must show\n// them somewhere or an author will write the box's note again on the card.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\n\n/** One line of documentation. `type` names its class; absent = editor-only. */\nexport interface DocLine { type?: string; text: string }\n\n/** What applies to one thing: its own notes, and the ancestors' that reach it.\n * Inherited notes are READ-ONLY - they are edited where they live, which is\n * what stops a note being copied down and then disagreeing with itself. */\nexport interface Notes {\n own: DocLine[];\n inherited: { id: string; label: string; lines: DocLine[] }[];\n /** The classes this app offers, in the order they should appear. */\n classes: string[];\n}\n\nconst EDITOR_ONLY = \"\";\nconst UNTYPED_KEY = \"__untyped\";\n\n/** The classes the family already has words for; anything else is Title Cased,\n * so a studio can add one without touching this. Patterpad's labels. */\nconst CLASS_LABEL: Record<string, string> = {\n everyone: \"Everyone\", writing: \"Writing\", vo: \"Voice (VO)\", loc: \"Localisers\",\n};\nconst label = (cls: string): string =>\n cls === EDITOR_ONLY ? \"Note (editor-only)\"\n : CLASS_LABEL[cls] ?? (cls[0]!.toUpperCase() + cls.slice(1));\n\nconst PLACEHOLDER: Record<string, string> = {\n vo: \"direction for the voice actor / director\",\n loc: \"context for translators\",\n};\nconst placeholder = (cls: string): string =>\n cls === EDITOR_ONLY ? \"an internal note - never exported\"\n : PLACEHOLDER[cls] ?? \"intent / rationale - why this is here\";\n\nexport interface NotesEditorOptions {\n /** What this is about, for the dialog's title (\"Notes: Arrive at the gate\"). */\n subject: string;\n notes: Notes;\n /** Commit. Called once, on close, and only when something changed. */\n save: (lines: DocLine[]) => void;\n}\n\n/** Open the notes modal. Resolves when it closes. */\nexport function openNotesEditor(opts: NotesEditorOptions): void {\n const { notes } = opts;\n\n // Seed one text area per class that already has notes; a fresh thing gets the\n // first class in the vocabulary, so the commonest act (write a note) is typing\n // rather than choosing.\n const seed = new Map<string, string>();\n for (const line of notes.own) {\n const key = line.type ?? EDITOR_ONLY;\n seed.set(key, seed.has(key) ? `${seed.get(key)!}\\n${line.text}` : line.text);\n }\n const shown: string[] = [...seed.keys()];\n if (shown.length === 0) shown.push(notes.classes[0] ?? EDITOR_ONLY);\n const areas = new Map<string, HTMLTextAreaElement>();\n\n const value = (): DocLine[] => {\n const out: DocLine[] = [];\n for (const cls of shown) {\n const area = areas.get(cls);\n if (!area) continue;\n for (const raw of area.value.split(\"\\n\")) {\n const text = raw.trim();\n if (text !== \"\") out.push(cls === EDITOR_ONLY ? { text } : { type: cls, text });\n }\n }\n return out;\n };\n\n const body = el(\"div\", \"shell-notes-body\");\n\n const render = (): void => {\n body.replaceChildren();\n\n // The inherited context first, because it is what an author needs BEFORE\n // deciding whether to write anything: outermost first, muted, and labelled\n // with where it lives so the answer to \"why can I not edit this\" is on\n // screen.\n if (notes.inherited.length > 0) {\n const from = el(\"div\", { className: \"shell-notes-inherited\" },\n el(\"div\", \"shell-notes-caption\", \"Inherited\"));\n for (const level of notes.inherited) {\n from.append(el(\"div\", { className: \"shell-notes-level\" },\n el(\"span\", \"shell-notes-level-label\", level.label),\n ...level.lines.map((line) => el(\"div\", { className: \"shell-notes-line\" },\n ...(line.type !== undefined && line.type !== \"everyone\"\n ? [el(\"span\", \"shell-notes-cls\", label(line.type))] : []),\n el(\"span\", \"\", line.text))),\n ));\n }\n body.append(from);\n }\n\n for (const cls of shown) {\n const block = el(\"div\", { className: \"shell-notes-class\" },\n el(\"label\", \"shell-notes-caption\", label(cls)));\n const area = el(\"textarea\", \"shell-notes-text\");\n area.value = areas.get(cls)?.value ?? seed.get(cls) ?? \"\";\n area.rows = 4;\n area.placeholder = placeholder(cls);\n area.spellcheck = true;\n areas.set(cls, area);\n block.append(area);\n body.append(block);\n }\n\n // One line per note, said once where somebody is about to type.\n body.append(el(\"p\", \"shell-notes-hint\", \"One note per line.\"));\n\n const remaining = [...notes.classes, EDITOR_ONLY].filter((c) => !shown.includes(c));\n if (remaining.length > 0) {\n const add = el(\"select\", \"shell-notes-add\");\n add.append(new Option(\"Add a note for…\", \"\"));\n for (const cls of remaining) add.append(new Option(label(cls), cls === EDITOR_ONLY ? UNTYPED_KEY : cls));\n add.addEventListener(\"change\", () => {\n if (add.value === \"\") return;\n const cls = add.value === UNTYPED_KEY ? EDITOR_ONLY : add.value;\n shown.push(cls);\n render();\n areas.get(cls)?.focus();\n });\n body.append(add);\n }\n };\n render();\n\n const done = el(\"button\", \"shell-notes-btn primary\", \"Done\");\n const dialog = el(\"dialog\", { className: \"shell-notes\" },\n el(\"div\", { className: \"shell-notes-title\", text: `Notes: ${opts.subject}` }),\n body,\n el(\"div\", { className: \"shell-notes-actions\" }, done),\n );\n dialog.setAttribute(\"aria-label\", `Notes: ${opts.subject}`);\n document.body.append(dialog);\n\n const before = JSON.stringify(notes.own);\n // Done and Escape both end up here (dialog.close() fires the close event), so\n // the guard is what stops one gesture saving twice.\n let closed = false;\n const close = (): void => {\n if (closed) return;\n closed = true;\n const next = value();\n // Only when something changed: opening a note to read it must not cost an\n // undo step, and must not touch the file (or the version control status).\n if (JSON.stringify(next) !== before) opts.save(next);\n dialog.close();\n dialog.remove();\n };\n done.addEventListener(\"click\", close);\n // Escape closes a <dialog> by itself, and commits: this editor has no Cancel,\n // because a note is prose and losing a paragraph to a stray key is worse than\n // an unwanted note somebody can delete.\n dialog.addEventListener(\"close\", close);\n\n dialog.showModal();\n areas.get(shown[0] ?? EDITOR_ONLY)?.focus();\n}\n","// ---------------------------------------------------------------------------\n// The comment thread popover: Patterpad's (#148), as the family's.\n//\n// Comments are the conversation; documentation states the reason. Anchored\n// rather than modal, which is the deliberate contrast with the notes editor: a\n// comment is had BESIDE the thing, and a modal covering the card would hide\n// what is being discussed.\n//\n// Everything here that looks like a small decision is one somebody already\n// made: author and timestamp on every message (a comment without a \"who\" is an\n// anonymous complaint); Enter posts and closes while Shift+Enter is a newline;\n// the button reads Comment on a new thread and Reply on an old one; focus goes\n// to the composer ONLY for a new thread, because opening an existing one to\n// read must not yank the cursor out of the work; and a thread does not exist\n// until its first message is posted, so a composer somebody thought better of\n// leaves nothing behind.\n//\n// THE SEAM: an anchor is an opaque id. What it points at - a card, a beat, a\n// span of characters - is the app's, and so is where the threads are stored.\n// This takes a list and gives back \"post this\" and \"resolve that\".\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\nimport { openAnchoredPanel } from \"./anchored.js\";\n\n/** One message. The author is a NAME stamped at posting time, not a reference:\n * people leave projects and their words stay. */\nexport interface CommentMessage { author: string; ts: string; body: string }\n\n/** A thread, anchored to the id of whatever it is about. */\nexport interface Comment {\n id: string;\n anchor: string;\n resolved?: boolean;\n messages: CommentMessage[];\n}\n\nconst stamp = (ts: string): string => {\n const when = new Date(ts);\n if (Number.isNaN(when.getTime())) return ts;\n return when.toLocaleString(undefined, { dateStyle: \"medium\", timeStyle: \"short\" });\n};\n\n/** A preview of a thread, for the list: its first message, shortened. */\nconst gist = (thread: Comment): string => {\n const first = thread.messages[0]?.body ?? \"\";\n return first.length > 60 ? `${first.slice(0, 57)}…` : first;\n};\n\nexport interface CommentsOptions {\n anchor: HTMLElement;\n /** What is being discussed, for the panel's title. */\n subject: string;\n /** Every thread on this thing, resolved ones included. */\n threads: Comment[];\n /** Review ▸ Show Resolved Comments. */\n showResolved: boolean;\n /** A fresh thread id, minted by the caller so main never has to. */\n newThreadId: () => string;\n post: (threadId: string, body: string) => void;\n setResolved: (threadId: string, resolved: boolean) => void;\n}\n\nexport function openComments(opts: CommentsOptions): void {\n const visible = opts.threads.filter((t) => opts.showResolved || t.resolved !== true);\n const panel = openAnchoredPanel({\n anchor: opts.anchor, className: \"comments\", title: \"Comments\", width: 320,\n // Below its button (the topline), unlike Patterpad's inspector rows which\n // want it to the left; and never over the problems bar, which an author may\n // be stepping through while reading the thread.\n prefer: \"below\",\n keepClear: [\".problembar\"],\n });\n if (!panel) return; // the same anchor again: that click closed it\n const { body, close } = panel;\n\n // Which thread is on screen. A brand-new one until it is posted: it exists\n // only here, which is why nothing is written when the panel closes unposted.\n let open: Comment | undefined = visible.length === 1 ? visible[0] : undefined;\n let composing = visible.length === 0;\n\n const render = (): void => {\n body.replaceChildren();\n\n if (!composing && open === undefined) {\n // More than one: pick.\n for (const thread of visible) {\n const row = el(\"button\", { className: `shell-cmt-row${thread.resolved === true ? \" resolved\" : \"\"}` },\n el(\"span\", { className: \"shell-cmt-row-author\", text: thread.messages[0]?.author ?? \"Someone\" }),\n el(\"span\", { className: \"shell-cmt-row-gist\", text: gist(thread) }),\n ...(thread.messages.length > 1\n ? [el(\"span\", { className: \"shell-cmt-row-count\", text: `${thread.messages.length}` })] : []),\n );\n row.addEventListener(\"click\", () => { open = thread; render(); });\n body.append(row);\n }\n const fresh = el(\"button\", { className: \"shell-cmt-new\", text: \"New comment\" });\n fresh.addEventListener(\"click\", () => { open = undefined; composing = true; render(); });\n body.append(fresh);\n return;\n }\n\n const thread = open;\n if (thread) {\n const list = el(\"div\", { className: `shell-cmt-list${thread.resolved === true ? \" resolved\" : \"\"}` });\n for (const message of thread.messages) {\n list.append(el(\"div\", { className: \"shell-cmt-msg\" },\n el(\"div\", { className: \"shell-cmt-msg-head\" },\n el(\"span\", { className: \"shell-cmt-author\", text: message.author === \"\" ? \"Someone\" : message.author }),\n el(\"span\", { className: \"shell-cmt-ts\", text: stamp(message.ts) })),\n el(\"div\", { className: \"shell-cmt-body\", text: message.body })));\n }\n body.append(list);\n }\n\n const input = el(\"textarea\", { className: \"shell-cmt-input\" });\n input.rows = 2;\n input.placeholder = thread ? \"Reply…\" : \"Comment…\";\n input.addEventListener(\"keydown\", (e) => {\n e.stopPropagation();\n if (e.key !== \"Enter\" || e.shiftKey) return;\n e.preventDefault();\n submit(true);\n });\n body.append(input);\n\n const submit = (andClose: boolean): void => {\n const text = input.value.trim();\n if (text === \"\") return;\n const id = thread?.id ?? opts.newThreadId();\n opts.post(id, text);\n if (andClose) close(); else { input.value = \"\"; }\n };\n\n const actions = el(\"div\", { className: \"shell-cmt-actions\" });\n const post = el(\"button\", { className: \"shell-cmt-btn primary\", text: thread ? \"Reply\" : \"Comment\" });\n post.addEventListener(\"click\", () => submit(false));\n actions.append(post);\n\n if (thread) {\n const resolved = thread.resolved === true;\n const toggle = el(\"button\", { className: \"shell-cmt-btn\", text: resolved ? \"Reopen\" : \"Mark complete\" });\n toggle.addEventListener(\"click\", () => {\n opts.setResolved(thread.id, !resolved);\n // A resolved thread hides, so there is nothing left to look at.\n if (!resolved) close(); else render();\n });\n actions.append(toggle);\n }\n body.append(actions);\n\n // Only for a new thread: reading an old one must not steal the cursor.\n if (!thread) input.focus();\n };\n render();\n}\n","// ---------------------------------------------------------------------------\n// The long-job kit (renderer): the progress strip the main-side JobHost feeds.\n// A determinate bar, a count, elapsed and estimated remaining, and Cancel.\n//\n// A STRIP, NOT A MODAL. The window that started the job is the one that shows\n// it, and the rest of the app stays usable: these are tool windows, and taking\n// the whole editor hostage while a sweep runs was the old system's mistake.\n// The strip is hidden until a job starts and hides itself when one ends.\n//\n// The time readout only appears once there is enough evidence for it. An ETA\n// that swings wildly for the first second reads as a broken app, so the\n// estimate waits until a second of work and a tenth of the job are behind it.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\n\nexport interface JobProgressView {\n /** The strip; mount it where the job belongs. */\n readonly element: HTMLElement;\n /** Show the strip, reset to zero, and (optionally) name what is running. */\n begin(label?: string): void;\n /** Feed it a progress message from the main-side host. */\n update(done: number, total: number, elapsedMs: number): void;\n /** Hide the strip (the job finished, failed, or was cancelled). */\n end(): void;\n readonly visible: boolean;\n}\n\nexport interface JobProgressOptions {\n /** Called when Cancel is clicked. The button disables itself and says so;\n * the job stops at its next yield, and the host then calls end(). */\n onCancel: () => void;\n /** The word for the units, for the count readout (default \"runs\"). */\n units?: string;\n}\n\nconst clock = (ms: number): string => {\n const total = Math.round(ms / 1000);\n const m = Math.floor(total / 60);\n const s = total % 60;\n return m > 0 ? `${m}m ${String(s).padStart(2, \"0\")}s` : `${s}s`;\n};\n\n/** Enough of the job done, and enough time passed, for a remaining-time\n * estimate to be worth showing rather than misleading. */\nconst estimable = (done: number, total: number, elapsedMs: number): boolean =>\n done > 0 && total > 0 && elapsedMs > 1000 && done / total > 0.1;\n\nexport function mountJobProgress(host: HTMLElement, opts: JobProgressOptions): JobProgressView {\n const units = opts.units ?? \"runs\";\n\n const fill = el(\"i\", { className: \"job-fill\" });\n const bar = el(\"div\", { className: \"job-bar\" }, fill);\n const what = el(\"span\", { className: \"job-label\" });\n const count = el(\"span\", { className: \"job-count\" });\n const time = el(\"span\", { className: \"job-time\" });\n const cancel = el(\"button\", { className: \"job-cancel\", text: \"Cancel\" });\n\n const strip = el(\"div\", { className: \"job-strip\" }, what, bar, count, time, cancel);\n strip.hidden = true;\n host.append(strip);\n\n const view: JobProgressView = {\n element: strip,\n get visible() { return !strip.hidden; },\n\n begin(label?: string) {\n what.textContent = label ?? \"Running…\";\n count.textContent = \"\";\n time.textContent = \"\";\n fill.style.width = \"0%\";\n cancel.disabled = false;\n cancel.textContent = \"Cancel\";\n strip.hidden = false;\n },\n\n update(done: number, total: number, elapsedMs: number) {\n if (strip.hidden) strip.hidden = false;\n const pct = total > 0 ? Math.min(100, (done / total) * 100) : 0;\n fill.style.width = `${pct}%`;\n count.textContent = total > 0 ? `${done} / ${total} ${units}` : `${done} ${units}`;\n if (estimable(done, total, elapsedMs)) {\n const remaining = (elapsedMs / done) * (total - done);\n time.textContent = `${clock(elapsedMs)} elapsed · about ${clock(remaining)} left`;\n } else {\n time.textContent = clock(elapsedMs);\n }\n },\n\n end() {\n strip.hidden = true;\n },\n };\n\n cancel.addEventListener(\"click\", () => {\n // Cancellation lands at the job's next yield, which may be a moment away:\n // say so, rather than leaving a dead-looking button.\n cancel.disabled = true;\n cancel.textContent = \"Stopping…\";\n opts.onCancel();\n });\n\n return view;\n}\n","// ---------------------------------------------------------------------------\n// The long-job kit (main process): run a piece of work that takes long enough\n// to need a progress bar and a Cancel button, without freezing the app.\n//\n// The shape every editor in the family needs: coverage sweeps, big exports,\n// corpus runs. One kind of job at a time, progress reported to the windows\n// that asked for it, cancellation honoured at the next yield point, and a\n// partial result when the work can offer one.\n//\n// COOPERATIVE, NOT PARALLEL. The work still runs in the main process; it just\n// stops hogging it. `ctx.step()` is both the progress report and the yield:\n// the work hands the event loop back every few milliseconds, so IPC keeps\n// flowing, the renderer keeps painting, and Cancel is heard. The alternative\n// (a utilityProcess or a worker) buys true parallelism at the cost of\n// serialising the job's inputs and outputs across a process boundary, and a\n// second entry point per app. Reach for that only when the work is heavy\n// enough that a responsive main process is not enough.\n//\n// Electron is an OPTIONAL peer here: nothing in this file imports it. The host\n// supplies `send`, so the kit is testable headlessly and usable from any\n// message transport.\n// ---------------------------------------------------------------------------\n\n/** The channel a host should send progress on, and the shape it carries. */\nexport const JOB_PROGRESS = \"job:progress\";\n\nexport interface JobProgress {\n /** Which job this is about (\"coverage\"), so one listener can serve many. */\n kind: string;\n /** Units finished and expected. `total` 0 means \"no idea yet\". */\n done: number;\n total: number;\n /** Milliseconds since the job started, for elapsed + estimated remaining. */\n elapsedMs: number;\n}\n\n/** What the work is given: a cancellation flag and the one call that both\n * reports progress and yields. */\nexport interface JobContext {\n /** True once someone has cancelled. Check it to bail out early, or to\n * return a partial result rather than nothing. */\n readonly cancelled: boolean;\n /** Report progress and, when enough time has passed, hand the event loop\n * back. Await it: that await IS the yield. */\n step(done: number, total: number): Promise<void>;\n}\n\nexport type JobResult<T> =\n | { ok: true; value: T }\n | { cancelled: true; value?: T }\n | { error: string };\n\nexport interface JobHost {\n /** Run `work` as the job of this kind. Rejects a second start of the same\n * kind while one is in flight (the caller should have disabled the button;\n * this is the backstop). */\n start<T>(kind: string, work: (ctx: JobContext) => Promise<T>): Promise<JobResult<T>>;\n /** Ask the running job of this kind to stop. It stops at its next step(). */\n cancel(kind: string): void;\n running(kind: string): boolean;\n}\n\nexport interface JobHostOptions {\n /** Deliver a progress message to whoever is watching (typically\n * `window.webContents.send(JOB_PROGRESS, payload)` for each live window). */\n send: (channel: string, payload: JobProgress) => void;\n /** How long the work may hold the event loop between yields (default 16ms,\n * about one frame). Smaller is smoother and slower. */\n sliceMs?: number;\n /** How often a progress message may be sent (default 100ms). Progress is\n * for a human to read; flooding the channel just costs IPC. */\n reportMs?: number;\n /** Injectable clock + yield, for tests. */\n now?: () => number;\n yieldToLoop?: () => Promise<void>;\n}\n\nconst defaultYield = (): Promise<void> =>\n new Promise((resolve) => { setTimeout(resolve, 0); });\n\nexport function createJobHost(opts: JobHostOptions): JobHost {\n const now = opts.now ?? (() => Date.now());\n const handOver = opts.yieldToLoop ?? defaultYield;\n const sliceMs = opts.sliceMs ?? 16;\n const reportMs = opts.reportMs ?? 100;\n const live = new Map<string, { cancelled: boolean }>();\n\n return {\n running: (kind) => live.has(kind),\n\n cancel(kind) {\n const job = live.get(kind);\n if (job) job.cancelled = true;\n },\n\n async start<T>(kind: string, work: (ctx: JobContext) => Promise<T>): Promise<JobResult<T>> {\n if (live.has(kind)) return { error: `a ${kind} job is already running` };\n const job = { cancelled: false };\n live.set(kind, job);\n\n const started = now();\n let lastYield = started;\n // A flag, not a timestamp sentinel: on a clock that can legitimately\n // read 0, \"never reported yet\" and \"reported at 0\" must not be the\n // same state. The first step always reports, so the bar moves at once.\n let reported = false;\n let lastReport = 0;\n\n const ctx: JobContext = {\n get cancelled() { return job.cancelled; },\n async step(done: number, total: number): Promise<void> {\n const at = now();\n if (!reported || at - lastReport >= reportMs || done >= total) {\n reported = true;\n lastReport = at;\n opts.send(JOB_PROGRESS, { kind, done, total, elapsedMs: at - started });\n }\n if (at - lastYield >= sliceMs) {\n lastYield = at;\n await handOver();\n }\n },\n };\n\n try {\n const value = await work(ctx);\n return job.cancelled ? { cancelled: true, value } : { ok: true, value };\n } catch (e) {\n // A cancelled job that threw on the way out is still a cancellation:\n // the author asked it to stop, and an error report would be noise.\n if (job.cancelled) return { cancelled: true };\n return { error: e instanceof Error ? e.message : String(e) };\n } finally {\n live.delete(kind);\n }\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// A single themed tooltip the family controls, replacing native `title`\n// rollovers: those are unstyled OS chrome and appear on the platform's slow\n// (~1s) delay, which is exactly the seam design-language section 4 (\"coherent\n// to the edges\") says not to leave showing.\n//\n// ONE floating bubble, document-DELEGATED: any element carrying a `data-tip`\n// attribute gets it, so a caller just sets `data-tip=\"...\"` anywhere and wires\n// nothing. The bubble anchors above the element (flipping below near the top\n// edge), uses our own snappy delay and the shared panel-enter motion.\n// `initTooltips()` is idempotent, so it is safe to call from more than one\n// place in an app that has several entry points.\n//\n// Lifted from Patterpad's patterpad-surface/web/tooltip.ts, which proved it,\n// with the one app-specific rule (suppressed in Writing View) generalised into\n// the `suppressed` option so the shell knows nothing about either app's modes.\n// ---------------------------------------------------------------------------\n\n// Bold markers: a producer wraps a span in these PRIVATE control chars (U+0002 STX / U+0003 ETX,\n// never present in user text) and the renderer turns it into a <strong>. Lets a data-tip bold part\n// of itself (a name, a \"VO:\" prefix) without any HTML, and each segment is still set via\n// textContent, so there is no injection risk from user content.\nconst B_OPEN = \"\\u0002\";\nconst B_CLOSE = \"\\u0003\";\n\n/** Wrap `s` so it renders bold inside a tooltip (see the marker note above). */\nexport function tipBold(s: string): string { return `${B_OPEN}${s}${B_CLOSE}`; }\n\n/** Render `text` into `host`, turning `\\u0002…\\u0003`-marked spans into <strong>; all set as text (safe). */\nfunction renderTip(host: HTMLElement, text: string): void {\n host.textContent = \"\";\n const re = /\\u0002([^\\u0003]*)\\u0003/g;\n let last = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n if (m.index > last) host.append(text.slice(last, m.index));\n const strong = document.createElement(\"strong\");\n strong.textContent = m[1] ?? \"\";\n host.append(strong);\n last = m.index + m[0].length;\n }\n if (last < text.length) host.append(text.slice(last));\n}\n\nexport interface TooltipOptions {\n /** Called before every show: return true to keep the bubble down. For a mode\n * that wants no chrome at all (Patterpad's Writing View). */\n suppressed?: () => boolean;\n}\n\nlet inited = false;\nlet suppressed: (() => boolean) | undefined;\nconst SHOW_DELAY = 350; // ms - snappier than the OS title delay, slow enough not to flicker on pass-through\nconst EDGE = 6; // viewport inset so the bubble never touches the window edge\nconst GAP = 7; // px between the anchor and the bubble\n\nlet tip: HTMLDivElement | null = null;\nlet timer = 0;\nlet active: HTMLElement | null = null;\n/** The subject of a rect-anchored tip (see `tipAt`), when there is one. */\nlet activeKey: string | null = null;\n\nfunction ensureEl(): HTMLDivElement {\n if (tip) return tip;\n const el = document.createElement(\"div\");\n el.className = \"tooltip\";\n el.setAttribute(\"role\", \"tooltip\");\n el.hidden = true;\n document.body.appendChild(el);\n tip = el;\n return el;\n}\n\n/** A rectangle in VIEWPORT coordinates, for anchoring a tip to something that is\n * not a DOM element: a shape on a canvas, a run of text inside one. */\nexport interface TipRect { left: number; top: number; width: number; height: number }\n\nconst rectOf = (anchor: HTMLElement | TipRect): TipRect =>\n (anchor instanceof HTMLElement ? anchor.getBoundingClientRect() : anchor);\n\n/** Anchor the (already-visible, so measurable) bubble above the anchor, flipping below near the top. */\nfunction place(anchor: HTMLElement | TipRect): void {\n const t = ensureEl();\n const r = rectOf(anchor);\n const tr = t.getBoundingClientRect();\n let top = r.top - tr.height - GAP;\n if (top < EDGE) top = r.top + r.height + GAP; // not enough room above -> below\n let left = r.left + r.width / 2 - tr.width / 2;\n left = Math.max(EDGE, Math.min(left, window.innerWidth - tr.width - EDGE));\n t.style.left = `${Math.round(left)}px`;\n t.style.top = `${Math.round(top)}px`;\n}\n\nfunction show(anchor: HTMLElement): void {\n const text = anchor.dataset[\"tip\"];\n if (!text) return;\n paint(anchor, text);\n}\n\n/** Fill, place and reveal the bubble. The one path: both the delegated\n * `data-tip` route and `tipAt` end up here, so a canvas tip and a button tip\n * are the same bubble with the same look and the same motion. */\nfunction paint(anchor: HTMLElement | TipRect, text: string): void {\n if (suppressed?.()) return;\n const t = ensureEl();\n // A modal <dialog> (settings, a confirm) renders in the browser TOP LAYER, above every normal-flow\n // element whatever its z-index. If the anchor is inside one, move the bubble into that dialog so it\n // shares the top layer; otherwise keep it on <body>. Re-parenting per show also moves it back out\n // once the modal closes.\n const host: HTMLElement = (anchor instanceof HTMLElement ? anchor.closest<HTMLElement>(\"dialog[open]\") : null)\n ?? document.body;\n if (t.parentElement !== host) host.appendChild(t);\n active = anchor instanceof HTMLElement ? anchor : null;\n renderTip(t, text);\n t.hidden = false;\n t.classList.remove(\"show\"); void t.offsetWidth; // restart the enter animation\n place(anchor); // measure + position now it has content + is visible\n t.classList.add(\"show\");\n}\n\nfunction hide(): void {\n if (timer) { window.clearTimeout(timer); timer = 0; }\n active = null;\n activeKey = null;\n if (tip) { tip.hidden = true; tip.classList.remove(\"show\"); }\n}\n\nfunction schedule(anchor: HTMLElement): void {\n if (anchor === active) return;\n if (timer) window.clearTimeout(timer);\n timer = window.setTimeout(() => show(anchor), SHOW_DELAY);\n}\n\n/**\n * Show a tip for something that has no DOM node of its own: a card on a canvas,\n * a zone, a pin. The caller says WHERE (a viewport rectangle) and WHAT, and\n * identifies the subject with `key`.\n *\n * The key is what makes this usable from a canvas. Re-calling with the same key\n * re-places the bubble WITHOUT restarting the delay, so a tip can follow its\n * shape while the camera moves; calling with a different key starts the wait\n * again, so sweeping the pointer across a dense board does not strobe.\n *\n * The bubble is ordinary DOM, so it holds its size whatever the canvas is\n * zoomed to. That is the point of it: the label it stands in for is the one\n * thing on a canvas that must not shrink with everything else.\n */\nexport function tipAt(key: string, rect: TipRect, text: string): void {\n if (key === activeKey) {\n // Already up for this subject: move it, do not re-animate it.\n if (tip && !tip.hidden) { renderTip(tip, text); place(rect); }\n return;\n }\n // A different subject: down first, then wait again. Leaving the old bubble up\n // during the wait would put one card's name over another card, which is worse\n // than no name at all - and it matches the delegated route, where leaving an\n // element hides the tip before the next one is scheduled.\n hide();\n activeKey = key;\n timer = window.setTimeout(() => { timer = 0; paint(rect, text); }, SHOW_DELAY);\n}\n\n/** Take any tip down: the pointer has left, or a gesture has started. */\nexport function hideTip(): void { hide(); }\n\nconst tipAncestor = (n: EventTarget | null): HTMLElement | null =>\n (n instanceof Element ? n.closest<HTMLElement>(\"[data-tip]\") : null);\n\n/** Wire the one delegated controller. Idempotent; later options are ignored. */\nexport function initTooltips(opts: TooltipOptions = {}): void {\n if (inited) return;\n inited = true;\n suppressed = opts.suppressed;\n\n document.addEventListener(\"pointerover\", (e) => {\n const el = tipAncestor(e.target);\n if (el) schedule(el);\n });\n document.addEventListener(\"pointerout\", (e) => {\n const el = tipAncestor(e.target);\n if (!el) return;\n const to = (e as PointerEvent).relatedTarget;\n if (to instanceof Node && el.contains(to)) return; // still inside the same anchor\n hide();\n });\n // Keyboard parity: reveal on focus, dismiss on blur.\n document.addEventListener(\"focusin\", (e) => { const el = tipAncestor(e.target); if (el) schedule(el); });\n document.addEventListener(\"focusout\", () => hide());\n // It's ANCHORED (not cursor-following), so anything that moves the layout or context dismisses it.\n document.addEventListener(\"pointerdown\", () => hide(), true);\n document.addEventListener(\"scroll\", () => { if (active) hide(); }, true);\n document.addEventListener(\"keydown\", (e) => { if (e.key === \"Escape\") hide(); });\n window.addEventListener(\"blur\", () => hide());\n}\n","// ---------------------------------------------------------------------------\n// The save/dirty controller: the little machine that turns \"the author typed\n// something\" into \"the bytes are on disk\", and says so on screen.\n//\n// Both apps wrote one. Neither is complete, and the two are incomplete in\n// OPPOSITE directions, which is the clearest argument for a shared one that\n// this review has produced:\n//\n// - Patterpad runs an INTERVAL (one timer for the app's life, saving any\n// dirty scene every N seconds). Continuous typing is saved throughout, but\n// a single edit can sit unwritten for the whole period.\n// - Storyletter DEBOUNCES (write 700ms after edits settle). A single edit is\n// written promptly, but an author typing steadily is never written at all\n// until they pause - which on a long paragraph is a long time.\n//\n// So this does both: debounce for promptness, with a MAXIMUM AGE so a run of\n// edits cannot outrun the writer. Neither app had the pair; each had half.\n//\n// The other thing neither had is re-entrancy. Storyletter's flush cleared its\n// pending queue and then awaited the write, so a flush arriving mid-write saw\n// nothing pending and reported \"saved\" while bytes were still in the air. Here\n// a write in flight is a thing you can await, and the status is only \"saved\"\n// when nothing arrived while it was running.\n//\n// WHAT THIS DOES NOT OWN is what a save IS. The controller never touches the\n// document: it calls the app's `write` and reads its boolean. What is pending,\n// how it coalesces, what to repaint afterwards - all of that stays in the app,\n// because all of it is about the app's model.\n//\n// DOM-free, so a test can drive it with fake timers and no window.\n// ---------------------------------------------------------------------------\n\n/**\n * Three states, not a boolean.\n *\n * \"saving\" earns its place: on a slow disk or a slow VC hook a save takes long\n * enough to see, and a status that flips straight from unsaved to saved leaves\n * the author wondering whether the click did anything.\n */\nexport type SaveStatus = \"saved\" | \"unsaved\" | \"saving\";\n\nexport interface SaveControllerOptions {\n /**\n * Write whatever is pending. Return false if it failed: the controller stays\n * \"unsaved\" and will try again on the next touch or flush, rather than\n * quietly reporting success over a write that did not happen.\n *\n * Called only when something is pending, and never twice at once.\n */\n write: () => Promise<boolean> | boolean;\n /** Told every time the status changes, and only when it changes. */\n onStatus?: (status: SaveStatus) => void;\n /** How long after the last edit to write. Storyletter's 700ms by default. */\n delayMs?: number;\n /**\n * The longest an edit may go unwritten while more keep arriving.\n *\n * This is the interval half of the two apps put back in: without it, steady\n * typing defers the write for as long as the typing lasts.\n */\n maxWaitMs?: number;\n}\n\nexport interface SaveController {\n /** What the indicator should say. */\n readonly status: SaveStatus;\n /** Is anything unwritten? For a close guard, or an updater asking whether it\n * may restart. True while a write is in flight, too: it is not on disk yet. */\n readonly pending: boolean;\n /** Something changed. Starts (or extends) the countdown. */\n touch(): void;\n /**\n * Write now, and wait for it.\n *\n * Call on every transition that must see current bytes: switching what is\n * being edited, opening a tool window that reads the disk, closing the\n * window, Cmd+S. Safe to call when nothing is pending (it does nothing) and\n * safe to call during a write (it waits for it, then writes again if more\n * arrived while it ran).\n */\n flush(): Promise<void>;\n /** Give up on what is pending without writing it. For a project close after\n * the author has said to discard, and for teardown. */\n cancel(): void;\n /**\n * Autosave on or off (Patterpad's per-project setting).\n *\n * Off does not mean \"never write\": touch() still marks the work unsaved, and\n * flush() still writes on transitions and on Cmd+S. It only stops the clock.\n */\n setAuto(on: boolean): void;\n}\n\nexport function createSaveController(opts: SaveControllerOptions): SaveController {\n const delayMs = opts.delayMs ?? 700;\n const maxWaitMs = opts.maxWaitMs ?? 5000;\n\n let status: SaveStatus = \"saved\";\n let auto = true;\n let dirty = false;\n /** When the oldest unwritten edit arrived, for the maximum age. */\n let dirtySince = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n /** The write in flight, so a second flush awaits it instead of racing it. */\n let inFlight: Promise<void> | undefined;\n\n const setStatus = (next: SaveStatus): void => {\n if (next === status) return;\n status = next;\n opts.onStatus?.(status);\n };\n\n const clearTimer = (): void => {\n if (timer === undefined) return;\n clearTimeout(timer);\n timer = undefined;\n };\n\n const arm = (): void => {\n clearTimer();\n if (!auto) return;\n // Never later than maxWaitMs after the first unwritten edit, however much\n // longer the author keeps typing.\n const deadline = dirtySince + maxWaitMs;\n const wait = Math.max(0, Math.min(delayMs, deadline - Date.now()));\n timer = setTimeout(() => { timer = undefined; void run(); }, wait);\n };\n\n const run = async (): Promise<void> => {\n if (inFlight) return inFlight; // one writer at a time\n if (!dirty) { setStatus(\"saved\"); return; }\n clearTimer();\n dirty = false;\n setStatus(\"saving\");\n inFlight = (async () => {\n let ok = false;\n try {\n ok = await opts.write();\n } catch {\n // A thrown write is a failed write. The app has already said whatever\n // it wants to say about it; here it only means \"still unsaved\".\n ok = false;\n }\n if (!ok) {\n // Put the dirt back so the next touch or flush retries, and keep the\n // oldest timestamp: a failing save must not reset the maximum age.\n if (!dirty) { dirty = true; dirtySince = dirtySince || Date.now(); }\n }\n setStatus(dirty ? \"unsaved\" : \"saved\");\n if (dirty) arm(); // more arrived while we wrote, or it failed\n })();\n try {\n await inFlight;\n } finally {\n inFlight = undefined;\n }\n };\n\n return {\n get status() { return status; },\n get pending() { return dirty || inFlight !== undefined; },\n touch() {\n if (!dirty) dirtySince = Date.now();\n dirty = true;\n // An edit arriving mid-write leaves the status alone: a write really is\n // running, and it will settle to \"unsaved\" on its own when it lands.\n if (!inFlight) setStatus(\"unsaved\");\n arm();\n },\n async flush() {\n // Await a write already running, then write again if this call's work (or\n // anything that arrived mid-write) is still outstanding.\n if (inFlight) await inFlight;\n if (dirty) await run();\n else setStatus(\"saved\");\n },\n cancel() {\n clearTimer();\n dirty = false;\n setStatus(inFlight ? \"saving\" : \"saved\");\n },\n setAuto(on) {\n auto = on;\n if (on) { if (dirty) arm(); } else clearTimer();\n },\n };\n}\n"]}
|