Repository navigation
Conversation
The note's graphicsLayer faded with alpha < 1 under CompositingStrategy.Auto, so every open and close rendered the whole screen into a full-size offscreen buffer. Allocating and clearing it held the RenderThread for about 100 ms on the first frame on a Pixel 8. ModulateAlpha fades each draw instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each word of a turn was a LinkAnnotation.Clickable, so Compose made one child node per word. On a Pixel 8 this dominated the main thread while a long note scrolled, the open transition (35-125 ms frames) and the switch to the finished transcript, where the per-word clip paths stalled the RenderThread for 290 ms. A new active word also added a span, so every word change laid out the paragraph again. The paragraph now keeps one AnnotatedString per words, mode, unsettled range, matches and colours. One tap detector maps the tap position to a word through the text layout; a tap on a space goes to the nearer word on the line. The pressed and the active word are drawn from the layout in the draw phase, and the active word's glyphs are drawn again in onAccentFill inside its rectangles. The rectangles are cached per layout and word. An effect reports the active line when the word changes, because the text no longer lays out again. LocalDeferWordLinks and its provider go, as there are no links to defer. TalkBack loses the per-word action; the "Play from start of turn" action stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The word links used clickable, which in a scrolling list shows the press only after the tap timeout. The tap detector set the pressed word at once, so every scroll that started on a word flashed its accentTint background. The pressed word now shows after the tap timeout, and a quicker tap shows it for one frame. The pressed word is now cleared in a finally block. Before, a press that the detector stopped (new words, or a switch to Draft) left the word highlighted against the new layout. ui.md section 6 and its accessibility mapping now describe the tap detector and the words drawn from the layout instead of the word links. The paragraph's known gaps also name features.md section 14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The waveform redrew only when the levels changed: about ten times a second, and the 100 ms samples against 125 ms bars shifted it in an uneven 100/100/100/200 ms rhythm, so it stepped instead of scrolling. Each level emission now carries the captured time of its newest sample in a new WaveformLevels value of :ui, which also holds the bar count, the bar width and the sample period that RecordingController uses. While recording, the Waveform runs a frame loop that moves a playhead from that time plus the time since the sample arrived, at most two samples ahead and never back. The loop writes frame state that only the Canvas draw lambda reads, so each vsync redraws without a recomposition and without allocations in the draw. One more bar at each edge keeps the scroll free of gaps, and the newest bar grows to its level over a few frames. Paused, in previews, with reduced motion and in tests (which stop infinite animations) the waveform draws the same static picture as before, so the goldens do not change. The 1 s policy tick of the controller stays as it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the animation scale at 0, the running waveform showed the static picture but still asked for a frame on each vsync for the whole recording. Now the loop shows the static picture and waits for the scale factor, a snapshot state, to change before it asks for frames again. The Compose test of a running waveform only showed that a cancelled loop draws the static picture, so it now has that name. WaveformMotion is internal, and new JVM tests give it frame times and samples: the first frame is the static picture, the bars never move back and stop at the limit, a new bar keeps the picture in place and keeps the bar that scrolled out, the newest bar grows to its level, and an earlier captured time starts again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tracker computed progress only when a decode window (about 26 s of new audio) completed, so the card's bar sat still for seconds and then jumped. The tracker now publishes an anchor with the progress: the done fraction and the time of the last completion, the end of the window being decoded, and the pace measured by the new ProgressPace in :core (the first window of a run includes the model load, so it only sets the start). The anchor changes only with a completion or a new window, so equal ticks stay equal and the Note state is not rebuilt more often. The card's LinearTrack draws its fill in a modifier node. It predicts the fill from the anchor, never past the end of the current window and never backwards, and glides to each published value instead of jumping. Only the draw is invalidated, at most once per pixel; while it follows the pace, it waits for the next pixel instead of asking for every frame. With no change it draws the given fraction exactly, so the previews and goldens do not change. With reduced motion it goes to each value at once. The percent and the stats keep their update rate. TranscriptionProgress, verbatim code, does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The moving fill was drawn at x = 0, so in an RTL layout the card's bar filled from the left while the static track (and the Library banner) filled from the right. The fill now starts at the right edge in RTL. A new test compares the moving and static tracks in both directions. The glide decayed the distance to an aim that keeps moving with the anchor's prediction, so it trailed the aim at about a quarter second of pace. At any pace above about 6 px/s that lag stayed over the snap distance and the loop asked for every frame until the window end. TrackMotion.step now takes the aim of the frame before: the track moves with the aim and only the gap to it decays, so a glide settles in about the same time at any pace and the loop goes back to one wake per pixel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The words are no longer LinkAnnotations, so the design docs, the development notes and the device accessibility test lose the inline word link exception. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The push transition faded the note in over 1 ms. Its first frame drew the whole screen at alpha 0 through a full-size offscreen buffer, and allocating and clearing that buffer still held the RenderThread for about 93 ms on a Pixel 8 after the morph itself stopped using one. noteContainerTransition already starts the note at alpha 0, so the fadeIn did nothing visible. 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.
Perfetto traces on a Pixel 8 (120 Hz, minified release build) showed:
LinkAnnotation.Clickable, so every paragraph had one child node per word with its own clip path. Scrolling a long note spent most main-thread time inLinksComposables/LinksTextMeasurePolicy; each playing-word change relaid out the paragraph and rebuilt all its links; the frame after the open morph built them all at once. The per-wordPaths also fed NativeAlloc GCs that coincided with 100–300 ms RenderThread stalls.alpha < 1underCompositingStrategy.Auto, which rendered the whole screen through a full-size offscreen buffer (≈100 ms on the first frame).Changes:
LocalDeferWordLinksis gone.CompositingStrategy.ModulateAlpha.Goldens are unchanged. Local: detekt,
ciTest, lint (:appdebug + release,:data,:ui), verbatim, links, resource collisions, and:app:assembleDebugAndroidTestpass.🤖 Generated with Claude Code