dsh-brief-sidebar 0.9.0 → 0.9.1

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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-brief-sidebar",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "main": "./lib/index.mjs",
5
5
  "description": "会话简报侧栏页(brief):把会话的 todo 列表(`todos` 投影的进展看板)、产物列表(`dshSummaryDeliverables` 投影的实时产出文件)与记忆槽位(`memorySlots` 投影,来自 dsh-prime-memory,开关可关)一起渲染成右侧栏的一个「概要」tab,并遮蔽 DSH 官方那条 composer 上方的 todo 面板。只读展示,不做写回。",
6
6
  "engines": {
package/lib/client.js CHANGED
@@ -516,6 +516,106 @@ window.__ModuleLoader__.load({
516
516
  return sessionPaths.filter((path) => !active.has(normalized(path)));
517
517
  }
518
518
  //#endregion
519
+ //#region src/client/summary/FoldToggle.tsx
520
+ /**
521
+ * The two group chrome pieces the summary sections share: a counted group
522
+ * title, and the toggle that folds a group away.
523
+ *
524
+ * Extracted because the deliverables section (本回合 / 更早改动) and the recall
525
+ * section (活跃召回 / 其余历史召回) now fold a history group the same way. Two
526
+ * private copies of the same button would drift the first time either section
527
+ * touches padding — and the whole point of the split is that a reader can tell
528
+ * "what is live now" from "what the session also contains" without learning a
529
+ * different layout per section.
530
+ *
531
+ * Colours come from `--dsw-alias-*` tokens only (skin contract, enforced by
532
+ * `tests/purity.spec.ts`).
533
+ */
534
+ /** Section-level title under the section header: quieter, same left gutter. */
535
+ const GROUP_HEADER_STYLE = {
536
+ padding: "8px 12px 0",
537
+ color: "var(--dsw-alias-label-tertiary)",
538
+ fontSize: 11,
539
+ fontWeight: 600
540
+ };
541
+ /** The fold switch: full-width hit area, tertiary text, chevron at the end. */
542
+ const TOGGLE_STYLE = {
543
+ background: "none",
544
+ border: "none",
545
+ font: "inherit",
546
+ fontSize: 12,
547
+ color: "var(--dsw-alias-label-tertiary)",
548
+ cursor: "pointer",
549
+ display: "flex",
550
+ alignItems: "center",
551
+ gap: 4,
552
+ padding: "6px 12px",
553
+ textAlign: "left",
554
+ width: "calc(100% - 24px)"
555
+ };
556
+ /** The note beside a folded group (e.g. how many the capped list left out). */
557
+ const CAPTION_STYLE = {
558
+ padding: "0 12px 6px",
559
+ color: "var(--dsw-alias-label-tertiary)",
560
+ fontSize: 11,
561
+ lineHeight: "16px"
562
+ };
563
+ const CHEVRON_STYLE$1 = (open) => ({
564
+ flexShrink: 0,
565
+ transition: "transform 0.16s",
566
+ transform: open ? "rotate(180deg)" : "none"
567
+ });
568
+ /**
569
+ * A counted group title. Renders nothing when the group is empty — an empty
570
+ * "活跃召回 · 0" line would ask the reader to explain a heading rather than read
571
+ * a fact.
572
+ * @param props - already-interpolated text, plus an optional DOM marker.
573
+ */
574
+ function GroupHeader(props) {
575
+ if (props.text.length === 0) return null;
576
+ return (0, react.createElement)("div", {
577
+ style: GROUP_HEADER_STYLE,
578
+ "data-dsh-brief-sidebar": props.marker ?? "group-header"
579
+ }, props.text);
580
+ }
581
+ /**
582
+ * The fold switch. `marker` is what tests (and the browser probe) click, so it
583
+ * belongs to the component rather than to either caller's markup.
584
+ * @param props - label, current state, DOM marker, click handler.
585
+ */
586
+ function FoldToggle(props) {
587
+ const { label, open, marker } = props;
588
+ return (0, react.createElement)("button", {
589
+ type: "button",
590
+ "aria-expanded": open,
591
+ style: TOGGLE_STYLE,
592
+ "data-dsh-brief-sidebar": marker,
593
+ onClick: () => {
594
+ props.onToggle();
595
+ }
596
+ }, label, (0, react.createElement)("svg", {
597
+ width: 10,
598
+ height: 10,
599
+ viewBox: "0 0 16 16",
600
+ "aria-hidden": true,
601
+ style: CHEVRON_STYLE$1(open)
602
+ }, (0, react.createElement)("path", {
603
+ d: "M4 6l4 4 4-4",
604
+ fill: "none",
605
+ stroke: "currentColor",
606
+ strokeWidth: 1.5,
607
+ strokeLinecap: "round",
608
+ strokeLinejoin: "round"
609
+ })));
610
+ }
611
+ /** A quiet caption under a group, used for the "the list is capped" note. */
612
+ function GroupCaption(props) {
613
+ return (0, react.createElement)("div", {
614
+ style: CAPTION_STYLE,
615
+ "data-dsh-brief-sidebar": props.marker
616
+ }, props.text);
617
+ }
618
+ //#endregion
519
619
  //#region src/client/locales.ts
520
620
  /**
521
621
  * Plugin-owned i18n dictionaries.
@@ -560,6 +660,8 @@ window.__ModuleLoader__.load({
560
660
  "deliverables.empty": "本回合暂无改动文件",
561
661
  "deliverables.emptyHint": "会话里成功写入或修改文件后,它们会实时出现在这里。",
562
662
  "deliverables.history": "更早改动的文件 · {count}",
663
+ "deliverables.currentTurn": "本回合改动 · {count}",
664
+ "deliverables.historyCapped": "(会话共 {total} 个文件,列表已截断)",
563
665
  "deliverable.codeplanTag": "规划",
564
666
  "section.memorySlots": "记忆",
565
667
  "slots.empty": "当前没有激活的记忆槽位",
@@ -581,6 +683,9 @@ window.__ModuleLoader__.load({
581
683
  "recall.obsoleteWhy": "声明过时后,该记忆退场(可恢复),下一轮会告知模型",
582
684
  "recall.strikeHint": ";判过时的条目会一直保留在列表里并划上删除线(清空会话才移除)",
583
685
  "recall.supersededHidden": "已隐藏 {count} 条被摘要取代的旧召回(正文已不在上下文)",
686
+ "recall.activeGroup": "活跃召回 · {count}",
687
+ "recall.historyGroup": "其余历史召回 · {count}",
688
+ "recall.supersededTag": "已被摘要取代",
584
689
  "recall.noBody": "(本帧没有这条记忆的全文)",
585
690
  "section.skillUsage": "技能引用",
586
691
  "skillUsage.empty": "本会话还没载入过技能",
@@ -612,6 +717,8 @@ window.__ModuleLoader__.load({
612
717
  "deliverables.empty": "No files changed in this turn",
613
718
  "deliverables.emptyHint": "Files written or edited successfully in the session appear here live.",
614
719
  "deliverables.history": "Earlier changes · {count}",
720
+ "deliverables.currentTurn": "This turn · {count}",
721
+ "deliverables.historyCapped": "(the session produced {total} files; the list is capped)",
615
722
  "deliverable.codeplanTag": "Plan",
616
723
  "section.memorySlots": "Memory",
617
724
  "slots.empty": "No active memory slots",
@@ -633,6 +740,9 @@ window.__ModuleLoader__.load({
633
740
  "recall.obsoleteWhy": "Declaring it obsolete retires the record (restorable) and tells the model next turn",
634
741
  "recall.strikeHint": "; an entry you mark obsolete stays listed, struck through (removed only when you clear the session)",
635
742
  "recall.supersededHidden": "{count} older recalls hidden — their text was replaced by a summary",
743
+ "recall.activeGroup": "Active recalls · {count}",
744
+ "recall.historyGroup": "Earlier recalls · {count}",
745
+ "recall.supersededTag": "replaced by summary",
636
746
  "recall.noBody": "(this frame carries no full body for this record)",
637
747
  "section.skillUsage": "Skill references",
638
748
  "skillUsage.empty": "No skills loaded in this session",
@@ -664,6 +774,8 @@ window.__ModuleLoader__.load({
664
774
  "deliverables.empty": "このターンで変更されたファイルはありません",
665
775
  "deliverables.emptyHint": "セッションでファイルの書き込み・変更が成功すると、ここにリアルタイムで表示されます。",
666
776
  "deliverables.history": "以前の変更 · {count}",
777
+ "deliverables.currentTurn": "このターン · {count}",
778
+ "deliverables.historyCapped": "(セッション全体で {total} ファイル、一覧は上限で省略)",
667
779
  "deliverable.codeplanTag": "計画",
668
780
  "section.memorySlots": "メモリ",
669
781
  "slots.empty": "アクティブなメモリスロットはありません",
@@ -685,6 +797,9 @@ window.__ModuleLoader__.load({
685
797
  "recall.obsoleteWhy": "古いと宣言すると記録は退場(復元可)、次のターンでモデルに伝わります",
686
798
  "recall.strikeHint": ";古いと宣言した項目は取り消し線のまま一覧に保持されます(セッションをクリアすると消えます)",
687
799
  "recall.supersededHidden": "要約に置き換わった旧召回 {count} 件を非表示(本文はコンテキストにない)",
800
+ "recall.activeGroup": "アクティブな想起 · {count}",
801
+ "recall.historyGroup": "その他の履歴想起 · {count}",
802
+ "recall.supersededTag": "要約に置換済み",
688
803
  "recall.noBody": "このフレームには全文が含まれていない",
689
804
  "section.skillUsage": "スキル参照",
690
805
  "skillUsage.empty": "このセッションではスキルを読み込んでいません",
@@ -716,6 +831,8 @@ window.__ModuleLoader__.load({
716
831
  "deliverables.empty": "이번 턴에 변경된 파일이 없습니다",
717
832
  "deliverables.emptyHint": "세션에서 파일을 성공적으로 작성하거나 수정하면 여기에 실시간으로 표시됩니다.",
718
833
  "deliverables.history": "이전 변경 사항 · {count}",
834
+ "deliverables.currentTurn": "이번 턴 · {count}",
835
+ "deliverables.historyCapped": "(세션 전체 {total}개 파일, 목록은 잘림)",
719
836
  "deliverable.codeplanTag": "계획",
720
837
  "section.memorySlots": "메모리",
721
838
  "slots.empty": "활성화된 메모리 슬롯이 없습니다",
@@ -737,6 +854,9 @@ window.__ModuleLoader__.load({
737
854
  "recall.obsoleteWhy": "낡음으로 선언하면 기록이 퇴장하며(복원 가능), 다음 턴에서 모델에 알립니다",
738
855
  "recall.strikeHint": ";낡음으로 선언한 항목은 취소선 상태로 목록에 계속 표시됩니다(세션을 비우면 제거)",
739
856
  "recall.supersededHidden": "요약으로 대체된 이전 회수 {count}건 숨김(본문이 컨텍스트에 없음)",
857
+ "recall.activeGroup": "활성 회상 · {count}",
858
+ "recall.historyGroup": "나머지 이전 회상 · {count}",
859
+ "recall.supersededTag": "요약에 대체됨",
740
860
  "recall.noBody": "이 프레임에는 이 기록의 전문이 없습니다",
741
861
  "section.skillUsage": "스킬 참조",
742
862
  "skillUsage.empty": "이 세션에서 스킬을 불러오지 않았습니다",
@@ -768,6 +888,8 @@ window.__ModuleLoader__.load({
768
888
  "deliverables.empty": "Aucun fichier modifié à ce tour",
769
889
  "deliverables.emptyHint": "Les fichiers écrits ou modifiés dans la session apparaissent ici en direct.",
770
890
  "deliverables.history": "Modifications antérieures · {count}",
891
+ "deliverables.currentTurn": "Ce tour-ci · {count}",
892
+ "deliverables.historyCapped": "(la session a produit {total} fichiers ; la liste est tronquée)",
771
893
  "deliverable.codeplanTag": "Plan",
772
894
  "section.memorySlots": "Mémoire",
773
895
  "slots.empty": "Aucun emplacement mémoire actif",
@@ -789,6 +911,9 @@ window.__ModuleLoader__.load({
789
911
  "recall.obsoleteWhy": "Déclarer obsolète retire l’enregistrement (récupérable) et prévient le modèle au tour suivant",
790
912
  "recall.strikeHint": " ; une entrée déclarée obsolète reste listée, barrée (retirée uniquement quand vous videz la session)",
791
913
  "recall.supersededHidden": "{count} anciens rappels masqués — leur texte a été remplacé par un résumé",
914
+ "recall.activeGroup": "Rappels actifs · {count}",
915
+ "recall.historyGroup": "Rappels antérieurs · {count}",
916
+ "recall.supersededTag": "remplacé par le résumé",
792
917
  "recall.noBody": "(aucun texte complet pour cette entrée dans cette trame)",
793
918
  "section.skillUsage": "Références de compétences",
794
919
  "skillUsage.empty": "Aucune compétence chargée dans cette session",
@@ -820,6 +945,8 @@ window.__ModuleLoader__.load({
820
945
  "deliverables.empty": "Keine Dateien in diesem Durchgang geändert",
821
946
  "deliverables.emptyHint": "In der Sitzung erfolgreich geschriebene oder geänderte Dateien erscheinen hier live.",
822
947
  "deliverables.history": "Frühere Änderungen · {count}",
948
+ "deliverables.currentTurn": "Dieser Durchlauf · {count}",
949
+ "deliverables.historyCapped": "(die Sitzung erzeugte {total} Dateien; die Liste ist gekürzt)",
823
950
  "deliverable.codeplanTag": "Plan",
824
951
  "section.memorySlots": "Gedächtnis",
825
952
  "slots.empty": "Keine aktiven Speicherplätze",
@@ -841,6 +968,9 @@ window.__ModuleLoader__.load({
841
968
  "recall.obsoleteWhy": "Als veraltet erklären versetzt den Eintrag in den Ruhestand (wiederherstellbar) und informiert das Modell in der nächsten Runde",
842
969
  "recall.strikeHint": "; ein als veraltet markierter Eintrag bleibt durchgestrichen in der Liste (erst beim Leeren der Sitzung entfernt)",
843
970
  "recall.supersededHidden": "{count} ältere Treffer ausgeblendet — ihr Text wurde durch eine Zusammenfassung ersetzt",
971
+ "recall.activeGroup": "Aktive Abrufe · {count}",
972
+ "recall.historyGroup": "Weitere Abrufe · {count}",
973
+ "recall.supersededTag": "durch Zusammenfassung ersetzt",
844
974
  "recall.noBody": "(dieser Rahmen enthält keinen vollständigen Text für diesen Eintrag)",
845
975
  "section.skillUsage": "Skill-Referenzen",
846
976
  "skillUsage.empty": "In dieser Sitzung wurden keine Skills geladen",
@@ -872,6 +1002,8 @@ window.__ModuleLoader__.load({
872
1002
  "deliverables.empty": "Nessun file modificato in questo turno",
873
1003
  "deliverables.emptyHint": "I file scritti o modificati nella sessione appaiono qui in tempo reale.",
874
1004
  "deliverables.history": "Modifiche precedenti · {count}",
1005
+ "deliverables.currentTurn": "Questo turno · {count}",
1006
+ "deliverables.historyCapped": "(la sessione ha prodotto {total} file; l’elenco è troncato)",
875
1007
  "deliverable.codeplanTag": "Piano",
876
1008
  "section.memorySlots": "Memoria",
877
1009
  "slots.empty": "Nessuno slot di memoria attivo",
@@ -893,6 +1025,9 @@ window.__ModuleLoader__.load({
893
1025
  "recall.obsoleteWhy": "Dichiararlo obsoleto congeda il record (ripristinabile) e avvisa il modello al turno successivo",
894
1026
  "recall.strikeHint": "; una voce dichiarata obsoleta resta elencata, barrata (rimossa solo svuotando la sessione)",
895
1027
  "recall.supersededHidden": "{count} vecchi richiami nascosti — il testo è stato sostituito da un riassunto",
1028
+ "recall.activeGroup": "Richiami attivi · {count}",
1029
+ "recall.historyGroup": "Richiami precedenti · {count}",
1030
+ "recall.supersededTag": "sostituito dal riassunto",
896
1031
  "recall.noBody": "(questo frame non contiene il testo completo di questa voce)",
897
1032
  "section.skillUsage": "Riferimenti alle skill",
898
1033
  "skillUsage.empty": "Nessuna skill caricata in questa sessione",
@@ -924,6 +1059,8 @@ window.__ModuleLoader__.load({
924
1059
  "deliverables.empty": "За этот ход файлы не менялись",
925
1060
  "deliverables.emptyHint": "Успешно записанные или изменённые файлы появляются здесь в реальном времени.",
926
1061
  "deliverables.history": "Более ранние изменения · {count}",
1062
+ "deliverables.currentTurn": "Этот ход · {count}",
1063
+ "deliverables.historyCapped": "(в сессии всего {total} файлов; список усечён)",
927
1064
  "deliverable.codeplanTag": "План",
928
1065
  "section.memorySlots": "Память",
929
1066
  "slots.empty": "Активных слотов памяти нет",
@@ -945,6 +1082,9 @@ window.__ModuleLoader__.load({
945
1082
  "recall.obsoleteWhy": "Объявить устаревшим значит снимает запись восстанавлимо и сообщает модель на следующем ходе",
946
1083
  "recall.strikeHint": "; помеченная как устаревшая запись остаётся в списке, зачёркнутой (удаляется только при очистке сессии)",
947
1084
  "recall.supersededHidden": "Скрыто старых воспоминаний: {count} — их текст заменён кратким изложением",
1085
+ "recall.activeGroup": "Активные вызовы · {count}",
1086
+ "recall.historyGroup": "Прочие прежние вызовы · {count}",
1087
+ "recall.supersededTag": "заменено сводкой",
948
1088
  "recall.noBody": "В этом кадре нет полного текста этой записи",
949
1089
  "section.skillUsage": "Отсылки к скиллам",
950
1090
  "skillUsage.empty": "В этой сессии скиллы не загружались",
@@ -976,6 +1116,8 @@ window.__ModuleLoader__.load({
976
1116
  "deliverables.empty": "Ningún archivo modificado en este turno",
977
1117
  "deliverables.emptyHint": "Los archivos escritos o editados en la sesión aparecen aquí en directo.",
978
1118
  "deliverables.history": "Cambios anteriores · {count}",
1119
+ "deliverables.currentTurn": "Este turno · {count}",
1120
+ "deliverables.historyCapped": "(la sesión produjo {total} archivos; la lista está truncada)",
979
1121
  "deliverable.codeplanTag": "Plan",
980
1122
  "section.memorySlots": "Memoria",
981
1123
  "slots.empty": "No hay ranuras de memoria activas",
@@ -997,6 +1139,9 @@ window.__ModuleLoader__.load({
997
1139
  "recall.obsoleteWhy": "Declararlo obsoleto retira el registro (recuperable) y avisa al modelo en el turno siguiente",
998
1140
  "recall.strikeHint": "; una entrada marcada como obsoleta sigue en la lista, tachada (se retira solo al vaciar la sesión)",
999
1141
  "recall.supersededHidden": "{count} recuerdos antiguos ocultos — su texto fue reemplazado por un resumen",
1142
+ "recall.activeGroup": "Recuerdos activos · {count}",
1143
+ "recall.historyGroup": "Recuerdos anteriores · {count}",
1144
+ "recall.supersededTag": "reemplazado por el resumen",
1000
1145
  "recall.noBody": "(este fotograma no trae el texto completo de este registro)",
1001
1146
  "section.skillUsage": "Referencias de skills",
1002
1147
  "skillUsage.empty": "Esta sesión no ha cargado ningún skill",
@@ -1022,6 +1167,13 @@ window.__ModuleLoader__.load({
1022
1167
  function expandCount(template, count) {
1023
1168
  return template.replace("{count}", String(count));
1024
1169
  }
1170
+ /**
1171
+ * Fill a `{total}` placeholder — the counter that says how many there really
1172
+ * are, as opposed to how many the (capped) list shows.
1173
+ */
1174
+ function expandTotal(template, total) {
1175
+ return template.replace("{total}", String(total));
1176
+ }
1025
1177
  //#endregion
1026
1178
  //#region src/client/summary/DeliverablesSection.tsx
1027
1179
  /**
@@ -1066,26 +1218,6 @@ window.__ModuleLoader__.load({
1066
1218
  gap: 8,
1067
1219
  padding: "6px 12px"
1068
1220
  };
1069
- /** 折叠区开关行:与文件行同宽,tertiary 强调,整行可点。 */
1070
- const HISTORY_TOGGLE_STYLE = {
1071
- background: "none",
1072
- border: "none",
1073
- font: "inherit",
1074
- fontSize: 12,
1075
- color: "var(--dsw-alias-label-tertiary)",
1076
- cursor: "pointer",
1077
- display: "flex",
1078
- alignItems: "center",
1079
- gap: 4,
1080
- padding: "6px 12px",
1081
- textAlign: "left",
1082
- width: "calc(100% - 24px)"
1083
- };
1084
- const CHEVRON_STYLE$1 = (open) => ({
1085
- flexShrink: 0,
1086
- transition: "transform 0.16s",
1087
- transform: open ? "rotate(180deg)" : "none"
1088
- });
1089
1221
  const PATH_STYLE = {
1090
1222
  minWidth: 0,
1091
1223
  overflowWrap: "anywhere",
@@ -1142,8 +1274,9 @@ window.__ModuleLoader__.load({
1142
1274
  * The deliverables section. The subscription is unconditional (a plain
1143
1275
  * listener, not IO).
1144
1276
  *
1145
- * 分层:活跃文件(本回合 latest)直接列表;全会话更早改动折叠在开关行后面
1146
- * (与活跃文件按归一化路径去重),展开后是与活跃区相同的可点击文件行。
1277
+ * 分层:本回合改动(latest)与全会话更早改动分成两组各自计数——前者直列,后者
1278
+ * 折叠在开关行后面(与活跃文件按归一化路径去重),展开后是与活跃区相同的可点击
1279
+ * 文件行。列表有上限时,开关行说折叠区条数、附注说会话真数,两个数不混为一谈。
1147
1280
  * @param props - translation, client context, session scope.
1148
1281
  */
1149
1282
  function DeliverablesSection(props) {
@@ -1172,32 +1305,25 @@ window.__ModuleLoader__.load({
1172
1305
  return (0, react.createElement)("div", {
1173
1306
  style: SECTION_STYLE$3,
1174
1307
  "data-dsh-brief-sidebar": "deliverables-section"
1175
- }, (0, react.createElement)("div", { style: HEADER_STYLE$4 }, t("section.deliverables")), (0, react.createElement)("ul", { style: LIST_STYLE$3 }, latestPaths.map((path) => (0, react.createElement)(PathRow, {
1308
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$4 }, t("section.deliverables")), latestPaths.length > 0 && (0, react.createElement)(GroupHeader, {
1309
+ text: expandCount(t("deliverables.currentTurn"), latestPaths.length),
1310
+ marker: "deliverables-current-group"
1311
+ }), (0, react.createElement)("ul", { style: LIST_STYLE$3 }, latestPaths.map((path) => (0, react.createElement)(PathRow, {
1176
1312
  key: path,
1177
1313
  path,
1178
1314
  t,
1179
1315
  open
1180
- }))), history.length > 0 && (0, react.createElement)("button", {
1181
- type: "button",
1182
- "aria-expanded": historyOpen,
1183
- style: HISTORY_TOGGLE_STYLE,
1184
- onClick: () => {
1316
+ }))), history.length > 0 && (0, react.createElement)(FoldToggle, {
1317
+ label: expandCount(t("deliverables.history"), history.length),
1318
+ open: historyOpen,
1319
+ marker: "deliverables-history-toggle",
1320
+ onToggle: () => {
1185
1321
  setHistoryOpen((current) => !current);
1186
1322
  }
1187
- }, expandCount(t("deliverables.history"), history.length), (0, react.createElement)("svg", {
1188
- width: 10,
1189
- height: 10,
1190
- viewBox: "0 0 16 16",
1191
- "aria-hidden": true,
1192
- style: CHEVRON_STYLE$1(historyOpen)
1193
- }, (0, react.createElement)("path", {
1194
- d: "M4 6l4 4 4-4",
1195
- fill: "none",
1196
- stroke: "currentColor",
1197
- strokeWidth: 1.5,
1198
- strokeLinecap: "round",
1199
- strokeLinejoin: "round"
1200
- }))), historyOpen && (0, react.createElement)("ul", { style: LIST_STYLE$3 }, history.map((path) => (0, react.createElement)(PathRow, {
1323
+ }), history.length > 0 && view.sessionTotal > view.sessionPaths.length && (0, react.createElement)(GroupCaption, {
1324
+ text: expandTotal(t("deliverables.historyCapped"), view.sessionTotal),
1325
+ marker: "deliverables-history-capped"
1326
+ }), historyOpen && (0, react.createElement)("ul", { style: LIST_STYLE$3 }, history.map((path) => (0, react.createElement)(PathRow, {
1201
1327
  key: path,
1202
1328
  path,
1203
1329
  t,
@@ -2316,7 +2442,7 @@ window.__ModuleLoader__.load({
2316
2442
  * has already retired).
2317
2443
  */
2318
2444
  function RecallRow(props) {
2319
- const { item, selected, stale, mark, error, t } = props;
2445
+ const { item, selected, stale, wasSuperseded, mark, error, t } = props;
2320
2446
  const scenario = item.scenario === void 0 ? void 0 : item.scenario.join(" · ");
2321
2447
  const markText = mark === void 0 ? void 0 : mark === -1 ? t("recall.obsoleted") : expandCount(t("recall.rated"), mark);
2322
2448
  const staleText = stale && mark !== -1 ? t("recall.obsoleted") : void 0;
@@ -2344,7 +2470,10 @@ window.__ModuleLoader__.load({
2344
2470
  } }, (0, react.createElement)("span", { style: titleStyle }, item.title), scenario !== void 0 && (0, react.createElement)("p", { style: stale ? {
2345
2471
  ...SCENARIO_STYLE,
2346
2472
  ...STRIKE_STYLE
2347
- } : SCENARIO_STYLE }, scenario), staleText !== void 0 && (0, react.createElement)("span", { style: MARK_STYLE }, staleText), markText !== void 0 && (0, react.createElement)("span", { style: MARK_STYLE }, markText), error !== void 0 && (0, react.createElement)("span", { style: ERROR_STYLE }, `${t("recall.failed")}${error}`), selected && !stale && props.rating && (0, react.createElement)(RatingStrip, {
2473
+ } : SCENARIO_STYLE }, scenario), staleText !== void 0 && (0, react.createElement)("span", { style: MARK_STYLE }, staleText), wasSuperseded && (0, react.createElement)("span", {
2474
+ style: TYPE_STYLE,
2475
+ "data-dsh-brief-sidebar": "recall-superseded-tag"
2476
+ }, t("recall.supersededTag")), markText !== void 0 && (0, react.createElement)("span", { style: MARK_STYLE }, markText), error !== void 0 && (0, react.createElement)("span", { style: ERROR_STYLE }, `${t("recall.failed")}${error}`), selected && !stale && props.rating && (0, react.createElement)(RatingStrip, {
2348
2477
  current: mark,
2349
2478
  pending: props.pending,
2350
2479
  armed: props.armed,
@@ -2375,6 +2504,7 @@ window.__ModuleLoader__.load({
2375
2504
  const [errors, setErrors] = (0, react.useState)({});
2376
2505
  const [armed, setArmed] = (0, react.useState)(false);
2377
2506
  const [writeAbsent, setWriteAbsent] = (0, react.useState)(false);
2507
+ const [historyOpen, setHistoryOpen] = (0, react.useState)(false);
2378
2508
  const show = useConfigFlag(ctx, resolveShowMemoryRecall);
2379
2509
  const view = readRecall(useProjectionValue(ctx, scope?.sessionId, MEMORY_RECALL_KEY));
2380
2510
  const sessionId = scope?.sessionId ?? "";
@@ -2454,22 +2584,50 @@ window.__ModuleLoader__.load({
2454
2584
  if (view === void 0) return null;
2455
2585
  const isStale = (item) => item.retired === true || staleIds.includes(item.id);
2456
2586
  const rows = view.items.filter((item) => item.superseded !== true || item.retired === true);
2457
- const hidden = view.items.length - rows.length;
2587
+ const historyRows = view.items.filter((item) => item.superseded === true && item.retired !== true);
2588
+ const hidden = historyRows.length;
2458
2589
  if (rows.length === 0) return (0, react.createElement)("div", {
2459
2590
  style: SECTION_STYLE,
2460
2591
  "data-dsh-brief-sidebar": "memory-recall-section"
2461
2592
  }, (0, react.createElement)("div", { style: HEADER_STYLE$1 }, t("section.memoryRecall")), (0, react.createElement)("div", {
2462
2593
  style: EMPTY_STYLE,
2463
2594
  ...hidden > 0 ? { "data-dsh-brief-sidebar": "recall-superseded-note" } : {}
2464
- }, hidden > 0 ? expandCount(t("recall.supersededHidden"), hidden) : t("recall.empty")));
2595
+ }, hidden > 0 ? expandCount(t("recall.supersededHidden"), hidden) : t("recall.empty")), historyRows.length > 0 && (0, react.createElement)(FoldToggle, {
2596
+ label: expandCount(t("recall.historyGroup"), historyRows.length),
2597
+ open: historyOpen,
2598
+ marker: "recall-history-toggle",
2599
+ onToggle: () => {
2600
+ setHistoryOpen((current) => !current);
2601
+ }
2602
+ }), historyOpen && (0, react.createElement)("ul", { style: LIST_STYLE }, historyRows.map((item) => (0, react.createElement)(RecallRow, {
2603
+ key: item.id,
2604
+ item,
2605
+ selected: false,
2606
+ stale: isStale(item),
2607
+ wasSuperseded: true,
2608
+ pending: false,
2609
+ armed: false,
2610
+ mark: void 0,
2611
+ error: void 0,
2612
+ rating: false,
2613
+ t,
2614
+ onSelect: () => {},
2615
+ onScore: () => {},
2616
+ onEnter: () => {},
2617
+ onLeave: () => {}
2618
+ }))));
2465
2619
  return (0, react.createElement)("div", {
2466
2620
  style: SECTION_STYLE,
2467
2621
  "data-dsh-brief-sidebar": "memory-recall-section"
2468
- }, (0, react.createElement)("div", { style: HEADER_STYLE$1 }, t("section.memoryRecall")), rating && (0, react.createElement)("div", { style: HINT_STYLE$1 }, `${t("recall.rateHint")}${t("recall.strikeHint")}`), (0, react.createElement)("ul", { style: LIST_STYLE }, rows.map((item) => (0, react.createElement)(RecallRow, {
2622
+ }, (0, react.createElement)("div", { style: HEADER_STYLE$1 }, t("section.memoryRecall")), rating && (0, react.createElement)("div", { style: HINT_STYLE$1 }, `${t("recall.rateHint")}${t("recall.strikeHint")}`), rows.length > 0 && (0, react.createElement)(GroupHeader, {
2623
+ text: expandCount(t("recall.activeGroup"), rows.length),
2624
+ marker: "recall-active-group"
2625
+ }), (0, react.createElement)("ul", { style: LIST_STYLE }, rows.map((item) => (0, react.createElement)(RecallRow, {
2469
2626
  key: item.id,
2470
2627
  item,
2471
2628
  selected: selectedId === item.id,
2472
2629
  stale: isStale(item),
2630
+ wasSuperseded: item.superseded === true,
2473
2631
  pending: pendingId === item.id,
2474
2632
  armed: armed && selectedId === item.id,
2475
2633
  mark: marks[item.id] ?? item.rating,
@@ -2486,6 +2644,29 @@ window.__ModuleLoader__.load({
2486
2644
  onEnter(item.id, el);
2487
2645
  },
2488
2646
  onLeave
2647
+ }))), historyRows.length > 0 && (0, react.createElement)(FoldToggle, {
2648
+ label: expandCount(t("recall.historyGroup"), historyRows.length),
2649
+ open: historyOpen,
2650
+ marker: "recall-history-toggle",
2651
+ onToggle: () => {
2652
+ setHistoryOpen((current) => !current);
2653
+ }
2654
+ }), historyOpen && (0, react.createElement)("ul", { style: LIST_STYLE }, historyRows.map((item) => (0, react.createElement)(RecallRow, {
2655
+ key: item.id,
2656
+ item,
2657
+ selected: false,
2658
+ stale: isStale(item),
2659
+ wasSuperseded: true,
2660
+ pending: false,
2661
+ armed: false,
2662
+ mark: void 0,
2663
+ error: void 0,
2664
+ rating: false,
2665
+ t,
2666
+ onSelect: () => {},
2667
+ onScore: () => {},
2668
+ onEnter: () => {},
2669
+ onLeave: () => {}
2489
2670
  }))), (0, react.createElement)("div", { style: FOOTER_STYLE }, expandCount(t("recall.count"), view.count), hidden > 0 && (0, react.createElement)("div", { "data-dsh-brief-sidebar": "recall-superseded-note" }, expandCount(t("recall.supersededHidden"), hidden))), hover !== void 0 && typeof window !== "undefined" && (() => {
2490
2671
  const item = view.items.find((i) => i.id === hover.id);
2491
2672
  if (item === void 0) return null;