@timurproko/a1 0.2.5-dev.683 → 0.2.5-dev.704

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 (76) hide show
  1. package/dist/app/session-shell/prompt-suggestion-controller.d.ts +2 -0
  2. package/dist/app/session-shell/prompt-suggestion-controller.js +10 -0
  3. package/dist/app/session-shell/session-shell-root.js +15 -2
  4. package/dist/app/session-shell/session-shell.js +80 -23
  5. package/dist/app/session-shell/share-operation.d.ts +3 -0
  6. package/dist/app/session-shell/share-operation.js +11 -0
  7. package/dist/composition/owned-ui.js +2 -0
  8. package/dist/composition/settings-route-host.js +1 -1
  9. package/dist/contracts/presentation/index.d.ts +11 -1
  10. package/dist/contracts/presentation/index.js +6 -2
  11. package/dist/features/owned-ui/project-trust-dialog.js +7 -5
  12. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -2
  13. package/dist/features/owned-ui/reference-screen-app.js +10 -6
  14. package/dist/features/owned-ui/settings-app.d.ts +1 -1
  15. package/dist/features/owned-ui/settings-app.js +342 -66
  16. package/dist/integrations/pi/components/index.d.ts +3 -2
  17. package/dist/integrations/pi/components/index.js +2 -1
  18. package/dist/integrations/pi/components/modal-frame.d.ts +11 -1
  19. package/dist/integrations/pi/components/modal-frame.js +29 -1
  20. package/dist/integrations/pi/components/models-dialog.js +19 -19
  21. package/dist/integrations/pi/components/share-operation-dialog.d.ts +6 -0
  22. package/dist/integrations/pi/components/share-operation-dialog.js +40 -0
  23. package/dist/integrations/pi/components/shell-editor-autocomplete.js +55 -8
  24. package/dist/integrations/pi/components/shell-extension-ui.d.ts +3 -0
  25. package/dist/integrations/pi/components/shell-extension-ui.js +8 -4
  26. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +0 -4
  27. package/dist/integrations/pi/components/shell-selectors-dialogs.js +56 -34
  28. package/dist/integrations/pi/components/shell-session-info-reference.js +35 -2
  29. package/dist/integrations/pi/components/shell-shared-facade.d.ts +3 -1
  30. package/dist/integrations/pi/components/shell-shared-facade.js +13 -1
  31. package/dist/integrations/pi/components/skills-dialog.js +21 -8
  32. package/dist/integrations/pi/components/theme.d.ts +8 -1
  33. package/dist/integrations/pi/components/theme.js +28 -1
  34. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
  35. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +20 -6
  36. package/dist/integrations/pi/components/upstream/components/extension-editor.js +2 -2
  37. package/dist/integrations/pi/components/upstream/components/extension-input.js +4 -5
  38. package/dist/integrations/pi/components/upstream/components/extension-selector.js +2 -3
  39. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +4 -2
  40. package/dist/integrations/pi/components/upstream/components/owned-editor.js +48 -12
  41. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -2
  42. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +4 -15
  43. package/dist/integrations/pi/components/upstream/components/session-selector.d.ts +2 -0
  44. package/dist/integrations/pi/components/upstream/components/session-selector.js +137 -110
  45. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +9 -5
  46. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +65 -26
  47. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +31 -21
  48. package/dist/integrations/pi/components/upstream/components/tree-selector.js +255 -214
  49. package/dist/integrations/pi/components/upstream/components/trust-selector.js +13 -15
  50. package/dist/integrations/pi/engine/adapter.js +21 -2
  51. package/dist/integrations/pi/engine/workflow-runner.js +2 -1
  52. package/dist/native/darwin-arm64/manifest.json +1 -1
  53. package/dist/native/linux-x64/manifest.json +1 -1
  54. package/dist/native/win32-x64/manifest.json +2 -2
  55. package/dist/native/win32-x64/process-guardian.exe +0 -0
  56. package/dist/ui/apps/host.d.ts +4 -3
  57. package/dist/ui/apps/host.js +9 -4
  58. package/dist/ui/components/dialog-panel.d.ts +15 -0
  59. package/dist/ui/components/dialog-panel.js +55 -9
  60. package/dist/ui/components/index.d.ts +3 -3
  61. package/dist/ui/components/index.js +2 -2
  62. package/dist/ui/components/line-input.d.ts +2 -0
  63. package/dist/ui/components/line-input.js +2 -2
  64. package/dist/ui/components/list-view.d.ts +3 -3
  65. package/dist/ui/components/list-view.js +8 -9
  66. package/dist/ui/components/prompt-input.d.ts +2 -2
  67. package/dist/ui/components/prompt-input.js +5 -5
  68. package/dist/ui/components/shortcut-hints.d.ts +10 -3
  69. package/dist/ui/components/shortcut-hints.js +42 -6
  70. package/dist/ui/components/shortcuts.d.ts +1 -1
  71. package/dist/ui/components/shortcuts.js +4 -0
  72. package/dist/ui/components/theme.d.ts +1 -1
  73. package/dist/ui/components/value-menu.js +3 -1
  74. package/dist/ui/settings/manager.d.ts +1 -0
  75. package/dist/ui/settings/manager.js +9 -3
  76. package/package.json +1 -1
@@ -1,17 +1,24 @@
1
1
  /**
2
2
  * Provenance: @earendil-works/pi-coding-agent 1.0.4 (MIT), commit 7c10bd4337495ee613f2224843ecdf349b80d1df,
3
3
  * packages/coding-agent/src/modes/interactive/components/tree-selector.ts.
4
- * Modifications: Source-synchronized tree selector port: preserve filtering, folding, labels, copying,
5
- * tree navigation, key hints, focus, and viewport behavior while remapping public types/components
6
- * plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton;
7
- * bare A1 uses the shared semantic modal shortcut row and compact padded modal frame.
8
- * Deviations: owned-modal-shortcut-hints.
4
+ * Modifications: Port remaps public types/components plus owned keybindings/theme helpers while
5
+ * preserving tree behavior; bare A1 uses compact modal chrome and label editing, four-mode
6
+ * Models-style filter status with Tab/Shift+Tab directional cycling and concise all-first presentation
7
+ * excluding internal bookkeeping, standard search input, blue item-bounded menu-arrow selection that
8
+ * preserves per-entry semantic foregrounds and has no path bullets, accent entry labels, bracketed
9
+ * timestamps, numeric result counters without duplicate label-time status, and a stateful time on/off
10
+ * shortcut, semantic role colors with session naming for the system root, standard
11
+ * paging/first-last/non-root containing-branch folding keys with a permanently expanded system session
12
+ * entry even behind hidden metadata, single-character ellipses on both clipped edges with
13
+ * bracket-delimiter preservation and selected-fragment highlighting, and Models-ordered semantic
14
+ * Type/search shortcut footers without a redundant select hint.
15
+ * Deviations: owned-modal-shortcut-hints, owned-session-tree-dialog.
9
16
  */
