UI stack — chrome rendering decision
Phase 3 of PLAN.md introduces native chrome (statusline, tab strip, command
line, hint overlay). This ADR records the rendering stack chosen for the first
batch of chrome — statusline today, tab strip + command line later in Phase 3.
Options
- A —
softbufferstrip in the samewinitwindow. Chrome lives in a CPU-blitted strip docked to the bottom (or top) of the buffr window. CEF's child window is sized to the remaining rectangle and reparented throughWindowInfo::parent_window. One window, no compositor placement, no GPU dependency. - B — separate top-level
winitwindows for chrome. Each chrome panel is its own OS window positioned over the CEF window. Avoids resizing CEF, but Linux compositors (especially Wayland) routinely refuse client-requested positioning and z-ordering. Fragile. - C — OSR +
wgpucompositor. CEF paints into a buffer viaCefRenderHandler::OnPaint; chrome is drawn aswgpuquads on top. Required for hint mode (per-pixel composition over the live page) and native Wayland. Pulls inwgpu,naga, shaders, plus the OSR plumbing theosrfeature already scaffolds.
Decision — Option A with softbuffer = "0.4"
softbuffer is small, depends only on platform window-handle crates, and a
single-line statusline rendered with a bundled bitmap font is trivial to
software-blit. The current CEF embedding is windowed (X11/XWayland on Linux),
which already requires us to give CEF its own subrectangle inside the winit
window — A composes naturally with that. C drags in a GPU stack we do not
otherwise need at this phase.
Why A wins now
- One
winitwindow — no inter-window placement bugs. - No
wgpudependency for a 24-px strip. - CEF's windowed embedding stays in charge of page rendering.
- Future tab strip and command line slot into the same
softbuffer::Surface.
Trigger to migrate to C
Hint mode requires drawing labelled overlays on top of the live page, anchored
to DOM rectangles, and updating at scroll/animation rates. That is per-pixel
compositing, which a CPU strip cannot do without flicker or expensive readback.
When hint mode lands (later in Phase 3), the chrome layer migrates to the OSR +
wgpu path that crates/buffr-core/src/osr.rs already scaffolds. Statusline
and tab strip may stay on A or be ported in the same change — whichever costs
less.
Layout
STATUSLINE_HEIGHT = 24pixels, docked to the bottom of the buffr window.TAB_STRIP_HEIGHT = 30pixels, sits above the CEF page area and below the optional input bar. Always painted (zero tabs renders an empty bar in the strip's bg colour).INPUT_HEIGHT = 28pixels, docked to the top when the command line or omnibar is open. The input strip is hidden when the overlay is closed and the page region reclaims those rows.- Suggestion dropdown: each row is
STATUSLINE_HEIGHT(24 px) tall, max 8 rows. Stacks below the input strip when populated; the dropdown rectangle also shrinks the CEF child rect so suggestions never overlap the page. - CEF child window rect:
(0, overlay_h + TAB_STRIP_HEIGHT, w, h - overlay_h - TAB_STRIP_HEIGHT - STATUSLINE_HEIGHT), whereoverlay_hisINPUT_HEIGHT + dropdown_rows * STATUSLINE_HEIGHTwhen an overlay is open,0otherwise. The X11 XID is passed asWindowInfo::parent_windowat creation time; on resize the host walks every tab and callscef::Browser::host().was_resized()after winit reports the new size. Whenever the overlay opens / closes we re-issue the resize so CEF re-flows the page area. - Statusline + input paint surface: a single
softbuffer::Surfacesized to the full window. Each frame we paint the bottom strip (statusline) and, when an overlay is active, the top strip (input bar + dropdown). The middle page region is owned by CEF and never touched by us;present_with_damageis called with up to two damage rects so the page area is never repainted by softbuffer.