Skip to content

Redesign the Ember reader for compact feeds, focused discussions, and adaptive navigation - #2

Merged
wieslawsoltes merged 195 commits into
mainfrom
design/reader-polish
Nov 7, 2026
Merged

wieslawsoltes merged 195 commits into
mainfrom
design/reader-polish

Conversation

@wieslawsoltes

@wieslawsoltes wieslawsoltes commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

On-demand CJK text in the browser

The Wasm reader uses the existing Inter face until shaping needs a missing Han, kana or Hangul glyph. It then fetches one hash-pinned 16,437,364-byte Noto Sans CJK SC Regular face from the site's own origin. The offline worker excludes this optional payload from installation. A verified copy is reusable offline. On first use and reconnect, the reader reshapes existing labels and text editors while keeping the caret and selection.

The browser suite exercises Latin-only startup, a failed font request, recovery on the online event, actual shaped glyph IDs and offline reload. It now runs in one CI job against the site artifact produced by the build. Multilingual discussion and editor · Browser packaging and font provenance

Feed, search and history recovery

  • A partially loaded feed reports how many stories are visible in its loaded window and how many requests failed. Retry requests only those IDs and reinserts recovered stories in provider order. Reading more pages and switching public feeds preserve unresolved gaps. An offline refresh keeps loaded pages, their positions and the viewport, even when a cached record was evicted or updated.
  • Desktop search recognizes discussion and profile links. Over-budget UTF-8 text stays in the masthead with one inline correction and is kept out of Recents. Correcting an unavailable username on a phone returns to the same form, including the search draft and selected invalid username.
  • Reader Back reuses owned browser entries and preserves Forward. Find text, selection, reading offset, sort, focus reading and the New replies baseline travel with the visit. Separate visits to one URL keep distinct presentation. The extra browser cache is bounded at 24 visits and clearing history clears it.
  • Discussion Find distinguishes a pending draft or active IME composition from committed results. Its count and navigation buttons reflect the visible query. Reverting to the committed text restores the selected ordinal and viewport without rebuilding the discussion.
  • Regressions cover failed and partial page recovery, 320-pixel and short-landscape hit targets, cache eviction, direct permalinks, duplicate URLs, multi-entry traversals, abandoned Forward branches and explicit tab changes.

Feed recovery at 390 × 844 · Correcting a profile lookup

Final validation passed at 1d2fa76e. All 18 packaged scenarios passed on hosted Chrome and on a separate local Chromium release run. Linux recorded 138 Rust tests, 14 additional parser-only tests and 27 JavaScript tests. Three additional local font tests used the pinned face and exercised loading, shaping and reconnect. Android, iOS simulator and WebAssembly packaging passed. The Wasm module is 38,129,430 bytes. The 16,437,364-byte CJK face is optional and excluded from the startup shell.

CI with packaged Chromium interactions

Reader interaction refinements

  • The active rail or tab destination has a visible accent mark and an accessibility status. Arrow keys follow the rendered orientation, Home and End move to either end, and Enter or Space activates the focused control. Rotation transfers focus between equivalent destinations. Selecting the open story again preserves its loaded replies, Find text and reading position.
  • Local Find announces the result count and Down Arrow moves to the first matching card. A profile with missing submissions retries just those IDs and preserves the successful cards, their order and keyboard focus.
  • A reply timestamp opens the standalone comment inside Ember. The menu can copy decoded comment text, open the original on HN, and fold a leaf reply. Deleted and hidden-dead content uses the same article visibility rules in the feed, discussion, search and keyboard commands.
  • Back and Escape unwind Settings confirmations and generated backups before leaving the page. Closing maintenance disarms the pending action. Clearing History updates its open shelf and removes visit-only IDs while preserving bookmarks and hidden stories.
  • Ctrl+Z or Command+Z outside a text field invokes the available story Undo. Selecting More again while Explore is open keeps its disclosure state.

All 14 packaged Chromium interaction scenarios passed on this exact head, including navigation across three layouts, profile retry after partial failure, clipboard text, reply folding, bookmark undo, Settings Back and offline reload. The 390 × 844 and 1280 × 800 interaction captures were visually reviewed. CI passed 83 reader, 30 core, 20 renderer, 14 parser-only and 18 JavaScript tests, and packaged Android, iOS simulator and WebAssembly successfully. Reader interaction guide · Packaged desktop screenshot · CI for these changes

