feat(compose): generic switchable trace for lazy layout measure - #121
Open
bytemain wants to merge 192 commits into
Open
feat(compose): generic switchable trace for lazy layout measure#121bytemain wants to merge 192 commits into
bytemain wants to merge 192 commits into
Conversation
V2 styled string nodes are rendered by ArkUI Text internally. Registering the legacy foreground-draw callback for those nodes can route them through OH_Drawing_TypographyPaint before the paragraph is layout-ready, which spams paragraph-is-not-formatted warnings. Track the styled-string path on KRRichTextView, unregister/skip foreground draw for that path, and reset styled-string state when falling back to the legacy typography path.
Throttle Compose↔Native scroll sync (calc/expand deferral, contentSize dedup, scrollEnd finalize), quantize ArkUI onScroll in fling, and draw RichText when typography is ready during main-thread tasks. Co-authored-by: Cursor <cursoragent@cursor.com>
Quantize ArkUI scroll callbacks and SubcomposeLayout deltas at 0.5px, dedupe content size updates, and flush the final offset on scroll stop. Add OHOS demo cold-start page params and a CanScrollForward repro page. Co-authored-by: Cursor <cursoragent@cursor.com>
…ess test Add a 500-item repeated demo list with Card UI and inspector enabled to reproduce and profile HarmonyOS scroll performance locally. Co-authored-by: Cursor <cursoragent@cursor.com>
Clamp ArkUI scroll-frame-begin offset at nested list edges and skip compose-side expand/offset correction when nestedScroll is configured. Co-authored-by: Cursor <cursoragent@cursor.com>
Only recalculate native content size when contentView main-axis height changes during fling, avoiding ~650 redundant calc calls per gesture while keeping near-bottom and scrollEnd sync unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
Extend KuiklyScrollTrace with remeasure, frame timing, and offset/drag skip metrics for OHOS scroll stress validation; gate redundant contentOffset and isDragging writes; skip resetViewVisible when unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
Wake a retained Kuikly subcompose slot's draw ownership when it is successfully restored from the reusable pool, allowing hidden native descendants to flush visible on the first reactivated frame. Cover same-key and compatible-key reuse, consecutive reuse cycles, and the already-active no-op path through the real precompose/dispose/retrieve state machine. Signed-off-by: MingQi <raft-mobile-mingqi@mail.build>
Wake draw ancestry when a precomposed slot becomes active through real subcompose consumption. Signed-off-by: MingQi <raft-mobile-mingqi@mail.build>
Dirty restored lazy descendants and force their draw ancestry after offscreen visibility is restored. Cover the real native mount, placement, restore, and clean-leaf lifecycle with production-callsite mutation teeth. Signed-off-by: MingQi <raft-mobile-mingqi@mail.build>
Preserve child content for bare draw nodes, route view-aware fallback through ordinary draw overrides, and retain explicit view-aware precedence. Add behavior coverage for all three implementer classes and a self-testing production-wiring constraint for LayoutNodeDrawScope.drawDirect. Signed-off-by: MingQi <raft-mobile-mingqi@mail.build>
Signed-off-by: BiSheng <raft-mobile-bisheng@mail.build> Co-authored-by: BiSheng <raft-mobile-bisheng@mail.build>
Task Tencent-TDS#183: synchronize Compose offset after native viewport changes. Reviewed exact: 175c8ac Independent review: task Tencent-TDS#205 FINAL FULL-SEAT PASS / 0 blocker. Hosted exact matrix: 30852685305. Signed-off-by: MingQi <mingqi@mail.build>
Signed-off-by: Android-Developer-1 <raft-mobile-android-developer-1@mail.build> Co-authored-by: Android-Developer-1 <raft-mobile-android-developer-1@mail.build>
Signed-off-by: Jony <jony@mail.build> Co-authored-by: Jony <jony@mail.build>
Signed-off-by: KMP-专家 <raft-mobile-kmp-expert@mail.build> Co-authored-by: KMP-专家 <raft-mobile-kmp-expert@mail.build>
Keep the active programmatic scroll target aligned with the native scroller when a viewport shrink commits a smaller frame. Preserve owner fencing across synchronous native echoes, lock the behavior with a renderer-level regression fixture, and remove the obsolete source-code assertion gate. Signed-off-by: MingQi <mingqi@mail.build>
Adds a diagnostics seam inside the lazy list measure pass, for a host that needs to know which measure or placement first diverged from what the user saw. A consumer reading LazyListState.layoutInfo only sees a snapshot after the fact and cannot answer that. Generic by construction: no product vocabulary enters the library. The host supplies opaque traceSession/cycle/layoutGeneration/targetToken values, which are compared for equality so records can be joined and never parsed, persisted, or interpreted. The sink is injected by the caller and released with its composition, so this never becomes a second owner of anything. Switched by a compile-time constant rather than a runtime flag, so a disabled build can be shown to do no work rather than merely to stay quiet: the measure lambda is inline and the check precedes it, so no geometry is gathered, no list is traversed and no string is built when tracing is off. requireConsistentWith fails a build whose host and library switches disagree, since that yields a chain silently missing one layer. Viewport coverage unions clipped item spans instead of summing them, so sticky or overlapping items cannot report a viewport as fuller than it is and hide the empty band the measurement exists to expose. measureLazyList takes the handle as a defaulted trailing parameter, so its existing caller is unchanged and ordinary builds pass null. Signed-off-by: CC-希乐 <raft-mobile-cc-xile@mail.build>
Without this the seam added in the previous commit could never fire: the sole caller of measureLazyList passed nothing, so the defaulted parameter stayed null and no record was ever produced. The handle now reaches the measure policy through a static composition local, read once where the policy is remembered rather than inside the measure lambda — so the measure path performs no lookup, and a host that provides nothing still pays nothing. Scoped to the provider's composition, so the handle is released with the screen that supplied it instead of persisting as global state. Signed-off-by: CC-希乐 <raft-mobile-cc-xile@mail.build>
Two wiring gaps @mingqi found by reading the commit rather than the description, either of which produces a package that compiles green and traces nothing. ENABLED was hardcoded false with no build wiring, so the inline guard returned before the lambda and no record was ever produced — the exact "looks healthy, contains nothing" failure this work exists to expose. This forensics branch sets it true; main keeps it false, and that constant is the single line a build flips. The frame identity fell back to (-1, -1) when unsupplied, which yields records that cannot be joined but still look like data. With tracing enabled a missing frame is now a wiring error, and LazyList supplies a real identity: a monotonic sequence and clock reading, allocated only when a host actually provided a trace. Signed-off-by: CC-希乐 <raft-mobile-cc-xile@mail.build>
Found by @mingqi. The remembered measure lambda did not list the trace handle among its keys, so it closed over the first one permanently. The handle carries the host's correlation identity — which changes per IME cycle and layout generation — so every later record would be attributed to a stale cycle: records that look valid and join to the wrong pass, which is worse than no records. Signed-off-by: CC-希乐 <raft-mobile-cc-xile@mail.build>
Measure records where items should go; this records that placement actually ran for that frame, with the target's placed offset and size and the resulting viewport coverage. Without it the interval between "measure geometry was correct" and "the pixels were white" is a blind spot crossable only by assuming adjacent timestamps belong to the same frame — an inference already retracted once here. Placement carries the same frame identity as the measure record. Signed-off-by: CC-希乐 <raft-mobile-cc-xile@mail.build>
@mingqi read the exact and found the record sat at the top of the layout block, before any it.place() ran — it merely re-reported measure's positionedItems and proved nothing about placement. It now emits after the item and header placement calls and after attachToScope, so it describes work that has actually happened. Signed-off-by: CC-希乐 <raft-mobile-cc-xile@mail.build>
On a viewport shrink this path corrects only the Compose offset; nothing in it re-drives the native scroller. Whether the landed replay fix could apply at all depends on a pending programmatic offset existing at that moment, and that is not decidable by reading — the value is written on only two paths, each with an early return. Records the shrink direction, both offsets, content size, the pending programmatic offset and the dirty flag, plus what the Compose offset was corrected to and what the native offset was left at. Behind the same compile-time switch, so ordinary builds are unaffected. Signed-off-by: CC-希乐 <raft-mobile-cc-xile@mail.build>
Completes the shrink diagnostic per @mingqi. Recording only the shrink moment would leave a null pending offset ambiguous between never installed and already consumed by an earlier native echo — the same silence-versus-negative trap this investigation has hit repeatedly. Adds the install point in applyOffsetDelta (whose early return means an unchanged offset installs nothing), the consume point in consumeIgnoredScrollOffset (which clears whether or not it matched), and both outcomes of #117's replay guard, so a skipped replay is visible rather than silent. All behind the same compile-time switch. Signed-off-by: CC-希乐 <raft-mobile-cc-xile@mail.build>
bytemain
force-pushed
the
cc/lazy-layout-trace
branch
from
August 5, 2026 18:15
d9f0cfa to
7271a4d
Compare
This was referenced Aug 10, 2026
bytemain
pushed a commit
that referenced
this pull request
Aug 18, 2026
Task #141 follow-up to task #93. Adds the frozen 45-file closure for org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0-KBA-002 and its available-at kotlinx-coroutines-test-ohosarm64 module, plus a Hosted carrier that validates the contract and resolves the authority bytes from an empty cache. The file set is empirical: the authority publishes no directory listing, so it was derived from Gradle module metadata and then probed. Neither coordinate ships a .spdx.json, and only the OHOS module has a javadoc jar, so this closure is not shaped like task #121/#127. The resolver asserts module metadata only for the OHOS platform module and checks the root as artifact bytes. Resolving the multiplatform root on a JVM runner selects jvmApiElements-published, which redirects via available-at to kotlinx-coroutines-test-jvm, a platform this task does not mirror; that redirect is a property of the resolving host, not of the frozen bytes. The run prints that the mirrored root resolves for OHOS targets only. atomicfu 0.23.2-KBA-001 and kotlinx-coroutines-core 1.8.0-KBA-002 are already public. kotlin-stdlib 2.0.21-KBA-001 is absent from Raft but is a pre-existing condition of the already-published and already-consumed coroutines-core-ohosarm64, so it is recorded as a conditional closure member and is not published by this task. This commit publishes nothing. Plan is ALL_ABSENT 45/45, 0 conflict. Signed-off-by: BiSheng <raft-mobile-bisheng@mail.build>
bytemain
pushed a commit
that referenced
this pull request
Aug 18, 2026
Task #141 follow-up to task #93. Adds the frozen 45-file closure for org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0-KBA-002 and its available-at kotlinx-coroutines-test-ohosarm64 module, plus a Hosted carrier that validates the contract and resolves the authority bytes from an empty cache. The file set is empirical: the authority publishes no directory listing, so it was derived from Gradle module metadata and then probed. Neither coordinate ships a .spdx.json, and only the OHOS module has a javadoc jar, so this closure is not shaped like task #121/#127. The authority actually supplies 50 files for these two coordinates, not 45. The difference is the root -kotlin-tooling-metadata.json and its 4 checksum sidecars, which take no part in dependency resolution and are absent from Raft for the already-published sibling family kotlinx-coroutines-core 1.8.0-KBA-002 and atomicfu 0.23.2-KBA-001. This task keeps that family shape and mirrors 45, but the gap is now named in the manifest and bound by contract as authority_files=50, because a create-only Maven write can never be yanked. The 45-vs-50 split was measured by a 228-probe suffix sweep, and the probe grid is recorded in the manifest so the next coordinate does not depend on recall. The resolver asserts module metadata only for the OHOS platform module and checks the root as artifact bytes. Resolving the multiplatform root on a JVM runner selects jvmApiElements-published, which redirects via available-at to kotlinx-coroutines-test-jvm, a platform this task does not mirror; that redirect is a property of the resolving host, not of the frozen bytes. The run prints that the mirrored root resolves for OHOS targets only. atomicfu 0.23.2-KBA-001 and kotlinx-coroutines-core 1.8.0-KBA-002 are already public. kotlin-stdlib 2.0.21-KBA-001 is absent from Raft but is a pre-existing condition of the already-published and already-consumed coroutines-core-ohosarm64, so it is recorded as a conditional closure member and is not published by this task. This commit publishes nothing. Plan is ALL_ABSENT 45/45, 0 conflict. Signed-off-by: BiSheng <raft-mobile-bisheng@mail.build>
bytemain
pushed a commit
that referenced
this pull request
Aug 19, 2026
artin asked that the publication process land in the repo so the next one is easier. Tasks #120/#121/#127/#141 each grew a near-duplicate workflow and the knowledge stayed in threads. Captures the traps that actually cost rounds, rather than restating the happy path: the authority serves no directory listing and answers a directory URL with 200 plus a not-browseable notice rather than 404; Raft answers some absent paths with 401 so 404 is not the only absent signal; Raft serves no maven-metadata.xml so only exact versions resolve; carriers must be exactly one commit or identity-diff fails; and expected_sha must be the landed staging3 tip, because squash-merging means the reviewed head is not on staging3. Adds stage_manifest.py, which materialises a frozen manifest into a local Maven layout with per-byte verification so a consumer can be exercised before an unyankable write. It deliberately ships no consumer-side version check. Such a check reads a build file this repository does not own, so it could not run here; shipping it would imply coverage that does not exist. The consumer contract belongs to the consuming repository, and the runbook says so. The 1.7.3 trap is documented with measurements rather than speculation: kotlinx-coroutines-test:1.7.3 is absent from Tencent and Raft but present on Maven Central, while kotlinx-coroutines-test-ohosarm64:1.7.3 exists nowhere at all, because upstream never published an OHOS target. Copying that version therefore fails loudly at variant resolution instead of silently falling back. Suite is 22/22. Signed-off-by: BiSheng <raft-mobile-bisheng@mail.build>
bytemain
pushed a commit
that referenced
this pull request
Aug 19, 2026
artin asked that the publication process land in the repo so the next one is easier. Tasks #120/#121/#127/#141 each grew a near-duplicate workflow and the knowledge stayed in threads. Captures the traps that actually cost rounds, rather than restating the happy path: the authority serves no directory listing and answers a directory URL with 200 plus a not-browseable notice rather than 404; Raft answers some absent paths with 401 so 404 is not the only absent signal; Raft serves no maven-metadata.xml so only exact versions resolve; carriers must be exactly one commit or identity-diff fails; and expected_sha must be the landed staging3 tip, because squash-merging means the reviewed head is not on staging3. Adds stage_manifest.py, which materialises a frozen manifest into a local Maven layout with per-byte verification so a consumer can be exercised before an unyankable write. It deliberately ships no consumer-side version check. Such a check reads a build file this repository does not own, so it could not run here; shipping it would imply coverage that does not exist. The consumer contract belongs to the consuming repository, and the runbook says so. The 1.7.3 trap is documented with measurements rather than speculation: kotlinx-coroutines-test:1.7.3 is absent from Tencent and Raft but present on Maven Central, while kotlinx-coroutines-test-ohosarm64:1.7.3 exists nowhere at all, because upstream never published an OHOS target. Copying that version therefore fails loudly at variant resolution instead of silently falling back. Suite is 22/22. Signed-off-by: BiSheng <raft-mobile-bisheng@mail.build>
bytemain
pushed a commit
that referenced
this pull request
Aug 19, 2026
artin asked that the publication process land in the repo so the next one is easier. Tasks #120/#121/#127/#141 each grew a near-duplicate workflow and the knowledge stayed in threads. Captures the traps that actually cost rounds, rather than restating the happy path: the authority serves no directory listing and answers a directory URL with 200 plus a not-browseable notice rather than 404; Raft answers some absent paths with 401 so 404 is not the only absent signal; Raft serves no maven-metadata.xml so only exact versions resolve; carriers must be exactly one commit or identity-diff fails; and expected_sha must be the landed staging3 tip, because squash-merging means the reviewed head is not on staging3. Two enumeration habits are recorded as actions rather than maxims: keep the probe grid in the manifest header so the next coordinate runs a known grid, and pair every zero-hit claim with a known-present positive control, since a bare zero cannot distinguish absence from a filter aimed at the wrong thing. Adds stage_manifest.py, which materialises a frozen manifest into a local Maven layout with per-byte verification so a consumer can be exercised before an unyankable write. It deliberately ships no consumer-side version check. Such a check reads a build file this repository does not own, so it could not run here; shipping it would imply coverage that does not exist. The consumer contract belongs to the consuming repository, and the runbook says so. The 1.7.3 trap is documented with measurements rather than speculation: kotlinx-coroutines-test:1.7.3 is absent from Tencent and Raft but present on Maven Central, while kotlinx-coroutines-test-ohosarm64:1.7.3 exists nowhere at all, because upstream never published an OHOS target. Copying that version therefore fails loudly at variant resolution instead of silently falling back. Every stage_manifest failure path is covered by tests that go red when the exception type is removed. Without them the first draft shipped five raise sites referencing an undefined name, so the tool would have reported itself as broken at the exact moment it caught a real digest mismatch. Suite is 24/24. Signed-off-by: BiSheng <raft-mobile-bisheng@mail.build>
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.
Diagnostics seam inside the lazy list measure pass, for task Tencent-TDS#990 (Thread IME shrink white screen).
Why in the library
A host reading
LazyListState.layoutInfosees only a snapshot after the pass. In the incident under investigation every consumer-visible counter reads identically between a fully white frame and a healthy one — same descendant counts, same draw count, same visible range. Which measure or placement first diverged is only answerable from inside the measure pass.Generic by construction
No product vocabulary enters the library. The host supplies opaque
traceSession/cycle/layoutGeneration/targetToken; they are compared for equality so records can be joined, and never parsed, persisted, or interpreted. The sink is injected by the caller and released with its composition, so this never becomes a second owner of any state.Off means off
ENABLEDis a compile-time constant.measure { }is inline with the check ahead of the lambda, so a disabled build gathers no geometry, traverses no list, and formats no string.This branch sets it
truebecause it is the forensics carrier;mainkeeps itfalse, and that constant is the one line a build flips. An earlier revision of this branch left itfalseand would have installed, run, and recorded nothing — a package that compiles green and traces nothing is the failure this work exists to expose, so it is called out rather than buried.Fails closed rather than looking like data
With tracing enabled, a missing frame identity raises
LazyTraceWiringErrorinstead of substituting(-1, -1). A placeholder frame yields records that cannot be joined but still resemble data, which is worse than an absent record.Coverage is unioned, not summed
lazyTraceViewportCoverageunions clipped spans. Summing would double-count sticky or overlapping items and report a viewport as fuller than it is — concealing the empty band the measurement exists to find.Not included
LazyTraceStagedeclaresPlacementandNativeCommit; onlyMeasureResultis emitted. There are no library tests: this module has no test source set (compose/srchas nocommonTest, andbuild.gradle.ktsdeclares no test dependencies). Adding that infrastructure changes the fork's build and affects upstream absorption audits, so it is left to the repo owner. Carrier-side behaviour tests exercising this seam are being added on the mobile side.Merge status
Consumed by a forensics gitlink only — the mobile carrier pins this branch commit directly so a diagnostic package can be built without waiting on staging2. Per the repo rule, a publishable build must point at a staging2 commit; this SHA must not enter a release artifact.