samagotchi 0.152.0 → 0.154.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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -1
  3. data/README.md +2 -3
  4. data/bin/chi +23 -24
  5. data/docs/architecture.md +30 -33
  6. data/docs/broadcast.md +5 -5
  7. data/docs/bundle-authoring.md +1 -1
  8. data/docs/bundles.md +93 -12
  9. data/docs/cli.md +83 -99
  10. data/docs/configuration.md +46 -40
  11. data/docs/context.md +3 -3
  12. data/docs/desktop.md +1 -1
  13. data/docs/guardrails.md +15 -15
  14. data/docs/hooks.md +13 -12
  15. data/docs/internals/anthropic.md +3 -3
  16. data/docs/internals/attached-context.md +5 -2
  17. data/docs/internals/background-tasks.md +3 -4
  18. data/docs/internals/block-renderers.md +4 -0
  19. data/docs/internals/bridge.md +3 -3
  20. data/docs/internals/context-telemetry.md +1 -1
  21. data/docs/internals/engine.md +42 -12
  22. data/docs/internals/objectives.md +6 -5
  23. data/docs/internals/participants.md +8 -8
  24. data/docs/internals/prompt-caching.md +8 -4
  25. data/docs/internals/remote-sessions.md +7 -7
  26. data/docs/internals/web-frontend.md +20 -17
  27. data/docs/internals/web-push.md +1 -1
  28. data/docs/memory.md +2 -2
  29. data/docs/plugins.md +52 -43
  30. data/docs/releasing.md +1 -1
  31. data/docs/sessions.md +25 -25
  32. data/docs/sub-agent.md +2 -2
  33. data/docs/system-one.md +1 -1
  34. data/docs/testing.md +18 -8
  35. data/lib/samagotchi/answer_text.rb +34 -0
  36. data/lib/samagotchi/bridge.rb +43 -32
  37. data/lib/samagotchi/bridge_client.rb +10 -9
  38. data/lib/samagotchi/broadcast/recipients.rb +2 -2
  39. data/lib/samagotchi/broadcast/triage.rb +1 -1
  40. data/lib/samagotchi/broadcast_command.rb +5 -5
  41. data/lib/samagotchi/bundle_command/try/command.rb +1 -1
  42. data/lib/samagotchi/bundle_command/try/run.rb +1 -1
  43. data/lib/samagotchi/bundle_command.rb +2 -2
  44. data/lib/samagotchi/bundles/decision-card/manifest.yml +4 -4
  45. data/lib/samagotchi/bundles/decision-card/plugin.rb +1 -9
  46. data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
  47. data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -4
  48. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  49. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  50. data/lib/samagotchi/bundles/web-search/manifest.yml +13 -0
  51. data/lib/samagotchi/bundles/web-search/plugin.rb +174 -0
  52. data/lib/samagotchi/child_move.rb +3 -3
  53. data/lib/samagotchi/commands/registry.rb +6 -6
  54. data/lib/samagotchi/composer_route.rb +4 -5
  55. data/lib/samagotchi/config.rb +14 -12
  56. data/lib/samagotchi/context_candidate_choice.rb +1 -1
  57. data/lib/samagotchi/context_candidates.rb +4 -3
  58. data/lib/samagotchi/continue_offer.rb +1 -1
  59. data/lib/samagotchi/debug_log.rb +1 -1
  60. data/lib/samagotchi/degrade.rb +75 -0
  61. data/lib/samagotchi/desktop/macos/ChiRunner.swift +2 -2
  62. data/lib/samagotchi/engine/cards.rb +87 -0
  63. data/lib/samagotchi/engine/context_judge.rb +138 -0
  64. data/lib/samagotchi/engine/events.rb +131 -0
  65. data/lib/samagotchi/engine/guardrails.rb +87 -0
  66. data/lib/samagotchi/engine/hooks.rb +119 -0
  67. data/lib/samagotchi/engine/messages.rb +124 -0
  68. data/lib/samagotchi/engine/model_settings.rb +127 -0
  69. data/lib/samagotchi/engine/models.rb +261 -0
  70. data/lib/samagotchi/engine/objectives.rb +116 -0
  71. data/lib/samagotchi/engine/participants.rb +86 -0
  72. data/lib/samagotchi/engine/plugins.rb +208 -0
  73. data/lib/samagotchi/engine/questions.rb +98 -0
  74. data/lib/samagotchi/engine/recap.rb +130 -0
  75. data/lib/samagotchi/engine/session.rb +131 -0
  76. data/lib/samagotchi/engine/side_requests.rb +135 -0
  77. data/lib/samagotchi/engine/steering.rb +134 -0
  78. data/lib/samagotchi/engine/toolkit.rb +269 -0
  79. data/lib/samagotchi/engine/turn_end.rb +180 -0
  80. data/lib/samagotchi/engine/turn_helpers.rb +160 -0
  81. data/lib/samagotchi/engine/verify.rb +153 -0
  82. data/lib/samagotchi/engine.rb +63 -2642
  83. data/lib/samagotchi/grants.rb +575 -0
  84. data/lib/samagotchi/guardrail_wiring.rb +2 -2
  85. data/lib/samagotchi/guardrails/context.rb +2 -3
  86. data/lib/samagotchi/guardrails/parent_approvals.rb +2 -2
  87. data/lib/samagotchi/guardrails/parent_policy.rb +2 -2
  88. data/lib/samagotchi/guardrails/review_boundary.rb +4 -3
  89. data/lib/samagotchi/hook_routes.rb +3 -3
  90. data/lib/samagotchi/idle_recap.rb +4 -5
  91. data/lib/samagotchi/idle_reminders.rb +3 -4
  92. data/lib/samagotchi/iteration_limit.rb +2 -2
  93. data/lib/samagotchi/kernel_loop.rb +10 -11
  94. data/lib/samagotchi/launch_mode.rb +13 -32
  95. data/lib/samagotchi/level_setting_command.rb +3 -2
  96. data/lib/samagotchi/llm_context_command.rb +3 -2
  97. data/lib/samagotchi/llm_context_stale.rb +2 -2
  98. data/lib/samagotchi/log.rb +7 -7
  99. data/lib/samagotchi/log_line.rb +1 -1
  100. data/lib/samagotchi/log_path.rb +1 -1
  101. data/lib/samagotchi/message_parts.rb +3 -2
  102. data/lib/samagotchi/note_command.rb +1 -1
  103. data/lib/samagotchi/note_delivery.rb +18 -8
  104. data/lib/samagotchi/objectives.rb +24 -3
  105. data/lib/samagotchi/objectives_check.rb +8 -27
  106. data/lib/samagotchi/objectives_command.rb +4 -69
  107. data/lib/samagotchi/objectives_edit.rb +87 -0
  108. data/lib/samagotchi/output_formatter.rb +7 -1
  109. data/lib/samagotchi/owner_lock.rb +7 -1
  110. data/lib/samagotchi/parent_answers_command.rb +5 -4
  111. data/lib/samagotchi/participant_exposure.rb +42 -0
  112. data/lib/samagotchi/participant_mode.rb +1 -1
  113. data/lib/samagotchi/participant_name.rb +1 -1
  114. data/lib/samagotchi/participants.rb +17 -17
  115. data/lib/samagotchi/participants_watch.rb +1 -1
  116. data/lib/samagotchi/pending_input_queue.rb +2 -2
  117. data/lib/samagotchi/plugin/api.rb +3 -2
  118. data/lib/samagotchi/plugin/context.rb +21 -7
  119. data/lib/samagotchi/plugin/peer.rb +2 -1
  120. data/lib/samagotchi/plugin/sessions.rb +6 -4
  121. data/lib/samagotchi/plugin_tasks.rb +1 -2
  122. data/lib/samagotchi/private_files.rb +29 -0
  123. data/lib/samagotchi/project_env.rb +2 -2
  124. data/lib/samagotchi/question_desk.rb +8 -7
  125. data/lib/samagotchi/recap_command.rb +2 -1
  126. data/lib/samagotchi/recap_setup.rb +1 -1
  127. data/lib/samagotchi/recap_store.rb +1 -1
  128. data/lib/samagotchi/redact.rb +86 -0
  129. data/lib/samagotchi/reminder_queue.rb +2 -2
  130. data/lib/samagotchi/remote_attach_command.rb +1 -1
  131. data/lib/samagotchi/reply_wait/poll.rb +2 -2
  132. data/lib/samagotchi/review_start.rb +1 -1
  133. data/lib/samagotchi/run_options.rb +6 -6
  134. data/lib/samagotchi/self_report.rb +42 -39
  135. data/lib/samagotchi/send_command/busy.rb +1 -1
  136. data/lib/samagotchi/send_command.rb +4 -1
  137. data/lib/samagotchi/session.rb +11 -2
  138. data/lib/samagotchi/session_access/contract.rb +3 -3
  139. data/lib/samagotchi/session_access/local.rb +9 -9
  140. data/lib/samagotchi/session_access/remote.rb +17 -7
  141. data/lib/samagotchi/session_access.rb +4 -4
  142. data/lib/samagotchi/session_archive_command.rb +2 -2
  143. data/lib/samagotchi/session_chain.rb +8 -6
  144. data/lib/samagotchi/session_commands.rb +45 -48
  145. data/lib/samagotchi/session_continue.rb +1 -1
  146. data/lib/samagotchi/session_delete_command.rb +4 -4
  147. data/lib/samagotchi/session_manager.rb +59 -36
  148. data/lib/samagotchi/session_metrics.rb +2 -2
  149. data/lib/samagotchi/session_model_settings.rb +3 -2
  150. data/lib/samagotchi/session_participants.rb +1 -1
  151. data/lib/samagotchi/session_retention.rb +3 -3
  152. data/lib/samagotchi/sessions_command.rb +4 -4
  153. data/lib/samagotchi/share_command.rb +10 -10
  154. data/lib/samagotchi/steer.rb +1 -1
  155. data/lib/samagotchi/system_prompt.rb +102 -109
  156. data/lib/samagotchi/task_watch.rb +1 -1
  157. data/lib/samagotchi/terminal_ui/attach_launcher.rb +72 -10
  158. data/lib/samagotchi/terminal_ui/attached_loop/composer.rb +12 -9
  159. data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +16 -0
  160. data/lib/samagotchi/terminal_ui/attached_loop/questions.rb +2 -3
  161. data/lib/samagotchi/terminal_ui/attached_loop.rb +17 -8
  162. data/lib/samagotchi/terminal_ui/attached_view.rb +2 -3
  163. data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -5
  164. data/lib/samagotchi/terminal_ui/formatting.rb +7 -7
  165. data/lib/samagotchi/terminal_ui/input_support.rb +1 -7
  166. data/lib/samagotchi/terminal_ui/line_reader.rb +2 -2
  167. data/lib/samagotchi/terminal_ui/live_region.rb +2 -2
  168. data/lib/samagotchi/terminal_ui/log_tail.rb +135 -0
  169. data/lib/samagotchi/terminal_ui/one_shot_sink.rb +2 -2
  170. data/lib/samagotchi/terminal_ui/plain_surface.rb +1 -1
  171. data/lib/samagotchi/terminal_ui/question_prompt.rb +4 -5
  172. data/lib/samagotchi/terminal_ui/reline_seam.rb +3 -3
  173. data/lib/samagotchi/terminal_ui/status_row.rb +7 -8
  174. data/lib/samagotchi/terminal_ui.rb +142 -1387
  175. data/lib/samagotchi/title_command.rb +2 -2
  176. data/lib/samagotchi/tool_runner.rb +14 -16
  177. data/lib/samagotchi/toolkit.rb +8 -5
  178. data/lib/samagotchi/toolkit_binding.rb +30 -5
  179. data/lib/samagotchi/toolkit_command.rb +2 -1
  180. data/lib/samagotchi/toolkit_report_command.rb +9 -5
  181. data/lib/samagotchi/toolkit_suggest.rb +12 -6
  182. data/lib/samagotchi/tools/builtin_calls.rb +14 -10
  183. data/lib/samagotchi/tools/deferred_tools.rb +2 -2
  184. data/lib/samagotchi/tools/delegate.rb +3 -1
  185. data/lib/samagotchi/tools/delegate_cursor.rb +3 -3
  186. data/lib/samagotchi/tools/delegate_wait.rb +1 -1
  187. data/lib/samagotchi/tools/objectives_tool.rb +2 -2
  188. data/lib/samagotchi/tools/registry.rb +44 -20
  189. data/lib/samagotchi/tools/send_note.rb +2 -1
  190. data/lib/samagotchi/tools/tool_index.rb +27 -21
  191. data/lib/samagotchi/turn_endings.rb +2 -2
  192. data/lib/samagotchi/turn_flow.rb +4 -5
  193. data/lib/samagotchi/update_hint.rb +2 -2
  194. data/lib/samagotchi/version.rb +1 -1
  195. data/lib/samagotchi/web/app.rb +39 -50
  196. data/lib/samagotchi/web/participant_access.rb +2 -2
  197. data/lib/samagotchi/web/participant_review.rb +5 -5
  198. data/lib/samagotchi/web/participant_stream_filter.rb +8 -8
  199. data/lib/samagotchi/web/participant_view.rb +2 -82
  200. data/lib/samagotchi/web/participants_admin.rb +1 -1
  201. data/lib/samagotchi/web/public/app.js +61 -357
  202. data/lib/samagotchi/web/public/block_host.js +35 -8
  203. data/lib/samagotchi/web/public/block_renderers.css +9 -5
  204. data/lib/samagotchi/web/public/chains.js +2 -2
  205. data/lib/samagotchi/web/public/claim_link.js +4 -4
  206. data/lib/samagotchi/web/public/composer_mode.js +110 -0
  207. data/lib/samagotchi/web/public/format.js +3 -3
  208. data/lib/samagotchi/web/public/owner_claim.js +81 -0
  209. data/lib/samagotchi/web/public/owner_verify.js +112 -0
  210. data/lib/samagotchi/web/public/session_fields.js +3 -3
  211. data/lib/samagotchi/web/public/strip_grip.js +3 -3
  212. data/lib/samagotchi/web/public/strip_hidden.js +65 -0
  213. data/lib/samagotchi/web/public/version_notices.js +132 -0
  214. data/lib/samagotchi/web/push/store.rb +1 -1
  215. data/lib/samagotchi/web/session_access_routes.rb +5 -4
  216. data/lib/samagotchi/web/session_control.rb +12 -8
  217. data/lib/samagotchi/web/session_create.rb +1 -1
  218. data/lib/samagotchi/web/session_hub.rb +2 -2
  219. data/lib/samagotchi/web/session_show.rb +2 -2
  220. data/lib/samagotchi/web/session_summary.rb +1 -1
  221. data/lib/samagotchi/worker.rb +106 -9
  222. data/lib/samagotchi/worker_link.rb +3 -3
  223. data/lib/samagotchi/working_set_command.rb +3 -2
  224. metadata +36 -6
  225. data/lib/samagotchi/terminal_ui/between_turns.rb +0 -138
  226. data/lib/samagotchi/terminal_ui/repl_input.rb +0 -152
  227. data/lib/samagotchi/web/grants.rb +0 -575
  228. data/lib/samagotchi/web/participant_exposure.rb +0 -44
  229. data/lib/samagotchi/web/private_files.rb +0 -31
