feat(web): promote memory_dirs widget to Sources tab (closes #297)#300
Merged
feat(web): promote memory_dirs widget to Sources tab (closes #297)#300
Conversation
Move the ``memory_dirs`` management widget out of the Config tab's
Indexing card (where it sat inside a 2-column ``config-table`` grid that
truncated paths and fought with the batched Save/dirty semantics of the
rest of the card) into its own full-width panel at the top of the
Sources tab. The widget is action-based and persists each add / remove /
reindex immediately — that model fits the Sources tab's "things already
on disk" framing rather than the Config tab's "stage edits, press Save"
model.
Backend endpoints are unchanged: the panel calls the same
``/api/memory-dirs/{status,add,remove}`` + ``/api/{index,reindex}``
routes the old widget did.
Changes
-------
- New ``web/static/sources-memory-dirs.js`` with ``renderMemoryDirsPanel()``
+ ``_buildMemoryDirsPanel()``. Adapted from ``_buildMemoryDirsWidget``
in ``settings-config.js`` (now deleted). ``_categorizeMemoryDir`` and
the ``_MEMORY_DIR_CATEGORY_*`` constants move with it.
- ``index.html``: add ``#memory-dirs-panel`` container at the top of the
Sources tab and a ``<script>`` tag for the new module. Bumped cache
version on ``app.js`` (69 → 70) and ``settings-config.js`` (2 → 3).
- ``app.js``: call ``renderMemoryDirsPanel()`` when the Sources tab
activates. The hash-based initial ``activateTab()`` moves into the
i18n-aware ``DOMContentLoaded`` handler so ``t()`` calls in JS-built
widgets (like this panel) run after the locale cache is populated —
previously the two ``DOMContentLoaded`` handlers didn't chain, causing
raw-key flashes on direct ``#sources`` navigation.
- ``settings-config.js``: remove ``memory_dirs`` from ``_CONFIG_LABELS``,
drop its ``_NO_RESET_FIELDS`` entry, delete ``_CONFIG_CUSTOM_WIDGETS``
registration, and introduce ``_HIDDEN_CONFIG_FIELDS`` so the render
loop skips ``indexing.memory_dirs`` entirely (prevents a fallback to
the generic array input that ``_buildConfigInput`` would otherwise
produce). Append a breadcrumb below the Indexing card pointing users
who look in the old location over to the Sources tab.
- i18n: rename 14 ``settings.memory_dirs.*`` keys to
``sources.memory_dirs.*`` (all client references updated) and add a
new ``sources.memory_dirs.title`` for the panel heading plus two
``settings.memory_dirs.moved_notice*`` keys for the breadcrumb. en/ko
parity preserved (``test_i18n.py`` covers). ``toast.memory_dir.*`` and
``confirm.memory_dir_remove_*`` keys stay — they are action-shaped
and location-agnostic.
- ``style.css``: add ``.memory-dirs-panel`` wrapper + ``.memory-dirs-header``
+ ``.memory-dirs-title`` + ``.config-breadcrumb`` rules. Existing
``.memory-dirs-*`` classes unchanged; full-width parent container
resolves the truncation ellipsis for realistic absolute paths.
- ``config.py``: annotate ``_detect_provider_dirs`` with a drift-mirror
warning pointing at the client copy and at #299 (follow-up to
consolidate categorization into a server-returned ``category`` field
on ``/api/memory-dirs/status``).
Out of scope / follow-up
------------------------
- #299: consolidate ``_detect_provider_dirs`` and
``_categorizeMemoryDir`` into one server-side source of truth.
- Per-dir enrichment (last-indexed timestamp, file/chunk totals, stale
badges) mentioned in #297 is held for a separate PR so backend
schema changes don't ride on a pure UI relocation.
Verification
------------
- ``uv run pytest -m "not ollama"``: 1845 passed, 46 deselected.
- ``uv run ruff check packages/memtomem/src && ruff format --check``:
clean, 193 files.
- Browser smoke (real config, 29 dirs across 4 categories):
- Sources tab: panel renders at top, ``user`` category expanded
(1 dir), the three provider categories collapsed (26 / 1 / 1
entries), per-dir ``(N chunks)`` / ``(not indexed)`` badges load,
Codex dir surfaces in red because 0 chunks (expected — drive not
mounted at smoke time).
- Config tab: Indexing card no longer lists ``memory_dirs`` as a
field; breadcrumb row renders at the bottom and click on it
switches to the Sources tab.
- ``/api/memory-dirs/{add,remove}`` round-trip under the new module:
200 / entry appears in ``memory_dirs`` / 200 / entry removed.
- No console errors.
The ``/locales/{en,ko}.json`` files have no version token in their URL,
so any browser that cached them from a previous session keeps serving
the stale copy across upgrades. When a release renames or adds i18n
keys (as this PR does — ``settings.memory_dirs.*`` →
``sources.memory_dirs.*``), ``t()`` falls through to the raw-key
fallback for every affected string until the user hard-reloads.
Passing ``{cache: 'no-store'}`` to the locale fetch bypasses the HTTP
cache unconditionally. The files are ~20 KB each and only loaded once
per language per page load, so the bandwidth cost is negligible next
to the "raw keys everywhere after upgrade" failure mode.
Bumped ``/i18n.js`` cache token (v=1 → v=2) so the fix reaches users
without requiring a manual hard reload on first page visit.
Tightens the visual hierarchy of the Sources-tab Memory Dirs panel and
replaces icon-only action buttons with explicit text labels so the
intent is readable without hovering.
Layout
------
- Single-row header: title · total count ("29 dirs") · [+ Add path]
[Reindex all]. The total is always visible so users stay oriented
when every group is collapsed.
- Inline add-path form, opened via "+ Add path" header toggle. Replaces
the permanent bottom row — the form now appears between header and
groups with [Add] [Cancel] buttons and ``Esc``/``Enter`` shortcuts.
- Categorized groups use flat separators instead of boxed cards; the
caret (▸/▾) rotates on open, summary rows have a subtle hover tint,
and the group count sits in a small pill next to the label.
- Summary stats ("338 files · 724 chunks") right-align inside each
group header so the scannable columns are: label | count | stats |
action. Red for empty groups.
- Dir rows: hover-revealed action column, monospace path (left, 1fr,
ellipsis), pill status badge, then [Index]/[Delete] text buttons.
Default opacity 0.45 → 1 on hover so a 26-dir Claude group doesn't
read as button noise.
- Panel has no inner padding at the root; each sub-section carries its
own padding so the border-bottom lines extend full-width.
Actions
-------
Per-dir and per-group action buttons switch from icon glyphs to text:
- ``↻`` (per-dir reindex) → **Index**
- ``✕`` (per-dir remove) → **Delete**
- ``↻`` (group reindex) → **Reindex**
- ``↻ Reindex all`` (header) drops the leading glyph — label alone is
clearer and the ``btn`` styling already reads as a button.
Tooltips (``title`` attrs) keep the longer descriptions for
screen readers and hover-probing. ``aria-label`` preserved on delete.
i18n
----
New keys (en/ko parity covered by ``test_i18n.py``):
- ``sources.memory_dirs.total_one``/``total_many``
- ``sources.memory_dirs.add_btn`` → "+ Add path" / "+ 경로 추가"
- ``sources.memory_dirs.add_submit`` / ``add_cancel``
- ``sources.memory_dirs.action_index`` / ``action_delete`` /
``action_reindex_group``
- ``sources.memory_dirs.reindex_all`` text updated (dropped ``↻``
prefix)
Verification
------------
- ``uv run pytest packages/memtomem/tests/test_i18n.py`` — 11 passed.
- ``uv run ruff check packages/memtomem/src`` — clean.
- Browser smoke (29 dirs, 4 categories, ko locale): panel header
compact, category collapsed states preserve stats, hover-revealed
action labels readable, add-path toggle opens inline form with
focus trap and ``Esc``/``Enter`` shortcuts working.
Cache-version bumped on ``/sources-memory-dirs.js`` (1 → 2).
The Sources tab is a flex column (``#tab-sources`` uses ``display: flex; flex-direction: column`` via ``.tab-panel``), and ``.sources-layout`` asks for ``height: 100%``. With the default ``flex-shrink: 1`` and no explicit ``flex-basis`` on the Memory Dirs panel, that sibling's 100% demand won the flex bargaining round at desktop viewports — the panel got shrunk from its ~243px content height down to 2px, making it effectively invisible. Opening DevTools changed the viewport during layout recalc, which is why the panel would flash back into view while DevTools was open and vanish again when it was closed. Pinning ``flex-shrink: 0`` on ``.memory-dirs-panel`` keeps it at its content height regardless of what ``.sources-layout`` demands. The existing ``overflow: hidden`` on the panel + rounded corners + border visuals still apply — only the flex sizing contract changes. Verified at 1920x1080: panel.getBoundingClientRect().height is now 243px (previously 2px); all four category groups, the header actions, and the sources list below all render correctly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #297. Promotes the
memory_dirsmanagement widget from the Config tab's Indexing card to a full-width panel at the top of the Sources tab.config-tablecell) and the immediate-persist action model (add / remove / reindex each hit the server) sits alongside the other "things on disk" UI rather than fighting the Config tab's batched Save flow./api/memory-dirs/{status,add,remove}+/api/{index,reindex}), samememory_dir_statsshape. This is a pure UI relocation.Changes
web/static/sources-memory-dirs.js(extracted_buildMemoryDirsWidgetfromsettings-config.js, renamed entry pointrenderMemoryDirsPanel()).index.html:<div id="memory-dirs-panel">inserted above the sources layout; new<script>tag; cache-version bumps onapp.js(69 → 70) andsettings-config.js(2 → 3).app.js: activate panel on Sources tab. Also folds the initial hash-basedactivateTab()into the i18n-awareDOMContentLoadedhandler — the two handlers previously didn't chain, so JS-built widgets callingt()at build time (like this panel) rendered raw keys on a direct#sourcesload.settings-config.js: removes the custom widget registration (3 sites —_CONFIG_LABELS,_NO_RESET_FIELDS,_CONFIG_CUSTOM_WIDGETS) and adds a_HIDDEN_CONFIG_FIELDSset so the render loop skipsindexing.memory_dirsentirely (prevents a fallback to the generic array input in_buildConfigInput). Appends a breadcrumb row under the Indexing card: "Memory directories are managed in the Sources tab →" — click switches tabs.settings.memory_dirs.*keys →sources.memory_dirs.*, addssources.memory_dirs.title+ twosettings.memory_dirs.moved_notice*keys for the breadcrumb.toast.memory_dir.*andconfirm.memory_dir_remove_*stay — action-shaped, location-agnostic. en/ko parity preserved (test_i18n.py).style.css:.memory-dirs-panelwrapper,.memory-dirs-header,.config-breadcrumbrules. Existing.memory-dirs-*classes untouched.config.py: annotates_detect_provider_dirswith a drift-mirror warning pointing at the client copy and at Consolidate memory_dir categorization: server-returned field instead of client mirror #299.Out of scope / follow-up
_detect_provider_dirs(server) and_categorizeMemoryDir(client) into a single server-side source of truth by returningcategoryon/api/memory-dirs/status.Test plan
uv run pytest -m "not ollama"— 1845 passed, 46 deselected.uv run ruff check packages/memtomem/src && ruff format --check— clean, 193 files.usercategory auto-expanded, provider categories collapsed; per-dir(N chunks)/(not indexed)badges load; Codex dir flagged red for 0 chunks (expected).memory_dirsno longer appears as a field; breadcrumb renders below the table and its link switches to Sources./api/memory-dirs/{add,remove}round-trip from the new module: 200 / entry appears / 200 / entry removed.🤖 Generated with Claude Code