Closing the window (OS close button or the custom title bar's ×) now
goes through one handler:
- in a call → always hide to the tray (a close must never drop a call);
- saved "tray" → hide; "quit" → exit the app;
- "ask" (the default until chosen) → emit `lotus-close-requested` so the
web client shows the one-time dialog; if the page isn't listening yet,
fall back to the tray.
The choice lives in `close-behavior` in the app config dir;
get_close_behavior / set_close_behavior / resolve_close_request back the
dialog and the Settings select.
Verified on Linux under Xvfb + openbox with real WM close requests
(wmctrl -c): tray → hidden, still running; quit → exits; unset with no
page listening → hidden; ask with the page listening → window stays,
page receives the event; quit during a call → hidden, still running.
Bump cinny (the dialog + Settings control).
Closes#5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
- Launch-on-login now starts the app with `--autostart`. With "Start
minimized" on (the default), that launch begins "settled", so neither
the page-load reveal nor the 8 s failsafe shows the window: it stays in
the tray with sync and notifications running. The tray, a second launch
or a deep link reveals it.
- The choice is a file in the app config dir (`start-minimized`), read
before the web client loads; `get_start_minimized` / `set_start_minimized`
back the new Settings switch.
- Existing registrations made without args are rewritten at startup
(re-enable), so current launch-on-login users get `--autostart` too.
- window-state no longer restores VISIBLE: it show()ed the window at
creation whenever it was last closed visible (or on first run), before
the page painted and even on a login start. Visibility is owned by the
page-load reveal + failsafe.
Verified on Linux under Xvfb with the debug build:
normal launch → shown; --autostart → hidden (past the failsafe) and still
running; --autostart with the setting off → shown; --autostart after
quitting visible → hidden; normal launch after quitting visible → shown;
a second launch reveals the hidden instance; an existing autostart entry
gets `--autostart` added.
Bump cinny (the Settings switch and the SW protocol guard).
Closes#3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
`set_taskbar_progress(status, progress)` drives Tauri's window progress
bar (Windows taskbar button; launcher/dock elsewhere where supported):
none / normal (0-100) / indeterminate / error. Uses Tauri's built-in
ITaskbarList3 wrapper rather than more hand-written COM.
Bump cinny (the aggregation, throttling and error hold).
Closes#10
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
`set_tray_call_state({active, muted, deafened})` adds a small ringed dot
to the tray icon's bottom-left: green in a call, amber muted, red
deafened (deafened implies muted). The unread dot stays bottom-right.
The tooltip spells it out ("Lotus Chat — in call, muted · update ready").
Unread, call state and the pending update (#6) now live in one
`TrayIndicators` and re-render together, so none of them overwrites
another. The unread painter is generalised to `draw_dot(left, color)`.
Unit tests cover the tooltip text and dot placement.
Bump cinny to 25fa0f6e (the web half, plus the stale-state fix that
removed a "muted + deafened" flash at every join).
Closes#4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
`set_tray_update_ready(version | null)` inserts a "Restart to update
(vX)" item at the top of the tray menu and sets the tooltip to "Lotus
Chat — update ready"; null removes both. The item only exists while an
update is pending. Clicking it reveals the window and emits
`lotus-tray-install-update`, and cinny runs its normal install flow
(progress, retries, failure toast). No icon dot: the unread dot already
uses that corner.
Bump cinny to 6edfe700 (the web half).
Closes#6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
A friend's in-app update failed ten times ("error sending request for
url (…nsis.zip)") against a busy Gitea before the 11th worked.
- check_for_update / install_update retry transport errors (Reqwest,
Network, ReleaseNotFound) with 3 s / 8 s / 15 s backoff — four tries
per click. Signature or install errors fail at once, never masked.
- Every request gets a 600 s timeout; reqwest has none by default, so a
stalled connection could leave the UI on "installing" forever.
- Download and install are separate steps so a retry never re-installs.
- `lotus-update-progress` events (downloading %, retrying + wait,
installing) drive the new progress text in Settings.
- Errors are prefixed `check:` / `download:` / `install:` so the UI says
which step failed (it used to call a failed download "Update check
failed").
- Adds tokio (time feature only; already in the tree via tauri).
- Bump cinny to 568f218f (the matching UI).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Toasts with a room id get a Mark as read action next to Send; it emits
`lotus-notification-mark-read {roomId}` without raising the window, and
cinny marks the room read through its existing markAsRead path. Invite
toasts (no room id) get neither button.
Bump cinny to 23649f12.
Closes#9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
The updater launches the NSIS installer with /UPDATE and then exits the
app, but a WebView2 app takes a moment to die and the template only
sleeps 500 ms after its own kill before copying — so the copy raced the
old process ("Error opening file for writing: cinny.exe"), and Ignore
left the old exe in place.
A NSIS_HOOK_PREINSTALL now, on /UPDATE only, polls until cinny.exe can
really be opened for writing (every 250 ms, up to 15 s), then lets the
template carry on. Checked under Wine with a real running exe: without
the hook the copy fails; with it the installer waits out the remaining
~2.8 s and copies; a manual install (no /UPDATE) doesn't wait; a stuck
process hits the 15 s cap and falls through to the template's own
running-app handling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
- Rich toasts set a WinRT Tag (hash of the web notification tag, since
room:thread tags exceed the 64-char limit) in a fixed Group, so a newer
toast for the same room/thread replaces the older one instead of
stacking; the replaced toast's keep-alive entry is dropped (#16).
- The bridge passes tag, roomId and threadId separately. The reply target
is the real room id (+ thread), never the coalescing tag, which was
`room:thread` for threads and `lotus-invites` for invites, so those
replies failed silently. Toasts with no room id (invites) get no reply
box (#17).
- New `get_focus_assist` command serves the latest Focus Assist poll so
the web hook can hydrate on mount; the setup-time first emit was lost
before the page listened (#15).
- Bump cinny to 3b6de2fd (the matching web-side hooks).
Closes#15, #16, #17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
PTT and deafen are DOM key handlers in cinny and only fire while the Lotus
window has focus — alt-tab into a fullscreen game and the voice controls
stop working.
New native/hotkeys.rs (Windows): `set_global_hotkeys(bindings)` starts a
thread that samples GetAsyncKeyState for the configured W3C key codes every
~8 ms while a call is joined and emits a `lotus-global-hotkey` DOM event
{id, state, ctrl, alt, meta} on each press/release transition; an empty
list stops it. Deliberately NOT RegisterHotKey / a global-shortcut plugin:
those consume the key system-wide (a bare Space PTT would stop every other
app typing spaces). `global_hotkeys_supported` reports false off Windows,
where the commands are no-ops (Linux X11 could poll XQueryKeymap later;
Wayland has no non-consuming path). HotkeyPoll state is managed
unconditionally in setup. Cargo: Win32_UI_Input_KeyboardAndMouse feature.
Cross-checked against windows 0.61 for x86_64-pc-windows-msvc; Linux
cargo check clean.
Web side: cinny 00584d78 (useCallHotkeys + Settings → Calls toggle).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA