diff --git a/static/panels.js b/static/panels.js
index 30d19c2b..4a76ae6e 100644
--- a/static/panels.js
+++ b/static/panels.js
@@ -4624,6 +4624,14 @@ function _memorySectionMtime(key) {
return _memoryData.memory_mtime || 0;
}
+function _memorySectionPath(key) {
+ if (!_memoryData) return '';
+ if (key === 'user') return _memoryData.user_path || '';
+ if (key === 'soul') return _memoryData.soul_path || '';
+ if (key === 'project_context') return _memoryData.project_context_path || '';
+ return _memoryData.memory_path || '';
+}
+
function _setMemoryHeaderButtons(mode) {
const header = $('mainMemory') && $('mainMemory').querySelector('.main-view-header');
const show = b => b && (b.style.display = '');
@@ -4727,8 +4735,10 @@ function _renderMemoryDetail(section) {
const mtime = _memorySectionMtime(section);
const mtimeStr = mtime ? new Date(mtime * 1000).toLocaleString() : '';
const mtimeHtml = mtimeStr ? `
${esc(mtimeStr)}
` : '';
- const path = section === 'project_context' && _memoryData ? (_memoryData.project_context_path || '') : '';
- const fileName = section === 'project_context' && _memoryData ? (_memoryData.project_context_name || (path.split(/[\\/]/).pop() || '')) : '';
+ const path = _memorySectionPath(section);
+ const fileName = section === 'project_context' && _memoryData
+ ? (_memoryData.project_context_name || (path.split(/[\\/]/).pop() || ''))
+ : (path.split(/[\\/]/).pop() || '');
const pathHtml = path ? `${esc(fileName)} · ${esc(path)}
` : '';
const shadowed = section === 'project_context' && _memoryData && Array.isArray(_memoryData.project_context_shadowed)
? _memoryData.project_context_shadowed
diff --git a/tests/test_3866_project_context_memory_section.py b/tests/test_3866_project_context_memory_section.py
index 83b4e6a0..b9375077 100644
--- a/tests/test_3866_project_context_memory_section.py
+++ b/tests/test_3866_project_context_memory_section.py
@@ -1,5 +1,7 @@
import json
import pathlib
+import shutil
+import subprocess
from types import SimpleNamespace
from urllib.parse import urlencode
@@ -8,6 +10,7 @@ import api.routes as routes
REPO_ROOT = pathlib.Path(__file__).parent.parent.resolve()
+NODE = shutil.which("node")
def project_context_for(workspace):
@@ -129,6 +132,108 @@ def test_memory_panel_defines_read_only_project_context_section():
assert "/api/memory?session_id=" in panels
+def test_memory_panel_references_all_memory_path_fields():
+ panels = (REPO_ROOT / "static" / "panels.js").read_text(encoding="utf-8")
+
+ assert "function _memorySectionPath(key)" in panels
+ assert "_memoryData.memory_path" in panels
+ assert "_memoryData.user_path" in panels
+ assert "_memoryData.soul_path" in panels
+ assert "_memoryData.project_context_path" in panels
+
+
+def _memory_render_blocks():
+ panels = (REPO_ROOT / "static" / "panels.js").read_text(encoding="utf-8")
+ helper_start = panels.index("function _memorySectionContent(key)")
+ helper_end = panels.index("function _setMemoryHeaderButtons", helper_start)
+ render_start = panels.index("function _renderMemoryDetail(section)")
+ render_end = panels.index("function _renderMemoryEdit", render_start)
+ return panels[helper_start:helper_end], panels[render_start:render_end]
+
+
+def _run_memory_render_harness():
+ helper_block, render_block = _memory_render_blocks()
+ script = (
+ "const helperBlock = "
+ + json.dumps(helper_block)
+ + ";\nconst renderBlock = "
+ + json.dumps(render_block)
+ + ";\n"
+ + r"""
+let _memoryData = {
+ memory: 'Primary memory body',
+ memory_path: 'C:/Users/Rod/.hermes/memories/MEMORY.md',
+ memory_mtime: 1712345678,
+ user: 'User memory body',
+ user_path: 'C:/Users/Rod/.hermes/memories/USER.md',
+ user_mtime: 1712345678,
+ soul: 'Soul memory body',
+ soul_path: 'C:/Users/Rod/.hermes/SOUL.md',
+ soul_mtime: 1712345678,
+ project_context: 'Project context body',
+ project_context_path: 'D:/Repos/hermes-webui/AGENTS.md',
+ project_context_name: 'AGENTS.md',
+ project_context_mtime: 1712345678,
+ project_context_shadowed: [{name: 'CLAUDE.md', shadowed_by: 'AGENTS.md'}],
+};
+let _memoryMode = '';
+const nodes = {
+ memoryDetailTitle: {textContent: '', style: {}},
+ memoryDetailBody: {innerHTML: '', style: {}},
+ memoryDetailEmpty: {style: {}},
+};
+function $(id) { return nodes[id] || null; }
+function _memorySectionMeta(section) {
+ return {key: section, label: section, emptyKey: section + '_empty'};
+}
+function _memorySectionLabel(meta) { return meta.label; }
+function _memorySectionEmpty(meta) { return meta.emptyKey; }
+function _setMemoryHeaderButtons() {}
+function renderMd(content) { return 'rendered:' + content; }
+function esc(value) {
+ return String(value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+eval(helperBlock + "\n" + renderBlock);
+_renderMemoryDetail('memory');
+const memoryHtml = nodes.memoryDetailBody.innerHTML;
+_renderMemoryDetail('project_context');
+const projectHtml = nodes.memoryDetailBody.innerHTML;
+console.log(JSON.stringify({memoryHtml, projectHtml, memoryMode: _memoryMode}));
+"""
+ )
+ completed = subprocess.run(
+ [NODE, "-e", script],
+ capture_output=True,
+ text=True,
+ timeout=30,
+ check=True,
+ )
+ return json.loads(completed.stdout)
+
+
+def test_memory_detail_renders_path_for_non_project_sections():
+ """Base-fails/head-passes regression for issue #4999.
+
+ On base, `_renderMemoryDetail('memory')` ignores `memory_path`, so the
+ rendered header omits the path row entirely. On head, the same render must
+ show `MEMORY.md · ` using the existing pinned header row pattern.
+ """
+ if NODE is None:
+ return
+
+ rendered = _run_memory_render_harness()
+
+ assert "MEMORY.md" in rendered["memoryHtml"]
+ assert "C:/Users/Rod/.hermes/memories/MEMORY.md" in rendered["memoryHtml"]
+ assert "AGENTS.md · D:/Repos/hermes-webui/AGENTS.md" in rendered["projectHtml"]
+ assert "CLAUDE.md present, shadowed by AGENTS.md" in rendered["projectHtml"]
+
+
def test_blank_session_workspace_does_not_resolve_to_server_cwd(monkeypatch):
# Regression for the empty-workspace guard: a session with a blank workspace
# (freshly-created/draft sessions) must return None rather than letting