@timurproko/a1 0.1.8-dev.512 → 0.1.8-dev.521

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 (66) hide show
  1. package/README.md +64 -20
  2. package/dist/app/session-shell/index.d.ts +1 -1
  3. package/dist/app/session-shell/prompt-suggestion-controller.d.ts +2 -0
  4. package/dist/app/session-shell/prompt-suggestion-controller.js +12 -0
  5. package/dist/app/session-shell/session-shell-root.d.ts +10 -0
  6. package/dist/app/session-shell/session-shell-root.js +12 -2
  7. package/dist/app/session-shell/session-shell.d.ts +8 -1
  8. package/dist/app/session-shell/session-shell.js +217 -18
  9. package/dist/composition/index.d.ts +1 -1
  10. package/dist/composition/owned-ui.js +21 -3
  11. package/dist/composition/settings-route-host.d.ts +17 -4
  12. package/dist/composition/settings-route-host.js +47 -16
  13. package/dist/contracts/owned-ui/index.d.ts +1 -1
  14. package/dist/contracts/owned-ui/model.d.ts +1 -5
  15. package/dist/features/owned-ui/index.d.ts +3 -0
  16. package/dist/features/owned-ui/index.js +2 -0
  17. package/dist/features/owned-ui/reference-routes.d.ts +6 -0
  18. package/dist/features/owned-ui/reference-routes.js +6 -0
  19. package/dist/features/owned-ui/reference-screen-app.d.ts +37 -0
  20. package/dist/features/owned-ui/reference-screen-app.js +232 -0
  21. package/dist/features/owned-ui/settings-app.js +2 -7
  22. package/dist/integrations/pi/components/index.d.ts +4 -2
  23. package/dist/integrations/pi/components/index.js +2 -1
  24. package/dist/integrations/pi/components/models-dialog.d.ts +55 -0
  25. package/dist/integrations/pi/components/models-dialog.js +310 -0
  26. package/dist/integrations/pi/components/shell-editor-autocomplete.d.ts +10 -0
  27. package/dist/integrations/pi/components/shell-editor-autocomplete.js +29 -5
  28. package/dist/integrations/pi/components/shell-footer-status.js +2 -1
  29. package/dist/integrations/pi/components/shell-presenters-info.d.ts +10 -0
  30. package/dist/integrations/pi/components/shell-presenters-info.js +31 -11
  31. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +11 -1
  32. package/dist/integrations/pi/components/shell-selectors-dialogs.js +13 -0
  33. package/dist/integrations/pi/components/shell-shared-facade.d.ts +8 -0
  34. package/dist/integrations/pi/components/skills-command.d.ts +47 -0
  35. package/dist/integrations/pi/components/skills-command.js +118 -0
  36. package/dist/integrations/pi/components/skills-dialog.d.ts +9 -0
  37. package/dist/integrations/pi/components/skills-dialog.js +123 -0
  38. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +8 -2
  39. package/dist/integrations/pi/components/upstream/components/owned-editor.js +42 -7
  40. package/dist/integrations/pi/engine/adapter.d.ts +5 -1
  41. package/dist/integrations/pi/engine/adapter.js +13 -1
  42. package/dist/integrations/pi/engine/index.d.ts +2 -2
  43. package/dist/integrations/pi/engine/index.js +1 -1
  44. package/dist/integrations/pi/engine/resource-catalog.d.ts +2 -1
  45. package/dist/integrations/pi/engine/resource-catalog.js +19 -9
  46. package/dist/integrations/pi/engine/workflow-contexts.d.ts +8 -1
  47. package/dist/integrations/pi/engine/workflow-contexts.js +52 -8
  48. package/dist/integrations/pi/engine/workflow-runner.js +98 -74
  49. package/dist/integrations/pi/engine/workflows.d.ts +26 -2
  50. package/dist/integrations/pi/engine/workflows.js +8 -0
  51. package/dist/native/darwin-arm64/manifest.json +1 -1
  52. package/dist/native/linux-x64/manifest.json +1 -1
  53. package/dist/native/win32-x64/manifest.json +2 -2
  54. package/dist/native/win32-x64/process-guardian.exe +0 -0
  55. package/dist/ui/apps/contracts.d.ts +9 -1
  56. package/dist/ui/apps/index.d.ts +1 -1
  57. package/dist/ui/settings/declarations.d.ts +15 -29
  58. package/dist/ui/settings/declarations.js +14 -25
  59. package/dist/ui/settings/index.d.ts +1 -1
  60. package/dist/ui/settings/index.js +1 -1
  61. package/dist/ui/settings/migrations.js +18 -0
  62. package/docs/architecture/prompt-suggestions.md +1 -1
  63. package/docs/architecture/ui-reference-provenance.md +1 -0
  64. package/docs/ci-release-runbook.md +31 -23
  65. package/docs/features/modal-content-interaction.md +3 -2
  66. package/package.json +1 -1
@@ -9,12 +9,14 @@ import { assertPromptImages, ImageAttachmentError } from "../../contracts/owned-
9
9
  import { ContextualPromptSuggestionController } from "./prompt-suggestion-controller.js";
10
10
  import { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput } from "../../ui/components/mouse.js";
11
11
  import { readVisibleHyperlinks } from "../../ui/components/visible-hyperlinks.js";
12
- import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES } from "../../integrations/pi/engine/workflows.js";
12
+ import { PINNED_PI_HIDDEN_COMMAND_NAMES, workflowCommandNames } from "../../integrations/pi/engine/workflows.js";
13
13
  import { createPiExtensionUiBridge } from "../../integrations/pi/components/shell-extension-ui.js";
14
14
  import { createPiShellEditor } from "../../integrations/pi/components/shell-editor-autocomplete.js";
15
+ import { SKILLS_COMMAND_NAME, findSkillByArgument, rewriteSkillsTunnelSubmission, skillPrompt, skillsFromCommands, } from "../../integrations/pi/components/skills-command.js";
15
16
  import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
16
- import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellThinkingSelector, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
17
+ import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellSkillsSelector, createPiShellThinkingSelector, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
17
18
  import { createPiShellChangelog, createPiShellHotkeys, createPiShellSessionInfo, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
19
+ import { CHANGELOG_ROUTE } from "../../features/owned-ui/reference-routes.js";
18
20
  import { createPiShellTranscriptComponent, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellTranscriptBlock, } from "../../integrations/pi/components/shell-presenters-transcript.js";
19
21
  import { onPiThemeChange, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
20
22
  import { DamageAwareTerminalAdapter } from "../../integrations/pi/tui-runtime/damage-aware-terminal.js";
@@ -35,6 +37,9 @@ export class OwnedUiSessionShell {
35
37
  #unsubscribe;
36
38
  #unsubscribePromptSuggestions;
37
39
  #promptSuggestions;
40
+ #skills;
41
+ #unsubscribeSkills;
42
+ #installedCommandSignature = "";
38
43
  #promptHistory = null;
39
44
  #promptHistoryImageSidecar;
40
45
  #extensionBridge;
@@ -51,6 +56,8 @@ export class OwnedUiSessionShell {
51
56
  #shutdownPromise;
52
57
  #disposePromise;
53
58
  #pointerReporting = false;
59
+ // Invariant: the startup release notes open at most once per shell, whatever the view does later.
60
+ #startupChangelogHandled = false;
54
61
  #customViewport;
55
62
  #responseCopy;
56
63
  // Rationale: only an executor this shell created owns a spare copy helper worth warming and disposing.
@@ -96,6 +103,8 @@ export class OwnedUiSessionShell {
96
103
  let streamPresentation;
97
104
  let pendingClipboardWrite = Promise.resolve();
98
105
  let promptSuggestionController = null;
106
+ // Invariant: the collapsed skills presentation is a bare-A1 replacement; comparison profiles keep the pinned list.
107
+ this.#skills = this.#customViewport ? options.skills ?? null : null;
99
108
  const terminalCopy = terminal !== undefined || hasAsyncClipboardOutput();
100
109
  this.#responseCopy = this.#customViewport ? new ResponseCopyCoordinator({
101
110
  execute: responseCopy?.execute ?? (this.#copyExecutor = createResponseCopyExecutor({
@@ -160,7 +169,7 @@ export class OwnedUiSessionShell {
160
169
  onInterrupt: () => { void this.interrupt(); },
161
170
  onClear: () => { void this.clearOrExit(); },
162
171
  onExit: () => { void this.shutdown(); },
163
- onModelSelect: () => this.showModelSelector(),
172
+ onModelSelect: () => this.#customViewport ? this.showModelsDialog() : this.showModelSelector(),
164
173
  onModelCycle: direction => { void this.cycleModel(direction); },
165
174
  onThinkingCycle: () => { void this.cycleThinkingLevel(); },
166
175
  onThinkingToggle: () => {
@@ -170,7 +179,7 @@ export class OwnedUiSessionShell {
170
179
  onMessageCopy: () => { void this.runWorkflow({ command: "copy", argument: "" }); },
171
180
  onFollowUp: () => { void this.queueFollowUp().catch(() => this.#reportSubmissionError()); },
172
181
  onDequeue: () => this.restoreQueuedInput(),
173
- onEditorChange: () => { this.#editorRevision++; promptSuggestionController?.invalidate(); },
182
+ onEditorChange: () => { this.#editorRevision++; promptSuggestionController?.abortPending(); },
174
183
  onPromptSuggestionAccepted: () => promptSuggestionController?.accept(),
175
184
  onInputSurfaceChanged: () => {
176
185
  promptSuggestionController?.invalidate();
@@ -207,6 +216,7 @@ export class OwnedUiSessionShell {
207
216
  },
208
217
  ...(pasteDiagnostics === undefined ? {} : { pasteDiagnostics: pasteDiagnostics }),
209
218
  ...(pastePreparation === undefined ? {} : { pastePreparation }),
219
+ skillsPresentation: () => this.#skills?.presentation() ?? "expand",
210
220
  }, {
211
221
  ...startup,
212
222
  resources: startup?.resources ?? shellResourceEntries(this.backend),
@@ -287,6 +297,15 @@ export class OwnedUiSessionShell {
287
297
  this.#unsubscribePromptSuggestions = promptSuggestionOptions === undefined
288
298
  ? () => { }
289
299
  : promptSuggestionOptions.onChange(enabled => this.#promptSuggestions?.setEnabled(enabled));
300
+ // Rationale: the same listener refreshes the menu for the A1 presentation choice and for the engine's
301
+ // skill-command registration, both of which the owned settings manager reports through one change.
302
+ this.#unsubscribeSkills = this.#skills === null ? () => { } : this.#skills.onChange(() => {
303
+ // Invariant: reinstalling drops extension provider wrappers, so an unrelated setting change leaves the list alone.
304
+ if (this.#disposed || this.#commandListSignature() === this.#installedCommandSignature)
305
+ return;
306
+ this.#installAutocompleteCommands();
307
+ this.runtime.requestRender();
308
+ });
290
309
  const initialPiSettings = this.backend.pinnedSettingsSnapshot();
291
310
  this.runtime.setHardwareCursor(initialPiSettings.showHardwareCursor);
292
311
  this.runtime.setClearOnShrink(initialPiSettings.clearOnShrink);
@@ -453,7 +472,7 @@ export class OwnedUiSessionShell {
453
472
  },
454
473
  finishLogin: () => this.#finishWorkflowLogin(),
455
474
  });
456
- this.root.editor.setAutocompleteCommands(this.backend.workflowAutocompleteCommands());
475
+ this.#installAutocompleteCommands();
457
476
  this.#unsubscribe = this.backend.onEvent(event => {
458
477
  // Performance: a streamed chunk names one block, and touching only that block is what keeps the
459
478
  // cost of a chunk the same in a long session as in a new one. Everything else
@@ -669,7 +688,7 @@ export class OwnedUiSessionShell {
669
688
  }, displayInput);
670
689
  }
671
690
  async clearOrExit(now = Date.now()) {
672
- this.#promptSuggestions?.invalidate();
691
+ this.#promptSuggestions?.abortPending();
673
692
  if (now - this.#lastClearTime < 500)
674
693
  return this.shutdown();
675
694
  this.root.editor.setText("");
@@ -834,6 +853,23 @@ export class OwnedUiSessionShell {
834
853
  this.root.setInputSurface(component);
835
854
  this.runtime.requestRender();
836
855
  }
856
+ /** The Skills dialog: a replacement input like the model selector, applying the chosen skill through the prompt path. */
857
+ showSkillsSelector(skills = this.#skillSummaries()) {
858
+ const close = () => {
859
+ this.root.setInputSurface(null);
860
+ this.runtime.requestRender();
861
+ };
862
+ const component = createPiShellSkillsSelector({
863
+ skills,
864
+ onSelect: name => {
865
+ close();
866
+ void this.#submitSkillPrompt(skillPrompt(name), skillPrompt(name)).catch(() => this.#reportSubmissionError());
867
+ },
868
+ onCancel: close,
869
+ });
870
+ this.root.setInputSurface(component);
871
+ this.runtime.requestRender();
872
+ }
837
873
  showModelSelector(initialSearchInput) {
838
874
  const context = this.backend.pinnedModelSelectorContext();
839
875
  const close = () => {
@@ -1139,6 +1175,10 @@ export class OwnedUiSessionShell {
1139
1175
  this.showScopedModelsSelector();
1140
1176
  return { outcome: "completed", diagnostic: null };
1141
1177
  }
1178
+ if (request.command === "models" && request.selection === undefined) {
1179
+ this.showModelsDialog(request.argument.trim() || undefined);
1180
+ return { outcome: "completed", diagnostic: null };
1181
+ }
1142
1182
  if (request.command === "model" && request.selection === undefined && request.confirmed === undefined && request.argument.trim().length === 0) {
1143
1183
  this.showModelSelector();
1144
1184
  return { outcome: "completed", diagnostic: null };
@@ -1247,10 +1287,10 @@ export class OwnedUiSessionShell {
1247
1287
  if (request.command === "reload" && result.outcome === "completed") {
1248
1288
  this.root.resetWorkflowPresentation();
1249
1289
  this.root.editor.reloadKeybindings();
1250
- this.root.editor.setAutocompleteCommands(this.backend.workflowAutocompleteCommands());
1290
+ this.#installAutocompleteCommands();
1251
1291
  }
1252
1292
  this.root.appendWorkflowResult(result);
1253
- if (request.command === "model" && result.outcome === "completed")
1293
+ if ((request.command === "model" || request.command === "models") && result.outcome === "completed")
1254
1294
  this.#showDaxnutsForActiveModel();
1255
1295
  this.runtime.requestRender();
1256
1296
  return workflowAdapterResult(result);
@@ -1357,6 +1397,90 @@ export class OwnedUiSessionShell {
1357
1397
  this.runtime.requestRender();
1358
1398
  }).finally(() => clearTimeout(timeout));
1359
1399
  }
1400
+ // Invariant: bare A1 routes `models`; the comparison profile keeps the pinned `model`/`scoped-models` pair. Hidden routes are shared.
1401
+ #isWorkflowRoute(value) {
1402
+ return workflowCommandNames(this.#customViewport ? "bare" : "comparison").includes(value)
1403
+ || PINNED_PI_HIDDEN_COMMAND_NAMES.includes(value);
1404
+ }
1405
+ /** The bare-A1 unified Models dialog: switch on Enter, scope on Space, persist on Ctrl+S, all through the engine. */
1406
+ showModelsDialog(initialQuery) {
1407
+ const context = this.backend.modelsContext();
1408
+ const available = new Set(context.models.map(model => `${model.provider}/${model.id}`));
1409
+ const savedScopeIds = context.persistedScopeIds.filter(id => available.has(id));
1410
+ // Invariant: an explicit session scope wins; otherwise the dialog starts from what is persisted, never from "all rows scoped".
1411
+ const scopeIds = context.sessionScopeIds.length > 0 ? context.sessionScopeIds : savedScopeIds;
1412
+ let disposed = false;
1413
+ let timedOut = false;
1414
+ const controller = new AbortController();
1415
+ const timeout = setTimeout(() => {
1416
+ timedOut = true;
1417
+ controller.abort();
1418
+ }, 15_000);
1419
+ const close = () => {
1420
+ disposed = true;
1421
+ clearTimeout(timeout);
1422
+ controller.abort();
1423
+ this.root.setInputSurface(null);
1424
+ this.runtime.requestRender();
1425
+ };
1426
+ const dialog = createPiShellModelsDialog({
1427
+ models: context.models,
1428
+ activeModelId: context.activeModelId,
1429
+ scopeIds,
1430
+ savedScopeIds,
1431
+ ...(initialQuery === undefined ? {} : { initialQuery }),
1432
+ refreshStatus: "Refreshing model catalogs…",
1433
+ requestRender: () => this.runtime.requestRender(),
1434
+ onSelect: modelId => {
1435
+ void this.runWorkflow({ command: "models", argument: "", selection: modelId }).then(result => {
1436
+ if (!disposed && result.outcome === "completed")
1437
+ close();
1438
+ });
1439
+ },
1440
+ onScopeChange: ids => {
1441
+ this.backend.setSessionModelScope(ids);
1442
+ this.runtime.requestRender();
1443
+ },
1444
+ onSave: ids => {
1445
+ try {
1446
+ this.backend.persistModelScope(ids);
1447
+ }
1448
+ catch (error) {
1449
+ this.root.appendWorkflowResult({ command: "models", outcome: "failed", message: error instanceof Error ? error.message : String(error) });
1450
+ this.runtime.requestRender();
1451
+ throw error;
1452
+ }
1453
+ this.root.appendWorkflowStatus("Model selection saved to settings");
1454
+ this.runtime.requestRender();
1455
+ },
1456
+ onCancel: close,
1457
+ });
1458
+ const component = {
1459
+ ...dialog,
1460
+ dispose: () => {
1461
+ disposed = true;
1462
+ clearTimeout(timeout);
1463
+ controller.abort();
1464
+ dialog.dispose?.();
1465
+ },
1466
+ };
1467
+ this.root.setInputSurface(component);
1468
+ this.runtime.requestRender();
1469
+ void this.backend.refreshModels(controller.signal).then(refreshed => {
1470
+ if (disposed)
1471
+ return;
1472
+ component.updateModels(refreshed.models);
1473
+ component.setRefreshStatus(timedOut ? "Model refresh timed out; showing cached models." : refreshed.status, timedOut ? "warning" : refreshed.statusKind);
1474
+ this.runtime.requestRender();
1475
+ }).catch(error => {
1476
+ if (disposed)
1477
+ return;
1478
+ component.setRefreshStatus(timedOut
1479
+ ? "Model refresh timed out; showing cached models."
1480
+ : `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`, "warning");
1481
+ this.runtime.requestRender();
1482
+ }).finally(() => clearTimeout(timeout));
1483
+ }
1360
1484
  #showDaxnutsForActiveModel() {
1361
1485
  const model = this.view().activeModel;
1362
1486
  if (model?.providerId === "opencode" && model.modelId.toLowerCase().includes("kimi-k2.5")) {
@@ -1409,6 +1533,7 @@ export class OwnedUiSessionShell {
1409
1533
  attempt(() => this.#streamPresentation.dispose());
1410
1534
  attempt(() => this.#promptSuggestions?.dispose());
1411
1535
  attempt(() => this.#unsubscribePromptSuggestions());
1536
+ attempt(() => this.#unsubscribeSkills());
1412
1537
  attempt(() => this.#unsubscribeSettings());
1413
1538
  let fullscreenExitText = "";
1414
1539
  attempt(() => {
@@ -1453,11 +1578,13 @@ export class OwnedUiSessionShell {
1453
1578
  if (!this.runtime.active || this.runtime.mode !== "fullscreen")
1454
1579
  return null;
1455
1580
  try {
1456
- const { enabled, effect, durationMs } = outro.snapshot();
1457
- if (!enabled)
1581
+ if (!outro.snapshot().enabled)
1458
1582
  return null;
1459
1583
  const viewport = this.runtime.viewport();
1460
- return { rows: this.#damageTerminal.presentedRows(), columns: viewport.columns, height: viewport.rows, settings: { effect, durationMs } };
1584
+ return {
1585
+ rows: this.#damageTerminal.presentedRows(), columns: viewport.columns, height: viewport.rows,
1586
+ settings: { effect: QUIT_OUTRO_EFFECT, durationMs: QUIT_OUTRO_DURATION_MS },
1587
+ };
1461
1588
  }
1462
1589
  catch {
1463
1590
  return null;
@@ -1550,13 +1677,31 @@ export class OwnedUiSessionShell {
1550
1677
  }
1551
1678
  this.root.update(view);
1552
1679
  this.#syncDialog(view.dialog);
1680
+ this.#presentStartupChangelog(view);
1553
1681
  this.runtime.requestRender();
1554
1682
  for (const listener of this.#listeners)
1555
1683
  listener(view);
1556
1684
  return view;
1557
1685
  }
1558
- #openOwnedRoute(route) {
1559
- const surface = this.#routeHost?.open(route) ?? null;
1686
+ // Rationale: the engine has already stored the current version when the diagnostic arrives, so the
1687
+ // entries newer than the last acknowledged one exist only in its message; the screen shows exactly those.
1688
+ #presentStartupChangelog(view) {
1689
+ if (this.#startupChangelogHandled || !this.#customViewport || this.#routeHost === null)
1690
+ return;
1691
+ const diagnostic = view.diagnostics.find(candidate => candidate.code === "changelog-expanded");
1692
+ // Invariant: a view synchronized before the runtime starts is not the moment of arrival; start() replays it.
1693
+ if (diagnostic === undefined || !this.runtime.active)
1694
+ return;
1695
+ this.#startupChangelogHandled = true;
1696
+ // Invariant: a dialog, selector, or owned screen already up keeps the screen; the hint stays in the feed.
1697
+ if (this.#dialogHandle !== undefined || !this.root.usesDefaultInputSurface())
1698
+ return;
1699
+ if (!this.#routeHost.claims(CHANGELOG_ROUTE))
1700
+ return;
1701
+ this.#openOwnedRoute(CHANGELOG_ROUTE, { document: diagnostic.message });
1702
+ }
1703
+ #openOwnedRoute(route, input) {
1704
+ const surface = this.#routeHost?.open(route, input) ?? null;
1560
1705
  if (surface === null)
1561
1706
  return { outcome: "failed", diagnostic: `route is unavailable: ${route}` };
1562
1707
  if (!this.runtime.active)
@@ -1683,6 +1828,9 @@ export class OwnedUiSessionShell {
1683
1828
  });
1684
1829
  this.#dialogId = dialog.id;
1685
1830
  }
1831
+ hotkeysPresentation() {
1832
+ return this.root.hotkeysPresentation();
1833
+ }
1686
1834
  async #slashCommand(text) {
1687
1835
  const body = text.slice(1).trim();
1688
1836
  const separator = body.search(/\s/);
@@ -1690,8 +1838,15 @@ export class OwnedUiSessionShell {
1690
1838
  const argument = separator < 0 ? "" : body.slice(separator + 1).trimStart();
1691
1839
  if (this.#routeHost?.claims(name))
1692
1840
  return this.#openOwnedRoute(name);
1693
- if (isWorkflowRoute(name))
1841
+ if (this.#isWorkflowRoute(name))
1694
1842
  return this.runWorkflow({ command: name, argument });
1843
+ if (this.#skillsCollapsed()) {
1844
+ if (name === SKILLS_COMMAND_NAME)
1845
+ return this.#runSkillsCommand(text, argument);
1846
+ const rewritten = rewriteSkillsTunnelSubmission(text);
1847
+ if (rewritten !== text)
1848
+ return this.#submitSkillPrompt(rewritten, text);
1849
+ }
1695
1850
  // Compatibility: unknown slash input, prompt templates, skills, and extension commands remain Pi prompt input.
1696
1851
  this.#rememberInput(text, "slash");
1697
1852
  this.root.resumeViewportFollowing();
@@ -1702,6 +1857,51 @@ export class OwnedUiSessionShell {
1702
1857
  text,
1703
1858
  });
1704
1859
  }
1860
+ #installAutocompleteCommands() {
1861
+ this.root.editor.setAutocompleteCommands(this.backend.workflowAutocompleteCommands());
1862
+ this.#installedCommandSignature = this.#commandListSignature();
1863
+ }
1864
+ #commandListSignature() {
1865
+ return JSON.stringify([this.#skills?.presentation() ?? "expand", this.backend.disposed ? [] : this.backend.workflowAutocompleteCommands().map(command => command.name)]);
1866
+ }
1867
+ // Invariant: collapse applies only while the engine registers skill commands; otherwise there is nothing to collapse.
1868
+ #skillsCollapsed() {
1869
+ return this.#skills !== null && this.#skills.presentation() === "collapse"
1870
+ && !this.backend.disposed && this.backend.pinnedSettingsSnapshot().enableSkillCommands;
1871
+ }
1872
+ #skillSummaries() {
1873
+ return skillsFromCommands(this.backend.workflowAutocompleteCommands());
1874
+ }
1875
+ // Protocol: bare "/skills" opens the dialog, "/skills <name> [args]" applies directly, and an unknown name is a command outcome.
1876
+ async #runSkillsCommand(text, argument) {
1877
+ const skills = this.#skillSummaries();
1878
+ const trimmed = argument.trim();
1879
+ if (trimmed.length === 0) {
1880
+ this.showSkillsSelector(skills);
1881
+ return { outcome: "completed", diagnostic: null };
1882
+ }
1883
+ const separator = trimmed.search(/\s/u);
1884
+ const token = separator < 0 ? trimmed : trimmed.slice(0, separator);
1885
+ const skill = findSkillByArgument(skills, token);
1886
+ if (skill === undefined) {
1887
+ const message = "Unknown skill: " + token;
1888
+ this.root.appendWorkflowMessage({ kind: "error", message });
1889
+ this.runtime.requestRender();
1890
+ return { outcome: "failed", diagnostic: message };
1891
+ }
1892
+ return this.#submitSkillPrompt(skillPrompt(skill.name, separator < 0 ? "" : trimmed.slice(separator + 1)), text);
1893
+ }
1894
+ // Rationale: the engine expands "/skill:<name> args" itself; history keeps the form the user typed so recall restores the invocation.
1895
+ #submitSkillPrompt(prompt, typed) {
1896
+ this.#rememberInput(typed, "slash");
1897
+ this.root.resumeViewportFollowing();
1898
+ return this.#execute({
1899
+ type: this.view().lifecycle === "busy" ? "steer" : "prompt",
1900
+ correlationId: this.#correlation("prompt-command"),
1901
+ sessionId: this.backend.sessionId,
1902
+ text: prompt,
1903
+ });
1904
+ }
1705
1905
  async #completeTreeSelection(entryId, skipSummaryPrompt) {
1706
1906
  let summarize = false;
1707
1907
  let customInstructions;
@@ -1911,7 +2111,6 @@ function workflowAdapterResult(result) {
1911
2111
  const INTERRUPT = "\u0003";
1912
2112
  const INTERRUPT_CHORD_MS = 1_500;
1913
2113
  const RELOAD_SURFACE_MIN_VISIBLE_MS = 400;
1914
- function isWorkflowRoute(value) {
1915
- return PINNED_PI_WORKFLOW_COMMAND_NAMES.includes(value)
1916
- || PINNED_PI_HIDDEN_COMMAND_NAMES.includes(value);
1917
- }
2114
+ // Rationale: the outro is not configurable; the switch only decides whether this plan plays.
2115
+ const QUIT_OUTRO_EFFECT = "fall";
2116
+ const QUIT_OUTRO_DURATION_MS = 800;
@@ -1,2 +1,2 @@
1
1
  export { composeOwnedUi, composeOwnedUiApplication, type OwnedUiComposition, type OwnedUiCompositionOptions, } from "./owned-ui.js";
2
- export { createOwnedRouteHost } from "./settings-route-host.js";
2
+ export { createOwnedRouteHost, type OwnedReferenceProviders, type OwnedReferenceRows } from "./settings-route-host.js";
@@ -12,6 +12,8 @@ import { OwnedUiSessionShell } from "../app/session-shell/session-shell.js";
12
12
  import { OwnedSettingsManager } from "../ui/settings/manager.js";
13
13
  import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentation-adapter.js";
14
14
  import { createOwnedRouteHost } from "./settings-route-host.js";
15
+ import { renderPiShellChangelogLines, renderPiShellHotkeysLines } from "../integrations/pi/components/shell-presenters-info.js";
16
+ import { readPinnedCommandChangelog } from "../integrations/pi/engine/changelog.js";
15
17
  export async function composeOwnedUiApplication(options = {}) {
16
18
  return (await composeOwnedUi(options)).application;
17
19
  }
@@ -40,7 +42,19 @@ export async function composeOwnedUi(options = {}) {
40
42
  // dark, regardless of terminal detection or a previously stored Pi theme. The
41
43
  // comparison profile keeps Pi's configured theme behavior and settings surface.
42
44
  applyConfiguredPiTheme(ownedSurfaces ? "dark" : adapter.configuredTheme());
43
- const routeHost = settings === null || !ownedSurfaces ? null : createOwnedRouteHost(settings);
45
+ // Rationale: the reference screens read the shell the composition is about to construct; a route
46
+ // cannot open before the shell exists, so the closure is settled by the time it runs.
47
+ const references = {
48
+ changelog: async (input) => {
49
+ const markdown = input?.document ?? await readPinnedCommandChangelog();
50
+ return width => renderPiShellChangelogLines(markdown, width);
51
+ },
52
+ hotkeys: async () => {
53
+ const presentation = shell.hotkeysPresentation();
54
+ return width => renderPiShellHotkeysLines(presentation, width);
55
+ },
56
+ };
57
+ const routeHost = settings === null || !ownedSurfaces ? null : createOwnedRouteHost(settings, references);
44
58
  const viewportSettings = settings === null || !ownedSurfaces ? null : {
45
59
  snapshot: () => viewportSettingsSnapshot(settings),
46
60
  onChange: listener => settings.onChange(() => listener(viewportSettingsSnapshot(settings))),
@@ -54,6 +68,11 @@ export async function composeOwnedUi(options = {}) {
54
68
  enabled: () => settings.value("promptSuggestions"),
55
69
  onChange: (listener) => settings.onChange(() => listener(settings.value("promptSuggestions"))),
56
70
  };
71
+ const skills = settings === null || !ownedSurfaces ? null : {
72
+ presentation: () => settings.value("skillsPresentation"),
73
+ // Rationale: one listener covers both the A1 presentation choice and the engine skill-command toggle.
74
+ onChange: (listener) => settings.onChange(() => listener()),
75
+ };
57
76
  const historyLimit = settings?.value("promptHistoryMaxItems");
58
77
  const historyProfileLocation = settings === null || !ownedSurfaces || !settings.value("promptHistoryEnabled")
59
78
  ? null
@@ -92,6 +111,7 @@ export async function composeOwnedUi(options = {}) {
92
111
  paste: event => clipboardDiagnostics.paste(event),
93
112
  } }),
94
113
  ...(promptSuggestions === null ? {} : { suggestions: promptSuggestions }),
114
+ ...(skills === null ? {} : { skills }),
95
115
  ...(promptHistory === null ? {} : { history: {
96
116
  ...promptHistory,
97
117
  editor: await import("../integrations/pi/components/history-editor-loader.js").then(module => module.loadHistoryEditor()),
@@ -123,8 +143,6 @@ export async function composeOwnedUi(options = {}) {
123
143
  function quitOutroSettingsSnapshot(settings) {
124
144
  return {
125
145
  enabled: settings.value("quitAnimation"),
126
- effect: settings.value("quitEffect"),
127
- durationMs: settings.value("quitEffectDurationMs"),
128
146
  };
129
147
  }
130
148
  function viewportSettingsSnapshot(settings) {
@@ -1,7 +1,20 @@
1
1
  import type { OwnedSettingsManager } from "../ui/settings/manager.js";
2
- import type { UiRouteHost } from "../ui/apps/contracts.js";
2
+ import type { UiRouteHost, UiRouteInput } from "../ui/apps/contracts.js";
3
+ /** Styled document rows for one content width. */
4
+ export type OwnedReferenceRows = (width: number) => readonly string[];
3
5
  /**
4
- * Declares the A1-owned settings route without evaluating its presentation graph
5
- * during startup. Opening the route retains input while the optional module loads.
6
+ * The documents the reference routes present, supplied by the composition so the
7
+ * route host never reads the changelog or the shell's keybindings itself.
6
8
  */
7
- export declare function createOwnedRouteHost(settings: OwnedSettingsManager): UiRouteHost;
9
+ export interface OwnedReferenceProviders {
10
+ /** The complete pinned changelog, or the supplied document when the route input carries one. */
11
+ changelog(input?: UiRouteInput): Promise<OwnedReferenceRows>;
12
+ /** The shell's keyboard shortcuts as they stand when the screen opens. */
13
+ hotkeys(): Promise<OwnedReferenceRows>;
14
+ }
15
+ /**
16
+ * Declares the A1-owned settings route, and the changelog and hotkeys reference
17
+ * routes when their documents are supplied, without evaluating their presentation
18
+ * graphs during startup. Opening a route retains input while the optional module loads.
19
+ */
20
+ export declare function createOwnedRouteHost(settings: OwnedSettingsManager, references?: OwnedReferenceProviders): UiRouteHost;
@@ -1,24 +1,38 @@
1
+ import { CHANGELOG_APP_ID, CHANGELOG_ROUTE, CHANGELOG_TITLE, HOTKEYS_APP_ID, HOTKEYS_ROUTE, HOTKEYS_TITLE, } from "../features/owned-ui/reference-routes.js";
1
2
  import { SETTINGS_APP_ID, SETTINGS_ROUTE } from "../features/owned-ui/settings-route.js";
2
3
  import { piTheme } from "../integrations/pi/components/upstream/theme/theme.js";
3
4
  import { faint } from "../ui/components/text.js";
4
5
  /**
5
- * Declares the A1-owned settings route without evaluating its presentation graph
6
- * during startup. Opening the route retains input while the optional module loads.
6
+ * Declares the A1-owned settings route, and the changelog and hotkeys reference
7
+ * routes when their documents are supplied, without evaluating their presentation
8
+ * graphs during startup. Opening a route retains input while the optional module loads.
7
9
  */
8
- export function createOwnedRouteHost(settings) {
10
+ export function createOwnedRouteHost(settings, references) {
11
+ const routes = new Set([SETTINGS_ROUTE, ...(references === undefined ? [] : [CHANGELOG_ROUTE, HOTKEYS_ROUTE])]);
9
12
  return {
10
- claims: route => route === SETTINGS_ROUTE,
11
- open: route => route === SETTINGS_ROUTE ? deferredSettingsSurface(settings) : null,
13
+ claims: route => routes.has(route),
14
+ open: (route, input) => {
15
+ if (!routes.has(route))
16
+ return null;
17
+ if (route === SETTINGS_ROUTE) {
18
+ return deferredSurface(SETTINGS_APP_ID, "settings", () => loadSettingsSurface(settings));
19
+ }
20
+ const changelog = route === CHANGELOG_ROUTE;
21
+ const id = changelog ? CHANGELOG_APP_ID : HOTKEYS_APP_ID;
22
+ const title = changelog ? CHANGELOG_TITLE : HOTKEYS_TITLE;
23
+ const document = changelog ? references.changelog(input) : references.hotkeys();
24
+ return deferredSurface(id, title, () => loadReferenceSurface(settings, id, route, title, document));
25
+ },
12
26
  };
13
27
  }
14
- function deferredSettingsSurface(settings) {
28
+ function deferredSurface(id, label, load) {
15
29
  let delegate = null;
16
30
  let closed = false;
17
31
  let failure = null;
18
32
  let onRender = () => undefined;
19
33
  let onExit = () => undefined;
20
34
  const pending = [];
21
- void loadSettingsSurface(settings).then(surface => {
35
+ void load().then(surface => {
22
36
  if (closed) {
23
37
  surface.close();
24
38
  return;
@@ -30,7 +44,7 @@ function deferredSettingsSurface(settings) {
30
44
  action(surface);
31
45
  onRender();
32
46
  }).catch(error => {
33
- failure = `Could not load settings: ${error instanceof Error ? error.message : String(error)}`;
47
+ failure = `Could not load ${label}: ${error instanceof Error ? error.message : String(error)}`;
34
48
  onRender();
35
49
  });
36
50
  const defer = (action) => {
@@ -40,10 +54,9 @@ function deferredSettingsSurface(settings) {
40
54
  pending.push(action);
41
55
  };
42
56
  return {
43
- id: SETTINGS_APP_ID,
57
+ id,
44
58
  render: (width, height) => delegate?.render(width, height)
45
- ?? [...(height > 0 ? [failure ?? "Loading settings…"] : []), ...Array(Math.max(0, height - 1)).fill("")]
46
- .map(line => line.slice(0, Math.max(0, width))),
59
+ ?? Array.from({ length: Math.max(0, height) }, (_, row) => row === 0 && failure !== null ? failure.slice(0, Math.max(0, width)) : ""),
47
60
  handleInput: data => { defer(surface => surface.handleInput(data)); return true; },
48
61
  handleMouse: event => { defer(surface => surface.handleMouse(event)); return true; },
49
62
  isClosed: () => closed || delegate?.isClosed() === true,
@@ -53,13 +66,31 @@ function deferredSettingsSurface(settings) {
53
66
  };
54
67
  }
55
68
  async function loadSettingsSurface(settings) {
56
- const [{ SettingsApp }, { UiAppHost }, { UiAppRegistry }] = await Promise.all([
57
- import("../features/owned-ui/settings-app.js"),
69
+ const { SettingsApp } = await import("../features/owned-ui/settings-app.js");
70
+ return hostApp(SETTINGS_APP_ID, SETTINGS_ROUTE, () => new SettingsApp(settings));
71
+ }
72
+ async function loadReferenceSurface(settings, id, route, title, document) {
73
+ // Rationale: the app module and the document load together, so a document that fails
74
+ // to render is reported by the placeholder rather than by a screen that never fills.
75
+ const [{ ReferenceScreenApp }, rows] = await Promise.all([import("../features/owned-ui/reference-screen-app.js"), document]);
76
+ return hostApp(id, route, () => new ReferenceScreenApp({
77
+ id,
78
+ title,
79
+ document: { rows },
80
+ scrollSettings: () => ({
81
+ scrollbarAppearance: settings.value("scrollbarAppearance"),
82
+ scrollbarStyle: settings.value("scrollbarStyle"),
83
+ scrollbarSpeed: settings.value("scrollbarSpeed"),
84
+ }),
85
+ }));
86
+ }
87
+ async function hostApp(id, route, create) {
88
+ const [{ UiAppHost }, { UiAppRegistry }] = await Promise.all([
58
89
  import("../ui/apps/host.js"),
59
90
  import("../ui/apps/registry.js"),
60
91
  ]);
61
92
  const registry = new UiAppRegistry();
62
- registry.register({ id: SETTINGS_APP_ID, route: SETTINGS_ROUTE, create: () => new SettingsApp(settings) });
93
+ registry.register({ id, route, create });
63
94
  let size = { width: 80, height: 24 };
64
95
  let frame = [];
65
96
  let closed = false;
@@ -81,9 +112,9 @@ async function loadSettingsSurface(settings) {
81
112
  },
82
113
  },
83
114
  });
84
- host.open(SETTINGS_APP_ID);
115
+ host.open(id);
85
116
  return {
86
- id: SETTINGS_APP_ID,
117
+ id,
87
118
  render: (width, height) => {
88
119
  size = { width, height };
89
120
  host.render();
@@ -1,7 +1,7 @@
1
1
  export { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiExtensionUiPort, } from "./extension-ui.js";
2
2
  export type { OwnedUiExtensionAutocompleteItem, OwnedUiExtensionAutocompleteProvider, OwnedUiExtensionAutocompleteProviderFactory, OwnedUiExtensionAutocompleteSuggestions, OwnedUiExtensionComponent, OwnedUiExtensionComponentFactory, OwnedUiExtensionComponentFactoryContext, OwnedUiExtensionCustomFactory, OwnedUiExtensionCustomOptions, OwnedUiExtensionDialogOptions, OwnedUiExtensionEditorComponent, OwnedUiExtensionEditorFactory, OwnedUiExtensionEditorThemePort, OwnedUiExtensionEntryRenderInput, OwnedUiExtensionFooterDataPort, OwnedUiExtensionFooterFactory, OwnedUiExtensionInputListener, OwnedUiExtensionKeybindingsPort, OwnedUiExtensionMessageRenderInput, OwnedUiExtensionNotificationSeverity, OwnedUiExtensionOverlayAnchor, OwnedUiExtensionOverlayHandle, OwnedUiExtensionOverlayMargin, OwnedUiExtensionOverlayOptions, OwnedUiExtensionRenderCallback, OwnedUiExtensionRendererPort, OwnedUiExtensionRuntimePort, OwnedUiExtensionSelectListThemePort, OwnedUiExtensionSizeValue, OwnedUiExtensionTerminalPort, OwnedUiExtensionThemeBackground, OwnedUiExtensionThemeColor, OwnedUiExtensionThemeInfo, OwnedUiExtensionThemePort, OwnedUiExtensionToolRenderContext, OwnedUiExtensionUiCallback, OwnedUiExtensionUiPort, OwnedUiExtensionUiProperty, OwnedUiExtensionWidgetOptions, OwnedUiExtensionWidgetPlacement, OwnedUiExtensionWorkingIndicatorOptions, } from "./extension-ui.js";
3
3
  export { OWNED_UI_CONTRACT_VERSION } from "./model.js";
4
- export type { OwnedUiBlockStatus, OwnedUiCommand, OwnedUiCommandOutcome, OwnedUiCorrelationId, OwnedUiCustomization, OwnedUiDiagnostics, OwnedUiDialog, OwnedUiEditorState, OwnedUiEntityId, OwnedUiEvent, OwnedUiFooterView, OwnedUiImageAttachment, OwnedUiLifecycle, OwnedUiModelInfo, OwnedUiOverlay, OwnedUiPromptSuggestionGeneratorPort, OwnedUiPromptSuggestionIdentity, OwnedUiPromptSuggestionReasoning, OwnedUiPromptSuggestionRequest, OwnedUiPromptSuggestionResult, OwnedUiPromptSuggestionState, OwnedUiQuitEffect, OwnedUiQuitOutroSettings, OwnedUiQuitOutroSettingsPort, OwnedUiSessionId, OwnedUiSessionViewModel, OwnedUiSeverity, OwnedUiSlotId, OwnedUiSnapshot, OwnedUiStatusView, OwnedUiTerminalSurface, OwnedUiThinkingLevel, OwnedUiToolRendering, OwnedUiToolResultPart, OwnedUiToolState, OwnedUiTranscriptBlock, OwnedUiTranscriptBlockKind, OwnedUiTranscriptImageReference, OwnedUiUsageView, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, } from "./model.js";
4
+ export type { OwnedUiBlockStatus, OwnedUiCommand, OwnedUiCommandOutcome, OwnedUiCorrelationId, OwnedUiCustomization, OwnedUiDiagnostics, OwnedUiDialog, OwnedUiEditorState, OwnedUiEntityId, OwnedUiEvent, OwnedUiFooterView, OwnedUiImageAttachment, OwnedUiLifecycle, OwnedUiModelInfo, OwnedUiOverlay, OwnedUiPromptSuggestionGeneratorPort, OwnedUiPromptSuggestionIdentity, OwnedUiPromptSuggestionReasoning, OwnedUiPromptSuggestionRequest, OwnedUiPromptSuggestionResult, OwnedUiPromptSuggestionState, OwnedUiQuitOutroSettings, OwnedUiQuitOutroSettingsPort, OwnedUiSessionId, OwnedUiSessionViewModel, OwnedUiSeverity, OwnedUiSlotId, OwnedUiSnapshot, OwnedUiStatusView, OwnedUiTerminalSurface, OwnedUiThinkingLevel, OwnedUiToolRendering, OwnedUiToolResultPart, OwnedUiToolState, OwnedUiTranscriptBlock, OwnedUiTranscriptBlockKind, OwnedUiTranscriptImageReference, OwnedUiUsageView, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, } from "./model.js";
5
5
  export { acceptsTranscriptUpdate, transcriptToolState } from "./transcript-lifecycle.js";
6
6
  export { ImageAttachmentError, MAX_CLIPBOARD_TEXT_BYTES, MAX_IMAGE_DATA_BYTES, MAX_PROMPT_IMAGES, assertImageEncodedSize, assertPromptImages, } from "./image-attachments.js";
7
7
  export { CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, normalizePromptSuggestionCandidate } from "./prompt-suggestions.js";
@@ -64,14 +64,10 @@ export interface OwnedUiViewportSettingsPort {
64
64
  snapshot(): OwnedUiViewportSettings;
65
65
  onChange(listener: (settings: OwnedUiViewportSettings) => void): () => void;
66
66
  }
67
- export type OwnedUiQuitEffect = "fall" | "dissolve" | "starburst" | "waves";
68
67
  /** Profile-local quit outro choice, read at the moment bare A1 quits. */
69
68
  export interface OwnedUiQuitOutroSettings {
70
- /** False leaves the terminal immediately; the effect and duration are then ignored. */
69
+ /** False leaves the terminal immediately; true plays the shell's fixed effect. */
71
70
  readonly enabled: boolean;
72
- readonly effect: OwnedUiQuitEffect;
73
- /** Requested playback length; the player clamps it to its supported range. */
74
- readonly durationMs: number;
75
71
  }
76
72
  export interface OwnedUiQuitOutroSettingsPort {
77
73
  snapshot(): OwnedUiQuitOutroSettings;
@@ -4,3 +4,6 @@ export { createConsoleProjectTrustPrompt } from "./project-trust-prompt.js";
4
4
  export type { ConsoleProjectTrustPromptOptions, OwnedProjectTrustPrompt, OwnedProjectTrustPromptRequest } from "./project-trust-prompt.js";
5
5
  export { createConsoleSessionForkPrompt } from "./session-fork-prompt.js";
6
6
  export { SETTINGS_APP_ID, SETTINGS_ROUTE, SETTINGS_SHORTCUTS, SettingsApp } from "./settings-app.js";
7
+ export { REFERENCE_SCREEN_SHORTCUTS, ReferenceScreenApp } from "./reference-screen-app.js";
8
+ export type { ReferenceDocumentProvider, ReferenceScreenOptions } from "./reference-screen-app.js";
9
+ export { CHANGELOG_APP_ID, CHANGELOG_ROUTE, CHANGELOG_TITLE, HOTKEYS_APP_ID, HOTKEYS_ROUTE, HOTKEYS_TITLE, } from "./reference-routes.js";
@@ -2,3 +2,5 @@ export { runOwnedUi, terminateOwnedUiProcess } from "./run.js";
2
2
  export { createConsoleProjectTrustPrompt } from "./project-trust-prompt.js";
3
3
  export { createConsoleSessionForkPrompt } from "./session-fork-prompt.js";
4
4
  export { SETTINGS_APP_ID, SETTINGS_ROUTE, SETTINGS_SHORTCUTS, SettingsApp } from "./settings-app.js";
5
+ export { REFERENCE_SCREEN_SHORTCUTS, ReferenceScreenApp } from "./reference-screen-app.js";
6
+ export { CHANGELOG_APP_ID, CHANGELOG_ROUTE, CHANGELOG_TITLE, HOTKEYS_APP_ID, HOTKEYS_ROUTE, HOTKEYS_TITLE, } from "./reference-routes.js";