Skip to content

Share one drawer frame between the Simulate drawers and the full optimization view - #9607

Draft
kube wants to merge 13 commits into
claude/opt-proto-pause-resumefrom
claude/opt-proto-drawer-frame
Draft

Share one drawer frame between the Simulate drawers and the full optimization view#9607
kube wants to merge 13 commits into
claude/opt-proto-pause-resumefrom
claude/opt-proto-drawer-frame

Conversation

@kube

@kube kube commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Important

Experimental
Behind the In-browser optimization feature flag.

Summary

Before this PR, the experiment drawer, the optimization drawer and the full study view each built their own chrome. Each opened with a collapsible Summary section and a summary strip that scrolled with the body, so the status and the progress left the screen as soon as a reviewer scrolled to a chart. A note appearing, a status changing or a number growing a digit moved the cards below it.

This PR gives the three surfaces one DrawerFrame. Its header sits outside the body scroll, names the experiment or study in one line and carries the status, the runs, the time, the computing chip and the compute badge as a strip of labelled columns divided by hairlines, with a progress bar along its bottom edge. Every column is as wide as its widest value, so nothing in the header moves while results stream. Once the body scrolls the header condenses to one line with the columns as compact chips, and grows back under the pointer. The body puts the Parameters band across its width under the header, collapsible; beneath it, at the extra-large drawer's width and above, the surface sits left and the metric cards right, two to a row, so two swept parameters and four metrics fit without scrolling; in a narrower drawer the cards come first. Every card and the note row keep a fixed height. The Summary section is gone from every surface.

Links

Changes

Drawer frame

  • DrawerFrame renders the header, the body and the footer of every Simulate drawer and of the full study view

    With drawer it renders inside the ds Drawer with the frame header as custom header children and the close button over the header's right gutter. Without it the same header, body and footer fill their section.

  • Header is 68px at rest and 36px condensed

    useBodyScrolled reads scrollTop > 0 from the body's scroll event. Pointer or focus on the header releases the condensed state.
    Condensed, the strip collapses and a compact copy of the columns mounts in the title row with the labels hidden and each label as a tooltip.

  • Transitions run only when the animations setting is on and the OS does not prefer reduced motion

    usePrefersReducedMotion subscribes to matchMedia through useSyncExternalStore.

  • FrameStat is a labelled column as wide as its widest value

    The label sits over the value with a hairline before each column from the second on.
    An invisible copy of widest shares the value's grid cell, so a number growing a digit moves nothing.
    Numbers align to the end of their column; FrameStatusPill is sized the same way by its longest word.

  • Note row under the header is always mounted at 20px

    It shows the error in the error tone or a muted note, one line with an ellipsis and the full text as a tooltip.

  • FrameColumns arranges primary, secondary and after by the body's width

    One column below 960px with secondary first.
    Above it, primary and secondary side by side with after spanning both.
    secondary is never narrower than two chart cards and their gap; primary yields until the body fits the 3:5 split.
    Without primary the cards take the whole width. The query is a container query on the body.

  • FrameBand is the titled band for parameter controls, spanning the body under the header

    With collapsible a chevron folds the controls away; they stay mounted and inert, so slider positions survive.
    The fold animates the band's height alone and whatever follows moves as one block.

  • ComputeBatchesChip is always drawn and opens its batch list in a Popover

    It reads 0 computing, disabled, while nothing runs and reserves room for a three-digit count. Opening the list moves nothing.

  • frameLayoutSignature reads the header, the note, every card and the steps table heights from the DOM

    Tests compare the signature across states to pin the zero-shift rule.

Experiment drawer

  • One-line title SIR transmission sweep · Seasonal Flu · 100 runs · dt 1
  • ExperimentStats replaces the Summary section

    Status pill, Runs, Errors, Time, Elapsed, Selection for a sweep, the Activity column with the computing chip, then the Compute column with the backend badge.

  • Parameters band across the body, the surface in primary, the metric cards in secondary, the error in the note row

    Its sliders are the plain variant. The Surface footer is a grid with the X and Y selects on one row and the Metric select on a reserved second row.

  • Chart cards are CHART_CARD_MIN_WIDTH wide at minimum, 320px, so two fit the right column from the threshold up

Study view

  • StudyFrame builds the frame for the optimization drawer and the full view

    Title name · scenario · Maximize metric. The progress line with the Paused and verdict chips is the header's headline. StudyStats carries Status, Steps, Steps clear, Best step so far and the computing chip.

  • Connected studies put the Parameters band across the body, the surface in primary, the four chart cards in a grid in secondary, the steps table in after

    Remote studies put the Best parameters band across the body, the experimental surface band in primary and the objective by step in secondary.

  • Steps table is a fixed 320px box whose "Showing the latest 200" hint always occupies its row
  • Full view passes Back to list as leading and its actions sit in the frame footer
  • Status reads Reconnecting while the stream re-establishes
  • stepsProgressPercent drives the header bar; the followed step's secondary bar and its label are gone
  • Deleted summary-strip.tsx, compute-activity.tsx, study-summary-strip.tsx and experiment-summary.tsx

