claude-code-memory-explorer 0.7.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-memory-explorer",
3
- "version": "0.7.0",
3
+ "version": "0.9.1",
4
4
  "description": "Memory explorer dashboard for Claude Code",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -137,7 +137,7 @@ function buildThemeMenu() {
137
137
  const menu = document.getElementById('themeMenu');
138
138
  menu.innerHTML = COLOR_THEMES.map(
139
139
  ([id, label]) =>
140
- `<button type="button" class="theme-menu-item" data-theme-id="${id}"
140
+ `<button type="button" class="theme-menu-item theme-swatch-${id}" data-theme-id="${id}"
141
141
  onclick="event.stopPropagation(); setColorTheme('${id}'); toggleThemeMenu()">
142
142
  <span class="theme-swatch theme-swatch-${id}"><i class="sw-bg"></i><i class="sw-accent"></i><i class="sw-ink"></i></span>${label}
143
143
  </button>`,
@@ -194,6 +194,20 @@ async function loadProject() {
194
194
  document.getElementById('projectBtn').title = projectData.path;
195
195
  }
196
196
 
197
+ // Shared by the project picker, the boot restore, and the hub project shim. Throws with the
198
+ // server's error message so callers can surface it directly.
199
+ async function putProject(dirPath) {
200
+ const res = await fetch('/api/project', {
201
+ method: 'PUT',
202
+ headers: { 'Content-Type': 'application/json' },
203
+ body: JSON.stringify({ path: dirPath }),
204
+ });
205
+ if (!res.ok) {
206
+ const err = await res.json().catch(() => ({}));
207
+ throw new Error(err.error || `${res.status}`);
208
+ }
209
+ }
210
+
197
211
  function changeProject() {
198
212
  const current = document.getElementById('projectBtn').title;
199
213
  document.getElementById('projectPathInput').value = current;
@@ -209,20 +223,10 @@ async function submitProjectPicker() {
209
223
  btn.disabled = true;
210
224
  btn.textContent = 'Switching...';
211
225
  try {
212
- const res = await fetch('/api/project', {
213
- method: 'PUT',
214
- headers: { 'Content-Type': 'application/json' },
215
- body: JSON.stringify({ path: dirPath }),
216
- });
217
- if (!res.ok) {
218
- const err = await res.json();
219
- showToast(err.error, 'error');
220
- return;
221
- }
226
+ await putProject(dirPath);
222
227
  closeModal('projectPickerModal');
223
228
  addRecentProject(dirPath);
224
- await loadProject();
225
- await loadData();
229
+ await Promise.all([loadProject(), loadData()]);
226
230
  showToast('Project switched', 'success');
227
231
  } catch (err) {
228
232
  showToast(err.message, 'error');
@@ -977,14 +981,24 @@ document.addEventListener('keydown', (e) => {
977
981
  .catch(() => ({}));
978
982
  if (!cfg.enabled) return;
979
983
  window.__HUB__ = cfg;
984
+ const fwd = (e) =>
985
+ window.parent?.postMessage(
986
+ { type: 'hub:keydown', key: e.key, ctrl: e.ctrlKey, alt: e.altKey, shift: e.shiftKey },
987
+ '*',
988
+ );
980
989
  document.addEventListener('keydown', (e) => {
981
990
  if (e.ctrlKey && e.altKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) {
982
991
  e.preventDefault();
983
- window.parent?.postMessage({ type: 'hub:keydown', key: e.key }, '*');
992
+ fwd(e);
993
+ }
994
+ // Its own branch: the Alt+digit case below requires !ctrlKey.
995
+ if (e.ctrlKey && e.altKey && !e.shiftKey && !e.metaKey && e.key.toLowerCase() === 'p') {
996
+ e.preventDefault();
997
+ fwd(e);
984
998
  }
985
999
  if (e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey && /^[1-9]$/.test(e.key)) {
986
1000
  e.preventDefault();
987
- window.parent?.postMessage({ type: 'hub:keydown', key: e.key }, '*');
1001
+ fwd(e);
988
1002
  }
989
1003
  });
990
1004
  })();
@@ -994,10 +1008,12 @@ function _hubNavigate(app, url) {
994
1008
  window.parent?.postMessage({ type: 'hub:navigate', app, url }, '*');
995
1009
  }
996
1010
 
1011
+ // Hoisted out of initHubTheme so initHubProject can share it.
1012
+ const hubOrigin = () => (window.__HUB__?.url ? new URL(window.__HUB__.url).origin : null);
1013
+
997
1014
  (function initHubTheme() {
998
1015
  const getTheme = () => (document.body.classList.contains('light') ? 'light' : 'dark');
999
1016
  const getColorTheme = () => document.body.dataset.colorTheme || 'ember';
1000
- const hubOrigin = () => (window.__HUB__?.url ? new URL(window.__HUB__.url).origin : null);
1001
1017
  // lastTheme/lastColorTheme are updated synchronously when applying a hub
1002
1018
  // message, so the (async) observer sees no diff and doesn't echo it back.
1003
1019
  let lastTheme = getTheme();
@@ -1028,6 +1044,36 @@ function _hubNavigate(app, url) {
1028
1044
  });
1029
1045
  })();
1030
1046
 
1047
+ // Set synchronously when the hub pushes a project, so the DOMContentLoaded restore below can't
1048
+ // let a stale localStorage recent win the race against the hub's choice.
1049
+ let hubProjectPath = null;
1050
+
1051
+ (function initHubProject() {
1052
+ let lastApplied = null;
1053
+ window.addEventListener('message', async (e) => {
1054
+ if (e.source !== window.parent || e.origin !== hubOrigin()) return;
1055
+ if (e.data?.type !== 'hub:project') return;
1056
+ const dirPath = e.data.project;
1057
+ if (typeof dirPath !== 'string' || !dirPath) return;
1058
+ hubProjectPath = dirPath;
1059
+ // Dedupes against the last applied value, not just an in-flight one: the hub re-posts on
1060
+ // every iframe load, so without this each load would PUT and reload twice.
1061
+ if (lastApplied === dirPath) return;
1062
+ lastApplied = dirPath;
1063
+ try {
1064
+ await putProject(dirPath);
1065
+ // The hub does not persist its own scope, so a hub-pushed project must become the recent
1066
+ // head here: after a hard refresh the boot path PUTs getRecentProjects()[0], which would
1067
+ // otherwise clobber the server's hub-scoped project with the previous one.
1068
+ addRecentProject(dirPath);
1069
+ await Promise.all([loadProject(), loadData()]);
1070
+ } catch (err) {
1071
+ lastApplied = null;
1072
+ console.warn('hub:project failed:', err.message);
1073
+ }
1074
+ });
1075
+ })();
1076
+
1031
1077
  // #endregion HUB_INTEGRATION
1032
1078
 
1033
1079
  // #region RESIZE
@@ -1086,16 +1132,21 @@ document.addEventListener('DOMContentLoaded', async () => {
1086
1132
  // Handle ?project= query param, else restore last-used project from localStorage
1087
1133
  const params = new URLSearchParams(location.search);
1088
1134
  let desiredProject = params.get('project');
1089
- if (!desiredProject) desiredProject = getRecentProjects()[0] || null;
1135
+ // A hub-pushed project outranks the localStorage recent: initHub() is fire-and-forget at script
1136
+ // eval, so hub:project can land before or during this block.
1137
+ if (!desiredProject) desiredProject = hubProjectPath || getRecentProjects()[0] || null;
1090
1138
  if (desiredProject) {
1091
1139
  try {
1092
- const res = await fetch('/api/project', {
1093
- method: 'PUT',
1094
- headers: { 'Content-Type': 'application/json' },
1095
- body: JSON.stringify({ path: desiredProject }),
1096
- });
1097
- if (!res.ok && params.has('project')) showToast('Failed to switch project', 'error');
1098
- } catch {}
1140
+ await putProject(desiredProject);
1141
+ } catch {
1142
+ if (params.has('project')) showToast('Failed to switch project', 'error');
1143
+ }
1144
+ // A push that arrived while the PUT above was in flight must still win.
1145
+ if (hubProjectPath && hubProjectPath !== desiredProject) {
1146
+ try {
1147
+ await putProject(hubProjectPath);
1148
+ } catch {}
1149
+ }
1099
1150
  }
1100
1151
  if (params.has('project')) {
1101
1152
  params.delete('project');
package/public/style.css CHANGED
@@ -202,8 +202,11 @@ body {
202
202
  background: var(--bg-hover);
203
203
  color: var(--text-primary);
204
204
  }
205
+ /* Highlighted with the row's *own* theme accent rather than the active theme's, so the selected
206
+ row previews the theme it names. --sw-accent comes from the theme-swatch-<id> class on the row. */
205
207
  .theme-menu-item.on {
206
- color: var(--accent-text);
208
+ color: var(--sw-accent, var(--accent-text));
209
+ background: color-mix(in srgb, var(--sw-accent, var(--accent-text)) 16%, transparent);
207
210
  }
208
211
  /* 3-dot palette preview: surface, accent, ink (colors from themes.css vars) */
209
212
  .theme-swatch {