Pulse Desktop shows tabs for modules switched off in PULSE.toml [modules]
Los mantenedores suelen responder en 7 días
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 75/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- bun, typescript
- Área
- backend-api-design, desktop
Línea de trabajo
Empieza con LIFEOS/PULSE/Desktop/commands.ts y workspace.ts y luego compara cómo el encabezado del navegador filtra los módulos. Comprueba cómo main.ts gestiona las acciones del espacio de trabajo y cómo la paleta crea su grupo Pages. Se considerará terminado cuando las pestañas de Desktop y las entradas de la paleta se oculten si sus módulos están desactivados, sigan visibles si no se puede obtener el mapa de módulos y haya pruebas que cubran ambos comportamientos.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Version
LifeOS 8.761.0 · attestation: 71 system file(s) differ from danielmiessler/[email protected]:
- hooks/lib/texting-guard-core.ts
- hooks/handlers/UpdateCounts.ts
- skills/LifeOS/Tools/RenderIdentity.ts
- skills/VideoEssay/Tools/Theme.ts
- skills/Fabric/Patterns/create_better_frame/system.md
- skills/Fabric/Patterns/extract_article_wisdom/README.md
- skills/Fabric/Patterns/extract_wisdom/README.md
- skills/Fabric/Patterns/rate_value/README.md
- skills/Fabric/Patterns/extract_product_features/README.md
- skills/Fabric/Patterns/show_fabric_options_markmap/system.md
- skills/Interceptor/Tools/VerifyViewport.ts
- skills/Interceptor/References/CommandReference.md
- skills/Interceptor/SKILL.md
- LIFEOS/TOOLS/SettingsBackport.ts
- LIFEOS/TOOLS/IntegrityCheck.ts
- LIFEOS/TOOLS/ReferenceCheck.ts
- LIFEOS/TOOLS/CloudPager.ts
- LIFEOS/TOOLS/Inference.ts
- LIFEOS/TOOLS/UpgradeVerify.ts
- LIFEOS/TOOLS/GrokQuery.ts
- LIFEOS/TOOLS/MergeSettings.ts
- LIFEOS/TOOLS/SchedulerWatch.ts
- LIFEOS/TOOLS/derived-artifacts.json
- LIFEOS/TOOLS/models.ts
- LIFEOS/TOOLS/model-pricing.json
- LIFEOS/TOOLS/Services.ts
- LIFEOS/TOOLS/CodexProgress.ts
- LIFEOS/DOCUMENTATION/Tex/TexSystem.md
- LIFEOS/DOCUMENTATION/Workflows/WorkflowsSystem.md
- LIFEOS/DOCUMENTATION/ARCHITECTURE_SUMMARY.md
- LIFEOS/DOCUMENTATION/Hooks/HookSystem.md
- LIFEOS/DOCUMENTATION/Notifications/NotificationSystem.md
- LIFEOS/DOCUMENTATION/Services/BackgroundServices.md
- LIFEOS/PULSE/manage.sh
- LIFEOS/PULSE/Desktop/offline.html
- LIFEOS/PULSE/Desktop/commands.ts
- LIFEOS/PULSE/Desktop/main.ts
- LIFEOS/PULSE/Desktop/workspace.ts
- LIFEOS/PULSE/Desktop/README.md
- LIFEOS/PULSE/Desktop/policy.ts
- …and 31 more
What is broken
PULSE.toml's [modules] comment promises that switching a module off "hides its tab in the dashboard instead of leaving a nav entry that opens an empty page", and the web header keeps that promise through use-enabled-modules.ts (ported from PR #1749). Pulse Desktop does not: its tab strip and command palette build from the raw nav manifest (Desktop/commands.ts maps paletteEntries straight to tabs) and never read /api/config/modules, so on an install with bunker = false, da = false and gauntlet = false the Desktop window still shows Bunker, Assistant and Gauntlet tabs, and each opens a "SIGNAL LOST · /api/bunker — Error: HTTP 404" or "Unavailable: the Assistant backend is not installed" page. The same pages are correctly absent from the dashboard's own nav in a browser.
Where (file:line)
LIFEOS/PULSE/Desktop/commands.ts:3
Repro on a clean tree
1. In `LIFEOS/USER/CONFIG/PULSE.user.toml` set `[modules] bunker = false` (and `da = false`, `gauntlet = false`); restart Pulse.
2. `curl -s localhost:31337/api/config/modules` → `"bunker":false,"da":false,"gauntlet":false`.
3. Open the dashboard in a browser: the Bunker, Assistant and Gauntlet tabs are absent from the header nav (expected).
4. Open Pulse Desktop: the same three tabs are present in the Desktop tab strip and in the ⌘K palette's "Pages" group; clicking Bunker renders "SIGNAL LOST · /api/bunker — Error: HTTP 404".
5. Static form of the same fact (the probe): the shipped `LIFEOS/PULSE/Desktop/commands.ts` and `workspace.ts` contain zero references to `/api/config/modules`.
Negative control
Probe: sh -c 'git show 359084d4:LIFEOS/PULSE/Desktop/commands.ts | grep -q config/modules; r=$?; git show 359084d4:LIFEOS/PULSE/Desktop/commands.ts | grep -c config/modules | sed "s/^/shipped Desktop commands.ts references to config\/modules: /"; exit $r' · exit 1 · captured 2026-11-07T17:39:23.758Z
Red looks like: shipped Desktop commands.ts references to config/modules: 0
shipped Desktop commands.ts references to config/modules: 0
Suggested fix
Desktop applies the same fail-open filter the web header uses. commands.ts keeps each page's module key and exports enabledPages(list, modules) (no map, or a page without a module key, stays visible). main.ts adds a modules workspace action that fetches ORIGIN + "/api/config/modules" (2.5 s timeout, returns null on any failure). workspace.ts calls it after the tabs are built and once a minute, sets hidden on the tab buttons of disabled modules, and filters the palette's "Pages" group through the same function. commands.test.ts (3 tests) covers hide, fail-open, and that the real manifest carries bunker and da. The attached diff is against the 8.761.0 Desktop sources; the main.ts hunk sits inside the workspace:call switch next to reconnect.
diff --git a/LIFEOS/PULSE/Desktop/commands.ts b/LIFEOS/PULSE/Desktop/commands.ts
index b3d6cb74..79173ad4 100644
--- a/LIFEOS/PULSE/Desktop/commands.ts
+++ b/LIFEOS/PULSE/Desktop/commands.ts
@@ -4,7 +4,15 @@ export const pages=paletteEntries.map(p=>({
id:p.href,
title:p.label==='TELOS'?p.label:p.label.charAt(0)+p.label.slice(1).toLowerCase(),
shortcut:pageShortcuts[p.href],
+ module:p.module,
}));
+// LOCAL PATCH desktop-tabs-honor-modules: the Desktop tab strip and palette honour PULSE.toml [modules] the way the
+// web header does (use-enabled-modules.ts): a page whose module is switched off is hidden. Fails open: no map, or a
+// page with no module key, stays visible, so one failed request never empties the strip.
+export function enabledPages<T extends {module?:string}>(list:readonly T[],modules:Record<string,boolean>|null|undefined):T[]{
+ if(!modules)return [...list];
+ return list.filter(p=>!p.module||modules[p.module]!==false);
+}
export const nativeCommands=[
{id:'reload',label:'Reload current view',shortcut:'⌘R'},
{id:'reconnect',label:'Reconnect to Pulse',shortcut:'⇧⌘R'},
diff --git a/LIFEOS/PULSE/Desktop/main.ts b/LIFEOS/PULSE/Desktop/main.ts
index a8703200..2b4ee678 100644
--- a/LIFEOS/PULSE/Desktop/main.ts
+++ b/LIFEOS/PULSE/Desktop/main.ts
@@ -219,6 +219,10 @@ function setupIPC(){
case 'page':if(typeof args.path!=='string'||!isPulseURL(ORIGIN+args.path))throw new Error('Invalid page');showPage(args.path);return true;
case 'terminal':pageVisible=false;layout();win?.webContents.focus();return true;
case 'reconnect':await connect();return true;
+ // LOCAL PATCH desktop-tabs-honor-modules: the resolved PULSE.toml [modules] map, or null when Pulse cannot answer (the strip then stays whole).
+ case 'modules':{
+ try{const r=await fetch(ORIGIN+'/api/config/modules',{signal:AbortSignal.timeout(2500),redirect:'error'});if(!r.ok)return null;const j=await r.json();return j&&typeof j==='object'&&j.modules&&typeof j.modules==='object'?j.modules:null;}catch{return null;}
+ }
default:throw new Error('Unknown workspace action');
}
});
diff --git a/LIFEOS/PULSE/Desktop/workspace.ts b/LIFEOS/PULSE/Desktop/workspace.ts
index c60928a1..2b354400 100644
--- a/LIFEOS/PULSE/Desktop/workspace.ts
+++ b/LIFEOS/PULSE/Desktop/workspace.ts
@@ -4,7 +4,7 @@ import {GhosttyTerminalSurface} from './ghostty/surface';
import {ghosttyTheme} from './ghostty-theme';
import {terminalKeyDecision} from './terminal-keys';
import type {TerminalInfo} from './terminals';
-import {pages,nativeCommands,filterCommands,type Command} from './commands';
+import {pages,enabledPages,nativeCommands,filterCommands,type Command} from './commands';
import {pageIcon} from './page-icons';
import {openTerminalLink} from './terminal-links';
import {TerminalOutput} from './terminal-output';
@@ -53,6 +53,10 @@ function notice(error:unknown){$('notice').textContent=String(error).replace(/^E
const attempt=(fn:()=>Promise<unknown>)=>void fn().catch(notice);
function button(label:string,action:()=>void){const b=document.createElement('button');b.type='button';b.textContent=label;b.onclick=action;return b;}
pages.forEach(p=>{const b=button('',()=>attempt(()=>select(p.id)));b.dataset.id=p.id;b.append(pageIcon(p.id),Object.assign(document.createElement('span'),{textContent:p.title}),Object.assign(document.createElement('kbd'),{textContent:p.shortcut||''}));$('pages').append(b);});
+// LOCAL PATCH desktop-tabs-honor-modules: hide the tabs of modules switched off in PULSE.toml, as the web header does.
+let modules:Record<string,boolean>|null=null;
+function applyModules(){const on=new Set(enabledPages(pages,modules).map(p=>p.id));document.querySelectorAll<HTMLButtonElement>('#pages [data-id]').forEach(b=>{b.hidden=!on.has(b.dataset.id!);});}
+async function loadModules(){const map=await api.call('modules').catch(()=>null);modules=map&&typeof map==='object'?map as Record<string,boolean>:null;applyModules();}
$('pages-back').onclick=()=>$('pages').scrollBy({left:-Math.max(240,$('pages').clientWidth*.7),behavior:'instant'});
$('pages-next').onclick=()=>$('pages').scrollBy({left:Math.max(240,$('pages').clientWidth*.7),behavior:'instant'});
function pageScrollState(){$<HTMLButtonElement>('pages-back').disabled=$('pages').scrollLeft<1;$<HTMLButtonElement>('pages-next').disabled=$('pages').scrollLeft+$('pages').clientWidth>=$('pages').scrollWidth-1;}
@@ -209,7 +213,7 @@ function commandCatalog():Command[]{
{id:'rename',label:'Rename current session',group:'Actions',detail:current.title,run:()=>rename()},
{id:'end',label:'End current session…',group:'Actions',detail:'Asks before ending the shell and its programs',run:()=>endSelected()},
]:[]),
- ...pages.map(p=>({id:'page:'+p.id,label:'Go to '+p.title,group:'Pages',shortcut:p.shortcut,run:()=>select(p.id)})),
+ ...enabledPages(pages,modules).map(p=>({id:'page:'+p.id,label:'Go to '+p.title,group:'Pages',shortcut:p.shortcut,run:()=>select(p.id)})),
...sessions.map(s=>({id:s.id,label:s.title,group:'Sessions',detail:`${s.command} · ${s.directory}`,run:async()=>{await refresh();if(!sessions.some(item=>item.id===s.id))throw new Error('That session has ended.');await select(s.id);}})),
...nativeCommands.filter(c=>!['back','forward'].includes(c.id)||selected.startsWith('/')).map(c=>({...c,group:'View',run:async()=>{await api.call('native-command',{id:c.id});}})),
{id:'focus',label:'Focus sessions sidebar',group:'Actions',shortcut:'⌘B',run:()=>{(document.querySelector<HTMLButtonElement>('#sessions .active')||$('new')).focus();}},
@@ -284,3 +288,6 @@ commandDialog.addEventListener('keydown',e=>{
});
attempt(async()=>{await refresh();await select(selected);});
setInterval(()=>attempt(refresh),1000);
+// LOCAL PATCH desktop-tabs-honor-modules: read the module map once Pulse answers, and again after every reconnect.
+attempt(loadModules);
+setInterval(()=>attempt(loadModules),60_000);
diff --git a/LIFEOS/PULSE/Desktop/commands.test.ts b/LIFEOS/PULSE/Desktop/commands.test.ts
new file mode 100644
index 00000000..3cd7afcf
--- /dev/null
+++ b/LIFEOS/PULSE/Desktop/commands.test.ts
@@ -0,0 +1,20 @@
+// LOCAL PATCH desktop-tabs-honor-modules
+import {describe,expect,test} from 'bun:test';
+import {enabledPages,pages} from './commands';
+
+describe('enabledPages',()=>{
+ const list=[{id:'/today',module:'today'},{id:'/bunker',module:'bunker'},{id:'/assistant',module:'da'},{id:'/',module:undefined}] as const;
+ test('hides a page whose module is switched off, keeps the rest',()=>{
+ expect(enabledPages(list,{today:true,bunker:false,da:false}).map(p=>p.id)).toEqual(['/today','/']);
+ });
+ test('fails open: no map, or a module the map does not name, stays visible',()=>{
+ expect(enabledPages(list,null).map(p=>p.id)).toEqual(['/today','/bunker','/assistant','/']);
+ expect(enabledPages(list,{}).map(p=>p.id)).toEqual(['/today','/bunker','/assistant','/']);
+ });
+ test('the real page list carries each entry\'s module key, so Bunker and Assistant can be hidden',()=>{
+ const byId=new Map(pages.map(p=>[p.id,p.module]));
+ expect(byId.get('/bunker')).toBe('bunker');
+ expect(byId.get('/assistant')).toBe('da');
+ expect(enabledPages(pages,{bunker:false,da:false,gauntlet:false}).some(p=>['/bunker','/assistant','/gauntlet'].includes(p.id))).toBe(false);
+ });
+});
Environment
- OS: macOS 27.2 (arm64)
- Harness: claude-code
- bun: 1.3.5 · git: 2.54.0
- Doctor broken: auditer
- Lenguaje dominante
- TypeScript
- Estrellas
- 19.7k
- Forks
- 2.5k
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de danielmiessler/LifeOS
-
Dificultad 1/5 1-3 horas Aptitud para principiantes 84/100
danielmiessler/LifeOS#2655 ·
Los mantenedores suelen responder en 7 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
danielmiessler/LifeOS#2653 ·
Los mantenedores suelen responder en 7 días
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
danielmiessler/LifeOS#2639 ·
Los mantenedores suelen responder en 7 días
-
MergeSettings: nested __merge annotations under a user-only key are written raw into settings.jsonAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 67/100
danielmiessler/LifeOS#2636 ·
Los mantenedores suelen responder en 7 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
danielmiessler/LifeOS#2635 ·
Los mantenedores suelen responder en 7 días
Todos los issues de danielmiessler/LifeOS
Issues similares
-
Dificultad 1/5 1-3 horas Aptitud para principiantes 88/100
danielw-sudo/agentuto#63 ·
Los mantenedores suelen responder en 1 día
-
idea-scout idea:341
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
jeanibarz/agent-notebook#341 ·
Los mantenedores suelen responder en 1 día
-
documentation good first issue roadmap
Dificultad 1/5 1-3 horas Aptitud para principiantes 68/100
Los mantenedores suelen responder en 1 día
-
type:bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
nightgauge/nightgauge#3077 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
LibreChat-AI/LibreChat#17915 ·
Los mantenedores suelen responder en 1 día