Review fixes

  • Drawer body takes the opening focus

    Focus landed on the computing chip inside the header; the chip went idle under it without a blur, so the header held at 56px until the pointer visited it.
    The body is the Drawer's initialFocusRef, so wheel and arrow keys scroll it at once.

  • Header drops a remembered focus its element no longer holds

    useHeaderEngaged records which element took focus in the header.
    On the body's scroll it lets go once that element is disabled, removed or no longer active.

  • Drawer accepts initialFocusRef and returnFocusRef refs that start null, as Popover does
  • Two columns from 960px of body width, so the extra-large drawer lays out two columns at its default width

    The drawer is 1060px and its body's content box 1010px, 995px beside a classic scrollbar.
    secondary holds two 320px cards and their gap at minimum; primary is 342px in the drawer.

  • Band title row is a fixed 24px and its readout clips on one line

    In the 342px column Following step 12 wrapped and unwrapped on Pause, moving the surface 20.8px.
    The Follow steps toggle never shrinks, the navigator grid never exceeds the band, and the sweep sampling status is one fixed line.

  • Story Sweep, four metrics shows the common case
  • Header stats are labelled columns divided by hairlines, each as wide as its widest value

    The old strip's two-line columns return; the header is 68px at rest.
    A hidden copy of the widest text sizes each column in the real font, so the Time column holds 52.800 / 180 and 180 / 180 alike.
    Elapsed keeps its label once the experiment finishes; the label change to Duration moved the column.

  • Computing chip has a permanent column

    It reads 0 computing and is disabled while nothing runs, sized for three digits, never unmounted or clipped.

  • Compute badge is the strip's last column and stays among the compact chips once condensed

    It is 18px tall like the pill and the chip, so it fits the strip's value line.

  • Parameters band spans the body under the header and collapses behind a chevron

    Collapsed content stays mounted and inert, so slider values and refinement state survive; the columns beneath move as one block.
    FrameColumns holds the surface and the cards only, and gives the cards the whole width when there is no surface.

  • ds Slider gains a plain variant and the navigators use it

    A 12px round thumb of one fixed size, no swell while dragged, thumbSize given to Ark so the rail's ends meet the thumb's extremes.
    The Control spans the root at full width; the optimization navigator centred the root's children, which shrank the rail to the thumb.
    The sweep range slider takes the same thumb. The default variant is unchanged.

  • Surface footer is a grid of label and select pairs

    Selects share their row's width instead of a fixed 170px, so the footer fits the 342px column.
    The sweep's Metric select takes a second row the footer always reserves.

Known issues

  • The header strip clips at the right on a drawer narrower than the extra-large size rather than wrapping
  • Compact chips carry their labels only as tooltips
  • The note row is one line; multi-line errors show in full only in the tooltip

Next steps

  • Steps table as a ChartCard with a flush body
  • Width-driven compact density for the header strip

Test coverage

  • drawer-frame.test.tsx:

    The heights, the labelled columns with their sizers and the badge column last, the bar width at rest, condensing on scroll and release on pointer enter, the note row at one height with and without a note, a header control disabled or removed under the focus no longer holding the header open, the secondary column minimum matching two cards and their gap, the computing chip at zero, a collapsible band keeping its controls mounted and inert, FrameColumns without a primary.

  • view-experiment-drawer.test.tsx:

    The one-line title with the stats and badge in the header and no Summary, one layout signature across running, complete, cancelled and error, the error in the reserved row, a Median aggregation leaving the signature unchanged, a plain experiment's cards alone, a programmatic scroll condensing the header.

  • view-optimization-drawer.test.tsx, optimization-full-view.test.tsx:

    One signature across running, paused, stopped and complete, the resume note in the reserved row, aggregation leaving the objective card alone, the scenario and objective in the title, a programmatic scroll condensing the header, in the drawer also after the focused computing chip went idle.

  • study-stats.test.ts:

    Step progress wording.

  • Storybook probes:

    p6-drawer-frame.mjs measures the header, the cards, the bands and the columns on the sweep drawer, the three study states and the full view, scrolls every drawer's body with the opening focus untouched, and lays the four-metric sweep out at the extra-large drawer's width with the metrics grid inside the body at zero scroll.

  • Headless website measurements:

    Every header column's left and width across Running, Idle, Paused, Resumed and Stopped and a computing count going 34 to 0, in the drawer and the full view, all at zero delta; the band spanning the body, rails drawn, thumbs 12px inside the band, values surviving a fold; no card overflow; the surface footer fitting.

How to test

