@wildwinter/app-shell 0.46.5 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSES.md +5 -4
  2. package/dist/{chunk-J6RSONXM.js → chunk-62KWJPRP.js} +4 -4
  3. package/dist/{chunk-J6RSONXM.js.map → chunk-62KWJPRP.js.map} +1 -1
  4. package/dist/{chunk-WKPKBAXS.js → chunk-C245WRPF.js} +4 -4
  5. package/dist/{chunk-WKPKBAXS.js.map → chunk-C245WRPF.js.map} +1 -1
  6. package/dist/{chunk-R3NXPHRZ.js → chunk-CRUILFAT.js} +28 -6
  7. package/dist/chunk-CRUILFAT.js.map +1 -0
  8. package/dist/{chunk-EHR2KBZC.js → chunk-OMG6A5YB.js} +3 -3
  9. package/dist/{chunk-EHR2KBZC.js.map → chunk-OMG6A5YB.js.map} +1 -1
  10. package/dist/{chunk-2ECJUTSX.js → chunk-T25WXWPB.js} +3 -2
  11. package/dist/chunk-T25WXWPB.js.map +1 -0
  12. package/dist/{chunk-B2TC6Q5J.js → chunk-UQTUCOGV.js} +3 -3
  13. package/dist/{chunk-B2TC6Q5J.js.map → chunk-UQTUCOGV.js.map} +1 -1
  14. package/dist/{chunk-YRFJLJUH.js → chunk-VXEFIL43.js} +3 -3
  15. package/dist/{chunk-YRFJLJUH.js.map → chunk-VXEFIL43.js.map} +1 -1
  16. package/dist/{chunk-UG2P777F.js → chunk-W4WC4FO5.js} +102 -30
  17. package/dist/chunk-W4WC4FO5.js.map +1 -0
  18. package/dist/{chunk-CUFREPJ5.js → chunk-XSS7KM6F.js} +4 -4
  19. package/dist/{chunk-CUFREPJ5.js.map → chunk-XSS7KM6F.js.map} +1 -1
  20. package/dist/context-menu.cjs.map +1 -1
  21. package/dist/context-menu.js +2 -2
  22. package/dist/dialog.cjs.map +1 -1
  23. package/dist/dialog.js +2 -2
  24. package/dist/index.cjs +146 -36
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +97 -14
  27. package/dist/index.d.ts +97 -14
  28. package/dist/index.js +31 -17
  29. package/dist/index.js.map +1 -1
  30. package/dist/kit-gallery.cjs.map +1 -1
  31. package/dist/kit-gallery.js +3 -3
  32. package/dist/link-status.cjs +7 -0
  33. package/dist/link-status.cjs.map +1 -1
  34. package/dist/link-status.js +2 -2
  35. package/dist/menu.cjs +1 -0
  36. package/dist/menu.cjs.map +1 -1
  37. package/dist/menu.d.cts +8 -4
  38. package/dist/menu.d.ts +8 -4
  39. package/dist/menu.js +1 -1
  40. package/dist/{property-list-D3VZCJzi.d.cts → property-list-BgZlkcbz.d.cts} +19 -2
  41. package/dist/{property-list-D3VZCJzi.d.ts → property-list-BgZlkcbz.d.ts} +19 -2
  42. package/dist/property-list.cjs +93 -27
  43. package/dist/property-list.cjs.map +1 -1
  44. package/dist/property-list.d.cts +1 -1
  45. package/dist/property-list.d.ts +1 -1
  46. package/dist/property-list.js +2 -2
  47. package/dist/settings.css +78 -0
  48. package/dist/settings.css.map +1 -1
  49. package/dist/updater-view.cjs.map +1 -1
  50. package/dist/updater-view.js +3 -3
  51. package/dist/welcome.cjs.map +1 -1
  52. package/dist/welcome.js +4 -4
  53. package/package.json +1 -1
  54. package/dist/chunk-2ECJUTSX.js.map +0 -1
  55. package/dist/chunk-R3NXPHRZ.js.map +0 -1
  56. package/dist/chunk-UG2P777F.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { EDIT_MENU, FILE_MENU, GO_MENU, MenuLabel, PANE_MENU, PLAY_MENU, PUBLISH_MENU, REVIEW_MENU, VIEW_MENU } from './menu.cjs';
2
2
  export { HintBarItem, KeyHintOptions, KeyPlatform, hintBar, isKeyCombo, keyHint, keyLabel, keyLegends, keyPlatform, setKeyPlatform, tipWithKey } from './keys.cjs';
3
3
  export { ProblemCopy, ProblemCopyEntry, ProblemCopyTable, ProblemLike, defaultProblemCopy, describeProblem, problemLine, problemName } from './problems.cjs';
4
- export { D as DefaultControlOptions, a as DupGuard, P as PROPERTY_TYPES, b as PropertyDeclLike, c as PropertyListHandle, d as PropertyListOptions, e as PropertyListType, S as SettingsDialog, f as SettingsDialogOptions, g as SettingsSection, h as SettingsSectionHandle, i as defaultControl, j as dupGuard, k as expandableRow, l as focusNewRow, m as mountPropertyList, n as mountSettingsDialog, r as revealRow, o as revealRowWhenReady } from './property-list-D3VZCJzi.cjs';
4
+ export { D as DefaultControlOptions, a as DupGuard, P as PROPERTY_TYPES, b as PropertyDeclLike, c as PropertyListHandle, d as PropertyListOptions, e as PropertyListType, S as SettingsDialog, f as SettingsDialogOptions, g as SettingsSection, h as SettingsSectionHandle, i as defaultControl, j as dupGuard, k as expandableRow, l as focusNewRow, m as mountPropertyList, n as mountSettingsDialog, r as revealRow, o as revealRowWhenReady } from './property-list-BgZlkcbz.cjs';
5
5
  export { DialogFrame, DialogFrameOptions, dialogFrame } from './dialog.cjs';
