mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
Post-merge follow-ups from the deep review of #334 and #335, so they ship in the same release as the features. Tab-layout foundation (#335): - PUT /api/session-order drops unknown/foreign ids again instead of 400ing the whole write, in both the owner and the admin path (single-user requests are the synthetic admin, so that path is the one the browser hits). The frontend debounces its reorder push and swallows errors, so a session deleted inside the debounce window silently cost the user the entire reorder - and the endpoint sits on the stable /api/v1 surface, where the pre-layout server merged leniently. - A failed mux restore no longer locks explicit deletions into 500s for the process lifetime: runSessionDeletion and webviewDeleted degrade to best-effort without layout coordination, while the automated stale sweep (runStaleSessionCleanup) stays fail-closed. - sse-events doc comment: no 'suppressed' hook event exists; hooks stay 8. - registerSessionWithLayout resolves its owner through ownerLayoutKey() instead of a hardcoded '@single'. Vertical rail (#334) - all rail-awareness gaps in sidebar-only predicates, unified behind the new _isVerticalTabList() (sidebar OR rail): - Drag-reorder read the insertion side from clientX in the rail, so before/after was effectively arbitrary on vertical rows; the drag-over indicators now draw as top/bottom edges there like the sidebar's. - The active tab is scrolled into view in the rail (Alt+N/palette selection used to leave the row below the fold). - Floating subagent/ultracode windows anchor to the RIGHT of rail tabs, and the connector redraw gates (render tail + strip scroll) cover the rail. - Server-seeded tabOrientation is applied when the async settings load resolves, not only at boot, so a fresh device shows the rail immediately. - The pre-paint script stamps data-tab-orientation and --tab-rail-width (sidebar-wins and solo carve-outs included), removing the flash of the header strip on every vertical-mode load. - The session name font defaults to 12px, the sidebar's historical 0.75rem size, so installs that never touch the new slider are not restyled. Also documents the rail in CLAUDE.md (second #sessionTabs host, mover ordering, the axis-predicate rule) and gives tab-rail-resize.js its @dependency/@loadorder header. Full gate green (6093 tests); the excluded browser suite was run by hand - only the known environmental failures (opencode/codex binaries) remain, identical to pristine master. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
4.1 KiB
TypeScript
76 lines
4.1 KiB
TypeScript
/** Structural and schema coverage for vertical session navigation density and actions. */
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { SettingsUpdateSchema } from '../src/web/schemas.js';
|
|
|
|
const publicDir = resolve(import.meta.dirname, '../src/web/public');
|
|
const app = readFileSync(resolve(publicDir, 'app.js'), 'utf8');
|
|
const html = readFileSync(resolve(publicDir, 'index.html'), 'utf8');
|
|
const settingsUi = readFileSync(resolve(publicDir, 'settings-ui.js'), 'utf8');
|
|
const styles = readFileSync(resolve(publicDir, 'styles.css'), 'utf8');
|
|
const i18n = readFileSync(resolve(publicDir, 'i18n.js'), 'utf8');
|
|
const railController = readFileSync(resolve(publicDir, 'tab-rail-resize.js'), 'utf8');
|
|
|
|
describe('vertical session navigation UX contract', () => {
|
|
it('accepts only integer session-name sizes from 11 through 18 pixels', () => {
|
|
for (const value of [11, 14, 18]) {
|
|
expect(SettingsUpdateSchema.safeParse({ sessionSidebarFontSize: value }).success).toBe(true);
|
|
}
|
|
for (const value of [10, 19, 14.5, '14']) {
|
|
expect(SettingsUpdateSchema.safeParse({ sessionSidebarFontSize: value }).success).toBe(false);
|
|
}
|
|
});
|
|
|
|
it('renders one existing action cluster through a shared placement resolver', () => {
|
|
expect(app).toContain('shouldInlineSessionActions()');
|
|
expect(app).toContain('const inlineSessionActions = this.shouldInlineSessionActions();');
|
|
expect(app).toContain('const tabActionsHtml =');
|
|
expect(app).toContain("${inlineSessionActions ? tabActionsHtml : ''}");
|
|
expect(app).toContain("${inlineSessionActions ? '' : tabActionsHtml}");
|
|
expect(app.match(/class="tab-actions"/g)).toHaveLength(1);
|
|
expect(app).toContain("tab.querySelector(':scope > .tab-actions')");
|
|
});
|
|
|
|
it('limits inline actions to expanded sidebar and expanded non-compact rail', () => {
|
|
expect(app).toMatch(/isSessionSidebarActive\(\)[\s\S]{0,100}!this\.isSessionSidebarCollapsed\(\)/);
|
|
expect(app).toMatch(/_tabOrientation\(\) === 'vertical'[\s\S]{0,120}tab-rail-compact/);
|
|
expect(styles).toContain("html[data-session-list='sidebar'][data-sidebar='expanded']");
|
|
expect(styles).toContain("html[data-tab-orientation='vertical']:not(.tab-rail-compact)");
|
|
});
|
|
|
|
it('opens only the existing session actions from the overflow trigger', () => {
|
|
expect(railController).toContain('openTabRailActionMenu(event, sessionId)');
|
|
expect(railController).toContain("label: 'Session options'");
|
|
expect(railController).toContain("label: 'Open in a new window'");
|
|
expect(railController).toContain("label: 'Close session'");
|
|
expect(railController).not.toContain('Move to group');
|
|
});
|
|
|
|
it('wires the name-only size through first paint, settings, defaults, and both vertical surfaces', () => {
|
|
expect(html).toMatch(/id="appSettingsSessionSidebarFontSize"[^>]*min="11"[^>]*max="18"[^>]*step="1"/);
|
|
expect(html).toContain('aria-labelledby="appSettingsSessionSidebarFontSizeLabel"');
|
|
expect(html).toContain('--session-sidebar-name-font-size');
|
|
expect(settingsUi).toContain('sessionSidebarFontSize: this.resolveSessionSidebarFontSize(');
|
|
// 12 = the sidebar's historical 0.75rem name size: the default must
|
|
// never restyle an install whose user never touched the slider.
|
|
expect(settingsUi).toContain('sessionSidebarFontSize: 12,');
|
|
expect(settingsUi).toContain("'sessionSidebarFontSize'");
|
|
expect(app).toContain('resolveSessionSidebarFontSize(value)');
|
|
expect(app).toContain('applySessionSidebarFontSize(settings = null)');
|
|
expect(styles).toMatch(
|
|
/\.session-sidebar \.tab-name[^}]*font-size: var\(--session-sidebar-name-font-size, 12px\)/s
|
|
);
|
|
expect(styles).toMatch(
|
|
/\.tab-rail \.session-tab \.tab-name[^}]*font-size: var\(--session-sidebar-name-font-size, 12px\)/s
|
|
);
|
|
});
|
|
|
|
it('labels and translates the name-only scope', () => {
|
|
expect(html).toContain('Session Name Font Size');
|
|
expect(html).toContain('Adjust only session names in the vertical sidebar.');
|
|
expect(i18n).toContain("'Session Name Font Size':");
|
|
expect(i18n).toContain("'Adjust only session names in the vertical sidebar.':");
|
|
});
|
|
});
|