Skip to content

Polish the browser client, server logs and gallery for v0.10.0 - #237

Merged
zR-JB merged 27 commits into
mainfrom
client/rc2-polish
Oct 6, 2026
Merged

zR-JB merged 27 commits into
mainfrom
client/rc2-polish

Conversation

@zR-JB

@zR-JB zR-JB commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Polish for v0.10.0-rc.2: every reported browser-client issue, a robustness and motion pass, readable server logs, and a gallery recaptured from 0.10.0.

Browser client

Motion

  • Docked panels and the console move on one velocity-carrying spring: one shared bob (about 1%), the console's width rides along, and the shared edge stays within 0.2 px in every frame, through dock swaps and interruptions.
  • A panel closes in its click's frame instead of a frame later.
  • Every setting reshapes the console the same way, the skip-loaded-latency switch included.
  • Stage cards resize on one curve whether they grow or shrink.
  • The dial's needle never snaps or stutters, and the result takes over from the last needle without running back.
  • Run again rewinds in 160 ms.
  • Starting a run: the dash holds while the stage name and first figure fade in once, and the status figures no longer shift.

Multi-server latency

  • The latency lanes name their server in a quiet picker that works any time, mid-run included. The chosen server is probed between runs and leads the next run.
  • Details inspects any server during a run without moving the console.
  • The lanes show only their own server's failures.

Units: switching rate units relabels in place on round ticks, with no replayed sweep.

Layout and content

  • Tooltips: a longer rest, a smaller plate, at most two short lines, fewer surfaces; stale facts corrected.
  • Probe timeouts keep the stage icon and brighten the figure instead; only 5% or more warns.
  • About & legal shows every repository the same way for Go and Rust, with visible-text link names.
  • Four result cards pair up before a two-way figure loses its unit; the Details build row shows a short SHA.
  • Two theme presses inside the fade now count twice, and a skipped fade raises no error.

Phone: a default run fits one screen within an iPhone's safe area. The dial is smaller, the cards are compact with every fact kept, and the redundant status strip is gone.

Servers

  • Go writes the Rust server's aligned layout (time, level, topic) through a small logx package.
  • Both servers' startup lines fit 120 columns: a listener table, sign-in lines with only the mode's settings, and certificate expiry in days.
  • A short GM_MAX_BUFFER_BYTES refusal is three lines ending with a budget or connection count that starts; both suggestions were verified. DEPLOYMENT.md explains the pairing.

Gallery

The hero and every screenshot are real 0.10.0 runs over shaped links (netem delay, token bucket rate, 4 ms of queue): a laptop on Wi-Fi 7, a workstation on 10 GbE, a phone on Wi-Fi 6E, three servers, and a partial run. The hero sets the laptop and a true-proportion phone side by side, nothing covered.

Tested

  • mise run check passes locally.
  • Single e2e suites run locally: app, controls, interaction, history, access, readiness, multi-server (including a new mid-run picker test).
  • Motion was measured with a CDP frame recorder in headless Chrome at 60 Hz on this laptop; 120 Hz panels and real phones were not tested here.

zR-JB added 27 commits October 6, 2026 22:14
A chosen segment's plate jumped from one segment to the next, and the
cadence rows, which wrap their segments under the label, kept the 2 px
padding of a one-line control row, so each label sat on the plate's edge
and each control on the next rule.

The plate is now one thumb under the words that slides to the choice on
the settle easing. Its place comes from the chosen segment's index and
the segment count through :has(), so it needs no measuring, no script
and no anchor positioning, and it fades out when nothing is chosen. A
control row keeps 42 px on one line with 5 px of padding, so a label that
wraps keeps air; a stacked row (the cadences, and the steppers and units
in a narrow sheet) sits its label as a one-line row would and its control
under it.
A release stamps the full 40-character commit as the client's revision,
which wrapped across two lines of the Build row mid-hash. The row shows
git's eight-character form; its tip names the whole commit, and the
diagnostic report keeps the full build as before.
The dialog listed a component's links by kind (Source, Upstream, Changes),
so where a component comes from showed only on hover, and a fork read as
three equal words. Go and Rust servers send links from one generator, so
one layout serves both.

Each component now shows where it comes from as text, the repository by
owner and name or the registry page by package: a fork's upstream, any
other component's source. A modified component adds a quiet second line,
Modified, with the shipped fork's place when there is one and its changes,
each a link. A place too long for its column ends in an ellipsis rather
than breaking, and the e2e tests follow the new rows.
Switching bits and bytes or decimal and binary replayed the whole result:
the dial's hand-off was keyed by the formatted value, so a new unit read
as a new result, the arcs drained and mounted again, and their sweep ran
from zero for over a second with a bead left at the ring's start. The
ticks faded out and in, and in bytes they read 0.0625 and 6.25 because
the ceiling was chosen in bits.

The unit is no longer part of any hand-off: the readout, the ticks and
every card relabel in place. A result arc's length is a registered
property, so when a new scale moves it, it and its bead glide to their
place over 360 ms, as the needle does, without touching the sweep.
Ceilings are chosen on the shown unit's own ladder, bits or bytes in
powers of 1000 or 1024, so the dial's labels are round in every unit.
An e2e test switches the unit after a run and checks nothing replays.
Every selected server is probed for latency, but the lanes only ever showed
the first one, and the only way to change that was the results lens, which is
locked while a run is in flight. Details shared that lens, so inspecting a
server during a run was impossible too.

The lanes head now names its server in a quiet picker that works at any
moment and changes only what is shown. The chosen server is also the one
probed between runs (which also fixes idle latency never running when a
remote server was first) and leads the next run's latency headline. Details
keeps its own inspected server, following the lens until someone picks
another, so it works mid-run without moving the console.
Only the stage track and the two-way switch wrapped their change in a layout
flip, so the same reshaping jumped when it came from anywhere else: skipping
loaded latency with the Latency stage off, a preset, or a settings row.

The flip now lives where a configuration is applied, so any change that adds
or removes the lanes, a card or a stage moves the console alike. A flip inside
another is part of the outer one, so callers cannot double it.
The repository links carried aria-labels such as "Source of IBM Plex Mono"
that left out the visible "github.com/IBM/plex", which axe reports as a
serious label-in-name violation and which speech input cannot target.

Each link's name is now its visible text followed by a screen-reader-only
clause saying whose source or changes it is.
Closing Settings, Details or History stepped back through browser history
and waited for popstate, which lands in a later task: the button and the
surface held their old state for an extra frame. A close now commits at once
and the history step follows; a route taken while that step is pending is
written once it lands, so spamming the button cannot be undone by it.

A docked sheet slid on its own curve while the stage it pushes moved on the
layout curve with overshoot, so the two bounced apart. In a flip that holds a
sheet, everything moves on the sheet's curve and duration, as one surface.
Tips opened after 0.4 s on controls and 0.25 s on jargon, which a hand
crossing the console met constantly, and many ran to four or five lines of
formulas and constants that belong in the measurement docs.

A tip now waits 0.65 s on a control, 0.45 s on jargon and 0.2 s after
another; it is a title and at most two short lines on a plate up to 260 px
wide. Every explainer is rewritten to that shape, keeping what a reader acts
on, and the stale ones are corrected: the latency server is the shown one,
and the gauge steps 1, 2, 5. The cadence segments drop their own tips, as
the row's tip lists every interval and each segment's name carries its own;
the topbar tips are the button's name and key.
In History's default detail width, a result with a Bidirectional stage
squeezed four cards to 188 px each, which cut "Gbit/s" off the two-way
figure and the wire lines.

Below 960 px of container, four cards fall into two pairs; three keep their
single row as before.
When a stage came or went, a card that grew was wiped open by a clip on its
own curve while its neighbour slid on another, and a card that shrank
snapped to its new width at once; the row never moved as one.

Cards are now marked to resize within a flip: each animates its width from
the old to the new on the same curve as its move, so the row tiles in every
frame in both directions. It costs a layout of the cards per frame, about
0.4 ms here, and kept every 60 Hz frame with live strips; other surfaces keep
the compositor-only clip.
A single probe timeout replaced the lane's stage icon with a circled "i" in
warning yellow, off the icon's baseline, which read as an alarm for a few
dropped datagrams over WebTransport.

The icon now always stays. Missing or unaccounted replies brighten the
timeouts figure under a dotted mark whose tip lists every probe outcome;
only a twentieth or more of the probes turns it to the warning tone, and a
failed population tints its name and explains itself there.
Pressing Start set off three whole-hero hand-offs within 600 ms (the idle
dash out and back in with the stage name, then again for the first value),
so the dash blinked and the name faded twice; meanwhile the status strip's
phase word changed width at each step and pushed its figures sideways.

The live readout now hands off its stage name and its figure apart: the
name fades in once per stage, the figure only when a stage brings its first
value, and a dash that stays a dash holds still. The phase word's cell keeps
the width of the widest word a run passes through, so the figures never
move.
At the end of a run the last needle drained back to zero and the result
then swept up from zero, so the ring ran backwards and forwards again over
the same arc in the moment the run settled.

The needle now holds when a run ends in a speed result, and the result's
sweep starts where it stood: arcs below it show at once, longer ones run on
in their share of the sweep, and each bead pops as the front reaches it. A
result with no needle before it, as on reload, still sweeps from zero.
A layout-shift trace of a run showed every lane's plot moving 7 px right
when the first sub-0.1 ms jitter arrived: the figure columns reserved 7ch
for "9999 ms", and "< 0.1 ms" is eight characters.

Median and jitter now reserve 8ch from Start, so no arriving value moves
the plot.
A docked sheet slid on one curve while the console it pushed moved on
another, so their bobs ran into each other; the console's width changed in
the first frame; and every move began at full speed, so an interrupted one
stopped dead and leapt off again.