6
6
  export { Floating, FloatingOptions, createFloating } from './floating.cjs';
7
7
  export { ContextItem, PopoverOptions, openContextMenu, openPopover } from './context-menu.cjs';
@@ -87,7 +87,9 @@ declare const icon: {
87
87
  };
88
88
  /**
89
89
  * Every word in the vocabulary. The 26 the glyph table had, plus the seven the
90
- * 2026-09 review found the apps drawing by hand with nothing to check against.
90
+ * 2026-09 review found the apps drawing by hand with nothing to check against,
91
+ * and the two the 2026-10 Patterpad review found the same way (`copy`,
92
+ * `speaker`).
91
93
  */
92
94
  type IconName = keyof typeof icon
93
95
  /** History back, the topbar's quiet arrow pair (nav-history.ts). An ARROW,
@@ -107,7 +109,13 @@ type IconName = keyof typeof icon
107
109
  | "settings"
108
110
  /** Record. The icon is the DISC; painting it in the danger colour while
109
111
  * recording is the caller's job, through the button's own colour. */
110
- | "record";
112
+ | "record"
113
+ /** Copy this to the clipboard. The confirmation is `tick`, shown by the
114
+ * caller while `copyWithFeedback` holds `.copied` on the button. */
115
+ | "copy"
116
+ /** Sound: play with audio, a table read. The act of hearing, not a volume
117
+ * control. */
118
+ | "speaker";
111
119
  /**
112
120
  * The drawings as strings, by word, for a caller that builds markup itself.
113
121
  * Prefer `iconHtml(name, size)`, which sizes it; these carry no width/height.
@@ -128,6 +136,7 @@ declare const iconSvg: {
128
136
  readonly noteOutline: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.1\" stroke-linejoin=\"round\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M3.4 2h4.9l3.9 3.9v7.5a1 1 0 0 1-1 1H3.4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1Z\"/><path d=\"M8.3 2.2v3.8h3.8\"/><path d=\"M4.9 8h6.2M4.9 10h6.2M4.9 12h3.7\" stroke-width=\"0.95\"/></svg>";
129
137
  readonly search: string;
130
138
  readonly close: string;
139
+ readonly copy: string;
131
140
  readonly play: string;
132
141
  readonly more: string;
133
142
  readonly collapsed: string;
@@ -159,6 +168,7 @@ declare const iconSvg: {
159
168
  readonly pin: string;
160
169
  readonly settings: string;
161
170
  readonly record: string;
171
+ readonly speaker: string;
162
172
  };
163
173
  type IconSvgName = keyof typeof iconSvg;
164
174
  /** Every word, in table order, for a test or a gallery to walk. */
@@ -205,8 +215,13 @@ declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, classNameOrPr
205
215
  * call sites yet still gets the drawn set; any other string is set as text,
206
216
  * which is the old behaviour and on its way out. */
207
217
  declare function iconBtn(name: IconName | (string & {}), title: string, onClick: () => void, disabled?: boolean, danger?: boolean): HTMLButtonElement;
218
+ /** The sentence under a settings row: plain text, or nodes when it carries
219
+ * markup (a `code` for a file name, a `b` for an option's name). */
220
+ type FieldHint = string | Node | Array<string | Node>;
208
221
  /**
209
- * A captioned field: `<label class="shell-labelled"><span class="shell-fieldcap">…</span>control</label>`.
222
+ * A captioned field, the family's settings row (the 2026-10 review, ruling C):
223
+ * the caption on the left and the control inline beside it,
224
+ * `<label class="shell-labelled"><span class="shell-fieldcap">…</span>control</label>`.
210
225
  *
211
226
  * The label is pointed at the field it captions, EXPLICITLY, and never at a button. A `<label>` with no
212
227
  * `for` forwards a click to its first labelable descendant, and buttons are labelable - so a caption
@@ -214,12 +229,44 @@ declare function iconBtn(name: IconName | (string & {}), title: string, onClick:
214
229
  * turns every click on the row's dead space into a press of the FIRST button in it. That shipped:
215
230
  * patterkit/patter#44, where clicking a Game Data list value anywhere but its ✕ deleted the first value
216
231
  * in the list. Reported as "clicking a value removes the wrong one", which is not a wrong-index bug at
217
- * all - the click never reached the chip.
232
+ * all - the click never reached the chip. The same rule makes a control GROUP safe to caption: a field
233
+ * with its own Choose… or Import… button beside it (`.shell-inline`) captions the field, and the button
234
+ * stays a button.
218
235
  *
219
236
  * When the control holds no labelable field (a chips editor with the add input removed, say), this is a
220
237
  * plain `<div>`: no caption behaviour is better than a caption that presses something.
238
+ *
239
+ * With a `hint`, the row and the sentence under it come back together in a `.shell-field`, the hint as
240
+ * the field's description (`aria-describedby`) rather than inside the label, where it would become part
241
+ * of the field's NAME and be read out every time it takes focus. Without one, the row is exactly what it
242
+ * always was.
243
+ */
244
+ declare function labelled(label: string, control: HTMLElement, hint?: FieldHint): HTMLElement;
245
+ interface LabelledToggleOptions {
246
+ checked?: boolean;
247
+ /** The sentence under the row: what turning it on does. */
248
+ hint?: FieldHint;
249
+ /** An option that only means something while the row above it is set (Patterpad's "Embed source
250
+ * language for debug" under an IDs-only build): indented under that row with a rule, so it reads as
251
+ * belonging to it rather than as a peer. */
252
+ sub?: boolean;
253
+ disabled?: boolean;
254
+ /** Fired on every flip, with the new state. The input is handed back too, for a caller that reads it
255
+ * on Save instead. */
256
+ onChange?: (checked: boolean) => void;
257
+ }
258
+ /**
259
+ * The settings toggle, `labelled`'s partner (ruling C): the caption on the left, the checkbox beside it
260
+ * where any other row has its control, and the optional hint under the row. One shape in both apps, so
261
+ * a switch in one app's settings looks like a switch in the other's. Clicking the caption flips it, as
262
+ * the label is the checkbox's.
263
+ *
264
+ * Returns the outermost row (to append, hide, or indent) and the input (to read or listen to).
221
265
  */
