Skip to content

Experimental ComposableElementView - #548

Draft
cedrickcooke wants to merge 6 commits into
mainfrom
cedrickc/compose-native-element-exploration
Draft

Experimental ComposableElementView#548
cedrickcooke wants to merge 6 commits into
mainfrom
cedrickc/compose-native-element-exploration

Conversation

@cedrickcooke

@cedrickcooke cedrickcooke commented Jan 9, 2026

Copy link
Copy Markdown
Collaborator

Right now it's difficult to test that Krayon charts are working at the level of a integration test. This exploration provides a way to emit elements into the actual UI tree instead of just drawing to a canvas, which should allow for querying them via things like Espresso or Appium.

At time of writing, this is a very incomplete implementation. It falls back to a canvas for everything except touch targets (which have no way to actually select them programmatically; they have been tested on desktop only) and text. Text in particular should be able to provide the bare minimum amount of testability.


Note

Medium Risk
Large experimental surface area across element, compose, and interaction paths; behavior may diverge from canvas ElementView and APIs are unstable under ExperimentalKrayonApi.

Overview
Adds an experimental Compose path that renders Krayon element trees as real UI nodes (not only a single canvas), aimed at integration testing and tooling such as Espresso/Appium.

ComposableElementView walks the same RootElement / UpdateElement flow as canvas ElementView, but maps groups, clips, transforms, TextElement (BasicText), and custom ComposableElement into composables; everything else still draws via nested Kanvas. Pointer hover/click goes through existing RootElement hit-testing (including transforms and density), with JVM Compose UI tests covering clicks, hover, occlusion, translate/scale/rotate, and non-unit density.

Supporting pieces include ImmutableMatrix (Krayon Transform → Compose graphics), ExperimentalKrayonApi, global opt-in for experimental/internal APIs in kotlin-conventions, and Compose-stable element trees (mutableStateListOf / mutableStateMapOf, @Stable types, exposed mutableChildren). Transform.Rotate/Scale gain isPivoted for matrix splitting; Path uses InfiniteCache. The desktop sample switches from ElementView to ComposableElementView.

Reviewed by Cursor Bugbot for commit 367706d. Configure here.

@cedrickcooke cedrickcooke added the minor Changes that should bump the MINOR version number label Jan 9, 2026
@github-actions

github-actions Bot commented Jan 9, 2026

Copy link
Copy Markdown

Test Results

88 tests  +9   88 ✅ +9   6s ⏱️ +4s
16 suites +1    0 💤 ±0 
16 files   +1    0 ❌ ±0 

Results for commit 367706d. ± Comparison against base commit 1589cbc.

♻️ This comment has been updated with latest results.

cursoragent and others added 3 commits July 13, 2026 22:19
…3617c7-97d3-4d36-8529-8dca87615d75-577a

Co-authored-by: Cedrick Cooke <development@cedrickc.net>
Replace the per-element clip+clickable/hoverable interaction with a single
pointer-input overlay that reuses RootElement.onHover/onClick + isPointInPath
(as ElementView does). This fixes hover/click correctness under transforms and
at non-unit density, and restores onClickFallback support. Revert clickHandler/
hoverHandler and ComposeKanvas.scope to internal (no longer needed as public).

Add Compose UI interaction tests covering click/hover, occlusion, translate,
scale, pivoted rotate, and non-unit density.

Co-authored-by: Cedrick Cooke <development@cedrickc.net>
- core: ExperimentalKrayonApi annotation
- kanvas: Transform.Scale/Rotate.isPivoted
- element: @stable $stable fields, Element.mutableChildren
- compose: ComposableElement, ComposableElementView, ImmutableMatrix

Co-authored-by: Cedrick Cooke <development@cedrickc.net>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 367706d. Configure here.

if (width != newWidth || height != newHeight) {
width = newWidth
height = newHeight
update(dataState.value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State written during measure

Medium Severity

ComposableElementView assigns width and height and calls update inside the SubcomposeLayout measure lambda. That mutates Compose state and the element tree during measurement, which Compose can apply unpredictably (extra passes, skipped frames, or runtime errors) unlike ElementView, which applies size via SideEffect.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 367706d. Configure here.

fun update(data: T) {
if (width != 0f && height != 0f) {
updateElements.update(root, width, height, data)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative size not rejected

Low Severity

Chart updates run when width and height are merely non-zero. ElementView only updates when both dimensions are strictly positive, avoiding negative constraint sizes Compose can report; this guard uses != 0f instead.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 367706d. Configure here.

if (dataState.value != NotSet) {
@Suppress("UNCHECKED_CAST")
ComposableElementView(dataState as State<T>, updateElements, modifier)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flow overload renders nothing initially

Low Severity

The Flow overload withholds the entire ComposableElementView until the first emission because collectAsState starts at NotSet. ElementView always mounts its canvas immediately, so cold or slow flows show blank UI instead of an empty chart.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 367706d. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Changes that should bump the MINOR version number

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants