Repository navigation
Studio PWA: stop the random zoom, compact cards, full-screen reading, quieter composer and editor - #81
Merged
Conversation
Causes found, not guessed: - The full editor's draft textarea was 0.9rem (14.4px). iOS Safari zooms any field under 16px on focus and never zooms back, so opening the editor and tapping into the draft zoomed the page every time. - The [[ picker's filter and footer selects (0.85rem, 0.78rem) and the custom theme's hex fields (0.85rem) did the same. - No touch-action anywhere, so a quick second tap on a button was taken as a double-tap zoom. - Imported HTML's fixed-width tables, iframes and embeds had no max-width rule (only img and pre did). Fixes: those fields at 1rem; a pointer:coarse guard that keeps every field at >= 16px whatever a screen or extension rule says; body touch-action: manipulation (pinch zoom untouched, the viewport meta still allows user zoom); wide media, tables and embeds shrink or scroll inside the card, preview and version viewer. e2e/pwa-zoom.spec.ts (mobile): every visible field >= 16px across the main screens, the editor, the picker, a prompt sheet and the subscribe sheet; the viewport keeps user zoom; a post with a long URL, wide pre, table, image and embed never scrolls the page sideways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bottom row was ~48px of emoji thumbs and boxed buttons. Now: - thumbs are 18px monochrome line icons (inline SVG, currentColor, muted); the pressed one takes the accent and fills; - edit, stub ↗ and + add to hopper… are borderless muted text, wording unchanged; ⋯ is a small icon; - every control keeps a 40px tap target (.act: min-height/min-width 40, the hover wash drawn at 32px by a ::before) while the row reads ~20px; - the byline, badge, citation line and "more" are a notch smaller. The row and its handlers move out of Entry into useEntryActions and EntryBar so the full view (next commit) can carry the same row. Shared tokens land in studio.css: a spacing scale, --ctl/--hit, --radius-sm, --muted, and the .act / .btn-compact / .segmented.compact controls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A long thread or RSS post was clamped in its card and "more" expanded it
in place, so long posts made the feed unwieldy. No detail route existed
to reuse (the redesign's sources → timeline stopped short of an article
screen), so this adds one: /reading/{sub}/{id}, with ~own as the sub for
your own items (the id hoppers already use for own posts).
- "more", or a tap on the clamped text, opens it; the title still goes
to the origin, as tests and the read-state rules pin.
- It renders the whole sanitized body with its citation line, the card's
byline, and the same action row (thumbs, stub ↗, + add to hopper…, ⋯
with history), for own items, imported blyg items, threads and L0.
- Opening it marks the entry read, as expanding did. A row reading holds
goes through markReading; one opened by its address (no row) is
written direct, because an optimistic action with no row to change
never calls its mutationFn.
- Deep-linkable: the entry comes from what reading holds, else from
getItem / getImportedItem, so a reload stays on it. No API changes.
- back (and the browser's back gesture) returns to the timeline with the
entry where it was: the opener records the entry's key and viewport
offset (memory + sessionStorage), the timeline restores it.
- Clamp detection also watches the content's children and image loads,
so "more" appears when a post grows past the clamp after images load.
e2e/reading-article.spec.ts: more → URL, full content, reload, back and
browser back to the same entry, tap-to-open; an imported entry by its
address with the action row and a read PUT; an L0 entry; the compact
row's wording, icons and 40px targets. Both projects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Substack's composer; presentation only, no behaviour changes. - Toolbar: one row of small line-icon buttons (attach image, link from clipboard, scan text, make this a …, mark selection as generated), each a 40px .act target that keeps its words as its accessible name and its existing (blygger#48) hover text as its title. [[, ![[ and [TK] are syntax and keep their glyphs as small monospace buttons. The row fits at 390px in the composer and the editor, so no overflow menu. - fragment | thread and draft | preview: a compact segmented control. - save draft / publish: 32px compact buttons, right-aligned on the toolbar's row where width allows, their own row on a phone (also in the editor's action bar, whose ⋯ is now the line icon). Full Editor → is a quiet text action; the counter is small and muted by the actions. - Textareas: borderless in the composer, a soft card in the editor, more padding; the resize grip only with a fine pointer. - The syntax paragraph (composer and editor) folds behind "syntax help", closed by default, open state in localStorage (try/catch), text verbatim. - One scale: .btn-mini moves onto --ctl, the reading head's ☑ ↻ + lose their boxes; spacing, radius and muted tokens from studio.css. The attachments section is not touched. e2e/compose-layout.spec.ts covers names, titles, fit, compact buttons and the remembered disclosure; reading-article checks the read on the server instead of a feed page the entry may have left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Withdrawing them left url-less tombstones on the first feed page, which reading-pwa's every-card-has-a-source-link check then counted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
<details> fires toggle asynchronously, so a reload straight after the click could beat the handler's localStorage write (seen once on desktop). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Studio PWA: zoom, compact cards, full-screen reading, composer and editor
Four problems Aneesh reported with the Studio on the phone, in four commits.
1. The page zoomed in at random (iOS Safari, installed PWA)
Root causes, found by auditing every field's computed style rather than guessing:
field under 16px and does not zoom back, so every tap into the editor zoomed the page.
[[picker's filter and footer<select>s (0.85rem, 0.78rem) and the customtheme's hex fields (0.85rem) did the same.
touch-actionanywhere: a quick second tap on a button was read as a double-tap zoom.max-widthrule (onlyimgandpredid).Fixes: those fields at 1rem; a
pointer: coarseguard keeping every field at ≥ 16pxwhatever a screen or extension rule says;
body { touch-action: manipulation }(pinchzoom untouched, and the viewport meta still allows user zoom, no
maximum-scale); widemedia, tables and embeds shrink or scroll inside the card, the preview and the version
viewer.
2. A compact, Substack-scale card action row
currentColor, muted).The pressed one turns accent-coloured and fills.
edit,stub ↗and+ add to hopper…are borderless, muted text. The wording isunchanged.
⋯is a small icon..act: min 40×40, with a hover wash drawnat 32px), while the row reads at about 20px.
--ink-soft,--pencil,--rule…), so everytheme carries it.
3. "more" opens a full-screen view
There was no detail route to reuse: the redesign's sources → timeline stopped short of
an article screen. This adds
/reading/{sub}/{id}(~ownfor your own items, the idhoppers already use for own posts).
which existing tests and the read-state rules pin.
action row (thumbs, stub ↗, + add to hopper…, ⋯ with history). It works for own
items, imported blyg items, threads and L0 RSS.
getItem/getImportedItem, so a reload stays on it. No API changes.back, and the browser's back gesture (also after a reload), returns to the timelinewith the entry where it was.
post grows past the clamp after its images load.
Found on the way:
markReading(an optimistic action) never calls itsmutationFnwhen no loaded row matches. The full view therefore writes the read directly for an
entry opened by its address. Opening a hopper member that reading has not loaded likely
has the same silent no-op today (
HOPPER_READ), and this PR does not change it.4. Composer and editor, after Substack's composer
scan text, make this a …, mark selection as generated).
[[,![[and[TK]keeptheir glyphs as small monospace buttons. Each keeps its existing words as its
accessible name and its existing (The Studio UI needs tooltips #48) hover text as its
title, sogetByRole('button', { name: 'attach image' })still works. The row fits at 390pxwithout an overflow menu.
toolbar's row when there is room and on their own row on a phone. "Full Editor →" is a
quiet text action in the toolbar. The counter is small and muted, beside the actions.
padding. The resize grip shows only with a fine pointer.
It is closed by default, its open state is remembered in
localStorage(insidetry/catch), and its text is verbatim.
studio.cssgains spacing tokens (--s1…--s5),--ctl(32px compactcontrol),
--hit(40px target),--radius-smand--muted, plus the shared.act,.btn-compactand.segmented.compactcontrols..btn-minimoves onto--ctl, andthe reading head's ☑ ↻ + buttons drop their boxes.
The attachments section, the version/change dialog, the settings AI section, the stub
quote and the grammar files are untouched.
Wording
No labels changed. The emoji in "🔗 link from clipboard" and "⌸ scan text" became line
icons, so those buttons' accessible names are now "link from clipboard" and "scan text".
Suggestion only, not done: "+ add to hopper…" could shorten to "+ hopper" on the card.
Tests
e2e/pwa-zoom.spec.ts(mobile): every visible field ≥ 16px on the main screens, theeditor, the picker, a prompt sheet and the subscribe sheet; user zoom kept; a post with
a long URL, wide
<pre>, table, image and embed never scrolls the page sideways (cardand full view).
e2e/reading-article.spec.ts(desktop + mobile): more → URL changes and full contentshows, reload stays on it, back and browser back return to the same entry, tap to open;
an imported entry by its address with the action row and a read PUT; an L0 entry; the
compact row's wording, icons and 40px targets.
e2e/compose-layout.spec.ts(desktop + mobile): tool names and titles, the row fits,compact buttons, syntax help folded and remembered.
Results:
npm run typecheck: clean.npm test: 1745 passed, 5 skipped.npm run test:ui: 97 passed.<details>firestoggleasynchronously, so a reload could beat the localStorage write. The last commit makes the test wait for the stored value, andcompose-layout.spec.tsthen passes 20/20 (5 repeats, desktop + mobile).One thing to check in the demo: in a full-page screenshot of the phone editor, the sticky save draft/publish bar overlaps the character counter. That's probably a full-page capture artifact, since a sticky bar is painted at the viewport's bottom, but confirm on a real phone-size viewport that the counter isn't hidden behind the bar when you scroll to the end.
🤖 Generated with Claude Code