100 Commits
Author SHA1 Message Date
Elie Gambache 1f7422bf3d Merge pull request #701 from NucleusFramework/ci/ubuntu-22.04-everywhere-main
ci: run every Linux job on ubuntu-22.04
2026-09-23 08:21:37 +03:00
Elie GambacheandClaude Opus 5.5 7bc0402621 ci: run every Linux job on ubuntu-22.04
ubuntu-latest (24.04, glibc 2.39) is a moving target. Pin every workflow to
ubuntu-22.04 / ubuntu-22.04-arm, like the natives job (#699), so anything built
or packaged on Linux keeps loading on Debian 12, Devuan 5 and Ubuntu 22.04.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:18:27 +03:00
Elie Gambache 73230712ec Merge pull request #692 from takke/fix/graalvm-macos-developer-id-signing
fix(macos): make GraalVM native bundles signable and notarizable with Developer ID
2026-09-22 07:41:45 +03:00
Elie Gambache 8f9d9cc1dd Merge pull request #685 from wgtunnel/fix/pacman-upgrade-hooks
fix: pacman upgrade hooks
2026-09-18 06:42:28 +03:00
Elie Gambache 1a3589114a Merge pull request #664 from wgtunnel/feat/linux-install-scripts
feat(linux): after/before install/remove scripts
2026-09-17 21:49:34 +03:00
Elie Gambache e28fe04f85 docs: add ZonePane to Used by
Closed-source desktop client on Nucleus 2.5 with the Tao backend;
link the homepage rather than a GitHub repo, as requested in #596.
2026-09-17 16:27:08 +03:00
Elie Gambache 351bff8ce6 docs: add live GitHub star badges on the larger Used-by apps
Shields.io github/stars badges, so the counts stay current without
hardcoding them.
2026-09-17 13:11:53 +03:00
Elie Gambache e2f2ab7eb1 docs: list the exact Nucleus modules each Used-by app depends on
Call out the Tao backend when decorated-window-tao is on the classpath,
and the legacy JNI window backend for Flare.
2026-09-17 13:04:12 +03:00
Elie Gambache 67c78809c5 docs: expand Used by with more Nucleus consumers
Include apps that take a single module (notifications, tray, packaging)
alongside full-stack desktop apps, so the list shows the runtime is usable
piecewise.
2026-09-17 12:57:14 +03:00
Elie Gambache b60f23dbfc Merge pull request #665 from wgtunnel/feat/graalvm-headless-awt-skip
feat: graalvm headless optimization
2026-09-17 12:22:29 +03:00
Elie Gambache e3246b526d Merge pull request #672 from NucleusFramework/fix/670-egl-gl33-fallback
fix(tao): core-profile GL 3.3 then llvmpipe fallback on Linux EGL (#670)
2026-09-17 12:19:51 +03:00
Elie Gambache 0e49b42b3d fix(tao): fall back to a core-profile GL 3.3 context, then llvmpipe, on Linux EGL
Closes #670.

The X11 and Wayland attach paths asked for one thing only — a GL 3.3
compatibility-profile context — and returned 0 when the driver refused, which
aborts the window on the JVM side. Mesa r600 on an AMD RV610 (Debian 13) caps
the compat profile at 3.0, so the app crashed on launch.

- `create_gl_context()` (shared by both paths) asks for 3.3 compat, then 3.3
  core. Core is strictly more available (a 3.2+ driver must offer it, compat
  is optional), nothing here or in Skia needs the legacy entry points, and
  r600 reaches 3.3 in core — so this GPU stays hardware-accelerated.
- When neither profile is available, `fall_back_to_software()` sets
  `LIBGL_ALWAYS_SOFTWARE=1`, terminates the display and the attach re-enters
  itself once from `eglGetPlatformDisplay`: Mesa reads the variable in
  `eglInitialize` (driver load), not at context creation, so a bare retry of
  `eglCreateContext` would not pick llvmpipe up. Process-wide and one-shot;
  skipped when the user already set the variable.
- Wayland tears its subsurface chain down before retrying (flag consumed at
  `fail_after_subsurface`).

Both branches log one line on stderr so the chosen path is visible in a
bug report. Existing hardware that accepts the compat request is untouched.
2026-09-17 11:49:30 +03:00
Elie Gambache 6b7a1092e8 Merge pull request #656 from NucleusFramework/fix/652-654-macos-trackpad-scroll
fix(tao): match AWT trackpad scrolling on macOS and surface Pan events
2026-09-07 07:06:57 +03:00
Elie Gambache 412ab0d490 feat(demo): Trackpad Lab tab — manual rig for #652 / #653 / #654
One screen in nucleus-demo to test the macOS trackpad work by hand:
a root inspector (every Scroll / PanStart / PanMove / PanEnd with gaps,
counters and one summary per gesture incl. how long after the last move
the PanEnd came), vertical and horizontal strips for sign and magnitude,
a map canvas that pans on Pan and zooms on Scroll, a scrollable
DropdownMenu (inline, or NSPanel via "Open with native popup layers"),
and an embedded WKWebView whose page draws its own scrollY / wheel HUD.
The header shows density, px per wheel unit and the trackpadPanEvents
flag. NUCLEUS_DEMO_TAB=<tab> opens the demo straight on a tab.

Adds the composewebview dependency to nucleus-demo (in-tree Nucleus
modules excluded, as in tao-demo).
2026-09-06 15:02:44 +03:00
Elie Gambache 1ffdf8c7fd fix(tao): one timer per pan, each AppKit delta to a native view once (review 5)
- native_view.m: an AppKit event is marked spent only once its delta has
  actually travelled (replayed, or synthesised by the step that carries it),
  so an Ended-with-delta arriving with no pan open (PanStart + PanMove from
  one event) no longer loses the finger movement; per-child gesture state,
  keyed on the child handle rather than a raw NSView*, keeps a deferred
  PanEnd from sending a second terminal phase and resets the sub-point
  residue at gesture boundaries
- NativeView hands pan offsets over in scene px; the macOS host converts
  them back with its own scale, so an app-level LocalDensity override no
  longer skews the embedded view's distance. dispatchScrollToNative is back
  to its original signature (Windows / Linux untouched)
- TaoTrackpadPanRouter: the pan end is a deadline, one timer in flight;
  finger and momentum steps only move the deadline, the grace re-schedules
  once (earlier), the timer re-arms for the remainder — no coroutine, wake
  and cancel per 120 Hz step
- TaoSceneScrollRouter: a zero-delta orphaned tail end is not turned into a
  Scroll (AWT drops zero deltas); once-only announce on a plain volatile;
  the redundant sceneBundle guard in onPointerScroll is gone (the router's
  cancelled flag is the contract)
- TaoPopupSceneLayer documents why the pan uses the layer density while the
  surface uses host.scale; TaoWindow uses import aliases for the ABI alias
  constants; the injector checks the main thread before its static init
- headful: shared ScrollableColumn / ScrollableRow fixtures and a harness
  awaitUntilOrTimeout replace the copies; the wheel-notch baseline is taken
  right before the injection; the library drift test no longer reads the
  demo source
2026-09-06 14:13:48 +03:00
Elie Gambache 72ba91bca9 fix(tao): deliver each AppKit scroll event to a native view once, carry an orphaned tail (review 4)
- native_view.m remembers the AppKit event it last handed to a child
  (NSApp.currentEvent is not cleared while the app idles, and one event can
  yield two Compose steps), so a deferred PanEnd or a PanStart+PanMove pair
  never applies a delta twice; the synthesised fallback keeps a per-view
  sub-point residue instead of rounding slow drags to zero
- popup_panel.m and the vendored view.rs test NSEventPhase bits instead of
  switching on the NS_OPTIONS mask
- TaoSceneScrollRouter leaves the pan position alone for MayBegin (its
  PanEnd belongs to the previous gesture); an orphaned momentum tail is
  delivered as AWT-shaped Scroll instead of dropped; once-only CAS behind a
  plain read; scrollRouter declared with the rest of the host state
- TaoStandalonePopupHostMac.dispose() is idempotent on the invalid path too
- TaoWindow keeps AWT_PIXEL_TO_ROTATION / MACOS_AWT_SCROLL_AMOUNT as
  aliases of the shared constants (public statics in the validated ABI)
- demo ScrollTestScreen finalises a gesture inline on PanEnd / PanStart
- TaoScrollWireDriftTest also guards the kNv* native-view wire and the
  10-units-per-wheel factor in events.rs, native_view.m and the demo;
  TaoSceneScrollTest pins MacOSCocoaConfig's 10 dp per unit on macOS;
  harness routers get one timer slot each
2026-09-06 13:10:02 +03:00
Elie Gambache f93cc8cbdb fix(tao): give native views the whole pan, never stack a late momentum tail (review 3)
- NativeView forwards PanStart / PanMove / PanEnd with a phase; native_view.m
  replays the live AppKit event only when its phase class matches and
  otherwise synthesises a phased scroll, so an embedded scroll view sees the
  gesture begin and end (rubber-band, scroller fade) — Windows / Linux ignore
  the phase
- popups keep the gesture phase whatever hasPreciseScrollingDeltas says,
  like the Rust window path
- TaoTrackpadPanRouter: momentum steps only continue an open pan; a tail
  arriving after the grace already closed it is dropped instead of stacked on
  Compose's fling
- TaoSceneScrollRouter: lazily created timer scope with
  TaoNonFatalCoroutineExceptionHandler (a broken deferred PanEnd costs one
  gesture, not the app); TaoScrollGesturePhase.fromWire is a map lookup
- TaoStandalonePopupHostMac: finishPan() runs inside the frame pump; the
  invalid-host dispose path also shuts the render executor down
- nativeDiagInjectScrollWheel targets the NSView it is handed, is nil-safe on
  the primary screen, inlined; documented as whole-point only (CGEvent delta
  fields are integers — verified) and the headful momentum tail uses whole
  points
- demo ScrollTestScreen closes a gesture on PanEnd / next PanStart and names
  the 10 dp factor; harness closes the pan after the cursor-move guard like
  the host; TaoScrollWireDriftTest resolves its sources per test with a
  readable failure; stray @Suppress dropped
2026-09-06 12:45:36 +03:00
Elie Gambache 980eb8890d fix(tao): bound every trackpad pan, keep popups and native views in step (review 2)
- TaoTrackpadPanRouter: an open pan always has an end timer (150 ms grace
  after the finger Ended, 1 s stall watchdog otherwise); MayBegin during the
  momentum tail closes the pan at once; finishNow() for clicks / wheel
  notches; TaoScrollGesturePhase is now an enum (wire codes attached),
  distinct from the public TaoTrackpadPhase, unknown codes degrade to a
  plain precise scroll
- loop: the phase decides the route for the whole gesture — a step that
  arrives in lines is scaled to its point equivalent instead of being
  dropped as a stray wheel Scroll; dispatch_scroll_gesture loses its bogus
  dead_code allow; view.rs drops the dead ViewState binding
- TaoSceneScrollRouter: pan position / modifiers come from gesture steps
  only (PanEnd must hit the node that got the PanMoves), a click or a wheel
  notch closes the pan, cancel() makes late events no-ops, the timer scope
  carries TaoFatalCoroutineExceptionHandler; TaoPopupSceneLayer reads the
  live layer density; TaoStandalonePopupHostMac drops the native callback
  before cancelling the router; first Pan routing logs one CONFIG line
- NativeView consumes PanStart / PanEnd over an embedded view so the
  ancestor scrollable never opens a session of its own
- popup_panel.m: NucleusScrollGesture enum instead of literals;
  TaoScrollWireDriftTest compares Rust, ObjC and Kotlin wire codes plus the
  JNI descriptors
- nativeDiagInjectScrollWheel is inert unless NUCLEUS_TAO_INPUT_INJECTION=1
  (set by taoHeadfulTest) and main-thread only
- README / CLAUDE.md document the Pan behaviour and
  -Dnucleus.tao.trackpadPanEvents=false
2026-09-06 11:53:30 +03:00
Elie Gambache 79d91fe7d9 fix(tao): route macOS scroll input through one router, keep popups in step (review)
Review follow-ups on #652 / #653 / #654:

- TaoSceneScrollRouter: single front door for wheel / trackpad input shared
  by the window host and both NSPanel popup hosts; popup_panel.m now forwards
  the AppKit phase so a two-finger swipe over a popup list behaves like the
  window behind it. `-Dnucleus.tao.trackpadPanEvents=false` restores
  AWT-style Scroll events for handlers that only know PointerEventType.Scroll
- tao patch 0007: PixelDelta carries AppKit's logical points, so the loop no
  longer locks WINDOWS per event nor round-trips through two scale caches
- TaoTrackpadPanRouter: terminal steps with a delta still pan when no gesture
  is open; momentum grace 150 ms, tunable via
  -Dnucleus.tao.trackpadMomentumGraceMillis; isPanning removed
- NativeView forwards only non-zero PanMove (PanStart / PanEnd replayed
  NSApp.currentEvent, stale for the deferred end)
- host detach cancels the router's timer scope and ignores scrolls after
  the scene is gone
- TaoWindow: one preciseScrollEvent() shaping helper, shared AWT constants
  (apiDump: the two leaked private-companion fields are gone)
- headful cases: recorder reset per run, display-mode restore even when the
  window never reports the new scale; JNI descriptor drift guard for
  popup_panel.m; demo ScrollTestScreen logs Pan steps too
2026-09-06 10:45:13 +03:00
Elie Gambache 5b04066e7c fix(tao): match AWT trackpad scrolling on macOS and surface Pan events (#652, #653, #654)
Horizontal trackpad scrolling was reversed and precise deltas were scaled
by the display factor, both relative to the AWT backend; trackpad gestures
were indistinguishable from wheel notches.

- vendored tao (patch 0007): stop negating scrollingDeltaX in the macOS
  scroll_wheel (AppKit already follows the MouseScrollDelta convention, as
  winit does) and carry the full AppKit phase / momentumPhase on
  WindowEvent::MouseWheel as `scroll_phase: ScrollPhase`
- loop: hand SCROLL_PIXEL over in logical points (AWT never applies the
  display scale to preciseWheelRotation) and route phased steps to a new
  EventCallback.onScrollGesture
- host: TaoTrackpadPanRouter turns the gesture stream into Compose
  PanStart / PanMove / PanEnd with panOffset = AWT delta x 10 dp, keeping
  the pan open across AppKit's momentum tail (deferred PanEnd) so Compose
  does not stack its own fling; wheel notches and phase-less precise
  scrolls stay Scroll events; NativeView forwards Pan to the native view
- popups: appKitWheelToAwtScrollDelta flips both axes and drops the scale
- headful e2e: MacOsTrackpadScrollHeadfulCases inject real scrollWheel:
  NSEvents into the tao content view (nativeDiagInjectScrollWheel), the
  #653 case flipping the display to its HiDPI twin; plus router, scene and
  wire unit tests
2026-09-06 10:04:04 +03:00
Elie Gambache 9d9ce7fef5 Merge pull request #648 from NucleusFramework/fix/636-composable-target-window-scope
fix(application): keep window content on the UI applier (#636)
2026-09-04 17:57:00 +03:00
Elie G. 412666372e fix(application): keep window content on the UI applier (#636)
The window and dialog openers had their composition target inferred, so a
single non-UI-targeted composable called in the `nucleusApplication` scope
(the Compose compiler bakes `@ComposableTarget` onto any unmarked factory
forwarding a target-marked content lambda) reclassified the whole scope —
nested windows included — and every `@UiComposable` call in it warned, which
is fatal under `-Werror`.

Declare each opener `@ComposableOpenTarget(-1)` with a `@UiComposable`
content lambda, the way Compose Desktop's own `Window`/`Dialog` are open:
they are callable from any applier and always compose UI content in the new
window's own composition, so neither direction of the applier leak survives.
Covers `DecoratedWindow`/`DecoratedDialog`, the `NucleusWindowHost` /
`NucleusDialogHost` interfaces with their default implementations,
`HostedWindow`/`HostedDialog`, and Tao's `DecoratedWindow`,
`DecoratedDialog` and `TaoStandalonePopup`. The Material/Jewel wrappers
inherit the same scheme through inference (`[_[UiComposable]]`).

`COMPOSE_APPLIER_CALL_MISMATCH` is only a warning, so both test compilations
escalate it to an error and a fixture per module compiles the reported shape
(non-UI factory in the application scope, UI content in the windows).
2026-09-04 15:34:10 +03:00
Elie Gambache 15f0028219 Merge pull request #645 from NucleusFramework/fix/644-windows-ime-taskbar-reentrancy
fix(tao): release Windows input locks before pumping calls (#644)
2026-09-04 14:13:27 +03:00
Elie G. 3c105d2de7 fix(tao): release Windows input locks before pumping calls (#644)
Two more instances of the #640 bug class, both fixed upstream and ported by
hand — a non-reentrant lock held across a call that pumps or synchronously
sends messages, so a nested window-procedure dispatch re-locks it and the
event-loop thread parks in `WaitOnAddress` for good.

- IME: the window-state lock was held across `ImeHandler::process_message`,
  which peeks the queue on `WM_IME_ENDCOMPOSITION` (the Korean IME sends the
  committed string after the end message, so the queue decides whether to
  defer). The peek now happens in `ime::peek_commit_queued` before the lock is
  taken, and the result is passed in — mirroring upstream, where "IME
  character-pending detection happens before acquiring window state locks".
  Wider blast radius than #640: there only the keyboard arm took the lock,
  here nearly every arm does, so any nested message deadlocked.
  Upstream: tauri-apps/tao#1215.
- `TaskbarCreated`: the window-state lock was held across `set_skip_taskbar`,
  which goes through `CoCreateInstance` + `ITaskbarList`. An STA COM call pumps
  the message queue while it waits, so the window procedure is re-entered.
  The flag is now read out before the call. Upstream: tauri-apps/tao#1264.

`Imm32Source` carries the precomputed flag instead of peeking on demand;
`ImeSource` is unchanged, so the IME state-machine tests keep driving the same
sequences through their stub.
2026-09-04 13:58:26 +03:00
Elie Gambache 9fc67f0892 Merge pull request #642 from NucleusFramework/fix/640-windows-keyboard-layout-cache-reentrancy
fix(tao): never hold key-event locks across PeekMessageW on Windows (#640)
2026-09-04 13:48:59 +03:00
Elie G. c20018a511 fix(tao): never hold key-event locks across PeekMessageW on Windows (#640)
`PeekMessageW` delivers pending cross-thread *sent* messages inline: the
kernel re-enters the window procedure through `KiUserCallbackDispatcher`
before the peek returns. Two non-reentrant `parking_lot::Mutex`es were held
across such a peek, so the nested dispatch deadlocked the event-loop thread
against itself — it parked in `WaitOnAddress` and never pumped a message
again, leaving the window permanently "Not Responding".

- `event_loop.rs`: the keyboard callback held the global `KEY_EVENT_BUILDERS`
  map across `KeyEventBuilder::process_message`, which peeks. The builder is
  now taken out of the map for the duration and put back afterwards (only if
  the slot still exists, so a window dropped re-entrantly is not resurrected).
  Its map value became an `Option` slot to make that take/put-back possible.
- `keyboard.rs`: the key-press and key-release arms held `LAYOUT_CACHE` across
  their peek, while every other arm — and `update_modifiers`, reached from any
  mouse or focus message — locks it too. The guard is now scoped to end before
  the peek and re-acquired after it.

Reported as a freeze when moving a window between Windows 11 virtual
desktops: that path is keyboard-triggered and makes the shell send messages
to the window mid-peek. The reporter's stack shows the two nested window
procedure chains around `NtUserPeekMessage` and the park in `WaitOnAddress`.

Only injected or sent key messages can nest here — real keyboard input is
posted to the queue rather than sent, and the peeks use `PM_NOREMOVE` — so a
nested key message now finds an empty builder slot and is dropped instead of
hanging the process.
2026-09-04 12:57:15 +03:00
Elie Gambache c27224d138 Merge pull request #633 from NucleusFramework/fix/631-windows-always-on-top-sticky
fix(tao): keep alwaysOnTop sticky on Windows
2026-09-01 02:10:44 +03:00
Elie G. 6cf761128d fix(tao): keep alwaysOnTop sticky on Windows (#631)
Three layers were losing WS_EX_TOPMOST on Windows:

- DecoratedWindowComposable created every window with a hardcoded
  alwaysOnTop=false and only applied the real value from a later
  LaunchedEffect, which loses the race against creation-time style
  rewrites (acrylic, skip-taskbar). The real initial value is now passed
  at creation.
- tao only issues the z-order SetWindowPos when its WindowFlags cache
  diffs — and then with SWP_ASYNCWINDOWPOS — so once an external rewrite
  drops the topmost band while the cache still says ALWAYS_ON_TOP, no
  path ever repairs it. TaoWindow.setAlwaysOnTop now also applies the
  z-order directly and synchronously via the new deco-bridge
  nativeApplyTopmost (SetWindowPos without SWP_ASYNCWINDOWPOS), keeping
  tao's cache in sync for its own future rewrites.
- New TaoWindow.reassertAlwaysOnTop() repairs the band after the known
  clobber sites: every RESIZED event, and the WindowsBackdrop
  apply/teardown/survivor-restore paths.

Adds a nativeIsTopmost probe (real GWL_EXSTYLE bit, mirroring
nativeIsBackdropActive) and a headful regression case driving the
issue's scenario: enable topmost, apply acrylic, resize, toggle the
flag both ways, tear the backdrop down — asserting WS_EX_TOPMOST at
each step.
2026-09-01 02:10:23 +03:00
Elie Gambache 1e30103269 Merge pull request #632 from NucleusFramework/fix/tao-windows-clean-frame-present-skip
fix(tao): skip VSync presents of clean frames on Windows (multi-window lag)
2026-09-01 01:59:44 +03:00
Elie G. ca7bca59ce fix(tao): skip VSync presents of clean frames on Windows
Any snapshot write anywhere in the process wakes every scene's Recomposer
through the global apply observer, and the Windows host rendered and
presented those frames unconditionally. Each eglSwapBuffers (interval 1)
parks the shared event-loop thread until the next VBlank, so every
visible window re-presented at the animating window's rate — with two
windows open, VBlank waits serialize and input starves (dashboard lag
while a small always-on-top window is visible).

TaoSceneBundle now tracks a visualDirty flag fed by the scene's
invalidateLayout/invalidateDraw callbacks, and the Windows host skips
nativePresent when the frame carried no visual change. Presents that
must swap regardless of scene dirtiness are kept: a size/scale apply,
the OS modal resize/move loop, a resolved clear-colour change (read
outside the composition), and the first swap after show() — DWM does
not reliably retain a pre-show present, so TaoWindow.show() arms a
one-shot force through the new showHook.
2026-09-01 01:59:16 +03:00
Elie Gambache ad0a379079 Merge pull request #630 from takke/feat/nsis-menu-category
feat(nsis): add menuCategory and shortcutName to the NSIS targets
2026-08-31 19:31:48 +03:00
Elie Gambache 6f56231a9f Merge pull request #629 from takke/fix/msi-windows-installer-options
fix(msi): forward the Windows installer settings to electron-builder
2026-08-31 17:08:16 +03:00
Elie Gambache f6f4c39611 Merge pull request #627 from NucleusFramework/feat/fatal-dialog-scrollable-detail
feat(tao): fatal dialog with scrollable stack trace and copy button
2026-08-31 15:55:59 +03:00
Elie G. 487ee1359b feat(tao): Windows fatal dialog with scrollable trace and copy button
Replace the compact MessageBoxW with an in-memory DLGTEMPLATE dialog run
on a fresh thread: error icon + message, the stack trace in a read-only
multiline Consolas EDIT with both scrollbars, and Copy / Close buttons
(Copy = select-all + WM_COPY, keeps the dialog open like macOS/Linux).
Falls back to the compact MessageBoxW when the dialog cannot be created.

The trace goes through dwInitParam + SetWindowTextW instead of the
template: DialogBoxIndirectParamW returns -1 outright for large item
strings (a VerifyError toString() is a ~100 KB bytecode dump). The
compact-fallback summary is capped at 2000 chars for the same reason —
MessageBoxW silently never shows on a string that large; this also
protects the macOS CFUserNotification fallback.
2026-08-31 15:20:00 +03:00
Elie Gambache 175fae1ce3 feat(tao): macOS fatal dialog with scrollable trace and copy button
Parity with the Linux GTK dialog: the primary macOS path is now an
NSAlert with the stack trace in a scrollable monospace accessory view
and a Copy button, run by an osascript -l JavaScript child — a fresh
out-of-process NSApp, since ours cannot host a modal after tao latches
[NSApp stop:] on loop exit. The compact CFUserNotificationDisplayAlert
remains as fallback when the child cannot run (MDM-blocked osascript,
spawn failure, JXA error).
2026-08-31 14:33:46 +03:00
Elie Gambache 52c6316d61 feat(tao): compact fatal dialog with scrollable trace and copy button
The fatal-error dialog now receives the full stack trace as a separate
detail parameter. On Linux the title no longer doubles as the primary
line (it showed 'Fatal Error' twice on WMs that draw dialog titlebars)
and the trace renders in a bounded scrollable monospace view with a
Copy button instead of an unbounded secondary label that sized the
dialog to the whole screen. macOS and Windows keep the compact alert:
message plus the throwable toString extracted from the detail.
2026-08-31 14:11:50 +03:00
Elie Gambache c9d0b74022 Merge pull request #625 from takke/feat/621-window-exception-handler
feat(tao): add WindowExceptionHandlerFactory for Tao windows
2026-08-31 08:00:47 +03:00
Elie Gambache 7f396721b7 Merge pull request #623 from NucleusFramework/feat/622-tao-fatal-error-dialog
feat(tao): native error dialog for fatal exceptions (#622)
2026-08-30 20:02:21 +03:00
Elie Gambache 1c5d9d6553 test(plugin): expect AOT training to disable the Tao fatal dialog
buildAotJavaArgs now injects -Dnucleus.tao.fatalErrorDialog=false so a
training crash cannot hang in the #622 native dialog. Update the
argument-order assertions to match.
2026-08-30 19:49:36 +03:00
Elie Gambache 1712f2af25 feat(tao): Linux GTK fatal dialog and Compose crash routing (#622)
Show a modal GtkMessageDialog after the loop exits, install a shared
coroutine exception handler on every scene, and recheck pending fatals
before exitProcess(0) so a composition-side crash cannot quit with 0.
Unattended runs (headful tests, AOT training) disable the dialog via
-Dnucleus.tao.fatalErrorDialog=false.
2026-08-30 18:49:35 +03:00
Elie G. e39ae2dba7 feat(tao): Windows fatal-error dialog via MessageBoxW (#622)
Implements the Windows arm of the native fatal-error dialog: task-modal
MessageBoxW (MB_OK | MB_ICONERROR | MB_SETFOREGROUND), no owner HWND
since the Tao loop has exited and every window is destroyed.

The box must run on a dedicated thread: called directly on the ex-loop
thread it dismisses itself instantly (leftover quit/thread message in
that queue after tao's run_return) - the Windows analog of the stopped
NSApp problem that pushed macOS to CFUserNotification. A fresh thread
gets a fresh message queue; join() preserves the blocking contract.

Verified e2e on Windows: injected fatal in swing-tao-demo, dialog stays
open until dismissed, then rethrow and exit 1 from the caller.

Linux remains a silent no-op.
2026-08-30 16:38:57 +03:00
Elie Gambache 077d72e4a7 feat(tao): native fatal-error dialog and never-freeze fatal path (#622)
An unhandled exception on the Tao main thread used to be cleared at the
JNI boundary, leaving a frozen, silent, unclosable window. Default path
now: SEVERE log with the full stack trace, blocking native error dialog,
clean loop exit, rethrow from TaoApplication.run (taoApplication turns it
into exitProcess(1); 0 stays the normal-quit code).

- nativeShowErrorDialog in the existing nucleus_tao crate. macOS only for
  now via CFUserNotificationDisplayAlert - out-of-process, any-thread, no
  NSApp/run-loop dependency, so it is safe after the loop teardown where
  tao's latched [NSApp stop:] makes an NSAlert runModal session return
  immediately, and it cannot re-enter tao's non-reentrant handler mutex
  the way a modal pumped inside a callback frame can (Dock-reopen / deep
  link). Windows and Linux are silent no-ops until their implementations
  land.
- TaoApplication.reportFatal: first report wins (AtomicReference), exit()
  posted before logging so even a throwing formatter cannot freeze the
  loop; dialog + rethrow happen in run() after the loop exits. State is
  reset on run() entry so the loop can be re-run.
- taoApplication installs a CoroutineExceptionHandler routing composition
  and effect failures (recomposition, LaunchedEffect, setContent) to the
  fatal path - previously they hit stderr and the process exited 0. The
  macOS FrameDispatcher render loop gets the same handler (a frame
  exception silently stopped repainting under the SupervisorJob).
- Non-fatal native->JVM upcalls (deep link, a11y actions) and
  TaoMainDispatcher's swallowed block failures now log at SEVERE instead
  of vanishing / printStackTrace.
- -Dnucleus.tao.fatalErrorDialog=false skips the dialog for unattended
  runs (CI, AOT training).
- swing-tao-demo catches the documented rethrow and exits 1 instead of
  leaving a zombie process on the non-daemon EDT.

Verified end-to-end on macOS: raw-run fatal (dialog + rethrow, exit 42
from the caller) and Compose-composition fatal (CEH -> dialog -> exit 1).
2026-08-30 16:19:59 +03:00
Elie Gambache ec36b5ea5d Merge pull request #620 from NucleusFramework/fix/tao-standalone-popup-render-reentry
fix(tao): standalone popup crash on scrollbar drag (re-entrant render)
2026-08-30 12:58:31 +03:00
Elie G. 675226cc7f fix(tao): post frames scheduled from inside a scene pass in standalone popups
A scrollbar drag in a TrayApp popup scrolls synchronously inside the
pointer event (LazyListState.onScroll -> forceRemeasure), subcomposing
new lazy items whose LaunchedEffects dispatch into the scene dispatcher.
The standalone hosts' FlushingDispatcher schedules a frame on every
dispatch, and StandaloneFramePump rendered it inline: measureAndLayout
re-entered while the first pass was still on the stack, throwing
'performMeasureAndLayout called during measure layout' and corrupting
the scrollbar drag state ('entered drag with non-zero pending scroll').

The pump's re-entrancy guard only covered renders it started itself.
Extend it with nonReentrant {}: every scene entry point of the three
standalone hosts (pointer, scroll, key dispatch, setContent) now runs
inside it, so a schedule() arriving mid-pass is posted to the Tao main
queue instead of rendered inline - one event-loop turn of latency at
worst, no re-entry ever.

Covered by a scene-harness repro (real thumb drag over a LazyColumn in
the hosts' mirrored pump wiring, plus a negative test pinning the
re-entrant failure mode) and pump unit tests.
2026-08-30 12:42:24 +03:00
Elie Gambache 5b6ccedc1d Merge pull request #618 from NucleusFramework/fix/tao-headful-robot-hang
test(tao): stop the headful suite hanging on blocked Robot injection
2026-08-30 08:07:22 +03:00
Elie Gambache af8b5989b4 ci: cap job runtimes and fix the #576 chrome estimate on Xvfb
Three `gradle` jobs ran the full 6h Actions default before being killed —
`:launcher-linux:test` blocks in `g_bus_get_sync(G_BUS_TYPE_SESSION)` on a
runner with no session bus (the tao-headful job dbus-launches one, the gradle
job does not), and `:launcher-linux:check` never reports. The hang itself is
untouched here; the caps just stop one from holding a concurrency slot for six
hours. Every job observed takes 2-10 min, so the limits are 3-10x headroom.
test-packaging and test-graalvm already carried their own.

Also fix the #576 baseline chrome estimate, which has failed tao-headful on
Linux CI since 2026-08-27. Under Xvfb + openbox `outerBoundsPx()` reports a 1px
outer height for a while after mapping, so the baseline mode came out at -359
and every drift gate below it measured noise (reported 367px against a real
chrome of 8). Estimate only from frames where the outer box actually covers the
scene — an invariant on every platform — and fail with the measured numbers
instead of a bogus verdict when no such frame exists.
2026-08-30 08:07:04 +03:00
Elie Gambache bf0f0b25a2 Merge pull request #619 from NucleusFramework/fix/615-tao-subpixel-pointer-deadband
fix(tao): stop sub-pixel pointer jitter from eating clicks
2026-08-30 08:06:13 +03:00
Elie Gambache 171fe239c0 test(tao): cover the #615 sub-pixel click-eating repro in the scene harness
TaoScenePointerSlopTest reproduces the issue's parent-drag + child-clickable
shape: 0.2 px of press/release jitter must click (not drag) at 1x and 2x,
real motion must still drag, suppressed moves must never reach the scene,
press must dispatch at the last dispatched position, and touchSlop must be
18.dp in physical px. The harness mirrors the hosts' deadband dispatch, and
the new class is registered in the native-image test battery.
2026-08-30 07:52:00 +03:00
Elie Gambache 340769a7b7 fix(tao): suppress sub-pixel pointer jitter and density-scale touch slop
Fixes #615 — selections needing two clicks on the Tao backend.

Tao ships 1/1024-px cursor positions and macOS emits a CursorMoved before
every mouseDown/mouseUp, so a click drifting a fraction of a pixel between
press and release produces a real Move delta. Compose's mouse slop is only
touchSlop x (0.125dp / 18dp), so ~0.2 px of drift starts any parent drag
gesture, which consumes the move; the child clickable then sees a consumed
change in the Final pass and cancels the tap. The AWT backend never sees
this because it quantizes positions to integer points — a de-facto 1 dp
deadband.

Two changes, shipped together:

- TaoPointerDeadband: Moves under 1 dp from the last *dispatched* position
  are suppressed; press/release/exit/scroll dispatch at the last dispatched
  position (anything else and SyntheticEventSender re-injects the suppressed
  delta as a synthetic Move); motion past the deadband keeps its exact
  sub-pixel position. Wired into all three window hosts, including the
  Windows blending-overlay stream and the Linux overlay scroll dispatcher.

- TaoPlatformContextBase.viewConfiguration: density-scaled touchSlop
  (18.dp, AWT/Android parity) instead of PlatformContext.Empty's 18 raw px,
  which halved every drag/scroll threshold on 2x displays. sceneScale is
  provided live by each host and popup layer.
2026-08-30 07:51:53 +03:00
Elie Gambache 1768de068c test(tao): stop the headful suite hanging on blocked Robot injection
AWT Robot input injection ran on the Tao event-loop thread. On Wayland the
JDK routes it through the XDG RemoteDesktop portal, and when the compositor
refuses the session ("Session is not allowed to call NotifyPointer methods")
mousePress never returns from the native call — the loop froze and the global
watchdog halted the JVM, losing every result of the run.

- HeadfulRobot drives gestures on Dispatchers.IO under a timeout and latches
  the first failure so later calls fail fast instead of parking another thread
  on the same wedged native lock. new Robot() succeeds on such a host, so
  constructing one was never a usable availability probe.
- Apply TaoWindowTestCase.timeoutMillis, which was declared but never used:
  a driver that stalls now fails its own case instead of running out the
  global watchdog. TimeoutCancellationException is caught before the
  CancellationException rethrow so it is not mistaken for app teardown.
- Fix the #576 tremble metric. maxSceneVsInner was overwritten by an
  outer-derived value, duplicating maxSceneVsOuter and never comparing the
  scene against the reported inner size; a sample-count guard now keeps that
  gate from passing vacuously. GTK CSD frame extents are not a constant inset
  either (52/53/56 across one resize animation, outer leading the resize event
  by a frame), so the scene-vs-outer drift must survive consecutive frames to
  count as tremble.
2026-08-30 07:46:02 +03:00
Elie Gambache 89de516dd4 Merge pull request #617 from NucleusFramework/fix/612-press-and-hold-chrome-parity
fix(tao): drive macOS press-and-hold from replacementRange, not heuristics
2026-08-30 07:09:33 +03:00
Elie Gambache cf83ec2e09 fix(tao): harden the press-and-hold replacement path after review
Follow-ups from review of the two commits above.

Correctness:

- The `ImeReplaceCommit` branch did not set `key_triggered_ime`, so the
  key that picked the accent was also delivered as a raw key event. An app
  shortcut bound to a digit fired while the user was only choosing an
  accent. Both sibling IME branches already set it; the #595 invariant is
  that a key the input method consumed is not double-delivered.
- `attributedSubstringForProposedRange:` could underflow: `NSMaxRange`
  wraps on an overflowing proposed range, slips past both guards, and
  `end - loc` became a huge length, raising `NSRangeException` from inside
  an AppKit callout.
- Document-cache invalidation was global, so focus moving between windows
  (the old session tears down after the new one starts) wiped the cache
  the newly focused field had just installed. It is now scoped to the
  owning view, resets the marked-text anchor, and `detach()` invalidates
  explicitly so a freed view's address cannot be inherited by a later view
  allocated at the same address.
- A stale `markedText` ivar pinned the marked-text anchor from a previous
  field; an invalid cache now forces a re-anchor.
- `replaceCommit` ends the composition before committing: `commitText`
  replaces the composing region when one exists, which would have silently
  ignored the selection it had just set.
- The no-session fallback hardcoded a single Backspace regardless of the
  replaced length; it now deletes what the range asked for, bounded.

Fidelity and cost, both on the keystroke hot path:

- `nativeSetImeDocument` read the JVM string through modified UTF-8 and a
  lossy decode — two extra copies, and a length that can drift from the
  offsets the JVM computed (unpaired surrogates). It now reads the UTF-16
  directly via `GetStringRegion`, as its comment always claimed.
- The pushed window drops from ±512 to ±128 UTF-16 units, closer to the
  ±100 Chromium ships. The only reader is
  `attributedSubstringForProposedRange:`, which is only ever asked near
  the caret.
- `dispatch_ime_replace_commit` reuses the shared JNI helper instead of
  duplicating its attach/exception handling.

Tests:

- The probe left `e` held down when the layout-sanity check bailed out.
  Key state lives in the window server and outlives the child process, so
  that leaked a stuck key into the next scenario and the rest of the
  session.
- The repeat scenario asserted an exact six characters; the claim is that
  the key repeats, not how many synthetic autorepeats survive coalescing.
2026-08-29 23:29:02 +03:00
Elie Gambache efc383c356 test(tao): cover macOS press-and-hold against Chrome's behaviour
`TaoPressAndHoldE2ETest` (opt-in, `NUCLEUS_TAO_SMOKE=1`) replays real
keyboard traffic — CGEvents at the session tap, `kCGKeyboardEventAutorepeat`
stamped so AppKit sees a held key — against a focused Compose field. Each
scenario runs in its own child JVM so `ApplePressAndHoldEnabled` can be
set per scenario through the process argument domain, and because
`taoApplication` ends in `exitProcess(0)`.

The expectations are not invented: they are the behaviour recorded from
AppKit driving a reference `NSTextView` and a document-backed
`NSTextInputClient` on the same machine — what Chrome, Notes and TextEdit
do.

- press-and-hold disabled by the user: a held letter repeats
- press-and-hold enabled: the held letter never goes dead and the next
  keystroke is not eaten
- the picker's `insertText:"é" replacementRange:{0, 1}` replaces the base
  letter
- key roll-over while typing `xcode` is not misread as an accent pick

All four fail on the previous implementation: 'e' instead of 'eeeeee',
'x' instead of 'e…x', a watchdog timeout on the replacement commit, and
'xcde' instead of 'xcode'.

Stage-1 coverage for the replacement commit itself lands in
`TaoSceneImeTest`: in-range replacement, surrounding text left intact
with the caret after the accent, and an out-of-bounds range clamped
rather than thrown.

Two traps worth knowing, both hit while writing this:

- A scenario that engages the real picker must dismiss it with Escape
  before the next keystroke and at scenario end — the bubble is a system
  window and outlives the child process, so it steals the following
  scenario's keys.
- CGEvent posting inherits the machine keyboard layout, so the probe
  sanity-checks the first keystroke and reports `skip=layout` instead of
  failing on a non-Latin layout.

`nucleus_tao_post_key_to_view` gained an autorepeat flag and
`nucleus_tao_inject_insert_text` a replacement range; both default to the
previous behaviour for existing callers.
2026-08-29 23:15:27 +03:00
Elie Gambache e37a77777f fix(tao): drive macOS press-and-hold from replacementRange
Nucleus forced `ApplePressAndHoldEnabled` on in four domains — app,
argument volatile, CFPreferences, registration — from both `+load` and
runtime, then guessed when the accent picker was active from a state
machine (`g_letter_key_down`, `g_press_and_hold_queried`, `g_base_text`).
Both halves were wrong:

- Forcing the default suppresses key repeat, but does not guarantee the
  picker engages. Where it cannot (non-Apple keyboards, Karabiner virtual
  devices), a held letter did nothing at all — no repeat, no picker,
  strictly worse than either OS behaviour and invisible to the user
  (#612).
- The heuristics misread ordinary fast typing: with the previous key
  still physically down, a `selectedRange` query armed the picker flag
  and the next commit was routed into the replace path, deleting the
  character that had just been committed ("Xcode" landing as "Xcde")
  (#611).

Chromium does neither. `ApplePressAndHoldEnabled` has zero occurrences
in its tree and its history; the picker works because
RenderWidgetHostViewCocoa answers `selectedRange` /
`attributedSubstringForProposedRange` over a cached window of committed
text and honors `replacementRange` in `insertText:`. Recording AppKit
against an `NSTextView` and a document-backed `NSTextInputClient`
confirms the protocol: the accent pick arrives as `insertText:"é"
replacementRange:{caret-1, 1}` — UTF-16, document-absolute — and the
range carries everything. No client-side detection is involved.

Same model here:

- Never read, set or register the user default. The OS decides whether a
  held letter repeats or opens the picker.
- The host pushes the focused field's committed text (a bounded window),
  its document-absolute offset and the selection through
  `nativeSetImeDocument`, mirroring Chromium's renderer-to-browser
  selection + surrounding-text push. The swizzled getters serve
  document-absolute answers from that cache; the marked-text anchor is
  maintained optimistically in a `setMarkedText:` swizzle, the same
  fallback chain Chromium uses for `_markedRange`.
- `insert_text` routes a valid `replacementRange` outside a composition
  to `WindowEvent::ImeReplaceCommit`, and `TaoImeSession.replaceCommit`
  applies it select-then-insert — Blink's `ReplaceTextAndKeepSelection`
  semantics — instead of a blind `deleteSurroundingTextInCodePoints(1, 0)`.

Routing the pick through tao's event queue also removes a re-entrant
`EVENT_CALLBACK` lock: the previous direct upcall from the AppKit callout
deadlocked when the JVM re-entered native on the same thread.

Repeat keyDowns still reach `interpretKeyEvents:` (unchanged), which is
what lets the picker engage where the OS allows it.

Closes #612.
2026-08-29 23:15:14 +03:00
Elie Gambache 9391c8cc46 Merge pull request #616 from NucleusFramework/fix/614-autolaunch-xsettings-race
fix(autolaunch): order the systemd unit after GNOME publishes Xft.dpi
2026-08-29 22:15:27 +03:00
Elie Gambache f539f8ba1f test(autolaunch): cover the generated systemd unit content
Pin the x11-services ordering that keeps a login-started app from
freezing sun.java2d.uiScale at 1.0, and cover the ExecStart quoting of
paths containing spaces and double quotes, which had no coverage.

buildUnitContent becomes internal to be testable; the enclosing object is
already internal, so the public ABI is unchanged.
2026-08-29 22:14:28 +03:00
Elie Gambache ee0269ffc7 fix(autolaunch): order the systemd unit after GNOME publishes Xft.dpi
graphical-session.target is reached before gsd-xsettings writes Xft.dpi
into the X resource database. Toolkits that sample the display scale once
at startup - skiko's autodpi freezes sun.java2d.uiScale at library load -
then probe an empty database and stay at 1.0 for the life of the process,
so a HiDPI app auto-started at login renders at half size while a manual
launch is always correct.

Order the generated unit after gnome-session-x11-services-ready.target,
the synchronization point declared by org.gnome.SettingsDaemon.XSettings
via Before=. The non-"ready" target is ordered ahead of that service, so
it is listed for documentation only and does not close the gap on its own.

systemd ignores ordering dependencies on units that are not loaded, so
this is a no-op on non-GNOME desktops. XSettings.service is Type=dbus, so
the race is narrowed to sub-second rather than provably closed.

Closes #614
2026-08-29 22:14:21 +03:00
Elie Gambache c00685c9d3 Merge pull request #613 from takke/fix/558-linux-ime
fix(tao): forward Linux IME preedit into Compose
2026-08-29 22:00:21 +03:00
Elie Gambache b8f775d11d Merge pull request #610 from NucleusFramework/fix/tao-standalone-popup-scroll
fix(tao): align standalone tray popup wheel scroll with DecoratedWindow
2026-08-29 21:59:22 +03:00
Elie G. bde0fe2629 test(tao): inject StandaloneFramePump main-thread and post hooks
Tests no longer mutate TaoMainDispatcher.taoMainThread or swallow leaked fallback frames. Nested work is a drained queue; a posted frame after dispose is dropped.
2026-08-28 15:42:18 +03:00
Elie G. 8f91677141 test(tao): cover StandaloneFramePump reentry and scrollAmount asymmetry
Make disposed volatile, document that rendering is main-thread-only, and add mapper tests so Windows/Linux/macOS scrollAmount policy cannot be silently unified.
2026-08-28 15:38:29 +03:00
Elie G. ac8b8ee1df fix(tao): align standalone tray popup wheel scroll with DecoratedWindow
Feed tray/overlay/owner-based popup wheel events through the same AWT-shaped dispatch as TaoWindow, apply Windows lines-per-notch via LocalScrollConfig, and paint standalone popup frames inline when already on the main thread so a wheel flood cannot starve MainEventsCleared.
2026-08-28 15:33:31 +03:00
Elie Gambache 1604d8ac2b Merge pull request #609 from NucleusFramework/fix/macos-standalone-popup-show-desktop
fix(tao): keep macOS standalone tray popups off Show Desktop and the status item
2026-08-28 10:50:17 +03:00
Elie Gambache 911c77baa6 fix(tao): don't dismiss standalone tray popups on status-item clicks
The local outside-click monitor treated mouseDown on the NSStatusItem
(and the native context menu) as outside the panel. Right-click therefore
hid the tray popup before the menu appeared. Skip those windows for
standalone panels; left-click toggle stays with the tray callback.
2026-08-28 10:35:35 +03:00
Elie Gambache 8c69d00fed fix(tao): keep standalone macOS tray popups off Show Desktop
Ownerless NSPanels had default collection behavior, so Sonoma+
"Click wallpaper to reveal desktop" Exposé-slides the tray popup
while Compose is already fading it. Mark the standalone panel
stationary (and join-all-spaces / fullscreen-auxiliary / ignores-cycle)
so we dismiss it ourselves.
2026-08-28 10:32:58 +03:00
Elie Gambache 3066f56069 Merge pull request #608 from NucleusFramework/fix/macos-popup-scroll-direction
fix(tao): match AWT wheel mapping on macOS popups
2026-08-27 22:40:00 +03:00
Elie Gambache 52f19ee2c0 fix(tao): match AWT wheel mapping on macOS popups
Popup NSPanels forwarded raw AppKit scrollingDelta. TaoWindow already
reshapes those to AWT preciseWheelRotation (negate Y, physical /10 for
precise deltas). Tray and overlay popups skipped that path, so scroll
was inverted and trackpad magnitude disagreed with the main window.

Route both hosts through dispatchAppKitScroll.
2026-08-27 22:20:55 +03:00
Elie Gambache 61ce87755a Merge pull request #607 from NucleusFramework/fix/605-linux-standalone-popup-xdnd
fix(tao): register inbound XDND on Linux standalone tray popups
2026-08-27 22:20:47 +03:00
Elie Gambache 070d93c88a fix(tao): register inbound XDND on Linux standalone tray popups
TaoStandalonePopup on Linux is a raw override-redirect X11 window, so
GTK gtk_drag_dest_set cannot attach. Implement XDND 5 on the panel
event thread — the creating client, which is the only one that receives
mask-0 ClientMessages — and dispatch through TaoSceneDnD like the
Windows/macOS hosts.

Closes #605
2026-08-27 22:02:27 +03:00
Elie Gambache 431db0b81c Merge pull request #603 from takke/fix/558-windows-ime
fix(tao): forward Windows IME preedit into Compose
2026-08-27 20:32:43 +03:00
Elie Gambache 86a3ab8917 Merge pull request #604 from NucleusFramework/fix/standalone-popup-inbound-dnd
fix(tao): register inbound DnD on standalone tray popups
2026-08-27 20:29:04 +03:00
Elie G. 43048155b0 fix(tao): add Windows native-image JNI metadata for tray popup DnD 2026-08-27 20:23:38 +03:00
Elie G. ef6c3d824d fix(tao): register inbound DnD on standalone tray popups 2026-08-27 20:12:19 +03:00
Elie Gambache 27d4efcd6f Merge pull request #600 from takke/fix/javadoc-encoding-windows
fix(build): pin javac/javadoc encoding to UTF-8
2026-08-27 18:41:03 +03:00
Elie Gambache c0cab182de Merge pull request #602 from NucleusFramework/fix/windows-standalone-popup-skip-hidden-frames
fix(tao): skip GPU frames on hidden Windows standalone tray popup
2026-08-27 18:32:16 +03:00
Elie Gambache 62f72705f4 Merge pull request #601 from NucleusFramework/fix/windows-popup-scroll-direction
fix(tao): match AWT wheel sign on Windows popups
2026-08-27 18:32:08 +03:00
Elie G. 3ae76eb48e fix(tao): skip GPU frames on hidden Windows standalone tray popup 2026-08-27 18:27:23 +03:00
Elie G. c58f0a8b27 fix(tao): match AWT wheel sign on Windows popups
Popup and overlay WndProcs report raw Win32 wheel units. TaoWindow
already negates those to AWT preciseWheelRotation; standalone and
owned popup layers forwarded the raw sign, so TrayApp scrolling
was inverted.
2026-08-27 18:26:33 +03:00
Elie Gambache 167c8cbaba Merge pull request #596 from NucleusFramework/fix/595-macos-ime-preedit
fix(tao): forward macOS IME preedit into Compose
2026-08-27 16:09:30 +03:00
Elie Gambache 30b80e7242 Merge branch 'main' into fix/595-macos-ime-preedit 2026-08-27 16:03:23 +03:00
Elie Gambache 75255a597c Merge pull request #599 from takke/fix/595-press-and-hold-ime-gate
fix(tao): gate the PressAndHold heuristic while an IME composition is live
2026-08-27 15:57:17 +03:00
Elie Gambache bd2755a2d1 Merge pull request #598 from NucleusFramework/feat/graalvm-25-3-default
feat(graalvm): default the innovation channel to GraalVM 25.3
2026-08-27 15:37:20 +03:00
Elie G. e911f11ec5 feat(graalvm): default the innovation channel to GraalVM 25.3 2026-08-27 15:28:55 +03:00
Elie Gambache 3b9fdcbc32 fix(tao): keep macOS IME composition in sync with IMKit (#595)
NSTextInputClient was lying to Kotoeri/ATOK (markedRange off-by-one,
selectedRange NSNotFound, nil substring). Live conversion then committed
a U+F7xx function-key char, which wiped the Compose preedit.
2026-08-27 15:12:22 +03:00
Elie Gambache d42d5793cb Merge pull request #597 from NucleusFramework/fix/576-animated-window-size-tremble
fix(tao): stop TitleBar+content tremble when animating WindowState.size
2026-08-27 14:43:16 +03:00
Elie G. eb0f30f954 style(tao): fix ktlint spacing and modifier chain (#576) 2026-08-27 13:10:38 +03:00
Elie G. 558c8162f6 fix(tao): stop TitleBar+content tremble when animating WindowState.size
Programmatic height animation left the HWND/NSWindow a frame ahead of the Compose scene, and native resize echoes wrote WindowState.size backwards. Apply inner size synchronously, push EVENT_RESIZED in the same turn, and ignore in-flight programmatic echoes (#576).
2026-08-27 12:03:55 +03:00
Elie Gambache 95e3b4bbbc fix(tao): forward macOS IME preedit into Compose (#595)
CJK composition was invisible and the committing Enter leaked as a
newline. Forward setMarkedText/unmarkText as preedit, commit via
commitText, and drop keys the IME consumed on the native side.
2026-08-27 10:12:16 +03:00
Elie Gambache f56b6c25cc docs: align README with Nucleus 2.5.0
Bump plugin/runtime coordinates, Kotlin 2.4.10, and Compose 1.12.0;
mark AWT backends deprecated; add spellcheck and current perf numbers.
2026-08-26 18:35:01 +03:00
Elie Gambache e87a1ecff6 Merge pull request #592 from NucleusFramework/nucleus-2.5
Nucleus 2.5
2026-08-26 15:51:32 +03:00
Elie Gambache 4ef488865c fix(ci): derive the Kover agent jar path from the version catalog
The taoHeadfulTest javaagent path hardcoded kover-jvm-agent-0.9.8.jar,
so the Kover 0.9.9 catalog bump left the JVM pointing at a missing jar
and the headful suite failed to start on every OS.
2026-08-26 15:49:22 +03:00
Elie Gambache d17668bbac Merge branch 'main' into nucleus-2.5 2026-08-26 13:52:07 +03:00
Elie Gambache c35a04c1dc Merge pull request #591 from NucleusFramework/chore/version-catalog-bumps
chore(deps): bump version catalog dependencies
2026-08-26 13:48:14 +03:00
Elie Gambache 9ab8a94096 chore(deps): bump version catalog dependencies
Notable: Kotlin 2.4.0 -> 2.4.10, Compose 1.12.0-rc01 -> 1.12.0 stable,
Jewel 0.39.1, Ktor 3.5.2, OkHttp 5.5.0, AWS SDK 2.54.4, FileKit 0.15.0,
Logback 1.6.3, detekt 2.0.0-alpha.6, Kover 0.9.9, versionCheck 0.61.0,
lifecycleViewmodelNavigation3 2.11.0, Lighthouse 2.3.2
2026-08-26 13:46:37 +03:00
Elie Gambache 946e4b5ee6 Merge pull request #540 from xchacha20-poly1305/custom-scheduler
feat(scheduler): allow a custom command for scheduled tasks
2026-08-26 13:17:57 +03:00
Elie Gambache 2e7d695ccd Merge pull request #589 from NucleusFramework/fix/548-mpris-invalid-dbus-name
fix(media-control): never hang on invalid MPRIS D-Bus name (Linux)
2026-08-26 13:05:28 +03:00
Elie Gambache 92b7a44ec5 fix(media-control): never hang on invalid MPRIS D-Bus name (Linux)
An app.id with characters illegal in a D-Bus name (e.g. "Music Radio")
produced an invalid default MPRIS bus name. g_bus_own_name() rejected it
without invoking on_name_acquired/on_name_lost, so nativeStartListening()
waited on the ready condition forever — blocking the caller (often the
Tao UI thread during first composition) and leaving the app windowless
while it still owned the single-instance lock.

- MediaControlService: sanitize NucleusApp.appId into a legal D-Bus name
  for the default dbusName (illegal chars -> "_", digit-leading elements
  prefixed, empty elements dropped, 255-char limit respected)
- native: validate the name with g_dbus_is_name() before spawning the
  service thread and skip MPRIS with a warning instead of blocking
- native: fail open (g_ready = -1 + broadcast) if g_bus_own_name()
  returns 0 without invoking any callback
- native: bound the ready wait with a 10 s timeout as a backstop so a
  stalled session bus can never block the UI thread indefinitely
- NativeLinuxBridge: log a JUL warning when MPRIS registration fails

Fixes #548
2026-08-26 13:02:26 +03:00
Elie Gambache 7c7572432e Merge pull request #588 from NucleusFramework/fix/584-dialog-close-button-hover-fade
fix(tao): dialog close button hover fade animation on Windows
2026-08-26 12:54:30 +03:00
Elie Gambache 21a2a7ff47 fix(tao): animate dialog close button hover like DecoratedWindow
DialogTitleBar drew its own close button with hardcoded Win10 colors and
no hover fade. Delegate to the shared WindowsWindowControl so the dialog
close button is pixel-identical to DecoratedWindow's caption buttons:
Win11 WinUI tokens, snap-in / 150ms fade-out animation, active/inactive
and dark-mode glyph variants, custom TitleBarStyle colors, and native
HTCLOSE hit-zone publication.

Fixes #584
2026-08-26 12:52:52 +03:00
Elie Gambache f0d66a870f fix(plugin): stop Linux update helper leaking a gpg-agent into the app's systemd scope
gpg --import/--verify daemonize a gpg-agent for the throwaway homedir; with
no idle timeout it inherits the app's systemd scope and keeps it reported as
running forever, and the trailing exec bypassed the EXIT trap so the
root-owned work dir leaked too.

Verify with gpgv (never starts an agent) against a gpg --dearmor'd keyring
(pure filter), and drop the execs so the EXIT trap cleans the work dir —
set -e still propagates dpkg/rpm exit codes unchanged.

Fixes #567
2026-08-26 12:46:09 +03:00