• v0.3.20 Stable

    eugene released this 2026-06-17 00:42:51 -07:00 | 7 commits to main since this release

    gotifyBoard v0.3.20

    What's fixed

    Toast window no longer lingers as a bare dark rectangle after the last card

    Users intermittently reported that when a toast card finished exiting
    (either auto-dismiss or user click), the toast window would remain on
    screen as a featureless dark rectangle until the next notification
    arrived. Most of the time it worked correctly. The pattern showed up
    most often with short dismiss durations or when the user clicked X on
    a card within ~200 ms of it appearing.

    Root cause. The visibility effect in
    src/toast/ToastApp.tsx was an async IIFE with no cancellation or
    serialization guard. The show path has 4+ IPC awaits
    (setSize, pickMonitor, get_monitor_work_area, setPosition,
    show) — easily 10–250 ms of round-trips to the Rust backend. The
    hide path is a single fast dismiss_toast invoke. When effect A
    (show, slow) was still mid-pipeline and effect B (hide, fast) ran
    and completed first, effect A's final win.show() resolved last and
    re-showed the window — by which point ToastApp was rendering null
    because the stack was empty. The user saw the body's opaque
    --gb-bg color (the toast window isn't OS-transparent), and the
    state stuck until stack.length changed again (next notification).

    Fix. Every visibility effect run now chains off a shared
    Promise queue, so hide segments wait for prior show segments to
    complete in order. The show segment also re-checks
    latestStackLenRef.current (updated synchronously on every effect
    entry) immediately before its final win.show() — if the desired
    state has flipped to "hidden" while we were awaiting positioning IPC,
    we bail before showing, and the queued hide segment that caused the
    flip will run next.

    Files changed

    • src/toast/ToastApp.tsx — added visibilityOpsRef and
      latestStackLenRef; wrapped the existing visibility effect body
      inside a chained .then(async () => { ... }); added a
      pre-win.show() guard that bails when the latest desired length
      is 0.
    • CHANGELOG.md — v0.3.20 entry.
    • package.json, src-tauri/Cargo.toml, src-tauri/Cargo.lock,
      src-tauri/tauri.conf.json — version bump to 0.3.20.
    • docs/plans/phase-64-toast-window-stays-visible.md — approved
      plan from Phase 64.

    Verification

    • npm run typecheck, npm run lint, npm test — all green
      (24 files, 221 tests).
    • Manual: with toast dismiss = 3 s on the Linux dev build, fired
      notifications and either clicked X within ~200 ms or let them
      auto-dismiss. The bare-dark-rectangle state did not appear.
    • Stacked behavior verified: 3 toasts in rapid succession, mixed
      click-dismiss and auto-dismiss — window hides cleanly when the
      last card exits.

    Rollback

    git revert v0.3.20 and re-run scripts/patch-latest-json.mjs with
    v0.3.19 URLs and signatures. v0.3.19 assets remain at tag v0.3.19
    on eugene/gotifyboard and euronvault/gotifyboard-updates. The
    v0.3.19 toast countdown behaviour (= v0.3.15) returns; the intermittent
    window-lingering bug returns with it.

    Downloads