Skip to content

feat(pulse): hide nav tabs whose module is switched off - #1749

Open
elhoim wants to merge 2 commits into
danielmiessler:mainfrom
elhoim:feat/pulse-hide-disabled-tabs
Open

feat(pulse): hide nav tabs whose module is switched off#1749
elhoim wants to merge 2 commits into
danielmiessler:mainfrom
elhoim:feat/pulse-hide-disabled-tabs

Conversation

@elhoim

@elhoim elhoim commented Aug 3, 2026

Copy link
Copy Markdown

Stacked on #1748. This branch contains that commit as its parent; the diff below is the second commit only. Merge or port #1748 first — without /api/config/modules the hook fetch 404s and every tab stays visible (which is the designed fallback, so nothing breaks, but nothing hides either).

Problem

Switching a module off stops its backend but leaves its tab in both nav rows, the mobile menu and the command palette. Clicking one opens a page with nothing answering it — an empty view rather than an honest absence.

GROWTH shows this without any config at all: it's backed by an optional USER customization that isn't shipped, so on a fresh install the tab is always there and always empty.

Change

  • nav-manifest.tsNavItem.module?: string, set on 21 entries. Entries without one (Home, Agents, Skills, Hooks, Arbol, Security) are infrastructure and always render.
  • lib/use-enabled-modules.ts (new) — fetches /api/config/modules once, returns a useCallback-stable predicate so callers can memoise on it.
  • AppHeader.tsx — filters tier1Nav and systemNav at all four render sites (desktop tier-1, desktop system row, mobile sections, mobile system).
  • CommandPalette.tsx — searches the filtered set, so Cmd-K can't jump to a page the nav is hiding.

Fails open, deliberately

Until the fetch lands — and permanently if it errors, or the daemon predates the endpoint — every entry stays visible. A dashboard briefly showing a tab it then hides is a much smaller problem than a nav that empties itself because one request failed.

Verification

Ported to my own install, built (next build, 40 static routes), restarted Pulse with local = false.

Backend: /api/config/modules reports local off; /api/local-intelligence/latest → 404; LocalIntelligence module loaded absent from the current boot's journal.

Rendered nav — the real AppHeader at 1440px, read out of the live DOM:

/memory/graph /telos /work /content /projects /health /finances
/business /growth /assets /knowledge /amber /agents /assistant

14 links. /local sits between /growth and /assets in the manifest and is absent from the render; the sequence jumps straight from /growth to /assets. Every other entry is untouched.

Visually — captured the desktop header in a real Chrome and zoomed in on the nav strip. Row 1 reads TELOS WORK CONTENT PROJECTS HEALTH FINANCES BUSINESS, row 2 reads GROWTH ASSETS KNOWLEDGE AMBER. No LOCAL.

tsc --noEmit: 26 pre-existing errors in unrelated files, none in the four files this touches.

Not run: a fresh-system install verification (step 3 of the contributing process).

elhoim added 2 commits August 3, 2026 09:19
…d flags

loadModules() gates on config.local_intelligence, config.hypotheses and
config.upgrades, but loadPulseConfig() builds its return object key by key
and never copies those three through. The gates read undefined, every one
of them is written '?.enabled !== false', and undefined !== false is true —
so all three modules always loaded and setting [local_intelligence]
enabled = false in PULSE.toml silently did nothing.

Fourteen more user-facing modules (docs, memory, conduit, books, synapse,
ledger, projects, gear, atlas, threatmodel, usage, evals, algorithm,
hermes) had no switch at all, and the HEALTH/FINANCES/BUSINESS/GROWTH
surfaces have no module of their own to switch.

Adds a [modules] table as the single place to turn a surface off, resolved
in lib/modules.ts from three layers: defaults, the older [section].enabled
flags, then [modules]. Existing configs need no changes and behave
identically — the shipped defaults leave everything on except the opt-in
imessage and syslog. GET /api/config/modules exposes the resolved map so
the dashboard can hide a disabled module's tab.

Infrastructure Pulse needs to serve anything at all (observability, hooks,
tab-freshness, menubar, siri, doctor) is deliberately not switchable.
Switching a module off in PULSE.toml stopped its backend but left its tab
in both nav rows, the mobile menu and the command palette. Clicking one
opened a page with nothing answering it — an empty view rather than an
honest absence.

Nav entries now carry the [modules] key that backs them, and AppHeader and
CommandPalette filter on the resolved map from GET /api/config/modules.
Entries with no key (Home, Agents, Skills, Hooks, Arbol, Security) are
infrastructure and always render.

The hook fails open: until the fetch lands, and permanently if it errors or
the daemon predates the endpoint, every entry stays visible. Briefly
showing a tab that then disappears beats a nav that empties itself because
one request failed.

Depends on the [modules] table and /api/config/modules from
feat/pulse-module-toggles.
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