Experiment drawer

  • Open Petrinaut preview on Vercel
  • Menu > Load example > SIR
  • Simulate > Experiments > Create
  • Sweep one or two parameters, Run
  • Open the experiment row

    Expect a one-line title with name, scenario, runs and dt, a strip of labelled columns under it with Activity and Compute last, a progress bar along the header's bottom edge, no Summary section
    Expect Parameters across the body, the surface on the left and the metric cards on the right beneath it, two to a row, every card visible
    Expect no column to move while the runs stream and the computing count changes

  • Click the chevron before Parameters

    Expect the sliders to fold away, the cards to move up as one block, the same slider positions on expand

  • Shrink the window until the body scrolls, scroll it with the wheel

    Expect the header to condense to one line with compact stats beside the title before the pointer touches it

  • Move the pointer over the header

    Expect the header to grow back

Study view

  • Viewport controls > Settings > Simulation > In-browser optimization
  • Menu > Load example > Supply Chain Profit
  • Simulate > Optimizations > Create
  • Select metric Profit, direction Maximize, tick Optimize production_rate, set Optimization steps to 12, Run
  • Open the study row

    Expect Parameters across the body, the surface on the left, the four chart cards on the right beneath it, the steps table below, with no card changing height as steps land
    Expect plain round slider thumbs on rails drawn across the band

  • Scroll the body with the wheel

    Expect the header to condense to one line at once

  • Pause

    Expect the resume note in the line under the header with nothing below it moving

  • Open full view

    Expect Back to list before the title, the same two-column arrangement, the actions in the footer

@vercel

vercel Bot commented Sep 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hash Ready Ready Preview Sep 9, 2026 6:48pm UTC
petrinaut Ready Ready Preview Sep 9, 2026 6:48pm UTC
petrinaut-docs Ready Ready Preview Sep 9, 2026 6:48pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 9, 2026 6:48pm UTC

Request Review

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/apps > hash.design Affects the `hash.design` design site (app) labels Sep 9, 2026
@kube
kube added this pull request to stack #9549 September 9, 2026 06:39
@kube
kube force-pushed the claude/opt-proto-drawer-frame branch from cbf2e4e to c341dee Compare September 9, 2026 12:45
@kube
kube force-pushed the claude/opt-proto-drawer-frame branch from c341dee to fe9dfe3 Compare September 9, 2026 13:32
@kube
kube force-pushed the claude/opt-proto-drawer-frame branch from fe9dfe3 to cd5fd92 Compare September 9, 2026 13:51
kube added 13 commits September 9, 2026 18:38
…ol went idle

The study drawer's opening focus landed on the computing chip inside the header. The chip was disabled under it without a blur, so the header's engaged state stuck at true and the header stayed at 56px on scroll until the pointer visited it. The drawer body now takes the opening focus, and the frame remembers which element took focus in the header and drops it on scroll once that element is disabled, removed or no longer active. The ds Drawer's focus ref props accept a ref that starts null, as the Popover's do.
The two-column arrangement started at 1100px of body width while the extra-large drawer's body measures 1010px, 995px beside a classic scrollbar, so every drawer stacked and a two-axis sweep with four metrics could still put the metrics below the fold. The threshold is 960px, and the secondary column is never narrower than two chart cards and their gap: the primary column yields until the body is wide enough for the 3:5 split. The chart card minimum is one shared constant. A "Sweep, four metrics" story shows the common case.
In the two-column drawer the primary column is 342px wide, and the Parameters band's status wrapped onto a second line while a study followed its steps, then unwrapped on Pause: a 20.8px shift in the column below. The band's title row is a fixed 24px, its trailing readout clips with an ellipsis and a tooltip instead of wrapping, and the Follow steps toggle never shrinks. The sweep navigator's sampling status line is one fixed line the same way, and the study navigator's control grid never exceeds the band's width.
…idest value

Every stat is a column with its label over its value and a hairline before it, as the old summary strip drew them. The column is exactly as wide as the widest text its value can show, laid invisibly under the live one, so a number growing a digit, a status changing word or the computing count going to zero moves nothing. The computing chip is always drawn, 0 computing and disabled while nothing runs, with room for a three-digit count; the compute badge is the strip's last column and stays among the compact chips once the header condenses. The header is 68px at rest for the two-line columns.
The parameter controls are global to the drawer, so the band sits under the header across the whole body instead of sharing the left column with the surface. A chevron folds it; the fold animates the band's height alone, the controls stay mounted so slider positions and refinement state survive, and the columns beneath move as one block. FrameColumns now holds the surface and the cards only, and gives the cards the whole width when there is no surface.
The ds Slider gains variant plain: a 12px round thumb of one fixed size, no swell while dragged, and a rail that spans the root whatever aligns the root's children. The optimization navigator centred the slider root's children, which shrank the rail to the thumb; the sweep navigator's range slider takes the same plain thumb.
…he metric select

The axis selects share their row's width instead of a fixed 170px each, so the footer fits the surface card's narrowest column; the sweep's Metric select takes a second row the footer reserves at all times.
…or the strip

The live simulated time prints three decimals under the maximum, so the column's widest text is a fraction just under the maximum over the maximum. The compute badge is 18px tall like the status pill and the computing chip, so it fits the strip's value line instead of clipping under it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash.design Affects the `hash.design` design site (app) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

1 participant