@wildwinter/app-shell 0.38.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES.md +62 -0
- package/README.md +8 -0
- package/dist/about.css +13 -34
- package/dist/about.css.map +1 -1
- package/dist/anchored.css +5 -5
- package/dist/anchored.css.map +1 -1
- package/dist/app-store.cjs +15 -0
- package/dist/app-store.cjs.map +1 -1
- package/dist/app-store.d.cts +25 -1
- package/dist/app-store.d.ts +25 -1
- package/dist/app-store.js +14 -1
- package/dist/app-store.js.map +1 -1
- package/dist/chunk-2ECJUTSX.js +97 -0
- package/dist/chunk-2ECJUTSX.js.map +1 -0
- package/dist/chunk-2NMVIM54.js +55 -0
- package/dist/chunk-2NMVIM54.js.map +1 -0
- package/dist/chunk-C6UVQYKI.js +70 -0
- package/dist/chunk-C6UVQYKI.js.map +1 -0
- package/dist/chunk-IU7EY6BI.js +26 -0
- package/dist/chunk-IU7EY6BI.js.map +1 -0
- package/dist/chunk-K2AOLQW4.js +82 -0
- package/dist/chunk-K2AOLQW4.js.map +1 -0
- package/dist/chunk-KGAMP5LW.js +87 -0
- package/dist/chunk-KGAMP5LW.js.map +1 -0
- package/dist/chunk-L5HOBEKL.js +534 -0
- package/dist/chunk-L5HOBEKL.js.map +1 -0
- package/dist/chunk-P4G4ERG6.js +60 -0
- package/dist/chunk-P4G4ERG6.js.map +1 -0
- package/dist/chunk-P6WLAEKZ.js +71 -0
- package/dist/chunk-P6WLAEKZ.js.map +1 -0
- package/dist/chunk-S4KWQLYN.js +66 -0
- package/dist/chunk-S4KWQLYN.js.map +1 -0
- package/dist/chunk-XZYZXY7I.js +554 -0
- package/dist/chunk-XZYZXY7I.js.map +1 -0
- package/dist/comments.css +5 -3
- package/dist/comments.css.map +1 -1
- package/dist/confirm.css +1 -54
- package/dist/confirm.css.map +1 -1
- package/dist/context-menu.cjs +250 -0
- package/dist/context-menu.cjs.map +1 -0
- package/dist/context-menu.css +64 -0
- package/dist/context-menu.css.map +1 -0
- package/dist/context-menu.d.cts +25 -0
- package/dist/context-menu.d.ts +25 -0
- package/dist/context-menu.js +5 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/controls.css +155 -0
- package/dist/controls.css.map +1 -0
- package/dist/dialog.cjs +222 -0
- package/dist/dialog.cjs.map +1 -0
- package/dist/dialog.css +55 -0
- package/dist/dialog.css.map +1 -0
- package/dist/dialog.d.cts +28 -0
- package/dist/dialog.d.ts +28 -0
- package/dist/dialog.js +5 -0
- package/dist/dialog.js.map +1 -0
- package/dist/floating.cjs +83 -0
- package/dist/floating.cjs.map +1 -0
- package/dist/floating.d.cts +25 -0
- package/dist/floating.d.ts +25 -0
- package/dist/floating.js +4 -0
- package/dist/floating.js.map +1 -0
- package/dist/identity.css +15 -50
- package/dist/identity.css.map +1 -1
- package/dist/index.cjs +1307 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +349 -149
- package/dist/index.d.ts +349 -149
- package/dist/index.js +259 -822
- package/dist/index.js.map +1 -1
- package/dist/job.css +23 -0
- package/dist/job.css.map +1 -1
- package/dist/link-status.cjs +430 -0
- package/dist/link-status.cjs.map +1 -0
- package/dist/link-status.css +76 -0
- package/dist/link-status.css.map +1 -0
- package/dist/link-status.d.cts +53 -0
- package/dist/link-status.d.ts +53 -0
- package/dist/link-status.js +5 -0
- package/dist/link-status.js.map +1 -0
- package/dist/menu.cjs +62 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +88 -1
- package/dist/menu.d.ts +88 -1
- package/dist/menu.js +1 -1
- package/dist/notes-editor.css +3 -46
- package/dist/notes-editor.css.map +1 -1
- package/dist/pane-shell.css +16 -3
- package/dist/pane-shell.css.map +1 -1
- package/dist/property-list-D3VZCJzi.d.cts +162 -0
- package/dist/property-list-D3VZCJzi.d.ts +162 -0
- package/dist/property-list.cjs +784 -0
- package/dist/property-list.cjs.map +1 -0
- package/dist/property-list.d.cts +1 -0
- package/dist/property-list.d.ts +1 -0
- package/dist/property-list.js +4 -0
- package/dist/property-list.js.map +1 -0
- package/dist/settings.css +52 -8
- package/dist/settings.css.map +1 -1
- package/dist/stale.css +3 -0
- package/dist/stale.css.map +1 -1
- package/dist/stepper.css +35 -0
- package/dist/stepper.css.map +1 -1
- package/dist/tokens.css +19 -0
- package/dist/tokens.css.map +1 -1
- package/dist/tool-window.cjs +63 -0
- package/dist/tool-window.cjs.map +1 -1
- package/dist/tool-window.css +6 -3
- package/dist/tool-window.css.map +1 -1
- package/dist/tool-window.d.cts +67 -1
- package/dist/tool-window.d.ts +67 -1
- package/dist/tool-window.js +63 -1
- package/dist/tool-window.js.map +1 -1
- package/dist/updater-view.cjs +283 -0
- package/dist/updater-view.cjs.map +1 -0
- package/dist/updater-view.d.cts +23 -0
- package/dist/updater-view.d.ts +23 -0
- package/dist/updater-view.js +6 -0
- package/dist/updater-view.js.map +1 -0
- package/dist/updater.cjs +12 -16
- package/dist/updater.cjs.map +1 -1
- package/dist/updater.css +44 -0
- package/dist/updater.css.map +1 -0
- package/dist/updater.js +12 -16
- package/dist/updater.js.map +1 -1
- package/dist/util.cjs +78 -0
- package/dist/util.cjs.map +1 -0
- package/dist/util.d.cts +37 -0
- package/dist/util.d.ts +37 -0
- package/dist/util.js +3 -0
- package/dist/util.js.map +1 -0
- package/dist/vc.css +3 -2
- package/dist/vc.css.map +1 -1
- package/dist/welcome.cjs +215 -0
- package/dist/welcome.cjs.map +1 -0
- package/dist/welcome.css +112 -0
- package/dist/welcome.css.map +1 -0
- package/dist/welcome.d.cts +55 -0
- package/dist/welcome.d.ts +55 -0
- package/dist/welcome.js +4 -0
- package/dist/welcome.js.map +1 -0
- package/package.json +58 -2
- package/dist/chunk-QDPFU4WX.js +0 -43
- package/dist/chunk-QDPFU4WX.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tooltip.ts","../src/icons.ts","../src/dom.ts","../src/util.ts","../src/link-status.ts"],"names":["el","tip"],"mappings":"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","file":"link-status.cjs","sourcesContent":["// ---------------------------------------------------------------------------\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 checked = false; // the one-shot no-host warning below has been scheduled\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\n/**\n * Mount the renderer because something is about to speak the grammar.\n *\n * The themed tooltip is not a feature a host turns on; it is the renderer for a grammar the shell's own\n * components already speak, and `data-tip` with nothing listening draws NOTHING - not the themed\n * bubble, not the platform one. That silence cost Patterpad two releases of a pin with no tooltip, and\n * cost it again when a `title` was converted to `data-tip` in a window that had never mounted this.\n *\n * So `el` calls this whenever it sets a tip. Mounting a delegated listener is the same class of act as\n * `confirmDialog` appending to `document.body`, which this package has always done.\n */\nexport function ensureTooltipHost(): void { initTooltips(); }\n\n/**\n * One deferred check that an app writing `data-tip` ITSELF has a host, warning if not.\n *\n * `ensureTooltipHost` covers anything built through `el`. It cannot cover a `data-tip` written straight\n * into markup or set by hand, which is the case that leaves a whole window of dead tips with no shell\n * component involved - so that one gets a warning rather than silence. Scheduled once, after the\n * current task, so it sees a rendered document rather than an empty one.\n */\nexport function checkTooltipHost(): void {\n if (checked) return;\n checked = true;\n setTimeout(() => {\n if (inited || typeof document === \"undefined\") return;\n if (document.querySelector(\"[data-tip]\")) {\n console.warn(\"app-shell: something set `data-tip` but no tooltip host is mounted, so those rollovers will not draw. Call initTooltips() once per window.\");\n }\n }, 0);\n}\n\nconst tipAncestor = (n: EventTarget | null): HTMLElement | null =>\n (n instanceof Element ? n.closest<HTMLElement>(\"[data-tip]\") : null);\n\n/**\n * Wire the one delegated controller. Mounting happens once; OPTIONS do not.\n *\n * An explicit call is always authoritative about its options, even after something else has already\n * mounted the listener. That ordering is not hypothetical, and Storyletter caught it before it shipped:\n * once components self-mount (see `ensureTooltipHost`), a host's own\n * `initTooltips({ suppressed })` could land second and be dropped by an early return, silently taking\n * a rule like \"no tooltips in Writing View\" with it. Nothing would fail and nothing would show.\n *\n * Components therefore call this with NO arguments: they mount the renderer without claiming the\n * options, and the host keeps the last word on behaviour.\n */\nexport function initTooltips(opts: TooltipOptions = {}): void {\n if (opts.suppressed !== undefined) suppressed = opts.suppressed; // an explicit call always wins\n if (inited) return;\n inited = true;\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 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// `iconNode(\"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 a cross twelve times, Storyletter used the same\n// cross once and a multiplication sign twice, in one window, and a comment\n// bubble had just been invented from scratch with nothing to check it against.\n//\n// DRAWN, not typed (design-language.md section 4, \"Icons are drawn, and so are\n// separators\", 2026-09-16). The first cut of this table was Unicode glyphs,\n// which render from whatever symbol font the OS has, so weight, baseline and\n// optical size drifted per character and per platform. The vocabulary is now\n// one drawn set: Lucide, vendored below as inline SVG strings on one 24-unit\n// grid at one stroke weight. The words did not change; what they draw did.\n//\n// Lucide 1.46.0 (lucide-static), ISC licence.\n// https://lucide.dev / https://cdn.jsdelivr.net/npm/lucide-static@1.46.0/icons/<name>.svg\n// Fetched 2026-09-16. The chosen decision record is\n// patterkit/design/ui-review-2026-09/07c-track-c-icon-sets-notes.md.\n//\n// STROKE. The family draws icons at 14px inside its small square buttons and\n// wants a 1.5px rendered stroke against Inter at that size (Inter Regular's\n// stems are about 1.3px there). Lucide's native stroke is 2 on a 24 grid,\n// which is 2 x 14/24 = 1.17px at 14px, a shade light. So the root of every\n// stroke icon carries stroke-width=\"2.571\": 2.571 x 14/24 = 1.5. A caller\n// drawing at another size gets the proportional weight (12px: 1.29px; 16px:\n// 1.71px), which is what one drawn set means. Filled shapes (the discs) carry\n// fill=\"currentColor\" and no stroke at all.\n//\n// Every icon is `currentColor`, so it takes the colour of the button it sits\n// in, and none of the strings carries width/height: the size is the caller's\n// (`iconNode(name, size)`), so an icon can never be the wrong size somewhere\n// because a CSS rule failed to reach it.\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// Lucide licence (the icons vendored in this file):\n//\n// ISC License\n// Copyright (c) 2026 Lucide Icons and Contributors\n//\n// Permission to use, copy, modify, and/or distribute this software for any\n// purpose with or without fee is hereby granted, provided that the above\n// copyright notice and this permission notice appear in all copies.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n//\n// Portions of Lucide (among those used here: arrow-down, arrow-left,\n// arrow-right, arrow-up, check, chevron-down, chevron-left, chevron-right,\n// circle, lock, plus, search, x) derive from Feather, MIT,\n// Copyright (c) 2013-present Cole Bemis. See LICENSES.md.\n// ---------------------------------------------------------------------------\n\n/**\n * The typed glyphs the vocabulary used to draw.\n *\n * @deprecated Since the move to drawn icons (design-language.md, \"Icons are\n * drawn\"). Kept byte-for-byte so a consumer still doing\n * `textContent = icon.close` keeps working while each app moves; a new call\n * site uses `iconNode(name)` (a node to append) or `iconHtml(name)` (for an\n * innerHTML build). The names are the same in all three. Removed once both\n * apps have moved.\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 /** YOU hold this one: checked out / opened by the current user, still yours to\n * edit. Shares its glyph with `note`, which the table permits: an entry is a\n * MEANING, and two meanings may draw the same shape. */\n checkedOut: \"✎\",\n /** Tracked, with local changes not yet committed. */\n modified: \"●\",\n /** Not in version control yet: new since the last commit. */\n untracked: \"+\",\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 /** Start the thing again from the top: a play session, a sweep. Named for the\n * act, not the arrow, which is the table's rule. */\n restart: \"⟲\",\n /** The three ways a collection can be shown. Named by what you get rather than\n * by the glyph, so a second app can pick the same three without copying\n * somebody's taste in symbols. */\n viewCards: \"▦\",\n viewTable: \"☰\",\n viewNode: \"◈\",\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\n/**\n * Every word in the vocabulary. The 26 the glyph table had, plus the seven the\n * 2026-09 review found the apps drawing by hand with nothing to check against.\n */\nexport type IconName =\n | keyof typeof icon\n /** History back, the topbar's quiet arrow pair (nav-history.ts). An ARROW,\n * not a chevron: a chevron says structure, an arrow says time (the author's\n * ruling, 2026-08-28). Added beside `arrowRight` so the pair share a set. */\n | \"arrowLeft\"\n /** History forward; also the one honest \"go there\" arrow where a link used\n * to carry a typed one. */\n | \"arrowRight\"\n /** Connect to a server, a live link, a running game. */\n | \"connect\"\n /** Search. */\n | \"search\"\n /** Keep this window on top (tool-window-web.ts). */\n | \"pin\"\n /** Settings. */\n | \"settings\"\n /** Record. The icon is the DISC; painting it in the danger colour while\n * recording is the caller's job, through the button's own colour. */\n | \"record\";\n\n// The two roots every drawing shares. Kept as constants so the table below is\n// the inner elements only, which is what a reader compares against lucide.dev.\nconst STROKE = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.571\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">';\nconst FILL = '<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">';\nconst stroked = (inner: string): string => `${STROKE}${inner}</svg>`;\nconst filled = (inner: string): string => `${FILL}${inner}</svg>`;\n\n// The three discs are the one drawing in the table that is not Lucide's own\n// element as shipped: Lucide's `circle` is an r=10 outline that fills its box,\n// which at badge size beside a row title is a blot, so the disc is drawn at a\n// smaller radius with the fill forced (the same amount of hand-drawing as the\n// legacy noteFilled below, and the decision record's stated cost). `modified`\n// and `dot` share it, as their glyphs did; `record` is larger because a record\n// button is meant to fill more of its box.\nconst DISC = filled('<circle cx=\"12\" cy=\"12\" r=\"6\"/>');\nconst DISC_LARGE = filled('<circle cx=\"12\" cy=\"12\" r=\"9\"/>');\n\n/**\n * The drawings, by word. Each is a complete `<svg viewBox=\"0 0 24 24\" …>`\n * string with no width/height (see `iconNode` / `iconHtml`, which size it).\n * The Lucide name each derives from is on the entry, so a reviewer can put\n * the two side by side.\n */\nconst DRAWN: Record<IconName, string> = {\n // --- dismissing and revealing ---------------------------------------------\n /** Close a panel, a dialog, a popover. (x) */\n close: stroked('<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>'),\n /** The overflow menu on a document or a row: \"more about this\". (ellipsis) */\n more: stroked('<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>'),\n /** Collapsed disclosure, pointing at what would open. (chevron-right) */\n collapsed: stroked('<path d=\"m9 18 6-6-6-6\"/>'),\n /** Expanded disclosure. (chevron-down) */\n expanded: stroked('<path d=\"m6 9 6 6 6-6\"/>'),\n /** A dropdown's affordance, where it is not a real <select>. (chevron-down) */\n dropdown: stroked('<path d=\"m6 9 6 6 6-6\"/>'),\n\n // --- moving through things ------------------------------------------------\n /** Back / previous, in a breadcrumb or a stepper. (chevron-left) */\n back: stroked('<path d=\"m15 18-6-6 6-6\"/>'),\n /** Forward / next. (chevron-right) */\n forward: stroked('<path d=\"m9 18 6-6-6-6\"/>'),\n /** Up one, in a list. (arrow-up) */\n up: stroked('<path d=\"m5 12 7-7 7 7\"/><path d=\"M12 19V5\"/>'),\n /** Down one. (arrow-down) */\n down: stroked('<path d=\"M12 5v14\"/><path d=\"m19 12-7 7-7-7\"/>'),\n /** History back. (arrow-left) */\n arrowLeft: stroked('<path d=\"m12 19-7-7 7-7\"/><path d=\"M19 12H5\"/>'),\n /** History forward; \"go there\". (arrow-right) */\n arrowRight: stroked('<path d=\"M5 12h14\"/><path d=\"m12 5 7 7-7 7\"/>'),\n\n // --- state ----------------------------------------------------------------\n /** Done, applied, agreed. (check) */\n tick: stroked('<path d=\"M20 6 9 17l-5-5\"/>'),\n /** Somebody else holds this: the version-control lock. (lock) */\n locked: stroked('<rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/>'),\n /** Read-only on disk with no other holder. (lock-open) An open lock rather\n * than a struck-out eye: this state is not a prohibition. Nobody holds the\n * file, saving checks it out, and it is the resting state of everything the\n * author has not touched yet under a lock-based VCS. eye-off would say \"you\n * cannot\", which is false here; the open lock says \"not held\", beside\n * `locked`, which is the shut one, so the two read as a pair. */\n readOnly: stroked('<rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 9.9-1\"/>'),\n /** YOU hold this one: checked out, still yours to edit. (pencil) */\n checkedOut: stroked('<path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\"/><path d=\"m15 5 4 4\"/>'),\n /** Tracked, with local changes not yet committed. (a disc; see DISC) */\n modified: DISC,\n /** Not in version control yet: new since the last commit. (plus) */\n untracked: stroked('<path d=\"M5 12h14\"/><path d=\"M12 5v14\"/>'),\n /** Something needs attention but is not fatal. (triangle-alert) */\n warning: stroked('<path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/>'),\n /** A live thing: the running position, an active marker. (a disc) */\n dot: DISC,\n /** Play, in the sense of \"run this\". (play) */\n play: stroked('<path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/>'),\n /** Start the thing again from the top: a play session, a sweep. (rotate-ccw) */\n restart: stroked('<path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/><path d=\"M3 3v5h5\"/>'),\n /** The three ways a collection can be shown, named by what you get. */\n /** Cards. (layout-grid) */\n viewCards: stroked('<rect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\"/><rect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\"/>'),\n /** A table. (rows-3: a ruled box, which is a table. `list` is bullets, and\n * the view is not a list.) */\n viewTable: stroked('<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M21 9H3\"/><path d=\"M21 15H3\"/>'),\n /** A node graph. (waypoints: four nodes on edges reads as a graph.\n * `git-fork` reads as version control, which this family also draws, and\n * a picture that means two things is the fault this table exists to\n * prevent.) */\n viewNode: stroked('<path d=\"m10.586 5.414-5.172 5.172\"/><path d=\"m18.586 13.414-5.172 5.172\"/><path d=\"M6 12h12\"/><circle cx=\"12\" cy=\"20\" r=\"2\"/><circle cx=\"12\" cy=\"4\" r=\"2\"/><circle cx=\"20\" cy=\"12\" r=\"2\"/><circle cx=\"4\" cy=\"12\" r=\"2\"/>'),\n /** Record: the disc, in whatever colour the button is. (a disc; see DISC_LARGE) */\n record: DISC_LARGE,\n\n // --- handling -------------------------------------------------------------\n /** The drag grip on a reorderable row. (grip-vertical) */\n grip: stroked('<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/><circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/>'),\n /** Add one of whatever this list holds. (plus) */\n add: stroked('<path d=\"M5 12h14\"/><path d=\"M12 5v14\"/>'),\n /** Connect: to a server, a live link, a running game. (plug) */\n connect: stroked('<path d=\"M12 22v-5\"/><path d=\"M15 8V2\"/><path d=\"M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z\"/><path d=\"M9 8V2\"/>'),\n /** Search. (search) */\n search: stroked('<path d=\"m21 21-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/>'),\n /** Keep this window on top. (pin) */\n pin: stroked('<path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 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\"/>'),\n /** Settings. (settings) */\n settings: stroked('<path d=\"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>'),\n\n // --- annotation -----------------------------------------------------------\n /** Documentation notes: a document with lines on it. (file-text) The glyph\n * table drew a pencil and shared it with `checkedOut`; the drawn set gives\n * the two meanings two shapes, and notes get the document, which is what\n * the shell's own noteFilled/noteOutline pair had always drawn. */\n note: stroked('<path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5\"/><path d=\"M10 9H8\"/><path d=\"M16 13H8\"/><path d=\"M16 17H8\"/>'),\n /** A comment thread. Speech, not a document: documentation states the\n * reason, comments are the conversation, and the two must never look\n * alike. (message-square) */\n comment: stroked('<path d=\"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z\"/>'),\n};\n\n/**\n * The drawings as strings, by word, for a caller that builds markup itself.\n * Prefer `iconHtml(name, size)`, which sizes it; these carry no width/height.\n *\n * Two legacy entries ride along unchanged: `noteFilled` and `noteOutline`, the\n * has-notes / no-notes pair Patterpad drew on a 16-unit grid before the set\n * existed. They are sized in the markup (13px) and on their own grid, so they\n * are NOT part of the vocabulary above and `iconNode` does not know them; no\n * app uses them today. They stay until a 24-grid pair replaces them, because\n * their two-state meaning (there are notes / there are none) has no Lucide\n * shape, and re-weighting the 16-grid drawing to the family stroke merges its\n * text lines.\n */\nexport const iconSvg = {\n ...DRAWN,\n /** A document with lines: notes exist on this thing. (legacy, 16 grid) */\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. (legacy, 16 grid) */\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/** Every word, in table order, for a test or a gallery to walk. */\nexport const ICON_NAMES = Object.keys(DRAWN) as IconName[];\n\n/** The default size: 14px, the family's icon inside its small square button. */\nexport const ICON_SIZE = 14;\n\n/**\n * The drawing as markup with its size set, for a caller building innerHTML.\n * `data-icon` carries the word, so a stylesheet or a test can find it.\n */\nexport function iconHtml(name: IconName, size = ICON_SIZE): string {\n return DRAWN[name].replace(\"<svg \", `<svg width=\"${size}\" height=\"${size}\" data-icon=\"${name}\" `);\n}\n\n// One parsed template per word, filled on first use; every call clones it, so\n// the string is parsed once per window and each caller gets its own node.\nconst templates = new Map<IconName, SVGSVGElement>();\n\n/**\n * The drawing as a fresh SVG node, sized. Append it where the glyph used to be\n * set as text; it is `aria-hidden`, so the button's accessible name stays the\n * button's (`aria-label` / `data-tip`), as it always had to.\n */\nexport function iconNode(name: IconName, size = ICON_SIZE): SVGSVGElement {\n let tpl = templates.get(name);\n if (!tpl) {\n const holder = document.createElement(\"template\");\n holder.innerHTML = DRAWN[name];\n tpl = holder.content.firstElementChild as SVGSVGElement;\n tpl.setAttribute(\"data-icon\", name);\n templates.set(name, tpl);\n }\n const node = tpl.cloneNode(true) as SVGSVGElement;\n node.setAttribute(\"width\", String(size));\n node.setAttribute(\"height\", String(size));\n return node;\n}\n\n// The glyph table, reversed, so a legacy call site that still hands the shell\n// a typed character (\"✕\", \"↑\") draws the word it stood for. Where two words\n// shared a glyph the first in table order wins, which is fine: the picture was\n// the same either way. Goes when `icon` does.\nconst BY_GLYPH = new Map<string, IconName>();\nfor (const [name, glyph] of Object.entries(icon) as [IconName, string][]) {\n if (!BY_GLYPH.has(glyph)) BY_GLYPH.set(glyph, name);\n}\n\n/** The word a deprecated glyph stood for, or undefined for any other string. */\nexport function iconNameOfGlyph(glyph: string): IconName | undefined {\n return BY_GLYPH.get(glyph);\n}\n\n/** Whether a string is a word in the vocabulary. */\nexport function isIconName(s: string): s is IconName {\n return Object.prototype.hasOwnProperty.call(DRAWN, s);\n}\n","// ---------------------------------------------------------------------------\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\nimport { ensureTooltipHost, checkTooltipHost } from \"./tooltip.js\";\nimport { iconNode, iconNameOfGlyph, isIconName, type IconName } from \"./icons.js\";\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) {\n node.dataset[\"tip\"] = props.tip;\n node.setAttribute(\"aria-label\", props.tip);\n // Setting a tip mounts the renderer. Without this, `data-tip` draws nothing at all in a window whose\n // host never called `initTooltips()`, which is a silent failure rather than a degraded one. No\n // options passed: the host stays authoritative about behaviour (see tooltip.ts).\n ensureTooltipHost();\n }\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 checkTooltipHost(); // one-shot: warns if the APP wrote data-tip and nothing mounted a host\n return node;\n}\n\n/** A small square icon 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.\n *\n * `name` is a word from the vocabulary (\"up\", \"close\"), drawn at 14px. A\n * typed glyph from the deprecated `icon` table (\"↑\", \"✕\") is accepted for\n * now and drawn as the word it stood for, so an app that has not moved its\n * call sites yet still gets the drawn set; any other string is set as text,\n * which is the old behaviour and on its way out. */\nexport function iconBtn(name: IconName | (string & {}), title: string, onClick: () => void, disabled = false, danger = false): HTMLButtonElement {\n const b = el(\"button\", `shell-icon${danger ? \" danger\" : \"\"}`);\n b.type = \"button\"; b.dataset[\"tip\"] = title; b.setAttribute(\"aria-label\", title);\n const word = isIconName(name) ? name : iconNameOfGlyph(name);\n if (word) b.append(iconNode(word)); else b.textContent = name;\n b.disabled = disabled;\n b.addEventListener(\"click\", onClick);\n return b;\n}\n\n/**\n * A captioned field: `<label class=\"shell-labelled\"><span class=\"shell-fieldcap\">…</span>control</label>`.\n *\n * The label is pointed at the field it captions, EXPLICITLY, and never at a button. A `<label>` with no\n * `for` forwards a click to its first labelable descendant, and buttons are labelable - so a caption\n * wrapped around a control containing buttons (`tagChips`, whose every chip carries a remove button)\n * turns every click on the row's dead space into a press of the FIRST button in it. That shipped:\n * patterkit/patter#44, where clicking a Game Data list value anywhere but its ✕ deleted the first value\n * in the list. Reported as \"clicking a value removes the wrong one\", which is not a wrong-index bug at\n * all - the click never reached the chip.\n *\n * When the control holds no labelable field (a chips editor with the add input removed, say), this is a\n * plain `<div>`: no caption behaviour is better than a caption that presses something.\n */\nexport function labelled(label: string, control: HTMLElement): HTMLElement {\n const target = control.matches(\"input, select, textarea\")\n ? control\n : control.querySelector<HTMLElement>(\"input:not([type=button]):not([type=submit]), select, textarea\");\n const w = el(target ? \"label\" : \"div\", \"shell-labelled\");\n w.append(el(\"span\", \"shell-fieldcap\", label), control);\n if (target) {\n if (!target.id) target.id = `shell-f-${Math.random().toString(36).slice(2, 9)}`;\n (w as HTMLLabelElement).htmlFor = target.id;\n }\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/** The icon size inside a chip's remove / move buttons: the chip is set at\n * 0.76rem, so its controls draw smaller than a toolbar button's 14px. */\nconst CHIP_ICON = 10;\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 // Sentence-cased like every other hint (the microcopy ruling, 2026-08-28):\n // lowercase fragments read as adrift, and a placeholder is a hint speaking.\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\", iconNode(\"close\", CHIP_ICON));\n x.type = \"button\"; x.dataset[\"tip\"] = `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/**\n * An ORDERED chips editor for a string-list field whose order means something\n * (a quality's stages: `advance()` walks them, so 2 comes after 1): numbered\n * chips, each with move-earlier / move-later / remove, plus an add input\n * (Enter or \",\" commits; blank / duplicate ignored). Mutates `holder.stages`\n * in place; `onChange` fires after any add / move / remove.\n *\n * Patterpad's, lifted (ui-review-2026-09, finding 3); Storyletter had been\n * faking it with `tagChips` over a `values` holder, which loses the order\n * controls. Same class family as `tagChips` (`.shell-tags`), so it is styled by\n * settings.css already.\n */\nexport function stageChips(holder: { stages?: string[] }, onChange?: () => void): HTMLElement {\n const wrap = el(\"div\", \"shell-tags shell-stages\");\n const input = el(\"input\", \"shell-tag-input\");\n input.type = \"text\"; input.placeholder = \"Add stage\"; input.spellcheck = false;\n const control = (name: IconName, tip: string, disabled: boolean, onClick: () => void): HTMLButtonElement => {\n const b = el(\"button\", \"shell-tag-x shell-tag-move\", iconNode(name, CHIP_ICON));\n b.type = \"button\"; b.dataset[\"tip\"] = tip; b.setAttribute(\"aria-label\", tip);\n b.disabled = disabled;\n b.addEventListener(\"click\", onClick);\n return b;\n };\n const rebuild = (): void => {\n for (const c of Array.from(wrap.children)) if (c !== input) c.remove();\n const stages = holder.stages ?? [];\n stages.forEach((v, i) => {\n const chip = el(\"span\", \"shell-tag\", `${i + 1}. ${v}`);\n chip.dataset[\"stage\"] = v;\n chip.append(\n control(\"back\", `Move ${v} earlier`, i === 0,\n () => { if (moveItem(stages, i, -1)) { rebuild(); onChange?.(); } }),\n control(\"forward\", `Move ${v} later`, i === stages.length - 1,\n () => { if (moveItem(stages, i, 1)) { rebuild(); onChange?.(); } }),\n );\n const x = el(\"button\", \"shell-tag-x\", iconNode(\"close\", CHIP_ICON));\n x.type = \"button\"; x.dataset[\"tip\"] = `Remove ${v}`; x.setAttribute(\"aria-label\", `Remove ${v}`);\n x.addEventListener(\"click\", () => { holder.stages = (holder.stages ?? []).filter((o) => o !== v); rebuild(); onChange?.(); });\n chip.append(x);\n wrap.insertBefore(chip, input);\n });\n };\n const commit = (): void => {\n const v = input.value.trim();\n if (v && !(holder.stages ?? []).includes(v)) { (holder.stages ??= []).push(v); rebuild(); 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 wrap.append(input);\n rebuild();\n return wrap;\n}\n\n// --- drag-to-reorder ---------------------------------------------------------\n// One drag at a time per window, shared across every wired list, so the\n// midpoint test knows what is being carried without a dataTransfer round trip.\nlet reorderDragId: string | null = null;\nconst clearDropMarks = (host: ParentNode): void =>\n host.querySelectorAll(\".drop-before, .drop-after\").forEach((e) => e.classList.remove(\"drop-before\", \"drop-after\"));\n\n/**\n * Make `el` a drag source and a drop target for HTML5 drag-to-reorder among\n * its siblings. While carried it wears `.dragging`; a hovered target wears\n * `.drop-before` or `.drop-after` by the midpoint test on `axis` (\"y\" for\n * rows, \"x\" for a wrapping grid); a drop calls `onMove(draggedId, before,\n * targetId)` and the caller reorders its model and re-renders.\n *\n * Storyletter's `wireCardDrag`, lifted (ui-review-2026-09, finding 30);\n * Patterpad's `wireSceneDrag` is the same idea fixed on \"y\". The marks are\n * classes only: the app draws them (Patterpad's `box-shadow: 0 -2px 0 0\n * var(--accent)` pair is the family's look).\n */\nexport function wireReorder(el: HTMLElement, id: string, axis: \"x\" | \"y\", onMove: (id: string, before: boolean, targetId: string) => void): void {\n el.draggable = true;\n el.dataset[\"reorderId\"] = id;\n const host = (): ParentNode => el.parentElement ?? el;\n el.addEventListener(\"dragstart\", (e) => {\n reorderDragId = id; el.classList.add(\"dragging\");\n if (e.dataTransfer) { e.dataTransfer.effectAllowed = \"move\"; e.dataTransfer.setData(\"text/plain\", id); }\n });\n el.addEventListener(\"dragend\", () => { reorderDragId = null; el.classList.remove(\"dragging\"); clearDropMarks(host()); });\n el.addEventListener(\"dragover\", (e) => {\n if (!reorderDragId || reorderDragId === id) return;\n e.preventDefault();\n if (e.dataTransfer) e.dataTransfer.dropEffect = \"move\";\n const r = el.getBoundingClientRect();\n const before = axis === \"y\" ? e.clientY < r.top + r.height / 2 : e.clientX < r.left + r.width / 2;\n clearDropMarks(host());\n el.classList.add(before ? \"drop-before\" : \"drop-after\");\n });\n el.addEventListener(\"dragleave\", () => el.classList.remove(\"drop-before\", \"drop-after\"));\n el.addEventListener(\"drop\", (e) => {\n e.preventDefault();\n const before = el.classList.contains(\"drop-before\");\n clearDropMarks(host());\n if (reorderDragId && reorderDragId !== id) onMove(reorderDragId, before, id);\n });\n}\n","// ---------------------------------------------------------------------------\n// The small idioms both apps wrote by hand and never gave a name: the plural\n// suffix (about forty sites), the debounce (eight copies), \"is the focus in a\n// field\" (four copies that disagreed about <select>), the count with grouping\n// separators, clipboard copy with the `.copied` revert (three timings), and\n// relative time (two wordings). One answer each, here.\n//\n// DOM-free except where the idiom is about the DOM (isEditableTarget,\n// copyWithFeedback), so main-process code can import the rest.\n// ---------------------------------------------------------------------------\n\n/** \"1 link\" / \"3 links\". The plural form defaults to `noun + \"s\"`; pass the\n * irregular one (\"entry\", \"entries\") when that is wrong. */\nexport function plural(n: number, noun: string, pluralForm = `${noun}s`): string {\n return `${n} ${n === 1 ? noun : pluralForm}`;\n}\n\nexport interface Debounced<A extends unknown[]> {\n (...args: A): void;\n /** Drop the pending call. */\n cancel(): void;\n /** Run the pending call now, if there is one. */\n flush(): void;\n}\n\n/** Trailing-edge debounce: the call runs `ms` after the LAST call. */\nexport function debounce<A extends unknown[]>(fn: (...args: A) => void, ms: number): Debounced<A> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pending: A | undefined;\n const run = (): void => {\n timer = undefined;\n if (pending === undefined) return;\n const args = pending;\n pending = undefined;\n fn(...args);\n };\n const debounced = ((...args: A): void => {\n pending = args;\n if (timer !== undefined) clearTimeout(timer);\n timer = setTimeout(run, ms);\n }) as Debounced<A>;\n debounced.cancel = () => { if (timer !== undefined) clearTimeout(timer); timer = undefined; pending = undefined; };\n debounced.flush = () => { if (timer !== undefined) clearTimeout(timer); run(); };\n return debounced;\n}\n\n/**\n * Is the keyboard's focus in a field? The one agreed answer: an input, a\n * textarea, a select, or anything contenteditable (the attribute is read as\n * well as `isContentEditable`, which jsdom does not implement). A window-level\n * shortcut checks this before acting, so a bare letter typed into a field\n * stays a letter.\n */\nexport function isEditableTarget(t: EventTarget | null | undefined): boolean {\n if (!(t instanceof Element)) return false;\n if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) return true;\n if (t instanceof HTMLElement && t.isContentEditable === true) return true;\n const attr = t.getAttribute(\"contenteditable\");\n return attr !== null && attr !== \"false\";\n}\n\n/** A count for reading: grouping separators in the user's locale. */\nexport function formatCount(n: number): string {\n return n.toLocaleString();\n}\n\nconst copyTimers = new WeakMap<Element, ReturnType<typeof setTimeout>>();\n\n/**\n * Copy `text` to the clipboard and light `el` with `.copied` for `ms` (the\n * family's 1000). A second click restarts the timer rather than stacking a\n * second revert under the first. Resolves when the clipboard write has.\n */\nexport function copyWithFeedback(el: Element, text: string, ms = 1000): Promise<void> {\n const write = typeof navigator !== \"undefined\" && navigator.clipboard\n ? navigator.clipboard.writeText(text).catch(() => {})\n : Promise.resolve();\n el.classList.add(\"copied\");\n const prior = copyTimers.get(el);\n if (prior !== undefined) clearTimeout(prior);\n copyTimers.set(el, setTimeout(() => { el.classList.remove(\"copied\"); copyTimers.delete(el); }, ms));\n return write;\n}\n\n/**\n * How long ago, in one wording: \"just now\", \"4 minutes ago\", \"an hour ago\",\n * \"3 hours ago\", \"yesterday\", \"5 days ago\", and past a week the date itself.\n * Coarse on purpose: the question a reader has is whether the thing is from\n * this sitting, not how many seconds ago it was. A future date is \"just now\".\n */\nexport function relativeTime(date: Date | number | string, now: number = Date.now()): string {\n const then = date instanceof Date ? date.getTime() : new Date(date).getTime();\n if (!Number.isFinite(then)) return \"\";\n const mins = Math.floor((now - then) / 60000);\n if (mins < 1) return \"just now\";\n if (mins < 60) return `${plural(mins, \"minute\")} ago`;\n const hours = Math.floor(mins / 60);\n if (hours === 1) return \"an hour ago\";\n if (hours < 24) return `${hours} hours ago`;\n const days = Math.floor(hours / 24);\n if (days === 1) return \"yesterday\";\n if (days < 7) return `${days} days ago`;\n return new Date(then).toLocaleDateString(undefined, { day: \"numeric\", month: \"short\", year: \"numeric\" });\n}\n","// ---------------------------------------------------------------------------\n// The live-link status chip: a compact control in the bottom-right corner for\n// the loopback server a running game connects to. All the state collapses to\n// one coloured \"connect\" icon (grey off, amber listening, green connected and\n// in sync, red connected on a different build); click it to toggle the link\n// (off -> listening -> stop), and while the link is up the copiable ws://\n// address sits beside it. Patterpad's debug-panel.ts and Storyletter's\n// live-link.ts were this file twice, character for character down to the\n// 1000ms `.copied` revert. Styles ship as link-status.css.\n//\n// What the game sends is the app's business; this is the control and the\n// status only.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\nimport { iconNode } from \"./icons.js\";\nimport { copyWithFeedback } from \"./util.js\";\n\nexport type LinkStatus =\n | { state: \"off\" }\n | { state: \"error\"; message: string }\n | { state: \"listening\"; port: number; address?: string }\n | {\n state: \"connected\"; port: number; address?: string;\n /** What connected, if it said. */\n project?: string;\n /** Whether the game's build matches the editor's. */\n build?: \"match\" | \"stale\" | \"unknown\";\n /** One extra sentence for the tip (Storyletter names the live boxes). */\n note?: string;\n };\n\n/** The chip's colour class for a state: the four. */\nexport type LinkStatusClass = \"off\" | \"listening\" | \"live\" | \"stale\";\n\nexport interface LinkStatusOptions {\n /** A word beside the chip (\"Live link\"), so a bottom-corner plug is not a\n * mystery. Omitted draws none. */\n label?: string;\n /** Start when off or failed, stop otherwise. A returned status is applied;\n * a status pushed later through `apply` works as well. */\n onToggle: (current: LinkStatus) => LinkStatus | void | Promise<LinkStatus | void>;\n /** Runs after the address is copied. The copy itself is the chip's. */\n onCopy?: (address: string) => void;\n /** Controls before the address (Patterpad's flow picker). */\n lead?: HTMLElement[];\n}\n\nexport interface LinkStatusChip {\n el: HTMLElement;\n apply(status: LinkStatus): void;\n status(): LinkStatus;\n /** Show / hide the control (shown when a project is open). */\n setVisible(on: boolean): void;\n /** Toggle the link (a menu item routes here). */\n toggle(): void;\n}\n\nexport function linkStatusClass(s: LinkStatus): LinkStatusClass {\n if (s.state === \"off\" || s.state === \"error\") return \"off\";\n if (s.state === \"listening\") return \"listening\";\n return s.build === \"stale\" ? \"stale\" : \"live\";\n}\n\n/** The tooltip, spelling the state out. One wording for the family. */\nexport function linkStatusTip(s: LinkStatus): string {\n switch (s.state) {\n case \"off\": return \"Live link is off. Click to start listening.\";\n case \"error\": return `Live link failed (${s.message}). Click to retry.`;\n case \"listening\": return \"Live link is listening for a game. Click to stop.\";\n case \"connected\": {\n const who = s.project !== undefined ? ` to ${s.project}` : \"\";\n const build = s.build === \"stale\" ? \" on a different build, so save or rebuild to re-sync\"\n : s.build === \"match\" ? \" and in sync\" : \"\";\n const note = s.note ? ` ${s.note}` : \"\";\n return `Live link is connected${who}${build}.${note} Click to stop.`;\n }\n }\n}\n\n/** The address a game connects to. */\nexport function linkAddress(s: LinkStatus): string | undefined {\n if (s.state !== \"listening\" && s.state !== \"connected\") return undefined;\n return s.address ?? `ws://127.0.0.1:${s.port}`;\n}\n\n/** Mount the chip into `host` (document.body in both apps: it is fixed to the\n * window's corner). Hidden until `setVisible(true)`. */\nexport function mountLinkStatus(host: HTMLElement, opts: LinkStatusOptions): LinkStatusChip {\n const wrap = el(\"div\", \"linkstatus\"); wrap.hidden = true;\n const url = el(\"button\", \"linkstatus-url\"); url.type = \"button\"; url.hidden = true;\n const toggle = el(\"button\", \"linkstatus-toggle off\"); toggle.type = \"button\"; toggle.append(iconNode(\"connect\"));\n if (opts.label !== undefined) wrap.append(el(\"span\", \"linkstatus-word\", opts.label));\n wrap.append(...(opts.lead ?? []), url, toggle);\n host.append(wrap);\n\n let status: LinkStatus = { state: \"off\" };\n\n const render = (): void => {\n toggle.className = `linkstatus-toggle ${linkStatusClass(status)}`;\n const tip = linkStatusTip(status);\n toggle.dataset[\"tip\"] = tip;\n toggle.setAttribute(\"aria-label\", tip);\n // The copiable address means something once the server is up.\n const addr = linkAddress(status);\n if (addr !== undefined) {\n url.textContent = addr;\n url.dataset[\"tip\"] = \"Click to copy the live link address\";\n url.hidden = false;\n } else url.hidden = true;\n };\n const apply = (s: LinkStatus): void => { status = s; render(); };\n render();\n\n toggle.addEventListener(\"click\", () => {\n const out = opts.onToggle(status);\n void Promise.resolve(out).then((next) => { if (next) apply(next); });\n });\n url.addEventListener(\"click\", () => {\n const addr = url.textContent ?? \"\";\n if (!addr) return;\n void copyWithFeedback(url, addr);\n opts.onCopy?.(addr);\n });\n\n return {\n el: wrap, apply,\n status: () => status,\n setVisible(on: boolean): void { wrap.hidden = !on; },\n toggle(): void { toggle.click(); },\n };\n}\n"]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* src/link-status.css */
|
|
2
|
+
.linkstatus {
|
|
3
|
+
position: fixed;
|
|
4
|
+
right: 0.9rem;
|
|
5
|
+
bottom: 0.32rem;
|
|
6
|
+
z-index: 40;
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: 0.4rem;
|
|
10
|
+
}
|
|
11
|
+
.linkstatus[hidden] {
|
|
12
|
+
display: none;
|
|
13
|
+
}
|
|
14
|
+
.linkstatus-word {
|
|
15
|
+
font: 0.7rem var(--font-ui);
|
|
16
|
+
color: var(--muted);
|
|
17
|
+
user-select: none;
|
|
18
|
+
}
|
|
19
|
+
.linkstatus-url {
|
|
20
|
+
font: 0.72rem var(--font-mono);
|
|
21
|
+
color: var(--muted);
|
|
22
|
+
background: var(--surface);
|
|
23
|
+
border: 1px solid var(--line);
|
|
24
|
+
border-radius: var(--radius-sm);
|
|
25
|
+
padding: 0.14rem 0.45rem;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
transition: color var(--dur-quick) var(--ease-standard), border-color var(--dur-quick) var(--ease-standard);
|
|
28
|
+
}
|
|
29
|
+
.linkstatus-url:hover {
|
|
30
|
+
border-color: var(--accent);
|
|
31
|
+
color: var(--accent);
|
|
32
|
+
}
|
|
33
|
+
.linkstatus-url.copied {
|
|
34
|
+
color: var(--accent);
|
|
35
|
+
border-color: var(--accent);
|
|
36
|
+
}
|
|
37
|
+
.linkstatus-url[hidden] {
|
|
38
|
+
display: none;
|
|
39
|
+
}
|
|
40
|
+
.linkstatus-toggle {
|
|
41
|
+
flex: none;
|
|
42
|
+
width: 1.4rem;
|
|
43
|
+
height: 1.4rem;
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
padding: 0;
|
|
48
|
+
border: 1px solid var(--line);
|
|
49
|
+
border-radius: var(--radius-sm);
|
|
50
|
+
background: var(--surface);
|
|
51
|
+
color: var(--muted);
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
transition: color var(--dur-quick) var(--ease-standard), border-color var(--dur-quick) var(--ease-standard);
|
|
54
|
+
}
|
|
55
|
+
.linkstatus-toggle:hover {
|
|
56
|
+
border-color: var(--accent);
|
|
57
|
+
}
|
|
58
|
+
.linkstatus-toggle svg {
|
|
59
|
+
display: block;
|
|
60
|
+
}
|
|
61
|
+
.linkstatus-toggle.listening {
|
|
62
|
+
color: var(--warn);
|
|
63
|
+
border-color: var(--warn);
|
|
64
|
+
background: color-mix(in oklab, var(--warn) 12%, var(--surface));
|
|
65
|
+
}
|
|
66
|
+
.linkstatus-toggle.live {
|
|
67
|
+
color: var(--ok);
|
|
68
|
+
border-color: var(--ok);
|
|
69
|
+
background: color-mix(in oklab, var(--ok) 14%, var(--surface));
|
|
70
|
+
}
|
|
71
|
+
.linkstatus-toggle.stale {
|
|
72
|
+
color: var(--danger);
|
|
73
|
+
border-color: var(--danger);
|
|
74
|
+
background: color-mix(in oklab, var(--danger) 12%, var(--surface));
|
|
75
|
+
}
|
|
76
|
+
/*# sourceMappingURL=link-status.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/link-status.css"],"sourcesContent":["/* ---------------------------------------------------------------------------\n The live-link status chip (link-status.ts): the bottom-right plug, its\n four states, and the copiable address. Consumes the host palette\n (--surface / --ink / --muted / --line / --accent / --warn / --ok /\n --danger / --font-ui / --font-mono) over the shared tokens.\n --------------------------------------------------------------------------- */\n\n.linkstatus { position: fixed; right: 0.9rem; bottom: 0.32rem; z-index: 40; display: inline-flex; align-items: center; gap: 0.4rem; }\n.linkstatus[hidden] { display: none; }\n.linkstatus-word { font: 0.7rem var(--font-ui); color: var(--muted); user-select: none; }\n\n.linkstatus-url {\n font: 0.72rem var(--font-mono); color: var(--muted); background: var(--surface);\n border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 0.14rem 0.45rem; cursor: pointer;\n transition: color var(--dur-quick) var(--ease-standard), border-color var(--dur-quick) var(--ease-standard);\n}\n.linkstatus-url:hover { border-color: var(--accent); color: var(--accent); }\n/* Lit for a moment after a copy (copyWithFeedback). */\n.linkstatus-url.copied { color: var(--accent); border-color: var(--accent); }\n.linkstatus-url[hidden] { display: none; }\n\n.linkstatus-toggle {\n flex: none; width: 1.4rem; height: 1.4rem; display: inline-flex; align-items: center; justify-content: center;\n padding: 0; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);\n color: var(--muted); cursor: pointer;\n transition: color var(--dur-quick) var(--ease-standard), border-color var(--dur-quick) var(--ease-standard);\n}\n.linkstatus-toggle:hover { border-color: var(--accent); }\n.linkstatus-toggle svg { display: block; }\n/* The four states. */\n.linkstatus-toggle.listening { color: var(--warn); border-color: var(--warn); background: color-mix(in oklab, var(--warn) 12%, var(--surface)); }\n.linkstatus-toggle.live { color: var(--ok); border-color: var(--ok); background: color-mix(in oklab, var(--ok) 14%, var(--surface)); }\n.linkstatus-toggle.stale { color: var(--danger); border-color: var(--danger); background: color-mix(in oklab, var(--danger) 12%, var(--surface)); }\n"],"mappings":";AAOA,CAAC;AAAa,YAAU;AAAO,SAAO;AAAQ,UAAQ;AAAS,WAAS;AAAI,WAAS;AAAa,eAAa;AAAQ,OAAK;AAAQ;AACpI,CADC,UACU,CAAC;AAAU,WAAS;AAAM;AACrC,CAAC;AAAkB,QAAM,OAAO,IAAI;AAAY,SAAO,IAAI;AAAU,eAAa;AAAM;AAExF,CAAC;AACC,QAAM,QAAQ,IAAI;AAAc,SAAO,IAAI;AAAU,cAAY,IAAI;AACrE,UAAQ,IAAI,MAAM,IAAI;AAAS,iBAAe,IAAI;AAAc,WAAS,QAAQ;AAAS,UAAQ;AAClG,cAAY,MAAM,IAAI,aAAa,IAAI,gBAAgB,EAAE,aAAa,IAAI,aAAa,IAAI;AAC7F;AACA,CALC,cAKc;AAAS,gBAAc,IAAI;AAAW,SAAO,IAAI;AAAW;AAE3E,CAPC,cAOc,CAAC;AAAS,SAAO,IAAI;AAAW,gBAAc,IAAI;AAAW;AAC5E,CARC,cAQc,CAAC;AAAU,WAAS;AAAM;AAEzC,CAAC;AACC,QAAM;AAAM,SAAO;AAAQ,UAAQ;AAAQ,WAAS;AAAa,eAAa;AAAQ,mBAAiB;AACvG,WAAS;AAAG,UAAQ,IAAI,MAAM,IAAI;AAAS,iBAAe,IAAI;AAAc,cAAY,IAAI;AAC5F,SAAO,IAAI;AAAU,UAAQ;AAC7B,cAAY,MAAM,IAAI,aAAa,IAAI,gBAAgB,EAAE,aAAa,IAAI,aAAa,IAAI;AAC7F;AACA,CANC,iBAMiB;AAAS,gBAAc,IAAI;AAAW;AACxD,CAPC,kBAOkB;AAAM,WAAS;AAAO;AAEzC,CATC,iBASiB,CAAC;AAAY,SAAO,IAAI;AAAS,gBAAc,IAAI;AAAS,cAAY,UAAU,GAAG,KAAK,EAAE,IAAI,QAAQ,GAAG,EAAE,IAAI;AAAa;AAChJ,CAVC,iBAUiB,CAAC;AAAO,SAAO,IAAI;AAAO,gBAAc,IAAI;AAAO,cAAY,UAAU,GAAG,KAAK,EAAE,IAAI,MAAM,GAAG,EAAE,IAAI;AAAa;AACrI,CAXC,iBAWiB,CAAC;AAAQ,SAAO,IAAI;AAAW,gBAAc,IAAI;AAAW,cAAY,UAAU,GAAG,KAAK,EAAE,IAAI,UAAU,GAAG,EAAE,IAAI;AAAa;","names":[]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type LinkStatus = {
|
|
2
|
+
state: "off";
|
|
3
|
+
} | {
|
|
4
|
+
state: "error";
|
|
5
|
+
message: string;
|
|
6
|
+
} | {
|
|
7
|
+
state: "listening";
|
|
8
|
+
port: number;
|
|
9
|
+
address?: string;
|
|
10
|
+
} | {
|
|
11
|
+
state: "connected";
|
|
12
|
+
port: number;
|
|
13
|
+
address?: string;
|
|
14
|
+
/** What connected, if it said. */
|
|
15
|
+
project?: string;
|
|
16
|
+
/** Whether the game's build matches the editor's. */
|
|
17
|
+
build?: "match" | "stale" | "unknown";
|
|
18
|
+
/** One extra sentence for the tip (Storyletter names the live boxes). */
|
|
19
|
+
note?: string;
|
|
20
|
+
};
|
|
21
|
+
/** The chip's colour class for a state: the four. */
|
|
22
|
+
type LinkStatusClass = "off" | "listening" | "live" | "stale";
|
|
23
|
+
interface LinkStatusOptions {
|
|
24
|
+
/** A word beside the chip ("Live link"), so a bottom-corner plug is not a
|
|
25
|
+
* mystery. Omitted draws none. */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Start when off or failed, stop otherwise. A returned status is applied;
|
|
28
|
+
* a status pushed later through `apply` works as well. */
|
|
29
|
+
onToggle: (current: LinkStatus) => LinkStatus | void | Promise<LinkStatus | void>;
|
|
30
|
+
/** Runs after the address is copied. The copy itself is the chip's. */
|
|
31
|
+
onCopy?: (address: string) => void;
|
|
32
|
+
/** Controls before the address (Patterpad's flow picker). */
|
|
33
|
+
lead?: HTMLElement[];
|
|
34
|
+
}
|
|
35
|
+
interface LinkStatusChip {
|
|
36
|
+
el: HTMLElement;
|
|
37
|
+
apply(status: LinkStatus): void;
|
|
38
|
+
status(): LinkStatus;
|
|
39
|
+
/** Show / hide the control (shown when a project is open). */
|
|
40
|
+
setVisible(on: boolean): void;
|
|
41
|
+
/** Toggle the link (a menu item routes here). */
|
|
42
|
+
toggle(): void;
|
|
43
|
+
}
|
|
44
|
+
declare function linkStatusClass(s: LinkStatus): LinkStatusClass;
|
|
45
|
+
/** The tooltip, spelling the state out. One wording for the family. */
|
|
46
|
+
declare function linkStatusTip(s: LinkStatus): string;
|
|
47
|
+
/** The address a game connects to. */
|
|
48
|
+
declare function linkAddress(s: LinkStatus): string | undefined;
|
|
49
|
+
/** Mount the chip into `host` (document.body in both apps: it is fixed to the
|
|
50
|
+
* window's corner). Hidden until `setVisible(true)`. */
|
|
51
|
+
declare function mountLinkStatus(host: HTMLElement, opts: LinkStatusOptions): LinkStatusChip;
|
|
52
|
+
|
|
53
|
+
export { type LinkStatus, type LinkStatusChip, type LinkStatusClass, type LinkStatusOptions, linkAddress, linkStatusClass, linkStatusTip, mountLinkStatus };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type LinkStatus = {
|
|
2
|
+
state: "off";
|
|
3
|
+
} | {
|
|
4
|
+
state: "error";
|
|
5
|
+
message: string;
|
|
6
|
+
} | {
|
|
7
|
+
state: "listening";
|
|
8
|
+
port: number;
|
|
9
|
+
address?: string;
|
|
10
|
+
} | {
|
|
11
|
+
state: "connected";
|
|
12
|
+
port: number;
|
|
13
|
+
address?: string;
|
|
14
|
+
/** What connected, if it said. */
|
|
15
|
+
project?: string;
|
|
16
|
+
/** Whether the game's build matches the editor's. */
|
|
17
|
+
build?: "match" | "stale" | "unknown";
|
|
18
|
+
/** One extra sentence for the tip (Storyletter names the live boxes). */
|
|
19
|
+
note?: string;
|
|
20
|
+
};
|
|
21
|
+
/** The chip's colour class for a state: the four. */
|
|
22
|
+
type LinkStatusClass = "off" | "listening" | "live" | "stale";
|
|
23
|
+
interface LinkStatusOptions {
|
|
24
|
+
/** A word beside the chip ("Live link"), so a bottom-corner plug is not a
|
|
25
|
+
* mystery. Omitted draws none. */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Start when off or failed, stop otherwise. A returned status is applied;
|
|
28
|
+
* a status pushed later through `apply` works as well. */
|
|
29
|
+
onToggle: (current: LinkStatus) => LinkStatus | void | Promise<LinkStatus | void>;
|
|
30
|
+
/** Runs after the address is copied. The copy itself is the chip's. */
|
|
31
|
+
onCopy?: (address: string) => void;
|
|
32
|
+
/** Controls before the address (Patterpad's flow picker). */
|
|
33
|
+
lead?: HTMLElement[];
|
|
34
|
+
}
|
|
35
|
+
interface LinkStatusChip {
|
|
36
|
+
el: HTMLElement;
|
|
37
|
+
apply(status: LinkStatus): void;
|
|
38
|
+
status(): LinkStatus;
|
|
39
|
+
/** Show / hide the control (shown when a project is open). */
|
|
40
|
+
setVisible(on: boolean): void;
|
|
41
|
+
/** Toggle the link (a menu item routes here). */
|
|
42
|
+
toggle(): void;
|
|
43
|
+
}
|
|
44
|
+
declare function linkStatusClass(s: LinkStatus): LinkStatusClass;
|
|
45
|
+
/** The tooltip, spelling the state out. One wording for the family. */
|
|
46
|
+
declare function linkStatusTip(s: LinkStatus): string;
|
|
47
|
+
/** The address a game connects to. */
|
|
48
|
+
declare function linkAddress(s: LinkStatus): string | undefined;
|
|
49
|
+
/** Mount the chip into `host` (document.body in both apps: it is fixed to the
|
|
50
|
+
* window's corner). Hidden until `setVisible(true)`. */
|
|
51
|
+
declare function mountLinkStatus(host: HTMLElement, opts: LinkStatusOptions): LinkStatusChip;
|
|
52
|
+
|
|
53
|
+
export { type LinkStatus, type LinkStatusChip, type LinkStatusClass, type LinkStatusOptions, linkAddress, linkStatusClass, linkStatusTip, mountLinkStatus };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"link-status.js"}
|
package/dist/menu.cjs
CHANGED
|
@@ -5,7 +5,8 @@ var EDIT_MENU = {
|
|
|
5
5
|
undo: { label: "Undo", accelerator: "CmdOrCtrl+Z" },
|
|
6
6
|
redo: { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z" },
|
|
7
7
|
duplicate: { label: "Duplicate", accelerator: "CmdOrCtrl+D" },
|
|
8
|
-
find: { label: "Find\u2026", accelerator: "CmdOrCtrl+F" }
|
|
8
|
+
find: { label: "Find\u2026", accelerator: "CmdOrCtrl+F" },
|
|
9
|
+
replace: { label: "Replace\u2026", acceleratorMac: "Cmd+Alt+F", acceleratorOther: "Ctrl+H" }
|
|
9
10
|
};
|
|
10
11
|
var GO_MENU = {
|
|
11
12
|
back: { label: "Back", acceleratorMac: "Ctrl+Cmd+Left", acceleratorOther: "Alt+Left" },
|
|
@@ -39,12 +40,72 @@ function namedMenuItems(naming) {
|
|
|
39
40
|
}
|
|
40
41
|
};
|
|
41
42
|
}
|
|
43
|
+
var FILE_MENU = {
|
|
44
|
+
newProject: { label: "New Project\u2026", accelerator: "CmdOrCtrl+N" },
|
|
45
|
+
openProject: { label: "Open Project\u2026", accelerator: "CmdOrCtrl+O" },
|
|
46
|
+
openRecent: { label: "Open Recent" },
|
|
47
|
+
noRecents: { label: "No Recent Projects" },
|
|
48
|
+
clearRecents: { label: "Clear Recents" },
|
|
49
|
+
closeProject: { label: "Close Project" },
|
|
50
|
+
save: { label: "Save", accelerator: "CmdOrCtrl+S" },
|
|
51
|
+
saveAs: { label: "Save As\u2026", accelerator: "Shift+CmdOrCtrl+S" },
|
|
52
|
+
projectSettings: { label: "Project Settings\u2026", accelerator: "CmdOrCtrl+," }
|
|
53
|
+
};
|
|
54
|
+
var PLAY_MENU = {
|
|
55
|
+
liveLink: { label: "Live Link" }
|
|
56
|
+
};
|
|
57
|
+
var REVIEW_MENU = {
|
|
58
|
+
reviewFeedback: { label: "Review Feedback", accelerator: "CmdOrCtrl+Shift+R" },
|
|
59
|
+
nextFeedback: { label: "Next Feedback", accelerator: "F8" },
|
|
60
|
+
previousFeedback: { label: "Previous Feedback", accelerator: "Shift+F8" },
|
|
61
|
+
coverageTest: { label: "Coverage Test\u2026", accelerator: "Shift+CmdOrCtrl+C" },
|
|
62
|
+
findPropertyUsage: { label: "Find Property Usage\u2026" },
|
|
63
|
+
showResolvedComments: { label: "Show Resolved Comments" }
|
|
64
|
+
};
|
|
65
|
+
var PUBLISH_MENU = {
|
|
66
|
+
playableHtml: { label: "Publish Playable HTML\u2026" },
|
|
67
|
+
bundle: { label: "Publish Bundle", accelerator: "Shift+CmdOrCtrl+B" },
|
|
68
|
+
autoRebuild: { label: "Auto Rebuild" }
|
|
69
|
+
};
|
|
70
|
+
var VIEW_MENU = {
|
|
71
|
+
projectOverview: { label: "Project Overview" },
|
|
72
|
+
upALevel: { label: "Up a Level", accelerator: "CmdOrCtrl+[" },
|
|
73
|
+
colourTheme: { label: "Colour Theme" }
|
|
74
|
+
};
|
|
75
|
+
function tildePath(path, home) {
|
|
76
|
+
if (path === home) return "~";
|
|
77
|
+
const sep = home.includes("\\") ? "\\" : "/";
|
|
78
|
+
return path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;
|
|
79
|
+
}
|
|
80
|
+
function recentsSubmenu(recents, opts) {
|
|
81
|
+
const isMac = (opts.platform ?? process.platform) === "darwin";
|
|
82
|
+
if (recents.length === 0) return [{ label: FILE_MENU.noRecents.label, enabled: false }];
|
|
83
|
+
const items = recents.map((r) => {
|
|
84
|
+
const shown = tildePath(r.path, opts.home);
|
|
85
|
+
const name = r.name ?? r.path.split(/[\\/]/).filter(Boolean).pop() ?? r.path;
|
|
86
|
+
return {
|
|
87
|
+
label: isMac ? name : `${name} (${shown})`,
|
|
88
|
+
sublabel: shown,
|
|
89
|
+
toolTip: r.path,
|
|
90
|
+
click: () => opts.onOpen(r.path)
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
if (opts.onClear) items.push({ type: "separator" }, { label: FILE_MENU.clearRecents.label, click: opts.onClear });
|
|
94
|
+
return items;
|
|
95
|
+
}
|
|
42
96
|
|
|
43
97
|
exports.APP_MENU = APP_MENU;
|
|
44
98
|
exports.EDIT_MENU = EDIT_MENU;
|
|
99
|
+
exports.FILE_MENU = FILE_MENU;
|
|
45
100
|
exports.GO_MENU = GO_MENU;
|
|
46
101
|
exports.HELP_MENU = HELP_MENU;
|
|
47
102
|
exports.PANE_MENU = PANE_MENU;
|
|
103
|
+
exports.PLAY_MENU = PLAY_MENU;
|
|
104
|
+
exports.PUBLISH_MENU = PUBLISH_MENU;
|
|
105
|
+
exports.REVIEW_MENU = REVIEW_MENU;
|
|
106
|
+
exports.VIEW_MENU = VIEW_MENU;
|
|
48
107
|
exports.namedMenuItems = namedMenuItems;
|
|
108
|
+
exports.recentsSubmenu = recentsSubmenu;
|
|
109
|
+
exports.tildePath = tildePath;
|
|
49
110
|
//# sourceMappingURL=menu.cjs.map
|
|
50
111
|
//# sourceMappingURL=menu.cjs.map
|
package/dist/menu.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/menu.ts"],"names":[],"mappings":";;;AA2BO,IAAM,SAAA,GAKT;AAAA,EACF,IAAA,EAAM,EAAE,KAAA,EAAO,MAAA,EAAQ,aAAa,aAAA,EAAc;AAAA,EAClD,IAAA,EAAM,EAAE,KAAA,EAAO,MAAA,EAAQ,aAAa,mBAAA,EAAoB;AAAA,EACxD,SAAA,EAAW,EAAE,KAAA,EAAO,WAAA,EAAa,aAAa,aAAA,EAAc;AAAA,EAC5D,IAAA,EAAM,EAAE,KAAA,EAAO,YAAA,EAAS,aAAa,aAAA;AACvC;AASO,IAAM,OAAA,GAGT;AAAA,EACF,MAAM,EAAE,KAAA,EAAO,QAAQ,cAAA,EAAgB,eAAA,EAAiB,kBAAkB,UAAA,EAAW;AAAA,EACrF,SAAS,EAAE,KAAA,EAAO,WAAW,cAAA,EAAgB,gBAAA,EAAkB,kBAAkB,WAAA;AACnF;AAGO,IAAM,SAAA,GAIT;AAAA,EACF,OAAA,EAAS,EAAE,KAAA,EAAO,gBAAA,EAAkB,aAAa,aAAA,EAAc;AAAA,EAC/D,aAAA,EAAe,EAAE,KAAA,EAAO,gBAAA,EAAkB,aAAa,aAAA,EAAc;AAAA,EACrE,SAAA,EAAW,EAAE,KAAA,EAAO,YAAA;AACtB;AAkBO,IAAM,SAAA,GAET;AAAA,EACF,eAAA,EAAiB,EAAE,KAAA,EAAO,yBAAA;AAC5B;AAGO,IAAM,QAAA,GAET;AAAA;AAAA,EAEF,QAAA,EAAU,EAAE,KAAA,EAAO,wBAAA;AACrB;AA+BO,SAAS,eAAe,MAAA,EAI7B;AACA,EAAA,MAAM,KAAA,GAAQ,OAAO,SAAA,IAAa,QAAA;AAClC,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,EAAE,KAAA,EAAO,CAAA,MAAA,EAAS,OAAO,OAAO,CAAA,CAAA,EAAI,OAAO,IAAA,EAAK;AAAA,IACvD,IAAA,EAAM;AAAA,MACJ,KAAA,EAAO,CAAA,EAAG,MAAA,CAAO,OAAO,CAAA,cAAA,CAAA;AAAA,MACxB,GAAI,OAAO,OAAA,GAAU,EAAE,KAAK,MAAA,CAAO,OAAA,KAAY,EAAC;AAAA,MAChD,KAAA,EAAO,OAAO,OAAA,KAAY;AAAA,KAC5B;AAAA,IACA,SAAA,EAAW;AAAA,MACT,KAAA,EAAO,GAAG,KAAK,CAAA,mBAAA,CAAA;AAAA,MACf,GAAI,OAAO,YAAA,GAAe,EAAE,KAAK,MAAA,CAAO,YAAA,KAAiB,EAAC;AAAA,MAC1D,KAAA,EAAO,OAAO,YAAA,KAAiB;AAAA;AACjC,GACF;AACF","file":"menu.cjs","sourcesContent":["// ---------------------------------------------------------------------------\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/**\n * Back / Forward: navigation HISTORY, the other axis from Up a Level's\n * hierarchy. One string cannot serve both platforms - the mac pair is Xcode's\n * (Cmd+[ is already Up a Level in this family, so the browser pair is taken),\n * and the Alt pair is what every Windows browser and Explorer trained - so the\n * menu builder picks by platform.\n */\nexport const GO_MENU: {\n back: { label: string; acceleratorMac: string; acceleratorOther: string };\n forward: { label: string; acceleratorMac: string; acceleratorOther: string };\n} = {\n back: { label: \"Back\", acceleratorMac: \"Ctrl+Cmd+Left\", acceleratorOther: \"Alt+Left\" },\n forward: { label: \"Forward\", acceleratorMac: \"Ctrl+Cmd+Right\", acceleratorOther: \"Alt+Right\" },\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/**\n * The Help menu items the family shares, and the one platform rule that goes\n * with them.\n *\n * Lifted from Patterpad, which is the only app that has ever had these: a\n * second app writing its own updater or About is pure waste (`design/\n * shared-shell.md`), and a suite is recognised by finding Check for Updates\n * where you left it. Storyletter will grow both; until it does, these labels\n * are the agreement rather than a description of two implementations.\n *\n * PLACEMENT, which differs by platform and is easy to get wrong:\n * - macOS keeps About in the APP menu (the one named after the product), and\n * Help carries documentation + Check for Updates only.\n * - Windows and Linux have no app menu, so About goes at the foot of Help,\n * after a separator. That is the conventional home there.\n */\nexport const HELP_MENU: {\n checkForUpdates: MenuLabel;\n} = {\n checkForUpdates: { label: \"Check for Updates\\u2026\" },\n};\n\n/** The app-menu items the family shares (macOS; the identity item also belongs in File elsewhere). */\nexport const APP_MENU: {\n userInfo: MenuLabel;\n} = {\n /** Name + optional email, used to sign edits and comments. One label across the suite. */\n userInfo: { label: \"User Information\\u2026\" },\n};\n\n/** What an app must tell the shell to build its named menu items. */\nexport interface MenuNaming {\n /** The product, as it appears in menus: \"Patterpad\", \"Storyletter\". */\n appName: string;\n /** The product's own documentation URL. Omit while an app has no docs yet and\n * the item is rendered disabled rather than opening a broken link. */\n docsUrl?: string;\n /** The suite's documentation home, shared by every app in the family. */\n suiteDocsUrl?: string;\n /** The suite, as it appears in \"<suite> Documentation Home\". Defaults to \"Patter\". */\n suiteName?: string;\n}\n\n/** A named menu item: the label, plus the URL it opens (absent = nothing wired yet). */\nexport interface NamedMenuItem extends MenuLabel {\n url?: string;\n /** False when the app has not supplied a URL, so the builder can render it\n * disabled instead of opening nothing. A placeholder, honestly labelled. */\n ready: boolean;\n}\n\n/**\n * The menu labels that carry a product or suite name, so no app spells its own\n * About or Documentation item differently from the rest of the family.\n *\n * An app with no documentation site yet still gets the ITEM, marked\n * `ready: false`, so the menu has its family shape from day one and wiring the\n * URL later is a one-line change rather than a menu redesign.\n */\nexport function namedMenuItems(naming: MenuNaming): {\n about: NamedMenuItem;\n docs: NamedMenuItem;\n suiteDocs: NamedMenuItem;\n} {\n const suite = naming.suiteName ?? \"Patter\";\n return {\n about: { label: `About ${naming.appName}`, ready: true },\n docs: {\n label: `${naming.appName} Documentation`,\n ...(naming.docsUrl ? { url: naming.docsUrl } : {}),\n ready: naming.docsUrl !== undefined,\n },\n suiteDocs: {\n label: `${suite} Documentation Home`,\n ...(naming.suiteDocsUrl ? { url: naming.suiteDocsUrl } : {}),\n ready: naming.suiteDocsUrl !== undefined,\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/menu.ts"],"names":[],"mappings":"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","file":"menu.cjs","sourcesContent":["// ---------------------------------------------------------------------------\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\nimport type { RecentProject } from \"./app-store.js\";\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 /** Replace is Find's second tab, on the platform's key for it. */\n replace: { label: string; acceleratorMac: string; acceleratorOther: string };\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 replace: { label: \"Replace…\", acceleratorMac: \"Cmd+Alt+F\", acceleratorOther: \"Ctrl+H\" },\n};\n\n/**\n * Back / Forward: navigation HISTORY, the other axis from Up a Level's\n * hierarchy. One string cannot serve both platforms - the mac pair is Xcode's\n * (Cmd+[ is already Up a Level in this family, so the browser pair is taken),\n * and the Alt pair is what every Windows browser and Explorer trained - so the\n * menu builder picks by platform.\n */\nexport const GO_MENU: {\n back: { label: string; acceleratorMac: string; acceleratorOther: string };\n forward: { label: string; acceleratorMac: string; acceleratorOther: string };\n} = {\n back: { label: \"Back\", acceleratorMac: \"Ctrl+Cmd+Left\", acceleratorOther: \"Alt+Left\" },\n forward: { label: \"Forward\", acceleratorMac: \"Ctrl+Cmd+Right\", acceleratorOther: \"Alt+Right\" },\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/**\n * The Help menu items the family shares, and the one platform rule that goes\n * with them.\n *\n * Lifted from Patterpad, which is the only app that has ever had these: a\n * second app writing its own updater or About is pure waste (`design/\n * shared-shell.md`), and a suite is recognised by finding Check for Updates\n * where you left it. Storyletter will grow both; until it does, these labels\n * are the agreement rather than a description of two implementations.\n *\n * PLACEMENT, which differs by platform and is easy to get wrong:\n * - macOS keeps About in the APP menu (the one named after the product), and\n * Help carries documentation + Check for Updates only.\n * - Windows and Linux have no app menu, so About goes at the foot of Help,\n * after a separator. That is the conventional home there.\n */\nexport const HELP_MENU: {\n checkForUpdates: MenuLabel;\n} = {\n checkForUpdates: { label: \"Check for Updates\\u2026\" },\n};\n\n/** The app-menu items the family shares (macOS; the identity item also belongs in File elsewhere). */\nexport const APP_MENU: {\n userInfo: MenuLabel;\n} = {\n /** Name + optional email, used to sign edits and comments. One label across the suite. */\n userInfo: { label: \"User Information\\u2026\" },\n};\n\n/** What an app must tell the shell to build its named menu items. */\nexport interface MenuNaming {\n /** The product, as it appears in menus: \"Patterpad\", \"Storyletter\". */\n appName: string;\n /** The product's own documentation URL. Omit while an app has no docs yet and\n * the item is rendered disabled rather than opening a broken link. */\n docsUrl?: string;\n /** The suite's documentation home, shared by every app in the family. */\n suiteDocsUrl?: string;\n /** The suite, as it appears in \"<suite> Documentation Home\". Defaults to \"Patter\". */\n suiteName?: string;\n}\n\n/** A named menu item: the label, plus the URL it opens (absent = nothing wired yet). */\nexport interface NamedMenuItem extends MenuLabel {\n url?: string;\n /** False when the app has not supplied a URL, so the builder can render it\n * disabled instead of opening nothing. A placeholder, honestly labelled. */\n ready: boolean;\n}\n\n/**\n * The menu labels that carry a product or suite name, so no app spells its own\n * About or Documentation item differently from the rest of the family.\n *\n * An app with no documentation site yet still gets the ITEM, marked\n * `ready: false`, so the menu has its family shape from day one and wiring the\n * URL later is a one-line change rather than a menu redesign.\n */\nexport function namedMenuItems(naming: MenuNaming): {\n about: NamedMenuItem;\n docs: NamedMenuItem;\n suiteDocs: NamedMenuItem;\n} {\n const suite = naming.suiteName ?? \"Patter\";\n return {\n about: { label: `About ${naming.appName}`, ready: true },\n docs: {\n label: `${naming.appName} Documentation`,\n ...(naming.docsUrl ? { url: naming.docsUrl } : {}),\n ready: naming.docsUrl !== undefined,\n },\n suiteDocs: {\n label: `${suite} Documentation Home`,\n ...(naming.suiteDocsUrl ? { url: naming.suiteDocsUrl } : {}),\n ready: naming.suiteDocsUrl !== undefined,\n },\n };\n}\n\n// --- the rest of the spine (ui-review-2026-09, finding 12) -----------------------\n// Each app used roughly half of what is above and hand-typed the other half,\n// and the copies had already drifted (Review Feedback's accelerator spelled\n// two ways; \"No Recent Projects\" vs \"No recent projects\"). Labels are Title\n// Case, as the platform's menus are; an item that opens something ends in the\n// command ellipsis.\n\n/** The File menu items the family shares. */\nexport const FILE_MENU: {\n newProject: MenuLabel;\n openProject: MenuLabel;\n openRecent: MenuLabel;\n /** The empty Open Recent submenu's one disabled item. */\n noRecents: MenuLabel;\n clearRecents: MenuLabel;\n closeProject: MenuLabel;\n save: MenuLabel;\n saveAs: MenuLabel;\n projectSettings: MenuLabel;\n} = {\n newProject: { label: \"New Project…\", accelerator: \"CmdOrCtrl+N\" },\n openProject: { label: \"Open Project…\", accelerator: \"CmdOrCtrl+O\" },\n openRecent: { label: \"Open Recent\" },\n noRecents: { label: \"No Recent Projects\" },\n clearRecents: { label: \"Clear Recents\" },\n closeProject: { label: \"Close Project\" },\n save: { label: \"Save\", accelerator: \"CmdOrCtrl+S\" },\n saveAs: { label: \"Save As…\", accelerator: \"Shift+CmdOrCtrl+S\" },\n projectSettings: { label: \"Project Settings…\", accelerator: \"CmdOrCtrl+,\" },\n};\n\n/**\n * The Play menu's shared item. The play SURFACE itself (Patterpad's \"Play\n * Scene\" on Cmd+P, Storyletter's \"The Board\" on Cmd+T) is app-named and\n * app-keyed on purpose: the two are not the same act, so the family reserves\n * neither key.\n */\nexport const PLAY_MENU: {\n liveLink: MenuLabel;\n} = {\n liveLink: { label: \"Live Link\" },\n};\n\n/** The Review menu: examining what you have built rather than building or\n * playing it. Review Feedback's accelerator is spelled ONCE, here. */\nexport const REVIEW_MENU: {\n reviewFeedback: MenuLabel;\n nextFeedback: MenuLabel;\n previousFeedback: MenuLabel;\n coverageTest: MenuLabel;\n findPropertyUsage: MenuLabel;\n showResolvedComments: MenuLabel;\n} = {\n reviewFeedback: { label: \"Review Feedback\", accelerator: \"CmdOrCtrl+Shift+R\" },\n nextFeedback: { label: \"Next Feedback\", accelerator: \"F8\" },\n previousFeedback: { label: \"Previous Feedback\", accelerator: \"Shift+F8\" },\n coverageTest: { label: \"Coverage Test…\", accelerator: \"Shift+CmdOrCtrl+C\" },\n findPropertyUsage: { label: \"Find Property Usage…\" },\n showResolvedComments: { label: \"Show Resolved Comments\" },\n};\n\n/** The Publish menu: everything that turns the project into something you\n * hand to others (\"Publish\", never \"Export\"). */\nexport const PUBLISH_MENU: {\n playableHtml: MenuLabel;\n bundle: MenuLabel;\n autoRebuild: MenuLabel;\n} = {\n playableHtml: { label: \"Publish Playable HTML…\" },\n bundle: { label: \"Publish Bundle\", accelerator: \"Shift+CmdOrCtrl+B\" },\n autoRebuild: { label: \"Auto Rebuild\" },\n};\n\n/** The View menu items beyond the pane toggles (PANE_MENU) and history\n * (GO_MENU). Cmd+[ is family-reserved for Up a Level, which is why Back\n * takes Xcode's pair on the mac. */\nexport const VIEW_MENU: {\n projectOverview: MenuLabel;\n upALevel: MenuLabel;\n colourTheme: MenuLabel;\n} = {\n projectOverview: { label: \"Project Overview\" },\n upALevel: { label: \"Up a Level\", accelerator: \"CmdOrCtrl+[\" },\n colourTheme: { label: \"Colour Theme\" },\n};\n\n/** A path with the home directory as `~`, so a recents entry stays readable\n * at menu width. `home` is `app.getPath(\"home\")`; this file stays\n * electron-free. */\nexport function tildePath(path: string, home: string): string {\n if (path === home) return \"~\";\n const sep = home.includes(\"\\\\\") ? \"\\\\\" : \"/\";\n return path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;\n}\n\n/** As much of Electron's MenuItemConstructorOptions as a recents entry needs;\n * structurally assignable to it. */\nexport interface RecentsMenuItem {\n label?: string;\n sublabel?: string;\n toolTip?: string;\n enabled?: boolean;\n type?: \"separator\";\n click?: () => void;\n}\n\nexport interface RecentsSubmenuOptions {\n onOpen: (path: string) => void;\n /** Offered after a separator when there is anything to clear. */\n onClear?: () => void;\n /** `app.getPath(\"home\")`, for the `~` abbreviation. */\n home: string;\n /** Defaults to process.platform; a seam for the tests. */\n platform?: string;\n}\n\n/**\n * The Open Recent submenu: the fifteen lines both apps had.\n *\n * Shows WHERE each recent lives. macOS renders `sublabel` as a dimmed second\n * line and `toolTip` on hover; native menus elsewhere render neither, so\n * there the path is folded into the label itself. Empty, it is the one\n * disabled \"No Recent Projects\" item.\n */\nexport function recentsSubmenu(recents: RecentProject[], opts: RecentsSubmenuOptions): RecentsMenuItem[] {\n const isMac = (opts.platform ?? process.platform) === \"darwin\";\n if (recents.length === 0) return [{ label: FILE_MENU.noRecents.label, enabled: false }];\n const items: RecentsMenuItem[] = recents.map((r) => {\n const shown = tildePath(r.path, opts.home);\n const name = r.name ?? r.path.split(/[\\\\/]/).filter(Boolean).pop() ?? r.path;\n return {\n label: isMac ? name : `${name} (${shown})`,\n sublabel: shown,\n toolTip: r.path,\n click: () => opts.onOpen(r.path),\n };\n });\n if (opts.onClear) items.push({ type: \"separator\" }, { label: FILE_MENU.clearRecents.label, click: opts.onClear });\n return items;\n}\n"]}
|
package/dist/menu.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { RecentProject } from './app-store.cjs';
|
|
2
|
+
|
|
1
3
|
interface MenuLabel {
|
|
2
4
|
label: string;
|
|
3
5
|
/** Electron accelerator string, when the item has one. */
|
|
@@ -21,6 +23,12 @@ declare const EDIT_MENU: {
|
|
|
21
23
|
redo: MenuLabel;
|
|
22
24
|
duplicate: MenuLabel;
|
|
23
25
|
find: MenuLabel;
|
|
26
|
+
/** Replace is Find's second tab, on the platform's key for it. */
|
|
27
|
+
replace: {
|
|
28
|
+
label: string;
|
|
29
|
+
acceleratorMac: string;
|
|
30
|
+
acceleratorOther: string;
|
|
31
|
+
};
|
|
24
32
|
};
|
|
25
33
|
/**
|
|
26
34
|
* Back / Forward: navigation HISTORY, the other axis from Up a Level's
|
|
@@ -102,5 +110,84 @@ declare function namedMenuItems(naming: MenuNaming): {
|
|
|
102
110
|
docs: NamedMenuItem;
|
|
103
111
|
suiteDocs: NamedMenuItem;
|
|
104
112
|
};
|
|
113
|
+
/** The File menu items the family shares. */
|
|
114
|
+
declare const FILE_MENU: {
|
|
115
|
+
newProject: MenuLabel;
|
|
116
|
+
openProject: MenuLabel;
|
|
117
|
+
openRecent: MenuLabel;
|
|
118
|
+
/** The empty Open Recent submenu's one disabled item. */
|
|
119
|
+
noRecents: MenuLabel;
|
|
120
|
+
clearRecents: MenuLabel;
|
|
121
|
+
closeProject: MenuLabel;
|
|
122
|
+
save: MenuLabel;
|
|
123
|
+
saveAs: MenuLabel;
|
|
124
|
+
projectSettings: MenuLabel;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* The Play menu's shared item. The play SURFACE itself (Patterpad's "Play
|
|
128
|
+
* Scene" on Cmd+P, Storyletter's "The Board" on Cmd+T) is app-named and
|
|
129
|
+
* app-keyed on purpose: the two are not the same act, so the family reserves
|
|
130
|
+
* neither key.
|
|
131
|
+
*/
|
|
132
|
+
declare const PLAY_MENU: {
|
|
133
|
+
liveLink: MenuLabel;
|
|
134
|
+
};
|
|
135
|
+
/** The Review menu: examining what you have built rather than building or
|
|
136
|
+
* playing it. Review Feedback's accelerator is spelled ONCE, here. */
|
|
137
|
+
declare const REVIEW_MENU: {
|
|
138
|
+
reviewFeedback: MenuLabel;
|
|
139
|
+
nextFeedback: MenuLabel;
|
|
140
|
+
previousFeedback: MenuLabel;
|
|
141
|
+
coverageTest: MenuLabel;
|
|
142
|
+
findPropertyUsage: MenuLabel;
|
|
143
|
+
showResolvedComments: MenuLabel;
|
|
144
|
+
};
|
|
145
|
+
/** The Publish menu: everything that turns the project into something you
|
|
146
|
+
* hand to others ("Publish", never "Export"). */
|
|
147
|
+
declare const PUBLISH_MENU: {
|
|
148
|
+
playableHtml: MenuLabel;
|
|
149
|
+
bundle: MenuLabel;
|
|
150
|
+
autoRebuild: MenuLabel;
|
|
151
|
+
};
|
|
152
|
+
/** The View menu items beyond the pane toggles (PANE_MENU) and history
|
|
153
|
+
* (GO_MENU). Cmd+[ is family-reserved for Up a Level, which is why Back
|
|
154
|
+
* takes Xcode's pair on the mac. */
|
|
155
|
+
declare const VIEW_MENU: {
|
|
156
|
+
projectOverview: MenuLabel;
|
|
157
|
+
upALevel: MenuLabel;
|
|
158
|
+
colourTheme: MenuLabel;
|
|
159
|
+
};
|
|
160
|
+
/** A path with the home directory as `~`, so a recents entry stays readable
|
|
161
|
+
* at menu width. `home` is `app.getPath("home")`; this file stays
|
|
162
|
+
* electron-free. */
|
|
163
|
+
declare function tildePath(path: string, home: string): string;
|
|
164
|
+
/** As much of Electron's MenuItemConstructorOptions as a recents entry needs;
|
|
165
|
+
* structurally assignable to it. */
|
|
166
|
+
interface RecentsMenuItem {
|
|
167
|
+
label?: string;
|
|
168
|
+
sublabel?: string;
|
|
169
|
+
toolTip?: string;
|
|
170
|
+
enabled?: boolean;
|
|
171
|
+
type?: "separator";
|
|
172
|
+
click?: () => void;
|
|
173
|
+
}
|
|
174
|
+
interface RecentsSubmenuOptions {
|
|
175
|
+
onOpen: (path: string) => void;
|
|
176
|
+
/** Offered after a separator when there is anything to clear. */
|
|
177
|
+
onClear?: () => void;
|
|
178
|
+
/** `app.getPath("home")`, for the `~` abbreviation. */
|
|
179
|
+
home: string;
|
|
180
|
+
/** Defaults to process.platform; a seam for the tests. */
|
|
181
|
+
platform?: string;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The Open Recent submenu: the fifteen lines both apps had.
|
|
185
|
+
*
|
|
186
|
+
* Shows WHERE each recent lives. macOS renders `sublabel` as a dimmed second
|
|
187
|
+
* line and `toolTip` on hover; native menus elsewhere render neither, so
|
|
188
|
+
* there the path is folded into the label itself. Empty, it is the one
|
|
189
|
+
* disabled "No Recent Projects" item.
|
|
190
|
+
*/
|
|
191
|
+
declare function recentsSubmenu(recents: RecentProject[], opts: RecentsSubmenuOptions): RecentsMenuItem[];
|
|
105
192
|
|
|
106
|
-
export { APP_MENU, EDIT_MENU, GO_MENU, HELP_MENU, type MenuLabel, type MenuNaming, type NamedMenuItem, PANE_MENU, namedMenuItems };
|
|
193
|
+
export { APP_MENU, EDIT_MENU, FILE_MENU, GO_MENU, HELP_MENU, type MenuLabel, type MenuNaming, type NamedMenuItem, PANE_MENU, PLAY_MENU, PUBLISH_MENU, REVIEW_MENU, type RecentsMenuItem, type RecentsSubmenuOptions, VIEW_MENU, namedMenuItems, recentsSubmenu, tildePath };
|