Skip to content

feat(rust): honor width / height / fill on row + column scene layouts#1000

Merged
esengine merged 1 commit into
mainfrom
rust-scene-flex-sizing
May 16, 2026
Merged

feat(rust): honor width / height / fill on row + column scene layouts#1000
esengine merged 1 commit into
mainfrom
rust-scene-flex-sizing

Conversation

@esengine
Copy link
Copy Markdown
Owner

Unblocks multi-pane scene layouts. Foundation PR for the
`Sidebar / Main / ContextPanel` three-pane layout the user's
design mock points at.

Why

The scene schema already declared `width` / `height` on
`BoxLayout` as `Dim::Cells(n) | Dim::Fill`, but the Rust render
layer ignored both — every row child fell back to its intrinsic
width and every column child to its intrinsic height. That made
`direction: row` only useful for content-packed rows, never for
sized panes (244-cell sidebar + fluid main + 320-cell context panel
was literally not expressible end-to-end).

Algorithm

Single-pass distribution per axis:

  1. Reserve gap_total = gap × (n − 1) up front.
  2. Walk children; classify each:
    • `Dim::Cells(n)` → reserve n cells, clamped at remaining
      (first-come basis, source order).
    • `Dim::Fill` → defer.
    • unsized → reserve intrinsic size, clamped at remaining.
  3. Split whatever's left equally among deferred Fill children.
    Pixel remainder goes to the leading Fill children so the total
    adds up to exactly the gap-adjusted axis budget.

Backward compatible — every existing scene (column with unsized
children, row with intrinsic-width chips) keeps rendering identically.

Visible use

`titleRow` is now a row box: brand on the left, model name on the
right, `width: "fill"` spacer between them. Same pattern the
upcoming three-pane layout PR will use — sidebar with
`Dim::Cells(n)`, main with `Dim::Fill`, ctx panel with its own
fixed cell width.

Tests

`crates/reasonix-render/tests/render.rs` — 9 new cases:

  • fixed cell widths reserved in source order
  • one Fill child consumes all remaining row width
  • multiple Fill children split remainder evenly
  • uneven remainder goes to leading Fill children
  • fixed width clamped at remaining when oversized
  • unsized children keep their intrinsic widths
  • gap subtracted from available before distributing
  • column variant for both Cells + Fill

`tests/scene-trace-frame.test.ts` — 2 new cases on the producer-side
`titleRow` restructure.

`cargo test -p reasonix-render --test render` → 17/17 green.
`npm run verify` → green via prepush gate.

What still doesn't work

  • `flexGrow` / `flexShrink` — schema fields exist, render layer
    still ignores them. `Dim::Fill` is the primary way to grow.
  • `marginX` / `marginY` — same.
  • `align` / `justify` — same. (Right-alignment in a row is
    currently done by inserting a Fill spacer before the right-aligned
    child, as the new `titleRow` does.)
  • `borderStyle` / `borderColor` — same.

Each of these is a small isolated follow-up; `width` / `height`
was the highest-impact one because it unlocks fixed-sized panes.

Refs #868

Schema already had `width` / `height` as `Dim::Cells(n) | Dim::Fill`
on box children, but the Rust render layer ignored both — every row
child fell back to its intrinsic width and every column child to its
intrinsic height. That made the schema's `direction: row` only useful
for content-packed rows, never for sized panes.

## Algorithm

Walk children once; classify each:
- explicit `Dim::Cells(n)` → reserve exactly n cells (capped at the
  remaining axis space, in source order)
- explicit `Dim::Fill` → defer
- unsized → reserve intrinsic size, capped at remaining

After the first pass, split whatever's left equally among the deferred
Fill children, distributing any pixel remainder to the leading ones
so the total adds up to exactly the gap-adjusted axis budget. Gaps
between siblings come off the top.

## Visible use

`titleRow` is now a row box: brand on the left, model name on the
right, with a `width: "fill"` spacer between them. Sets up the
producer-side pattern that the upcoming three-pane layout PR
(Sidebar / Main / ContextPanel) will reuse — sidebar gets a fixed
cell width, main gets `Dim::Fill`, ctx panel gets its own fixed.

