@mariozechner/pi-coding-agent 0.45.7 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +24 -3
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +1 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +2 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +2 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-session.d.ts +11 -3
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +81 -14
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/compaction/compaction.d.ts.map +1 -1
  15. package/dist/core/compaction/compaction.js +6 -5
  16. package/dist/core/compaction/compaction.js.map +1 -1
  17. package/dist/core/export-html/ansi-to-html.d.ts +22 -0
  18. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  19. package/dist/core/export-html/ansi-to-html.js +249 -0
  20. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  21. package/dist/core/export-html/index.d.ts +17 -0
  22. package/dist/core/export-html/index.d.ts.map +1 -1
  23. package/dist/core/export-html/index.js +52 -23
  24. package/dist/core/export-html/index.js.map +1 -1
  25. package/dist/core/export-html/template.css +0 -33
  26. package/dist/core/export-html/template.js +171 -18
  27. package/dist/core/export-html/tool-renderer.d.ts +35 -0
  28. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  29. package/dist/core/export-html/tool-renderer.js +57 -0
  30. package/dist/core/export-html/tool-renderer.js.map +1 -0
  31. package/dist/core/extensions/index.d.ts +1 -1
  32. package/dist/core/extensions/index.d.ts.map +1 -1
  33. package/dist/core/extensions/index.js.map +1 -1
  34. package/dist/core/extensions/runner.d.ts +5 -1
  35. package/dist/core/extensions/runner.d.ts.map +1 -1
  36. package/dist/core/extensions/runner.js +41 -0
  37. package/dist/core/extensions/runner.js.map +1 -1
  38. package/dist/core/extensions/types.d.ts +24 -1
  39. package/dist/core/extensions/types.d.ts.map +1 -1
  40. package/dist/core/extensions/types.js.map +1 -1
  41. package/dist/core/keybindings.d.ts +1 -5
  42. package/dist/core/keybindings.d.ts.map +1 -1
  43. package/dist/core/keybindings.js +4 -12
  44. package/dist/core/keybindings.js.map +1 -1
  45. package/dist/core/model-resolver.d.ts.map +1 -1
  46. package/dist/core/model-resolver.js +1 -0
  47. package/dist/core/model-resolver.js.map +1 -1
  48. package/dist/core/prompt-templates.d.ts.map +1 -1
  49. package/dist/core/prompt-templates.js +4 -27
  50. package/dist/core/prompt-templates.js.map +1 -1
  51. package/dist/core/skills.d.ts.map +1 -1
  52. package/dist/core/skills.js +6 -37
  53. package/dist/core/skills.js.map +1 -1
  54. package/dist/core/system-prompt.d.ts.map +1 -1
  55. package/dist/core/system-prompt.js +19 -14
  56. package/dist/core/system-prompt.js.map +1 -1
  57. package/dist/core/tools/edit-diff.d.ts +30 -0
  58. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  59. package/dist/core/tools/edit-diff.js +82 -10
  60. package/dist/core/tools/edit-diff.js.map +1 -1
  61. package/dist/core/tools/edit.d.ts.map +1 -1
  62. package/dist/core/tools/edit.js +16 -13
  63. package/dist/core/tools/edit.js.map +1 -1
  64. package/dist/core/tools/path-utils.d.ts +1 -0
  65. package/dist/core/tools/path-utils.d.ts.map +1 -1
  66. package/dist/core/tools/path-utils.js +7 -0
  67. package/dist/core/tools/path-utils.js.map +1 -1
  68. package/dist/core/tools/read.d.ts.map +1 -1
  69. package/dist/core/tools/read.js +13 -2
  70. package/dist/core/tools/read.js.map +1 -1
  71. package/dist/index.d.ts +3 -1
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +3 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/main.d.ts.map +1 -1
  76. package/dist/main.js +70 -9
  77. package/dist/main.js.map +1 -1
  78. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  79. package/dist/modes/interactive/components/bash-execution.js +4 -3
  80. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  81. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/bordered-loader.js +2 -1
  83. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  84. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/branch-summary-message.js +4 -1
  86. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  87. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  88. package/dist/modes/interactive/components/compaction-summary-message.js +4 -1
  89. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  90. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  91. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  92. package/dist/modes/interactive/components/custom-editor.js +5 -5
  93. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  94. package/dist/modes/interactive/components/extension-editor.d.ts +3 -1
  95. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/extension-editor.js +15 -9
  97. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  98. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  99. package/dist/modes/interactive/components/extension-input.js +2 -1
  100. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  101. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/extension-selector.js +6 -1
  103. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  104. package/dist/modes/interactive/components/index.d.ts +1 -0
  105. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  106. package/dist/modes/interactive/components/index.js +1 -0
  107. package/dist/modes/interactive/components/index.js.map +1 -1
  108. package/dist/modes/interactive/components/keybinding-hints.d.ts +41 -0
  109. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  110. package/dist/modes/interactive/components/keybinding-hints.js +61 -0
  111. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  112. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/login-dialog.js +4 -3
  114. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  115. package/dist/modes/interactive/components/session-selector-search.d.ts +21 -0
  116. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  117. package/dist/modes/interactive/components/session-selector-search.js +146 -0
  118. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  119. package/dist/modes/interactive/components/session-selector.d.ts +7 -1
  120. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/session-selector.js +35 -8
  122. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  123. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  124. package/dist/modes/interactive/components/tool-execution.js +14 -8
  125. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  126. package/dist/modes/interactive/components/tree-selector.d.ts +7 -0
  127. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  128. package/dist/modes/interactive/components/tree-selector.js +143 -4
  129. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  130. package/dist/modes/interactive/interactive-mode.d.ts +4 -5
  131. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  132. package/dist/modes/interactive/interactive-mode.js +63 -126
  133. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  134. package/dist/modes/print-mode.d.ts.map +1 -1
  135. package/dist/modes/print-mode.js +1 -1
  136. package/dist/modes/print-mode.js.map +1 -1
  137. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  138. package/dist/modes/rpc/rpc-mode.js +1 -0
  139. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  140. package/dist/utils/frontmatter.d.ts +8 -0
  141. package/dist/utils/frontmatter.d.ts.map +1 -0
  142. package/dist/utils/frontmatter.js +26 -0
  143. package/dist/utils/frontmatter.js.map +1 -0
  144. package/dist/utils/image-convert.d.ts.map +1 -1
  145. package/dist/utils/image-convert.js +12 -14
  146. package/dist/utils/image-convert.js.map +1 -1
  147. package/dist/utils/image-resize.d.ts +2 -2
  148. package/dist/utils/image-resize.d.ts.map +1 -1
  149. package/dist/utils/image-resize.js +102 -122
  150. package/dist/utils/image-resize.js.map +1 -1
  151. package/docs/extensions.md +51 -0
  152. package/docs/rpc.md +15 -15
  153. package/docs/tui.md +26 -0
  154. package/examples/extensions/input-transform.ts +43 -0
  155. package/examples/extensions/modal-editor.ts +1 -1
  156. package/examples/extensions/overlay-test.ts +8 -3
  157. package/examples/extensions/plan-mode/README.md +1 -1
  158. package/examples/extensions/plan-mode/index.ts +2 -2
  159. package/examples/extensions/question.ts +1 -1
  160. package/examples/extensions/questionnaire.ts +1 -1
  161. package/examples/extensions/rainbow-editor.ts +1 -8
  162. package/examples/extensions/subagent/agents.ts +3 -32
  163. package/examples/extensions/with-deps/package-lock.json +2 -2
  164. package/examples/extensions/with-deps/package.json +1 -1
  165. package/package.json +6 -5
  166. package/dist/utils/vips.d.ts +0 -11
  167. package/dist/utils/vips.d.ts.map +0 -1
  168. package/dist/utils/vips.js +0 -35
  169. package/dist/utils/vips.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-selector-search.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector-search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAEpE,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IACzB,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;CACd;AAUD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CA2EjE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAsCzF;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,WAAW,EAAE,CA+B/G","sourcesContent":["import { fuzzyMatch } from \"@mariozechner/pi-tui\";\nimport type { SessionInfo } from \"../../../core/session-manager.js\";\n\nexport type SortMode = \"recent\" | \"relevance\";\n\nexport interface ParsedSearchQuery {\n\tmode: \"tokens\" | \"regex\";\n\ttokens: { kind: \"fuzzy\" | \"phrase\"; value: string }[];\n\tregex: RegExp | null;\n\t/** If set, parsing failed and we should treat query as non-matching. */\n\terror?: string;\n}\n\nexport interface MatchResult {\n\tmatches: boolean;\n\t/** Lower is better; only meaningful when matches === true */\n\tscore: number;\n}\n\nfunction normalizeWhitespaceLower(text: string): string {\n\treturn text.toLowerCase().replace(/\\s+/g, \" \").trim();\n}\n\nfunction getSessionSearchText(session: SessionInfo): string {\n\treturn `${session.id} ${session.name ?? \"\"} ${session.allMessagesText} ${session.cwd}`;\n}\n\nexport function parseSearchQuery(query: string): ParsedSearchQuery {\n\tconst trimmed = query.trim();\n\tif (!trimmed) {\n\t\treturn { mode: \"tokens\", tokens: [], regex: null };\n\t}\n\n\t// Regex mode: re:<pattern>\n\tif (trimmed.startsWith(\"re:\")) {\n\t\tconst pattern = trimmed.slice(3).trim();\n\t\tif (!pattern) {\n\t\t\treturn { mode: \"regex\", tokens: [], regex: null, error: \"Empty regex\" };\n\t\t}\n\t\ttry {\n\t\t\treturn { mode: \"regex\", tokens: [], regex: new RegExp(pattern, \"i\") };\n\t\t} catch (err) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\treturn { mode: \"regex\", tokens: [], regex: null, error: msg };\n\t\t}\n\t}\n\n\t// Token mode with quote support.\n\t// Example: foo \"node cve\" bar\n\tconst tokens: { kind: \"fuzzy\" | \"phrase\"; value: string }[] = [];\n\tlet buf = \"\";\n\tlet inQuote = false;\n\tlet hadUnclosedQuote = false;\n\n\tconst flush = (kind: \"fuzzy\" | \"phrase\"): void => {\n\t\tconst v = buf.trim();\n\t\tbuf = \"\";\n\t\tif (!v) return;\n\t\ttokens.push({ kind, value: v });\n\t};\n\n\tfor (let i = 0; i < trimmed.length; i++) {\n\t\tconst ch = trimmed[i]!;\n\t\tif (ch === '\"') {\n\t\t\tif (inQuote) {\n\t\t\t\tflush(\"phrase\");\n\t\t\t\tinQuote = false;\n\t\t\t} else {\n\t\t\t\tflush(\"fuzzy\");\n\t\t\t\tinQuote = true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!inQuote && /\\s/.test(ch)) {\n\t\t\tflush(\"fuzzy\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tbuf += ch;\n\t}\n\n\tif (inQuote) {\n\t\thadUnclosedQuote = true;\n\t}\n\n\t// If quotes were unbalanced, fall back to plain whitespace tokenization.\n\tif (hadUnclosedQuote) {\n\t\treturn {\n\t\t\tmode: \"tokens\",\n\t\t\ttokens: trimmed\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.map((t) => t.trim())\n\t\t\t\t.filter((t) => t.length > 0)\n\t\t\t\t.map((t) => ({ kind: \"fuzzy\" as const, value: t })),\n\t\t\tregex: null,\n\t\t};\n\t}\n\n\tflush(inQuote ? \"phrase\" : \"fuzzy\");\n\n\treturn { mode: \"tokens\", tokens, regex: null };\n}\n\nexport function matchSession(session: SessionInfo, parsed: ParsedSearchQuery): MatchResult {\n\tconst text = getSessionSearchText(session);\n\n\tif (parsed.mode === \"regex\") {\n\t\tif (!parsed.regex) {\n\t\t\treturn { matches: false, score: 0 };\n\t\t}\n\t\tconst idx = text.search(parsed.regex);\n\t\tif (idx < 0) return { matches: false, score: 0 };\n\t\treturn { matches: true, score: idx * 0.1 };\n\t}\n\n\tif (parsed.tokens.length === 0) {\n\t\treturn { matches: true, score: 0 };\n\t}\n\n\tlet totalScore = 0;\n\tlet normalizedText: string | null = null;\n\n\tfor (const token of parsed.tokens) {\n\t\tif (token.kind === \"phrase\") {\n\t\t\tif (normalizedText === null) {\n\t\t\t\tnormalizedText = normalizeWhitespaceLower(text);\n\t\t\t}\n\t\t\tconst phrase = normalizeWhitespaceLower(token.value);\n\t\t\tif (!phrase) continue;\n\t\t\tconst idx = normalizedText.indexOf(phrase);\n\t\t\tif (idx < 0) return { matches: false, score: 0 };\n\t\t\ttotalScore += idx * 0.1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst m = fuzzyMatch(token.value, text);\n\t\tif (!m.matches) return { matches: false, score: 0 };\n\t\ttotalScore += m.score;\n\t}\n\n\treturn { matches: true, score: totalScore };\n}\n\nexport function filterAndSortSessions(sessions: SessionInfo[], query: string, sortMode: SortMode): SessionInfo[] {\n\tconst trimmed = query.trim();\n\tif (!trimmed) return sessions;\n\n\tconst parsed = parseSearchQuery(query);\n\tif (parsed.error) return [];\n\n\t// Recent mode: filter only, keep incoming order.\n\tif (sortMode === \"recent\") {\n\t\tconst filtered: SessionInfo[] = [];\n\t\tfor (const s of sessions) {\n\t\t\tconst res = matchSession(s, parsed);\n\t\t\tif (res.matches) filtered.push(s);\n\t\t}\n\t\treturn filtered;\n\t}\n\n\t// Relevance mode: sort by score, tie-break by modified desc.\n\tconst scored: { session: SessionInfo; score: number }[] = [];\n\tfor (const s of sessions) {\n\t\tconst res = matchSession(s, parsed);\n\t\tif (!res.matches) continue;\n\t\tscored.push({ session: s, score: res.score });\n\t}\n\n\tscored.sort((a, b) => {\n\t\tif (a.score !== b.score) return a.score - b.score;\n\t\treturn b.session.modified.getTime() - a.session.modified.getTime();\n\t});\n\n\treturn scored.map((r) => r.session);\n}\n"]}
@@ -0,0 +1,146 @@
1
+ import { fuzzyMatch } from "@mariozechner/pi-tui";
2
+ function normalizeWhitespaceLower(text) {
3
+ return text.toLowerCase().replace(/\s+/g, " ").trim();
4
+ }
5
+ function getSessionSearchText(session) {
6
+ return `${session.id} ${session.name ?? ""} ${session.allMessagesText} ${session.cwd}`;
7
+ }
8
+ export function parseSearchQuery(query) {
9
+ const trimmed = query.trim();
10
+ if (!trimmed) {
11
+ return { mode: "tokens", tokens: [], regex: null };
12
+ }
13
+ // Regex mode: re:<pattern>
14
+ if (trimmed.startsWith("re:")) {
15
+ const pattern = trimmed.slice(3).trim();
16
+ if (!pattern) {
17
+ return { mode: "regex", tokens: [], regex: null, error: "Empty regex" };
18
+ }
19
+ try {
20
+ return { mode: "regex", tokens: [], regex: new RegExp(pattern, "i") };
21
+ }
22
+ catch (err) {
23
+ const msg = err instanceof Error ? err.message : String(err);
24
+ return { mode: "regex", tokens: [], regex: null, error: msg };
25
+ }
26
+ }
27
+ // Token mode with quote support.
28
+ // Example: foo "node cve" bar
29
+ const tokens = [];
30
+ let buf = "";
31
+ let inQuote = false;
32
+ let hadUnclosedQuote = false;
33
+ const flush = (kind) => {
34
+ const v = buf.trim();
35
+ buf = "";
36
+ if (!v)
37
+ return;
38
+ tokens.push({ kind, value: v });
39
+ };
40
+ for (let i = 0; i < trimmed.length; i++) {
41
+ const ch = trimmed[i];
42
+ if (ch === '"') {
43
+ if (inQuote) {
44
+ flush("phrase");
45
+ inQuote = false;
46
+ }
47
+ else {
48
+ flush("fuzzy");
49
+ inQuote = true;
50
+ }
51
+ continue;
52
+ }
53
+ if (!inQuote && /\s/.test(ch)) {
54
+ flush("fuzzy");
55
+ continue;
56
+ }
57
+ buf += ch;
58
+ }
59
+ if (inQuote) {
60
+ hadUnclosedQuote = true;
61
+ }
62
+ // If quotes were unbalanced, fall back to plain whitespace tokenization.
63
+ if (hadUnclosedQuote) {
64
+ return {
65
+ mode: "tokens",
66
+ tokens: trimmed
67
+ .split(/\s+/)
68
+ .map((t) => t.trim())
69
+ .filter((t) => t.length > 0)
70
+ .map((t) => ({ kind: "fuzzy", value: t })),
71
+ regex: null,
72
+ };
73
+ }
74
+ flush(inQuote ? "phrase" : "fuzzy");
75
+ return { mode: "tokens", tokens, regex: null };
76
+ }
77
+ export function matchSession(session, parsed) {
78
+ const text = getSessionSearchText(session);
79
+ if (parsed.mode === "regex") {
80
+ if (!parsed.regex) {
81
+ return { matches: false, score: 0 };
82
+ }
83
+ const idx = text.search(parsed.regex);
84
+ if (idx < 0)
85
+ return { matches: false, score: 0 };
86
+ return { matches: true, score: idx * 0.1 };
87
+ }
88
+ if (parsed.tokens.length === 0) {
89
+ return { matches: true, score: 0 };
90
+ }
91
+ let totalScore = 0;
92
+ let normalizedText = null;
93
+ for (const token of parsed.tokens) {
94
+ if (token.kind === "phrase") {
95
+ if (normalizedText === null) {
96
+ normalizedText = normalizeWhitespaceLower(text);
97
+ }
98
+ const phrase = normalizeWhitespaceLower(token.value);
99
+ if (!phrase)
100
+ continue;
101
+ const idx = normalizedText.indexOf(phrase);
102
+ if (idx < 0)
103
+ return { matches: false, score: 0 };
104
+ totalScore += idx * 0.1;
105
+ continue;
106
+ }
107
+ const m = fuzzyMatch(token.value, text);
108
+ if (!m.matches)
109
+ return { matches: false, score: 0 };
110
+ totalScore += m.score;
111
+ }
112
+ return { matches: true, score: totalScore };
113
+ }
114
+ export function filterAndSortSessions(sessions, query, sortMode) {
115
+ const trimmed = query.trim();
116
+ if (!trimmed)
117
+ return sessions;
118
+ const parsed = parseSearchQuery(query);
119
+ if (parsed.error)
120
+ return [];
121
+ // Recent mode: filter only, keep incoming order.
122
+ if (sortMode === "recent") {
123
+ const filtered = [];
124
+ for (const s of sessions) {
125
+ const res = matchSession(s, parsed);
126
+ if (res.matches)
127
+ filtered.push(s);
128
+ }
129
+ return filtered;
130
+ }
131
+ // Relevance mode: sort by score, tie-break by modified desc.
132
+ const scored = [];
133
+ for (const s of sessions) {
134
+ const res = matchSession(s, parsed);
135
+ if (!res.matches)
136
+ continue;
137
+ scored.push({ session: s, score: res.score });
138
+ }
139
+ scored.sort((a, b) => {
140
+ if (a.score !== b.score)
141
+ return a.score - b.score;
142
+ return b.session.modified.getTime() - a.session.modified.getTime();
143
+ });
144
+ return scored.map((r) => r.session);
145
+ }
146
+ //# sourceMappingURL=session-selector-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-selector-search.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAmBlD,SAAS,wBAAwB,CAAC,IAAY,EAAU;IACvD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAAA,CACtD;AAED,SAAS,oBAAoB,CAAC,OAAoB,EAAU;IAC3D,OAAO,GAAG,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,IAAI,IAAI,EAAE,IAAI,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;AAAA,CACvF;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAqB;IAClE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACpD,CAAC;IAED,2BAA2B;IAC3B,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;QACzE,CAAC;QACD,IAAI,CAAC;YACJ,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;QACvE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAC/D,CAAC;IACF,CAAC;IAED,iCAAiC;IACjC,8BAA8B;IAC9B,MAAM,MAAM,GAAkD,EAAE,CAAC;IACjE,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAE7B,MAAM,KAAK,GAAG,CAAC,IAAwB,EAAQ,EAAE,CAAC;QACjD,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACrB,GAAG,GAAG,EAAE,CAAC;QACT,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAAA,CAChC,CAAC;IAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QACvB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAChB,IAAI,OAAO,EAAE,CAAC;gBACb,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAChB,OAAO,GAAG,KAAK,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACP,KAAK,CAAC,OAAO,CAAC,CAAC;gBACf,OAAO,GAAG,IAAI,CAAC;YAChB,CAAC;YACD,SAAS;QACV,CAAC;QAED,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/B,KAAK,CAAC,OAAO,CAAC,CAAC;YACf,SAAS;QACV,CAAC;QAED,GAAG,IAAI,EAAE,CAAC;IACX,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACb,gBAAgB,GAAG,IAAI,CAAC;IACzB,CAAC;IAED,yEAAyE;IACzE,IAAI,gBAAgB,EAAE,CAAC;QACtB,OAAO;YACN,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,OAAO;iBACb,KAAK,CAAC,KAAK,CAAC;iBACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;iBAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YACpD,KAAK,EAAE,IAAI;SACX,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAEpC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAAA,CAC/C;AAED,MAAM,UAAU,YAAY,CAAC,OAAoB,EAAE,MAAyB,EAAe;IAC1F,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAE3C,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACnB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACrC,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,GAAG,GAAG,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE,CAAC;IAC5C,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,cAAc,GAAkB,IAAI,CAAC;IAEzC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;gBAC7B,cAAc,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;YACjD,CAAC;YACD,MAAM,MAAM,GAAG,wBAAwB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,GAAG,GAAG,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC3C,IAAI,GAAG,GAAG,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;YACjD,UAAU,IAAI,GAAG,GAAG,GAAG,CAAC;YACxB,SAAS;QACV,CAAC;QAED,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC,CAAC,OAAO;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACpD,UAAU,IAAI,CAAC,CAAC,KAAK,CAAC;IACvB,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAAA,CAC5C;AAED,MAAM,UAAU,qBAAqB,CAAC,QAAuB,EAAE,KAAa,EAAE,QAAkB,EAAiB;IAChH,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAE5B,iDAAiD;IACjD,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YACpC,IAAI,GAAG,CAAC,OAAO;gBAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED,6DAA6D;IAC7D,MAAM,MAAM,GAA8C,EAAE,CAAC;IAC7D,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,CAAC,OAAO;YAAE,SAAS;QAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAClD,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;IAAA,CACnE,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAAA,CACpC","sourcesContent":["import { fuzzyMatch } from \"@mariozechner/pi-tui\";\nimport type { SessionInfo } from \"../../../core/session-manager.js\";\n\nexport type SortMode = \"recent\" | \"relevance\";\n\nexport interface ParsedSearchQuery {\n\tmode: \"tokens\" | \"regex\";\n\ttokens: { kind: \"fuzzy\" | \"phrase\"; value: string }[];\n\tregex: RegExp | null;\n\t/** If set, parsing failed and we should treat query as non-matching. */\n\terror?: string;\n}\n\nexport interface MatchResult {\n\tmatches: boolean;\n\t/** Lower is better; only meaningful when matches === true */\n\tscore: number;\n}\n\nfunction normalizeWhitespaceLower(text: string): string {\n\treturn text.toLowerCase().replace(/\\s+/g, \" \").trim();\n}\n\nfunction getSessionSearchText(session: SessionInfo): string {\n\treturn `${session.id} ${session.name ?? \"\"} ${session.allMessagesText} ${session.cwd}`;\n}\n\nexport function parseSearchQuery(query: string): ParsedSearchQuery {\n\tconst trimmed = query.trim();\n\tif (!trimmed) {\n\t\treturn { mode: \"tokens\", tokens: [], regex: null };\n\t}\n\n\t// Regex mode: re:<pattern>\n\tif (trimmed.startsWith(\"re:\")) {\n\t\tconst pattern = trimmed.slice(3).trim();\n\t\tif (!pattern) {\n\t\t\treturn { mode: \"regex\", tokens: [], regex: null, error: \"Empty regex\" };\n\t\t}\n\t\ttry {\n\t\t\treturn { mode: \"regex\", tokens: [], regex: new RegExp(pattern, \"i\") };\n\t\t} catch (err) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\treturn { mode: \"regex\", tokens: [], regex: null, error: msg };\n\t\t}\n\t}\n\n\t// Token mode with quote support.\n\t// Example: foo \"node cve\" bar\n\tconst tokens: { kind: \"fuzzy\" | \"phrase\"; value: string }[] = [];\n\tlet buf = \"\";\n\tlet inQuote = false;\n\tlet hadUnclosedQuote = false;\n\n\tconst flush = (kind: \"fuzzy\" | \"phrase\"): void => {\n\t\tconst v = buf.trim();\n\t\tbuf = \"\";\n\t\tif (!v) return;\n\t\ttokens.push({ kind, value: v });\n\t};\n\n\tfor (let i = 0; i < trimmed.length; i++) {\n\t\tconst ch = trimmed[i]!;\n\t\tif (ch === '\"') {\n\t\t\tif (inQuote) {\n\t\t\t\tflush(\"phrase\");\n\t\t\t\tinQuote = false;\n\t\t\t} else {\n\t\t\t\tflush(\"fuzzy\");\n\t\t\t\tinQuote = true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!inQuote && /\\s/.test(ch)) {\n\t\t\tflush(\"fuzzy\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tbuf += ch;\n\t}\n\n\tif (inQuote) {\n\t\thadUnclosedQuote = true;\n\t}\n\n\t// If quotes were unbalanced, fall back to plain whitespace tokenization.\n\tif (hadUnclosedQuote) {\n\t\treturn {\n\t\t\tmode: \"tokens\",\n\t\t\ttokens: trimmed\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.map((t) => t.trim())\n\t\t\t\t.filter((t) => t.length > 0)\n\t\t\t\t.map((t) => ({ kind: \"fuzzy\" as const, value: t })),\n\t\t\tregex: null,\n\t\t};\n\t}\n\n\tflush(inQuote ? \"phrase\" : \"fuzzy\");\n\n\treturn { mode: \"tokens\", tokens, regex: null };\n}\n\nexport function matchSession(session: SessionInfo, parsed: ParsedSearchQuery): MatchResult {\n\tconst text = getSessionSearchText(session);\n\n\tif (parsed.mode === \"regex\") {\n\t\tif (!parsed.regex) {\n\t\t\treturn { matches: false, score: 0 };\n\t\t}\n\t\tconst idx = text.search(parsed.regex);\n\t\tif (idx < 0) return { matches: false, score: 0 };\n\t\treturn { matches: true, score: idx * 0.1 };\n\t}\n\n\tif (parsed.tokens.length === 0) {\n\t\treturn { matches: true, score: 0 };\n\t}\n\n\tlet totalScore = 0;\n\tlet normalizedText: string | null = null;\n\n\tfor (const token of parsed.tokens) {\n\t\tif (token.kind === \"phrase\") {\n\t\t\tif (normalizedText === null) {\n\t\t\t\tnormalizedText = normalizeWhitespaceLower(text);\n\t\t\t}\n\t\t\tconst phrase = normalizeWhitespaceLower(token.value);\n\t\t\tif (!phrase) continue;\n\t\t\tconst idx = normalizedText.indexOf(phrase);\n\t\t\tif (idx < 0) return { matches: false, score: 0 };\n\t\t\ttotalScore += idx * 0.1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst m = fuzzyMatch(token.value, text);\n\t\tif (!m.matches) return { matches: false, score: 0 };\n\t\ttotalScore += m.score;\n\t}\n\n\treturn { matches: true, score: totalScore };\n}\n\nexport function filterAndSortSessions(sessions: SessionInfo[], query: string, sortMode: SortMode): SessionInfo[] {\n\tconst trimmed = query.trim();\n\tif (!trimmed) return sessions;\n\n\tconst parsed = parseSearchQuery(query);\n\tif (parsed.error) return [];\n\n\t// Recent mode: filter only, keep incoming order.\n\tif (sortMode === \"recent\") {\n\t\tconst filtered: SessionInfo[] = [];\n\t\tfor (const s of sessions) {\n\t\t\tconst res = matchSession(s, parsed);\n\t\t\tif (res.matches) filtered.push(s);\n\t\t}\n\t\treturn filtered;\n\t}\n\n\t// Relevance mode: sort by score, tie-break by modified desc.\n\tconst scored: { session: SessionInfo; score: number }[] = [];\n\tfor (const s of sessions) {\n\t\tconst res = matchSession(s, parsed);\n\t\tif (!res.matches) continue;\n\t\tscored.push({ session: s, score: res.score });\n\t}\n\n\tscored.sort((a, b) => {\n\t\tif (a.score !== b.score) return a.score - b.score;\n\t\treturn b.session.modified.getTime() - a.session.modified.getTime();\n\t});\n\n\treturn scored.map((r) => r.session);\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { type Component, Container } from "@mariozechner/pi-tui";
2
2
  import type { SessionInfo, SessionListProgress } from "../../../core/session-manager.js";
