Imbox selection toolbar

Repository
rubas/inbox
Base
1f2a6f8 main
Head
e2a3f24 on the local branch worktree-agent-a18fe6b74e892aabe (not pushed, no PR)
Captured
24 September 2026, Chromium 153, local dev server with task dev:seed data
Status
Exploration. Three placements behind a local switch. Nothing ships until you pick one.

Pick

My pick: Seam. It sits 200 px from the checkbox you just clicked at every width, against 549 px on average for the Dock and 676 px for today's top bar. It covers no text: 6.5 px clear above and 5.5 px below.

If you prefer calm over reach: Dock. It never moves, it sits under the thumb on a phone, and it is the least code, because it lives in the lower lane that already holds the toasts and Compose.

What still bothers me about the Seam. It moves with every check, with a 160 ms glide on a click and an instant jump on x. The dark pill is the loudest thing on the screen while a selection is open. And it needs a small position loop that runs every frame, which the Dock does not.

Variant Where Travel to Archive, px
row high / middle / low
What it covers Cost
TodayTop bar, replaces the tabs and search580 / 697 / 750Nothing, but at 390 px it wraps to two lines and the count stands aloneThe TopNav bulk rules at 402, 420, and 640 px
DockBottom of the window, centred on the list; takes the Compose slot714 / 498 / 434The lowest 76 px of the window. Rows scroll above it, and j/k keep the cursor row clear of itCSS only
SeamOn the gap under the row you checked last; follows it200 / 200 / 200The padding between two rows, no textA per-frame position loop, about 50 lines
RailLeft margin, beside the row you checked last (1360 px and wider)143 / 143 / 143NothingThe same loop, plus the Dock below 1360 px: two places to learn

Travel is the distance from the clicked checkbox to the Archive button, with the checked row centred at 200, 450, and 700 px in a 1440 × 900 window.

Variants

Two rows are checked in each capture: Carla Rossi and Rail Tickets. All three share one component (SelectionBar.svelte), the same buttons, and the data-bulk-* hooks. The master checkbox shows a dash while only part of the list is checked. Mark read turns into Mark unread when every checked message is read. Keycaps show only with a mouse or trackpad. The bar enters with a 4 px rise over the 160 ms settle token, which is zero under reduced motion.

These stills drop the checkbox focus after the tap, because that focus opens the row's action plate (polish fix 2). The clips show it as it is today.

1440 px

Dock A light card bar, 52 px tall, centred on the list at the bottom. Labels and keycaps.
Seam A dark 32 px pill on the gap under the last checked row. Its first glyph sits on the sender edge.
Rail A 176 px labeled column in the left margin, top-aligned with the last checked row.

768 px, touch

Dock Labels, no keycaps on touch.
Seam Labels, no keycaps on touch.
Rail No margin to sit in, so it is the Dock.

390 px, phone

Dock Full width under the thumb, icons only. Compose steps aside while rows are checked.
Seam Icons only, 280 px wide, still clear of the text above and below.
Rail The Dock.

Today, for comparison

1440 px The bar replaces the tabs and search, far from the rows.
768 px
390 px Two lines, the count stands alone, the wordmark hides.

Clips

Each clip checks three rows, archives them from the toolbar, and undoes with Z. The grey dot is the pointer.

Seam with a mouse. The pill glides to each new check. Chromium, 1440 × 900.
Seam by keyboard: j x three times, e, then Z. The pill jumps without motion. Chromium, 1440 × 900.
Dock with a mouse. The Undo toast stacks above the bar in the lower lane. Chromium, 1440 × 900.
Rail with a mouse. It follows the last checked row down the margin. Chromium, 1440 × 900.
Seam on a phone, by tap. The tapped row also opens its action plate (polish fix 2). Chromium touch, 390 × 844.
Dock on a phone, by tap. Chromium touch, 390 × 844.
Today: the same task with the bar in the top bar. Chromium, 1440 × 900.

What the pick still needs

Polish

Ranked by what a user hits first. Each has a measured number and a crop from the current main.

1. k hides the cursor row under the top bar

After k scrolls the list, the sticky 56 px bar covers 44 px, then 56 px, of the 72 px cursor row. Only its focus ring shows. Fix: one line in layout.css, html { scroll-padding-top: calc(var(--topnav-h) + env(safe-area-inset-top) + 8px) }.

1440 × 520, after two presses of k. The boxed row holds focus.

2. A tapped checkbox opens the row's action plate

On a touch screen in Chromium, the tapped checkbox takes focus, and focus opens the plate. At 390 px the plate covers 196 of the row's 358 px (55%), runs 5 px into the sender, and the row turns hover grey. Fix: open the plate on keyboard focus only (:focus-visible inside the row), not on any focus.

390 px, Chromium touch, right after the tap.

3. A checked row looks like an unchecked row

Checked and unchecked rows share one fill, oklch(99.3% 0.006 92): 0% lightness apart. Only the 18 px checkbox differs. The design reference fills a checked row with the purple wash (.row[aria-checked="true"] in 02-hey.html). Fix: give checked rows bg-accent-wash, a token we already have, 5.9% below the card.

1440 px. The top two rows are checked.

4. The unread count shows three times

The same 9 shows on the Imbox tab, on the All address tab, and in the yellow bubble, at 390, 768, and 1440 px. Fix: keep the yellow bubble, the one bold element on the page. Drop the Imbox tab count while the Imbox is open, and the count on All.

390 px.

5. The address tabs float right on a row of their own

At 1440 px the tabs start at x 942, while every other block starts at x 233. Their row adds 32 px plus a 16 px gap above the greeting. They sit 28.5 px above the greeting, closer than the 42.4 px between sections inside the list, so ui-evidence fails the groups check: they read as part of the greeting. Fix: put them on the greeting's line, right-aligned to the column edge.

1440 px. The same crop shows fix 7: a German date under an English greeting.

6. A double space before the preview dash

The gap between the subject and the preview dash is 8 px; one space is 4 px. A template line break adds a space before the  . Fix: remove the whitespace in MailRow.svelte:164 and SenderStack.svelte:126.

1440 px, zoomed 2 times.

7. German dates in an English interface

The greeting reads "Good evening" over "Donnerstag, 24. September". Every date goes through de-CH, so older rows and the reader show German month and weekday names too. Fix: en-CH keeps the Swiss day-month order and 24-hour time, with English names ("Thursday, 24 September"). WRITING.md fixes de-CH, so this needs you.

8. Six type sizes, the budget is five

ui-evidence fails the type budget at every width: 11, 13, 15, 17, 20, and 34 px. The 20 px step carries only the section headings, the wordmark, and the sign-in codes. Candidate: section headings at 17 px extrabold. A scale change, so it needs a design pass with you.

9. The More tab is a bare chevron from 560 to 839 px

At 768 px the trigger is 44 px wide with no visible label, so a new user cannot name it. Fix: keep the "More" label. If the bar runs short of room, drop the Imbox tab count first (fix 4).

768 px.

Open decisions

WhereWhat remainsWhyNeeds
Selection toolbarSeam, Dock, or RailReach against calm; see PickYour pick
client/WRITING.mdEnglish names in dates (fix 7)The file fixes de-CHYour call
client/DESIGN.md type scaleFive sizes instead of six (fix 8)Changes the look of every headingA design pass with you

Checks

CheckResultOutput
svelte-checkPassed699 files, 0 errors, 0 warnings.
Seam text clearance (pixel-measure gap)Passed6.5 px clear above the pill, 5.5 px below, at 1440 and 390 px. No text box overlaps it in the DOM.
Hit areas in the barsPassedEvery control is a 44 px hit. The Seam's 32 px pill lets its hits bleed 6 px past its edge.
Pointer travelMeasuredScript in Chromium, see the table under Pick.
ui-evidence on today's ImboxFailedType budget (fix 8), groups (fix 5), radius (the Imbox count bubble is not concentric with its tab), edges (one pile avatar 2.3 px off the avatar column), hit area (flags Search and Settings at exactly 44 × 44; not a defect).
Client unit testsNot runExploration only. The switch moves the bar out of the top bar, so tests that expect it there would fail.
SafariNot runNo Safari here, and Playwright WebKit does not start on this machine (missing system libraries).

Test it

  1. In the worktree, run PORT=5373 task dev, then PORT=5373 task dev:seed, and open http://localhost:5373.

    Expected. The Imbox with the seeded mail.

  2. In the console, run localStorage.setItem("fsel-variant", "seam") and reload. The values are dock (the default), seam, rail, and top (today).

    Expected. Checking a row shows that variant.

  3. Check rows with the mouse, then with j and x. Press Archive or e, then Z.

    Expected. The rows leave, the toast offers Undo, and Z brings them back.

Click the image for actual size. Escape closes.