Skip to content

InteractionDialog: host on the right form + don't lose queued sibling dialogs (#5193)#5214

Open
shai-almog wants to merge 5 commits into
masterfrom
fix-5193-interactiondialog-host-form
Open

InteractionDialog: host on the right form + don't lose queued sibling dialogs (#5193)#5214
shai-almog wants to merge 5 commits into
masterfrom
fix-5193-interactiondialog-host-form

Conversation

@shai-almog

@shai-almog shai-almog commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #5195 for #5193. The remaining symptoms ThomasH99 reported — the first dialog never appearing, dialogs popping up later on a different screen, and background/foreground making them appear — are not the layer-sharing collision; they come from two other mechanisms:

  1. Wrong-form attach. show(int,int,int,int) and showPopupDialogImpl anchored the dialog to Display.getCurrent(), which still returns the outgoing form while a form transition is queued or in flight (Form.show() is asynchronous). The dialog attached to the form leaving the screen, stayed invisible, and only materialized when that form's animation queue was flushed — i.e. when navigating back to it or on minimize/restore (Form.deinitializeImpl()AnimationManager.flush()). That is exactly the "shows up at another screen, like if the adding to the layer was delayed" report.

  2. Stackable cleanup could eat a pending sibling. In stackable mode cleanupLayer() used getComponentCount() == 0 to decide to detach the shared layer. That count doesn't see inserts deferred by Container.insertComponentAt() while an animation is in progress (e.g. dialog B shown during dialog A's blocking dispose animation, since invokeAndBlock keeps serving EDT callbacks). The layer was detached with B's insert still queued, B then flushed into the detached container and was lost permanently — which is why setStackable(true) didn't help the reporter.

Changes

No new public API.

  • show(...) and showPopupDialog(Rectangle) gate on the existing Display.isInTransition() and defer themselves with callSerially when a form transition is in flight. The EDT doesn't process serial calls while a transition is animating (edtLoopImpl returns right after paintTransitionAnimation), so the deferred show runs exactly when the destination form has become the current form and Display.getCurrent() is correct again. A pendingShow flag keeps isShowing()/showDialog() truthful through the deferral window and lets dispose() abandon a deferred show.
  • showPopupDialog(Component) hosts the popup on the target component's own form (it already computed it for the formMode check) and pins it so the show() call at the end of the positioning flow targets the same form used for the positioning math — no deferral needed on this path.
  • Stackable cleanupLayer() uses getChildrenAsList(true).isEmpty() so a layer with a queued insert is not detached (same trap Form.getLayeredPane already guards against).
  • resize() uses the form the dialog is actually on (getComponentForm()).

Test coverage

Five new regression tests in InteractionDialogTest (core-unittests), all verified to fail against the pre-fix InteractionDialog and pass with the fix:

  • showDuringFormTransitionAttachesToDestinationForm — show() mid-transition defers and attaches to the destination form, not the outgoing one.
  • showPopupDialogPinsTargetComponentFormDuringTransition — component popups attach immediately to the target component's form.
  • showPopupDialogRectDefersDuringTransition — rect popups defer like show().
  • disposeDuringTransitionAbandonsDeferredShow — a dialog disposed during the deferral window never materializes.
  • stackableDisposeKeepsSharedLayerWhenSiblingInsertQueued — deterministic reproduction of the queued-sibling-insert state; dispose must not detach the shared layer.

Mac-native suite tracing (separate commit)

The mac-native screenshot job intermittently stops mid-suite in the dual-appearance theme phase (observed on master runs 27287581172 / 27208374291 / 27183747605 before this PR, and on this PR's first attempt) with nothing in the CI log between the last capture and the 1500s timeout. The harness now:

  • streams the per-test CN1SS:INFO/ERR:suite markers (the Screenshot suite: retry once + stage logging for silent capture timeouts #5213 stage logging) into the CI console live, so a wedged run shows exactly which test it died on;
  • on STAGE:TIMEOUT, dumps the last suite markers from both log channels and captures a 5s sample of the stuck process into the uploaded artifacts (ParparVM emits readable com_codename1_* symbols, so the sample shows where the EDT is parked).

Fixes #5193

🤖 Generated with Claude Code

… dialogs

Fixes the "dialog never appears / appears later on another screen"
symptoms of #5193, which the stackable layer fix (#5195) didn't cover:

- show() and showPopupDialog(Rectangle) anchored the dialog to
  Display.getCurrent(), which still returns the outgoing form while a
  form transition is queued or in flight (Form.show() is asynchronous).
  The dialog would attach to the form leaving the screen, stay
  invisible, and only materialize when that form's animation queue was
  flushed on re-show or minimize/restore. They now resolve the host via
  the newly public Display.getCurrentUpcoming(), which returns the
  transition's destination form.

- showPopupDialog(Component) now hosts the popup on the target
  component's own form instead of Display.getCurrent(), and pins it so
  the show() call at the end of the positioning flow targets the same
  form used for the positioning math.

- In stackable mode, cleanupLayer() decided to detach the shared layer
  using getComponentCount() == 0, which doesn't see inserts that
  Container.insertComponentAt() deferred to the animation queue (e.g. a
  sibling dialog shown while this dialog's dispose animation ran). The
  pending insert would then flush into the detached container and the
  sibling dialog was lost forever. Use getChildrenAsList(true), which
  reflects queued changes.

- resize() now uses the form the dialog is actually on rather than
  Display.getCurrent().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 128 screenshots: 128 matched.

Native Android coverage

  • 📊 Line coverage: 14.25% (8644/60658 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 11.53% (42605/369524), branch 5.07% (1760/34737), complexity 6.05% (2015/33305), method 10.48% (1633/15575), class 17.19% (377/2193)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 14.25% (8644/60658 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 11.53% (42605/369524), branch 5.07% (1760/34737), complexity 6.05% (2015/33305), method 10.48% (1633/15575), class 17.19% (377/2193)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 194ms / native 95ms = 2.0x speedup
SIMD float-mul (64K x300) java 181ms / native 89ms = 2.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 553.000 ms
Base64 CN1 decode 254.000 ms
Base64 native encode 1012.000 ms
Base64 encode ratio (CN1/native) 0.546x (45.4% faster)
Base64 native decode 1122.000 ms
Base64 decode ratio (CN1/native) 0.226x (77.4% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 128 screenshots: 128 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 167 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 74ms / native 2ms = 37.0x speedup
SIMD float-mul (64K x300) java 58ms / native 3ms = 19.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 358.000 ms
Base64 CN1 decode 206.000 ms
Base64 native encode 758.000 ms
Base64 encode ratio (CN1/native) 0.472x (52.8% faster)
Base64 native decode 356.000 ms
Base64 decode ratio (CN1/native) 0.579x (42.1% faster)
Base64 SIMD encode 59.000 ms
Base64 encode ratio (SIMD/CN1) 0.165x (83.5% faster)
Base64 SIMD decode 47.000 ms
Base64 decode ratio (SIMD/CN1) 0.228x (77.2% faster)
Base64 encode ratio (SIMD/native) 0.078x (92.2% faster)
Base64 decode ratio (SIMD/native) 0.132x (86.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 17.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.235x (76.5% faster)
Image applyMask (SIMD off) 121.000 ms
Image applyMask (SIMD on) 85.000 ms
Image applyMask ratio (SIMD on/off) 0.702x (29.8% faster)
Image modifyAlpha (SIMD off) 143.000 ms
Image modifyAlpha (SIMD on) 83.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.580x (42.0% faster)
Image modifyAlpha removeColor (SIMD off) 120.000 ms
Image modifyAlpha removeColor (SIMD on) 78.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.650x (35.0% faster)

@shai-almog

shai-almog commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 128 screenshots: 128 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 280 seconds

Build and Run Timing

Metric Duration
Simulator Boot 92000 ms
Simulator Boot (Run) 1000 ms
App Install 14000 ms
App Launch 4000 ms
Test Execution 276000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1201.000 ms
Base64 CN1 encode 1889.000 ms
Base64 encode ratio (CN1/native) 1.573x (57.3% slower)
Base64 native decode 528.000 ms
Base64 CN1 decode 1403.000 ms
Base64 decode ratio (CN1/native) 2.657x (165.7% slower)
Base64 SIMD encode 614.000 ms
Base64 encode ratio (SIMD/native) 0.511x (48.9% faster)
Base64 encode ratio (SIMD/CN1) 0.325x (67.5% faster)
Base64 SIMD decode 676.000 ms
Base64 decode ratio (SIMD/native) 1.280x (28.0% slower)
Base64 decode ratio (SIMD/CN1) 0.482x (51.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 85.000 ms
Image createMask (SIMD on) 44.000 ms
Image createMask ratio (SIMD on/off) 0.518x (48.2% faster)
Image applyMask (SIMD off) 224.000 ms
Image applyMask (SIMD on) 111.000 ms
Image applyMask ratio (SIMD on/off) 0.496x (50.4% faster)
Image modifyAlpha (SIMD off) 192.000 ms
Image modifyAlpha (SIMD on) 98.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.510x (49.0% faster)
Image modifyAlpha removeColor (SIMD off) 219.000 ms
Image modifyAlpha removeColor (SIMD on) 126.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.575x (42.5% faster)
Image PNG encode (SIMD off) 1774.000 ms
Image PNG encode (SIMD on) 1354.000 ms
Image PNG encode ratio (SIMD on/off) 0.763x (23.7% faster)
Image JPEG encode 817.000 ms

@shai-almog

shai-almog commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 52 screenshots: 52 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 235 seconds

Build and Run Timing

Metric Duration
Simulator Boot 67000 ms
Simulator Boot (Run) 2000 ms
App Install 17000 ms
App Launch 6000 ms
Test Execution 1502000 ms

shai-almog and others added 4 commits June 11, 2026 03:46
…ansitions

Review feedback: getCurrentUpcoming is too niche/confusing to expose.
Display.java is reverted to master. Instead, show(int,int,int,int) and
showPopupDialogImpl gate on the existing public Display.isInTransition()
and defer themselves with callSerially: the EDT doesn't process serial
calls while a form transition is animating (edtLoopImpl returns right
after paintTransitionAnimation), so the deferred show runs once the
destination form has become the current form and Display.getCurrent()
is correct.

A pendingShow flag keeps isShowing()/showDialog() truthful through the
deferral window and lets dispose() abandon a deferred show. The
component-form pinning in showPopupDialog(Component), the stackable
cleanupLayer fix and the resize() fix are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five new tests in InteractionDialogTest, all verified to fail against
the pre-fix InteractionDialog and pass with the fix:

- showDuringFormTransitionAttachesToDestinationForm: show() during an
  in-flight form transition defers (isShowing() true, no form attach)
  and lands on the transition's destination form, not the outgoing one.
- showPopupDialogPinsTargetComponentFormDuringTransition: the component
  popup attaches immediately to the target component's own form even
  mid-transition.
- showPopupDialogRectDefersDuringTransition: the rect popup defers like
  show() and ends up on the destination form.
- disposeDuringTransitionAbandonsDeferredShow: a dialog disposed during
  the deferral window never materializes.
- stackableDisposeKeepsSharedLayerWhenSiblingInsertQueued: reproduces
  the queued-sibling-insert state deterministically (manual gate
  animation + single updateAnimations pop) and asserts dispose keeps
  the shared layer so the sibling materializes when the queue drains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mac-native screenshot job intermittently stops mid-suite (observed
on master runs 27287581172/27208374291/27183747605 and PR #5214's first
attempt, always inside the dual-appearance theme phase at a varying
test) and the CI log shows nothing between the last capture and the
1500s DeviceRunner timeout, because the app's CN1SS suite markers only
ever landed in artifact files.

- Stream the per-test CN1SS:INFO/ERR:suite markers (the #5213 stage
  logging) into the CI console live from whichever log channel
  (unified-log stream or `open` stdout capture) produces them first,
  so a wedged run shows exactly which test it died on.
- On STAGE:TIMEOUT, dump the last 25 suite markers from both channels
  and capture a 5s `sample` of the stuck process into the uploaded
  artifacts (ParparVM emits readable com_codename1_* symbols, so the
  sample shows where the EDT is parked), echoing the CN1 frames to the
  console.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same tracing as the mac-native harness: build-ios wedged twice on PR
#5214 (33/124 then 52/124 covered, both times stopping silently inside
the graphics test block) and the console showed nothing between launch
and the 1500s DeviceRunner timeout because the per-test CN1SS suite
markers only existed in the device-log artifact.

Stream the markers live into the CI console, and on STAGE:TIMEOUT dump
the trailing markers plus a 5s `sample` of the simulator app process
(it runs as a host macOS process; ParparVM's com_codename1_* symbols
make the EDT stack readable) into the uploaded artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Problems with InteractionDialog not being shown

1 participant