Skip to content

feat: add collapsible side rails to the feed - #72

Merged
dimslaev merged 2 commits into
masterfrom
feat/rails-shell
Oct 10, 2026
Merged

dimslaev merged 2 commits into
masterfrom
feat/rails-shell

Conversation

@dimslaev

@dimslaev dimslaev commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Session 1 of 4: the rail shell only. The registry (components/Rails/panels.ts) is empty, so the page looks the same as today.

What's in it

  • components/Rails/:
    • RailsProvider: open state per side below 1360px, collapsed state per panel, the panel registry, and the [ / ] / Esc keys.
    • Rail, RailPanel, RailStrip.
    • RailsLayout: the grid, the inline rails, the mid-size overlay and the phone sheet.
    • RailToggles: the header buttons.
    • ScoreMarks: MiniRail and ScoreBars, both built on scoreFraction and SCORE_STANDOUT.
  • components/Common/PageColumn.tsx: the mx-auto max-w-3xl px-4 column, moved out of the _layout.tsx main.
  • _layout.tsx mounts RailsProvider next to FiltersProvider and passes RailToggles to the header next to UserMenu.
  • Analytics: rail_toggle {side, open, via} and rail_panel_toggle {panel, open}.

Behaviour by width

  • ≥1360px: both rails are always open inline. There are no header buttons, no hide button and no strips, and [ / ] do nothing. Panels inside a rail still collapse one by one.
  • 760–1359px: rails start closed as 36px edge strips. A strip, a header button or [ / ] opens a rail as an overlay over the feed edge. Esc or a click outside closes it.
  • <760px: header buttons open a rail as a full-screen sheet under the header. The same button closes it.
  • localStorage agentique.rails.v1 holds only {collapsed: {panelId: true}}. Every read and write is wrapped in try/catch.

Where I changed the plan, and why

  • Wide screens (follow-up commit, on request): the plan had collapsible rails on wide screens with saved left/right state. Rails are now always open there, and the saved left/right state is gone.
  • Column wrapper: pages still get PageColumn from the layout by default. The feed route opts out with staticData: { wide: true } and sets its own width through RailsLayout. This means admin, developers and profile stay unchanged, and an error or empty page inside the layout keeps the column. The other way was wrapping every page by hand.
  • 760 to ~870px: a 36px strip doesn't fit in the gutter. When a side has panels in mid mode, its gutter gets minmax(52px,1fr), as on design board A. The feed column only narrows when the viewport is under about 872px. With no panels nothing changes.
  • One overlay or sheet at a time below 1360px. Opening one closes the other.
  • Click outside closes a rail without sending a rail_toggle event, because via only allows key, button or strip. Esc is sent as via: "key". Focus goes back to the trigger on Esc or a button press, but not on an outside click.
  • Rail headings are placeholders in panels.ts (RAIL_TITLES): "People and topics" and "Stories". The panel sessions can rename them.
  • Sheet: it's non-modal, so the header button stays clickable and closes it. The shadcn X is hidden because the rail heading row already has a hide button. ui/ is untouched.

Verified

  • bun run lint (biome) and bun run build pass. Lint also reformats vite.config.js / vite.config.d.ts, the same as on master, so I left those files out of the commit.
  • Empty registry looks the same as master. I built master and this branch into separate dist folders, served both with vite preview, and mocked the API with Playwright using the same fixture articles. I took full-page screenshots at 1440, 1000 and 390 of /, /developers and /login, and of /, /profile and /admin logged in (mocked users/me). All 18 pairs are byte-identical PNGs. I re-ran this after the follow-up commit.
  • Throwaway placeholder panels (not committed): two left and one right. A Playwright script found:
    • Wide (1440): both rails open, no header buttons, no hide buttons, no strips, and the keys do nothing. Panel collapse sets aria-expanded=false, aria-controls points at the panel body, and the collapse survives a reload. The saved value is {"collapsed":{"who-wrote":true}}.
    • Mid (1000): header buttons are shown. Rails start closed with strips showing. A strip opens a fixed overlay with focus on the heading. Esc closes it and focus goes back to the strip. [ opens the left rail and a click on the feed closes it. A header button opens and closes it.
    • Phone (390): no strips. The header button opens a sheet at y=48, full width, with focus on the heading. The same button closes it and focus goes back to the button. [ opens a sheet and Esc closes it.
    • Edges: no horizontal scroll at 1360, 1359, 900, 760 or 759.
    • Storage: with the agentique.rails.v1 key throwing on read and write, the page renders and the rails work. With all localStorage throwing, the page crashes on master as well. The cause is the unguarded access_token reads in useAuth and analytics.send, which are outside this PR.
  • Playwright rail tests come with the first real panel (who-wrote), as planned.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EL8QWwpqLcAfQ2uN5vqj5T

@dimslaev
dimslaev merged commit c0b44bb into master Oct 10, 2026
3 of 5 checks passed
@dimslaev
dimslaev deleted the feat/rails-shell branch October 10, 2026 08:20
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.

2 participants