222
- declare function labelled(label: string, control: HTMLElement): HTMLElement;
266
+ declare function labelledToggle(label: string, opts?: LabelledToggleOptions): {
267
+ row: HTMLElement;
268
+ input: HTMLInputElement;
269
+ };
223
270
  /** Swap item `i` with its neighbour `i + delta` IN PLACE (the up/down reorder list editors share);
224
271
  * a no-op when the target is out of range. Returns whether anything moved. */
225
272
  declare function moveItem<T>(arr: T[], i: number, delta: number): boolean;
@@ -229,12 +276,28 @@ declare function moveItem<T>(arr: T[], i: number, delta: number): boolean;
229
276
  declare function tagChips(holder: {
230
277
  values?: string[];
231
278
  }, onChange?: () => void): HTMLElement;
279
+ /** What `stageChips` tells its caller beyond "something changed". */
280
+ interface StageChipsOptions {
281
+ /**
282
+ * A stage was renamed in place, `from` to `to`. Given, a rename calls this
283
+ * INSTEAD of `onChange`: values are stored by stage name, so whoever knows
284
+ * where those values live has to carry them, and doing it as one edit is
285
+ * theirs to arrange. Absent, a rename is an ordinary change.
286
+ */
287
+ onRename?: (from: string, to: string) => void;
288
+ /** The stages were reordered (after `onChange`): the one edit that changes
289
+ * what every ordering gate means without changing any value. */
290
+ onReorder?: () => void;
291
+ }
232
292
  /**
233
293
  * An ORDERED chips editor for a string-list field whose order means something
234
294
  * (a quality's stages: `advance()` walks them, so 2 comes after 1): numbered
235
295
  * chips, each with move-earlier / move-later / remove, plus an add input
236
- * (Enter or "," commits; blank / duplicate ignored). Mutates `holder.stages`
237
- * in place; `onChange` fires after any add / move / remove.
296
+ * (Enter or "," commits; blank / duplicate ignored). A stage's name renames
297
+ * in place when clicked (Enter or blur commits, Escape cancels; blank, the same
298
+ * name or another stage's is ignored). Mutates `holder.stages` in place;
299
+ * `onChange` fires after any add / move / remove, and after a rename unless
300
+ * `opts.onRename` takes it.
238
301
  *
239
302
  * Patterpad's, lifted (ui-review-2026-09, finding 3); Storyletter had been
240
303
  * faking it with `tagChips` over a `values` holder, which loses the order
@@ -243,7 +306,7 @@ declare function tagChips(holder: {
243
306
  */
244
307
  declare function stageChips(holder: {
245
308
  stages?: string[];
246
- }, onChange?: () => void): HTMLElement;
309
+ }, onChange?: () => void, opts?: StageChipsOptions): HTMLElement;
247
310
  /**
248
311
  * Make `el` a drag source and a drop target for HTML5 drag-to-reorder among
249
312
  * its siblings. While carried it wears `.dragging`; a hovered target wears
@@ -775,7 +838,9 @@ interface DocLine {
775
838
  * what stops a note being copied down and then disagreeing with itself. */
776
839
  interface Notes {
777
840
  own: DocLine[];
778
- inherited: {
841
+ /** Outermost first. Absent or empty in an app whose own UI already shows
842
+ * each ancestor's notes (Patterpad's stacked inspector). */
843
+ inherited?: {
779
844
  id: string;
780
845
  label: string;
781
846
  lines: DocLine[];
@@ -784,13 +849,25 @@ interface Notes {
784
849
  classes: string[];
785
850
  }
786
851
  interface NotesEditorOptions {
787
- /** What this is about, for the dialog's title ("Notes: Arrive at the gate"). */
788
- subject: string;
852
+ /** What this is about, for the dialog's title ("Notes: Arrive at the gate").
853
+ * Absent, the title is "Notes". */
854
+ subject?: string;
855
+ /** One line under the title, for what the classes are FOR in this app
856
+ * (Patterpad's "A VO note travels with the voice script…"): the dialog
857
+ * frame's own sub line. */
858
+ sub?: string;
789
859
  notes: Notes;
860
+ /** Open on this class: its text area is shown even when empty, and the
861
+ * caret is in it. Patterpad's "Needs re-record" asks why the line needs
862
+ * another take, and the answer is a VO note, so it opens on "vo". Ignored
863
+ * when the class is neither offered (`notes.classes`) nor already written
864
+ * on, so a prompt cannot add a class the thing does not take. "" is the
865
+ * editor-only bucket. */
866
+ focus?: string;
790
867
  /** Commit. Called once, on close, and only when something changed. */
791
868
  save: (lines: DocLine[]) => void;
792
869
  }
793
- /** Open the notes modal. Resolves when it closes. */
870
+ /** Open the notes modal. Returns at once; `save` runs when it closes. */
794
871
  declare function openNotesEditor(opts: NotesEditorOptions): void;
795
872
 
796
873
  /** One message. The author is a NAME stamped at posting time, not a reference:
@@ -1135,6 +1212,12 @@ interface NavHistory<Place> {
1135
1212
  * is validating the whole stack on every paint). */
1136
1213
  canBack(): boolean;
1137
1214
  canForward(): boolean;
1215
+ /** Forget both sides, for when the places stop meaning anything: a
1216
+ * different project opened, or the welcome screen with none. Without it
1217
+ * Back walks into the project just closed (Patterpad, 2026-10-07: on the
1218
+ * welcome screen the arrows looked live and did nothing). The caller
1219
+ * re-`set`s its arrows afterwards, as after any other change. */
1220
+ clear(): void;
1138
1221
  }
1139
1222
  declare function createNavHistory<Place>(opts: NavHistoryOptions<Place>): NavHistory<Place>;
1140
1223
  /**
@@ -1158,4 +1241,4 @@ declare function historyNav(onBack: () => void, onForward: () => void): {
1158
1241
  set(canBack: boolean, canForward: boolean): void;
1159
1242
  };
1160
1243
 
1161
- export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type Crumb, type DocLine, type ElProps, type FollowButton, type FollowButtonOptions, type GameIdEditorOptions, ICON_NAMES, ICON_SIZE, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobBeginOptions, type JobProgressOptions, type JobProgressView, type NavHistory, type NavHistoryOptions, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, type PropertyRefOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type ShardVc, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type ToastKind, type ToastOptions, type ToolWindowHeadOptions, type TooltipOptions, type VcBadge, type VcMap, askIdentity, bindPropertyName, bindPropertyRef, breadcrumb, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createNavHistory, createSaveController, dismissToast, el, firstIllegalPropertyName, foldVc, followButton, gameIdify, hideTip, historyNav, icon, iconBtn, iconHtml, iconNameOfGlyph, iconNode, iconSvg, initTooltips, isCaseOnlyPropertyName, isIconName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, metaLine, mountJobProgress, mountPaneShell, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, stageChips, staleBar, tagChips, tipAt, tipBold, toast, toolWindowHead, vcBadgeFor, wireReorder };
1244
+ export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type Crumb, type DocLine, type ElProps, type FieldHint, type FollowButton, type FollowButtonOptions, type GameIdEditorOptions, ICON_NAMES, ICON_SIZE, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobBeginOptions, type JobProgressOptions, type JobProgressView, type LabelledToggleOptions, type NavHistory, type NavHistoryOptions, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, type PropertyRefOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type ShardVc, type StageChipsOptions, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type ToastKind, type ToastOptions, type ToolWindowHeadOptions, type TooltipOptions, type VcBadge, type VcMap, askIdentity, bindPropertyName, bindPropertyRef, breadcrumb, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createNavHistory, createSaveController, dismissToast, el, firstIllegalPropertyName, foldVc, followButton, gameIdify, hideTip, historyNav, icon, iconBtn, iconHtml, iconNameOfGlyph, iconNode, iconSvg, initTooltips, isCaseOnlyPropertyName, isIconName, isValidGameId, isValidPropertyName, labelled, labelledToggle, lockControls, lockNotice, metaLine, mountJobProgress, mountPaneShell, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, stageChips, staleBar, tagChips, tipAt, tipBold, toast, toolWindowHead, vcBadgeFor, wireReorder };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { EDIT_MENU, FILE_MENU, GO_MENU, MenuLabel, PANE_MENU, PLAY_MENU, PUBLISH_MENU, REVIEW_MENU, VIEW_MENU } from './menu.js';
2
2
  export { HintBarItem, KeyHintOptions, KeyPlatform, hintBar, isKeyCombo, keyHint, keyLabel, keyLegends, keyPlatform, setKeyPlatform, tipWithKey } from './keys.js';
3
3
  export { ProblemCopy, ProblemCopyEntry, ProblemCopyTable, ProblemLike, defaultProblemCopy, describeProblem, problemLine, problemName } from './problems.js';
4
- export { D as DefaultControlOptions, a as DupGuard, P as PROPERTY_TYPES, b as PropertyDeclLike, c as PropertyListHandle, d as PropertyListOptions, e as PropertyListType, S as SettingsDialog, f as SettingsDialogOptions, g as SettingsSection, h as SettingsSectionHandle, i as defaultControl, j as dupGuard, k as expandableRow, l as focusNewRow, m as mountPropertyList, n as mountSettingsDialog, r as revealRow, o as revealRowWhenReady } from './property-list-D3VZCJzi.js';
4
+ export { D as DefaultControlOptions, a as DupGuard, P as PROPERTY_TYPES, b as PropertyDeclLike, c as PropertyListHandle, d as PropertyListOptions, e as PropertyListType, S as SettingsDialog, f as SettingsDialogOptions, g as SettingsSection, h as SettingsSectionHandle, i as defaultControl, j as dupGuard, k as expandableRow, l as focusNewRow, m as mountPropertyList, n as mountSettingsDialog, r as revealRow, o as revealRowWhenReady } from './property-list-BgZlkcbz.js';
5
5
  export { DialogFrame, DialogFrameOptions, dialogFrame } from './dialog.js';
6
6
  export { Floating, FloatingOptions, createFloating } from './floating.js';
7
7
  export { ContextItem, PopoverOptions, openContextMenu, openPopover } from './context-menu.js';
@@ -87,7 +87,9 @@ declare const icon: {
87
87
  };
88
88
  /**
89
89
  * Every word in the vocabulary. The 26 the glyph table had, plus the seven the
90
- * 2026-09 review found the apps drawing by hand with nothing to check against.
90
+ * 2026-09 review found the apps drawing by hand with nothing to check against,
91
+ * and the two the 2026-10 Patterpad review found the same way (`copy`,
92
+ * `speaker`).
91
93
  */
92
94
  type IconName = keyof typeof icon
93
95
  /** History back, the topbar's quiet arrow pair (nav-history.ts). An ARROW,
@@ -107,7 +109,13 @@ type IconName = keyof typeof icon
107
109
  | "settings"
108
110
  /** Record. The icon is the DISC; painting it in the danger colour while
109
111
  * recording is the caller's job, through the button's own colour. */
110
- | "record";
112
+ | "record"
113
+ /** Copy this to the clipboard. The confirmation is `tick`, shown by the
114
+ * caller while `copyWithFeedback` holds `.copied` on the button. */
115
+ | "copy"
116
+ /** Sound: play with audio, a table read. The act of hearing, not a volume
117
+ * control. */
118
+ | "speaker";
111
119
  /**
112
120
  * The drawings as strings, by word, for a caller that builds markup itself.
113
121
  * Prefer `iconHtml(name, size)`, which sizes it; these carry no width/height.
@@ -128,6 +136,7 @@ declare const iconSvg: {
128
136
  readonly noteOutline: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.1\" stroke-linejoin=\"round\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M3.4 2h4.9l3.9 3.9v7.5a1 1 0 0 1-1 1H3.4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1Z\"/><path d=\"M8.3 2.2v3.8h3.8\"/><path d=\"M4.9 8h6.2M4.9 10h6.2M4.9 12h3.7\" stroke-width=\"0.95\"/></svg>";
129
137
  readonly search: string;
130
138
  readonly close: string;
139
+ readonly copy: string;
131
140
  readonly play: string;
132
141
  readonly more: string;
133
142
  readonly collapsed: string;
@@ -159,6 +168,7 @@ declare const iconSvg: {
159
168
  readonly pin: string;
160
169
  readonly settings: string;
161
170
  readonly record: string;
171
+ readonly speaker: string;
162
172
  };
163
173
  type IconSvgName = keyof typeof iconSvg;
164
174
  /** Every word, in table order, for a test or a gallery to walk. */
@@ -205,8 +215,13 @@ declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, classNameOrPr
205
215
  * call sites yet still gets the drawn set; any other string is set as text,
206
216
  * which is the old behaviour and on its way out. */
207
217
  declare function iconBtn(name: IconName | (string & {}), title: string, onClick: () => void, disabled?: boolean, danger?: boolean): HTMLButtonElement;
218
+ /** The sentence under a settings row: plain text, or nodes when it carries
219
+ * markup (a `code` for a file name, a `b` for an option's name). */
220
+ type FieldHint = string | Node | Array<string | Node>;
208
221
  /**
209
- * A captioned field: `<label class="shell-labelled"><span class="shell-fieldcap">…</span>control</label>`.
222
+ * A captioned field, the family's settings row (the 2026-10 review, ruling C):
223
+ * the caption on the left and the control inline beside it,
224
+ * `<label class="shell-labelled"><span class="shell-fieldcap">…</span>control</label>`.
210
225
  *
211
226
  * The label is pointed at the field it captions, EXPLICITLY, and never at a button. A `<label>` with no
212
227
  * `for` forwards a click to its first labelable descendant, and buttons are labelable - so a caption
@@ -214,12 +229,44 @@ declare function iconBtn(name: IconName | (string & {}), title: string, onClick:
214
229
  * turns every click on the row's dead space into a press of the FIRST button in it. That shipped:
215
230
  * patterkit/patter#44, where clicking a Game Data list value anywhere but its ✕ deleted the first value
216
231
  * in the list. Reported as "clicking a value removes the wrong one", which is not a wrong-index bug at
217
- * all - the click never reached the chip.
232
+ * all - the click never reached the chip. The same rule makes a control GROUP safe to caption: a field
233
+ * with its own Choose… or Import… button beside it (`.shell-inline`) captions the field, and the button
234
+ * stays a button.
218
235
  *
219
236
  * When the control holds no labelable field (a chips editor with the add input removed, say), this is a
220
237
  * plain `<div>`: no caption behaviour is better than a caption that presses something.
238
+ *
239
+ * With a `hint`, the row and the sentence under it come back together in a `.shell-field`, the hint as
240
+ * the field's description (`aria-describedby`) rather than inside the label, where it would become part
241
+ * of the field's NAME and be read out every time it takes focus. Without one, the row is exactly what it
242
+ * always was.
243
+ */
244
+ declare function labelled(label: string, control: HTMLElement, hint?: FieldHint): HTMLElement;
245
+ interface LabelledToggleOptions {
246
+ checked?: boolean;
247
+ /** The sentence under the row: what turning it on does. */
248
+ hint?: FieldHint;
249
+ /** An option that only means something while the row above it is set (Patterpad's "Embed source
250
+ * language for debug" under an IDs-only build): indented under that row with a rule, so it reads as
251
+ * belonging to it rather than as a peer. */
252
+ sub?: boolean;
253
+ disabled?: boolean;
254
+ /** Fired on every flip, with the new state. The input is handed back too, for a caller that reads it
255
+ * on Save instead. */
256
+ onChange?: (checked: boolean) => void;
257
+ }
258
+ /**
259
+ * The settings toggle, `labelled`'s partner (ruling C): the caption on the left, the checkbox beside it
260
+ * where any other row has its control, and the optional hint under the row. One shape in both apps, so
261
+ * a switch in one app's settings looks like a switch in the other's. Clicking the caption flips it, as
262
+ * the label is the checkbox's.
263
+ *
264
+ * Returns the outermost row (to append, hide, or indent) and the input (to read or listen to).
221
265
  */
222
- declare function labelled(label: string, control: HTMLElement): HTMLElement;
266
+ declare function labelledToggle(label: string, opts?: LabelledToggleOptions): {
267
+ row: HTMLElement;
268
+ input: HTMLInputElement;
269
+ };
223
270
  /** Swap item `i` with its neighbour `i + delta` IN PLACE (the up/down reorder list editors share);
224
271
  * a no-op when the target is out of range. Returns whether anything moved. */
225
272
  declare function moveItem<T>(arr: T[], i: number, delta: number): boolean;
@@ -229,12 +276,28 @@ declare function moveItem<T>(arr: T[], i: number, delta: number): boolean;
229
276
  declare function tagChips(holder: {
230
277
  values?: string[];
231
278
  }, onChange?: () => void): HTMLElement;
279
+ /** What `stageChips` tells its caller beyond "something changed". */
280
+ interface StageChipsOptions {
281
+ /**
282
+ * A stage was renamed in place, `from` to `to`. Given, a rename calls this
283
+ * INSTEAD of `onChange`: values are stored by stage name, so whoever knows
284
+ * where those values live has to carry them, and doing it as one edit is
285
+ * theirs to arrange. Absent, a rename is an ordinary change.
286
+ */
287
+ onRename?: (from: string, to: string) => void;
288
+ /** The stages were reordered (after `onChange`): the one edit that changes
289
+ * what every ordering gate means without changing any value. */
290
+ onReorder?: () => void;
291
+ }
232
292
  /**
233
293
  * An ORDERED chips editor for a string-list field whose order means something
234
294
  * (a quality's stages: `advance()` walks them, so 2 comes after 1): numbered
235
295
  * chips, each with move-earlier / move-later / remove, plus an add input
236
- * (Enter or "," commits; blank / duplicate ignored). Mutates `holder.stages`
237
- * in place; `onChange` fires after any add / move / remove.
296
+ * (Enter or "," commits; blank / duplicate ignored). A stage's name renames
297
+ * in place when clicked (Enter or blur commits, Escape cancels; blank, the same
298
+ * name or another stage's is ignored). Mutates `holder.stages` in place;
299
+ * `onChange` fires after any add / move / remove, and after a rename unless
300
+ * `opts.onRename` takes it.
238
301
  *
239
302
  * Patterpad's, lifted (ui-review-2026-09, finding 3); Storyletter had been
240
303
  * faking it with `tagChips` over a `values` holder, which loses the order
@@ -243,7 +306,7 @@ declare function tagChips(holder: {
243
306
  */
244
307
  declare function stageChips(holder: {
245
308
  stages?: string[];
246
- }, onChange?: () => void): HTMLElement;
309
+ }, onChange?: () => void, opts?: StageChipsOptions): HTMLElement;
247
310
  /**
248
311
  * Make `el` a drag source and a drop target for HTML5 drag-to-reorder among
249
312
  * its siblings. While carried it wears `.dragging`; a hovered target wears
@@ -775,7 +838,9 @@ interface DocLine {
775
838
  * what stops a note being copied down and then disagreeing with itself. */