10
- import { Container, Input, Spacer, sliceByColumn, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
17
+ import { Container, Input, matchesKey, Spacer, sliceByColumn, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
11
18
  import { DynamicBorder } from "../../../startup-public.js";
12
19
  import { KeybindingsManager } from "../adjacent/core/keybindings.js";
13
20
  import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
14
- import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
21
+ import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalShortcutHints } from "../../theme.js";
15
22
  const theme = new Proxy({}, {
16
23
  get(_target, property) {
17
24
  const active = piTheme();
@@ -19,7 +26,7 @@ const theme = new Proxy({}, {
19
26
  return typeof value === "function" ? value.bind(active) : value;
20
27
  },
21
28
  });
22
- const treeKeybindings = KeybindingsManager.create();
29
+ const treeKeybindings = KeybindingsManager.createForOwnedInput();
23
30
  function formatKeyText(key) {
24
31
  return key
25
32
  .split("/")
@@ -57,26 +64,44 @@ function renderHorizontalViewport(rows, width) {
57
64
  }
58
65
  // Clip only the body; the fixed-width gutter remains visible as navigation context.
59
66
  return rows.map((row) => {
60
- const line = horizontalScroll > 0
61
- ? `${row.gutter}${sliceByColumn(row.body, horizontalScroll, viewportWidth, true)}\x1b[0m`
62
- : row.gutter + row.body;
67
+ if (viewportWidth === 0)
68
+ return truncateToWidth(row.gutter, width, "");
69
+ const leftClipped = horizontalScroll > 0;
70
+ const widthAfterLeftMarker = Math.max(0, viewportWidth - (leftClipped ? 1 : 0));
71
+ const rightClipped = row.bodyWidth - horizontalScroll > widthAfterLeftMarker;
72
+ const rightMarkerText = `…${row.rightClipSuffix}`;
73
+ const rightMarkerWidth = rightClipped ? visibleWidth(rightMarkerText) : 0;
74
+ const bodyWidth = Math.max(0, widthAfterLeftMarker - rightMarkerWidth);
75
+ const marker = (text) => row.isSelected
76
+ ? theme.bg("selectedBg", theme.fg("muted", text))
77
+ : theme.fg("muted", text);
78
+ const body = sliceByColumn(row.body, horizontalScroll, bodyWidth, true);
79
+ const line = `${row.gutter}${leftClipped ? marker("…") : ""}${body}${rightClipped ? marker(rightMarkerText) : ""}\x1b[0m`;
63
80
  return truncateToWidth(line, width, "");
64
81
  });
65
82
  }
83
+ const FILTER_MODES = ["all", "no-tools", "user-only", "labeled-only"];
84
+ const FILTER_LABELS = {
85
+ all: "all",
86
+ default: "all",
87
+ "no-tools": "no tools",
88
+ "user-only": "user",
89
+ "labeled-only": "labeled",
90
+ };
66
91
  class TreeList {
67
92
  flatNodes = [];
68
93
  filteredNodes = [];
69
94
  selectedIndex = 0;
70
95
  currentLeafId;
71
96
  maxVisibleLines;
72
- filterMode = "default";
97
+ filterMode = "all";
73
98
  searchQuery = "";
74
99
  toolCallMap = new Map();
75
100
  multipleRoots = false;
76
101
  showLabelTimestamps = false;
77
- activePathIds = new Set();
78
102
  visibleParentMap = new Map();
79
103
  visibleChildrenMap = new Map();
104
+ sessionRootIds = new Set();
80
105
  lastSelectedId = null;
81
106
  foldedNodes = new Set();
82
107
  onSelect;
@@ -86,10 +111,9 @@ class TreeList {
86
111
  constructor(tree, currentLeafId, maxVisibleLines, initialSelectedId, initialFilterMode) {
87
112
  this.currentLeafId = currentLeafId;
88
113
  this.maxVisibleLines = maxVisibleLines;
89
- this.filterMode = initialFilterMode ?? "default";
114
+ this.filterMode = initialFilterMode && initialFilterMode !== "default" ? initialFilterMode : "all";
90
115
  this.multipleRoots = tree.length > 1;
91
116
  this.flatNodes = this.flattenTree(tree);
92
- this.buildActivePath();
93
117
  this.applyFilter();
94
118
  // Start with initialSelectedId if provided, otherwise current leaf
95
119
  const targetId = initialSelectedId ?? currentLeafId;
@@ -124,29 +148,10 @@ class TreeList {
124
148
  // Fallback: last visible entry
125
149
  return this.filteredNodes.length - 1;
126
150
  }
127
- /** Build the set of entry IDs on the path from root to current leaf */
128
- buildActivePath() {
129
- this.activePathIds.clear();
130
- if (!this.currentLeafId)
131
- return;
132
- // Build a map of id -> entry for parent lookup
133
- const entryMap = new Map();
134
- for (const flatNode of this.flatNodes) {
135
- entryMap.set(flatNode.node.entry.id, flatNode);
136
- }
137
- // Walk from leaf to root
138
- let currentId = this.currentLeafId;
139
- while (currentId) {
140
- this.activePathIds.add(currentId);
141
- const node = entryMap.get(currentId);
142
- if (!node)
143
- break;
144
- currentId = node.node.entry.parentId ?? null;
145
- }
146
- }
147
151
  flattenTree(roots) {
148
152
  const result = [];
149
153
  this.toolCallMap.clear();
154
+ this.sessionRootIds.clear();
150
155
  const stack = [];
151
156
  // Determine which subtrees contain the active leaf (to sort current branch first)
152
157
  // Use iterative post-order traversal to avoid stack overflow
@@ -186,8 +191,11 @@ class TreeList {
186
191
  }
187
192
  while (stack.length > 0) {
188
193
  const [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild] = stack.pop();
189
- // Extract tool calls from assistant messages for later lookup
194
+ // Extract structural roots and tool calls from messages for later lookup.
190
195
  const entry = node.entry;
196
+ if (entry.type === "message" && entry.message.role === "system") {
197
+ this.sessionRootIds.add(entry.id);
198
+ }
191
199
  if (entry.type === "message" && entry.message.role === "assistant") {
192
200
  const content = entry.message.content;
193
201
  if (Array.isArray(content)) {
@@ -266,7 +274,7 @@ class TreeList {
266
274
  const searchTokens = this.searchQuery.toLowerCase().split(/\s+/).filter(Boolean);
267
275
  this.filteredNodes = this.flatNodes.filter((flatNode) => {
268
276
  const entry = flatNode.node.entry;
269
- if (entry.type === "usage")
277
+ if (entry.type === "usage" || entry.type === "model_change" || entry.type === "thinking_level_change")
270
278
  return false;
271
279
  const isCurrentLeaf = entry.id === this.currentLeafId;
272
280
  // Skip assistant messages with only tool calls (no text) unless error/aborted
@@ -286,8 +294,6 @@ class TreeList {
286
294
  const isSettingsEntry = entry.type === "label" ||
287
295
  entry.type === "context_edit" ||
288
296
  entry.type === "custom" ||
289
- entry.type === "model_change" ||
290
- entry.type === "thinking_level_change" ||
291
297
  entry.type === "session_info";
292
298
  switch (this.filterMode) {
293
299
  case "user-only":
@@ -303,11 +309,8 @@ class TreeList {
303
309
  passesFilter = flatNode.node.label !== undefined;
304
310
  break;
305
311
  case "all":
306
- // Show everything
307
- passesFilter = true;
308
- break;
309
312
  default:
310
- // Default mode: hide settings/bookkeeping entries
313
+ // Product "all" omits internal settings/bookkeeping entries.
311
314
  passesFilter = !isSettingsEntry;
312
315
  break;
313
316
  }
@@ -523,6 +526,12 @@ class TreeList {
523
526
  getSearchQuery() {
524
527
  return this.searchQuery;
525
528
  }
529
+ getFilterMode() {
530
+ return this.filterMode;
531
+ }
532
+ isLabelTimestampVisible() {
533
+ return this.showLabelTimestamps;
534
+ }
526
535
  getSelectedNode() {
527
536
  return this.filteredNodes[this.selectedIndex]?.node;
528
537
  }
@@ -545,32 +554,10 @@ class TreeList {
545
554
  }
546
555
  }
547
556
  }
548
- getStatusLabels() {
549
- let labels = "";
550
- switch (this.filterMode) {
551
- case "no-tools":
552
- labels += " [no-tools]";
553
- break;
554
- case "user-only":
555
- labels += " [user]";
556
- break;
557
- case "labeled-only":
558
- labels += " [labeled]";
559
- break;
560
- case "all":
561
- labels += " [all]";
562
- break;
563
- }
564
- if (this.showLabelTimestamps) {
565
- labels += " [+label time]";
566
- }
567
- return labels;
568
- }
569
557
  render(width) {
570
558
  const lines = [];
571
559
  if (this.filteredNodes.length === 0) {
572
560
  lines.push(truncateToWidth(theme.fg("muted", " No entries found"), width));
573
- lines.push(truncateToWidth(theme.fg("muted", ` (0/0)${this.getStatusLabels()}`), width));
574
561
  return lines;
575
562
  }
576
563
  const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisibleLines / 2), this.filteredNodes.length - this.maxVisibleLines));
@@ -580,8 +567,8 @@ class TreeList {
580
567
  const flatNode = this.filteredNodes[i];
581
568
  const entry = flatNode.node.entry;
582
569
  const isSelected = i === this.selectedIndex;
583
- // Build line: cursor + prefix + path marker + label + content
584
- const cursor = isSelected ? theme.fg("accent", "› ") : " ";
570
+ // Build line: menu arrow + tree prefix + path marker + label + content
571
+ const cursor = isSelected ? theme.fg("accent", "→ ") : " ";
585
572
  // If multiple roots, shift display (roots at 0, not 1)
586
573
  const displayIndent = this.multipleRoots ? Math.max(0, flatNode.indent - 1) : flatNode.indent;
587
574
  // Build prefix with gutters at their correct positions
@@ -626,15 +613,12 @@ class TreeList {
626
613
  // Fold marker for nodes without connectors (roots)
627
614
  const showsFoldInConnector = flatNode.showConnector && !flatNode.isVirtualRootChild;
628
615
  const foldMarker = isFolded && !showsFoldInConnector ? theme.fg("accent", "⊞ ") : "";
629
- // Active path marker - shown right before the entry text
630
- const isOnActivePath = this.activePathIds.has(entry.id);
631
- const pathMarker = isOnActivePath ? theme.fg("accent", "• ") : "";
632
- const label = flatNode.node.label ? theme.fg("warning", `[${flatNode.node.label}] `) : "";
616
+ const label = flatNode.node.label ? theme.fg("accent", `[${flatNode.node.label}] `) : "";
633
617
  const labelTimestamp = this.showLabelTimestamps && flatNode.node.label && flatNode.node.labelTimestamp
634
- ? theme.fg("muted", `${this.formatLabelTimestamp(flatNode.node.labelTimestamp)} `)
618
+ ? theme.fg("accent", `[${this.formatLabelTimestamp(flatNode.node.labelTimestamp)}] `)
635
619
  : "";
636
- const content = this.getEntryDisplayText(flatNode.node, isSelected);
637
- const prefixPart = theme.fg("dim", prefix) + foldMarker + pathMarker;
620
+ const content = this.getEntryDisplayText(flatNode.node);
621
+ const prefixPart = theme.fg("dim", prefix) + foldMarker;
638
622
  const anchorCol = visibleWidth(prefixPart);
639
623
  let gutter = cursor;
640
624
  let body = prefixPart + label + labelTimestamp + content;
@@ -642,58 +626,53 @@ class TreeList {
642
626
  gutter = theme.bg("selectedBg", gutter);
643
627
  body = theme.bg("selectedBg", body);
644
628
  }
645
- renderedRows.push({ gutter, body, anchorCol, bodyWidth: visibleWidth(body), isSelected });
629
+ const rightClipSuffix = entry.type === "message" && entry.message.role === "toolResult" ? "]" : "";
630
+ renderedRows.push({ gutter, body, anchorCol, bodyWidth: visibleWidth(body), rightClipSuffix, isSelected });
646
631
  }
647
632
  lines.push(...renderHorizontalViewport(renderedRows, width));
648
- lines.push(truncateToWidth(theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredNodes.length})${this.getStatusLabels()}`), width));
633
+ lines.push(truncateToWidth(theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredNodes.length})`), width));
649
634
  return lines;
650
635
  }
651
- getEntryDisplayText(node, isSelected) {
636
+ getEntryDisplayText(node) {
652
637
  const entry = node.entry;
653
638
  let result;
654
639
  const normalize = (s) => s.replace(/[\n\t]/g, " ").trim();
640
+ const primary = (color, text) => theme.fg(color, text);
641
+ const description = (text, color = "muted") => theme.fg(color, text);
655
642
  switch (entry.type) {
656
643
  case "message": {
657
644
  const msg = entry.message;
658
645
  const role = msg.role;
659
646
  if (role === "user") {
660
- const msgWithContent = msg;
661
- const content = normalize(this.extractContent(msgWithContent.content));
662
- result = theme.fg("accent", "user: ") + content;
647
+ const content = normalize(this.extractContent(msg.content));
648
+ result = primary("success", "user: ") + description(content);
663
649
  }
664
650
  else if (role === "assistant") {
665
- const msgWithContent = msg;
666
- const textContent = normalize(this.extractContent(msgWithContent.content));
667
- if (textContent) {
668
- result = theme.fg("success", "assistant: ") + textContent;
669
- }
670
- else if (msgWithContent.stopReason === "aborted") {
671
- result = theme.fg("success", "assistant: ") + theme.fg("muted", "(aborted)");
672
- }
673
- else if (msgWithContent.errorMessage) {
674
- const errMsg = normalize(msgWithContent.errorMessage).slice(0, 80);
675
- result = theme.fg("success", "assistant: ") + theme.fg("error", errMsg);
676
- }
677
- else {
678
- result = theme.fg("success", "assistant: ") + theme.fg("muted", "(no content)");
679
- }
651
+ const assistant = msg;
652
+ const textContent = normalize(this.extractContent(assistant.content));
653
+ const label = primary("warning", "assistant: ");
654
+ if (textContent)
655
+ result = label + description(textContent);
656
+ else if (assistant.stopReason === "aborted")
657
+ result = label + description("(aborted)", "muted");
658
+ else if (assistant.errorMessage)
659
+ result = label + description(normalize(assistant.errorMessage).slice(0, 80), "error");
660
+ else
661
+ result = label + description("(no content)", "muted");
680
662
  }
681
663
  else if (role === "toolResult") {
682
664
  const toolMsg = msg;
683
665
  const toolCall = toolMsg.toolCallId ? this.toolCallMap.get(toolMsg.toolCallId) : undefined;
684
- if (toolCall) {
685
- result = theme.fg("muted", this.formatToolCall(toolCall.name, toolCall.arguments));
686
- }
687
- else {
688
- result = theme.fg("muted", `[${toolMsg.toolName ?? "tool"}]`);
689
- }
666
+ result = primary("muted", toolCall
667
+ ? this.formatToolCall(toolCall.name, toolCall.arguments)
668
+ : `[${toolMsg.toolName ?? "tool"}]`);
690
669
  }
691
670
  else if (role === "bashExecution") {
692
- const bashMsg = msg;
693
- result = theme.fg("dim", `[bash]: ${normalize(bashMsg.command ?? "")}`);
671
+ const command = normalize(msg.command ?? "");
672
+ result = primary("dim", "[bash]: ") + description(command, "dim");
694
673
  }
695
674
  else {
696
- result = theme.fg("dim", `[${role}]`);
675
+ result = primary("dim", role === "system" ? "session" : `[${role}]`);
697
676
  }
698
677
  break;
699
678
  }
@@ -704,41 +683,41 @@ class TreeList {
704
683
  .filter((c) => c.type === "text")
705
684
  .map((c) => c.text)
706
685
  .join("");
707
- result = theme.fg("customMessageLabel", `[${entry.customType}]: `) + normalize(content);
686
+ result = primary("customMessageLabel", `[${entry.customType}]: `) + description(normalize(content));
708
687
  break;
709
688
  }
710
689
  case "compaction": {
711
690
  const tokens = Math.round(entry.tokensBefore / 1000);
712
- result = theme.fg("borderAccent", `[compaction: ${tokens}k tokens]`);
691
+ result = primary("borderAccent", `[compaction: ${tokens}k tokens]`);
713
692
  break;
714
693
  }
715
694
  case "branch_summary":
716
- result = theme.fg("warning", `[branch summary]: `) + normalize(entry.summary);
695
+ result = primary("warning", "[branch summary]: ") + description(normalize(entry.summary));
717
696
  break;
718
697
  case "model_change":
719
- result = theme.fg("dim", `[model: ${entry.modelId}]`);
698
+ result = primary("dim", `[model: ${entry.modelId}]`);
720
699
  break;
721
700
  case "thinking_level_change":
722
- result = theme.fg("dim", `[thinking: ${entry.thinkingLevel}]`);
701
+ result = primary("dim", `[thinking: ${entry.thinkingLevel}]`);
723
702
  break;
724
703
  case "custom":
725
- result = theme.fg("dim", `[custom: ${entry.customType}]`);
704
+ result = primary("dim", `[custom: ${entry.customType}]`);
726
705
  break;
727
706
  case "context_edit":
728
- result = theme.fg("dim", `[context ${entry.replacement === null ? "omit" : "replace"}: ${entry.targetId}]`);
707
+ result = primary("dim", `[context ${entry.replacement === null ? "omit" : "replace"}: ${entry.targetId}]`);
729
708
  break;
730
709
  case "label":
731
- result = theme.fg("dim", `[label: ${entry.label ?? "(cleared)"}]`);
710
+ result = primary("dim", `[label: ${entry.label ?? "(cleared)"}]`);
732
711
  break;
733
712
  case "session_info":
734
713
  result = entry.name
735
- ? [theme.fg("dim", "[title: "), theme.fg("dim", entry.name), theme.fg("dim", "]")].join("")
714
+ ? primary("dim", `[title: ${entry.name}]`)
736
715
  : [theme.fg("dim", "[title: "), theme.italic(theme.fg("dim", "empty")), theme.fg("dim", "]")].join("");
737
716
  break;
738
717
  default:
739
718
  result = "";
740
719
  }
741
- return isSelected ? theme.bold(result) : result;
720
+ return result;
742
721
  }
743
722
  formatLabelTimestamp(timestamp) {
744
723
  const date = new Date(timestamp);
@@ -850,7 +829,7 @@ class TreeList {
850
829
  .replace(/[\n\t]/g, " ")
851
830
  .trim()
852
831
  .slice(0, 50);
853
- return `[bash: ${cmd}${rawCmd.length > 50 ? "..." : ""}]`;
832
+ return `[bash: ${cmd}${rawCmd.length > 50 ? "…" : ""}]`;
854
833
  }
855
834
  case "grep": {
856
835
  const pattern = String(args.pattern || "");
@@ -869,7 +848,7 @@ class TreeList {
869
848
  default: {
870
849
  // Custom tool - show name and truncated JSON args
871
850
  const argsStr = JSON.stringify(args).slice(0, 40);
872
- return `[${name}: ${argsStr}${JSON.stringify(args).length > 40 ? "..." : ""}]`;
851
+ return `[${name}: ${argsStr}${JSON.stringify(args).length > 40 ? "…" : ""}]`;
873
852
  }
874
853
  }
875
854
  }
@@ -881,6 +860,21 @@ class TreeList {
881
860
  else if (kb.matches(keyData, "tui.select.down")) {
882
861
  this.selectedIndex = this.selectedIndex === this.filteredNodes.length - 1 ? 0 : this.selectedIndex + 1;
883
862
  }
863
+ else if (kb.matches(keyData, "owned.tree.collapse")) {
864
+ const currentId = this.filteredNodes[this.selectedIndex]?.node.entry.id;
865
+ const branchId = currentId ? this.findNearestExpandedBranch(currentId) : null;
866
+ if (branchId) {
867
+ this.foldedNodes.add(branchId);
868
+ this.applyFilter();
869
+ }
870
+ }
871
+ else if (kb.matches(keyData, "owned.tree.expand")) {
872
+ const currentId = this.filteredNodes[this.selectedIndex]?.node.entry.id;
873
+ if (currentId && this.foldedNodes.has(currentId)) {
874
+ this.foldedNodes.delete(currentId);
875
+ this.applyFilter();
876
+ }
877
+ }
884
878
  else if (kb.matches(keyData, "app.tree.foldOrUp")) {
885
879
  const currentId = this.filteredNodes[this.selectedIndex]?.node.entry.id;
886
880
  if (currentId && this.isFoldable(currentId) && !this.foldedNodes.has(currentId)) {
@@ -901,14 +895,18 @@ class TreeList {
901
895
  this.selectedIndex = this.findBranchSegmentStart("down");
902
896
  }
903
897
  }
904
- else if (kb.matches(keyData, "tui.editor.cursorLeft") || kb.matches(keyData, "tui.select.pageUp")) {
905
- // Page up
898
+ else if (kb.matches(keyData, "tui.select.pageUp")) {
906
899
  this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisibleLines);
907
900
  }
908
- else if (kb.matches(keyData, "tui.editor.cursorRight") || kb.matches(keyData, "tui.select.pageDown")) {
909
- // Page down
901
+ else if (kb.matches(keyData, "tui.select.pageDown")) {
910
902
  this.selectedIndex = Math.min(this.filteredNodes.length - 1, this.selectedIndex + this.maxVisibleLines);
911
903
  }
904
+ else if (kb.matches(keyData, "owned.tree.first")) {
905
+ this.selectedIndex = 0;
906
+ }
907
+ else if (kb.matches(keyData, "owned.tree.last")) {
908
+ this.selectedIndex = Math.max(0, this.filteredNodes.length - 1);
909
+ }
912
910
  else if (kb.matches(keyData, "tui.select.confirm")) {
913
911
  const selected = this.filteredNodes[this.selectedIndex];
914
912
  if (selected && this.onSelect) {
@@ -929,48 +927,42 @@ class TreeList {
929
927
  }
930
928
  }
931
929
  else if (kb.matches(keyData, "app.tree.filter.default")) {
932
- // Direct filter: default
933
- this.filterMode = "default";
930
+ this.filterMode = "all";
934
931
  this.foldedNodes.clear();
935
932
  this.applyFilter();
936
933
  }
937
934
  else if (kb.matches(keyData, "app.tree.filter.noTools")) {
938
- // Toggle filter: no-tools ↔ default
939
- this.filterMode = this.filterMode === "no-tools" ? "default" : "no-tools";
935
+ // Toggle filter: no-tools ↔ all
936
+ this.filterMode = this.filterMode === "no-tools" ? "all" : "no-tools";
940
937
  this.foldedNodes.clear();
941
938
  this.applyFilter();
942
939
  }
943
940
  else if (kb.matches(keyData, "app.tree.filter.userOnly")) {
944
- // Toggle filter: user-only ↔ default
945
- this.filterMode = this.filterMode === "user-only" ? "default" : "user-only";
941
+ // Toggle filter: user-only ↔ all
942
+ this.filterMode = this.filterMode === "user-only" ? "all" : "user-only";
946
943
  this.foldedNodes.clear();
947
944
  this.applyFilter();
948
945
  }
949
946
  else if (kb.matches(keyData, "app.tree.filter.labeledOnly")) {
950
- // Toggle filter: labeled-only ↔ default
951
- this.filterMode = this.filterMode === "labeled-only" ? "default" : "labeled-only";
947
+ // Toggle filter: labeled-only ↔ all
948
+ this.filterMode = this.filterMode === "labeled-only" ? "all" : "labeled-only";
952
949
  this.foldedNodes.clear();
953
950
  this.applyFilter();
954
951
  }
955
952
  else if (kb.matches(keyData, "app.tree.filter.all")) {
956
- // Toggle filter: all ↔ default
957
- this.filterMode = this.filterMode === "all" ? "default" : "all";
953
+ this.filterMode = "all";
958
954
  this.foldedNodes.clear();
959
955
  this.applyFilter();
960
956
  }
961
- else if (kb.matches(keyData, "app.tree.filter.cycleBackward")) {
962
- // Cycle filter backwards
963
- const modes = ["default", "no-tools", "user-only", "labeled-only", "all"];
964
- const currentIndex = modes.indexOf(this.filterMode);
965
- this.filterMode = modes[(currentIndex - 1 + modes.length) % modes.length];
957
+ else if (matchesKey(keyData, "shift+tab") || kb.matches(keyData, "app.tree.filter.cycleBackward")) {
958
+ const currentIndex = FILTER_MODES.indexOf(this.filterMode);
959
+ this.filterMode = FILTER_MODES[(currentIndex - 1 + FILTER_MODES.length) % FILTER_MODES.length];
966
960
  this.foldedNodes.clear();
967
961
  this.applyFilter();
968
962
  }
969
963
  else if (kb.matches(keyData, "app.tree.filter.cycleForward")) {
970
- // Cycle filter forwards: default → no-tools → user-only → labeled-only → all → default
971
- const modes = ["default", "no-tools", "user-only", "labeled-only", "all"];
972
- const currentIndex = modes.indexOf(this.filterMode);
973
- this.filterMode = modes[(currentIndex + 1) % modes.length];
964
+ const currentIndex = FILTER_MODES.indexOf(this.filterMode);
965
+ this.filterMode = FILTER_MODES[(currentIndex + 1) % FILTER_MODES.length];
974
966
  this.foldedNodes.clear();
975
967
  this.applyFilter();
976
968
  }
@@ -1003,11 +995,14 @@ class TreeList {
1003
995
  }
1004
996
  }
1005
997
  /**
1006
- * Whether a node can be folded. A node is foldable if it has visible children
1007
- * and is either a root (no visible parent) or a segment start (visible parent
1008
- * has multiple visible children).
998
+ * Whether a node can be folded. The system entry rendered as the session root
999
+ * stays expanded even when hidden metadata precedes it; other nodes are
1000
+ * foldable when they have visible children and either appear at the filtered
1001
+ * root or begin a segment below a visible branch point.
1009
1002
  */
1010
1003
  isFoldable(entryId) {
1004
+ if (this.sessionRootIds.has(entryId))
1005
+ return false;
1011
1006
  const children = this.visibleChildrenMap.get(entryId);
1012
1007
  if (!children || children.length === 0)
1013
1008
  return false;
@@ -1017,13 +1012,17 @@ class TreeList {
1017
1012
  const siblings = this.visibleChildrenMap.get(parentId);
1018
1013
  return siblings !== undefined && siblings.length > 1;
1019
1014
  }
1020
- /**
1021
- * Find the index of the next branch segment start in the given direction.
1022
- * A segment start is the first child of a branch point.
1023
- *
1024
- * "up" walks the visible parent chain; "down" walks visible children
1025
- * (always following the first child).
1026
- */
1015
+ /** Resolve the nearest expanded branch containing the selected entry. */
1016
+ findNearestExpandedBranch(entryId) {
1017
+ let currentId = entryId;
1018
+ while (currentId !== null) {
1019
+ if (this.isFoldable(currentId) && !this.foldedNodes.has(currentId))
1020
+ return currentId;
1021
+ currentId = this.visibleParentMap.get(currentId) ?? null;
1022
+ }
1023
+ return null;
1024
+ }
1025
+ /** Find the next branch segment start for the retained modified-arrow shortcuts. */
1027
1026
  findBranchSegmentStart(direction) {
1028
1027
  const selectedId = this.filteredNodes[this.selectedIndex]?.node.entry.id;
1029
1028
  if (!selectedId)
@@ -1040,7 +1039,6 @@ class TreeList {
1040
1039
  currentId = children[0];
1041
1040
  }
1042
1041
  }
1043
- // direction === "up"
1044
1042
  while (true) {
1045
1043
  const parentId = this.visibleParentMap.get(currentId) ?? null;
1046
1044
  if (parentId === null)
@@ -1048,38 +1046,70 @@ class TreeList {
1048
1046
  const children = this.visibleChildrenMap.get(parentId) ?? [];
1049
1047
  if (children.length > 1) {
1050
1048
  const segmentStart = indexByEntryId.get(currentId);
1051
- if (segmentStart < this.selectedIndex) {
1049
+ if (segmentStart < this.selectedIndex)
1052
1050
  return segmentStart;
1053
- }
1054
1051
  }
1055
1052
  currentId = parentId;
1056
1053
  }
1057
1054
  }
1058
1055
  }
1059
- /** Component that displays the current search query */
1060
- class SearchLine {
1056
+ class TreeSearchInput {
1061
1057
  treeList;
1058
+ input = new Input();
1059
+ renderedQuery = "";
1062
1060
  constructor(treeList) {
1063
1061
  this.treeList = treeList;
1064
1062
  }
1065
- invalidate() { }
1063
+ get focused() {
1064
+ return this.input.focused;
1065
+ }
1066
+ set focused(value) {
1067
+ this.input.focused = value;
1068
+ }
1069
+ invalidate() {
1070
+ this.input.invalidate();
1071
+ }
1066
1072
  render(width) {
1067
1073
  const query = this.treeList.getSearchQuery();
1068
- if (query) {
1069
- return [truncateToWidth(` ${theme.fg("muted", "Type to search:")} ${theme.fg("accent", query)}`, width)];
1074
+ if (query !== this.renderedQuery) {
1075
+ this.input = Object.assign(new Input(), { focused: this.input.focused });
1076
+ this.input.handleInput(query);
1077
+ this.renderedQuery = query;
1070
1078
  }
1071
- return [truncateToWidth(` ${theme.fg("muted", "Type to search:")}`, width)];
1079
+ return this.input.render(width);
1080
+ }
1081
+ }
1082
+ /** Models-style summary of the active tree filter. */
1083
+ class TreeFilter {
1084
+ treeList;
1085
+ constructor(treeList) {
1086
+ this.treeList = treeList;
1087
+ }
1088
+ invalidate() { }
1089
+ render(width) {
1090
+ const active = this.treeList.getFilterMode();
1091
+ const choices = FILTER_MODES.map((mode) => theme.fg(mode === active ? "accent" : "muted", FILTER_LABELS[mode]));
1092
+ return [truncateToWidth(theme.fg("muted", "Filter: ") + choices.join(theme.fg("muted", " | ")), width)];
1072
1093
  }
1073
- handleInput(_keyData) { }
1074
1094
  }
1075
1095
  /** Component that renders tree help as semantic rows with chunk-aware wrapping */
1076
1096
  class TreeHelp {
1097
+ treeList;
1098
+ constructor(treeList) {
1099
+ this.treeList = treeList;
1100
+ }
1077
1101
  invalidate() { }
1078
1102
  render(width) {
1079
- const items = TREE_HELP_ITEMS.map(({ keys, label, labelFirst }) => {
1080
- const key = formatHelpKeys(keys);
1081
- return renderPiModalShortcutHints([key ? { key, action: label, ...(labelFirst === undefined ? {} : { actionFirst: labelFirst }) } : { action: label }]);
1082
- });
1103
+ const items = [
1104
+ ...TREE_HELP_ITEMS.map(({ keys, label, labelFirst, displayKey }) => {
1105
+ const key = displayKey ?? formatHelpKeys(keys);
1106
+ const action = keys.includes("app.tree.toggleLabelTimestamp")
1107
+ ? `time (${this.treeList.isLabelTimestampVisible() ? "on" : "off"})`
1108
+ : label;
1109
+ return renderPiModalShortcutHints([key ? { key, action, ...(labelFirst === undefined ? {} : { actionFirst: labelFirst }) } : { action }]);
1110
+ }),
1111
+ renderPiModalShortcutHints([DIALOG_CLOSE_SHORTCUT_HINT]),
1112
+ ];
1083
1113
  const availableWidth = Math.max(1, width);
1084
1114
  // The frame supplies the shared outer cell; help content needs no duplicate chrome inset.
1085
1115
  const indent = "";
@@ -1106,24 +1136,15 @@ class TreeHelp {
1106
1136
  }
1107
1137
  }
1108
1138
  const TREE_HELP_ITEMS = [
1109
- { keys: ["tui.select.up", "tui.select.down"], label: "move" },
1110
- { keys: ["tui.editor.cursorLeft", "tui.editor.cursorRight"], label: "page" },
1111
- { keys: ["app.tree.foldOrUp", "app.tree.unfoldOrDown"], label: "branch" },
1139
+ { keys: [], displayKey: "Type", label: "search" },
1140
+ { keys: ["tui.select.up", "tui.select.down"], label: "navigate" },
1141
+ { keys: ["app.tree.filter.cycleForward"], label: "filter" },
1142
+ { keys: ["tui.select.pageUp", "tui.select.pageDown"], label: "page" },
1143
+ { keys: ["owned.tree.first", "owned.tree.last"], label: "first/last" },
1144
+ { keys: ["owned.tree.collapse", "owned.tree.expand"], label: "branch" },
1112
1145
  { keys: ["app.message.copy"], label: "copy" },
1113
1146
  { keys: ["app.tree.editLabel"], label: "label" },
1114
- { keys: ["app.tree.toggleLabelTimestamp"], label: "label time" },
1115
- {
1116
- keys: [
1117
- "app.tree.filter.default",
1118
- "app.tree.filter.noTools",
1119
- "app.tree.filter.userOnly",
1120
- "app.tree.filter.labeledOnly",
1121
- "app.tree.filter.all",
1122
- ],
1123
- label: "filters",
1124
- labelFirst: true,
1125
- },
1126
- { keys: ["app.tree.filter.cycleForward", "app.tree.filter.cycleBackward"], label: "cycle", labelFirst: true },
1147
+ { keys: ["app.tree.toggleLabelTimestamp"], label: "time" },
1127
1148
  ];
1128
1149
  function formatHelpKeys(keybindings) {
1129
1150
  const keys = [];
@@ -1135,6 +1156,7 @@ function formatHelpKeys(keybindings) {
1135
1156
  if (keys.length === 0)
1136
1157
  return "";
1137
1158
  return formatKeyText(compactRawKeys(keys))
1159
+ .replace(/\btab\b/gi, "Tab")
1138
1160
  .replace(/\bpageUp\b/g, "pgup")
1139
1161
  .replace(/\bpageDown\b/g, "pgdn")
1140
1162
  .replace(/\bup\b/g, "↑")
@@ -1174,22 +1196,23 @@ class LabelInput {
1174
1196
  constructor(entryId, currentLabel) {
1175
1197
  this.entryId = entryId;
1176
1198
  this.input = new Input();
1177
- if (currentLabel) {
1178
- this.input.setValue(currentLabel);
1179
- }
1199
+ if (currentLabel)
1200
+ this.input.handleInput(currentLabel);
1201
+ }
1202
+ invalidate() {
1203
+ this.input.invalidate();
1180
1204
  }
1181
- invalidate() { }
1182
1205
  render(width) {
1183
- const lines = [];
1184
- const indent = " ";
1185
- const availableWidth = width - indent.length;
1186
- lines.push(truncateToWidth(`${indent}${theme.fg("muted", "Label (empty to remove):")}`, width));
1187
- lines.push(...this.input.render(availableWidth).map((line) => truncateToWidth(`${indent}${line}`, width)));
1188
- lines.push(truncateToWidth(renderPiModalShortcutHints([
1189
- shortcutHint("tui.select.confirm", "save"),
1190
- shortcutHint("tui.select.cancel", "cancel"),
1191
- ], indent.length), width));
1192
- return lines;
1206
+ return [
1207
+ truncateToWidth(theme.fg("muted", "Empty to remove"), width),
1208
+ "",
1209
+ ...this.input.render(width),
1210
+ "",
1211
+ truncateToWidth(renderPiModalShortcutHints([
1212
+ shortcutHint("tui.select.confirm", "save"),
1213
+ DIALOG_CLOSE_SHORTCUT_HINT,
1214
+ ]), width),
1215
+ ];
1193
1216
  }
1194
1217
  handleInput(keyData) {
1195
1218
  const kb = treeKeybindings;
@@ -1210,9 +1233,15 @@ class LabelInput {
1210
1233
  */
1211
1234
  export class TreeSelectorComponent extends Container {
1212
1235
  treeList;
1236
+ searchInput;
1237
+ treeFilter;
1238
+ titleText;
1239
+ searchInputContainer = new Container();
1240
+ treeContainer = new Container();
1241
+ labelInputContainer = new Container();
1242
+ footerContainer = new Container();
1243
+ treeHelp;
1213
1244
  labelInput = null;
1214
- labelInputContainer;
1215
- treeContainer;
1216
1245
  onLabelChangeCallback;
1217
1246
  onCopy;
1218
1247
  // Focusable implementation - propagate to labelInput when active for IME cursor positioning
@@ -1222,10 +1251,9 @@ export class TreeSelectorComponent extends Container {
1222
1251
  }
1223
1252
  set focused(value) {
1224
1253
  this._focused = value;
1225
- // Propagate to labelInput when it's active
1226
- if (this.labelInput) {
1254
+ this.searchInput.focused = value && this.labelInput === null;
1255
+ if (this.labelInput)
1227
1256
  this.labelInput.focused = value;
1228
- }
1229
1257
  }
1230
1258
  constructor(tree, currentLeafId, terminalHeight, onSelect, onCancel, onLabelChange, initialSelectedId, initialFilterMode) {
1231
1259
  super();
@@ -1236,30 +1264,39 @@ export class TreeSelectorComponent extends Container {
1236
1264
  this.treeList.onCancel = onCancel;
1237
1265
  this.treeList.onCopy = (text) => this.onCopy?.(text);
1238
1266
  this.treeList.onLabelEdit = (entryId, currentLabel) => this.showLabelInput(entryId, currentLabel);
1239
- this.treeContainer = new Container();
1240
- this.treeContainer.addChild(this.treeList);
1241
- this.labelInputContainer = new Container();
1242
- this.addChild(new Spacer(1));
1243
- const header = addPiModalHeader(this, new DynamicBorder(), new Text(theme.bold("Session Tree"), 0, 0));
1244
- this.addChild(new TreeHelp());
1245
- this.addChild(new SearchLine(this.treeList));
1246
- const separator = new DynamicBorder();
1247
- this.addChild(separator);
1248
- this.addChild(new Spacer(1));
1267
+ this.searchInput = new TreeSearchInput(this.treeList);
1268
+ this.treeFilter = new TreeFilter(this.treeList);
1269
+ this.treeHelp = new TreeHelp(this.treeList);
1270
+ this.titleText = new Text(theme.fg("accent", theme.bold("Session Tree")), 0, 0);
1271
+ this.restoreTreeContent();
1272
+ const header = addPiModalHeader(this, new DynamicBorder(), this.titleText);
1273
+ this.addChild(this.searchInputContainer);
1249
1274
  this.addChild(this.treeContainer);
1250
1275
  this.addChild(this.labelInputContainer);
1251
- this.addChild(new Spacer(1));
1276
+ this.addChild(this.footerContainer);
1252
1277
  this.addChild(new DynamicBorder());
1253
1278
  adoptPiModalFrame(this, {
1254
- topIndex: 1,
1279
+ topIndex: 0,
1255
1280
  bottomIndex: this.children.length - 1,
1256
1281
  header,
1257
- fullWidthContent: [separator],
1258
1282
  });
1259
1283
  if (tree.length === 0) {
1260
1284
  setTimeout(() => onCancel(), 100);
1261
1285
  }
1262
1286
  }
1287
+ restoreTreeContent() {
1288
+ this.titleText.setText(theme.fg("accent", theme.bold("Session Tree")));
1289
+ this.searchInputContainer.clear();
1290
+ this.searchInputContainer.addChild(this.treeFilter);
1291
+ this.searchInputContainer.addChild(new Spacer(1));
1292
+ this.searchInputContainer.addChild(this.searchInput);
1293
+ this.searchInputContainer.addChild(new Spacer(1));
1294
+ this.treeContainer.clear();
1295
+ this.treeContainer.addChild(this.treeList);
1296
+ this.footerContainer.clear();
1297
+ this.footerContainer.addChild(new Spacer(1));
1298
+ this.footerContainer.addChild(this.treeHelp);
1299
+ }
1263
1300
  showLabelInput(entryId, currentLabel) {
1264
1301
  this.labelInput = new LabelInput(entryId, currentLabel);
1265
1302
  this.labelInput.onSubmit = (id, label) => {
@@ -1268,17 +1305,21 @@ export class TreeSelectorComponent extends Container {
1268
1305
  this.hideLabelInput();
1269
1306
  };
1270
1307
  this.labelInput.onCancel = () => this.hideLabelInput();
1271
- // Propagate current focused state to the new labelInput
1308
+ // Focus only the active editor.
1309
+ this.searchInput.focused = false;
1272
1310
  this.labelInput.focused = this._focused;
1311
+ this.titleText.setText(theme.fg("accent", theme.bold("Label")));
1312
+ this.searchInputContainer.clear();
1273
1313
  this.treeContainer.clear();
1274
1314
  this.labelInputContainer.clear();
1275
1315
  this.labelInputContainer.addChild(this.labelInput);
1316
+ this.footerContainer.clear();
1276
1317
  }
1277
1318
  hideLabelInput() {
1278
1319
  this.labelInput = null;
1320
+ this.searchInput.focused = this._focused;
1279
1321
  this.labelInputContainer.clear();
1280
- this.treeContainer.clear();
1281
- this.treeContainer.addChild(this.treeList);
1322
+ this.restoreTreeContent();
1282
1323
  }
1283
1324
  handleInput(keyData) {
1284
1325
  if (this.labelInput) {