Dialogue · Front end · The pane refactor
← Block catalog

The chat pane becomes an object

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.

The whole plan in one line: on WIDE, stop faking a right pane with padding and occupy one — position:fixed; left:var(--sidebar-w); right:0 — then delete the four patches that exist only to compensate for the fake.

1 · What is actually wrong

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(#addPagethe chat pane(.mainpane
positionfixedstatic
boxleft:363 right:0 → 917px0 → 1280px, the whole window
held off the list bybeing positioned therepadding-left:var(--sidebar-w)
backgroundvar(--bg)transparent
stackingz-index:86auto

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:

the six versions v879 gave it a z-index — still invisible. v881 gave it a slide-in — still invisible(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.
The process lesson, which is the real cost: the fix was one property per round instead of one question up front — “what does a page that covers the right pane need, and which of those does this pane have?” That comparison is a ten-line probe(§6)and it lists all four gaps at once. Four of those six versions were avoidable.

2 · The one fact that makes this safe

read this before worryingNARROW IS ALREADY A PROPER PANE, and narrow is where the danger lives. Under @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.

3 · Target state

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.

4 · What gets DELETED by it

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:

rulewhy 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:

5 · The steps

step 1Take the measurement first, and keep it. Run the §6 probe on today's build at 1280 and at 1400, chat open and chat closed, and save the numbers. Everything after this is “did the numbers stay the same except where I meant them to change?”
step 2Flip the geometry. The @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.
step 3Delete the compensations(the table in §4), one at a time, re-running the probe after each. If deleting one changes nothing, that is the proof it was only ever compensating.
step 4Check the three neighbours that share the wide layout: the pane-sizer drag(.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.
step 5Then the covers: a chat entered from Vibes(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.
step 6 · the gateThen narrow, on a real phone or a 375px window: open a chat, raise the keyboard, send a message, rotate. The refactor should be invisible here — that is the assertion, and it is worth the five minutes because it is the one thing that would be expensive to get wrong.

6 · The probe that should have been run first

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).

7 · What NOT to do

8 · Status

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.