Files
NeitzelFXDesigner/docs/ide-host-reliability.md

5.8 KiB

IntelliJ save completion and canvas startup

This follow-up addresses the save and initial-render failures recorded during generated ViewModel qualification. The unchanged initial module-path baseline passed once; that did not close the intermittent gate. Controlled scheduling regressions then reproduced both failures without manual input.

Causes and fixes

Save completion: IntelliJ's FileDocumentManager is an asynchronous VFS write requestor. It can mark a Document clean before its bytes reach the filesystem. Our adapter completed its save future and read the old bytes through NIO. That also left a stale external-change baseline after a native autosave. The adapter now flushes pending updates for the affected file when establishing the initial disk baseline, before clean-disk comparisons and before completing an explicit save. Native save notifications record the accepted Document version. Actual external edits still fail with the draft retained; no competing file writer or undo store was introduced.

Canvas startup: the document-change render debounce and the host revision poll are independent. Validation can start against the old host revision, followed by loading a newer revision with identical text (edit/revert or native reload). Its reply is correctly rejected by revision, but the old in-flight marker previously remained. Later rendering joined that dead validation request indefinitely. Loading a new revision now invalidates obsolete validation state, and loading a usable projection schedules rendering again after synchronization. Stale replies remain rejected, including after a replacement validation starts.

Broader document-event filtering was considered but was not needed to reproduce or correct these failures. The fixes preserve the existing captured-draft and preview invalidation policy.

Deterministic regressions

IntellijDocumentsTest.testSaveWaitsForQueuedVfsWrites runs against the real IntelliJ Document and asynchronous local VFS. Its test-only scheduling control holds the VFS executor queue; the real per-file flush must execute queued writes. Before the fix, the completed save returned clean while NIO read before instead of after. It additionally checks a queued native save followed by another edit/save, with no false external conflict. Opening a second designer adapter while the native save was queued reproduced External change; draft retained before the initial-baseline flush was added. The existing real external-conflict and draft-preservation tests remain required. Reflection into the executor is limited to this test and pinned to the qualified SDK; no host implementation depends on that internal field.

JAVA_HOME="$JAVA_HOME" designer-intellij/gradlew -p designer-intellij --no-daemon \
  -PideaHome="$IDEA_HOME" test --tests '*IntellijDocumentsTest'

ProjectionHostChecks.renderRevisionRace pauses only the test's document-poll and render timers, performs an edit/revert through the real host document service, starts target validation and delivers the real poll callback before its reply. It requires fresh target pixels on the newer revision without another user action and verifies unchanged source. Before the fix it failed with Host revision stranded target validation/rendering. This scenario runs in both standalone and registered IntelliJ workflows. A narrow native standalone entry point runs the same scenario against a real module-path application:

python3 scripts/check-render-race.py --java-home "$JAVA_HOME" \
  --target-java-home "$TARGET_JAVA_HOME"

Build/install the modules and run the bootstrap once to prepare its test classpath. The narrow script creates a disposable fixture and closes the host and workers. It does not replace whole-host qualification.

Whole-host qualification

The real IDE runner now accepts --mode classpath|modulepath|all and --repeat N. Every repetition starts a fresh isolated IDE/project; it retains per-run evidence under target/ide-workflow/runs-*/<mode>-run-<N>. Existing exact-source save, history, diagnostics, generated discovery, exclusive JavaFX 26 natives and worker-shutdown assertions and timeouts remain intact.

python3 scripts/check-ide-host.py --java-home "$JAVA_HOME" \
  --target-java-home "$TARGET_JAVA_HOME" --idea-home "$IDEA_HOME" \
  --mode modulepath --repeat 3

Qualification on 2026-11-13: the full bootstrap passed 141 Maven tests, target linkage/bytecode/packaging checks, 38 target processes with 548 checks, both standalone native workflows, 48 lifecycle cycles and abrupt owner death. After extending the initial-baseline regression, all 11 IntelliJ unit/adapter tests and the plugin rebuild passed again (152 unit/adapter tests overall, zero failures/errors/skips). Three consecutive fresh registered IntelliJ module-path sessions and a final fresh classpath session passed with the final plugin, without retrying a failed run. Each includes the forced revision race, source-preserving edits and exact save assertions, Undo/Redo, native diagnostics/fixes, generated registry discovery, exclusive JavaFX 26 natives and worker shutdown.

The previous failures remain historical evidence. Red/green logs: /tmp/ide-save-red.log, /tmp/ide-save-late-open.log, /tmp/ide-save-final.log, /tmp/render-race-red.log and /tmp/render-race-green.log. Whole-suite evidence: /tmp/ide-reliability-bootstrap.log, /tmp/ide-reliability-modulepath.log /tmp/ide-reliability-classpath.log, target/ide-workflow/runs-ip2002jp (module-path) and target/ide-workflow/runs-4_sojrex (classpath). These are disposable local evidence, not published artifacts. This closes the bounded IntelliJ module-path save/startup gate on the pinned macOS/IDE/JavaFX setup; other platforms and IDE SDKs remain outside that qualification. No manual testing is part of this follow-up; previously skipped human tests remain skipped.