dsh-brief-sidebar 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -231,11 +231,11 @@ window.__ModuleLoader__.load({
231
231
  in_progress: "var(--dsw-alias-state-business-primary)",
232
232
  pending: "var(--dsw-alias-label-tertiary)"
233
233
  };
234
- const SECTION_STYLE$2 = {
234
+ const SECTION_STYLE$3 = {
235
235
  flexShrink: 0,
236
236
  borderBottom: "1px solid var(--dsw-alias-border-secondary)"
237
237
  };
238
- const HEADER_STYLE$3 = {
238
+ const HEADER_STYLE$4 = {
239
239
  padding: "10px 12px 0",
240
240
  color: "var(--dsw-alias-label-secondary)",
241
241
  fontSize: 12,
@@ -246,14 +246,14 @@ window.__ModuleLoader__.load({
246
246
  color: "var(--dsw-alias-label-secondary)",
247
247
  fontSize: 12
248
248
  };
249
- const LIST_STYLE$2 = {
249
+ const LIST_STYLE$3 = {
250
250
  listStyle: "none",
251
251
  margin: 0,
252
252
  padding: "6px 0",
253
253
  display: "flex",
254
254
  flexDirection: "column"
255
255
  };
256
- const ROW_STYLE$3 = {
256
+ const ROW_STYLE$4 = {
257
257
  display: "flex",
258
258
  alignItems: "flex-start",
259
259
  gap: 8,
@@ -305,7 +305,7 @@ window.__ModuleLoader__.load({
305
305
  /** One task row. */
306
306
  function TodoRow(props) {
307
307
  const { item, t } = props;
308
- return (0, react.createElement)("li", { style: ROW_STYLE$3 }, (0, react.createElement)("span", { style: pillStyle(item.status) }, t(statusKey(item.status))), (0, react.createElement)("span", { style: contentStyle(item.status) }, item.content));
308
+ return (0, react.createElement)("li", { style: ROW_STYLE$4 }, (0, react.createElement)("span", { style: pillStyle(item.status) }, t(statusKey(item.status))), (0, react.createElement)("span", { style: contentStyle(item.status) }, item.content));
309
309
  }
310
310
  /**
311
311
  * The progress section. The subscription is unconditional (a plain listener,
@@ -316,24 +316,24 @@ window.__ModuleLoader__.load({
316
316
  const { t, ctx, scope } = props;
317
317
  const todos = useTodos(ctx, scope?.sessionId);
318
318
  if (todos === void 0) return (0, react.createElement)("div", {
319
- style: SECTION_STYLE$2,
319
+ style: SECTION_STYLE$3,
320
320
  "data-dsh-brief-sidebar": "progress-section"
321
- }, (0, react.createElement)("div", { style: HEADER_STYLE$3 }, t("section.progress")), (0, react.createElement)(Notice, {
321
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$4 }, t("section.progress")), (0, react.createElement)(Notice, {
322
322
  title: t("board.unavailable"),
323
323
  detail: t("board.unavailableHint")
324
324
  }));
325
325
  if (todos.length === 0) return (0, react.createElement)("div", {
326
- style: SECTION_STYLE$2,
326
+ style: SECTION_STYLE$3,
327
327
  "data-dsh-brief-sidebar": "progress-section"
328
- }, (0, react.createElement)("div", { style: HEADER_STYLE$3 }, t("section.progress")), (0, react.createElement)(Notice, {
328
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$4 }, t("section.progress")), (0, react.createElement)(Notice, {
329
329
  title: t("board.empty"),
330
330
  detail: t("board.emptyHint")
331
331
  }));
332
332
  const counts = countTodos(todos);
333
333
  return (0, react.createElement)("div", {
334
- style: SECTION_STYLE$2,
334
+ style: SECTION_STYLE$3,
335
335
  "data-dsh-brief-sidebar": "progress-section"
336
- }, (0, react.createElement)("div", { style: HEADER_STYLE$3 }, t("section.progress")), (0, react.createElement)("div", { style: PROGRESS_STYLE }, progressLine(counts, t)), (0, react.createElement)("ul", { style: LIST_STYLE$2 }, todos.map((item, index) => (0, react.createElement)(TodoRow, {
336
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$4 }, t("section.progress")), (0, react.createElement)("div", { style: PROGRESS_STYLE }, progressLine(counts, t)), (0, react.createElement)("ul", { style: LIST_STYLE$3 }, todos.map((item, index) => (0, react.createElement)(TodoRow, {
337
337
  key: `${index}:${item.content}`,
338
338
  item,
339
339
  t
@@ -561,10 +561,15 @@ window.__ModuleLoader__.load({
561
561
  "slots.status.done": "已完成",
562
562
  "slots.status.dropped": "已弃置",
563
563
  "slots.status.expired": "已过期",
564
+ "section.memoryRecall": "本会话召回",
565
+ "recall.empty": "本会话还没有召回到记忆",
566
+ "recall.count": "注入 {count} 条 · 压缩后自动清空",
564
567
  "settings.title": "概要侧栏",
565
568
  "settings.desc": "右侧栏概要 tab 的分区开关(只读展示)。",
566
569
  "settings.memorySlots": "记忆槽位分区",
567
570
  "settings.memorySlotsHint": "开启后,概要 tab 在进展与产物之下显示 dsh-prime-memory 的激活记忆槽位(只读)。",
571
+ "settings.memoryRecall": "本会话召回分区",
572
+ "settings.memoryRecallHint": "开启后,概要 tab 显示本会话已注入、尚未被压缩覆盖的记忆(只读,非常驻)。",
568
573
  "settings.unavailable": "配置面不可用,记忆分区按默认「开」处理。",
569
574
  "settings.readonly": "当前配置只读,无法在此修改。"
570
575
  },
@@ -592,10 +597,15 @@ window.__ModuleLoader__.load({
592
597
  "slots.status.done": "Done",
593
598
  "slots.status.dropped": "Dropped",
594
599
  "slots.status.expired": "Expired",
600
+ "section.memoryRecall": "Session recall",
601
+ "recall.empty": "No memories recalled in this session yet",
602
+ "recall.count": "{count} injected · cleared on compaction",
595
603
  "settings.title": "Brief sidebar",
596
604
  "settings.desc": "Section switches for the summary tab in the right sidebar (read-only views).",
597
605
  "settings.memorySlots": "Memory slots section",
598
606
  "settings.memorySlotsHint": "When on, the summary tab lists the active memory slots from dsh-prime-memory beneath progress and produced files (read-only).",
607
+ "settings.memoryRecall": "Session recall section",
608
+ "settings.memoryRecallHint": "When on, the summary tab lists memories injected in this session that compaction has not overwritten (read-only, not resident).",
599
609
  "settings.unavailable": "The config face is unavailable; the memory section falls back to its default (on).",
600
610
  "settings.readonly": "This config is read-only here; edits are not available."
601
611
  },
@@ -623,10 +633,15 @@ window.__ModuleLoader__.load({
623
633
  "slots.status.done": "完了",
624
634
  "slots.status.dropped": "破棄",
625
635
  "slots.status.expired": "期限切れ",
636
+ "section.memoryRecall": "このセッションの想起",
637
+ "recall.empty": "このセッションではまだ記憶を想起していません",
638
+ "recall.count": "{count} 件注入 · 圧縮で自動消去",
626
639
  "settings.title": "概要サイドバー",
627
640
  "settings.desc": "右サイドバー概要タブのセクション表示切り替え(読み取り専用)。",
628
641
  "settings.memorySlots": "メモリスロットセクション",
629
642
  "settings.memorySlotsHint": "オンにすると、概要タブの進行状況と成果物の下に dsh-prime-memory のアクティブなメモリスロット(読み取り専用)が表示されます。",
643
+ "settings.memoryRecall": "セッション想起のセクション",
644
+ "settings.memoryRecallHint": "オンにすると、概要タブにこのセッションで注入され、まだ圧縮で失われていない記憶を表示します(読み取り専用、常駐しません)。",
630
645
  "settings.unavailable": "設定画面を利用できません。メモリセクションは既定(オン)で動作します。",
631
646
  "settings.readonly": "この設定はここでは読み取り専用のため変更できません。"
632
647
  },
@@ -654,10 +669,15 @@ window.__ModuleLoader__.load({
654
669
  "slots.status.done": "완료",
655
670
  "slots.status.dropped": "폐기",
656
671
  "slots.status.expired": "만료",
672
+ "section.memoryRecall": "이 세션 회상",
673
+ "recall.empty": "이 세션에서는 아직 기억을 회상하지 않았습니다",
674
+ "recall.count": "{count}개 주입 · 압축 시 자동 삭제",
657
675
  "settings.title": "요약 사이드바",
658
676
  "settings.desc": "오른쪽 사이드바 요약 탭의 섹션 표시 전환(읽기 전용).",
659
677
  "settings.memorySlots": "메모리 슬롯 섹션",
660
678
  "settings.memorySlotsHint": "켜면 요약 탭의 진행 상황과 산출물 아래에 dsh-prime-memory의 활성 메모리 슬롯(읽기 전용)이 표시됩니다.",
679
+ "settings.memoryRecall": "세션 회상 섹션",
680
+ "settings.memoryRecallHint": "켜면 개요 탭에 이 세션에 주입되고 아직 압축으로 사라지지 않은 기억이 표시됩니다(읽기 전용, 상주하지 않음).",
661
681
  "settings.unavailable": "설정 화면을 사용할 수 없어 메모리 섹션은 기본값(켜짐)으로 동작합니다.",
662
682
  "settings.readonly": "이 설정은 여기서 읽기 전용이므로 변경할 수 없습니다."
663
683
  },
@@ -685,10 +705,15 @@ window.__ModuleLoader__.load({
685
705
  "slots.status.done": "Terminé",
686
706
  "slots.status.dropped": "Abandonné",
687
707
  "slots.status.expired": "Expiré",
708
+ "section.memoryRecall": "Rappel de la session",
709
+ "recall.empty": "Aucun souvenir rappelé dans cette session",
710
+ "recall.count": "{count} injectés · effacés à la compression",
688
711
  "settings.title": "Barre latérale Résumé",
689
712
  "settings.desc": "Interrupteurs des sections de l’onglet Résumé de la barre latérale (lecture seule).",
690
713
  "settings.memorySlots": "Section des emplacements mémoire",
691
714
  "settings.memorySlotsHint": "Une fois activé, l’onglet Résumé liste les emplacements mémoire actifs de dsh-prime-memory sous l’avancement et les fichiers produits (lecture seule).",
715
+ "settings.memoryRecall": "Section rappel de session",
716
+ "settings.memoryRecallHint": "Affiche dans l’onglet Résumé les souvenirs injectés dans cette session et non encore effacés par la compression (lecture seule, non résidents).",
692
717
  "settings.unavailable": "Le canal de configuration est indisponible ; la section mémoire revient à sa valeur par défaut (activée).",
693
718
  "settings.readonly": "Cette configuration est en lecture seule ici ; aucune modification possible."
694
719
  },
@@ -716,10 +741,15 @@ window.__ModuleLoader__.load({
716
741
  "slots.status.done": "Erledigt",
717
742
  "slots.status.dropped": "Verworfen",
718
743
  "slots.status.expired": "Abgelaufen",
744
+ "section.memoryRecall": "Sitzungserinnerung",
745
+ "recall.empty": "In dieser Sitzung wurden noch keine Erinnerungen geholt",
746
+ "recall.count": "{count} eingefügt · beim Komprimieren geleert",
719
747
  "settings.title": "Übersicht-Seitenleiste",
720
748
  "settings.desc": "Schalter für die Abschnitte des Übersicht-Tabs in der rechten Seitenleiste (schreibgeschützt).",
721
749
  "settings.memorySlots": "Speicherplätze-Abschnitt",
722
750
  "settings.memorySlotsHint": "Ist der Schalter eingeschaltet, listet der Übersicht-Tab die aktiven Speicherplätze aus dsh-prime-memory unter Fortschritt und erzeugten Dateien auf (schreibgeschützt).",
751
+ "settings.memoryRecall": "Bereich Sitzungserinnerung",
752
+ "settings.memoryRecallHint": "Zeigt in der Übersichtsseite die in dieser Sitzung eingefügten, noch nicht komprimierten Erinnerungen (schreibgeschützt, nicht dauerhaft).",
723
753
  "settings.unavailable": "Die Konfigurationsoberfläche ist nicht verfügbar; der Gedächtnis-Abschnitt fällt auf den Standard (ein) zurück.",
724
754
  "settings.readonly": "Diese Konfiguration ist hier schreibgeschützt; Änderungen sind nicht möglich."
725
755
  },
@@ -747,10 +777,15 @@ window.__ModuleLoader__.load({
747
777
  "slots.status.done": "Completato",
748
778
  "slots.status.dropped": "Scartato",
749
779
  "slots.status.expired": "Scaduto",
780
+ "section.memoryRecall": "Richiamo della sessione",
781
+ "recall.empty": "Nessun ricordo richiamato in questa sessione",
782
+ "recall.count": "{count} inseriti · svuotati alla compressione",
750
783
  "settings.title": "Barra laterale Riepilogo",
751
784
  "settings.desc": "Interruttori delle sezioni della scheda Riepilogo nella barra laterale destra (sola lettura).",
752
785
  "settings.memorySlots": "Sezione slot di memoria",
753
786
  "settings.memorySlotsHint": "Quando attivo, la scheda Riepilogo elenca gli slot di memoria attivi di dsh-prime-memory sotto avanzamento e file prodotti (sola lettura).",
787
+ "settings.memoryRecall": "Sezione richiamo sessione",
788
+ "settings.memoryRecallHint": "Mostra nella pagina Riepilogo i ricordi inseriti in questa sessione e non ancora rimossi dalla compressione (sola lettura, non residenti).",
754
789
  "settings.unavailable": "La superficie di configurazione non è disponibile; la sezione memoria torna al valore predefinito (attivo).",
755
790
  "settings.readonly": "Questa configurazione è in sola lettura qui; non è possibile modificarla."
756
791
  },
@@ -778,10 +813,15 @@ window.__ModuleLoader__.load({
778
813
  "slots.status.done": "Завершён",
779
814
  "slots.status.dropped": "Отброшен",
780
815
  "slots.status.expired": "Истёк",
816
+ "section.memoryRecall": "Воспоминания сессии",
817
+ "recall.empty": "В этой сессии воспоминания ещё не вызывались",
818
+ "recall.count": "вставлено {count} · очищается при сжатии",
781
819
  "settings.title": "Боковая панель «Сводка»",
782
820
  "settings.desc": "Переключатели разделов вкладки «Сводка» на правой панели (только чтение).",
783
821
  "settings.memorySlots": "Раздел слотов памяти",
784
822
  "settings.memorySlotsHint": "Когда включено, вкладка «Сводка» показывает активные слоты памяти из dsh-prime-memory под ходом работы и созданными файлами (только чтение).",
823
+ "settings.memoryRecall": "Раздел Воспоминания сессии",
824
+ "settings.memoryRecallHint": "Показывает на странице Обзор воспоминания, вставленные в этой сессии и ещё не удалённые сжатием (только чтение, не постоянные).",
785
825
  "settings.unavailable": "Панель конфигурации недоступна; раздел памяти работает по умолчанию (включён).",
786
826
  "settings.readonly": "Эта конфигурация здесь только для чтения; изменить её нельзя."
787
827
  },
@@ -809,10 +849,15 @@ window.__ModuleLoader__.load({
809
849
  "slots.status.done": "Hecha",
810
850
  "slots.status.dropped": "Descartada",
811
851
  "slots.status.expired": "Caducada",
852
+ "section.memoryRecall": "Recuerdo de la sesión",
853
+ "recall.empty": "Aún no se ha recuperado ningún recuerdo en esta sesión",
854
+ "recall.count": "{count} insertados · se limpia al comprimir",
812
855
  "settings.title": "Barra lateral Resumen",
813
856
  "settings.desc": "Interruptores de las secciones de la pestaña Resumen en la barra lateral derecha (solo lectura).",
814
857
  "settings.memorySlots": "Sección de ranuras de memoria",
815
858
  "settings.memorySlotsHint": "Al activarla, la pestaña Resumen enumera las ranuras de memoria activas de dsh-prime-memory bajo el avance y los archivos generados (solo lectura).",
859
+ "settings.memoryRecall": "Sección de recuerdo de sesión",
860
+ "settings.memoryRecallHint": "Muestra en la pestaña Resumen los recuerdos insertados en esta sesión y aún no eliminados por la compresión (solo lectura, no residentes).",
816
861
  "settings.unavailable": "La superficie de configuración no está disponible; la sección de memoria vuelve a su valor por defecto (activada).",
817
862
  "settings.readonly": "Esta configuración es de solo lectura aquí; no se puede modificar."
818
863
  }
@@ -846,24 +891,24 @@ window.__ModuleLoader__.load({
846
891
  *
847
892
  * Colours come from `--dsw-alias-*` tokens only (skin contract).
848
893
  */
849
- const SECTION_STYLE$1 = {
894
+ const SECTION_STYLE$2 = {
850
895
  flexShrink: 0,
851
896
  borderBottom: "1px solid var(--dsw-alias-border-secondary)"
852
897
  };
853
- const HEADER_STYLE$2 = {
898
+ const HEADER_STYLE$3 = {
854
899
  padding: "10px 12px 0",
855
900
  color: "var(--dsw-alias-label-secondary)",
856
901
  fontSize: 12,
857
902
  fontWeight: 600
858
903
  };
859
- const LIST_STYLE$1 = {
904
+ const LIST_STYLE$2 = {
860
905
  listStyle: "none",
861
906
  margin: 0,
862
907
  padding: "6px 0",
863
908
  display: "flex",
864
909
  flexDirection: "column"
865
910
  };
866
- const ROW_STYLE$2 = {
911
+ const ROW_STYLE$3 = {
867
912
  display: "flex",
868
913
  alignItems: "flex-start",
869
914
  gap: 8,
@@ -929,11 +974,11 @@ window.__ModuleLoader__.load({
929
974
  const title = plan === null ? path : `${t("deliverable.codeplanTag")}: ${plan.task} · ${path}`;
930
975
  const label = basename(path);
931
976
  const content = plan === null ? null : (0, react.createElement)("span", { style: TAG_STYLE }, t("deliverable.codeplanTag"));
932
- if (open === void 0) return (0, react.createElement)("li", { style: ROW_STYLE$2 }, content, (0, react.createElement)("span", {
977
+ if (open === void 0) return (0, react.createElement)("li", { style: ROW_STYLE$3 }, content, (0, react.createElement)("span", {
933
978
  style: PATH_STYLE,
934
979
  title
935
980
  }, label));
936
- return (0, react.createElement)("li", { style: ROW_STYLE$2 }, content, (0, react.createElement)("button", {
981
+ return (0, react.createElement)("li", { style: ROW_STYLE$3 }, content, (0, react.createElement)("button", {
937
982
  style: BUTTON_STYLE,
938
983
  title,
939
984
  onClick: () => {
@@ -958,9 +1003,9 @@ window.__ModuleLoader__.load({
958
1003
  const latestPaths = view.latest?.paths ?? [];
959
1004
  const history = historyPaths(view.sessionPaths, latestPaths);
960
1005
  if (latestPaths.length === 0 && view.sessionTotal === 0) return (0, react.createElement)("div", {
961
- style: SECTION_STYLE$1,
1006
+ style: SECTION_STYLE$2,
962
1007
  "data-dsh-brief-sidebar": "deliverables-section"
963
- }, (0, react.createElement)("div", { style: HEADER_STYLE$2 }, t("section.deliverables")), (0, react.createElement)("div", { style: {
1008
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$3 }, t("section.deliverables")), (0, react.createElement)("div", { style: {
964
1009
  margin: "0 auto",
965
1010
  padding: 24,
966
1011
  maxWidth: 460,
@@ -973,9 +1018,9 @@ window.__ModuleLoader__.load({
973
1018
  color: "var(--dsw-alias-label-secondary)"
974
1019
  } }, t("deliverables.empty")), (0, react.createElement)("div", null, t("deliverables.emptyHint"))));
975
1020
  return (0, react.createElement)("div", {
976
- style: SECTION_STYLE$1,
1021
+ style: SECTION_STYLE$2,
977
1022
  "data-dsh-brief-sidebar": "deliverables-section"
978
- }, (0, react.createElement)("div", { style: HEADER_STYLE$2 }, t("section.deliverables")), (0, react.createElement)("ul", { style: LIST_STYLE$1 }, latestPaths.map((path) => (0, react.createElement)(PathRow, {
1023
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$3 }, t("section.deliverables")), (0, react.createElement)("ul", { style: LIST_STYLE$2 }, latestPaths.map((path) => (0, react.createElement)(PathRow, {
979
1024
  key: path,
980
1025
  path,
981
1026
  t,
@@ -1000,7 +1045,7 @@ window.__ModuleLoader__.load({
1000
1045
  strokeWidth: 1.5,
1001
1046
  strokeLinecap: "round",
1002
1047
  strokeLinejoin: "round"
1003
- }))), historyOpen && (0, react.createElement)("ul", { style: LIST_STYLE$1 }, history.map((path) => (0, react.createElement)(PathRow, {
1048
+ }))), historyOpen && (0, react.createElement)("ul", { style: LIST_STYLE$2 }, history.map((path) => (0, react.createElement)(PathRow, {
1004
1049
  key: path,
1005
1050
  path,
1006
1051
  t,
@@ -1048,7 +1093,7 @@ window.__ModuleLoader__.load({
1048
1093
  function readSlots(value) {
1049
1094
  if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
1050
1095
  const raw = value;
1051
- if (!isCounter(raw["rev"]) || !isCounter(raw["count"]) || !isCounter(raw["openCount"])) return void 0;
1096
+ if (!isCounter$1(raw["rev"]) || !isCounter$1(raw["count"]) || !isCounter$1(raw["openCount"])) return void 0;
1052
1097
  if (!Array.isArray(raw["slots"])) return void 0;
1053
1098
  const slots = [];
1054
1099
  for (const entry of raw["slots"]) {
@@ -1063,7 +1108,7 @@ window.__ModuleLoader__.load({
1063
1108
  };
1064
1109
  }
1065
1110
  /** Whether a value is a non-negative safe integer (the view's counter shape). */
1066
- function isCounter(value) {
1111
+ function isCounter$1(value) {
1067
1112
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1068
1113
  }
1069
1114
  /** Narrow one slot entry; `undefined` = malformed, dropped. */
@@ -1116,8 +1161,62 @@ window.__ModuleLoader__.load({
1116
1161
  * explicit `false` is the only off.
1117
1162
  */
1118
1163
  function resolveShowMemorySlots(snapshot) {
1164
+ if (snapshot === void 0 || snapshot.status !== "ready") return false;
1165
+ return snapshot.value?.showMemorySlots === true;
1166
+ }
1167
+ /**
1168
+ * Resolve the session-recall switch from one snapshot (pure; testable).
1169
+ * Any cannot-answer shape falls back to {@link DEFAULT_SHOW_MEMORY_RECALL};
1170
+ * an explicit `false` is the only off.
1171
+ */
1172
+ function resolveShowMemoryRecall(snapshot) {
1119
1173
  if (snapshot === void 0 || snapshot.status !== "ready") return true;
1120
- return snapshot.value?.showMemorySlots !== false;
1174
+ return snapshot.value?.showMemoryRecall !== false;
1175
+ }
1176
+ //#endregion
1177
+ //#region src/client/summary/use-config-flag.ts
1178
+ /**
1179
+ * The shared `configForms` subscription behind this plugin's section switches.
1180
+ *
1181
+ * Both memory sections (pinned slots / session recall) gate on one boolean of
1182
+ * this plugin's entry config. The plumbing — defensive scope resolution,
1183
+ * `useSyncExternalStore`, and a stable absent-snapshot — is identical, so it
1184
+ * lives here once; each section passes its own pure resolver, which is what
1185
+ * the unit tests pin.
1186
+ *
1187
+ * A host without `configForms` (or a scope still loading) degrades to the
1188
+ * resolver's default instead of throwing inside a render.
1189
+ */
1190
+ /**
1191
+ * The snapshot served while no config scope is reachable — every resolver
1192
+ * maps it to its own default.
1193
+ */
1194
+ const ABSENT_SNAPSHOT = {
1195
+ status: "unavailable",
1196
+ value: void 0,
1197
+ revision: void 0,
1198
+ writable: false
1199
+ };
1200
+ /** A no-op unsubscribe source, kept as one identity so the absent-scope
1201
+ * subscription never thrashes (a store contract requires a disposer). */
1202
+ const NO_SUBSCRIBE$1 = () => () => {};
1203
+ /**
1204
+ * Read one config switch, live.
1205
+ *
1206
+ * @param ctx - the client root context handed to the tab body.
1207
+ * @param resolve - pure snapshot → boolean resolver (owns the default).
1208
+ * @returns the resolved switch value.
1209
+ */
1210
+ function useConfigFlag(ctx, resolve) {
1211
+ const scope = (0, react.useMemo)(() => {
1212
+ if (ctx === void 0) return void 0;
1213
+ const configForms = ctx.get("configForms");
1214
+ if (configForms === void 0 || typeof configForms.get !== "function") return void 0;
1215
+ return configForms.get("dsh-brief-sidebar");
1216
+ }, [ctx]);
1217
+ const subscribe = (0, react.useCallback)((listener) => scope?.subscribe(listener) ?? NO_SUBSCRIBE$1(), [scope]);
1218
+ const getSnapshot = (0, react.useCallback)(() => scope?.getSnapshot() ?? ABSENT_SNAPSHOT, [scope]);
1219
+ return resolve((0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot));
1121
1220
  }
1122
1221
  //#endregion
1123
1222
  //#region src/client/summary/MemorySlotsSection.tsx
@@ -1129,9 +1228,10 @@ window.__ModuleLoader__.load({
1129
1228
  * paints nothing here:
1130
1229
  *
1131
1230
  * 1. **The switch.** This plugin's entry config (`configForms` → entry
1132
- * `dsh-brief-sidebar`, field `showMemorySlots`, default on). When the
1133
- * config face cannot answer (service absent, scope not ready, field
1134
- * unset) the default applies — see `scope-face.ts`.
1231
+ * `dsh-brief-sidebar`, field `showMemorySlots`, default OFF — the pinned
1232
+ * list moved out of the summary pane's way in favour of the session-recall
1233
+ * section). When the config face cannot answer (service absent, scope not
1234
+ * ready, field unset) the default applies — see `scope-face.ts`.
1135
1235
  * 2. **The capability.** The `memorySlots` projection must exist for the
1136
1236
  * session; `undefined` hides the section entirely, exactly like the
1137
1237
  * optional deliverables section.
@@ -1139,35 +1239,35 @@ window.__ModuleLoader__.load({
1139
1239
  * Colours come from `--dsw-alias-*` tokens only (skin contract, enforced by
1140
1240
  * `tests/purity.spec.ts`).
1141
1241
  */
1142
- const SECTION_STYLE = {
1242
+ const SECTION_STYLE$1 = {
1143
1243
  flexShrink: 0,
1144
1244
  borderBottom: "1px solid var(--dsw-alias-border-secondary)"
1145
1245
  };
1146
- const HEADER_STYLE$1 = {
1246
+ const HEADER_STYLE$2 = {
1147
1247
  padding: "10px 12px 0",
1148
1248
  color: "var(--dsw-alias-label-secondary)",
1149
1249
  fontSize: 12,
1150
1250
  fontWeight: 600
1151
1251
  };
1152
- const LIST_STYLE = {
1252
+ const LIST_STYLE$1 = {
1153
1253
  listStyle: "none",
1154
1254
  margin: 0,
1155
1255
  padding: "6px 0",
1156
1256
  display: "flex",
1157
1257
  flexDirection: "column"
1158
1258
  };
1159
- const ROW_STYLE$1 = {
1259
+ const ROW_STYLE$2 = {
1160
1260
  display: "flex",
1161
1261
  alignItems: "flex-start",
1162
1262
  gap: 8,
1163
1263
  padding: "6px 12px"
1164
1264
  };
1165
- const TITLE_STYLE = {
1265
+ const TITLE_STYLE$1 = {
1166
1266
  minWidth: 0,
1167
1267
  overflowWrap: "anywhere",
1168
1268
  color: "var(--dsw-alias-label-secondary)"
1169
1269
  };
1170
- const META_STYLE = {
1270
+ const META_STYLE$1 = {
1171
1271
  flexShrink: 0,
1172
1272
  color: "var(--dsw-alias-label-tertiary)",
1173
1273
  fontSize: 11,
@@ -1218,7 +1318,7 @@ window.__ModuleLoader__.load({
1218
1318
  color: "var(--dsw-alias-label-tertiary)",
1219
1319
  fontSize: 12
1220
1320
  };
1221
- const EMPTY_STYLE = {
1321
+ const EMPTY_STYLE$1 = {
1222
1322
  margin: "0 auto",
1223
1323
  padding: 24,
1224
1324
  maxWidth: 460,
@@ -1245,57 +1345,29 @@ window.__ModuleLoader__.load({
1245
1345
  const statusLabel = STATUS_KEY[slot.status] === void 0 ? slot.status : t(STATUS_KEY[slot.status]);
1246
1346
  const title = slot.body === void 0 ? slot.title : `${slot.title} — ${slot.body}`;
1247
1347
  const refsLine = slot.refViews !== void 0 ? slot.refViews.map((view) => view.title).join(" · ") : slot.refs !== void 0 ? slot.refs.join(" · ") : void 0;
1248
- return (0, react.createElement)("li", { style: ROW_STYLE$1 }, (0, react.createElement)("span", { style: KIND_STYLE }, slot.kind), (0, react.createElement)("div", { style: {
1348
+ return (0, react.createElement)("li", { style: ROW_STYLE$2 }, (0, react.createElement)("span", { style: KIND_STYLE }, slot.kind), (0, react.createElement)("div", { style: {
1249
1349
  minWidth: 0,
1250
1350
  flex: "1 1 auto",
1251
1351
  display: "flex",
1252
1352
  flexDirection: "column",
1253
1353
  gap: 2
1254
1354
  } }, (0, react.createElement)("span", {
1255
- style: TITLE_STYLE,
1355
+ style: TITLE_STYLE$1,
1256
1356
  title
1257
1357
  }, slot.title), slot.body !== void 0 && (0, react.createElement)("p", { style: BODY_STYLE$1 }, truncateBody(slot.body)), refsLine !== void 0 && (0, react.createElement)("p", {
1258
1358
  style: REFS_STYLE,
1259
1359
  title: slot.refs !== void 0 ? slot.refs.join("\n") : void 0
1260
- }, refsLine)), (0, react.createElement)("span", { style: META_STYLE }, `P${slot.priority}`), (0, react.createElement)("span", { style: slot.status === "open" ? STATUS_OPEN_STYLE : STATUS_SETTLED_STYLE }, statusLabel));
1360
+ }, refsLine)), (0, react.createElement)("span", { style: META_STYLE$1 }, `P${slot.priority}`), (0, react.createElement)("span", { style: slot.status === "open" ? STATUS_OPEN_STYLE : STATUS_SETTLED_STYLE }, statusLabel));
1261
1361
  }
1262
1362
  /**
1263
- * A no-op unsubscribe source, kept as one identity so the absent-scope
1264
- * subscription never thrashes (returns a no-op disposer, as the store
1265
- * contract requires).
1266
- */
1267
- const NO_SUBSCRIBE$1 = () => () => {};
1268
- /**
1269
- * The snapshot served while no config scope is reachable — resolves to the
1270
- * switch default (on) in `resolveShowMemorySlots`.
1271
- */
1272
- const ABSENT_SNAPSHOT = {
1273
- status: "unavailable",
1274
- value: void 0,
1275
- revision: void 0,
1276
- writable: false
1277
- };
1278
- /**
1279
- * Subscribe to this plugin's entry config and resolve the section switch.
1280
- *
1281
- * The scope is resolved defensively (a host without `configForms` degrades to
1282
- * the default instead of throwing inside a render) and subscribed with
1283
- * `useSyncExternalStore`, so flipping the switch in settings updates the open
1284
- * tab without a reload.
1363
+ * Subscribe to this plugin's entry config and resolve the slots switch.
1364
+ * Plumbing lives in `use-config-flag`; the default (off) lives in the resolver.
1285
1365
  *
1286
1366
  * @param ctx - the client root context handed to the tab body.
1287
1367
  * @returns whether the memory-slots section may render at all.
1288
1368
  */
1289
1369
  function useShowMemorySlots(ctx) {
1290
- const scope = (0, react.useMemo)(() => {
1291
- if (ctx === void 0) return void 0;
1292
- const configForms = ctx.get("configForms");
1293
- if (configForms === void 0 || typeof configForms.get !== "function") return void 0;
1294
- return configForms.get("dsh-brief-sidebar");
1295
- }, [ctx]);
1296
- const subscribe = (0, react.useCallback)((listener) => scope?.subscribe(listener) ?? NO_SUBSCRIBE$1(), [scope]);
1297
- const getSnapshot = (0, react.useCallback)(() => scope?.getSnapshot() ?? ABSENT_SNAPSHOT, [scope]);
1298
- return resolveShowMemorySlots((0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot));
1370
+ return useConfigFlag(ctx, resolveShowMemorySlots);
1299
1371
  }
1300
1372
  /**
1301
1373
  * The memory-slots section. Both hooks run unconditionally (a conditional
@@ -1310,31 +1382,239 @@ window.__ModuleLoader__.load({
1310
1382
  if (show === false) return null;
1311
1383
  if (view === void 0) return null;
1312
1384
  if (view.slots.length === 0) return (0, react.createElement)("div", {
1313
- style: SECTION_STYLE,
1385
+ style: SECTION_STYLE$1,
1314
1386
  "data-dsh-brief-sidebar": "memory-slots-section"
1315
- }, (0, react.createElement)("div", { style: HEADER_STYLE$1 }, t("section.memorySlots")), (0, react.createElement)("div", { style: EMPTY_STYLE }, (0, react.createElement)("div", { style: {
1387
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$2 }, t("section.memorySlots")), (0, react.createElement)("div", { style: EMPTY_STYLE$1 }, (0, react.createElement)("div", { style: {
1316
1388
  marginBottom: 6,
1317
1389
  color: "var(--dsw-alias-label-secondary)"
1318
1390
  } }, t("slots.empty")), (0, react.createElement)("div", null, t("slots.emptyHint"))));
1319
1391
  return (0, react.createElement)("div", {
1320
- style: SECTION_STYLE,
1392
+ style: SECTION_STYLE$1,
1321
1393
  "data-dsh-brief-sidebar": "memory-slots-section"
1322
- }, (0, react.createElement)("div", { style: HEADER_STYLE$1 }, t("section.memorySlots")), (0, react.createElement)("ul", { style: LIST_STYLE }, view.slots.map((slot) => (0, react.createElement)(SlotRow, {
1394
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$2 }, t("section.memorySlots")), (0, react.createElement)("ul", { style: LIST_STYLE$1 }, view.slots.map((slot) => (0, react.createElement)(SlotRow, {
1323
1395
  key: slot.id,
1324
1396
  slot,
1325
1397
  t
1326
1398
  }))), (0, react.createElement)("div", { style: TOTAL_STYLE }, expandCount(t("slots.openCount"), view.openCount)));
1327
1399
  }
1328
1400
  //#endregion
1401
+ //#region src/client/summary/memory-recall.ts
1402
+ /**
1403
+ * The `memoryRecall` projection — reading it.
1404
+ *
1405
+ * The projection unit is registered by `dsh-prime-memory` (key `memoryRecall`,
1406
+ * wire view `{rev, sessionId, count, items[]}`): the memories this session has
1407
+ * actually injected and that compaction has not overwritten yet. Unlike
1408
+ * `memorySlots` (pinned, resident conventions written by hand), this set is
1409
+ * NOT resident — it is the at-a-glance answer to "what is the model seeing
1410
+ * right now", and it empties itself when the context is compacted.
1411
+ *
1412
+ * This plugin does NOT depend on prime-memory: with the unit unregistered the
1413
+ * client reads `undefined` from the face and the section hides. The module is
1414
+ * deliberately PURE — no React, no context, no IO — so the narrowing is
1415
+ * testable directly, exactly like `memory-slots.ts`.
1416
+ */
1417
+ /** The projection key `dsh-prime-memory` registers (its canonical constant). */
1418
+ const MEMORY_RECALL_KEY = "memoryRecall";
1419
+ /** Whether a value is a non-negative safe integer (the view's counter shape). */
1420
+ function isCounter(value) {
1421
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1422
+ }
1423
+ /** Narrow one item entry; `undefined` = malformed, dropped. */
1424
+ function readItem(value) {
1425
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
1426
+ const raw = value;
1427
+ if (typeof raw["id"] !== "string" || raw["id"].length === 0) return void 0;
1428
+ if (typeof raw["type"] !== "string" || typeof raw["title"] !== "string") return void 0;
1429
+ if (typeof raw["priority"] !== "number") return void 0;
1430
+ const item = {
1431
+ id: raw["id"],
1432
+ type: raw["type"],
1433
+ title: raw["title"],
1434
+ priority: raw["priority"]
1435
+ };
1436
+ if (Array.isArray(raw["scenario"])) {
1437
+ const phrases = raw["scenario"].filter((s) => typeof s === "string" && s.length > 0);
1438
+ if (phrases.length > 0) item.scenario = phrases;
1439
+ }
1440
+ return item;
1441
+ }
1442
+ /**
1443
+ * Narrow a raw projection value into a recall view.
1444
+ *
1445
+ * `undefined` means the capability is ABSENT (prime-memory not in the
1446
+ * composition, or no frame carried the key) or the frame drifted beyond repair;
1447
+ * a view with `items: []` IS the honest empty state (prime-memory publishes as
1448
+ * soon as the unit registers). Malformed items are dropped, never thrown on.
1449
+ *
1450
+ * @param value - the raw `unknown` snapshot of the `memoryRecall` face.
1451
+ * @returns the validated view, or `undefined` when the capability is absent.
1452
+ */
1453
+ function readRecall(value) {
1454
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
1455
+ const raw = value;
1456
+ if (!isCounter(raw["rev"]) || !isCounter(raw["count"])) return void 0;
1457
+ if (typeof raw["sessionId"] !== "string") return void 0;
1458
+ if (!Array.isArray(raw["items"])) return void 0;
1459
+ const items = [];
1460
+ for (const entry of raw["items"]) {
1461
+ const item = readItem(entry);
1462
+ if (item !== void 0) items.push(item);
1463
+ }
1464
+ return {
1465
+ rev: raw["rev"],
1466
+ sessionId: raw["sessionId"],
1467
+ count: raw["count"],
1468
+ items
1469
+ };
1470
+ }
1471
+ //#endregion
1472
+ //#region src/client/summary/MemoryRecallSection.tsx
1473
+ /**
1474
+ * The session-recall section: what this session actually injected, drawn as a
1475
+ * part of the summary tab.
1476
+ *
1477
+ * Where `MemorySlotsSection` lists hand-pinned resident conventions (written
1478
+ * once, injected every turn), this one answers a different question: **which
1479
+ * memories is the model seeing right now**. The set is not resident — it is
1480
+ * relevance recall, and prime-memory empties it when the context is compacted
1481
+ * or cleared, so an entry disappearing here is honest, not a bug.
1482
+ *
1483
+ * TWO gates, both fail-open to "hidden", mirroring the slots section:
1484
+ *
1485
+ * 1. **The switch.** This plugin's entry config (`configForms` → field
1486
+ * `showMemoryRecall`, default on).
1487
+ * 2. **The capability.** The `memoryRecall` projection must exist for the
1488
+ * session; `undefined` hides the section entirely — a composition without
1489
+ * dsh-prime-memory paints nothing here.
1490
+ *
1491
+ * Rows are read-only by design: the write surface for memory is prime-memory's
1492
+ * own panel, and offering pin/activate affordances here would re-introduce the
1493
+ * "resident list" reading this section exists to replace.
1494
+ *
1495
+ * Colours come from `--dsw-alias-*` tokens only (skin contract, enforced by
1496
+ * `tests/purity.spec.ts`).
1497
+ */
1498
+ const SECTION_STYLE = {
1499
+ flexShrink: 0,
1500
+ borderBottom: "1px solid var(--dsw-alias-border-secondary)"
1501
+ };
1502
+ const HEADER_STYLE$1 = {
1503
+ padding: "10px 12px 0",
1504
+ color: "var(--dsw-alias-label-secondary)",
1505
+ fontSize: 12,
1506
+ fontWeight: 600
1507
+ };
1508
+ const LIST_STYLE = {
1509
+ listStyle: "none",
1510
+ margin: 0,
1511
+ padding: "6px 0",
1512
+ display: "flex",
1513
+ flexDirection: "column"
1514
+ };
1515
+ const ROW_STYLE$1 = {
1516
+ display: "flex",
1517
+ alignItems: "flex-start",
1518
+ gap: 8,
1519
+ padding: "6px 12px"
1520
+ };
1521
+ /** The type pill: same form as the slot kind pill, tertiary emphasis. */
1522
+ const TYPE_STYLE = {
1523
+ flexShrink: 0,
1524
+ color: "var(--dsw-alias-label-tertiary)",
1525
+ border: "1px solid var(--dsw-alias-label-tertiary)",
1526
+ borderRadius: 999,
1527
+ padding: "0 6px",
1528
+ fontSize: 11,
1529
+ lineHeight: "16px",
1530
+ whiteSpace: "nowrap"
1531
+ };
1532
+ const TITLE_STYLE = {
1533
+ minWidth: 0,
1534
+ overflowWrap: "anywhere",
1535
+ color: "var(--dsw-alias-label-secondary)"
1536
+ };
1537
+ const SCENARIO_STYLE = {
1538
+ margin: 0,
1539
+ minWidth: 0,
1540
+ overflowWrap: "anywhere",
1541
+ color: "var(--dsw-alias-label-tertiary)",
1542
+ fontSize: 11,
1543
+ lineHeight: "16px"
1544
+ };
1545
+ const META_STYLE = {
1546
+ flexShrink: 0,
1547
+ color: "var(--dsw-alias-label-tertiary)",
1548
+ fontSize: 11,
1549
+ lineHeight: "18px",
1550
+ whiteSpace: "nowrap"
1551
+ };
1552
+ const FOOTER_STYLE = {
1553
+ padding: "6px 12px 10px",
1554
+ color: "var(--dsw-alias-label-tertiary)",
1555
+ fontSize: 12
1556
+ };
1557
+ const EMPTY_STYLE = {
1558
+ margin: "0 auto",
1559
+ padding: 24,
1560
+ maxWidth: 460,
1561
+ textAlign: "center",
1562
+ color: "var(--dsw-alias-label-tertiary)",
1563
+ lineHeight: 1.7,
1564
+ fontSize: 12
1565
+ };
1566
+ /**
1567
+ * One recalled memory: type pill, first line of the body (full text and the
1568
+ * "when should this surface" phrases ride the tooltip), its own priority.
1569
+ */
1570
+ function RecallRow(props) {
1571
+ const { item } = props;
1572
+ const scenario = item.scenario === void 0 ? void 0 : item.scenario.join(" · ");
1573
+ return (0, react.createElement)("li", { style: ROW_STYLE$1 }, (0, react.createElement)("span", { style: TYPE_STYLE }, item.type), (0, react.createElement)("div", { style: {
1574
+ minWidth: 0,
1575
+ flex: "1 1 auto",
1576
+ display: "flex",
1577
+ flexDirection: "column",
1578
+ gap: 2
1579
+ } }, (0, react.createElement)("span", {
1580
+ style: TITLE_STYLE,
1581
+ title: scenario === void 0 ? item.title : `${item.title} — ${scenario}`
1582
+ }, item.title), scenario !== void 0 && (0, react.createElement)("p", { style: SCENARIO_STYLE }, scenario)), (0, react.createElement)("span", { style: META_STYLE }, `P${item.priority}`));
1583
+ }
1584
+ /**
1585
+ * The session-recall section. Both hooks run unconditionally (a conditional
1586
+ * hook order would break the moment the switch flips); the switch only decides
1587
+ * whether the resolved view paints.
1588
+ * @param props - translation, client context, session scope.
1589
+ */
1590
+ function MemoryRecallSection(props) {
1591
+ const { t, ctx, scope } = props;
1592
+ const show = useConfigFlag(ctx, resolveShowMemoryRecall);
1593
+ const view = readRecall(useProjectionValue(ctx, scope?.sessionId, MEMORY_RECALL_KEY));
1594
+ if (show === false) return null;
1595
+ if (view === void 0) return null;
1596
+ if (view.items.length === 0) return (0, react.createElement)("div", {
1597
+ style: SECTION_STYLE,
1598
+ "data-dsh-brief-sidebar": "memory-recall-section"
1599
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$1 }, t("section.memoryRecall")), (0, react.createElement)("div", { style: EMPTY_STYLE }, t("recall.empty")));
1600
+ return (0, react.createElement)("div", {
1601
+ style: SECTION_STYLE,
1602
+ "data-dsh-brief-sidebar": "memory-recall-section"
1603
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$1 }, t("section.memoryRecall")), (0, react.createElement)("ul", { style: LIST_STYLE }, view.items.map((item) => (0, react.createElement)(RecallRow, {
1604
+ key: item.id,
1605
+ item
1606
+ }))), (0, react.createElement)("div", { style: FOOTER_STYLE }, expandCount(t("recall.count"), view.count)));
1607
+ }
1608
+ //#endregion
1329
1609
  //#region src/client/SummaryTab.tsx
1330
1610
  /**
1331
1611
  * The summary tab body: the session's at-a-glance state, composed of sections.
1332
1612
  *
1333
1613
  * Section order: 进展 (the todos board) → 产物 (produced files, codeplan
1334
- * artifacts marked inline) → 记忆 (the active memory slots of the session,
1335
- * served by the `memorySlots` projection dsh-prime-memory registers).
1336
- * Each section owns its own data chain and its own gating contract; this
1337
- * component owns only the shell.
1614
+ * artifacts marked inline) → 本会话召回 (what prime-memory injected into this
1615
+ * session and compaction has not overwritten) → 记忆槽位 (hand-pinned resident
1616
+ * conventions, off by default). Each section owns its own data chain and its
1617
+ * own gating contract; this component owns only the shell.
1338
1618
  *
1339
1619
  * Two better-sidebar contracts are honoured:
1340
1620
  *
@@ -1381,6 +1661,11 @@ window.__ModuleLoader__.load({
1381
1661
  t,
1382
1662
  ctx,
1383
1663
  scope
1664
+ }), (0, react.createElement)(MemoryRecallSection, {
1665
+ key: "memory-recall",
1666
+ t,
1667
+ ctx,
1668
+ scope
1384
1669
  }), (0, react.createElement)(MemorySlotsSection, {
1385
1670
  key: "memory-slots",
1386
1671
  t,
@@ -1628,6 +1913,25 @@ window.__ModuleLoader__.load({
1628
1913
  cursor: "pointer"
1629
1914
  };
1630
1915
  /**
1916
+ * One switch row: label + hint on the left, checkbox on the right. Extracted
1917
+ * because the card now binds two sections (session recall / pinned slots).
1918
+ */
1919
+ function FlagRow(props) {
1920
+ return (0, react.createElement)("div", { style: ROW_STYLE }, (0, react.createElement)("div", { style: ROW_TEXT_STYLE }, (0, react.createElement)("label", {
1921
+ htmlFor: props.id,
1922
+ style: LABEL_STYLE
1923
+ }, props.label), (0, react.createElement)("p", { style: HINT_STYLE }, props.hint)), (0, react.createElement)("input", {
1924
+ id: props.id,
1925
+ type: "checkbox",
1926
+ checked: props.checked,
1927
+ disabled: props.disabled,
1928
+ style: SWITCH_STYLE,
1929
+ onChange: (event) => {
1930
+ props.onToggle(event.currentTarget.checked);
1931
+ }
1932
+ }));
1933
+ }
1934
+ /**
1631
1935
  * The settings card body. `scope` is optional on purpose: the inject factory
1632
1936
  * always provides it, but a host whose `configForms` face degrades must not
1633
1937
  * crash the settings page — the card renders the unavailable note and keeps
@@ -1639,7 +1943,8 @@ window.__ModuleLoader__.load({
1639
1943
  const snapshot = (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => scope?.subscribe(listener) ?? NO_SUBSCRIBE(), [scope]), (0, react.useCallback)(() => scope?.getSnapshot() ?? ABSENT, [scope]), (0, react.useCallback)(() => scope?.getSnapshot() ?? ABSENT, [scope]));
1640
1944
  const unavailable = scope === void 0 || snapshot.status === "unavailable";
1641
1945
  const readonly = unavailable || !snapshot.writable;
1642
- const checked = snapshot.value?.showMemorySlots !== false;
1946
+ const slotsChecked = resolveShowMemorySlots(snapshot);
1947
+ const recallChecked = resolveShowMemoryRecall(snapshot);
1643
1948
  return (0, react.createElement)("div", {
1644
1949
  style: CARD_STYLE,
1645
1950
  "data-dsh-brief-sidebar": "settings-card"
@@ -1663,17 +1968,29 @@ window.__ModuleLoader__.load({
1663
1968
  strokeWidth: 1.5,
1664
1969
  strokeLinecap: "round",
1665
1970
  strokeLinejoin: "round"
1666
- }))), open && (0, react.createElement)("div", { style: BODY_STYLE }, unavailable ? (0, react.createElement)("p", { style: HINT_STYLE }, t("settings.unavailable")) : (0, react.createElement)("div", { style: ROW_STYLE }, (0, react.createElement)("div", { style: ROW_TEXT_STYLE }, (0, react.createElement)("label", {
1667
- htmlFor: "dsh-brief-sidebar-show-memory-slots",
1668
- style: LABEL_STYLE
1669
- }, t("settings.memorySlots")), (0, react.createElement)("p", { style: HINT_STYLE }, t("settings.memorySlotsHint"))), (0, react.createElement)("input", {
1971
+ }))), open && (0, react.createElement)("div", { style: BODY_STYLE }, unavailable ? (0, react.createElement)("p", { style: HINT_STYLE }, t("settings.unavailable")) : (0, react.createElement)("div", { style: {
1972
+ display: "flex",
1973
+ flexDirection: "column",
1974
+ gap: 12
1975
+ } }, (0, react.createElement)(FlagRow, {
1976
+ key: "recall",
1977
+ id: "dsh-brief-sidebar-show-memory-recall",
1978
+ label: t("settings.memoryRecall"),
1979
+ hint: t("settings.memoryRecallHint"),
1980
+ checked: recallChecked,
1981
+ disabled: readonly,
1982
+ onToggle: (next) => {
1983
+ scope?.set("showMemoryRecall", next);
1984
+ }
1985
+ }), (0, react.createElement)(FlagRow, {
1986
+ key: "slots",
1670
1987
  id: "dsh-brief-sidebar-show-memory-slots",
1671
- type: "checkbox",
1672
- checked,
1988
+ label: t("settings.memorySlots"),
1989
+ hint: t("settings.memorySlotsHint"),
1990
+ checked: slotsChecked,
1673
1991
  disabled: readonly,
1674
- style: SWITCH_STYLE,
1675
- onChange: (event) => {
1676
- scope?.set("showMemorySlots", event.currentTarget.checked);
1992
+ onToggle: (next) => {
1993
+ scope?.set("showMemorySlots", next);
1677
1994
  }
1678
1995
  })), !unavailable && !snapshot.writable && (0, react.createElement)("p", { style: HINT_STYLE }, t("settings.readonly"))));
1679
1996
  }