3
+ import { type SortMode } from "./session-selector-search.js";
3
4
  /**
4
5
  * Custom session list component with multi-line items and search
5
6
  */
@@ -9,12 +10,15 @@ declare class SessionList implements Component {
9
10
  private selectedIndex;
10
11
  private searchInput;
11
12
  private showCwd;
13
+ private sortMode;
12
14
  onSelect?: (sessionPath: string) => void;
13
15
  onCancel?: () => void;
14
16
  onExit: () => void;
15
17
  onToggleScope?: () => void;
18
+ onToggleSort?: () => void;
16
19
  private maxVisible;
17
- constructor(sessions: SessionInfo[], showCwd: boolean);
20
+ constructor(sessions: SessionInfo[], showCwd: boolean, sortMode: SortMode);
21
+ setSortMode(sortMode: SortMode): void;
18
22
  setSessions(sessions: SessionInfo[], showCwd: boolean): void;
19
23
  private filterSessions;
20
24
  invalidate(): void;
@@ -29,6 +33,7 @@ export declare class SessionSelectorComponent extends Container {
29
33
  private sessionList;
30
34
  private header;
31
35
  private scope;
36
+ private sortMode;
32
37
  private currentSessions;
33
38
  private allSessions;
34
39
  private currentSessionsLoader;
@@ -37,6 +42,7 @@ export declare class SessionSelectorComponent extends Container {
37
42
  private requestRender;
38
43
  constructor(currentSessionsLoader: SessionsLoader, allSessionsLoader: SessionsLoader, onSelect: (sessionPath: string) => void, onCancel: () => void, onExit: () => void, requestRender: () => void);
39
44
  private loadCurrentSessions;
45
+ private toggleSortMode;
40
46
  private toggleScope;
41
47
  getSessionList(): SessionList;
42
48
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,SAAS,EACd,SAAS,EAOT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AA+EzF;;GAEG;AACH,cAAM,WAAY,YAAW,SAAS;IACrC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,OAAO,CAAS;IACjB,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,IAAI,CAAY;IAC9B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,OAAO,CAAC,UAAU,CAAa;IAE/B,YAAY,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,EAepD;IAED,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAI3D;IAED,OAAO,CAAC,cAAc;IAStB,UAAU,IAAI,IAAI,CAAG;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAwE9B;IAED,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA0CjC;CACD;AAED,KAAK,cAAc,GAAG,CAAC,UAAU,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;AAEnF;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,SAAS;IACtD,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,qBAAqB,CAAiB;IAC9C,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAa;IAElC,YACC,qBAAqB,EAAE,cAAc,EACrC,iBAAiB,EAAE,cAAc,EACjC,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,EACvC,QAAQ,EAAE,MAAM,IAAI,EACpB,MAAM,EAAE,MAAM,IAAI,EAClB,aAAa,EAAE,MAAM,IAAI,EA+BzB;IAED,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,WAAW;IAoCnB,cAAc,IAAI,WAAW,CAE5B;CACD","sourcesContent":["import * as os from \"node:os\";\nimport {\n\ttype Component,\n\tContainer,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\nimport type { SessionInfo, SessionListProgress } from \"../../../core/session-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\ntype SessionScope = \"current\" | \"all\";\n\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (!path) return path;\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nfunction formatSessionDate(date: Date): string {\n\tconst now = new Date();\n\tconst diffMs = now.getTime() - date.getTime();\n\tconst diffMins = Math.floor(diffMs / 60000);\n\tconst diffHours = Math.floor(diffMs / 3600000);\n\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\tif (diffMins < 1) return \"just now\";\n\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffDays === 1) return \"1 day ago\";\n\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\treturn date.toLocaleDateString();\n}\n\nclass SessionSelectorHeader implements Component {\n\tprivate scope: SessionScope;\n\tprivate loading = false;\n\tprivate loadProgress: { loaded: number; total: number } | null = null;\n\n\tconstructor(scope: SessionScope) {\n\t\tthis.scope = scope;\n\t}\n\n\tsetScope(scope: SessionScope): void {\n\t\tthis.scope = scope;\n\t}\n\n\tsetLoading(loading: boolean): void {\n\t\tthis.loading = loading;\n\t\tif (!loading) {\n\t\t\tthis.loadProgress = null;\n\t\t}\n\t}\n\n\tsetProgress(loaded: number, total: number): void {\n\t\tthis.loadProgress = { loaded, total };\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = this.scope === \"current\" ? \"Resume Session (Current Folder)\" : \"Resume Session (All)\";\n\t\tconst leftText = theme.bold(title);\n\t\tlet scopeText: string;\n\t\tif (this.loading) {\n\t\t\tconst progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : \"...\";\n\t\t\tscopeText = `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", `Loading ${progressText}`)}`;\n\t\t} else {\n\t\t\tscopeText =\n\t\t\t\tthis.scope === \"current\"\n\t\t\t\t\t? `${theme.fg(\"accent\", \"◉ Current Folder\")}${theme.fg(\"muted\", \" | ○ All\")}`\n\t\t\t\t\t: `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", \"◉ All\")}`;\n\t\t}\n\t\tconst rightText = truncateToWidth(scopeText, width, \"\");\n\t\tconst availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);\n\t\tconst left = truncateToWidth(leftText, availableLeft, \"\");\n\t\tconst spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));\n\t\tconst hint = theme.fg(\"muted\", \"Tab to toggle scope\");\n\t\treturn [`${left}${\" \".repeat(spacing)}${rightText}`, hint];\n\t}\n}\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionInfo[] = [];\n\tprivate filteredSessions: SessionInfo[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tprivate showCwd = false;\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tpublic onExit: () => void = () => {};\n\tpublic onToggleScope?: () => void;\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionInfo[], showCwd: boolean) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\t\tthis.showCwd = showCwd;\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tsetSessions(sessions: SessionInfo[], showCwd: boolean): void {\n\t\tthis.allSessions = sessions;\n\t\tthis.showCwd = showCwd;\n\t\tthis.filterSessions(this.searchInput.getValue());\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tthis.filteredSessions = fuzzyFilter(\n\t\t\tthis.allSessions,\n\t\t\tquery,\n\t\t\t(session) => `${session.id} ${session.name ?? \"\"} ${session.allMessagesText} ${session.cwd}`,\n\t\t);\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tif (this.showCwd) {\n\t\t\t\t// \"All\" scope - no sessions anywhere that match filter\n\t\t\t\tlines.push(theme.fg(\"muted\", \" No sessions found\"));\n\t\t\t} else {\n\t\t\t\t// \"Current folder\" scope - hint to try \"all\"\n\t\t\t\tlines.push(theme.fg(\"muted\", \" No sessions in current folder. Press Tab to view all.\"));\n\t\t\t}\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Use session name if set, otherwise first message\n\t\t\tconst hasName = !!session.name;\n\t\t\tconst displayText = session.name ?? session.firstMessage;\n\t\t\tconst normalizedMessage = displayText.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message (truncate to visible width)\n\t\t\t// Use warning color for custom names to distinguish from first message\n\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor (2 visible chars)\n\t\t\tconst truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, \"...\");\n\t\t\tlet styledMsg = truncatedMsg;\n\t\t\tif (hasName) {\n\t\t\t\tstyledMsg = theme.fg(\"warning\", truncatedMsg);\n\t\t\t}\n\t\t\tif (isSelected) {\n\t\t\t\tstyledMsg = theme.bold(styledMsg);\n\t\t\t}\n\t\t\tconst messageLine = cursor + styledMsg;\n\n\t\t\t// Second line: metadata (dimmed) - also truncate for safety\n\t\t\tconst modified = formatSessionDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadataParts = [modified, msgCount];\n\t\t\tif (this.showCwd && session.cwd) {\n\t\t\t\tmetadataParts.push(shortenPath(session.cwd));\n\t\t\t}\n\t\t\tconst metadata = ` ${metadataParts.join(\" · \")}`;\n\t\t\tconst metadataLine = theme.fg(\"dim\", truncateToWidth(metadata, width, \"\"));\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;\n\t\t\tconst scrollInfo = theme.fg(\"muted\", truncateToWidth(scrollText, width, \"\"));\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\tif (kb.matches(keyData, \"tab\")) {\n\t\t\tif (this.onToggleScope) {\n\t\t\t\tthis.onToggleScope();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Page up - jump up by maxVisible items\n\t\telse if (kb.matches(keyData, \"selectPageUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t}\n\t\t// Page down - jump down by maxVisible items\n\t\telse if (kb.matches(keyData, \"selectPageDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + this.maxVisible);\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\ntype SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\tprivate header: SessionSelectorHeader;\n\tprivate scope: SessionScope = \"current\";\n\tprivate currentSessions: SessionInfo[] | null = null;\n\tprivate allSessions: SessionInfo[] | null = null;\n\tprivate currentSessionsLoader: SessionsLoader;\n\tprivate allSessionsLoader: SessionsLoader;\n\tprivate onCancel: () => void;\n\tprivate requestRender: () => void;\n\n\tconstructor(\n\t\tcurrentSessionsLoader: SessionsLoader,\n\t\tallSessionsLoader: SessionsLoader,\n\t\tonSelect: (sessionPath: string) => void,\n\t\tonCancel: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\t\tthis.currentSessionsLoader = currentSessionsLoader;\n\t\tthis.allSessionsLoader = allSessionsLoader;\n\t\tthis.onCancel = onCancel;\n\t\tthis.requestRender = requestRender;\n\t\tthis.header = new SessionSelectorHeader(this.scope);\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.header);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list (starts empty, will be populated after load)\n\t\tthis.sessionList = new SessionList([], false);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\t\tthis.sessionList.onExit = onExit;\n\t\tthis.sessionList.onToggleScope = () => this.toggleScope();\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Start loading current sessions immediately\n\t\tthis.loadCurrentSessions();\n\t}\n\n\tprivate loadCurrentSessions(): void {\n\t\tthis.header.setLoading(true);\n\t\tthis.requestRender();\n\t\tthis.currentSessionsLoader((loaded, total) => {\n\t\t\tthis.header.setProgress(loaded, total);\n\t\t\tthis.requestRender();\n\t\t}).then((sessions) => {\n\t\t\tthis.currentSessions = sessions;\n\t\t\tthis.header.setLoading(false);\n\t\t\tthis.sessionList.setSessions(sessions, false);\n\t\t\tthis.requestRender();\n\t\t});\n\t}\n\n\tprivate toggleScope(): void {\n\t\tif (this.scope === \"current\") {\n\t\t\t// Switching to \"all\" - load if not already loaded\n\t\t\tif (this.allSessions === null) {\n\t\t\t\tthis.header.setLoading(true);\n\t\t\t\tthis.header.setScope(\"all\");\n\t\t\t\tthis.sessionList.setSessions([], true); // Clear list while loading\n\t\t\t\tthis.requestRender();\n\t\t\t\t// Load asynchronously with progress updates\n\t\t\t\tthis.allSessionsLoader((loaded, total) => {\n\t\t\t\t\tthis.header.setProgress(loaded, total);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t}).then((sessions) => {\n\t\t\t\t\tthis.allSessions = sessions;\n\t\t\t\t\tthis.header.setLoading(false);\n\t\t\t\t\tthis.scope = \"all\";\n\t\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t\t// If no sessions in All scope either, cancel\n\t\t\t\t\tif (this.allSessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {\n\t\t\t\t\t\tthis.onCancel();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.scope = \"all\";\n\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\tthis.header.setScope(this.scope);\n\t\t\t}\n\t\t} else {\n\t\t\t// Switching back to \"current\"\n\t\t\tthis.scope = \"current\";\n\t\t\tthis.sessionList.setSessions(this.currentSessions ?? [], false);\n\t\t\tthis.header.setScope(this.scope);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,SAAS,EACd,SAAS,EAOT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAGzF,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAwFpF;;GAEG;AACH,cAAM,WAAY,YAAW,SAAS;IACrC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAyB;IAClC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,IAAI,CAAY;IAC9B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,OAAO,CAAC,UAAU,CAAa;IAE/B,YAAY,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAgBxE;IAED,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAGpC;IAED,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAI3D;IAED,OAAO,CAAC,cAAc;IAKtB,UAAU,IAAI,IAAI,CAAG;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAwE9B;IAED,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAgDjC;CACD;AAED,KAAK,cAAc,GAAG,CAAC,UAAU,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;AAEnF;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,SAAS;IACtD,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,qBAAqB,CAAiB;IAC9C,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAa;IAElC,YACC,qBAAqB,EAAE,cAAc,EACrC,iBAAiB,EAAE,cAAc,EACjC,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,EACvC,QAAQ,EAAE,MAAM,IAAI,EACpB,MAAM,EAAE,MAAM,IAAI,EAClB,aAAa,EAAE,MAAM,IAAI,EAgCzB;IAED,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,WAAW;IAoCnB,cAAc,IAAI,WAAW,CAE5B;CACD","sourcesContent":["import * as os from \"node:os\";\nimport {\n\ttype Component,\n\tContainer,\n\tgetEditorKeybindings,\n\tInput,\n\tmatchesKey,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\nimport type { SessionInfo, SessionListProgress } from \"../../../core/session-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { filterAndSortSessions, type SortMode } from \"./session-selector-search.js\";\n\ntype SessionScope = \"current\" | \"all\";\n\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (!path) return path;\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nfunction formatSessionDate(date: Date): string {\n\tconst now = new Date();\n\tconst diffMs = now.getTime() - date.getTime();\n\tconst diffMins = Math.floor(diffMs / 60000);\n\tconst diffHours = Math.floor(diffMs / 3600000);\n\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\tif (diffMins < 1) return \"just now\";\n\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffDays === 1) return \"1 day ago\";\n\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\treturn date.toLocaleDateString();\n}\n\nclass SessionSelectorHeader implements Component {\n\tprivate scope: SessionScope;\n\tprivate sortMode: SortMode;\n\tprivate loading = false;\n\tprivate loadProgress: { loaded: number; total: number } | null = null;\n\n\tconstructor(scope: SessionScope, sortMode: SortMode) {\n\t\tthis.scope = scope;\n\t\tthis.sortMode = sortMode;\n\t}\n\n\tsetScope(scope: SessionScope): void {\n\t\tthis.scope = scope;\n\t}\n\n\tsetSortMode(sortMode: SortMode): void {\n\t\tthis.sortMode = sortMode;\n\t}\n\n\tsetLoading(loading: boolean): void {\n\t\tthis.loading = loading;\n\t\tif (!loading) {\n\t\t\tthis.loadProgress = null;\n\t\t}\n\t}\n\n\tsetProgress(loaded: number, total: number): void {\n\t\tthis.loadProgress = { loaded, total };\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = this.scope === \"current\" ? \"Resume Session (Current Folder)\" : \"Resume Session (All)\";\n\t\tconst leftText = theme.bold(title);\n\n\t\tconst sortLabel = this.sortMode === \"recent\" ? \"Recent\" : \"Fuzzy\";\n\t\tconst sortText = theme.fg(\"muted\", \"Sort: \") + theme.fg(\"accent\", sortLabel);\n\n\t\tlet scopeText: string;\n\t\tif (this.loading) {\n\t\t\tconst progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : \"...\";\n\t\t\tscopeText = `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", `Loading ${progressText}`)}`;\n\t\t} else {\n\t\t\tscopeText =\n\t\t\t\tthis.scope === \"current\"\n\t\t\t\t\t? `${theme.fg(\"accent\", \"◉ Current Folder\")}${theme.fg(\"muted\", \" | ○ All\")}`\n\t\t\t\t\t: `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", \"◉ All\")}`;\n\t\t}\n\n\t\tconst rightText = truncateToWidth(`${scopeText} ${sortText}`, width, \"\");\n\t\tconst availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);\n\t\tconst left = truncateToWidth(leftText, availableLeft, \"\");\n\t\tconst spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));\n\t\tconst hint = theme.fg(\"muted\", 'Tab: scope · Ctrl+R: sort · re:<pattern> for regex · \"phrase\" for exact phrase');\n\t\treturn [`${left}${\" \".repeat(spacing)}${rightText}`, hint];\n\t}\n}\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionInfo[] = [];\n\tprivate filteredSessions: SessionInfo[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tprivate showCwd = false;\n\tprivate sortMode: SortMode = \"relevance\";\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tpublic onExit: () => void = () => {};\n\tpublic onToggleScope?: () => void;\n\tpublic onToggleSort?: () => void;\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionInfo[], showCwd: boolean, sortMode: SortMode) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\t\tthis.showCwd = showCwd;\n\t\tthis.sortMode = sortMode;\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tsetSortMode(sortMode: SortMode): void {\n\t\tthis.sortMode = sortMode;\n\t\tthis.filterSessions(this.searchInput.getValue());\n\t}\n\n\tsetSessions(sessions: SessionInfo[], showCwd: boolean): void {\n\t\tthis.allSessions = sessions;\n\t\tthis.showCwd = showCwd;\n\t\tthis.filterSessions(this.searchInput.getValue());\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tthis.filteredSessions = filterAndSortSessions(this.allSessions, query, this.sortMode);\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tif (this.showCwd) {\n\t\t\t\t// \"All\" scope - no sessions anywhere that match filter\n\t\t\t\tlines.push(theme.fg(\"muted\", \" No sessions found\"));\n\t\t\t} else {\n\t\t\t\t// \"Current folder\" scope - hint to try \"all\"\n\t\t\t\tlines.push(theme.fg(\"muted\", \" No sessions in current folder. Press Tab to view all.\"));\n\t\t\t}\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Use session name if set, otherwise first message\n\t\t\tconst hasName = !!session.name;\n\t\t\tconst displayText = session.name ?? session.firstMessage;\n\t\t\tconst normalizedMessage = displayText.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message (truncate to visible width)\n\t\t\t// Use warning color for custom names to distinguish from first message\n\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor (2 visible chars)\n\t\t\tconst truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, \"...\");\n\t\t\tlet styledMsg = truncatedMsg;\n\t\t\tif (hasName) {\n\t\t\t\tstyledMsg = theme.fg(\"warning\", truncatedMsg);\n\t\t\t}\n\t\t\tif (isSelected) {\n\t\t\t\tstyledMsg = theme.bold(styledMsg);\n\t\t\t}\n\t\t\tconst messageLine = cursor + styledMsg;\n\n\t\t\t// Second line: metadata (dimmed) - also truncate for safety\n\t\t\tconst modified = formatSessionDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadataParts = [modified, msgCount];\n\t\t\tif (this.showCwd && session.cwd) {\n\t\t\t\tmetadataParts.push(shortenPath(session.cwd));\n\t\t\t}\n\t\t\tconst metadata = ` ${metadataParts.join(\" · \")}`;\n\t\t\tconst metadataLine = theme.fg(\"dim\", truncateToWidth(metadata, width, \"\"));\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;\n\t\t\tconst scrollInfo = theme.fg(\"muted\", truncateToWidth(scrollText, width, \"\"));\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\tif (kb.matches(keyData, \"tab\")) {\n\t\t\tif (this.onToggleScope) {\n\t\t\t\tthis.onToggleScope();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(keyData, \"ctrl+r\")) {\n\t\t\tthis.onToggleSort?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Page up - jump up by maxVisible items\n\t\telse if (kb.matches(keyData, \"selectPageUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t}\n\t\t// Page down - jump down by maxVisible items\n\t\telse if (kb.matches(keyData, \"selectPageDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + this.maxVisible);\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\ntype SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\tprivate header: SessionSelectorHeader;\n\tprivate scope: SessionScope = \"current\";\n\tprivate sortMode: SortMode = \"relevance\";\n\tprivate currentSessions: SessionInfo[] | null = null;\n\tprivate allSessions: SessionInfo[] | null = null;\n\tprivate currentSessionsLoader: SessionsLoader;\n\tprivate allSessionsLoader: SessionsLoader;\n\tprivate onCancel: () => void;\n\tprivate requestRender: () => void;\n\n\tconstructor(\n\t\tcurrentSessionsLoader: SessionsLoader,\n\t\tallSessionsLoader: SessionsLoader,\n\t\tonSelect: (sessionPath: string) => void,\n\t\tonCancel: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\t\tthis.currentSessionsLoader = currentSessionsLoader;\n\t\tthis.allSessionsLoader = allSessionsLoader;\n\t\tthis.onCancel = onCancel;\n\t\tthis.requestRender = requestRender;\n\t\tthis.header = new SessionSelectorHeader(this.scope, this.sortMode);\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.header);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list (starts empty, will be populated after load)\n\t\tthis.sessionList = new SessionList([], false, this.sortMode);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\t\tthis.sessionList.onExit = onExit;\n\t\tthis.sessionList.onToggleScope = () => this.toggleScope();\n\t\tthis.sessionList.onToggleSort = () => this.toggleSortMode();\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Start loading current sessions immediately\n\t\tthis.loadCurrentSessions();\n\t}\n\n\tprivate loadCurrentSessions(): void {\n\t\tthis.header.setLoading(true);\n\t\tthis.requestRender();\n\t\tthis.currentSessionsLoader((loaded, total) => {\n\t\t\tthis.header.setProgress(loaded, total);\n\t\t\tthis.requestRender();\n\t\t}).then((sessions) => {\n\t\t\tthis.currentSessions = sessions;\n\t\t\tthis.header.setLoading(false);\n\t\t\tthis.sessionList.setSessions(sessions, false);\n\t\t\tthis.requestRender();\n\t\t});\n\t}\n\n\tprivate toggleSortMode(): void {\n\t\tthis.sortMode = this.sortMode === \"recent\" ? \"relevance\" : \"recent\";\n\t\tthis.header.setSortMode(this.sortMode);\n\t\tthis.sessionList.setSortMode(this.sortMode);\n\t\tthis.requestRender();\n\t}\n\n\tprivate toggleScope(): void {\n\t\tif (this.scope === \"current\") {\n\t\t\t// Switching to \"all\" - load if not already loaded\n\t\t\tif (this.allSessions === null) {\n\t\t\t\tthis.header.setLoading(true);\n\t\t\t\tthis.header.setScope(\"all\");\n\t\t\t\tthis.sessionList.setSessions([], true); // Clear list while loading\n\t\t\t\tthis.requestRender();\n\t\t\t\t// Load asynchronously with progress updates\n\t\t\t\tthis.allSessionsLoader((loaded, total) => {\n\t\t\t\t\tthis.header.setProgress(loaded, total);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t}).then((sessions) => {\n\t\t\t\t\tthis.allSessions = sessions;\n\t\t\t\t\tthis.header.setLoading(false);\n\t\t\t\t\tthis.scope = \"all\";\n\t\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t\t// If no sessions in All scope either, cancel\n\t\t\t\t\tif (this.allSessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {\n\t\t\t\t\t\tthis.onCancel();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.scope = \"all\";\n\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\tthis.header.setScope(this.scope);\n\t\t\t}\n\t\t} else {\n\t\t\t// Switching back to \"current\"\n\t\t\tthis.scope = \"current\";\n\t\t\tthis.sessionList.setSessions(this.currentSessions ?? [], false);\n\t\t\tthis.header.setScope(this.scope);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}
@@ -1,7 +1,8 @@
1
1
  import * as os from "node:os";
2
- import { Container, fuzzyFilter, getEditorKeybindings, Input, Spacer, truncateToWidth, visibleWidth, } from "@mariozechner/pi-tui";
2
+ import { Container, getEditorKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "@mariozechner/pi-tui";
3
3
  import { theme } from "../theme/theme.js";
4
4
  import { DynamicBorder } from "./dynamic-border.js";
5
+ import { filterAndSortSessions } from "./session-selector-search.js";
5
6
  function shortenPath(path) {
6
7
  const home = os.homedir();
7
8
  if (!path)
@@ -31,14 +32,19 @@ function formatSessionDate(date) {
31
32
  }
32
33
  class SessionSelectorHeader {
33
34
  scope;
35
+ sortMode;
34
36
  loading = false;
35
37
  loadProgress = null;
36
- constructor(scope) {
38
+ constructor(scope, sortMode) {
37
39
  this.scope = scope;
40
+ this.sortMode = sortMode;
38
41
  }
39
42
  setScope(scope) {
40
43
  this.scope = scope;
41
44
  }
45
+ setSortMode(sortMode) {
46
+ this.sortMode = sortMode;
47
+ }
42
48
  setLoading(loading) {
43
49
  this.loading = loading;
44
50
  if (!loading) {
@@ -52,6 +58,8 @@ class SessionSelectorHeader {
52
58
  render(width) {
53
59
  const title = this.scope === "current" ? "Resume Session (Current Folder)" : "Resume Session (All)";
54
60
  const leftText = theme.bold(title);
61
+ const sortLabel = this.sortMode === "recent" ? "Recent" : "Fuzzy";
62
+ const sortText = theme.fg("muted", "Sort: ") + theme.fg("accent", sortLabel);
55
63
  let scopeText;
56
64
  if (this.loading) {
57
65
  const progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : "...";
@@ -63,11 +71,11 @@ class SessionSelectorHeader {
63
71
  ? `${theme.fg("accent", "◉ Current Folder")}${theme.fg("muted", " | ○ All")}`
64
72
  : `${theme.fg("muted", "○ Current Folder | ")}${theme.fg("accent", "◉ All")}`;
65
73
  }
66
- const rightText = truncateToWidth(scopeText, width, "");
74
+ const rightText = truncateToWidth(`${scopeText} ${sortText}`, width, "");
67
75
  const availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);
68
76
  const left = truncateToWidth(leftText, availableLeft, "");
69
77
  const spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));
70
- const hint = theme.fg("muted", "Tab to toggle scope");
78
+ const hint = theme.fg("muted", 'Tab: scope · Ctrl+R: sort · re:<pattern> for regex · "phrase" for exact phrase');
71
79
  return [`${left}${" ".repeat(spacing)}${rightText}`, hint];
72
80
  }
73
81
  }
@@ -80,16 +88,19 @@ class SessionList {
80
88
  selectedIndex = 0;
81
89
  searchInput;
82
90
  showCwd = false;
91
+ sortMode = "relevance";
83
92
  onSelect;
84
93
  onCancel;
85
94
  onExit = () => { };
86
95
  onToggleScope;
96
+ onToggleSort;
87
97
  maxVisible = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)
88
- constructor(sessions, showCwd) {
98
+ constructor(sessions, showCwd, sortMode) {
89
99
  this.allSessions = sessions;
90
100
  this.filteredSessions = sessions;
91
101
  this.searchInput = new Input();
92
102
  this.showCwd = showCwd;
103
+ this.sortMode = sortMode;
93
104
  // Handle Enter in search input - select current item
94
105
  this.searchInput.onSubmit = () => {
95
106
  if (this.filteredSessions[this.selectedIndex]) {
@@ -100,13 +111,17 @@ class SessionList {
100
111
  }
101
112
  };
102
113
  }
114
+ setSortMode(sortMode) {
115
+ this.sortMode = sortMode;
116
+ this.filterSessions(this.searchInput.getValue());
117
+ }
103
118
  setSessions(sessions, showCwd) {
104
119
  this.allSessions = sessions;
105
120
  this.showCwd = showCwd;
106
121
  this.filterSessions(this.searchInput.getValue());
107
122
  }
108
123
  filterSessions(query) {
109
- this.filteredSessions = fuzzyFilter(this.allSessions, query, (session) => `${session.id} ${session.name ?? ""} ${session.allMessagesText} ${session.cwd}`);
124
+ this.filteredSessions = filterAndSortSessions(this.allSessions, query, this.sortMode);
110
125
  this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));
111
126
  }
112
127
  invalidate() { }
@@ -179,6 +194,10 @@ class SessionList {
179
194
  }
180
195
  return;
181
196
  }
197
+ if (matchesKey(keyData, "ctrl+r")) {
198
+ this.onToggleSort?.();
199
+ return;
200
+ }
182
201
  // Up arrow
183
202
  if (kb.matches(keyData, "selectUp")) {
184
203
  this.selectedIndex = Math.max(0, this.selectedIndex - 1);
@@ -222,6 +241,7 @@ export class SessionSelectorComponent extends Container {
222
241
  sessionList;
223
242
  header;
224
243
  scope = "current";
244
+ sortMode = "relevance";
225
245
  currentSessions = null;
226
246
  allSessions = null;
227
247
  currentSessionsLoader;
@@ -234,7 +254,7 @@ export class SessionSelectorComponent extends Container {
234
254
  this.allSessionsLoader = allSessionsLoader;
235
255
  this.onCancel = onCancel;
236
256
  this.requestRender = requestRender;
237
- this.header = new SessionSelectorHeader(this.scope);
257
+ this.header = new SessionSelectorHeader(this.scope, this.sortMode);
238
258
  // Add header
239
259
  this.addChild(new Spacer(1));
240
260
  this.addChild(this.header);
@@ -242,11 +262,12 @@ export class SessionSelectorComponent extends Container {
242
262
  this.addChild(new DynamicBorder());
243
263
  this.addChild(new Spacer(1));
244
264
  // Create session list (starts empty, will be populated after load)
245
- this.sessionList = new SessionList([], false);
265
+ this.sessionList = new SessionList([], false, this.sortMode);
246
266
  this.sessionList.onSelect = onSelect;
247
267
  this.sessionList.onCancel = onCancel;
248
268
  this.sessionList.onExit = onExit;
249
269
  this.sessionList.onToggleScope = () => this.toggleScope();
270
+ this.sessionList.onToggleSort = () => this.toggleSortMode();
250
271
  this.addChild(this.sessionList);
251
272
  // Add bottom border
252
273
  this.addChild(new Spacer(1));
@@ -267,6 +288,12 @@ export class SessionSelectorComponent extends Container {
267
288
  this.requestRender();
268
289
  });
269
290
  }
291
+ toggleSortMode() {
292
+ this.sortMode = this.sortMode === "recent" ? "relevance" : "recent";
293
+ this.header.setSortMode(this.sortMode);
294
+ this.sessionList.setSortMode(this.sortMode);
295
+ this.requestRender();
296
+ }
270
297
  toggleScope() {
271
298
  if (this.scope === "current") {
272
299
  // Switching to "all" - load if not already loaded
@@ -1 +1 @@
1
- {"version":3,"file":"session-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport {\n\ttype Component,\n\tContainer,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\nimport type { SessionInfo, SessionListProgress } from \"../../../core/session-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\ntype SessionScope = \"current\" | \"all\";\n\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (!path) return path;\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nfunction formatSessionDate(date: Date): string {\n\tconst now = new Date();\n\tconst diffMs = now.getTime() - date.getTime();\n\tconst diffMins = Math.floor(diffMs / 60000);\n\tconst diffHours = Math.floor(diffMs / 3600000);\n\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\tif (diffMins < 1) return \"just now\";\n\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffDays === 1) return \"1 day ago\";\n\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\treturn date.toLocaleDateString();\n}\n\nclass SessionSelectorHeader implements Component {\n\tprivate scope: SessionScope;\n\tprivate loading = false;\n\tprivate loadProgress: { loaded: number; total: number } | null = null;\n\n\tconstructor(scope: SessionScope) {\n\t\tthis.scope = scope;\n\t}\n\n\tsetScope(scope: SessionScope): void {\n\t\tthis.scope = scope;\n\t}\n\n\tsetLoading(loading: boolean): void {\n\t\tthis.loading = loading;\n\t\tif (!loading) {\n\t\t\tthis.loadProgress = null;\n\t\t}\n\t}\n\n\tsetProgress(loaded: number, total: number): void {\n\t\tthis.loadProgress = { loaded, total };\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = this.scope === \"current\" ? \"Resume Session (Current Folder)\" : \"Resume Session (All)\";\n\t\tconst leftText = theme.bold(title);\n\t\tlet scopeText: string;\n\t\tif (this.loading) {\n\t\t\tconst progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : \"...\";\n\t\t\tscopeText = `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", `Loading ${progressText}`)}`;\n\t\t} else {\n\t\t\tscopeText =\n\t\t\t\tthis.scope === \"current\"\n\t\t\t\t\t? `${theme.fg(\"accent\", \"◉ Current Folder\")}${theme.fg(\"muted\", \" | ○ All\")}`\n\t\t\t\t\t: `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", \"◉ All\")}`;\n\t\t}\n\t\tconst rightText = truncateToWidth(scopeText, width, \"\");\n\t\tconst availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);\n\t\tconst left = truncateToWidth(leftText, availableLeft, \"\");\n\t\tconst spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));\n\t\tconst hint = theme.fg(\"muted\", \"Tab to toggle scope\");\n\t\treturn [`${left}${\" \".repeat(spacing)}${rightText}`, hint];\n\t}\n}\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionInfo[] = [];\n\tprivate filteredSessions: SessionInfo[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tprivate showCwd = false;\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tpublic onExit: () => void = () => {};\n\tpublic onToggleScope?: () => void;\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionInfo[], showCwd: boolean) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\t\tthis.showCwd = showCwd;\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tsetSessions(sessions: SessionInfo[], showCwd: boolean): void {\n\t\tthis.allSessions = sessions;\n\t\tthis.showCwd = showCwd;\n\t\tthis.filterSessions(this.searchInput.getValue());\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tthis.filteredSessions = fuzzyFilter(\n\t\t\tthis.allSessions,\n\t\t\tquery,\n\t\t\t(session) => `${session.id} ${session.name ?? \"\"} ${session.allMessagesText} ${session.cwd}`,\n\t\t);\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tif (this.showCwd) {\n\t\t\t\t// \"All\" scope - no sessions anywhere that match filter\n\t\t\t\tlines.push(theme.fg(\"muted\", \" No sessions found\"));\n\t\t\t} else {\n\t\t\t\t// \"Current folder\" scope - hint to try \"all\"\n\t\t\t\tlines.push(theme.fg(\"muted\", \" No sessions in current folder. Press Tab to view all.\"));\n\t\t\t}\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Use session name if set, otherwise first message\n\t\t\tconst hasName = !!session.name;\n\t\t\tconst displayText = session.name ?? session.firstMessage;\n\t\t\tconst normalizedMessage = displayText.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message (truncate to visible width)\n\t\t\t// Use warning color for custom names to distinguish from first message\n\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor (2 visible chars)\n\t\t\tconst truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, \"...\");\n\t\t\tlet styledMsg = truncatedMsg;\n\t\t\tif (hasName) {\n\t\t\t\tstyledMsg = theme.fg(\"warning\", truncatedMsg);\n\t\t\t}\n\t\t\tif (isSelected) {\n\t\t\t\tstyledMsg = theme.bold(styledMsg);\n\t\t\t}\n\t\t\tconst messageLine = cursor + styledMsg;\n\n\t\t\t// Second line: metadata (dimmed) - also truncate for safety\n\t\t\tconst modified = formatSessionDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadataParts = [modified, msgCount];\n\t\t\tif (this.showCwd && session.cwd) {\n\t\t\t\tmetadataParts.push(shortenPath(session.cwd));\n\t\t\t}\n\t\t\tconst metadata = ` ${metadataParts.join(\" · \")}`;\n\t\t\tconst metadataLine = theme.fg(\"dim\", truncateToWidth(metadata, width, \"\"));\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;\n\t\t\tconst scrollInfo = theme.fg(\"muted\", truncateToWidth(scrollText, width, \"\"));\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\tif (kb.matches(keyData, \"tab\")) {\n\t\t\tif (this.onToggleScope) {\n\t\t\t\tthis.onToggleScope();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Page up - jump up by maxVisible items\n\t\telse if (kb.matches(keyData, \"selectPageUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t}\n\t\t// Page down - jump down by maxVisible items\n\t\telse if (kb.matches(keyData, \"selectPageDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + this.maxVisible);\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\ntype SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\tprivate header: SessionSelectorHeader;\n\tprivate scope: SessionScope = \"current\";\n\tprivate currentSessions: SessionInfo[] | null = null;\n\tprivate allSessions: SessionInfo[] | null = null;\n\tprivate currentSessionsLoader: SessionsLoader;\n\tprivate allSessionsLoader: SessionsLoader;\n\tprivate onCancel: () => void;\n\tprivate requestRender: () => void;\n\n\tconstructor(\n\t\tcurrentSessionsLoader: SessionsLoader,\n\t\tallSessionsLoader: SessionsLoader,\n\t\tonSelect: (sessionPath: string) => void,\n\t\tonCancel: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\t\tthis.currentSessionsLoader = currentSessionsLoader;\n\t\tthis.allSessionsLoader = allSessionsLoader;\n\t\tthis.onCancel = onCancel;\n\t\tthis.requestRender = requestRender;\n\t\tthis.header = new SessionSelectorHeader(this.scope);\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.header);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list (starts empty, will be populated after load)\n\t\tthis.sessionList = new SessionList([], false);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\t\tthis.sessionList.onExit = onExit;\n\t\tthis.sessionList.onToggleScope = () => this.toggleScope();\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Start loading current sessions immediately\n\t\tthis.loadCurrentSessions();\n\t}\n\n\tprivate loadCurrentSessions(): void {\n\t\tthis.header.setLoading(true);\n\t\tthis.requestRender();\n\t\tthis.currentSessionsLoader((loaded, total) => {\n\t\t\tthis.header.setProgress(loaded, total);\n\t\t\tthis.requestRender();\n\t\t}).then((sessions) => {\n\t\t\tthis.currentSessions = sessions;\n\t\t\tthis.header.setLoading(false);\n\t\t\tthis.sessionList.setSessions(sessions, false);\n\t\t\tthis.requestRender();\n\t\t});\n\t}\n\n\tprivate toggleScope(): void {\n\t\tif (this.scope === \"current\") {\n\t\t\t// Switching to \"all\" - load if not already loaded\n\t\t\tif (this.allSessions === null) {\n\t\t\t\tthis.header.setLoading(true);\n\t\t\t\tthis.header.setScope(\"all\");\n\t\t\t\tthis.sessionList.setSessions([], true); // Clear list while loading\n\t\t\t\tthis.requestRender();\n\t\t\t\t// Load asynchronously with progress updates\n\t\t\t\tthis.allSessionsLoader((loaded, total) => {\n\t\t\t\t\tthis.header.setProgress(loaded, total);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t}).then((sessions) => {\n\t\t\t\t\tthis.allSessions = sessions;\n\t\t\t\t\tthis.header.setLoading(false);\n\t\t\t\t\tthis.scope = \"all\";\n\t\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t\t// If no sessions in All scope either, cancel\n\t\t\t\t\tif (this.allSessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {\n\t\t\t\t\t\tthis.onCancel();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.scope = \"all\";\n\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\tthis.header.setScope(this.scope);\n\t\t\t}\n\t\t} else {\n\t\t\t// Switching back to \"current\"\n\t\t\tthis.scope = \"current\";\n\t\t\tthis.sessionList.setSessions(this.currentSessions ?? [], false);\n\t\t\tthis.header.setScope(this.scope);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport {\n\ttype Component,\n\tContainer,\n\tgetEditorKeybindings,\n\tInput,\n\tmatchesKey,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\nimport type { SessionInfo, SessionListProgress } from \"../../../core/session-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { filterAndSortSessions, type SortMode } from \"./session-selector-search.js\";\n\ntype SessionScope = \"current\" | \"all\";\n\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (!path) return path;\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nfunction formatSessionDate(date: Date): string {\n\tconst now = new Date();\n\tconst diffMs = now.getTime() - date.getTime();\n\tconst diffMins = Math.floor(diffMs / 60000);\n\tconst diffHours = Math.floor(diffMs / 3600000);\n\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\tif (diffMins < 1) return \"just now\";\n\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffDays === 1) return \"1 day ago\";\n\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\treturn date.toLocaleDateString();\n}\n\nclass SessionSelectorHeader implements Component {\n\tprivate scope: SessionScope;\n\tprivate sortMode: SortMode;\n\tprivate loading = false;\n\tprivate loadProgress: { loaded: number; total: number } | null = null;\n\n\tconstructor(scope: SessionScope, sortMode: SortMode) {\n\t\tthis.scope = scope;\n\t\tthis.sortMode = sortMode;\n\t}\n\n\tsetScope(scope: SessionScope): void {\n\t\tthis.scope = scope;\n\t}\n\n\tsetSortMode(sortMode: SortMode): void {\n\t\tthis.sortMode = sortMode;\n\t}\n\n\tsetLoading(loading: boolean): void {\n\t\tthis.loading = loading;\n\t\tif (!loading) {\n\t\t\tthis.loadProgress = null;\n\t\t}\n\t}\n\n\tsetProgress(loaded: number, total: number): void {\n\t\tthis.loadProgress = { loaded, total };\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = this.scope === \"current\" ? \"Resume Session (Current Folder)\" : \"Resume Session (All)\";\n\t\tconst leftText = theme.bold(title);\n\n\t\tconst sortLabel = this.sortMode === \"recent\" ? \"Recent\" : \"Fuzzy\";\n\t\tconst sortText = theme.fg(\"muted\", \"Sort: \") + theme.fg(\"accent\", sortLabel);\n\n\t\tlet scopeText: string;\n\t\tif (this.loading) {\n\t\t\tconst progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : \"...\";\n\t\t\tscopeText = `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", `Loading ${progressText}`)}`;\n\t\t} else {\n\t\t\tscopeText =\n\t\t\t\tthis.scope === \"current\"\n\t\t\t\t\t? `${theme.fg(\"accent\", \"◉ Current Folder\")}${theme.fg(\"muted\", \" | ○ All\")}`\n\t\t\t\t\t: `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", \"◉ All\")}`;\n\t\t}\n\n\t\tconst rightText = truncateToWidth(`${scopeText} ${sortText}`, width, \"\");\n\t\tconst availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);\n\t\tconst left = truncateToWidth(leftText, availableLeft, \"\");\n\t\tconst spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));\n\t\tconst hint = theme.fg(\"muted\", 'Tab: scope · Ctrl+R: sort · re:<pattern> for regex · \"phrase\" for exact phrase');\n\t\treturn [`${left}${\" \".repeat(spacing)}${rightText}`, hint];\n\t}\n}\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionInfo[] = [];\n\tprivate filteredSessions: SessionInfo[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tprivate showCwd = false;\n\tprivate sortMode: SortMode = \"relevance\";\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tpublic onExit: () => void = () => {};\n\tpublic onToggleScope?: () => void;\n\tpublic onToggleSort?: () => void;\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionInfo[], showCwd: boolean, sortMode: SortMode) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\t\tthis.showCwd = showCwd;\n\t\tthis.sortMode = sortMode;\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tsetSortMode(sortMode: SortMode): void {\n\t\tthis.sortMode = sortMode;\n\t\tthis.filterSessions(this.searchInput.getValue());\n\t}\n\n\tsetSessions(sessions: SessionInfo[], showCwd: boolean): void {\n\t\tthis.allSessions = sessions;\n\t\tthis.showCwd = showCwd;\n\t\tthis.filterSessions(this.searchInput.getValue());\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tthis.filteredSessions = filterAndSortSessions(this.allSessions, query, this.sortMode);\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tif (this.showCwd) {\n\t\t\t\t// \"All\" scope - no sessions anywhere that match filter\n\t\t\t\tlines.push(theme.fg(\"muted\", \" No sessions found\"));\n\t\t\t} else {\n\t\t\t\t// \"Current folder\" scope - hint to try \"all\"\n\t\t\t\tlines.push(theme.fg(\"muted\", \" No sessions in current folder. Press Tab to view all.\"));\n\t\t\t}\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Use session name if set, otherwise first message\n\t\t\tconst hasName = !!session.name;\n\t\t\tconst displayText = session.name ?? session.firstMessage;\n\t\t\tconst normalizedMessage = displayText.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message (truncate to visible width)\n\t\t\t// Use warning color for custom names to distinguish from first message\n\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor (2 visible chars)\n\t\t\tconst truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, \"...\");\n\t\t\tlet styledMsg = truncatedMsg;\n\t\t\tif (hasName) {\n\t\t\t\tstyledMsg = theme.fg(\"warning\", truncatedMsg);\n\t\t\t}\n\t\t\tif (isSelected) {\n\t\t\t\tstyledMsg = theme.bold(styledMsg);\n\t\t\t}\n\t\t\tconst messageLine = cursor + styledMsg;\n\n\t\t\t// Second line: metadata (dimmed) - also truncate for safety\n\t\t\tconst modified = formatSessionDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadataParts = [modified, msgCount];\n\t\t\tif (this.showCwd && session.cwd) {\n\t\t\t\tmetadataParts.push(shortenPath(session.cwd));\n\t\t\t}\n\t\t\tconst metadata = ` ${metadataParts.join(\" · \")}`;\n\t\t\tconst metadataLine = theme.fg(\"dim\", truncateToWidth(metadata, width, \"\"));\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;\n\t\t\tconst scrollInfo = theme.fg(\"muted\", truncateToWidth(scrollText, width, \"\"));\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\tif (kb.matches(keyData, \"tab\")) {\n\t\t\tif (this.onToggleScope) {\n\t\t\t\tthis.onToggleScope();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(keyData, \"ctrl+r\")) {\n\t\t\tthis.onToggleSort?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Page up - jump up by maxVisible items\n\t\telse if (kb.matches(keyData, \"selectPageUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t}\n\t\t// Page down - jump down by maxVisible items\n\t\telse if (kb.matches(keyData, \"selectPageDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + this.maxVisible);\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\ntype SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\tprivate header: SessionSelectorHeader;\n\tprivate scope: SessionScope = \"current\";\n\tprivate sortMode: SortMode = \"relevance\";\n\tprivate currentSessions: SessionInfo[] | null = null;\n\tprivate allSessions: SessionInfo[] | null = null;\n\tprivate currentSessionsLoader: SessionsLoader;\n\tprivate allSessionsLoader: SessionsLoader;\n\tprivate onCancel: () => void;\n\tprivate requestRender: () => void;\n\n\tconstructor(\n\t\tcurrentSessionsLoader: SessionsLoader,\n\t\tallSessionsLoader: SessionsLoader,\n\t\tonSelect: (sessionPath: string) => void,\n\t\tonCancel: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\t\tthis.currentSessionsLoader = currentSessionsLoader;\n\t\tthis.allSessionsLoader = allSessionsLoader;\n\t\tthis.onCancel = onCancel;\n\t\tthis.requestRender = requestRender;\n\t\tthis.header = new SessionSelectorHeader(this.scope, this.sortMode);\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.header);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list (starts empty, will be populated after load)\n\t\tthis.sessionList = new SessionList([], false, this.sortMode);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\t\tthis.sessionList.onExit = onExit;\n\t\tthis.sessionList.onToggleScope = () => this.toggleScope();\n\t\tthis.sessionList.onToggleSort = () => this.toggleSortMode();\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Start loading current sessions immediately\n\t\tthis.loadCurrentSessions();\n\t}\n\n\tprivate loadCurrentSessions(): void {\n\t\tthis.header.setLoading(true);\n\t\tthis.requestRender();\n\t\tthis.currentSessionsLoader((loaded, total) => {\n\t\t\tthis.header.setProgress(loaded, total);\n\t\t\tthis.requestRender();\n\t\t}).then((sessions) => {\n\t\t\tthis.currentSessions = sessions;\n\t\t\tthis.header.setLoading(false);\n\t\t\tthis.sessionList.setSessions(sessions, false);\n\t\t\tthis.requestRender();\n\t\t});\n\t}\n\n\tprivate toggleSortMode(): void {\n\t\tthis.sortMode = this.sortMode === \"recent\" ? \"relevance\" : \"recent\";\n\t\tthis.header.setSortMode(this.sortMode);\n\t\tthis.sessionList.setSortMode(this.sortMode);\n\t\tthis.requestRender();\n\t}\n\n\tprivate toggleScope(): void {\n\t\tif (this.scope === \"current\") {\n\t\t\t// Switching to \"all\" - load if not already loaded\n\t\t\tif (this.allSessions === null) {\n\t\t\t\tthis.header.setLoading(true);\n\t\t\t\tthis.header.setScope(\"all\");\n\t\t\t\tthis.sessionList.setSessions([], true); // Clear list while loading\n\t\t\t\tthis.requestRender();\n\t\t\t\t// Load asynchronously with progress updates\n\t\t\t\tthis.allSessionsLoader((loaded, total) => {\n\t\t\t\t\tthis.header.setProgress(loaded, total);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t}).then((sessions) => {\n\t\t\t\t\tthis.allSessions = sessions;\n\t\t\t\t\tthis.header.setLoading(false);\n\t\t\t\t\tthis.scope = \"all\";\n\t\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t\t// If no sessions in All scope either, cancel\n\t\t\t\t\tif (this.allSessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {\n\t\t\t\t\t\tthis.onCancel();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.scope = \"all\";\n\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\tthis.header.setScope(this.scope);\n\t\t\t}\n\t\t} else {\n\t\t\t// Switching back to \"current\"\n\t\t\tthis.scope = \"current\";\n\t\t\tthis.sessionList.setSessions(this.currentSessions ?? [], false);\n\t\t\tthis.header.setScope(this.scope);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}