diff --git a/docs/wiki/The-Dashboard.md b/docs/wiki/The-Dashboard.md index 67ae2445..e0aee973 100644 --- a/docs/wiki/The-Dashboard.md +++ b/docs/wiki/The-Dashboard.md @@ -15,7 +15,7 @@ page says so and names the setting. | **Header, left** | The "C" logo (goes home) and the session list, unless you moved it to the sidebar. | | **Header, right** | Status chips and panel buttons, most of them off by default. | | **Center** | The terminal for the active session, or the home screen when nothing is selected. | -| **Bottom toolbar** | Run, Stop, Run Shell, the case picker, and the instance counters. | +| **Bottom toolbar** | Run, Stop, Run Shell, the case picker, and the instance counter. | | **Overlays** | Panels and modals: Respawn, Cron, Subagents, File Viewer, Settings. | ## Session list layout diff --git a/src/web/public/index.html b/src/web/public/index.html index e93baae0..af6a37b6 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -700,11 +700,9 @@ -
- - - -
+
index for a case's session tabs (e.g. w1-, // w2- for agents, s1- for shells), shared by the local and // remote/docker launch paths so all tabs follow the same naming convention. @@ -2082,7 +2069,9 @@ Object.assign(CodemanApp.prototype, { async runShell() { const caseName = document.getElementById('quickStartCase').value || 'testcase'; - const shellCount = Math.min(20, Math.max(1, parseInt(document.getElementById('shellCount').value) || 1)); + // Run Shell reads the toolbar's one instance stepper, like every other run*(); + // its own second `− 1 +` group (#shellCount) was removed (#428). + const shellCount = this._readTabCount(); const ownsLaunchTerminal = this._beginSessionLaunchStatus( `Starting ${shellCount} Shell session(s) in ${caseName}...`, diff --git a/test/run-mode-ui.test.ts b/test/run-mode-ui.test.ts index b290cf8f..21c1c8af 100644 --- a/test/run-mode-ui.test.ts +++ b/test/run-mode-ui.test.ts @@ -734,7 +734,9 @@ describe('case selector refresh', () => { it('creates remote shell sessions by caseName instead of remote display path', async () => { const elements: Record = { quickStartCase: { value: 'gpu-work' }, - shellCount: { value: '1' }, + // The toolbar's one instance stepper, shared by Run and Run Shell since + // the second (#shellCount) group was removed. + tabCount: { value: '1' }, }; const requests: Array<{ url: string; body?: any }> = []; const CodemanApp = function CodemanApp(this: any) {}; @@ -871,6 +873,45 @@ describe('case selector refresh', () => { }); }); +describe('toolbar instance count', () => { + // Run Shell used to carry its own `#shellCount` stepper next to the Run one. + // It was removed (#428), so every launch path reads #tabCount through + // _readTabCount(), and an absent stepper (phones and tablets hide the group) + // has to read as 1, not throw. + function loadCounter(elements: Record) { + const CodemanApp = function CodemanApp(this: any) {}; + const context = vm.createContext({ + CodemanApp, + localStorage: { getItem: () => null, setItem: () => {} }, + document: { getElementById: (id: string) => elements[id] ?? null }, + console, + }); + const sessionUi = readFileSync(resolve(import.meta.dirname, '../src/web/public/session-ui.js'), 'utf8'); + vm.runInContext(sessionUi, context, { filename: 'session-ui.js' }); + return new (CodemanApp as any)(); + } + + it('reads the shared stepper and falls back to 1 when it is absent', () => { + expect(loadCounter({ tabCount: { value: '3' } })._readTabCount()).toBe(3); + expect(loadCounter({})._readTabCount()).toBe(1); + expect(loadCounter({ tabCount: { value: '' } })._readTabCount()).toBe(1); + expect(loadCounter({ tabCount: { value: '0' } })._readTabCount()).toBe(1); + expect(loadCounter({ tabCount: { value: '99' } })._readTabCount()).toBe(20); + }); + + it('no longer exposes the removed shell stepper handlers', () => { + const app = loadCounter({ tabCount: { value: '1' } }); + expect(app.incrementShellCount).toBeUndefined(); + expect(app.decrementShellCount).toBeUndefined(); + }); + + it('ships one stepper in the toolbar markup, not two', () => { + const html = readFileSync(resolve(import.meta.dirname, '../src/web/public/index.html'), 'utf8'); + expect(html).not.toContain('id="shellCount"'); + expect(html.match(/class="tab-count-group"/g)).toHaveLength(1); + }); +}); + describe('Gemini quick start', () => { // Regression guard for the ApiResponse-envelope unwrap in runGemini(): the // status check must read `.data.available` and the quick-start response must @@ -1233,7 +1274,6 @@ describe('case lookup before a local launch', () => { function loadLaunchHarness(caseAnswer: Record) { const elements: Record = { quickStartCase: { value: 'nas-case' }, - shellCount: { value: '1' }, tabCount: { value: '1' }, }; const requests: Array<{ url: string; method?: string }> = [];