Skip to content

fix(web): vendor sub-tab follow-through on cross-tab source navigation#673

Merged
memtomem merged 2 commits intomainfrom
fix/web-sources-vendor-followthrough
May 1, 2026
Merged

fix(web): vendor sub-tab follow-through on cross-tab source navigation#673
memtomem merged 2 commits intomainfrom
fix/web-sources-vendor-followthrough

Conversation

@memtomem
Copy link
Copy Markdown
Owner

@memtomem memtomem commented May 1, 2026

Summary

Two cross-tab navigation entry-points into the Sources panel set one axis (namespace filter or source path) but did not align the panel's vendor sub-tab axis (user / claude / openai). Because the panel only renders the active vendor's tree (#570), the user-visible chip / active-source state suggested the link succeeded while the rendered content was an unrelated vendor — reported as "Sources 화면으로 넘어가긴 하는데 직접 링크가 안 된다."

Audit (3 parallel Explore agents) found _navigateToSource is the same shape of bug; both fixes share the helper and live next to each other in app.js, so they ship together as one focused change.

Changes

navigateToSourcesByNs (Settings → Namespaces → Sources button)

packages/memtomem/src/memtomem/web/static/app.js_renderMemorySourceTree:

  • filterActive now also returns true when STATE.sourcesNsFilter is set, not just when the path-text input is non-empty. Empty memory_dir headers (28 of 29 in the repro for a claude:… namespace) are now hidden under the existing visibleCatsRaw guard.
  • After computing per-vendor totals, if the current vendor has zero matches and another vendor has any, the active sub-tab shifts to the vendor with the most. Not persisted via _writeActiveSourcesVendor — this is a navigation hint, not the user's stored preference, so the next manual click still wins.

_navigateToSource (Home recent source, command palette)

Same root cause, narrower entry-point. Resolves the target vendor from the source's memory_dir → provider (looked up via STATE.allSources + STATE.memoryStatusByPath) before activateTab('sources'), so a Claude/OpenAI source clicked from Home doesn't land on whichever sub-tab the user last viewed and silently miss the .source-item[title=path] scroll-into-view that follows. Falls through to current behaviour when STATE.allSources isn't populated yet (cold load) — no regression.

Cache bust

index.html app.js?v=82 → v=84 so the JS body change is actually picked up by browsers (per feedback_static_asset_cache_bust.md, unbumped query strings get served stale from disk cache).

Verification

  • uv run ruff check packages/memtomem/src — clean.
  • uv run pytest -m "not ollama" -k "namespace or sources or web" — 986 passed.
  • Playwright (against mm web):
    • localStorage forced to user vendor → click claude:-Users-pdstudio-Work-agent-harness-memtomem namespace's Sources button → vendor tab shifts to claude, exactly 1 dir group with 284 file items, chip shows the namespace.
    • Clear chip → 29 dir groups + 356 files reappear under Claude.
    • Click default namespace from user vendor → vendor stays user, 2 dirs + 22 files (auto-switch correctly inert when current vendor has matches).
    • _navigateToSource(<claude path>) from user vendor → vendor switches to claude, target source becomes .source-item.active and gets scrollIntoView.
    • _navigateToSource(<user path>) from user vendor → no vendor switch, target source becomes active (regression check).

Out-of-scope sibling

_searchByTag(tag) (app.js:3631-3640) populates both search-input and tag-filter with the tag string — same axis-mismatch shape but v0.1.0-since behaviour, not a recent regression. Filed as a separate issue (#672) for an intent-vs-design-flaw decision; user-visible behaviour change so it needs its own CHANGELOG entry.

Test plan

  • ruff check clean
  • Pytest namespace/sources/web suite green
  • Playwright: cross-vendor namespace card → auto-switch to matching vendor sub-tab
  • Playwright: chip-clear restores full vendor tree
  • Playwright: same-vendor namespace card preserves current vendor
  • Playwright: _navigateToSource cross-vendor + same-vendor regression
  • Manual: refresh in real browser to confirm ?v=84 bust takes effect

🤖 Generated with Claude Code

pandas-studio and others added 2 commits May 2, 2026 00:21
Two cross-tab navigation entry-points to the Sources panel set one axis
(namespace filter or path) but did not align the Sources panel's vendor
sub-tab axis (user / claude / openai). Because the panel only renders
the active vendor's content (issue #570), the chip or "active source"
visual state would suggest the link worked while the visible tree
showed an unrelated vendor — the user reported it as "the link doesn't
actually link to anything."

navigateToSourcesByNs (Settings → Namespaces → "Sources" button):
- _renderMemorySourceTree's filterActive guard now reads sourcesNsFilter
  in addition to the path-text input, so empty memory_dir headers are
  hidden when only a namespace filter is active.
- After computing per-vendor totals, if the current vendor has zero
  matches and another vendor has any, switch the active sub-tab to the
  vendor with the most matches. Not persisted via
  _writeActiveSourcesVendor — this is a navigation hint, not the user's
  stored preference.

_navigateToSource (Home recent source list, command palette):
- Resolves the target vendor from the source's memory_dir → provider
  before activateTab('sources'), so a Claude or OpenAI source clicked
  from Home doesn't land on whichever vendor sub-tab the user last
  viewed and silently miss the .source-item lookup that follows.

Bumps app.js?v=82 → v=84 to bust browser disk cache; static asset body
changes that don't bump the query string get served stale (per
feedback_static_asset_cache_bust.md, see #587).

Co-Authored-By: Claude <[email protected]>
Reviewer flagged a one-tick stale on the "N files · M chunks" caption
above the Sources tree: ``renderSourceTree`` was reading
``STATE.sourcesActiveVendor`` and computing the totals before
``_renderMemorySourceTree`` had a chance to fire the NS-filter
follow-through. Result was visible — clicking a Claude namespace's
Sources button while sitting on the User sub-tab showed:

  active vendor tab: claude     ← post-switch
  tree:              claude     ← post-switch
  chip:              claude     ← present
  stats line:        user       ← stale until the next render

Until the next ``renderSourceTree`` call (filter input, sort click,
reindex), the caption read the wrong vendor's totals. Reproduced via
Playwright before fix: ``statsText: "22 files · 318 chunks"`` (user
vendor) above 284 visible Claude items.

Extracted the stats compute into ``_renderSourcesStats(vendor)`` and
moved the call inside ``_renderMemorySourceTree`` right after the
NS-filter auto-switch resolves the active vendor. ``renderSourceTree``
now just delegates to ``_renderMemorySourceTree`` — the caption can no
longer race the sub-tab. Verified all three vendor branches: stats
follow the active vendor (Claude → 357/1084, User → 22/318, OpenAI →
hidden).

Also documented the tie-break policy on the auto-switch sort: stable
sort + ``PROVIDER_ORDER = ['user','claude','openai']`` means equal
totals favour ``user``. Reviewer noted the policy wasn't named — added
the rationale ("user is the conventional primary surface") so a future
reader doesn't second-guess it.

Cold-load silent-miss in ``_navigateToSource`` (deferred per review):
acceptable as-is for now — the existing inline comment names the
trade-off. A separate follow-up can wire a ``loadSources()`` retry if
it ever surfaces.

Co-Authored-By: Claude <[email protected]>
@memtomem
Copy link
Copy Markdown
Owner Author

memtomem commented May 1, 2026

Review responses pushed in 9b4ae1d:

Concern 1 (stats badge one-tick stale) — confirmed visible, fixed.
Reproduced with Playwright before the fix: clicking the claude:… namespace's Sources button while sitting on the User sub-tab landed activeVendor=claude, fileItems=284 (Claude), but statsText="22 files · 318 chunks" — the User vendor totals sitting above a Claude tree. Until the next renderSourceTree call, the caption claimed a different vendor than the tree.

Fix: extracted the stats compute into _renderSourcesStats(vendor) and moved the call inside _renderMemorySourceTree right after the NS-filter auto-switch resolves activeVendor. renderSourceTree is now a thin delegator, so the caption can't race the sub-tab. Verified all three vendor branches:

  • chip-clear → vendor=claude, stats=357 files · 1,084 chunks (matches tree)
  • switch to User → vendor=user, stats=22 files · 318 chunks
  • switch to OpenAI (empty) → stats hidden=true

Concern 2 (tie-break sort stability rationale) — documented.
Added a comment naming the policy: stable sort + PROVIDER_ORDER = ['user', 'claude', 'openai'] means equal NS-match totals favour user because it's the conventional primary surface. Avoids the next reader wondering whether the asymmetry was intentional.

Concern 3 (cold-load silent miss in _navigateToSource) — deferred per review verdict ("후속 개선 여지").
Existing inline comment names the trade-off. If users hit it (unlikely — both call sites are post-dashboard) a follow-up can wire a loadSources() retry.

v=84 → v=85 cache-bust, ruff clean, namespace/sources/web tests still 986 passed.

@memtomem memtomem merged commit b2d54d2 into main May 1, 2026
8 of 9 checks passed
@memtomem memtomem deleted the fix/web-sources-vendor-followthrough branch May 1, 2026 23:00
@github-actions github-actions Bot locked and limited conversation to collaborators May 1, 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.

2 participants