Switching tabs shifted the layout. The cause was broader than History being
full width: all four subroutes set their own width, so nothing lined up.
Send max-w-xl 576px
Bulk max-w-3xl 768px
API max-w-4xl 896px
History none full bleed
The header and tab strip were also full width while the view beneath them was
constrained, so on Send the tabs ran roughly 200px past the form.
The column is now defined once in the messaging layout and wraps the header,
the tabs and the children together. The per-page wrappers are gone, so a new
subroute inherits the width instead of inventing one.
Guard: an e2e visits all four tabs, measures the view rendered beneath the tab
strip, and asserts the widths match and that the column is constrained rather
than stretched.
Getting that guard to actually work took three attempts, each a real hole:
- Comparing the column against the viewport passed even with the constraint
removed, because the sidebar and padding already made it narrower.
- Comparing against the parent's border box passed too, since that box
includes the padding the column sits inside. It now subtracts the padding.
- Measuring the layout column could not detect a page re-constraining itself,
because that column is identical on every tab by construction. It now
measures the rendered view instead.
Verified by reintroducing both regressions separately and watching it fail
each one, with the offending tab named in the message.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new shared RouteTabs primitive (components/shared/route-tabs.tsx): a
link-based segmented control; tabs are real routes so the active tab
survives refresh and deep links are shareable; aria-current on the active
link and the active pill scrolls into view on mobile
- messaging/layout.tsx renders the section header + tabs; views become
subroutes: / (Send, focused composer), /bulk, /history (now full width,
previously cramped in a half column), /api-guide (the 419-line guide gets
its own URL instead of half the page)
- delete the obsolete client-side tab wrapper (components)/messaging.tsx and
dead main-dashboard.tsx
- messaging e2e rewritten for route tabs, including a direct-load test of
/dashboard/messaging/history as the refresh-survival guarantee
Build, 25 unit tests, and 12 e2e green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guards the messaging screen (Send/Bulk/History tabs and the message-history
list against mocked data) before decomposing the 1051-line message-history
component.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>