// Shell link handling on the real dockview shell (preview harness `/?shell&split`, needs `vite dev`): // "beside" links split once and reuse only their own side pane (never a feed the user opened), a link // inside a side pane stays there, a plain new-tab link stays in its group, and a late `focused` // report from a tab that a link just replaced does not bring it back. // // bun run dev (in another terminal) then node scripts/shell-links-check.mjs [baseUrl] import { chromium } from 'playwright'; const base = process.argv[2] ?? 'http://127.0.0.1:1420'; const failures = []; const check = (label, ok, detail = '') => { console.log(`${ok ? 'ok ' : 'FAIL'} ${label}${detail ? ` (${detail})` : ''}`); if (!ok) failures.push(label); }; const browser = await chromium.launch(); const page = await browser.newPage({ viewport: { width: 1500, height: 900 } }); await page.goto(`${base}/?shell&split`); await page.waitForFunction(() => window.__btEmit && window.__btShellLayout?.()?.groups.length >= 2); await page.waitForTimeout(300); const layout = () => page.evaluate(() => window.__btShellLayout()); const emit = (event, payload) => page.evaluate(([event, payload]) => window.__btEmit(event, payload), [event, payload]); const settle = () => page.waitForTimeout(250); const tabs = state => state.groups.map(group => group.urls.map(url => url.replace(/^https?:\/\//, ''))); const start = await layout(); const feed = start.groups[1]; check('harness starts with two groups side by side', start.groups.length === 2, JSON.stringify(tabs(start))); // 1. A beside link from the left feed must not land in the right feed: it gets its own split. await emit('tab-open', { url: 'https://example.com/one', from: 'tab0', background: false, beside: true }); await settle(); const one = await layout(); const side = one.groups.find(group => group.urls.includes('https://example.com/one')); check('beside link from the left group splits a new pane', one.groups.length === 3 && side && side.id !== feed.id && side.id !== start.groups[0].id, JSON.stringify(tabs(one))); check('the right feed keeps its tabs', JSON.stringify(one.groups.find(group => group.id === feed.id)?.tabs) === JSON.stringify(feed.tabs)); check('the side pane sits right of the source', side && side.left > one.groups[0].left); // 2. The next beside link from the same source reuses that side pane. await emit('tab-open', { url: 'https://example.com/two', from: 'tab0', background: false, beside: true }); await settle(); const two = await layout(); const side2 = two.groups.find(group => group.id === side?.id); check('second beside link reuses the side pane', two.groups.length === 3 && side2?.urls.includes('https://example.com/two'), JSON.stringify(tabs(two))); // 3. A beside link clicked inside the side pane stays in it. await emit('tab-open', { url: 'https://example.com/three', from: side2?.active, background: false, beside: true }); await settle(); const three = await layout(); check('a beside link inside the side pane stays there', three.groups.length === 3 && three.groups.find(group => group.id === side?.id)?.urls.includes('https://example.com/three'), JSON.stringify(tabs(three))); // 4. A plain new-tab link opens in its group and becomes active; a late focus from the replaced tab is ignored. await emit('tab-open', { url: 'https://example.com/tab', from: 'tab0', background: false, beside: false }); await settle(); const opened = await layout(); const source = opened.groups.find(group => group.tabs.includes('tab0')); const newTab = source.tabs[source.urls.indexOf('https://example.com/tab')]; check('a new-tab link opens in the source group, active', !!newTab && source.active === newTab, JSON.stringify(source)); await emit('tab-state', { id: 'tab0', focused: true }); await settle(); const afterFocus = (await layout()).groups.find(group => group.tabs.includes('tab0')); check('a late focus from the replaced tab does not re-activate it', afterFocus.active === newTab, `${newTab} -> ${afterFocus.active}`); // 5. A focused report from a visible pane still activates it (clicking into a split). await emit('tab-state', { id: feed.active, focused: true }); await settle(); check('a focused visible pane becomes the active tab', (await layout()).active === feed.active); await browser.close(); console.log(failures.length ? `\n${failures.length} failed` : '\nall passed'); process.exit(failures.length ? 1 : 0);