Conversation
…er text (#581) Surfaces what `_resolve_namespace()` actually applies, end-to-end. The Index-tab input → indexing → result-display path looked coherent but broke asymmetrically on every leg: a placeholder that lied about the default, no echo of what was actually applied, and a backward-compat carve-out (`default_namespace == "default"` → untagged) invisible in the UI. The cluster gets one PR because UX writing drifts when the same surface is touched four times. 4.3 — Auto-NS preview echo + honest placeholder - `IndexEngine.discover_indexable_files()` (new) and `resolve_namespaces_for()` (new) — the public file-walk + namespace resolution helpers shared by both `index_path` and the new preview route, locking file-set parity at the helper layer. - `GET /api/index/preview-namespace?path=...&recursive=true` returns `{resolved_namespaces: list[str | None], truncated, scanned_files}`. Capped at 200 files so focus-event latency stays bounded on large memory_dirs; `truncated=true` flags the cap. - `IndexingStats.resolved_namespaces` and `IndexResponse` now carry the same list. `index_path_stream` complete event includes it too. - `index.html`: new `Namespace` row in `#index-result`. - `app.js`: shared `renderResolvedNamespaces()` honors 1/2/N elements and the truncated suffix; result-row populator uses it with `applied`. - `settings-config.js`: three event surfaces — namespace `focus` (immediate preview), path `input` (300ms-debounced, namespace-empty guard), namespace `input` (cache invalidation). Per-input late-arrival guard via `dataset.previewPath` so stale responses don't overwrite fresh ones. - Placeholder reads `${default_namespace} (auto-determined from path)` instead of `(auto-ns active)` — the previous wording sounded like *the* default name, hiding that auto-NS would resolve to something else. 4.13 — Hot-reload placeholder when Settings change - Already wired: `_syncConfigToUI()` (called after PATCH /api/config) invokes `_syncIndexHints()` which now re-derives the i18n-keyed suffix. No new wiring; covered by existing config-reload tests for the plumbing layer. JS reflow itself isn't pytest-Playwright-tested in CI — manual smoke is the only gate. 4.14 — Default NS backward-compat helper text - Tightened the `Default NS` field-guide text in `_CONFIG_GUIDES`: "Leaving this as 'default' results in untagged chunks (backward compatibility — chunks indexed before namespaces existed)." English only; the dict bypasses the `t()` resolver — KO migration tracked separately so we don't ship a one-string KO translation in a 6-string-only guide. 4.15 — Memory_dir root edge case - No code change; the new echo automatically shows the fallback (default NS or `(untagged)`) when the user enters a memory_dir root, surfacing the auto-NS root-skip rule without a special case. i18n: 11 new keys in en.json + ko.json (parity required, same commit) covering placeholder suffixes, the `Namespace` row label, and the `ns_render.{untagged,single,multi}_{preview,applied}` family that backs both the placeholder hint and the result-row echo. Tests: 8 new route-layer tests (leaf/uniform/rule-variance/truncated/ out-of-memory_dirs/missing-path/walk-matches-index-walk for preview; `resolved_namespaces` in trigger_index response) and 7 new engine tests (rule-variance distinct list, None-sorts-last, walk parity). Co-Authored-By: Claude <[email protected]>
Local checks ran ``ruff format --check`` against ``packages/memtomem/src`` only; CI checks the wider set (``src tests tools``) and flagged two files. Format-only — no behavior change. Co-Authored-By: Claude <[email protected]>
Two cleanups surfaced in self-review of #595: 1. ``_NS_PREVIEW_STATE`` WeakMap was set in five places and read in zero. The intent was tracking "is this placeholder a fresh preview vs. config-derived?" so the input handler could be selective, but the handler unconditionally invalidates regardless. Drop it; race-guard functionality lives in ``dataset.previewPath`` and that still works. 2. The compose tab's ``add-namespace`` input was wired against ``add-file``, but ``add-file`` is the *target save path* of a memory being composed — it doesn't exist on disk yet. ``discover_indexable_files`` only enumerates existing files, so focusing the namespace input would always 0-result and render ``(untagged) (preview)``, which is exactly the kind of quiet misrepresentation this PR is trying to eliminate. Remove the wire; leave a comment explaining why a phantom-path API is the right path forward (follow-up). The compose tab still gets the config-derived placeholder via ``_syncIndexHints``. Co-Authored-By: Claude <[email protected]>
…ynamic suffix Manual smoke uncovered a bug introduced by leaving ``data-i18n-placeholder`` on ``<input id="index-namespace">`` and ``<input id="add-namespace">``: i18n.js ``applyDOM`` ran on every ``langchange`` event and overwrote ``_syncIndexHints``'s dynamic placeholder with the static fallback ``index.ns_placeholder`` value. After a single locale toggle the user saw ``default (from config)`` even when ``enable_auto_ns=true``. The fix mirrors the existing ``_refreshAddFilePlaceholder`` pattern (the codebase already documented this gotcha for ``add-file``): - Drop ``data-i18n-placeholder`` from both namespace inputs in the HTML; keep the static ``placeholder=...`` as a pre-JS fallback. - Hook ``_syncIndexHints()`` into the ``langchange`` handler so the dynamic auto/config suffix re-renders in the new locale. Verified end-to-end via Playwright on isolated HOME with multi-NS fixture: A) honest placeholder reflects auto_ns config, B) debounced typing fires preview, C) focus on namespace fires immediate preview, D) multi-NS folder renders ``notes, scratch (preview, 2 namespaces)``, E) KO locale renders Korean strings, F) Settings → Save flips placeholder without page reload, G) result row after index shows ``notes, scratch (2 namespaces)``, plus live EN↔KO toggle re-renders the dynamic suffix. Co-Authored-By: Claude <[email protected]>
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 #581. Bundles four corrective items in the namespace mental-model
cluster — splitting them touches the same code surface
(
_syncIndexHints,_resolve_namespace, theDefault NShelper text,#index-result) four separate times, and UX prose drifts acrossreleases.
What changed
4.3 Auto-NS preview echo + honest placeholder
GET /api/index/preview-namespace?path=...&recursive=true(new)returns
{resolved_namespaces: list[str | None], truncated, scanned_files}.Capped at 200 files so the focus event stays snappy on big dirs.
IndexEngine.discover_indexable_files()andresolve_namespaces_for()are new public helpers. Both
index_pathand the preview route callthem, so the preview cannot silently disagree with the actually-applied
walk. (
engine.py:348-450)IndexingStats.resolved_namespaces(models.py:172) andIndexResponse.resolved_namespaces(web/schemas/memory.py:53)carry the distinct list end-to-end. The SSE complete event from
index_path_streamincludes it too.Namespacerow added to#index-result(index.html:525),populated via the shared
renderResolvedNamespaces()inapp.jsusing the
_appliedi18n variant.settings-config.js:1326-1418) — three eventsurfaces:
focus→ immediate previewinput→ 300ms-debounced preview (skipped whenthe namespace input has an explicit value)
input→ invalidate cached preview stateLate-arrival guard via
dataset.previewPathdrops stale responses.${default_namespace} (auto-determined from path)replaces the ambiguous
(auto-ns active).4.13 Hot-reload placeholder when Settings change
Already wired before this PR:
settings-config.js:1172calls_syncConfigToUI()after PATCH success, which calls_syncIndexHints()(line 291). With this PR's i18n-keyed suffix, the placeholder
re-renders correctly when
enable_auto_nsflips. The JS reflow isnot CI-tested (pytest-Playwright not in the suite); manual smoke
is the only gate. See "Verification" below.
4.14 Default NS backward-compat helper text
Tightened the
Default NSfield-guide text in_CONFIG_GUIDES(
settings-config.js:600):4.15 Memory_dir root edge case
No code change. The auto-NS root-skip in
_resolve_namespace(
engine.py:498-503) becomes visible because the new echo surfacesthe fallback automatically.
Decisions / non-obvious bits
the issue). Server is the source of truth for resolution rules;
client-side reimplementation drifts on every server change.
(e.g.
alpha/**→ns-alpha,beta/**→ns-beta) is exactly thecase where a scalar would silently lie. The list is sorted with
Nonelast so named NSes render before the untagged sentinel."default"areal tag (instead of the current backward-compat
Nonecarve-out)would force a chunk migration. Documenting the asymmetry is the
zero-migration value lever.
trust gate as
POST /api/index. Comment inweb/routes/system.py:861-863.stall on memory_dirs with thousands of files.
truncated=trueletsthe UI render
, scanned 200+. P95 measurement on a 250-filefixture in the test suite passes; live measurement on real dirs is
expected to be sub-100ms.
yanks all four sub-tasks together — intended; the entire argument
for bundling is that the legs disagree if revert-able independently.
Test plan
uv run ruff check packages/memtomem/src— cleanuv run ruff format --check packages/memtomem/src— cleanuv run pytest -m "not ollama" packages/memtomem/tests/— 3299 passedmm web:["notes"]["notes"]["notes", "scratch"]/etc→ 403POST /api/indexagainst multi-NS root →{"resolved_namespaces": ["notes", "scratch"], ...}enable_auto_nsON → Save → switch to Indextab without page reload → focus Namespace input → placeholder
reads
default (auto-determined from path). Toggle OFF + Save →placeholder reverts to
default (from config).path → focus Namespace input → placeholder shows
<folder> (preview)immediately on focus.
Namespace empty → DevTools network tab confirms preview fires only
after typing pauses (~300ms), not on every keystroke.
ns_a, ns_b (preview, 2 namespaces).Namespacerow.translated.
Known limitation / risk
If a future PR refactors
_syncConfigToUIand drops the_syncIndexHintscall, no automated test will catch the regression— manual smoke is the only gate. Documented here so a future bisect
notices.
Out of scope (per #581)
("NS CRUD model"), tracked in docs: post-STM-extraction cleanup + ground-truth count fixes #5.2.
"default"a real tag) — has migrationcost, separate PR.
_CONFIG_GUIDES→ i18n migration — tracked as follow-up so 4.14'sKO gap can be closed in a coherent unit.
🤖 Generated with Claude Code