mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
feat(split-pane): add showSplitButton setting and header button
This commit is contained in:
@@ -191,6 +191,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="btn-icon-header btn-file-viewer" onclick="app.toggleFileBrowserButton()" title="File Viewer" aria-label="Open file viewer" aria-expanded="false"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></button>
|
<button class="btn-icon-header btn-file-viewer" onclick="app.toggleFileBrowserButton()" title="File Viewer" aria-label="Open file viewer" aria-expanded="false"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></button>
|
||||||
<button class="btn-icon-header btn-multimonitor btn-multimonitor--hidden" onclick="app.launchMultiMonitor()" title="Open Codeman across all displays" aria-label="Open Codeman across all displays"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg></button>
|
<button class="btn-icon-header btn-multimonitor btn-multimonitor--hidden" onclick="app.launchMultiMonitor()" title="Open Codeman across all displays" aria-label="Open Codeman across all displays"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg></button>
|
||||||
|
<button class="btn-icon-header btn-split btn-split--hidden" onclick="app.openSplitPicker()" title="Split: open a second session beside this one" aria-label="Split: open a second session beside this one"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="18" rx="2"/><line x1="12" y1="3" x2="12" y2="21"/></svg></button>
|
||||||
<button class="btn-icon-header btn-ultracode-agents btn-ultracode-agents--hidden" onclick="app.toggleUltracodeAgentsPanel()" title="Ultracode / Workflow agents" aria-label="Open ultracode workflow agents"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><path d="M8.2 7.2 15.6 11M8.2 16.8 15.6 13"/></svg></button>
|
<button class="btn-icon-header btn-ultracode-agents btn-ultracode-agents--hidden" onclick="app.toggleUltracodeAgentsPanel()" title="Ultracode / Workflow agents" aria-label="Open ultracode workflow agents"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><path d="M8.2 7.2 15.6 11M8.2 16.8 15.6 13"/></svg></button>
|
||||||
<div class="header-plan-usage header-plan-usage--hidden" id="planUsageChip" title="Claude and Codex plan usage limits">—</div>
|
<div class="header-plan-usage header-plan-usage--hidden" id="planUsageChip" title="Claude and Codex plan usage limits">—</div>
|
||||||
<button class="btn-icon-header btn-notifications" onclick="app.toggleNotifications()" title="Notifications" aria-label="Toggle notifications" style="display:none;">
|
<button class="btn-icon-header btn-notifications" onclick="app.toggleNotifications()" title="Notifications" aria-label="Toggle notifications" style="display:none;">
|
||||||
@@ -1924,6 +1925,7 @@
|
|||||||
<label class="set-chip" data-preview="header" data-preview-order="9"><input type="checkbox" id="appSettingsShowAttachmentsButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg><span>Attachments</span></label>
|
<label class="set-chip" data-preview="header" data-preview-order="9"><input type="checkbox" id="appSettingsShowAttachmentsButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg><span>Attachments</span></label>
|
||||||
<label class="set-chip" data-preview="header" data-preview-order="10"><input type="checkbox" id="appSettingsShowFileViewerButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span>File Viewer</span></label>
|
<label class="set-chip" data-preview="header" data-preview-order="10"><input type="checkbox" id="appSettingsShowFileViewerButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span>File Viewer</span></label>
|
||||||
<label class="set-chip" data-preview="header" data-preview-order="11"><input type="checkbox" id="appSettingsShowMultiMonitorButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg><span>Multi-monitor</span></label>
|
<label class="set-chip" data-preview="header" data-preview-order="11"><input type="checkbox" id="appSettingsShowMultiMonitorButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg><span>Multi-monitor</span></label>
|
||||||
|
<label class="set-chip" data-preview="header" data-preview-order="12"><input type="checkbox" id="appSettingsShowSplitButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="18" rx="2"/><line x1="12" y1="3" x2="12" y2="21"/></svg><span>Split</span></label>
|
||||||
<label class="set-chip" data-preview="header" data-preview-order="13" data-preview-text="42%"><input type="checkbox" id="appSettingsShowPlanUsageLimits"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18a8 8 0 1 1 16 0"/><path d="M12 18l4.5-5"/></svg><span>Plan Usage</span></label>
|
<label class="set-chip" data-preview="header" data-preview-order="13" data-preview-text="42%"><input type="checkbox" id="appSettingsShowPlanUsageLimits"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18a8 8 0 1 1 16 0"/><path d="M12 18l4.5-5"/></svg><span>Plan Usage</span></label>
|
||||||
<label class="set-chip" data-preview="header" data-preview-order="14"><input type="checkbox" id="appSettingsShowLifecycleLog"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg><span>Lifecycle Log</span></label>
|
<label class="set-chip" data-preview="header" data-preview-order="14"><input type="checkbox" id="appSettingsShowLifecycleLog"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg><span>Lifecycle Log</span></label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -407,6 +407,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
document.getElementById('appSettingsUltracodeFloatingWindows').checked =
|
document.getElementById('appSettingsUltracodeFloatingWindows').checked =
|
||||||
settings.ultracodeFloatingWindows ?? defaults.ultracodeFloatingWindows ?? false;
|
settings.ultracodeFloatingWindows ?? defaults.ultracodeFloatingWindows ?? false;
|
||||||
document.getElementById('appSettingsShowMultiMonitorButton').checked = settings.showMultiMonitorButton ?? defaults.showMultiMonitorButton ?? false;
|
document.getElementById('appSettingsShowMultiMonitorButton').checked = settings.showMultiMonitorButton ?? defaults.showMultiMonitorButton ?? false;
|
||||||
|
document.getElementById('appSettingsShowSplitButton').checked = settings.showSplitButton ?? defaults.showSplitButton ?? false;
|
||||||
document.getElementById('appSettingsShowPlanUsageLimits').checked = this.planUsageChipEnabled(settings);
|
document.getElementById('appSettingsShowPlanUsageLimits').checked = this.planUsageChipEnabled(settings);
|
||||||
document.getElementById('appSettingsShowRedrawButton').checked = settings.showRedrawButton ?? defaults.showRedrawButton ?? false;
|
document.getElementById('appSettingsShowRedrawButton').checked = settings.showRedrawButton ?? defaults.showRedrawButton ?? false;
|
||||||
// Phone overview home screen: only meaningful under 600px, so the row is
|
// Phone overview home screen: only meaningful under 600px, so the row is
|
||||||
@@ -2120,6 +2121,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
readMyMindEnabled: document.getElementById('appSettingsReadMyMind').checked,
|
readMyMindEnabled: document.getElementById('appSettingsReadMyMind').checked,
|
||||||
ultracodeFloatingWindows: document.getElementById('appSettingsUltracodeFloatingWindows').checked,
|
ultracodeFloatingWindows: document.getElementById('appSettingsUltracodeFloatingWindows').checked,
|
||||||
showMultiMonitorButton: document.getElementById('appSettingsShowMultiMonitorButton').checked,
|
showMultiMonitorButton: document.getElementById('appSettingsShowMultiMonitorButton').checked,
|
||||||
|
showSplitButton: document.getElementById('appSettingsShowSplitButton').checked,
|
||||||
showPlanUsageLimits: document.getElementById('appSettingsShowPlanUsageLimits').checked,
|
showPlanUsageLimits: document.getElementById('appSettingsShowPlanUsageLimits').checked,
|
||||||
showRedrawButton: document.getElementById('appSettingsShowRedrawButton').checked,
|
showRedrawButton: document.getElementById('appSettingsShowRedrawButton').checked,
|
||||||
mobileOverviewEnabled: document.getElementById('appSettingsMobileOverview').checked,
|
mobileOverviewEnabled: document.getElementById('appSettingsMobileOverview').checked,
|
||||||
@@ -2742,6 +2744,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
showUltracodeAgents: false,
|
showUltracodeAgents: false,
|
||||||
ultracodeFloatingWindows: false,
|
ultracodeFloatingWindows: false,
|
||||||
showMultiMonitorButton: false,
|
showMultiMonitorButton: false,
|
||||||
|
showSplitButton: false,
|
||||||
// Desktop defaults this ON (see planUsageChipEnabled); handhelds keep it
|
// Desktop defaults this ON (see planUsageChipEnabled); handhelds keep it
|
||||||
// OFF so the phone header stays minimal and the mobile-header-buttons
|
// OFF so the phone header stays minimal and the mobile-header-buttons
|
||||||
// policy guard keeps passing.
|
// policy guard keeps passing.
|
||||||
@@ -2947,6 +2950,13 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
multiMonitorBtn.classList.toggle('btn-multimonitor--hidden', !showMultiMonitorButton);
|
multiMonitorBtn.classList.toggle('btn-multimonitor--hidden', !showMultiMonitorButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split button — hidden by default
|
||||||
|
const showSplitButton = settings.showSplitButton ?? defaults.showSplitButton ?? false;
|
||||||
|
const splitBtn = document.querySelector('.btn-split');
|
||||||
|
if (splitBtn) {
|
||||||
|
splitBtn.classList.toggle('btn-split--hidden', !showSplitButton);
|
||||||
|
}
|
||||||
|
|
||||||
// Ultracode/Workflow agents launcher — hidden by default; reveal when enabled.
|
// Ultracode/Workflow agents launcher — hidden by default; reveal when enabled.
|
||||||
// Marker class only (base is display:inline-flex !important) so it's auto-excluded
|
// Marker class only (base is display:inline-flex !important) so it's auto-excluded
|
||||||
// from the mobile-header-buttons-policy guard.
|
// from the mobile-header-buttons-policy guard.
|
||||||
|
|||||||
@@ -1376,6 +1376,7 @@ export const SettingsUpdateSchema = z
|
|||||||
showFileBrowser: z.boolean().optional(),
|
showFileBrowser: z.boolean().optional(),
|
||||||
showSubagents: z.boolean().optional(),
|
showSubagents: z.boolean().optional(),
|
||||||
showMultiMonitorButton: z.boolean().optional(),
|
showMultiMonitorButton: z.boolean().optional(),
|
||||||
|
showSplitButton: z.boolean().optional(),
|
||||||
// Doubles as the plan-usage telemetry COLLECTION switch, read fresh from
|
// Doubles as the plan-usage telemetry COLLECTION switch, read fresh from
|
||||||
// disk by readPlanUsageTelemetryEnabled() (hooks-config.ts) at every claude
|
// disk by readPlanUsageTelemetryEnabled() (hooks-config.ts) at every claude
|
||||||
// session create/respawn — not just the chip's DISPLAY preference. See that
|
// session create/respawn — not just the chip's DISPLAY preference. See that
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { createRouteTestHarness, type RouteTestHarness } from './_route-test-utils.js';
|
||||||
|
import { registerSystemRoutes } from '../../src/web/routes/system-routes.js';
|
||||||
|
|
||||||
|
const { subagentWatcher, imageWatcher, workflowRunWatcher, fileSystem } = vi.hoisted(() => {
|
||||||
|
const makeWatcher = () => ({
|
||||||
|
isRunning: vi.fn(() => false),
|
||||||
|
start: vi.fn(),
|
||||||
|
stop: vi.fn(),
|
||||||
|
getStats: vi.fn(() => ({})),
|
||||||
|
watchSession: vi.fn(),
|
||||||
|
getRecentRunSummaries: vi.fn(() => []),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate in-memory file storage
|
||||||
|
let storedSettings: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
subagentWatcher: makeWatcher(),
|
||||||
|
imageWatcher: makeWatcher(),
|
||||||
|
workflowRunWatcher: makeWatcher(),
|
||||||
|
fileSystem: {
|
||||||
|
storedSettings,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('node:fs/promises', () => ({
|
||||||
|
default: {
|
||||||
|
readFile: vi.fn(async () => JSON.stringify(fileSystem.storedSettings)),
|
||||||
|
writeFile: vi.fn(async (_path: string, content: string) => {
|
||||||
|
fileSystem.storedSettings = JSON.parse(content);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('node:fs', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('node:fs')>();
|
||||||
|
return { ...actual, existsSync: vi.fn(() => true), mkdirSync: vi.fn(), readdirSync: vi.fn(() => []) };
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('../../src/subagent-watcher.js', () => ({ subagentWatcher }));
|
||||||
|
vi.mock('../../src/image-watcher.js', () => ({ imageWatcher }));
|
||||||
|
vi.mock('../../src/workflow-run-watcher.js', () => ({ workflowRunWatcher }));
|
||||||
|
|
||||||
|
describe('showSplitButton setting', () => {
|
||||||
|
let harness: RouteTestHarness;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
harness = await createRouteTestHarness(registerSystemRoutes);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await harness.app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('round-trips through PUT and GET /api/settings', async () => {
|
||||||
|
const putRes = await harness.app.inject({
|
||||||
|
method: 'PUT',
|
||||||
|
url: '/api/settings',
|
||||||
|
payload: { showSplitButton: true },
|
||||||
|
});
|
||||||
|
expect(putRes.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const getRes = await harness.app.inject({ method: 'GET', url: '/api/settings' });
|
||||||
|
const body = JSON.parse(getRes.body);
|
||||||
|
expect(body.showSplitButton).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user