## Tests

9 new Rust cases on the layout algorithm (fixed reservation order,
single Fill, multiple Fill, uneven remainder distribution, fixed
clamped at remaining, intrinsic fallback for unsized, gap subtraction,
column height variants).

2 new JS cases on the producer-side titleRow restructure (row box
with brand + fill spacer + model; spacer-only row when no model).

Refs #868
@esengine esengine merged commit d5a976e into main May 16, 2026
5 checks passed
@esengine esengine deleted the rust-scene-flex-sizing branch May 16, 2026 02:48
esengine added a commit that referenced this pull request May 16, 2026
…#1001)

First concrete consumer of the new flex layout primitive (#1000) and
the smallest visible step toward the multi-segment status bar the
design mock points at. Wallet balance is the most direct visible
signal of reasonix's cheap-positioning claim — users see how slowly
the number goes down across a session.

- New SceneTraceInput.walletBalance + walletCurrency scalars,
  primitives so effect deps stay stable.
- statusRow upgrades from a single text node to a row box only when
  the balance is present: left segments (cardCount · busy/idle · activity)
  + fill spacer + right segment (`wallet ¥184.20`). When balance is
  null/undefined the row stays a single text node — back-compat with
  the existing test that asserts `kind === "text"` on the status row.
- Currency rendering: ¥ for CNY/RMB/JPY, $ for USD, € EUR, £ GBP;
  unknown codes fall back to `CODE 5.00` so nothing silently looks
  like the wrong currency.
- App.tsx forwards `balance.total` and `balance.currency` from
  useSessionInfo — both null until the first successful
  getBalance() call, so the wallet segment appears once the
  endpoint responds and stays hidden offline.

Cache-hit %, branch, spent (this session), jobs count all also belong
in this row; each is a small isolated wiring step from here.

Refs #868

Co-authored-by: reasonix <reasonix@deepseek.com>
esengine added a commit that referenced this pull request May 16, 2026
…/ContextPanel layout (#1003)

Stage 1 toward the user's design mock — restructure from a flat
column of rows to the classic IDE three-pane shape:

  ┌───────────── title (full width) ─────────────┐
  │ SIDEBAR (24) │   MAIN (fill)   │  CTX (32)   │
  └───────────── status (full width) ────────────┘

The middle band is a `direction: "row"` box with `height: "fill"`,
made possible by the flex-sizing infra from #1000. Sidebar and ctx
pane have fixed cell widths; main pane fills the remainder. Both
side panes auto-hide on narrow terminals — sidebar below 60 cols,
ctx pane below 100 cols — so an 80-col terminal still works the way
it did before, just with a sessions-rail on the left.

## Main pane content (unchanged)

The cards stack, composer / approval / sessions-picker takeover,
slash overlay — exactly the same nodes as before, now living inside
the main pane's column instead of inline in the root.

## Sidebar (stage-1 placeholder)

Just a `─ SESSIONS ─` header + `use /sessions to browse` hint. The
real session list goes here in stage 2 once App.tsx surfaces a
permanently-loaded list (today it's loaded only when the picker is
open).

## Context pane (uses what's wired)

`─ CONTEXT ─` + `model · cards · wallet`. Wallet moves from the
status row into the ctx pane when both are visible — avoids
duplicating it. At cols < 100 the ctx pane is hidden and wallet
stays on the right of the status row (as in #1001).

## Test rewrite

The flat-children shape was load-bearing in 15 tests. Added two
helpers — `mainOf(f)` / `statusOf(f)` / `titleOf(f)` — and rewrote
each test to drill through the new structure. Coverage is identical
in spirit (still asserts behavior of card rows, composer, approval
modal, sessions picker, slash overlay, status segments) but
expressed in terms of the new hierarchy.

3 new shape tests:
- root is now `[title, middle, status]` (3 children, not 4-7)
- sidebar hidden below 60 cols
- ctx pane hidden below 100 cols

Refs #868

Co-authored-by: reasonix <reasonix@deepseek.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant