Skip to content

Smooth Library return, Baseline Profile, word-accurate highlight - #16

Merged
silviot merged 9 commits into
mainfrom
word-sync
Oct 17, 2026
Merged

silviot merged 9 commits into
mainfrom
word-sync

Conversation

@silviot

@silviot silviot commented Oct 17, 2026

Copy link
Copy Markdown
Member
  • Library kept composed under an open note (LibraryNoteScene): closing a note no longer composes the Library from scratch. Pixel 8, five open/close cycles: worst frame 30 ms (was 69–98 ms on close, 42–77 ms on open).
  • Baseline Profile for the app's own code (:baselineprofile, scripts/baseline-profile.sh): the code no longer runs interpreted the first times.
  • Word highlight switches at each word's start using a frame clock extrapolated from the 150 ms position ticks (was 100–200 ms late). Word timestamps were measured against the audio and are accurate.
  • Codex's review fixes: cached Library row mapping, lazy-list content types, slider draw-phase read.
  • 0.3.0 changelog.

Each change was reviewed adversarially; detekt, unit/screenshot tests and lint pass locally.

🤖 Generated with Claude Code

silviot and others added 9 commits October 16, 2026 21:31
LibraryListing caches the mapped day sections and search results and maps them
again only when Room or search publish a new list, the row context changes, or
the day or zone changes. A progress update or an opened dialog no longer walks
the whole catalogue. The lazy lists of the Library, the draft and the Trash
give their items content types, and the playback slider reads its value in
the draw phase.

From a review of smooth-ui by Codex on DrMeyers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app had no Baseline Profile of its own, only the rules of its libraries, so
the Library, Note and playback code ran interpreted the first times. On a
Pixel 8 closing a note took 69-98 ms; fully compiled it took about 43 ms.

The :baselineprofile module drives the releaseTest build (release without R8)
through startup, the Library, three note openings with scrolling and playback,
Settings and Trash. scripts/baseline-profile.sh runs it with direct adb
instrumentation, so the app data stays, and writes app/src/main/baseline-prof.txt.
R8 rewrites the profile for the minified release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stack that ends in Library, Note is now one scene, LibraryNoteScene,
that holds the Library under the note. Navigation 3 renders each entry
through one movableContentOf and, in a transition, renders a shared entry
in the target scene only, in the composition that adds the target. So the
Library moves into the note's scene on open and back into its single-pane
scene as the close starts, also on a predictive back and its cancel:
closing a note no longer composes the Library from scratch.

Under the note the Library is capped at CREATED, so it collects no state
and its search back handler is off. While the note covers it, it is not
placed: no drawing, no touch, no semantics. The note animates alone over
it: the morph from its row, or the slide of push()/pop() when the row
bounds are unknown, instead of sliding the whole scene.

AppShellTest checks that the Library's layout nodes survive a close and a
predictive back, that a cancelled gesture keeps the note, and that back
from a note opened from search results leaves the Library's query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pushing a screen over the note, or popping back to it, no longer places
the Library or exposes its semantics: the scene hides it unless
NoteMorph.noteId (now snapshot state) names this note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The active word followed the position published every 150 ms, so it
switched on average 100 ms (at most 200 ms) after the word began. Each
published position now carries the time it was read and an epoch that
changes on every jump. While the sealed transcript plays, the pane
extrapolates that anchor in a frame loop, slews small corrections without
going back, resets on a seek, pause or large difference, and looks the
word up in a precomputed array of starts. The state changes only when the
word does; the slider, time text and saves keep the 150 ms tick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frame loop is keyed on playing only, so a new turns list no longer
restarts it and drops the clock; the word is cleared when playback stops.
The clock is clamped to the file's duration, the unused speed is gone,
the frame loop allocates nothing, and the anchor and epoch tests cover
pause, drafts, the seek release, samples, reload and play from the end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pushes and pops faded both screens while the new one moved 30 % of the width.
Two half-transparent screens over each other looked muddy, and an alpha below 1
draws each screen through a full-size offscreen buffer on every frame. The new
screen now slides in over the full width on top while the old one moves 25 %
under it (350 ms, Material 3 emphasized decelerate); a pop is the reverse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@silviot
silviot merged commit 7068c58 into main Oct 17, 2026
10 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