Pulse Desktop shows tabs for modules switched off in PULSE.toml [modules]
メンテナーはふだん 7 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 75/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- bun, typescript
調査の方向性
LIFEOS/PULSE/Desktop/commands.ts と workspace.ts から始め、ブラウザーのヘッダーがモジュールをどのようにフィルタリングするかを比較してください。main.ts がワークスペースのアクションをどのように処理し、パレットが Pages グループをどのように構成するかを確認してください。モジュールが無効な場合に Desktop のタブとパレットの項目が非表示になり、モジュールマップを取得できない場合は表示されたままであり、両方の動作をテストでカバーできていれば完了です。
索引モデルが issue の本文から書いたものです。
説明
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
- 主要言語
- TypeScript
- スター
- 19.7k
- フォーク
- 2.5k
- PR マージ指標
- 30日以内にマージされた PR はありません
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
danielmiessler/LifeOS のほかの issue
-
難易度 1/5 1〜3時間 初心者へのやさしさ 84/100
danielmiessler/LifeOS#2655 ·
メンテナーはふだん 7 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
danielmiessler/LifeOS#2653 ·
メンテナーはふだん 7 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
danielmiessler/LifeOS#2639 ·
メンテナーはふだん 7 日以内に返信
-
MergeSettings: nested __merge annotations under a user-only key are written raw into settings.jsonオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
danielmiessler/LifeOS#2636 ·
メンテナーはふだん 7 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
danielmiessler/LifeOS#2635 ·
メンテナーはふだん 7 日以内に返信
danielmiessler/LifeOS の issue をすべて見る
似ている issue
-
難易度 1/5 1〜3時間 初心者へのやさしさ 88/100
danielw-sudo/agentuto#63 ·
メンテナーはふだん 1 日以内に返信
-
idea-scout idea:341
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
jeanibarz/agent-notebook#341 ·
メンテナーはふだん 1 日以内に返信
-
documentation good first issue roadmap
難易度 1/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
type:bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
nightgauge/nightgauge#3077 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
LibreChat-AI/LibreChat#17915 ·
メンテナーはふだん 1 日以内に返信