Skip to content

feat(admin): persist memory subtab in URL as ?memsub= param#281

Merged
mattmezza merged 11 commits into
mainfrom
feat/admin-memory-subtabs-url
Jul 10, 2026
Merged

feat(admin): persist memory subtab in URL as ?memsub= param#281
mattmezza merged 11 commits into
mainfrom
feat/admin-memory-subtabs-url

Conversation

@mattmezza

Copy link
Copy Markdown
Owner

Summary

Memory subtabs (Settings | Long-term | Short-term) now sync their active state to a ?memsub= URL param, enabling bookmarking, refresh persistence, and browser back/forward support.

Changes

base.html

  • Added memorySubtab() Alpine component (pattern-matched from vaultTabs())
  • Reads ?memsub= from URL on init, falls back to localStorage, defaults to settings
  • On subtab selection: updates URL via replaceState, saves to localStorage, fires HTMX to load the new view

dashboard.html (adminTabs())

  • Forwards ?memsub= param on initial memory tab load
  • Forwards ?memsub= when switching to the memory tab from another tab
  • Always re-loads memory tab on popstate (browser back/forward restores correct subtab)
  • Preserves ?memsub= in URL when switching between memory and other tabs

memory.html

  • Replaced plain HTMX hx-get subtab buttons with Alpine-managed ones
  • Active state now uses Alpine :class bindings instead of Jinja {% if %}
  • Nav rendered once before the view-specific content blocks (no more duplication)
  • Pagination/search buttons continue to work as before (they stay within the same subtab)

Testing

  • Visit /admin?tab=memory&memsub=long-term directly → lands on Long-term tab
  • Click subtabs → URL updates, bookmarking works
  • Browser back/forward → correct subtab restored
  • Page refresh → last selected subtab preserved
  • Switch to another tab and back → memsub param preserved

Fixes the 'long-term subtab not clickable' issue by moving from HTMX-driven to Alpine-driven navigation.

- Add memorySubtab() Alpine component (modeled after vaultTabs) that
  reads/writes ?memsub= URL param and localStorage for bookmarking
- Replace plain HTMX nav buttons with Alpine @click + :class bindings
- Forward ?memsub= in adminTabs() on memory tab load and tab switch
- Preserve ?memsub= when switching between memory and other tabs
- Re-load memory tab on popstate when only memsub param changes
  (browser back/forward now restores the correct subtab)
 in Jinja2 iterates its left operand and finds the minimum —
it doesn't accept an argument. Wrapping operands in a list fixes it.
@kindralai

kindralai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Workflow run #281 → failure. One test needs updating.

test_sub_tab_nav_buttons_link_correctly asserts hx-get="/partials/memory?view=settings" etc. on the sub-tab buttons, but the PR replaced plain HTMX hx-get buttons with Alpine-managed @click="select(...)" buttons. The test needs to check for Alpine attributes instead.

Will push a fix.

The PR replaced plain HTMX hx-get sub-tab buttons with Alpine-managed
@click handlers. The test must now verify Alpine attributes instead.

- Check for x-data="memorySubtab()" and x-init="init()"
- Check for @click="select(...)" on each button
- Check for :class bindings with 'tab-link-active'
@kindralai

kindralai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Pushed a fix commit e1a563atest_sub_tab_nav_buttons_link_correctly now asserts Alpine @click handlers and :class bindings instead of the old hx-get attributes.

The rendered memory.html nav:

<div x-data="memorySubtab()" x-init="init()" ...>
  <button ... @click="select(settings)">Settings</button>
  <button ... @click="select(long-term)">Long-term</button>
  <button ... @click="select(short-term)">Short-term</button>
</div>

The test now checks for:

  • x-data="memorySubtab()" and x-init="init()" on the nav wrapper
  • @click="select(settings/long-term/short-term)" on each button
  • :class bindings with tab-link-active

mattmezza and others added 5 commits July 10, 2026 10:35
…h URL params

Convert 5 admin tabs from monolithic partials with x-show/hash to
wrapper + lazy-loaded sub-tab partials via htmx:

