mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
Merge #428: one toolbar instance stepper, not two
Only the stepper half of #428 lands here. Its other half, the phone case picker search, shipped separately in #488 (1.33.1) and is kept as it is on master, so every file of this merge starts from ours and only the stepper changes are ported onto it: - index.html: the second `− 1 +` group (#shellCount) after Run Shell is gone. - session-ui.js: incrementShellCount/decrementShellCount are removed and runShell() reads the toolbar's one stepper through _readTabCount(), the helper master grew since #428 was opened (same clamp and absent-element fallback as #428's _toolbarInstanceCount()). - run-mode-ui tests stub #tabCount instead of #shellCount, plus #428's "toolbar instance count" block adapted to _readTabCount() and a markup check. - wiki: "the instance counter", singular. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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, 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. |
|
| **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. |
|
| **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. |
|
| **Overlays** | Panels and modals: Respawn, Cron, Subagents, File Viewer, Settings. |
|
||||||
|
|
||||||
## Session list layout
|
## Session list layout
|
||||||
|
|||||||
@@ -700,11 +700,9 @@
|
|||||||
<button class="btn-toolbar btn-enter" onclick="app.sendEnterKey()" title="Send Enter">
|
<button class="btn-toolbar btn-enter" onclick="app.sendEnterKey()" title="Send Enter">
|
||||||
Enter
|
Enter
|
||||||
</button>
|
</button>
|
||||||
<div class="tab-count-group" title="Instance count">
|
<!-- Run Shell had a second, identical instance-count stepper here. The
|
||||||
<button class="tab-count-btn" onclick="app.decrementShellCount()">−</button>
|
toolbar carried two of them side by side, so it is gone and Run
|
||||||
<input type="number" id="shellCount" class="tab-count-input" value="1" min="1" max="20" readonly>
|
Shell reads the one above (#tabCount) like the Run button does. -->
|
||||||
<button class="tab-count-btn" onclick="app.incrementShellCount()">+</button>
|
|
||||||
</div>
|
|
||||||
<div class="case-select-group">
|
<div class="case-select-group">
|
||||||
<div class="case-combobox" id="quickStartCasePicker">
|
<div class="case-combobox" id="quickStartCasePicker">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1803,19 +1803,6 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
input.value = Math.max(1, current - 1);
|
input.value = Math.max(1, current - 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Shell count stepper functions
|
|
||||||
incrementShellCount() {
|
|
||||||
const input = document.getElementById('shellCount');
|
|
||||||
const current = parseInt(input.value) || 1;
|
|
||||||
input.value = Math.min(20, current + 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
decrementShellCount() {
|
|
||||||
const input = document.getElementById('shellCount');
|
|
||||||
const current = parseInt(input.value) || 1;
|
|
||||||
input.value = Math.max(1, current - 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Next free <prefix><n> index for a case's session tabs (e.g. w1-<case>,
|
// Next free <prefix><n> index for a case's session tabs (e.g. w1-<case>,
|
||||||
// w2-<case> for agents, s1-<case> for shells), shared by the local and
|
// w2-<case> for agents, s1-<case> for shells), shared by the local and
|
||||||
// remote/docker launch paths so all tabs follow the same naming convention.
|
// remote/docker launch paths so all tabs follow the same naming convention.
|
||||||
@@ -2082,7 +2069,9 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
|
|
||||||
async runShell() {
|
async runShell() {
|
||||||
const caseName = document.getElementById('quickStartCase').value || 'testcase';
|
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(
|
const ownsLaunchTerminal = this._beginSessionLaunchStatus(
|
||||||
`Starting ${shellCount} Shell session(s) in ${caseName}...`,
|
`Starting ${shellCount} Shell session(s) in ${caseName}...`,
|
||||||
|
|||||||
@@ -734,7 +734,9 @@ describe('case selector refresh', () => {
|
|||||||
it('creates remote shell sessions by caseName instead of remote display path', async () => {
|
it('creates remote shell sessions by caseName instead of remote display path', async () => {
|
||||||
const elements: Record<string, any> = {
|
const elements: Record<string, any> = {
|
||||||
quickStartCase: { value: 'gpu-work' },
|
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 requests: Array<{ url: string; body?: any }> = [];
|
||||||
const CodemanApp = function CodemanApp(this: 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<string, any>) {
|
||||||
|
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', () => {
|
describe('Gemini quick start', () => {
|
||||||
// Regression guard for the ApiResponse-envelope unwrap in runGemini(): the
|
// Regression guard for the ApiResponse-envelope unwrap in runGemini(): the
|
||||||
// status check must read `.data.available` and the quick-start response must
|
// 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<string, unknown>) {
|
function loadLaunchHarness(caseAnswer: Record<string, unknown>) {
|
||||||
const elements: Record<string, any> = {
|
const elements: Record<string, any> = {
|
||||||
quickStartCase: { value: 'nas-case' },
|
quickStartCase: { value: 'nas-case' },
|
||||||
shellCount: { value: '1' },
|
|
||||||
tabCount: { value: '1' },
|
tabCount: { value: '1' },
|
||||||
};
|
};
|
||||||
const requests: Array<{ url: string; method?: string }> = [];
|
const requests: Array<{ url: string; method?: string }> = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user