Skip to content

feat(web): promote memory_dirs widget to Sources tab (closes #297)#300

Merged
memtomem merged 4 commits intomainfrom
feat/memory-dirs-to-sources-tab
Apr 19, 2026
Merged

feat(web): promote memory_dirs widget to Sources tab (closes #297)#300
memtomem merged 4 commits intomainfrom
feat/memory-dirs-to-sources-tab

Conversation

@memtomem
Copy link
Copy Markdown
Owner

Summary

Closes #297. Promotes the memory_dirs management widget from the Config tab's Indexing card to a full-width panel at the top of the Sources tab.

  • UI: Paths no longer truncate (escaped the 2-column config-table cell) 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.
  • Backend: Unchanged. Same endpoints (/api/memory-dirs/{status,add,remove} + /api/{index,reindex}), same memory_dir_stats shape. This is a pure UI relocation.

Changes

  • New module web/static/sources-memory-dirs.js (extracted _buildMemoryDirsWidget from settings-config.js, renamed entry point renderMemoryDirsPanel()).
  • index.html: <div id="memory-dirs-panel"> inserted above the sources layout; new <script> tag; cache-version bumps on app.js (69 → 70) and settings-config.js (2 → 3).
  • app.js: activate panel on Sources tab. Also folds the initial hash-based activateTab() into the i18n-aware DOMContentLoaded handler — the two handlers previously didn't chain, so JS-built widgets calling t() at build time (like this panel) rendered raw keys on a direct #sources load.
  • settings-config.js: removes the custom widget registration (3 sites — _CONFIG_LABELS, _NO_RESET_FIELDS, _CONFIG_CUSTOM_WIDGETS) and adds a _HIDDEN_CONFIG_FIELDS set so the render loop skips indexing.memory_dirs entirely (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.
  • i18n: renames 14 settings.memory_dirs.* keys → sources.memory_dirs.*, adds sources.memory_dirs.title + two settings.memory_dirs.moved_notice* keys for the breadcrumb. toast.memory_dir.* and confirm.memory_dir_remove_* stay — action-shaped, location-agnostic. en/ko parity preserved (test_i18n.py).
  • style.css: .memory-dirs-panel wrapper, .memory-dirs-header, .config-breadcrumb rules. Existing .memory-dirs-* classes untouched.
  • config.py: annotates _detect_provider_dirs with 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

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.
  • Browser smoke (real config, 29 dirs across 4 categories):
    • Sources tab: panel renders at top; user category auto-expanded, provider categories collapsed; per-dir (N chunks) / (not indexed) badges load; Codex dir flagged red for 0 chunks (expected).
    • Config tab → Indexing card: memory_dirs no 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.
    • No console errors.

🤖 Generated with Claude Code

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.
@memtomem memtomem merged commit 0d2790b into main Apr 19, 2026
7 checks passed
@memtomem memtomem deleted the feat/memory-dirs-to-sources-tab branch April 19, 2026 15:52
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Promote memory_dirs management to Sources tab (follow-up to #295)

2 participants