**Jobs** (#hash → ?jobsub=)
- Wrapper with skeleton; scheduled/subagents as separate partials
- scheduled content keeps its own Alpine state for form/pagination
- subagents points at existing /partials/subagent-runs

**Memory** (?memsub=)
- Split settings/long-term/short-term into separate templates
- Pagination/search macros moved to shared _macros.html
- Delete/update routes return sub-tab content to #memory-sub

**LLM** (?llmsub=)
- Keep the big llmTab Alpine component in the wrapper
- inference/providers/history as separate partials
- setSection() now fires HTMX to load sub-tab content
- init() loads initial sub-tab after seeding model values

**Accounts** (?accountsub=)
- Already using subTabs pattern; skeleton loading verified

**Vault** (?vaultsub=)
- agents/infra/import as separate partials
- vaultTabs() now uses subTabs pattern with skeleton

All use the shared subTabs() utility which handles:
- URL param read/write via replaceState
- localStorage fallback
- Skeleton display during HTMX load
- Browser back/forward (via popstate in adminTabs)
- Remove Alpine-bound subagentCount from wrapper badge (not available
  in subTabs pattern); use static span with hidden badge
- Fix x-data quote conflict in jobs_scheduled.html — querySelector
  with escaped double-quotes inside HTML attribute broke Alpine parsing.
  Replaced with getElementById('job-form') and added id to the form
The tab-partial refactor (d8ca2ab) split monolithic tab partials into
wrapper + lazy-loaded sub-tab partials via htmx. Tests that hit the
wrapper endpoints now see Alpine components with skeleton loaders
rather than actual content.

Changes:
- test_add_and_list_secret_via_admin: POST creates secret, then GET
  /partials/secrets/agents to verify listing
- test_llm_tab_collapses_vaulted_key: hit /partials/llm/providers instead
  of wrapper
- test_llm_partial_has_subtabs: wrapper assertions stay; 'Context
  compaction' verified via /partials/llm/history
- test_llm_partial_has_reply_decision: hit /partials/llm/inference
- test_llm_partial_has_vision_fallback: hit /partials/llm/inference
- test_llm_partial_has_openrouter: hit /partials/llm/providers
- test_bitwarden_import_parse_and_commit: remove 'Site' assertion
  (template no longer renders parsed items), commit+verify still works
@kindralai

kindralai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Updated 7 integration tests for the lazy-loaded sub-tab architecture:

test_admin_ui.py (4 tests)

  • test_llm_partial_has_subtabs: Alpine wrapper assertions stay on /partials/llm; "Context compaction" now fetched from /partials/llm/history
  • test_llm_partial_has_vision_fallback → hits /partials/llm/inference
  • test_llm_partial_has_reply_decision → hits /partials/llm/inference
  • test_llm_partial_has_openrouter → hits /partials/llm/providers

test_secrets_vault.py (3 tests)

  • test_add_and_list_secret_via_admin: POST creates secret (status 200), then GET /partials/secrets/agents to verify listing
  • test_llm_tab_collapses_vaulted_key: hits /partials/llm/providers instead of wrapper
  • test_bitwarden_import_parse_and_commit: removed "Site" assertion (import sub-tab template renders only the upload form); commit + verify still works

Update tests that hit /partials/memory and /partials/jobs wrappers
to use the new sub-tab endpoints instead.

test_admin_api.py:
- test_partial_jobs_hides_completed_by_default: /partials/jobs/scheduled
- test_partial_jobs_show_completed_reveals_done: /partials/jobs/scheduled?show_completed=true

test_memory_subtabs.py:
- 6 tests updated from /partials/memory?view=X to /partials/memory/X

test_memory_admin.py:
- test_memory_partial_renders: /partials/memory/settings
@kindralai

kindralai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Updated 8 more tests in the core chunk:

test_admin_api.py (2 tests)

  • test_partial_jobs_hides_completed_by_default/partials/jobs/scheduled
  • test_partial_jobs_show_completed_reveals_done/partials/jobs/scheduled?show_completed=true

test_memory_subtabs.py (5 tests + 2 pagination)

  • All ?view= based tests updated to use /partials/memory/settings, /partials/memory/long-term, /partials/memory/short-term

test_memory_admin.py (1 test)

  • test_memory_partial_renders/partials/memory/settings

memory_pag macro called twice per sub-tab (top + bottom) with same
id='ps-{view}'. Added optional suffix param; bottom calls use '-b'.
… host state

reply_decision fixture hit the default embeddings sidecar (12s timeout
per call); skills tests picked up a locally-installed pptx skill.
@mattmezza
mattmezza force-pushed the feat/admin-memory-subtabs-url branch from 5114fe6 to d7d75bd Compare July 10, 2026 09:15
@mattmezza
mattmezza merged commit 7f79eb8 into main Jul 10, 2026
3 checks passed
@mattmezza
mattmezza deleted the feat/admin-memory-subtabs-url branch July 10, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant