claude-code-memory-explorer 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-memory-explorer",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Memory explorer dashboard for Claude Code",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -221,11 +221,10 @@ async function putProject(dirPath) {
221
221
  }
222
222
 
223
223
  function changeProject() {
224
- const current = document.getElementById('projectBtn').title;
225
- document.getElementById('projectPathInput').value = current;
226
- renderRecentProjects();
224
+ document.getElementById('projectPickerInput').value = '';
225
+ renderProjectPicker();
227
226
  document.getElementById('projectPickerModal').classList.add('open');
228
- setTimeout(() => document.getElementById('projectPathInput').focus(), 100);
227
+ setPickerMode(false);
229
228
  }
230
229
 
231
230
  async function submitProjectPicker() {
@@ -248,6 +247,8 @@ async function submitProjectPicker() {
248
247
  }
249
248
  }
250
249
 
250
+ const RECENT_PROJECTS_MAX = 20;
251
+
251
252
  function getRecentProjects() {
252
253
  try {
253
254
  return JSON.parse(localStorage.getItem('recentProjects') || '[]');
@@ -259,38 +260,102 @@ function getRecentProjects() {
259
260
  function addRecentProject(p) {
260
261
  const recent = getRecentProjects().filter((r) => r !== p);
261
262
  recent.unshift(p);
262
- localStorage.setItem('recentProjects', JSON.stringify(recent.slice(0, 10)));
263
+ localStorage.setItem('recentProjects', JSON.stringify(recent.slice(0, RECENT_PROJECTS_MAX)));
263
264
  }
264
265
 
265
- function _removeRecentProject(p, e) {
266
- e.stopPropagation();
267
- const recent = getRecentProjects().filter((r) => r !== p);
268
- localStorage.setItem('recentProjects', JSON.stringify(recent));
269
- renderRecentProjects();
266
+ // The recents list is the switcher; typing a path is the secondary act behind "+ Add path".
267
+ let pickerRows = [];
268
+ let pickerIdx = -1;
269
+
270
+ function setPickerMode(add) {
271
+ document.getElementById('projectPickerFilter').hidden = add;
272
+ document.getElementById('projectPickerAddForm').hidden = !add;
273
+ const input = document.getElementById(add ? 'projectPathInput' : 'projectPickerInput');
274
+ if (add) input.value = document.getElementById('projectBtn').title;
275
+ // The overlay fades in, so a focus() on a field that is still hidden is dropped.
276
+ setTimeout(() => input.focus(), 100);
270
277
  }
271
278
 
272
- function _selectRecentProject(p) {
273
- document.getElementById('projectPathInput').value = p;
279
+ function splitProjectPath(p) {
280
+ const trimmed = p.replace(/[/\\]+$/, '');
281
+ const cut = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\'));
282
+ return { name: trimmed.slice(cut + 1), parent: trimmed.slice(0, Math.max(cut, 0)) };
274
283
  }
275
284
 
276
- function renderRecentProjects() {
277
- const container = document.getElementById('recentProjectsList');
285
+ function renderProjectPicker() {
286
+ const list = document.getElementById('projectPickerList');
287
+ const q = document.getElementById('projectPickerInput').value.trim().toLowerCase();
288
+ const current = document.getElementById('projectBtn').title;
278
289
  const recent = getRecentProjects();
279
- if (!recent.length) {
280
- container.innerHTML = '';
290
+ pickerRows = q ? recent.filter((p) => p.toLowerCase().includes(q)) : recent;
291
+ if (!pickerRows.length) {
292
+ pickerIdx = -1;
293
+ const msg = recent.length ? 'No projects match' : 'No recent projects — add one below';
294
+ list.innerHTML = `<div class="picker-empty">${msg}</div>`;
281
295
  return;
282
296
  }
283
- container.innerHTML =
284
- '<div class="recent-projects-label">Recent</div>' +
285
- recent
286
- .map(
287
- (p) =>
288
- `<div class="recent-project-item" onclick="_selectRecentProject('${escAttrJs(p)}')">` +
289
- `<span>${esc(p)}</span>` +
290
- `<button class="recent-project-remove" onclick="_removeRecentProject('${escAttrJs(p)}', event)" title="Remove">&#10005;</button>` +
291
- `</div>`,
292
- )
293
- .join('');
297
+ list.innerHTML = pickerRows
298
+ .map((p, i) => {
299
+ const { name, parent } = splitProjectPath(p);
300
+ const cur = p === current;
301
+ return `<div class="picker-row${cur ? ' current' : ''}" onclick="_pickProject(${i})" title="${esc(p)}">
302
+ <span class="picker-name">${esc(name)}</span>
303
+ <span class="picker-parent">${esc(parent)}</span>
304
+ ${cur ? '<span class="picker-tag">current</span>' : ''}
305
+ <button class="picker-remove" onclick="_removeRecentProject(${i}, event)" title="Remove">&#10005;</button>
306
+ </div>`;
307
+ })
308
+ .join('');
309
+ // Opens on the first row that would actually change something.
310
+ setPickerCursor(pickerRows.findIndex((p) => p !== current));
311
+ }
312
+
313
+ function setPickerCursor(idx) {
314
+ const list = document.getElementById('projectPickerList');
315
+ list.children[pickerIdx]?.classList.remove('selected');
316
+ pickerIdx = Math.min(Math.max(idx, 0), pickerRows.length - 1);
317
+ const row = list.children[pickerIdx];
318
+ row?.classList.add('selected');
319
+ row?.scrollIntoView({ block: 'nearest' });
320
+ }
321
+
322
+ function _pickProject(idx) {
323
+ const p = pickerRows[idx];
324
+ if (!p) return;
325
+ document.getElementById('projectPathInput').value = p;
326
+ submitProjectPicker();
327
+ }
328
+
329
+ function _removeRecentProject(idx, e) {
330
+ e.stopPropagation();
331
+ const target = pickerRows[idx];
332
+ if (!target) return;
333
+ localStorage.setItem('recentProjects', JSON.stringify(getRecentProjects().filter((p) => p !== target)));
334
+ renderProjectPicker();
335
+ }
336
+
337
+ function initProjectPicker() {
338
+ document.getElementById('projectPickerAdd').addEventListener('click', () => setPickerMode(true));
339
+ document.getElementById('projectPickerBack').addEventListener('click', () => setPickerMode(false));
340
+ const filter = document.getElementById('projectPickerInput');
341
+ filter.addEventListener('input', renderProjectPicker);
342
+ filter.addEventListener('keydown', (e) => {
343
+ if (e.key === 'Escape') closeModal('projectPickerModal');
344
+ else if (e.key === 'ArrowDown' || (e.key === 'n' && e.ctrlKey)) setPickerCursor(pickerIdx + 1);
345
+ else if (e.key === 'ArrowUp' || (e.key === 'p' && e.ctrlKey)) setPickerCursor(pickerIdx - 1);
346
+ else if (e.key === 'Enter') _pickProject(pickerIdx);
347
+ else return;
348
+ e.preventDefault();
349
+ e.stopPropagation();
350
+ });
351
+ document.getElementById('projectPathInput').addEventListener('keydown', (e) => {
352
+ if (e.key === 'Enter') submitProjectPicker();
353
+ // Escape steps back to the list rather than closing — the list is where you came from.
354
+ else if (e.key === 'Escape') setPickerMode(false);
355
+ else return;
356
+ e.preventDefault();
357
+ e.stopPropagation();
358
+ });
294
359
  }
295
360
 
296
361
  // #endregion PROJECT
@@ -301,7 +366,80 @@ function closeModal(id) {
301
366
  document.getElementById(id).classList.remove('open');
302
367
  }
303
368
 
369
+ // Each entry pairs a left and a right group onto the same grid rows, so their
370
+ // headings sit level and the shorter group just leaves empty rows.
371
+ // `combo` joins the keys with a plus (a chord) instead of listing them as
372
+ // alternatives; `hub` marks rows that only work inside Claude Code Hub.
373
+ const SHORTCUT_PAIRS = [
374
+ [
375
+ {
376
+ title: 'Navigate',
377
+ rows: [
378
+ { keys: ['j', '↓'], label: 'Next item' },
379
+ { keys: ['k', '↑'], label: 'Previous item' },
380
+ { keys: ['l', '→'], label: 'Expand / enter child' },
381
+ { keys: ['h', '←'], label: 'Collapse / parent' },
382
+ { keys: ['Enter'], label: 'Select item' },
383
+ { keys: ['Esc'], label: 'Close modal' },
384
+ ],
385
+ },
386
+ {
387
+ title: 'Actions',
388
+ rows: [
389
+ { keys: ['e'], label: 'Open in editor' },
390
+ { keys: ['r'], label: 'Refresh data' },
391
+ { keys: ['t'], label: 'Toggle theme' },
392
+ { keys: ['Shift', 'P'], combo: true, label: 'Switch project' },
393
+ { keys: ['?'], label: 'Show this help' },
394
+ ],
395
+ },
396
+ ],
397
+ [
398
+ {
399
+ title: 'Hub',
400
+ hub: true,
401
+ rows: [
402
+ { keys: ['Ctrl', 'Alt', '←/→'], combo: true, label: 'Previous / next hub app' },
403
+ { keys: ['Alt', '1…9'], combo: true, label: 'Jump to hub app by number' },
404
+ { keys: ['Ctrl', 'Alt', 'P'], combo: true, label: 'Project picker' },
405
+ ],
406
+ },
407
+ ],
408
+ ];
409
+
410
+ const EMPTY_GROUP = { title: '', rows: [] };
411
+
412
+ // Interleaves each pair's rows left-then-right so CSS grid auto-placement lands
413
+ // them on shared row tracks (see .shortcuts in style.css).
414
+ function buildHelpShortcuts() {
415
+ const cells = [];
416
+ SHORTCUT_PAIRS.forEach(([left, right = EMPTY_GROUP], pair) => {
417
+ const first = pair === 0 ? ' sc-first' : '';
418
+ const sides = [
419
+ [left, 'sc-l'],
420
+ [right, 'sc-r'],
421
+ ];
422
+ const head = (g, side) =>
423
+ g.title ? `<div class="${esc(`sc-group ${side}${first}${g.hub ? ' sc-hub' : ''}`)}">${g.title}</div>` : '';
424
+ cells.push(sides.map(([g, side]) => head(g, side)).join(''));
425
+ for (let i = 0; i < Math.max(left.rows.length, right.rows.length); i++) {
426
+ for (const [group, side] of sides) {
427
+ const row = group.rows[i];
428
+ if (!row) continue;
429
+ const sep = row.combo ? '<span class="sc-plus">+</span>' : '<span class="sc-or">/</span>';
430
+ const keys = row.keys.map((k) => `<kbd>${esc(k)}</kbd>`).join(sep);
431
+ const cls = side + (group.hub ? ' sc-hub' : '');
432
+ cells.push(`<dt class="${esc(cls)}">${keys}</dt><dd class="${esc(cls)}">${esc(row.label)}</dd>`);
433
+ }
434
+ }
435
+ });
436
+ return cells.join('');
437
+ }
438
+
304
439
  function toggleHelpModal() {
440
+ const list = document.getElementById('helpShortcuts');
441
+ if (!list.childElementCount) list.innerHTML = buildHelpShortcuts();
442
+ list.classList.toggle('sc-standalone', !window.__HUB__?.enabled);
305
443
  document.getElementById('helpModal').classList.toggle('open');
306
444
  }
307
445
 
@@ -312,19 +450,6 @@ function openHealthView() {
312
450
  if (healthViewOpen) refreshAnalysis();
313
451
  }
314
452
 
315
- function bindModalKeys(inputId, modalId, submitFn) {
316
- document.getElementById(inputId).addEventListener('keydown', (e) => {
317
- if (e.key === 'Enter') {
318
- e.preventDefault();
319
- submitFn();
320
- }
321
- if (e.key === 'Escape') {
322
- e.preventDefault();
323
- closeModal(modalId);
324
- }
325
- });
326
- }
327
-
328
453
  // #endregion MODAL
329
454
 
330
455
  // #region RENDER_TREE
@@ -1911,7 +2036,7 @@ document.addEventListener('keydown', (e) => {
1911
2036
  if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
1912
2037
  const modal = document.querySelector('.modal-overlay.open');
1913
2038
  if (modal) {
1914
- if (e.key === 'Escape') {
2039
+ if (e.key === 'Escape' || (e.key === '?' && modal.id === 'helpModal')) {
1915
2040
  modal.classList.remove('open');
1916
2041
  e.preventDefault();
1917
2042
  }
@@ -2116,7 +2241,7 @@ window.addEventListener('popstate', (e) => {
2116
2241
  document.addEventListener('DOMContentLoaded', async () => {
2117
2242
  loadTheme();
2118
2243
  initResize();
2119
- bindModalKeys('projectPathInput', 'projectPickerModal', submitProjectPicker);
2244
+ initProjectPicker();
2120
2245
  // Handle ?project= query param, else restore last-used project from localStorage
2121
2246
  const params = new URLSearchParams(location.search);
2122
2247
  let desiredProject = params.get('project');
package/public/index.html CHANGED
@@ -100,15 +100,21 @@
100
100
  <button class="modal-close" onclick="closeModal('projectPickerModal')">&#10005;</button>
101
101
  </div>
102
102
  <div class="modal-body">
103
- <div class="modal-field">
104
- <label>Project directory</label>
105
- <input type="text" id="projectPathInput" placeholder="/path/to/project" autocomplete="off">
106
- <span class="modal-hint">Full path to the project directory</span>
103
+ <div class="picker-filter" id="projectPickerFilter">
104
+ <input type="text" id="projectPickerInput" class="picker-input" placeholder="Filter projects..." autocomplete="off">
105
+ <div class="picker-list" id="projectPickerList"></div>
106
+ <button class="picker-add" id="projectPickerAdd">+ Add path...</button>
107
107
  </div>
108
- <div id="recentProjectsList"></div>
109
- <div class="modal-actions">
110
- <button class="action-btn" onclick="closeModal('projectPickerModal')">Cancel</button>
111
- <button class="action-btn primary" id="projectPickerSubmit" onclick="submitProjectPicker()">Switch</button>
108
+ <div class="picker-add-form" id="projectPickerAddForm" hidden>
109
+ <div class="modal-field">
110
+ <label>Project directory</label>
111
+ <input type="text" id="projectPathInput" placeholder="/path/to/project" autocomplete="off">
112
+ <span class="modal-hint">Full path to the project directory</span>
113
+ </div>
114
+ <div class="modal-actions">
115
+ <button class="action-btn" id="projectPickerBack">Back</button>
116
+ <button class="action-btn primary" id="projectPickerSubmit" onclick="submitProjectPicker()">Switch</button>
117
+ </div>
112
118
  </div>
113
119
  </div>
114
120
  </div>
@@ -152,7 +158,7 @@
152
158
 
153
159
  <!-- Help modal -->
154
160
  <div class="modal-overlay" id="helpModal" onclick="if(event.target===this)toggleHelpModal()">
155
- <div class="modal">
161
+ <div class="modal modal-help">
156
162
  <div class="modal-header">
157
163
  <h3>Memory Diagnoser</h3>
158
164
  <button class="modal-close" onclick="toggleHelpModal()">&#10005;</button>
@@ -161,31 +167,8 @@
161
167
  <p class="modal-desc">Browse and understand all memory sources that influence Claude Code behavior for your project.</p>
162
168
  <hr class="modal-divider">
163
169
  <h4 class="shortcut-section">Keyboard Shortcuts</h4>
164
- <div class="shortcut-grid">
165
- <div>
166
- <h4 class="shortcut-section">Navigation</h4>
167
- <table class="shortcut-table">
168
- <tr><td><kbd>j</kbd> / <kbd>&darr;</kbd></td><td>Next item</td></tr>
169
- <tr><td><kbd>k</kbd> / <kbd>&uarr;</kbd></td><td>Previous item</td></tr>
170
- <tr><td><kbd>l</kbd> / <kbd>&rarr;</kbd></td><td>Expand / enter child</td></tr>
171
- <tr><td><kbd>h</kbd> / <kbd>&larr;</kbd></td><td>Collapse / parent</td></tr>
172
- <tr><td><kbd>Enter</kbd></td><td>Select item</td></tr>
173
- <tr><td><kbd>Esc</kbd></td><td>Close modal</td></tr>
174
- </table>
175
- </div>
176
- <div>
177
- <h4 class="shortcut-section">Actions</h4>
178
- <table class="shortcut-table">
179
- <tr><td><kbd>r</kbd></td><td>Refresh data</td></tr>
180
- <tr><td><kbd>t</kbd></td><td>Toggle theme</td></tr>
181
- <tr><td><kbd>e</kbd></td><td>Open in editor</td></tr>
182
- <tr><td><kbd>Shift+P</kbd></td><td>Switch project</td></tr>
183
- <tr><td><kbd>?</kbd></td><td>Show this help</td></tr>
184
- </table>
185
- </div>
186
- </div>
170
+ <dl class="shortcuts" id="helpShortcuts"></dl>
187
171
  </div>
188
- <button class="modal-close-btn" onclick="toggleHelpModal()">Close</button>
189
172
  </div>
190
173
  </div>
191
174
 
package/public/style.css CHANGED
@@ -805,6 +805,24 @@ body.light .tree-conditional .file-name {
805
805
  max-height: 80vh;
806
806
  overflow-y: auto;
807
807
  }
808
+ /* The paired shortcut columns need room the 440px default cannot give. */
809
+ .modal.modal-help {
810
+ width: 960px;
811
+ }
812
+ /* Matches the roomier help modals in the other hub apps: larger title, 24px gutters. */
813
+ .modal-help .modal-header {
814
+ padding: 20px 24px;
815
+ }
816
+ .modal-help .modal-header h3 {
817
+ font-family: var(--font-mono);
818
+ font-size: 18px;
819
+ }
820
+ .modal-help .modal-body {
821
+ padding: 24px;
822
+ }
823
+ .modal-help .modal-desc {
824
+ font-size: 13px;
825
+ }
808
826
  .modal.modal-wide {
809
827
  width: 1140px;
810
828
  }
@@ -878,21 +896,6 @@ body.light .tree-conditional .file-name {
878
896
  border-top: 1px solid var(--border);
879
897
  margin: 12px 0;
880
898
  }
881
- .modal-close-btn {
882
- display: block;
883
- width: calc(100% - 32px);
884
- margin: 0 16px 16px;
885
- padding: 8px;
886
- background: var(--bg-elevated);
887
- border: 1px solid var(--border);
888
- border-radius: 4px;
889
- color: var(--text-secondary);
890
- cursor: pointer;
891
- font-size: 12px;
892
- }
893
- .modal-close-btn:hover {
894
- background: var(--bg-hover);
895
- }
896
899
  /* matches the marketplace app's action-btn look */
897
900
  .action-btn {
898
901
  padding: 4px 10px;
@@ -961,19 +964,90 @@ body.light .tree-conditional .file-name {
961
964
  margin-bottom: 6px;
962
965
  text-transform: uppercase;
963
966
  }
964
- .shortcut-grid {
967
+ /* One shared grid for both halves (key | label | spacer | key | label): each key
968
+ gutter is sized by its column's widest combo, and left/right rows share row
969
+ tracks, so paired groups line up — a shorter group just leaves empty rows.
970
+ Cells opt into a half via sc-l / sc-r; the markup is generated from
971
+ SHORTCUT_PAIRS in app.js. */
972
+ .shortcuts {
973
+ margin: 0;
965
974
  display: grid;
966
- grid-template-columns: 1fr 1fr;
967
- gap: 16px;
975
+ grid-template-columns: max-content 1fr 28px max-content 1fr;
976
+ column-gap: 14px;
968
977
  }
969
- .shortcut-table {
970
- font-size: 12px;
978
+ .shortcuts dt.sc-l {
979
+ grid-column: 1;
980
+ }
981
+ .shortcuts dd.sc-l {
982
+ grid-column: 2;
983
+ }
984
+ .shortcuts dt.sc-r {
985
+ grid-column: 4;
986
+ }
987
+ .shortcuts dd.sc-r {
988
+ grid-column: 5;
989
+ }
990
+ .shortcuts .sc-group.sc-l {
991
+ grid-column: 1 / 3;
992
+ }
993
+ .shortcuts .sc-group.sc-r {
994
+ grid-column: 4 / 6;
995
+ }
996
+ .shortcuts dt {
997
+ margin: 0;
998
+ display: flex;
999
+ align-items: center;
1000
+ align-self: center;
1001
+ gap: 4px;
1002
+ padding: 3px 0;
1003
+ }
1004
+ .shortcuts dd {
1005
+ margin: 0;
1006
+ /* The other hub apps set a mono body font; memory is sans, so the help rows
1007
+ opt into mono to keep the shortcut tables reading the same across apps. */
1008
+ font-family: var(--font-mono);
1009
+ font-size: 13px;
1010
+ color: var(--text-secondary);
1011
+ align-self: center;
1012
+ padding: 3px 0;
1013
+ }
1014
+ /* Section headings carry the column: full-width rule + primary ink, so the eye
1015
+ finds the group before it reads any single row. */
1016
+ .shortcuts .sc-group {
1017
+ padding: 18px 0 6px;
1018
+ margin-bottom: 6px;
1019
+ font-size: 11px;
1020
+ font-weight: 700;
1021
+ letter-spacing: 0.16em;
1022
+ text-transform: uppercase;
1023
+ color: var(--text-primary);
1024
+ border-bottom: 1px solid var(--border);
971
1025
  }
972
- .shortcut-table td {
973
- padding: 2px 0;
1026
+ .shortcuts .sc-group.sc-first {
1027
+ padding-top: 2px;
974
1028
  }
975
- .shortcut-table td:first-child {
976
- padding-right: 12px;
1029
+ /* keycap chips: thicker bottom border reads as a key */
1030
+ .shortcuts kbd {
1031
+ display: inline-flex;
1032
+ align-items: center;
1033
+ justify-content: center;
1034
+ min-width: 22px;
1035
+ height: 22px;
1036
+ padding: 0 6px;
1037
+ box-sizing: border-box;
1038
+ color: var(--text-primary);
1039
+ border-bottom-width: 2px;
1040
+ border-radius: 5px;
1041
+ }
1042
+ .shortcuts .sc-plus,
1043
+ .shortcuts .sc-or {
1044
+ color: var(--text-muted);
1045
+ font-size: 11px;
1046
+ }
1047
+ /* Hub keys do nothing outside Claude Code Hub, so the standalone app hides that
1048
+ whole column rather than listing keys that no-op. */
1049
+ .shortcuts.sc-standalone .sc-hub {
1050
+ display: none;
977
1051
  }
978
1052
  kbd {
979
1053
  background: var(--bg-hover);
@@ -989,31 +1063,77 @@ kbd {
989
1063
  font-size: 10px;
990
1064
  line-height: 1;
991
1065
  }
992
- .recent-projects-label {
993
- font-size: 11px;
994
- color: var(--text-muted);
995
- margin: 10px 0 4px;
1066
+ .picker-filter,
1067
+ .picker-add-form {
1068
+ display: flex;
1069
+ flex-direction: column;
1070
+ gap: 10px;
1071
+ }
1072
+ .picker-filter[hidden],
1073
+ .picker-add-form[hidden] {
1074
+ display: none;
1075
+ }
1076
+ .picker-input {
1077
+ width: 100%;
1078
+ padding: 8px 10px;
1079
+ background: var(--bg-deep);
1080
+ border: 1px solid var(--border);
1081
+ border-radius: 4px;
1082
+ color: var(--text-primary);
1083
+ font-family: var(--font-mono);
1084
+ font-size: 13px;
1085
+ }
1086
+ .picker-input:focus {
1087
+ outline: none;
1088
+ border-color: var(--accent);
996
1089
  }
997
- .recent-project-item {
1090
+ .picker-list {
1091
+ max-height: 50vh;
1092
+ overflow-y: auto;
998
1093
  display: flex;
999
- align-items: center;
1094
+ flex-direction: column;
1095
+ gap: 2px;
1096
+ }
1097
+ .picker-row {
1098
+ display: flex;
1099
+ align-items: baseline;
1100
+ gap: 8px;
1000
1101
  padding: 5px 8px;
1102
+ border: 1px solid transparent;
1001
1103
  border-radius: 4px;
1002
1104
  cursor: pointer;
1003
1105
  font-size: 12px;
1004
1106
  font-family: var(--font-mono);
1005
1107
  color: var(--text-secondary);
1006
1108
  }
1007
- .recent-project-item:hover {
1109
+ .picker-row:hover {
1008
1110
  background: var(--bg-hover);
1111
+ color: var(--text-primary);
1112
+ }
1113
+ .picker-row.selected {
1114
+ background: var(--bg-hover);
1115
+ border-color: var(--accent);
1116
+ color: var(--text-primary);
1009
1117
  }
1010
- .recent-project-item span {
1118
+ .picker-name {
1119
+ white-space: nowrap;
1120
+ }
1121
+ .picker-parent {
1011
1122
  flex: 1;
1123
+ min-width: 0;
1012
1124
  overflow: hidden;
1013
1125
  text-overflow: ellipsis;
1014
1126
  white-space: nowrap;
1127
+ font-size: 11px;
1128
+ color: var(--text-muted);
1129
+ }
1130
+ .picker-tag {
1131
+ font-size: 10px;
1132
+ text-transform: uppercase;
1133
+ letter-spacing: 0.5px;
1134
+ color: var(--accent-text);
1015
1135
  }
1016
- .recent-project-remove {
1136
+ .picker-remove {
1017
1137
  background: none;
1018
1138
  border: none;
1019
1139
  color: var(--text-muted);
@@ -1022,9 +1142,33 @@ kbd {
1022
1142
  padding: 0 4px;
1023
1143
  opacity: 0;
1024
1144
  }
1025
- .recent-project-item:hover .recent-project-remove {
1145
+ .picker-row:hover .picker-remove,
1146
+ .picker-row.selected .picker-remove {
1026
1147
  opacity: 1;
1027
1148
  }
1149
+ .picker-remove:hover {
1150
+ color: var(--error);
1151
+ }
1152
+ .picker-empty {
1153
+ padding: 12px 10px;
1154
+ color: var(--text-muted);
1155
+ font-size: 12px;
1156
+ }
1157
+ .picker-add {
1158
+ width: 100%;
1159
+ text-align: left;
1160
+ background: none;
1161
+ border: none;
1162
+ border-top: 1px solid var(--border);
1163
+ padding: 8px 2px 2px;
1164
+ color: var(--text-muted);
1165
+ cursor: pointer;
1166
+ font-family: inherit;
1167
+ font-size: 12px;
1168
+ }
1169
+ .picker-add:hover {
1170
+ color: var(--accent-text);
1171
+ }
1028
1172
  /* #endregion MODAL */
1029
1173
 
1030
1174
  /* #region LOADING */
@@ -1131,8 +1275,12 @@ body.light {
1131
1275
  .summary-bar {
1132
1276
  flex-wrap: wrap;
1133
1277
  }
1134
- .shortcut-grid {
1135
- grid-template-columns: 1fr;
1278
+ /* Rows are interleaved left-then-right, so a one-column stack would read out
1279
+ of order. Keep both halves and just tighten the gutters. */
1280
+ .shortcuts {
1281
+ grid-template-columns: max-content 1fr 12px max-content 1fr;
1282
+ column-gap: 8px;
1283
+ font-size: 11px;
1136
1284
  }
1137
1285
  }
1138
1286
  /* #endregion RESPONSIVE */