Performance

The feed shares immutable story payloads and updates only the row ranges that changed. Discussion Find reuses up to 4 MiB of normalized reply text, and its virtual rows reference the original bodies. SQLite and IndexedDB now batch cache operations. The reader writes a compact state snapshot and still accepts existing backups.

Workload Before After
Cold phone permalink to the first frame exposing the title 5.72 s 5.27 s
First warm reload of that permalink (five pairs) 541 ms 588 ms
Repeated warm reloads (21 per release) 517 ms 508 ms
Load nine pages, growing the feed from 30 to 300 stories 883 ms 524 ms
Feed scroll, p95 interval between animation frames 33.4 ms 16.7 ms
Submit four repeat Find queries across 4,096 rich replies 357 ms 55 ms
Find an author and source across 1,200 saved local items 309 ms 204 ms
Read and independently verify 1,001 cached items in Chromium IndexedDB 196.6 ms / 2,002 transactions 47.8 ms / 65 transactions
Same read and verification workload, in-memory SQLite 38.68 ms 10.51 ms
Store 10,000 history entries with 100,000 seen replies 3,560,332 bytes / 4.77 ms 900,220 bytes / 1.17 ms

Timings use the packaged Chromium releases at ec9eb5e and 76362e6 for the UI, and isolated native and browser repository fixtures for storage. On the phone permalink, unrelated HN requests fell from 31 to zero. The Wasm request started at 133 ms instead of 338 ms. In a second comparison with three fresh profile pairs, cold title readiness was 4.95 s versus 4.92 s. Medians for seven worker-controlled reloads per profile changed by −20, −6 and +12 ms across the pairs. Startup work decreased, but the full launch-time gains varied with host load. The first uncached discussion Find took 168 ms in one run. The local Find fixture contained 800 stories and 400 HTML comments. The final frame reported all 16 matches without an API call, and each of five paired runs reached it sooner on the candidate. The measured module grew by 25,234 bytes. The final module is 38,089,633 bytes, an increase of 25,339 bytes (0.067%) over baseline. Discussion-scroll results overlapped on repeat runs. The frame data uses SwiftShader on Linux. All 12 interaction scenarios passed on the final packaged browser build. A shell-only upgrade activated and reloaded offline with its 38 MB module unavailable from the server.

The final three-line reader change keeps keyboard refresh scoped to a single visible discussion. The native key-event regression covers phone and split view. In the final phone-sized release, pressing R with focus in the reader fetched only the visible discussion. Back then fetched the front page exactly once. Linux, desktop, Android, iOS simulator and WebAssembly CI passed. The test jobs recorded 126 Rust, 14 parser-only and 18 JavaScript tests. Performance methodology and reproduction commands · Final CI

Reader workflow updates

  • Returning to Front, Latest, Best, Ask, Show or Jobs restores that tab's loaded pages, pagination cursor and scroll position. In split view, S, O, J and K act on the focused feed row.
  • Reply menus open a standalone comment thread or jump directly between visible siblings. Back restores the deeper range loaded with Load more comments, the reading position and the originating search-result row. Standalone comments no longer identify their own author as the story's OP.
  • Settings preserves unfinished edits on reselection and keeps the hide-read preference synchronized with the feed. Readers can discard edits or select generated backup text for manual copying.
  • Download directly from a feed without marking the story read. The progress strip stays available across tabs and supports cancellation. Verification is local-only. A bookmark edit made during a download takes precedence on completion.

Verified. All 118 Rust tests, 13 parser-only checks, 15 JavaScript tests and 12 release Chromium scenarios passed. The release cases exercised page-two feed restoration, shortcut targeting while a different discussion is visible, nested reply navigation, preference draft discard, canceled downloads, phone rotation and cold offline reload. Android, iOS simulator, Linux desktop and WebAssembly builds passed at ec9eb5e. The final extracted site was byte-identical to the release used for the twelve Chromium scenarios. Implementation CI · Final PR checks

Reply navigation · Offline download progress · Preference drafts

Reader experience

  • Compact feed rows keep the headline first, with the source, author and age on a single metadata line. Swiping previews Save, Unsave, Hide or Restore, indicates when the action is armed, and allows the gesture to be reversed before release.
  • Discussion Find works across loaded prose, code and links. Search this discussion can search indexed comments beyond the loaded thread and open an exact result. Code samples have per-block wrapping, a line count and an exact-source copy action. Supported HN links in story text, comments and biographies open inside the reader with Back navigation.
  • Downloads remain available after their bookmark is removed. A verified discussion has controls to check its local copy or stop keeping it offline. Settings measures cached storage on request and offers reviewed cleanup that protects bookmarked roots, shared replies and verified downloads.
  • Mute-rule edits survive switching rule types. Readers can save all draft changes together or discard them to restore their last saved settings.
  • The reader also supports contextual reading suggestions, reversible story actions, Find across local collections, accessible keyboard navigation through folded branches, new-reply catch-up, profiles with recent activity, and adaptive phone, tablet and desktop layouts.

Reading position and navigation

  • The Unread count covers loaded, visible stories. Find more unread checks up to three more pages per action and stops when it finds a visible unread item. The U shortcut works when the feed is visible.
  • Read in context opens a Find match or new reply at its location in the full discussion. It expands the path to the selected reply and keeps unrelated branches folded. Ctrl/Command+Enter opens the active match from Find. Reading positions now include the distance into long comments and story headers.
  • Back restores unsent search values, selections, filters, focus and scroll position after opening a profile or discussion. Repeating the same search retains its loaded pages. In split view, Find follows the active pane.
  • Legacy permalink forms round-trip through browser history. Duplicate browser events preserve an open Find session. For comment search hits found in the parent thread, reading the comment clears its unread marker and keeps the parent discussion in History.
  • The browser worker can recover when Chromium rejects caching the streamed WebAssembly response. Activation still requires a complete shell. The fallback preserves the Wasm content type and handles decoded HTTP bodies.

Phone screenshot · Desktop screenshot

Previous verification

CI passed at 2a168d6.

  • 110 Rust tests passed across the reader, renderer and core, plus 13 parser-only checks and 15 JavaScript tests. Linux desktop, Android packaging, the iOS simulator bundle and the WebAssembly build all passed.
  • All eight Chromium scenarios passed. They exercised phone touch, desktop keyboard navigation, live resizing, opening a filtered reply in context and a cold offline reload with the network disabled. The final CI site's extracted files matched the browser-tested site byte-for-byte.
  • A fresh Chromium profile activated the worker and read back all 12 cached assets, including the complete 38,019,193-byte WebAssembly module.

Earlier verification

Earlier PR checks and platform artifacts passed at c081576.

  • All 102 Rust tests passed locally and on CI (56 reader, 19 renderer, 27 core). The renderer-free build also passed its 13 parser tests. The locked Linux desktop check, 12 JavaScript tests, release WebAssembly build, Android APK and iOS simulator bundle passed. Application build and artifacts.
  • Seven Chromium scenarios passed against the verified release artifact. They exercised reversible touch swipes, phone rotation, opening and releasing an unbookmarked download, cold offline reload, temporary cleanup in IndexedDB, code wrapping and exact clipboard contents, internal navigation, indexed discussion search and return-visit reply navigation.
  • A separate phone interaction verified that discarding mute-rule drafts across types restored the controls and preserved saved rules across reload.
  • A separate live-data check sent comment,story_8863 to Algolia and opened matching comment 9707 in the release reader. It used the host’s trusted HTTPS client for the public API transport.

Preview

Compact feed · Swipe feedback · Code tools · Discussion search · Offline storage · Full gallery

…subscriptions

The FerroUI submodule moves to the latest main, with the workspace blocks
written by sync-workspace.py and the lock file to match. The launcher and
the font collection key take url::Url now. Subscriptions return a guard
that unsubscribes when dropped; the menu, theme and timer handlers that
live as long as their source are detached, as on main.
The FerroUI submodule moves to main 0c0ea2a72: control themes as static
tables (#607), type checks by identity (#616), and the Java layer of the
Android backend building on the newer JDK of ubuntu-latest (#621), which
the Android package job of this pull request failed on. The workspace
blocks come from sync-workspace.py.
@wieslawsoltes
wieslawsoltes merged commit 805c7a7 into main Nov 7, 2026
7 checks passed
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