Files
pastilhasandClaude Opus 5 c0e0848e6a pairing moves in, and the extension download stops being a snapshot
Two halves of the same coupling, both left behind by this morning's extraction.

The pairing UI was Settings/IntegrationsSettings/BrowserRelay.tsx — a PLATFORM screen driving this
plugin's pairing, calling /browser/relay-token, and working only because the plugin happens to serve
that path. It is a panel here now, beside the tab list rather than buried in settings: it shows
whether the extension is connected and how many tabs it holds, which is the first thing anyone looks
at when a capture does not work, and that is not configuration you set once.

The download link pointed at public/browser-relay-extension.zip, a static file. That file was 58 lines
behind the source it claimed to be — built in August from something that predated a February commit,
never rebuilt, and serving an extension missing the multi-session recovery path that the same February
commit added. Anyone downloading it got the version that cannot recover from exactly the failure they
were about to hit.

So the archive is built from the extension directory per request. There is no second copy to fall
behind, which is the only durable fix for that class.

zip.ts is hand-rolled and stored rather than deflated. The route's own history says it used to shell
out to `zip`; this manifest's "nothing to declare" is worth more than 30 KB on a download that happens
once, and Chrome accepts stored entries. Timestamps are fixed at the format's zero so the same
directory always produces identical bytes — which is what makes the ETag meaningful and "did this
change" answerable by comparing two files. Verified by unzipping the output and diffing background.js
against the source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:20:32 +00:00

41 lines
1.4 KiB
TypeScript

import type { LayoutNode } from 'officerdev';
// The tab list beside a preview, with chat underneath it — the shape the screen had before the
// extraction, with the `appType: null` placeholders replaced by the panels' real keys.
//
// Chat is deliberately still here. It is the platform's own panel, not this plugin's, and the point of
// the layout is driving a browser while talking to an agent about what is on it.
export const defaultLayout: LayoutNode = {
type: 'group',
id: 'browser-root',
direction: 'horizontal',
children: [
{
node: {
type: 'group',
id: 'browser-left',
direction: 'vertical',
children: [
{ node: { type: 'panel', id: 'browser-tabs', appType: 'officerdev/browser-tabs' }, size: 55 },
// Pairing under the tab list: on a server with nothing paired the tab list is empty, and the
// panel that explains why should be the one next to it.
{ node: { type: 'panel', id: 'browser-pairing', appType: 'officerdev/browser-pairing' }, size: 45 },
],
},
size: 25,
},
{
node: {
type: 'group',
id: 'browser-right',
direction: 'vertical',
children: [
{ node: { type: 'panel', id: 'browser-preview', appType: 'officerdev/browser-preview' }, size: 60 },
{ node: { type: 'panel', id: 'browser-chat', appType: 'officerdev/chat' }, size: 40 },
],
},
size: 75,
},
],
};