776
839
  interface Notes {
777
840
  own: DocLine[];
778
- inherited: {
841
+ /** Outermost first. Absent or empty in an app whose own UI already shows
842
+ * each ancestor's notes (Patterpad's stacked inspector). */
843
+ inherited?: {
779
844
  id: string;
780
845
  label: string;
781
846
  lines: DocLine[];
@@ -784,13 +849,25 @@ interface Notes {
784
849
  classes: string[];
785
850
  }
786
851
  interface NotesEditorOptions {
787
- /** What this is about, for the dialog's title ("Notes: Arrive at the gate"). */
788
- subject: string;
852
+ /** What this is about, for the dialog's title ("Notes: Arrive at the gate").
853
+ * Absent, the title is "Notes". */
854
+ subject?: string;
855
+ /** One line under the title, for what the classes are FOR in this app
856
+ * (Patterpad's "A VO note travels with the voice script…"): the dialog
857
+ * frame's own sub line. */
858
+ sub?: string;
789
859
  notes: Notes;
860
+ /** Open on this class: its text area is shown even when empty, and the
861
+ * caret is in it. Patterpad's "Needs re-record" asks why the line needs
862
+ * another take, and the answer is a VO note, so it opens on "vo". Ignored
863
+ * when the class is neither offered (`notes.classes`) nor already written
864
+ * on, so a prompt cannot add a class the thing does not take. "" is the
865
+ * editor-only bucket. */
866
+ focus?: string;
790
867
  /** Commit. Called once, on close, and only when something changed. */
791
868
  save: (lines: DocLine[]) => void;
792
869
  }
793
- /** Open the notes modal. Resolves when it closes. */
870
+ /** Open the notes modal. Returns at once; `save` runs when it closes. */
794
871
  declare function openNotesEditor(opts: NotesEditorOptions): void;
795
872
 
796
873
  /** One message. The author is a NAME stamped at posting time, not a reference:
@@ -1135,6 +1212,12 @@ interface NavHistory<Place> {
1135
1212
  * is validating the whole stack on every paint). */
1136
1213
  canBack(): boolean;
1137
1214
  canForward(): boolean;
1215
+ /** Forget both sides, for when the places stop meaning anything: a
1216
+ * different project opened, or the welcome screen with none. Without it
1217
+ * Back walks into the project just closed (Patterpad, 2026-10-07: on the
1218
+ * welcome screen the arrows looked live and did nothing). The caller
1219
+ * re-`set`s its arrows afterwards, as after any other change. */
1220
+ clear(): void;
1138
1221
  }
1139
1222
  declare function createNavHistory<Place>(opts: NavHistoryOptions<Place>): NavHistory<Place>;
1140
1223
  /**
@@ -1158,4 +1241,4 @@ declare function historyNav(onBack: () => void, onForward: () => void): {
1158
1241
  set(canBack: boolean, canForward: boolean): void;
1159
1242
  };
1160
1243
 
1161
- export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type Crumb, type DocLine, type ElProps, type FollowButton, type FollowButtonOptions, type GameIdEditorOptions, ICON_NAMES, ICON_SIZE, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobBeginOptions, type JobProgressOptions, type JobProgressView, type NavHistory, type NavHistoryOptions, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, type PropertyRefOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type ShardVc, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type ToastKind, type ToastOptions, type ToolWindowHeadOptions, type TooltipOptions, type VcBadge, type VcMap, askIdentity, bindPropertyName, bindPropertyRef, breadcrumb, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createNavHistory, createSaveController, dismissToast, el, firstIllegalPropertyName, foldVc, followButton, gameIdify, hideTip, historyNav, icon, iconBtn, iconHtml, iconNameOfGlyph, iconNode, iconSvg, initTooltips, isCaseOnlyPropertyName, isIconName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, metaLine, mountJobProgress, mountPaneShell, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, stageChips, staleBar, tagChips, tipAt, tipBold, toast, toolWindowHead, vcBadgeFor, wireReorder };
1244
+ export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type Crumb, type DocLine, type ElProps, type FieldHint, type FollowButton, type FollowButtonOptions, type GameIdEditorOptions, ICON_NAMES, ICON_SIZE, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobBeginOptions, type JobProgressOptions, type JobProgressView, type LabelledToggleOptions, type NavHistory, type NavHistoryOptions, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, type PropertyRefOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type ShardVc, type StageChipsOptions, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type ToastKind, type ToastOptions, type ToolWindowHeadOptions, type TooltipOptions, type VcBadge, type VcMap, askIdentity, bindPropertyName, bindPropertyRef, breadcrumb, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createNavHistory, createSaveController, dismissToast, el, firstIllegalPropertyName, foldVc, followButton, gameIdify, hideTip, historyNav, icon, iconBtn, iconHtml, iconNameOfGlyph, iconNode, iconSvg, initTooltips, isCaseOnlyPropertyName, isIconName, isValidGameId, isValidPropertyName, labelled, labelledToggle, lockControls, lockNotice, metaLine, mountJobProgress, mountPaneShell, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, stageChips, staleBar, tagChips, tipAt, tipBold, toast, toolWindowHead, vcBadgeFor, wireReorder };
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
- export { mountWelcome } from './chunk-CUFREPJ5.js';
2
- export { feedUpdaterDownloadProgress, showUpdaterDialog } from './chunk-WKPKBAXS.js';
1
+ export { mountWelcome } from './chunk-XSS7KM6F.js';
2
+ export { feedUpdaterDownloadProgress, showUpdaterDialog } from './chunk-C245WRPF.js';
3
3
  export { defaultProblemCopy, describeProblem, problemLine, problemName } from './chunk-4OAORWEM.js';
4
- export { PROPERTY_NAME_HINT, PROPERTY_TYPES, RESERVED_PROPERTY_NAMES, bindPropertyName, bindPropertyRef, defaultControl, dupGuard, expandableRow, firstIllegalPropertyName, focusNewRow, isCaseOnlyPropertyName, isValidPropertyName, mountPropertyList, mountSettingsDialog, propertyNameProblem, propertyNameify, propertyRefProblem, revalidatePropertyRefs, revealRow, revealRowWhenReady } from './chunk-R3NXPHRZ.js';
4
+ export { PROPERTY_NAME_HINT, PROPERTY_TYPES, RESERVED_PROPERTY_NAMES, bindPropertyName, bindPropertyRef, defaultControl, dupGuard, expandableRow, firstIllegalPropertyName, focusNewRow, isCaseOnlyPropertyName, isValidPropertyName, mountPropertyList, mountSettingsDialog, propertyNameProblem, propertyNameify, propertyRefProblem, revalidatePropertyRefs, revealRow, revealRowWhenReady } from './chunk-CRUILFAT.js';
5
5
  import { isKeyCombo, tipWithKey } from './chunk-YWMSQFXX.js';
6
6
  export { hintBar, isKeyCombo, keyHint, keyLabel, keyLegends, keyPlatform, setKeyPlatform, tipWithKey } from './chunk-YWMSQFXX.js';
7
- export { kitTile, openKitGallery } from './chunk-J6RSONXM.js';
8
- export { linkAddress, linkStatusClass, linkStatusTip, mountLinkStatus } from './chunk-B2TC6Q5J.js';
7
+ export { kitTile, openKitGallery } from './chunk-62KWJPRP.js';
8
+ export { linkAddress, linkStatusClass, linkStatusTip, mountLinkStatus } from './chunk-UQTUCOGV.js';
9
9
  import { isEditableTarget } from './chunk-QVACRWG7.js';
10
10
  export { copyWithFeedback, debounce, formatCount, isEditableTarget, isLocalOrigin, plural, relativeTime } from './chunk-QVACRWG7.js';
11
- export { EDIT_MENU, FILE_MENU, GO_MENU, PANE_MENU, PLAY_MENU, PUBLISH_MENU, REVIEW_MENU, VIEW_MENU } from './chunk-2ECJUTSX.js';
12
- import { dialogFrame } from './chunk-YRFJLJUH.js';
13
- export { dialogFrame } from './chunk-YRFJLJUH.js';
11
+ export { EDIT_MENU, FILE_MENU, GO_MENU, PANE_MENU, PLAY_MENU, PUBLISH_MENU, REVIEW_MENU, VIEW_MENU } from './chunk-T25WXWPB.js';
12
+ import { dialogFrame } from './chunk-VXEFIL43.js';
13
+ export { dialogFrame } from './chunk-VXEFIL43.js';
14
14
  export { createFloating } from './chunk-PFERSFRB.js';
15
15
  export { JOB_PROGRESS } from './chunk-MDGOIT3O.js';
16
- export { openContextMenu, openPopover } from './chunk-EHR2KBZC.js';
17
- import { ensureTooltipHost, el, iconNode, labelled, icon } from './chunk-UG2P777F.js';
18
- export { ICON_NAMES, ICON_SIZE, breadcrumb, el, hideTip, icon, iconBtn, iconHtml, iconNameOfGlyph, iconNode, iconSvg, initTooltips, isIconName, labelled, metaLine, moveItem, stageChips, tagChips, tipAt, tipBold, wireReorder } from './chunk-UG2P777F.js';
16
+ export { openContextMenu, openPopover } from './chunk-OMG6A5YB.js';
17
+ import { ensureTooltipHost, el, iconNode, labelled, icon } from './chunk-W4WC4FO5.js';
18
+ export { ICON_NAMES, ICON_SIZE, breadcrumb, el, hideTip, icon, iconBtn, iconHtml, iconNameOfGlyph, iconNode, iconSvg, initTooltips, isIconName, labelled, labelledToggle, metaLine, moveItem, stageChips, tagChips, tipAt, tipBold, wireReorder } from './chunk-W4WC4FO5.js';
19
19
  import { closeWithExit } from './chunk-IU7EY6BI.js';
20
20
  export { closeWithExit } from './chunk-IU7EY6BI.js';
21
21
 
@@ -640,16 +640,19 @@ var CLASS_LABEL = {
640
640
  everyone: "Everyone",
641
641
  writing: "Writing",
642
642
  vo: "Voice (VO)",
643
- loc: "Localisers"
643
+ loc: "Localisers",
644
+ editor: "Editors"
644
645
  };
645
646
  var label = (cls) => cls === EDITOR_ONLY ? "Note (editor-only)" : CLASS_LABEL[cls] ?? cls[0].toUpperCase() + cls.slice(1);
646
647
  var PLACEHOLDER = {
647
648
  vo: "Direction for the voice actor or director",
648
- loc: "Context for translators"
649
+ loc: "Context for translators",
650
+ editor: "Context for an outside editor"
649
651
  };
650
652
  var placeholder = (cls) => cls === EDITOR_ONLY ? "An internal note, never exported" : PLACEHOLDER[cls] ?? "Why this is here";
651
653
  function openNotesEditor(opts) {
652
654
  const { notes } = opts;
655
+ const inherited = notes.inherited ?? [];
653
656
  const seed = /* @__PURE__ */ new Map();
654
657
  for (const line of notes.own) {
655
658
  const key = line.type ?? EDITOR_ONLY;
@@ -657,6 +660,8 @@ function openNotesEditor(opts) {
657
660
  ${line.text}` : line.text);
658
661
  }
659
662
  const shown = [...seed.keys()];
663
+ const focus = opts.focus !== void 0 && (opts.focus === EDITOR_ONLY || notes.classes.includes(opts.focus) || seed.has(opts.focus)) ? opts.focus : void 0;
664
+ if (focus !== void 0 && !shown.includes(focus)) shown.push(focus);
660
665
  if (shown.length === 0) shown.push(notes.classes[0] ?? EDITOR_ONLY);
661
666
  const areas = /* @__PURE__ */ new Map();
662
667
  const value = () => {
@@ -674,13 +679,13 @@ ${line.text}` : line.text);
674
679
  const body = el("div", "shell-notes-body");
675
680
  const render = () => {
676
681
  body.replaceChildren();
677
- if (notes.inherited.length > 0) {
682
+ if (inherited.length > 0) {
678
683
  const from = el(
679
684
  "div",
680
685
  { className: "shell-notes-inherited" },
681
686
  el("div", "shell-notes-caption", "Inherited")
682
687
  );
683
- for (const level of notes.inherited) {
688
+ for (const level of inherited) {
684
689
  from.append(el(
685
690
  "div",
686
691
  { className: "shell-notes-level" },
@@ -737,7 +742,12 @@ ${line.text}` : line.text);
737
742
  const next = value();
738
743
  if (JSON.stringify(next) !== before) opts.save(next);
739
744
  };
740
- const frame = dialogFrame({ title: `Notes: ${opts.subject}`, className: "shell-notes", onClose: commit });
745
+ const frame = dialogFrame({
746
+ title: opts.subject !== void 0 ? `Notes: ${opts.subject}` : "Notes",
747
+ ...opts.sub !== void 0 ? { sub: opts.sub } : {},
748
+ className: "shell-notes",
749
+ onClose: commit
750
+ });
741
751
  frame.body.append(body);
742
752
  frame.actions.append(done);
743
753
  done.addEventListener("click", () => {
@@ -745,7 +755,7 @@ ${line.text}` : line.text);
745
755
  frame.close();
746
756
  });
747
757
  frame.open();
748
- areas.get(shown[0] ?? EDITOR_ONLY)?.focus();
758
+ areas.get(focus ?? shown[0] ?? EDITOR_ONLY)?.focus();
749
759
  }
750
760
 
751
761
  // src/comments.ts
@@ -1217,6 +1227,10 @@ function createNavHistory(opts) {
1217
1227
  },
1218
1228
  canForward() {
1219
1229
  return ahead.length > 0;
1230
+ },
1231
+ clear() {
1232
+ past = [];
1233
+ ahead = [];
1220
1234
  }
1221
1235
  };
1222
1236
  }