A refactor plan, written 2026-08-14 after six versions(v879–v884)were spent making the chat pane cover the right half — something any sub-page does with one class. Nothing here is urgent. The app behaves correctly today; this removes the reason it took six tries, and the standing tax on whoever next asks the chat to sit somewhere it has not sat before. Written for a fresh session: everything needed is on this page.
position:fixed; left:var(--sidebar-w); right:0 — then delete the four patches that exist only to compensate for the fake.Every sub-page in the app is a real object: fixed to the right half, with its own ground, its own place in the stack, its own slide. The chat pane is not one of them — it predates that vocabulary. It spans the whole window and holds itself off the list with padding. Measured, both in their resting state:
a sub-page(#addPage) | the chat pane(.mainpane) | |
|---|---|---|
| position | fixed | static |
| box | left:363 right:0 → 917px | 0 → 1280px, the whole window |
| held off the list by | being positioned there | padding-left:var(--sidebar-w) |
| background | var(--bg) | transparent |
| stacking | z-index:86 | auto |
So “lay the chat over the right pane” was never one property. It was four missing ones, and each masked the next — which is exactly why it arrived one version at a time:
visibility:hidden on any foreign root).
v882 fixed visibility — now visible, but its 363px of transparent padding ate every click on the left pane.
v883 made the padding click-through — now live, but the pane is transparent, so the chat and the feed printed on top of each other.
v884 gave it a background clipped to the content box — done.
@media(max-width:760px) the pane is already position:fixed; top:0; left:0; right:0; height:var(--app-h,100dvh); background:var(--bg); overflow:hidden — the v488 seal. All of the virtual-keyboard machinery(--app-h, --composer-h, fitShellToKeyboard, the seal)is on that path and this refactor does not touch it. The padding trick is @media(min-width:761px) only. If you remember one thing from this page: the refactor is wide-only, and the hard-won keyboard work(v477–v488)is not in scope.On wide, .mainpane becomes what every sub-page already is:
@media(min-width:761px){
.mainpane{ position:fixed; top:0; bottom:0; left:var(--sidebar-w); right:0;
background:var(--bg); } /* no padding-left, no background-clip */
}
…and the wildcard drawer's padding-right:var(--wild-w)(≥1080px)becomes right:var(--wild-w), which is the same idea said in the same grammar as everything else.
These exist only to compensate for the fake pane. Each one going away is how you know the refactor worked — and each is a place a future change would otherwise have to remember:
| rule | why it exists today |
|---|---|
.mainpane{padding-left:var(--sidebar-w)} | the fake itself |
pointer-events:none + > *{auto}(v883) | so the padding stops eating left-pane clicks when raised |
background-clip:content-box(v884) | so its ground does not paint over the left pane |
body.vibes-chat/.vg-chat .mainpane{z-index:87} | may stay — but becomes an ordinary lift over a sibling, not a lift of the whole window |
@media(min-width:1080px) .mainpane{padding-right:var(--wild-w)} | becomes right: |
@media(min-width:761px) block only. Do not touch the narrow block. Re-run the probe: the content box should be identical(left 363, width 917)with position:fixed and no padding..pane-sizing, live width — it currently animates padding-right), the wildcard drawer at ≥1080, and the peek/admin view(body.peek). Each has a rule keyed to the pane's shape.body.vibes-chat)and from the folded Vibes chats(body.vg-chat)must still cover the right half, slide in, and leave the left pane live. §6's probe answers all three.Paste in the console. It asks what a finger actually lands on and what the boxes really are — not whether a rule was applied, which is the question that cost four versions.
const g=el=>getComputedStyle(el), R=el=>el.getBoundingClientRect();
const mp=document.getElementById('mainPane'), dr=document.getElementById('chatsDrawer');
const at=(x,y)=>{const h=document.elementFromPoint(x,y);
return {inChat:!!h?.closest('#mainPane'), inLeft:!!h?.closest('#chatsDrawer')};};
console.log({
pane:{pos:g(mp).position, left:g(mp).left, width:g(mp).width,
padL:g(mp).paddingLeft, bg:g(mp).backgroundColor, z:g(mp).zIndex,
vis:g(mp).visibility, box:R(mp)},
leftPress: at(180, 400), // must be the list
rightPress: at(innerWidth-200, 400), // must be the chat
});
Rules of thumb this refactor earned: a z-index cannot be seen through visibility:hidden · hit-testing goes to the topmost BOX, not the topmost pixel · a z-index fix has to be checked against the DOM it is stacking, not the panes it is named after(raising the left drawer also raises Vibes — the feed is its child).
.mainpane to a .drw. It is not a sub-page: it has no back-stack entry, no header of its own, and it is the app's default surface, not something opened over one.Not started. Written 2026-08-14. Owner intends a dedicated session. When it lands, update this line and record the deleted rules in the Style Guide's version log.