Skip to content

feat(viewer): opt-in wide layout for wide monitors - #268

Open
banozz0 wants to merge 4 commits into
modem-dev:mainfrom
banozz0:feat/wide-mode
Open

banozz0 wants to merge 4 commits into
modem-dev:mainfrom
banozz0:feat/wide-mode

Conversation

@banozz0

@banozz0 banozz0 commented Sep 25, 2026 •

Copy link
Copy Markdown

Refs #223. This covers the "display the text over a wider area" part. It doesn't include the collapsible sidebar or the text-density config.

Problem

The feed column is capped at max-width: 860px (#stream and .standalone-main). On a ~1930px window about 60% of the screen stays empty, and diff and code cards scroll sideways to show lines that would fit.

What this adds

  • Per-workspace width setting. GET/PUT /api/width takes {"id":"normal"|"wide"}. It's stored with Store.setSetting("width") like the theme, so there's no schema change and it works on SqlStore, JsonFileStore and the Durable Object. A width-changed SSE event makes other open tabs follow live. Like /api/theme, the GET is readable in public-read mode and the PUT still needs auth.
  • Toggle. A ↔ pill sits beside Stream/Timeline and is hidden on phones. It puts a wide class on the engine root, and #stream / .standalone-main grow to 1600px. Only max-width changes, so the phone @media rules are untouched.
  • Markdown keeps a readable measure. MD_CSS caps prose elements (p, headings, lists, dl, blockquote) at 780px, just above their width in the normal column. Fenced code blocks and tables aren't capped, so they use the full width in wide mode. The CSS is static, so there's no render-cache key change and nothing reloads on toggle.
  • Long lines soft-wrap in wide mode. Even a 1600px column can't fit a 4000px line, so in wide mode the viewer requests ?wrap=1 for the server-rendered text kinds. The wrappable kinds are markdown, code, terminal and diff, marked by softWrap in SURFACE_KIND_METADATA. Markdown fences, code and terminal output use pre-wrap, and wrapped code lines hang under the code rather than the line number. Diffs use @pierre/diffs' native overflow: "wrap". The flag is part of the render cache key and ignored for other kinds. With wrap off, output is byte-identical: a unit test deep-compares it, and an e2e test checks that all four kinds scroll in normal mode and don't in wide mode. surfaceSrc now builds every frame URL, including the version picker's.

Normal stays the default, and the self-hosted normal layout is unchanged.

Side effects worth knowing

  • A markdown surface opened fullscreen, or loaded top-level at /s/:id, now caps prose at 780px in normal mode too. It used to span the whole dialog or page.
  • A fenced block nested inside a list item or blockquote stays within its capped parent at 780px.
  • On first load in wide mode, the column can briefly render at 860px before the width setting arrives, because it's fetched in parallel with posts.
  • Toggling width reloads the text frames. If an older version is picked in the version dropdown, those frames reload at the latest version while html and mermaid frames stay on the older one.
  • Embed hosts get the toggle and the .wide class through the engine as-is. There's no embed-specific e2e.

Verification

  • npm test: 502/502 node, 8/8 viewer
  • npm run test:worker: 1/1
  • npm run test:e2e: 190 passed (chromium + webkit), including the new e2e/width.spec.ts. That spec checks that the toggle widens the column and persists across a reload, that another tab follows live, that the phone layout is unchanged, that a long-line diff has no horizontal overflow in wide mode, that markdown prose is the same width as its code block in normal mode, and that in wide mode prose stays ≤780px while the code block goes past 1200px.
  • typecheck, lint, format:check: pass
  • bench:check --gate deterministic: no regressions
  • Checked by eye at 1930px in Chromium and WebKit, light and dark: markdown, unified and split diffs, code, terminal, flowchart and sequence mermaid, json, html, image, timeline, standalone, and Home (which keeps its own 880px column).

Changeset: minor.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VZX7jtqqerDjNe5Q813hbK

banozz0 and others added 3 commits September 25, 2026 22:43
The feed column is capped at 860px, so on a wide window most of the
screen is empty and diffs scroll sideways. Add a per-workspace width
setting, persisted and broadcast like the theme:

- GET/PUT /api/width ({"id":"normal"|"wide"}), stored via
  Store.setSetting("width") (no schema change), width-changed SSE so
  other tabs follow, public-read GET like /api/theme.
- A toggle beside Stream/Timeline (hidden on phones) puts a `wide`
  class on the engine root; #stream and .standalone-main grow to
  1600px. Mobile rules and normal mode are untouched.
- Markdown keeps a readable ~80ch measure via right padding on its
  iframe, so no server render change, cache key or frame reload.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Padding the markdown iframe capped the whole document at 740px, so fenced
code blocks and tables still scrolled sideways inside a half-empty wide card.
Cap prose elements in MD_CSS at just above their normal-column width instead:
normal mode is unchanged, wide frames keep a readable measure, and pre/table
use the full width.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZX7jtqqerDjNe5Q813hbK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZX7jtqqerDjNe5Q813hbK
Even a 1600px column can't fit a 4000px line, so text surfaces still
scrolled sideways. In wide mode the viewer now asks the server-rendered
text kinds (markdown, code, terminal, diff; `softWrap` in the surface
metadata) for `?wrap=1`: pre-wrap for markdown fences, code and terminal,
with code lines hanging under the code rather than the line number, and
@pierre/diffs' native `overflow: "wrap"` for diffs. The flag is part of the
render cache key and ignored for other kinds. Normal mode output is
byte-identical. The frame URL is now built in one place (surfaceSrc).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZX7jtqqerDjNe5Q813hbK

This branch has not been deployed

No deployments
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