mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
feat(tabs): COD-358 add resizable vertical session rail
This commit is contained in:
@@ -34,6 +34,7 @@ const SCHEMAS = readFileSync(new URL('../src/web/schemas.ts', import.meta.url),
|
||||
|
||||
interface LayoutApp {
|
||||
soloSessionId: string | null;
|
||||
isSoloWindow: boolean;
|
||||
sessions: Map<string, unknown>;
|
||||
sessionOrder: string[];
|
||||
_tallTabsEnabled?: boolean;
|
||||
@@ -44,6 +45,7 @@ interface LayoutApp {
|
||||
isSessionSidebarActive(): boolean;
|
||||
isSessionSidebarCollapsed(): boolean;
|
||||
applySessionListLayout(): void;
|
||||
applyTabOrientation(): void;
|
||||
toggleSessionSidebar(): void;
|
||||
updateSidebarCount(): void;
|
||||
closeSessionSidebarOnHandheld(): void;
|
||||
@@ -86,6 +88,7 @@ const SHELL = `
|
||||
</div>
|
||||
</header>
|
||||
<main class="main">
|
||||
<div class="tab-rail" id="tabRail"></div>
|
||||
<aside class="session-sidebar" id="sessionSidebar" aria-label="Sessions">
|
||||
<div class="session-sidebar-head">
|
||||
<span class="session-sidebar-title">Sessions</span>
|
||||
@@ -154,6 +157,7 @@ function boot(
|
||||
// terminal stack. Only the layout surface is under test here.
|
||||
const app = Object.create(win.__CodemanApp.prototype) as LayoutApp;
|
||||
app.soloSessionId = options.solo ?? null;
|
||||
app.isSoloWindow = !!app.soloSessionId;
|
||||
app.sessions = new Map();
|
||||
app.sessionOrder = [];
|
||||
app._elemCache = new Map();
|
||||
@@ -177,6 +181,39 @@ describe('session list layout', () => {
|
||||
expect(toggleBtn(win).classList.contains('btn-sidebar-toggle--hidden')).toBe(true);
|
||||
});
|
||||
|
||||
it('preserves vertical rail ownership when the session-list layout reapplies', () => {
|
||||
const { win, app } = boot({ stored: { sessionListLayout: 'header', tabOrientation: 'vertical' } });
|
||||
|
||||
app.applySessionListLayout();
|
||||
app.applyTabOrientation();
|
||||
expect(tabsEl(win).parentElement?.id).toBe('tabRail');
|
||||
|
||||
app.applySessionListLayout();
|
||||
|
||||
expect(tabsEl(win).parentElement?.id).toBe('tabRail');
|
||||
expect(tabsEl(win).getAttribute('aria-orientation')).toBe('vertical');
|
||||
});
|
||||
|
||||
it('keeps aria orientation synchronized when tab orientation moves hosts', () => {
|
||||
const { win, app } = boot({ stored: { sessionListLayout: 'header', tabOrientation: 'vertical' } });
|
||||
|
||||
app.applySessionListLayout();
|
||||
app.applyTabOrientation();
|
||||
|
||||
expect(tabsEl(win).parentElement?.id).toBe('tabRail');
|
||||
expect(tabsEl(win).getAttribute('aria-orientation')).toBe('vertical');
|
||||
|
||||
win.localStorage.setItem(
|
||||
'codeman-app-settings',
|
||||
JSON.stringify({ sessionListLayout: 'header', tabOrientation: 'horizontal' })
|
||||
);
|
||||
delete (app as unknown as { _cachedAppSettings?: unknown })._cachedAppSettings;
|
||||
app.applyTabOrientation();
|
||||
|
||||
expect(tabsEl(win).parentElement?.id).toBe('sessionTabsHost');
|
||||
expect(tabsEl(win).getAttribute('aria-orientation')).toBe('horizontal');
|
||||
});
|
||||
|
||||
it('re-parents the tab list into the sidebar and flips the a11y state', () => {
|
||||
const { win, app } = boot({ stored: { sessionListLayout: 'sidebar' } });
|
||||
expect(app.getSessionListLayout()).toBe('sidebar');
|
||||
@@ -225,6 +262,20 @@ describe('session list layout', () => {
|
||||
expect(tabsEl(win).parentElement?.id).toBe('sessionTabsHost');
|
||||
});
|
||||
|
||||
it('forces horizontal tabs in a solo window even when vertical orientation is preferred', () => {
|
||||
const { win, app } = boot({
|
||||
stored: { sessionListLayout: 'header', tabOrientation: 'vertical' },
|
||||
solo: 'sess-1',
|
||||
});
|
||||
|
||||
app.applySessionListLayout();
|
||||
app.applyTabOrientation();
|
||||
|
||||
expect(win.document.documentElement.dataset.tabOrientation).toBe('horizontal');
|
||||
expect(tabsEl(win).parentElement?.id).toBe('sessionTabsHost');
|
||||
expect(tabsEl(win).getAttribute('aria-orientation')).toBe('horizontal');
|
||||
});
|
||||
|
||||
it('round-trips the collapse state through its own storage key', () => {
|
||||
// Deliberately NOT in the app-settings blob: saveAppSettings() rebuilds that
|
||||
// blob from the DOM controls, so a key without a control is wiped on Save.
|
||||
|
||||
Reference in New Issue
Block a user