5.1 KiB
Context
JavaFxRuntime.runLater(...) currently combines lazy JavaFX toolkit startup
with task submission. Mission Control and Process Manager use it in the active
Hub graph; Composer, Test Tool, Soda Task Manager, and Suwimo Client use it in
dormant modern paths. Consequently, any consumer can create global runtime
state without a registered component or Hub lifecycle milestone.
The Registry already indexes one registered object through every implemented
component interface, and the Hub already starts selected NenjimProcess
objects in a hardcoded dependency-safe order and records IDs only after
successful return. The new runtime uses those existing mechanisms.
Goals / Non-Goals
Goals:
- Represent the one JVM-wide JavaFX toolkit as an explicit service object.
- Keep lifecycle control out of the public service API.
- Permit exactly one startup attempt and publish availability only after the startup callback configures JavaFX successfully.
- Reject every submission until successful startup and submit accepted work
only through
Platform.runLater(...). - Inject one shared service into all six modern JavaFX consumers.
- Start and record the runtime before both enabled JavaFX applications.
Non-Goals:
- Stop, restart, recovery, supervision, health, status, configuration, events, window discovery/management, automatic dependency startup, Contexts, classloading, headless JavaFX, or replacement of the JavaFX UI APIs.
- Refactoring unrelated legacy prototype code or adding a general test suite.
Decisions
One object has separate service and process views
JavaFxRuntimeService extends only NenjimComponent and exposes only
runLater(Runnable). The public JavaFxRuntimeServiceImpl implements that
service plus NenjimProcess. A single Registry registration therefore indexes
the identical object through both interfaces without exposing lifecycle on the
service contract or requiring a manager/admin API.
Startup is strict, synchronous at the milestone, and non-recovering
An atomic guard is consumed before any JavaFX call, so a second start()
fails even after an interrupted or failed first attempt. start() calls
Platform.startup(...) exactly once. Its callback sets implicit exit to false,
captures callback failure, and releases a latch. The caller waits on that latch
before opening a volatile availability gate or returning.
Synchronous toolkit failures, including an externally initialized toolkit, are wrapped with a service-specific startup diagnostic. Callback failure and interruption likewise leave availability closed. No path accepts an existing external toolkit, retries initialization, or rolls JavaFX back.
Submission is gated and remains asynchronous
runLater(...) validates the task before checking availability, so null is
always reported as a clear caller error. An unavailable service rejects work
without invoking any JavaFX API. After startup it delegates directly to
Platform.runLater(...); the service never invokes application work itself,
and failures inside queued UI work remain application failures.
Hub ordering expresses the dependency
During application composition the Hub creates one runtime implementation and
registers it as nenjim.javafx-runtime.service. The same service reference is
injected into Mission Control and Process Manager. In the fixed startup list,
the runtime follows the non-JavaFX business processes and precedes Mission
Control and Process Manager. The common startProcess helper records its ID
only after successful return. A runtime failure therefore propagates before
either dependent JavaFX process starts.
Dormant consumers have no compatibility path
Composer, Test Tool, Soda Task Manager, and Suwimo Client receive the service through their only constructors even though the active Hub does not currently construct them. All six consumers retain that dependency and replace static submission with the injected service. Removing the old utility then makes an implicit modern initialization path impossible at compile time.
Risks / Trade-offs
- [JavaFX is JVM-global and cannot be restarted] → The service deliberately accepts one attempt and treats prior external initialization as failure.
- [Startup callback execution timing is platform-controlled] → A latch makes
the successful service milestone explicit before
start()returns. - [Interrupting startup can leave a JVM toolkit that later finishes] → The service remains unavailable and non-retryable; no unsupported rollback or recovery semantics are claimed.
- [Queued UI work can fail later] → Task execution remains application-owned and is not confused with the already completed runtime startup milestone.
Migration Plan
- Introduce the service/process implementation and migrate all six consumers.
- Register, inject, start, and record the shared object in Hub order.
- Delete the static helper and audit modern sources for initialization paths.
- Update documentation and this focused active OpenSpec change.
- Strictly validate OpenSpec, build the repository, and use disposable probes
for lifecycle guards without starting production
main().