@@ -38,7 +38,7 @@ import { createParticipantsControl } from "./participants_panel.js";
38
38
  import { createSettingsControl } from "./settings_chip.js";
39
39
  import { renderInfoBar } from "./info_bar.js";
40
40
  import { createMoreSheet } from "./more_sheet.js";
41
- import { sessionBar, workerBadgeFor } from "./info_bar_model.js";
41
+ import { sessionBar } from "./info_bar_model.js";
42
42
  import { createShownTexts } from "./shown_texts.js";
43
43
  import { emptySessionState, sessionStateFrom } from "./session_state.js";
44
44
  import { sessionFieldsFrom } from "./session_payload.js";
@@ -63,13 +63,12 @@ import { createTaskStopper } from "./task_stop.js";
63
63
  import { createSystemOneWrongAction } from "./system_one_wrong.js";
64
64
  import { appendQuote } from "./annotations.js";
65
65
  import { applyComposerText, applyRowTemplate, fetchApply } from "./review_apply.js";
66
- import { CHECK_NOT_ON_PAGE, nameVerdictCards, ownerCheckHash } from "./verdict_card.js";
67
- import { freshURL, placeVerdictWithFresh, redrawFresh, settleRunning } from "./verdict_fresh.js";
66
+ import { nameVerdictCards } from "./verdict_card.js";
67
+ import { redrawFresh } from "./verdict_fresh.js";
68
68
  import { authorBadge, nameUnnamedBadges } from "./participant_names.js";