Every flip now moves on one damped spring sampled into compositor
keyframes: it sets off from rest, overshoots once by about 1% (5 px on a
420 px sheet) and settles within 1% in 0.26 s. The console's width rides
the same spring, so its far edge holds still and the edge it shares with a
sheet stays within 0.2 px of it in every frame, through the bob, a dock
swap and an interruption. A move that interrupts another starts at that
one's speed from where it is seen, measured against the true new layout;
a sheet reopened while its copy still leaves takes over from the copy. A
docked sheet's ground runs past the screen edge so its bob opens no gap.
DESIGN.md still described the eased glide and the no-overshoot sheet slide
that the spring replaced.
A frame recorder over nine run scenarios found the needle jumping from its
last value to zero at full opacity as a result took over: the handover
snapped it while its layer was still fading out. It now fades where it
stood, over a result that starts at the same place, and returns to zero
only when the next stage reveals it.

The needle's eased glides started at full speed, and a scale step during a
rise restarted them, so a rising needle surged and stalled. An eased glide
now sets off at the speed the needle already had (from rest when it had
none) and comes to rest in place, so a rise reads as one smooth sweep.

Run again rewinds the result in 160 ms instead of 300.
Startup lines ran to 130–190 characters (a version on every listener line,
roles spelled out in full, the password mode listing OIDC fields, "719h0m0s"),
so `podman logs` wrapped them, and Go's lines had no level to skim for.

Go now writes the Rust server's layout through a small logx package: local
time, a level column, a padded topic, then the message, with colour and a
help line on a terminal; the standard logger is routed through it too. Both
servers print the same startup shape within 120 columns: one starting line,
the sign-in mode with only the settings it uses (OIDC on a second line),
the certificate's expiry in days, a listener table (address, protocol,
roles), ready, and the stop lines. The admission counters split into one
line per limiter, and peer failures log under `peer` at most once a minute.
A Pi that set GM_MAX_BUFFER_BYTES to 1 GiB stopped with one 230-character
line of raw byte counts that named the shortfall but not a value that would
start, and wrapped in any terminal.

The refusal now logs three lines: what is short of what, what each part
costs, and a budget or a connection count that fits, with a tenth spare as
the kernel may double the endpoints' socket buffers. Both suggestions start
the server. A failure's lines log one after another, and DEPLOYMENT.md says
how the budget and GM_MAX_CONNECTIONS go together on a small host.
On a 430 × 839 phone the dial took 336 px of the screen and each card 250 to
272 px with a four-row fact table, so after a run only the Latency card
showed under the key; the status strip repeated what the chips and cards
already say.

A phone's dial now keeps about three tenths of the screen with the ring as
large as before; the chips drop their legend; each card stands its figure at
the right beside its name and detail over a 36 px strip, with every fact
kept as one row of label-over-value columns, so cards are 136 px and a
default run's three show at once on a large phone, its home-screen app
included. The status strip leaves phones.
An installed app on a large iPhone gets 839 px between the status bar and
the home indicator, 22 px less than the cards needed, so the Upload card's
facts fell under the indicator.

The phone's dial takes 29% of the screen, the gaps between dial, key and
cards 8 px, a chip 2 px less padding and a card 3 px less, so the three
cards end at 835 px.
With several servers, the lanes show one server, but a failure on any
server's latency path marked them: in a run where one server dropped out,
the shown server's healthy loaded-down row was tinted as failed.

The lanes now take only the failures of the server they show.
The theme key chose its next theme when pressed but applied it when the
fade ran, so a second press inside the fade started from the old theme and
the two presses advanced it once; the skipped fade also left an unhandled
rejection.

The next theme is chosen as the change applies, and a skipped fade is
ignored.
The README hero and the gallery still showed an older build; the hero's phone
covered the laptop's cards and lanes, cut its own screen off mid-card under a
status strip, and stood in a frame of no real phone's proportions.

Every image is a fresh run of 0.10.0 over HTTPS through shaped links (a
token bucket bounding each queue to 4 ms of data under netem's delay):
the laptop on Wi-Fi 7, the workstation on 10 GbE, the phone on Wi-Fi 6E and
three servers on their own paths, one stopped mid-download for the partial
run. The hero sets the 10 GbE run in a laptop with its hinge and base and,
beside it, the phone's finished run at 430 × 932 with its status bar,
island and home indicator, nothing of either covered. The terminal capture
replays the TUI's own 120 × 40 output. The gallery text follows 0.10.0: the
latency server chosen in the lanes, Details inspecting any server, and the
phone's compact cards.
The timeouts figure drew its own dotted underline, which the primitive
guard reserves for the shared jargon mark. A figure with missing replies now
is a marked term, so the one mark draws it.
The interop check finds the least budget the server accepts by reading the
minimum from a refusal; the three-line message rounded it to binary units,
so the check could not parse it, and an operator scripting a fix lost the
exact figure too.

The first line gives both values in bytes; the parts and the suggested
values stay on the next lines. The interop check reads the new wording.
@zR-JB
zR-JB merged commit 2396f4d into main Oct 6, 2026
24 checks passed
@zR-JB
zR-JB deleted the client/rc2-polish branch October 6, 2026 23:31
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