69
69
  import { parsePresets } from "./annotate_presets.js";
70
70
  import { createAnnotateBar } from "./annotate_bar.js";
71
71
  import { VERIFY_PRESET, VERIFY_UNAVAILABLE, verifyWorkerUnavailable, withVerify } from "./verify_preset.js";
72
- import { OWNER_FINAL, verifyTurnOf } from "./verify_target.js";
73
72
  import { actionLine, annotationActions, composerWithAction } from "./annotate_actions.js";
74
73
  import { createBlockHost } from "./block_host.js";
75
74
  import { copyText, installCopy } from "./copy.js";
@@ -80,24 +79,21 @@ import { createQuestionCards } from "./question_cards.js";
80
79
  import { splitSnapshotCards } from "./card.js";
81
80
  import { PROMPT_LINE_PX, PROMPT_MIN_PX } from "./composer_size.js";
82
81
  import { mountComposer } from "./composer_mount.js";
83
- import { readStored, writeStored } from "./storage.js";
84
82
  import { createCommandList } from "./command_list.js";
85
83
  import { ALL_SESSIONS_HASH, sessionHash } from "./route.js";
86
84
  import { hashIntent } from "./hash_intent.js";
87
- import {
88
- claimFromHash, ownerClaimHash, participantClaimLink, selectionClaim, showClaim, turnIdOf,
89
- } from "./claim_link.js";
85
+ import { claimFromHash } from "./claim_link.js";
86
+ import { createOwnerClaim } from "./owner_claim.js";
87
+ import { createOwnerVerify } from "./owner_verify.js";
90
88
  import { scopeDir, scopeTemplate } from "./scope.js";
91
89
  import { createProjectPicker } from "./project_picker.js";
92
- import { html, render } from "./vendor/lit-html.js";
90
+ import { render } from "./vendor/lit-html.js";
93
91
  import { shouldFollowScroll } from "./scroll.js";
94
- import { SHORT_WINDOW_QUERY, stripAutoHidden } from "./strip.js";
95
- import { createStripGrip } from "./strip_grip.js";
92
+ import { createStripHidden } from "./strip_hidden.js";
96
93
  import { waitingOn } from "./notify.js";
97
94
  import { applySessionEvent, sortedByUpdated } from "./sessions_list.js";
98
95
  import { createNotifications } from "./notifications.js";
99
96
  import { renameShown, tabTitle, topBarTitle } from "./session_title.js";
100
- import { mergeVersionState, restartConfirmText, updateFrameToast, versionNotice } from "./update_notice.js";
101
97
  import {
102
98
  appendAboveLiveTiming, mergeTiming, normalizeTiming,
103
99
  } from "./timing.js";
@@ -112,6 +108,7 @@ import { createComposerCommands } from "./composer.js";
112
108
  import { createStreamHandlers } from "./stream_handlers.js";
113
109
  import { createListsView } from "./lists_view.js";
114
110
  import { createDeliveryControl, QUEUE_NEEDS_WORKER } from "./delivery_line.js";
111
+ import { createComposerMode } from "./composer_mode.js";
115
112
  import { liveSlots } from "./stage_model.js";
116
113
  import { currentGen } from "./turn_model.js";
117
114
  import { toolName } from "./activity.js";
@@ -121,7 +118,7 @@ import { createUnqueue } from "./unqueue.js";
121
118
  import { createStartPage } from "./start_page.js";
122
119
  import { createSessionActions } from "./session_actions.js";
123
120
  import { createStopSheet } from "./stop_sheet.js";
124
- import { createUpdateSheet } from "./update_sheet.js";
121
+ import { createVersionNotices } from "./version_notices.js";
125
122
  import { createRename } from "./session_rename.js";
126
123
  import { createSendTurn } from "./send_turn.js";
127
124
  import { createMarkdownTip } from "./markdown_tip.js";
@@ -251,7 +248,7 @@ const featureGates = createFeatureGates([
251
248
  {
252
249
  name: "verify",
253
250
  unavailable: (cur) => verifyWorkerUnavailable(cur.workerFeatures, cur.bridgeUp),
254
- redraw: () => redrawFresh(historyEl, verifyFreshState()),
251
+ redraw: () => redrawFresh(historyEl, ownerVerify.freshState()),
255
252
  },
256
253
  // Revise on an answer and the composer's Revising chip (revise_stack.js).
257
254
  {
@@ -263,7 +260,6 @@ const featureGates = createFeatureGates([
263
260
  featureGates.prime(store.current);
264
261
  // The open session's id, for the stale-answer guards (latest.js stillOn).
265
262
  const openId = () => store.selected;
266
- let pendingVerifyAnchor = null;
267
263
  // The composer's armed Revise ({turn_id, label}: the answer the next
268
264
  // message rewrites), shown as its chip; Verify's lifecycle (an emptied
269
265
  // composer, another session, the send's ack let go of it). Set only
@@ -528,9 +524,9 @@ function openSessionEvents() {
528
524
  // The list redraws the lists and the bar (its children chip, its chain's
529
525
  // ← → links): store.update({sessions}).
530
526
  snapshot: (data) => store.batch(() => {
531
- noticeVersions(data);
532
- if (updateSheet.reconnected(data.version)) showToast(`chi web reconnected on chi ${data.version}`);
533
- if (data.chi_update) updateFrame(data.chi_update, { snapshot: true });
527
+ versionNotices.notice(data);
528
+ versionNotices.reconnected(data.version);
529
+ if (data.chi_update) versionNotices.frame(data.chi_update, { snapshot: true });
534
530
  notifications.noticeSessions("snapshot", data);
535
531
  store.update({ sessions: applySessionEvent(store.sessions, "snapshot", data) });
536
532
  sweepFoldsOnce();
@@ -548,8 +544,8 @@ function openSessionEvents() {
548
544
  store.update({ sessions: applySessionEvent(store.sessions, "session_gone", data) });
549
545
  leaveGoneSession(data.id, "deleted");
550
546
  },
551
- chi: (data) => noticeVersions(data),
552
- chi_update: (data) => updateFrame(data),
547
+ chi: (data) => versionNotices.notice(data),
548
+ chi_update: (data) => versionNotices.frame(data),
553
549
  }, {
554
550
  dir: scope.dir,
555
551
  onError: () => {
@@ -570,72 +566,6 @@ function openSessionEvents() {
570
566
 
571
567
  const EVENTS_REOPEN_MS = 3000;
572
568
 
573
- // The chi versions carried by each event snapshot and `chi` frame. A toast
574
- // says when chi web was upgraded, a newer one is installed, or an aged one
575
- // is available to update to.
576
- // Once per notice per tab: every reconnect's snapshot says it again.
577
- const versions = { served: null, installed: null };
578
- // The sessions whose Restart is under way (their button says so): one
579
- // session's restart never blocks another's.
580
- const restartingIds = new Set();
581
- // An available release's notice is once per version per browser, not per
582
- // tab: chi web says it in every snapshot until the owner updates.
583
- const AVAILABLE_SHOWN_KEY = "chi_update_available_shown";
584
- const versionNoticesShown = new Set();
585
- const availableShown = readStored(AVAILABLE_SHOWN_KEY);
586
- if (availableShown) versionNoticesShown.add(`available:${availableShown}`);
587
- let versionToast = null;
588
-
589
- function noticeVersions(data) {
590
- Object.assign(versions, mergeVersionState(versions, data));
591
- const notice = versionNotice({ loaded: document.body.dataset.version, served: versions.served,
592
- installed: versions.installed, available: versions.available,
593
- updateBlocked: versions.updateBlocked, auto: versions.auto, restart: versions.restart,
594
- autoOffers: versions.autoOffers, shown: versionNoticesShown });
595
- store.touch("infoBar"); // the worker badge compares with the newest installed
596
- if (!notice) return;
597
- versionNoticesShown.add(notice.key);
598
- if (notice.key.startsWith("available:")) writeStored(AVAILABLE_SHOWN_KEY, notice.key.slice("available:".length));
599
- const text = notice.releaseUrl
600
- ? html`${notice.text} <a href=${notice.releaseUrl} target="_blank" rel="noopener noreferrer">RubyGems</a>`
601
- : notice.text;
602
- const action = notice.reload
603
- ? { label: "Reload", run: () => location.reload() }
604
- : notice.updateVersion
605
- ? { label: "Update", run: () => updateSheet.install(notice.updateVersion) }
606
- : notice.actions
607
- ? notice.actions.map((name) => ({
608
- label: name === "restartWorkers" ? "Restart workers" : "Restart chi web",
609
- run: () => (name === "restartWorkers" ? updateSheet.restartWorkers() : updateSheet.restartWeb()),
610
- }))
611
- : notice.command
612
- ? { label: "Copy", run: () => copyUpdateCommand(notice.command) }
613
- : null;
614
- // One version toast at a time: a newer notice (Reload after a restart)
615
- // supersedes the one before it.
616
- toasts.dismiss(versionToast);
617
- versionToast = toasts.show(text, action, { sticky: true });
618
- }
619
-
620
- function updateFrame(data, { snapshot = false } = {}) {
621
- const wasOpen = updateSheet.isOpen();
622
- if (!updateSheet.receive(data)) return;
623
- if (data.operation === "install" && data.state === "done" && data.version) noticeVersions({ installed: data.version });
624
- const text = updateFrameToast(data, { wasOpen, snapshot });
625
- if (text) showToast(text, { label: "View", run: () => updateSheet.view() }, { sticky: true });
626
- }
627
-
628
- function copyUpdateCommand(command) {
629
- if (!navigator.clipboard?.writeText) {
630
- showToast(`Clipboard unavailable; run ${command}`);
631
- return;
632
- }
633
- navigator.clipboard.writeText(command).then(
634
- () => showToast(`Copied ${command}`),
635
- () => showToast(`Couldn't copy ${command}`),
636
- );
637
- }
638
-
639
569
  // The selected session's card changed: its owner and, without a live
640
570
  // stream, its status show in the info bar; a worker whose bridge is up
641
571
  // (a turn another client sent woke one, or a replacement after a kill)
@@ -798,60 +728,18 @@ function setSessionHash(id) {
798
728
  // the composer stop addressing the session the URL has left behind.
799
729
  function selectFromHash() {
800
730
  const claim = claimFromHash(location.hash);
801
- if (claim) pendingClaim = claim;
731
+ if (claim) ownerClaim.pending(claim);
802
732
  const intent = hashIntent(location.hash, { selected: store.selected, claim });
803
733
  if (intent.kind === "session" && intent.id !== store.selected) select(intent.id);
804
- else if (claim) showPendingClaim();
734
+ else if (claim) ownerClaim.showPending();
805
735
  else if (intent.kind === "start") clearSelection();
806
736
  }
807
- let pendingClaim = null;
808
- // Where a claim's text can be on this page: answers, own messages, steps.
809
- const CLAIM_ROOTS = ".bubble.output, .bubble.user .user-message, .gen-text";
810
- // The claim link's quote scrolled to and marked, its text read as the
811
- // participant page shows it (the session's folder relative, home ~); a
812
- // quote not found marks its turn; neither found: the top, and a toast.
813
- // The hash is the session's again (select() already set it when it
814
- // switched), so the same link opened again is a hashchange.
815
- function showPendingClaim() {
816
- const claim = pendingClaim;
817
- if (!claim || claim.sessionId !== store.selected) return;
818
- pendingClaim = null;
819
- if (!allViewOpen() && location.hash !== sessionHash(store.selected)) {
820
- history.replaceState(null, "", location.pathname + location.search + sessionHash(store.selected));
821
- }
822
- if (showClaim(historyEl, claim, { roots: CLAIM_ROOTS, opts: claimOpts() })) return;
823
- historyEl.scrollTop = 0;
824
- showToast("The linked turn isn't in this session.");
825
- }
826
- // How the participant page shows this page's text (claim_link.js).
827
- function claimOpts() {
828
- return { home: document.body.dataset.home || "", cwd: store.current.dir || "" };
829
- }
830
- // Annotate's "Copy link" for a selection in a turn's answer, step or own
831
- // message: the participant page's link when the session has a person
832
- // active (it opens in the browser their review link was opened in: one
833
- // link per browser, the cookie), else this page's own.
834
- function claimLinkFor(source, range) {
835
- // Not in a Revise stack's switched copy (revise_switch.js: view only, no claim key; Verify isn't offered there either).
836
- if (!store.selected || !["answer", "step", "user"].includes(source.kind) || !historyEl.contains(source.root)
837
- || source.root.closest("[data-stack-view]")) return null;
838
- const turnId = turnIdOf(historyEl, source.root);
839
- if (!turnId) return null;
840
- const claim = { turnId, ...selectionClaim(source.root, range, claimOpts()) };
841
- if (!claim.q) return null;
842
- const target = participantsControl.claimTarget();
843
- if (!target) {
844
- return { url: `${location.origin}${location.pathname}${location.search}${ownerClaimHash(store.selected, claim)}`,
845
- title: "A link to this on your page (no person is invited to this session)" };
846
- }
847
- const whose = target.names.map((name) => `${name}'s`).join(" or ");
848
- return { url: participantClaimLink(target.base, store.selected, claim),
849
- title: `A link to this for ${whose} browser: it opens where their review link was opened (one link per browser). Opened here, it shows your page.` };
850
- }
851
- async function copyClaimLink({ url }) {
852
- if (await copyText(url)) showToast("Link copied");
853
- else showToast(`Couldn't copy: ${url}`, null, { sticky: true });
854
- }
737
+ // The page's claim links (owner_claim.js): one opened here, once drawn, and
738
+ // annotate's Copy link.
739
+ const ownerClaim = createOwnerClaim({
740
+ historyEl, store, claimTarget: () => participantsControl.claimTarget(),
741
+ toast: (...args) => showToast(...args), copyText, allViewOpen, home: document.body.dataset.home || "",
742
+ });
855
743
  function applyView() {
856
744
  const open = allViewOpen();
857
745
  if (!open) {
@@ -881,82 +769,12 @@ window.addEventListener("hashchange", () => {
881
769
  selectFromHash();
882
770
  });
883
771
 
884
- // A phone's composer fits a few words and has no Shift+Enter: the short
885
- // placeholder there.
886
- const narrowScreen = window.matchMedia("(max-width: 600px)");
887
- // An input the store doesn't hold, with the composer as its only reader: a
888
- // direct call (the bar doesn't read it).
889
- narrowScreen.addEventListener("change", () => {
890
- updateComposerMode();
891
- // ⋯ is a phone's alone: a window grown past it takes its sheet along.
892
- if (!narrowScreen.matches && moreSheet.isOpen()) moreSheet.close();
772
+ // The composer's placeholder, hint, buttons and delivery row (composer_mode.js).
773
+ const composerMode = createComposerMode({
774
+ els: { body: document.body, prompt: promptEl, hint: composeHintEl, button: actionBtn, attach: attachBtn, empty: emptyEl,
775
+ history: historyEl },
776
+ store, liveTurn, startPage, deliveryControl, moreSheet, fitPrompt, placeToast,
893
777
  });
894
- function setPlaceholder(long, short) {
895
- promptEl.placeholder = narrowScreen.matches ? short : long;
896
- }
897
-
898
- // The key hint under the composer (shown while it has focus; on the start
899
- // page only: in a session the placeholder carries it, the hint row is gone).
900
- const SEND_HINT = "Enter to send · Shift+Enter for a newline · ↑ history";
901
-
902
- let hadSession = false;
903
- // While a turn runs, Send still sends: the prompt merges into the turn at
904
- // its next step (steering). Cancel is its own button then.
905
- function updateComposerMode() {
906
- // In a session the box is one line: its minimum follows the class.
907
- if (document.body.classList.toggle("has-session", !!store.selected) !== hadSession) {
908
- hadSession = !hadSession;
909
- // Back on the start page: its remembered model, not the last link's.
910
- if (!hadSession) startPage.restoreStartPick();
911
- fitPrompt();
912
- placeToast();
913
- }
914
- startPage.syncModelPick();
915
- // The start page's choice was for a new session: opening a session drops it.
916
- if (store.selected) startPage.dropChoices();
917
- const running = liveTurn.running && !!store.selected;
918
- // No images for a session a terminal owns (it takes no message from here);
919
- // on a phone none while a turn runs either: the row keeps its room for the
920
- // message box (steering).
921
- attachBtn.hidden = (!!store.selected && store.current.owner === "tui") || (running && narrowScreen.matches);
922
- syncDeliveryLine();
923
- if (running) {
924
- setPlaceholder("Running… a message goes in at the next step · Enter to send · Alt+Enter queues", "Steer the turn…");
925
- composeHintEl.textContent = SEND_HINT;
926
- actionBtn.textContent = "Send";
927
- emptyEl.classList.add("hidden");
928
- return;
929
- }
930
- if (!store.selected) {
931
- setPlaceholder("Ask chi anything…", "Ask chi anything…");
932
- composeHintEl.textContent = "Enter to start · Shift+Enter for a newline";
933
- actionBtn.textContent = "Start";
934
- actionBtn.disabled = false;
935
- emptyEl.classList.remove("hidden");
936
- historyEl.replaceChildren();
937
- } else if (store.current.owner === "tui") {
938
- setPlaceholder("A terminal chi owns this session · run it with chi --shared to send from here", "A terminal chi owns this session");
939
- composeHintEl.textContent = "";
940
- actionBtn.textContent = "Send";
941
- actionBtn.disabled = true;
942
- emptyEl.classList.add("hidden");
943
- } else {
944
- // The keys are in the delivery line's row above.
945
- setPlaceholder("Message chi…", "Message chi…");
946
- composeHintEl.textContent = SEND_HINT;
947
- actionBtn.textContent = "Send";
948
- actionBtn.disabled = false;
949
- emptyEl.classList.add("hidden");
950
- }
951
- }
952
-
953
- // The delivery line's row in a session (delivery_line.js): the key hint
954
- // while idle, where a message goes in while the turn runs.
955
- function syncDeliveryLine() {
956
- // A terminal owns the session: the composer is off, so is its row.
957
- if (!store.selected || store.current.owner === "tui") deliveryControl.show("off");
958
- else deliveryControl.show(liveTurn.running && store.current.owner !== "tui" ? "running" : "idle");
959
- }
960
778
 
961
779
  function setTurnRunning(running) {
962
780
  // A running turn the stage held none of goes with it (runningWithoutTurn).
@@ -1015,7 +833,7 @@ function onInitEvent(type, data) {
1015
833
  // "delegated by" parent, its commands, its card actions) never shows for
1016
834
  // the next one, also when the next one's read fails.
1017
835
  function forgetSession({ keepVerify = false } = {}) {
1018
- if (!keepVerify) pendingVerifyAnchor = null;
836
+ if (!keepVerify) ownerVerify.clear();
1019
837
  if (!keepVerify) setReviseAnchor(null);
1020
838
  store.batch(() => {
1021
839
  store.replaceCurrent(emptySessionState());
@@ -1061,7 +879,7 @@ promptEl.addEventListener("input", () => noteDraft());
1061
879
  promptEl.addEventListener("input", () => {
1062
880
  if (promptEl.value.trim()) return;
1063
881
  deliveryControl.release();
1064
- pendingVerifyAnchor = null;
882
+ ownerVerify.clear();
1065
883
  setReviseAnchor(null);
1066
884
  });
1067
885
 
@@ -1147,7 +965,7 @@ async function resync({ endText = null } = {}) {
1147
965
  const kept = keptCommandBubbles(historyEl, id);
1148
966
  resetTurnView();
1149
967
  renderSessionView(data, { kept });
1150
- showPendingClaim();
968
+ ownerClaim.showPending();
1151
969
  // Preview is read-only: a worker is woken only by POST /turn, so attach
1152
970
  // only when a live bridge exists.
1153
971
  store.update({ current: { streamSeq: typeof data.last_event_seq === "number" ? (data.last_event_id || data.last_event_seq) : null } });
@@ -1276,7 +1094,7 @@ function renderHistory(items) {
1276
1094
  }
1277
1095
  if (typeof items[0] === "object" && items[0].role) {
1278
1096
  historyEl.replaceChildren(turnHistoryNodes(items, store.current.timing, { thumbs: (images) => thumbsTemplate(store.selected, images), workingSet: !!workingSet }));
1279
- renderSavedVerdicts();
1097
+ ownerVerify.renderSaved();
1280
1098
  // The live dividers count from the history's last activity.
1281
1099
  gapClock.seed(lastActivity(turnGroups(items, store.current.timing), items));
1282
1100
  linkTextRefs();
@@ -1389,7 +1207,7 @@ function endTurnView(kind, endText = null, { emptyAnswer = null, stopped = false
1389
1207
  }
1390
1208
 
1391
1209
  infoTextEl.addEventListener("click", (e) => {
1392
- if (e.target.closest(".worker-restart")) restartWorker();
1210
+ if (e.target.closest(".worker-restart")) versionNotices.restart();
1393
1211
  // Pushed as openAllView does, so the view's back returns here.
1394
1212
  if (e.target.closest(".children-chip")) {
1395
1213
  e.preventDefault();
@@ -1411,55 +1229,10 @@ function placeApply(data) {
1411
1229
  appendToHistory(elementOf(applyRowTemplate(data.turn_id, badge)));
1412
1230
  }
1413
1231
 
1414
- function placeVerdict(record) {
1415
- if (!record) return;
1416
- const known = (store.current.verdicts || []).some((item) => item.check_turn_id === record.check_turn_id);
1417
- const patch = {};
1418
- if (!known) patch.verdicts = [...(store.current.verdicts || []), record];
1419
- // A fresh verdict ends its check's "verifying fresh" (the card shows it).
1420
- if (record.fresh) patch.verifyFresh = settleRunning(store.current.verifyFresh, record);
1421
- store.update({ current: patch });
1422
- const badge = record.by && authorBadge(record.by);
1423
- placeVerdictWithFresh(historyEl, record, {
1424
- who: badge ? badge.name : "You",
1425
- author: record.by || null,
1426
- roots: CLAIM_ROOTS,
1427
- opts: claimOpts(),
1428
- onMissingCheck: (missing) => {
1429
- const hash = ownerCheckHash(store.sessions, store.selected, missing.check_turn_id);
1430
- if (hash) location.hash = hash;
1431
- else showToast(CHECK_NOT_ON_PAGE);
1432
- },
1433
- }, verifyFreshState());
1434
- }
1435
-
1436
- // The owner's Verify fresh on an unclear verdict's card (verdict_fresh.js):
1437
- // always theirs to start; its running check links to the child's session.
1438
- const verifyFreshErrors = {};
1439
- function verifyFreshState() {
1440
- const id = store.selected;
1441
- const unavailable = verifyWorkerUnavailable(store.current.workerFeatures, store.current.bridgeUp);
1442
- return {
1443
- verdicts: store.current.verdicts || [],
1444
- running: store.current.verifyFresh || [],
1445
- errors: verifyFreshErrors,
1446
- canStart: !!id && !unavailable,
1447
- unavailableReason: unavailable ? VERIFY_UNAVAILABLE : null,
1448
- start: async (of) => {
1449
- try {
1450
- const body = await api(freshURL(id, of), { method: "POST", body: "{}" });
1451
- return { ok: true, child_id: body.child_id || null };
1452
- } catch (error) {
1453
- return { ok: false, error: error.message };
1454
- }
1455
- },
1456
- childHref: (childId) => sessionHash(childId),
1457
- };
1458
- }
1459
-
1460
- function renderSavedVerdicts() {
1461
- for (const record of store.current.verdicts || []) placeVerdict(record);
1462
- }
1232
+ // The owner's Verify (owner_verify.js): verdict cards, Verify fresh, the claim.
1233
+ const ownerVerify = createOwnerVerify({
1234
+ historyEl, store, api, claim: ownerClaim, liveTurn, toast: (text) => showToast(text),
1235
+ });
1463
1236
 
1464
1237
  // Apply: the owner's Apply turn into the composer, after what is typed
1465
1238
  // there; never sent from here.
@@ -1482,31 +1255,6 @@ historyEl.addEventListener("click", async (e) => {
1482
1255
  }
1483
1256
  });
1484
1257
 
1485
- // The open session's worker badge (info_bar_model.js workerBadgeFor).
1486
- function currentWorkerBadge() {
1487
- return workerBadgeFor(store.selected, store.current, versions);
1488
- }
1489
-
1490
- // Restart: the session moves to a new worker on the newest chi; the page
1491
- // follows it (its bridge_up), the composer's draft stays as it is.
1492
- async function restartWorker() {
1493
- const badge = currentWorkerBadge();
1494
- if (!badge || !badge.restart || restartingIds.has(store.selected)) return;
1495
- if (!confirm(restartConfirmText(store.current.workerVersion, badge.newest))) return;
1496
- const id = store.selected;
1497
- restartingIds.add(id);
1498
- store.touch("infoBar");
1499
- try {
1500
- const result = await restartSession(id);
1501
- showToast(result.version ? `Restarted on chi ${result.version}` : "Restarting: the new worker is starting");
1502
- } catch (e) {
1503
- showToast(cantText("restart", e), null, { sticky: true });
1504
- } finally {
1505
- restartingIds.delete(id);
1506
- store.touch("infoBar");
1507
- }
1508
- }
1509
-
1510
1258
  // The session bar (info_bar_model.js sessionBar): its buttons, and the
1511
1259
  // info line rendered by info_bar.js.
1512
1260
  // The open session waits on the user: its card is up, or its summary says so
@@ -1518,8 +1266,8 @@ function sessionWaits() {
1518
1266
  function updateInfoBar() {
1519
1267
  annotateBar?.refresh();
1520
1268
  const { buttons, bar } = sessionBar({
1521
- id: store.selected, state: store.current, sessions: store.sessions, versions, cardTitle: (s) => lists.cardTitle(s),
1522
- continuing: sessionActions.continuing, restarting: restartingIds, streaming: !!streamControl, running: liveTurn.running, waiting: sessionWaits(),
1269
+ id: store.selected, state: store.current, sessions: store.sessions, versions: versionNotices.versions(), cardTitle: (s) => lists.cardTitle(s),
1270
+ continuing: sessionActions.continuing, restarting: versionNotices.restarting, streaming: !!streamControl, running: liveTurn.running, waiting: sessionWaits(),
1523
1271
  workingSet: workingSet ? workingSet.chip() : undefined, participants: participantsControl.chip(),
1524
1272
  });
1525
1273
  const stopBtn = document.getElementById("infoStopBtn");
@@ -1731,8 +1479,8 @@ const streamHandlers = createStreamHandlers({
1731
1479
  workingSet,
1732
1480
  participantsControl,
1733
1481
  placeApply,
1734
- placeVerdict,
1735
- restoreVerify: (anchor) => { pendingVerifyAnchor = anchor; },
1482
+ placeVerdict: ownerVerify.placeVerdict,
1483
+ restoreVerify: (anchor) => ownerVerify.set(anchor),
1736
1484
  restoreRevise,
1737
1485
  sentIds,
1738
1486
  earlyRestores,
@@ -1842,9 +1590,9 @@ const sendTurnControl = createSendTurn({
1842
1590
  sentIds,
1843
1591
  earlyRestores,
1844
1592
  api: { createIdleSession, uploadImage, sendTurn },
1845
- verify: () => pendingVerifyAnchor,
1846
- clearVerify: (anchor) => { if (pendingVerifyAnchor === anchor) pendingVerifyAnchor = null; },
1847
- restoreVerify: (anchor) => { pendingVerifyAnchor = anchor; },
1593
+ verify: () => ownerVerify.anchor(),
1594
+ clearVerify: (anchor) => ownerVerify.clear(anchor),
1595
+ restoreVerify: (anchor) => ownerVerify.set(anchor),
1848
1596
  // Not while the gate refuses (the chip says so; the line goes as a plain message).
1849
1597
  revise: () => (reviseUnavailable() ? null : pendingReviseAnchor),
1850
1598
  clearRevise: (anchor) => { if (pendingReviseAnchor === anchor) setReviseAnchor(null); },
@@ -2051,11 +1799,11 @@ function restoreDraft(draft, opener) {
2051
1799
  }
2052
1800
 
2053
1801
  const toasts = createToasts({ el: $("#toast"), onChange: placeToast });
2054
- const updateSheet = createUpdateSheet({
2055
- dialog: $("#updateSheet"),
2056
- update: updateChi,
2057
- restartWorkers,
2058
- restartWeb,
1802
+ // chi's versions, the update sheet and the worker Restart (version_notices.js).
1803
+ // After toasts: openSessionEvents, its first caller, runs at boot.
1804
+ const versionNotices = createVersionNotices({
1805
+ store, toasts, dialog: $("#updateSheet"), loadedVersion: document.body.dataset.version,
1806
+ api: { updateChi, restartWorkers, restartWeb, restartSession },
2059
1807
  });
2060
1808
 
2061
1809
  function showToast(text, action = null, options = {}) { toasts.show(text, action, options); }
@@ -2066,7 +1814,7 @@ function showToast(text, action = null, options = {}) { toasts.show(text, action
2066
1814
  // quote to the composer, the session's plugins' pills
2067
1815
  // (chi.annotation, annotate_actions.js), which fill the composer with their
2068
1816
  // command line and the quote, or send it at once, and Copy link in a turn
2069
- // (claimLinkFor).
1817
+ // (owner_claim.js linkFor).
2070
1818
  function fillComposer(text) {
2071
1819
  openForVisit();
2072
1820
  promptEl.value = text;
@@ -2081,14 +1829,12 @@ annotateBar = createAnnotateBar({
2081
1829
  presets: withVerify(parsePresets(document.body.dataset.annotatePresets)),
2082
1830
  disabledPreset: (preset) => preset === VERIFY_PRESET
2083
1831
  && verifyWorkerUnavailable(store.current.workerFeatures, store.current.bridgeUp) ? VERIFY_UNAVAILABLE : null,
2084
- offeredPreset: (preset, source, range) => preset !== VERIFY_PRESET || !!verifyAnchorFor({ source, range }),
1832
+ offeredPreset: (preset, source, range) => preset !== VERIFY_PRESET || !!ownerVerify.anchorFor({ source, range }),
2085
1833
  canAnnotate: () => !!store.selected && store.current.owner !== "tui",
2086
1834
  roots: turnRoots,
2087
1835
  isLive: (root) => liveTurn.running && turnDom.containsLive(root),
2088
1836
  onQuote: (text) => fillComposer(appendQuote(promptEl.value, text)),
2089
- onPreset: (preset, fields) => {
2090
- pendingVerifyAnchor = preset === VERIFY_PRESET ? verifyAnchorFor(fields) : null;
2091
- },
1837
+ onPreset: (preset, fields) => ownerVerify.set(preset === VERIFY_PRESET ? ownerVerify.anchorFor(fields) : null),
2092
1838
  actions: () => annotationActions(store.current.commands),
2093
1839
  running: () => liveTurn.running,
2094
1840
  onAction: (action, fields) => {
@@ -2096,21 +1842,11 @@ annotateBar = createAnnotateBar({
2096
1842
  if (action.send) runSessionCommand(line);
2097
1843
  else fillComposer(composerWithAction(promptEl.value, action, line));
2098
1844
  },
2099
- link: claimLinkFor,
2100
- onLink: copyClaimLink,
1845
+ link: ownerClaim.linkFor,
1846
+ onLink: ownerClaim.copyLink,
2101
1847
  dock: () => $("#dock"),
2102
1848
  });
2103
1849
 
2104
- // Verify's claim: a selection in an ended turn's final answer (the only
2105
- // text Engine#verified_anchor takes; verify_target.js), else null and no
2106
- // Verify on the bar.
2107
- function verifyAnchorFor({ source, range }) {
2108
- if (source?.kind !== "answer" || !range) return null;
2109
- const running = liveTurn.running ? { turnId: liveTurn.timing.turnId || null } : null;
2110
- const turnId = verifyTurnOf(historyEl, source.root, OWNER_FINAL, { running });
2111
- const quote = turnId ? selectionClaim(source.root, range, claimOpts()).q : "";
2112
- return turnId && quote ? { turn_id: turnId, quote } : null;
2113
- }
2114
1850
  historyEl.addEventListener("scroll", annotateBar.schedule);
2115
1851
  // A command bubble's Apply (CacheConfirm): sends the change's --yes line
2116
1852
  // once; its own bubble then says what it did.
@@ -2158,43 +1894,11 @@ const notifications = createNotifications({
2158
1894
  allEvents: (handlers, opts) => openEvents(handlers, opts),
2159
1895
  });
2160
1896
 
2161
- // The latest-sessions strip can be hidden for more chat room; the choice
2162
- // stays per browser (it replaced zen mode, so a zen user starts hidden).
2163
- function stripHiddenSaved() {
2164
- const saved = readStored("chi_strip_hidden");
2165
- return saved === null ? readStored("chi_zen") === "1" : saved === "1";
2166
- }
2167
- // A short window hides the strip by itself, so the stage keeps room for a
2168
- // card that waits (its question and choices); the grip brings it back
2169
- // until the window is tall again. Not saved: the saved choice is the
2170
- // user's own hide or show.
2171
- const shortWindow = window.matchMedia(SHORT_WINDOW_QUERY);
2172
- let stripShownWhileShort = false;
2173
- // The strip's grip (strip_grip.js): the handle on the edge between the strip
2174
- // and the chat. It is given the hidden state and the toggle; it speaks its own
2175
- // title and aria-expanded, refreshed below after every change.
2176
- const stripGripRefresh = createStripGrip({
2177
- gripEl: $("#stripGrip"),
2178
- isHidden: () => document.body.classList.contains("strip-hidden"),
2179
- setHidden: (hidden) => setStripHidden(hidden),
2180
- });
2181
- function applyStripHidden() {
2182
- document.body.classList.toggle("strip-hidden", stripAutoHidden({
2183
- saved: stripHiddenSaved(), short: shortWindow.matches, shownWhileShort: stripShownWhileShort,
2184
- }));
2185
- stripGripRefresh();
2186
- updateSessionTitle();
2187
- }
2188
- function setStripHidden(hidden) {
2189
- if (hidden) lists.familyPop.close();
2190
- writeStored("chi_strip_hidden", hidden ? "1" : "0");
2191
- stripShownWhileShort = !hidden && shortWindow.matches;
2192
- applyStripHidden();
2193
- }
2194
- applyStripHidden();
2195
- shortWindow.addEventListener("change", () => {
2196
- if (!shortWindow.matches) stripShownWhileShort = false;
2197
- applyStripHidden();
1897
+ // The strip hidden or shown (strip_hidden.js): its grip, the saved choice and
1898
+ // the short window's rule; applied now.
1899
+ createStripHidden({
1900
+ gripEl: $("#stripGrip"), body: document.body, onApply: () => updateSessionTitle(),
1901
+ closeFamilyPop: () => lists.familyPop.close(),
2198
1902
  });
2199
1903
 
2200
1904
  // Esc leaves select mode first, then the all-sessions view.
@@ -2216,7 +1920,7 @@ store.on("settingsPanel", () => settingsControl.refresh());
2216
1920
  // The working set's list offers pin only under forget.
2217
1921
  store.on("workingSetStrategy", () => workingSet?.strategyChanged());
2218
1922
  store.on("featureGates", () => featureGates.check(store.current));
2219
- store.on("composer", updateComposerMode);
1923
+ store.on("composer", composerMode.update);
2220
1924
  store.on("composer", () => objectives.redraw());
2221
1925
  // The answers' Revise buttons follow liveTurn.running too (none on the
2222
1926
  // running turn's answer), which touches the composer.