On CachyOS the in-app update failed with "Permission denied (os error 13)
at path /usr/bin/tauri_current_app…": the app was installed from the
Arch package, and Tauri's Linux updater can only replace an AppImage — for
anything else it tries to write next to the binary in /usr/bin.
- update_install_kind command: "in-app" on Windows and for an AppImage
($APPIMAGE set); on Linux package installs "pacman" (ID/ID_LIKE arch:
Arch, CachyOS, Manjaro, EndeavourOS…), "deb" (debian/ubuntu and
derivatives) or "manual". The web UI shows the matching update command.
- install_update refuses up front on a package install
("install: package-managed (…)") instead of downloading the whole
update and failing at the last step.
Tests: CachyOS/Arch → pacman; Ubuntu/Debian/Mint → deb; Fedora/unknown →
manual; AppImage and Windows → in-app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
The CI VM has no microphone (getUserMedia → NotFoundError). With
LOTUS_WEBVIEW2_DEBUG_PORT set the app also passes
--use-fake-device-for-media-stream; permission requests still go through
the real PermissionRequested handler.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
WebView2's WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS is ignored because the app
sets its browser arguments explicitly (seen on the runner: the WebView2
command line had only the app's arguments). The app now appends
--remote-debugging-port only when LOTUS_WEBVIEW2_DEBUG_PORT holds a valid
port (>= 1024); otherwise the arguments are exactly as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Commands for the web client to keep a copy of the login tokens in the OS
keychain: secure_session_supported / _set / _get / _clear.
- Windows: Credential Manager via the keyring crate (3.6, windows-native),
entry "session" in service "Lotus Chat". Only the secrets are stored
(userId, deviceId, accessToken, refreshToken); the serialized value is
capped at 1200 chars (Windows' limit is 2560 bytes).
- Other platforms: supported = false and the other commands answer "not
supported on this platform" (Linux Secret Service can prompt to unlock a
wallet at startup; that needs its own testing). No new Linux dependency:
without a platform feature the crate only has its mock store.
- Keychain calls run on the blocking pool, off the main thread.
Step 1 is a mirror only (the web client still reads its session from
localStorage); see the cinny PR.
Tests: round trip + clear, clearing an empty keychain, incomplete and
oversized sessions rejected with nothing written, the JSON shape the web
client sends, a realistic OIDC session fits (keyring's mock store). Linux
release build: commands answer as designed and login is unaffected.
Windows: type-checked only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
The bundled Element Call page ran on the app's own origin
(http://localhost:44548), so the call frame could read the app's storage
(login token) and DOM. Serve it from http://127.0.0.1:44548 instead: the
same local server and bundle, a different origin.
- The local server binds 127.0.0.1 explicitly. The app is still loaded as
http://localhost:44548 (its storage stays where it is; the engines try
127.0.0.1 for `localhost`). Binding the name `localhost` could pick ::1
only (Windows lists it first), and then 127.0.0.1 wouldn't answer.
- config.json: desktopCallOrigin = http://127.0.0.1:44548. cinny loads the
call page from there only when this is set (cinny #43 PR).
- CSP frame-src allows http://127.0.0.1:44548.
- Permissions (on top of #22): the call page's origin gets microphone/
camera/screen only; nothing else.
- The call page gets no IPC: the capability only matches
http://localhost:44548.
Tested (Linux release build): the server listens on 127.0.0.1:44548 and
the app loads as http://localhost:44548; the call page loads from
127.0.0.1 inside the app under its CSP; from that frame parent.localStorage
and parent.document are SecurityError, while a same-origin frame (the old
setup) reads the app's storage. The call itself was tested in a simulated
desktop (Chromium, the WebView2 engine) against a local Synapse + LiveKit;
see the cinny PR. Rust tests 17 passed; Windows code type-checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Linux (WebKitGTK) allowed every permission request of every kind; Windows
(WebView2) auto-allowed mic/camera/notifications without checking who asked.
Now (src-tauri/src/webview_permissions.rs, unit-tested):
- Linux: microphone/camera/screen, device labels, notifications and location
are granted when the page in the window is the app
(http://localhost:44548; debug builds also the bundled/dev page).
Everything else is denied (WebKitGTK has no prompt of its own). WebKitGTK
doesn't say which frame asked; frames are gated earlier by the Permissions
Policy (cinny gives microphone/camera only to the same-origin call frame).
- Windows: the same grants (minus location, which keeps WebView2's prompt),
checked against the origin of the frame that asked (args.Uri()). Other
origins are denied mic/camera/notifications/location; other kinds keep
WebView2's default handling.
- Denials are logged ("webview: denied …").
Tested on Linux with a release build under Xvfb + PulseAudio (no WebDriver:
WebKit's automation mode bypasses the handler), before/after:
- app page: mic, device labels, location allowed (unchanged)
- same-origin call frame: mic allowed (unchanged)
- cross-origin frame without allow=: blocked before the handler (unchanged)
- foreign top-level page: mic, device labels, location now denied (were
allowed)
Real cinny build: boots, logs in, no denials. Windows code type-checked
(x86_64-pc-windows-gnu).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
The 4.12.343 workaround (WEBKIT_DISABLE_DMABUF_RENDERER=1 + GDK_BACKEND=x11)
made the app launch but pushed every frame through shared memory under
XWayland: noticeably laggy at 3840x2058. The reporter's WAYLAND_DEBUG trace
showed the real cause on native Wayland:
wl_display#1.error(wp_linux_drm_syncobj_surface_v1#51, 4,
"explicit sync is used, but no acquire point is set")
Gdk-Message: Error 71 (Protocol error) dispatching to Wayland display.
An explicit-sync bug, not a GBM format/modifier one. Their test matrix
(RTX 3070, driver 615.71.09, webkit2gtk 2.52.6, KDE Wayland):
- __NV_DISABLE_EXPLICIT_SYNC=1 alone: clean, GPU (DMA-BUF) renderer, smooth;
- WEBKIT_DISABLE_DMABUF_RENDERER=1 alone on Wayland: clean (no X11 needed);
- WEBKIT_DMABUF_RENDERER_DISABLE_GBM=1: same explicit-sync error on Wayland,
"Failed to import DMABuf" under XWayland;
- X11/XWayland with the DMA-BUF renderer: "Failed to create GBM buffer".
New defaults when the NVIDIA driver is loaded:
- native Wayland: __NV_DISABLE_EXPLICIT_SYNC=1, GPU renderer kept;
- X11 session or user-forced GDK_BACKEND=x11: WEBKIT_DISABLE_DMABUF_RENDERER=1;
- never force X11 any more;
- LOTUS_GPU_SAFE_MODE=1: opt-in shared-memory rendering on Wayland too;
- LOTUS_NO_GPU_WORKAROUNDS=1 / user-set values: untouched.
One stderr line says what was set. 9 unit tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
NVIDIA + Wayland (reported on CachyOS/KDE, driver 615.71, webkit2gtk
2.52.6): no window. GDK dies with "Error 71 (Protocol error) dispatching to
Wayland display"; forced onto XWayland, WebKit's DMA-BUF renderer then fails
with "Failed to create GBM buffer … Invalid argument". The reporter's
workaround, WEBKIT_DISABLE_DMABUF_RENDERER=1 GDK_BACKEND=x11, runs fine.
gpu_workarounds::apply(), first thing in main() (before GTK/WebKit init):
when the NVIDIA driver is loaded (/proc/driver/nvidia/version or
/sys/module/nvidia), set WEBKIT_DISABLE_DMABUF_RENDERER=1, and on a Wayland
session that has XWayland (DISPLAY set) also GDK_BACKEND=x11. Values the user
already set win; LOTUS_NO_GPU_WORKAROUNDS=1 turns it all off. Logs what it
set to stderr. Non-NVIDIA systems are untouched. Unit tests for the decision.
config.json: webAppUrl = https://chat.lotusguild.org, used by the web client
(cinny `calls-open-in-browser`) to send calls this WebKitGTK build can't make
to the web app in the browser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
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
WebKitGTK ships enable-media-stream/enable-webrtc off by default
(unlike WebView2/WKWebView), leaving navigator.mediaDevices undefined
and Element Call reporting "browser does not support WebRTC" on every
Linux build. Enable both settings and auto-grant the resulting
camera/mic permission request, mirroring the existing WebView2 handling.
Also add a build-arch CI job that repackages the existing .deb into a
real .pkg.tar.zst and uploads it to the Gitea release alongside the
Windows/Linux artifacts, for Arch/CachyOS users who'd rather use pacman
than an AppImage.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Tauri frame-src is an enumerated allowlist (unlike the permissive web CSP
`frame-src 'self' https:`), so embeds whose host isn't listed render as a
blank box in the desktop app with no visible error. Adds the two new music
providers (www.mixcloud.com, widget.deezer.com) and store.steampowered.com,
whose Steam widget shipped earlier but was already blank on desktop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Taskbar showed two icons because the AppUserModelID was (a) set too late — after
the main window was built, so its taskbar button grouped under a mismatched
implicit AUMID — and (b) valued 'LotusGuild.LotusChat', which differs from the
bundle identifier 'org.lotusguild.lotus-chat' that Tauri's NSIS installer stamps
on its shortcuts. Fix both: set the AUMID at the top of run() before the window
is built (new set_process_aumid, split out of ensure_app_user_model_id), and
align the constant to the bundle identifier so the running window and a pinned
installer shortcut group together.
Notification click didn't raise the window: add a focus_main_window command
(reuses show_main) for the web/service-worker path, and call show_main directly
in the rich-toast Activated body-click handler.
Note: existing users who pinned the old mis-grouped icon may need to re-pin once.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add platform.twitter.com (interactive tweet embed) and embed.music.apple.com
(Apple Music player) to the webview frame-src.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend webview frame-src for the newly-added inline media players: www.tiktok.com,
www.dailymotion.com, geo.dailymotion.com, streamable.com, player.twitch.tv,
clips.twitch.tv, open.spotify.com, w.soundcloud.com.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lotus Chat now plays YouTube/Vimeo links inline; the webview CSP frame-src was
'self' blob: openstreetmap only, which blocked the embed players. Add
youtube-nocookie.com, youtube.com, and player.vimeo.com.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Windows CI failed: PROPERTYKEY is not in Win32::UI::Shell::PropertiesSystem.
Use the ready-made PKEY_AppUserModel_ID constant from Win32::Storage::Enhanced
Storage (same module jumplist.rs uses for PKEY_Title, feature already enabled)
instead of hand-rolling the PROPERTYKEY — drops the GUID::from_u128 dependency.
Also simplify IPersistFile::Save's fremember arg (it's bool, not BOOL).
All windows-crate symbols now verified against windows-docs-rs (PKEY_AppUser
Model_ID / IPersistFile::Save / SetCurrentProcessExplicitAppUserModelID /
CreateToastNotifierWithId).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
on_open_url and the argv fallback can both forward the same launch matrix: URL
on cold start, navigating the room twice. forward_deeplink now drops a repeat of
the same URL within ~1s (plain std Mutex/Instant — no windows-crate surface).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WinRT rich toast (reply box P5-41, click-to-open-room P5-35) was inert on
Windows: CreateToastNotifier needs the process under an AppUserModelID mapped to
a Start-Menu shortcut, and none was registered — so it errored and silently fell
back to the plain plugin toast.
New native/aumid.rs (Windows-only; no-op elsewhere), called first in
native::setup: (1) SetCurrentProcessExplicitAppUserModelID("LotusGuild.LotusChat"),
(2) install/refresh a Start-Menu "Lotus Chat.lnk" carrying PKEY_AppUserModel_ID,
reusing jumplist.rs's IShellLinkW + IPropertyStore + PROPVARIANT + IPersistFile
pattern (best-effort; failures logged + swallowed). toast.rs now binds the
notifier via CreateToastNotifierWithId(AUMID).
CI-compile-only (windows runner); runtime needs a Windows build to confirm the
toast shows a reply box + opens the room. windows-crate 0.61 symbol assumptions
(IPersistFile, SetCurrentProcessExplicitAppUserModelID, PROPERTYKEY,
GUID::from_u128, CreateToastNotifierWithId) validated by CI — all mirror existing
jumplist.rs usage where possible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- D1 (HIGH): Linux no-sleep was completely non-functional — the zbus
ScreenSaver inhibit was bound to a function-local D-Bus connection dropped on
return, so the screensaver service auto-released it instantly. Keep a
long-lived Connection in managed state (InhibitState { conn, cookie }) so the
same connection holds Inhibit and issues UnInhibit; created once, reused.
- D3: tray "Do Not Disturb" desynced from the web manualDndAtom after any reload
(custom-chrome toggle / logout) — the atom is in-memory and reset while the
tray stayed checked. Added TrayDndState + a get_tray_dnd command so the web
hook re-hydrates the atom on mount.
- D5: install_update now calls app.restart() after a successful install so the
new version actually runs (Linux AppImage kept running the old binary; the UI
hung on "installing").
CI-compile-verified (windows + linux). Web-side wiring (get_tray_dnd query,
updater terminal state) landed on cinny:lotus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rounds out the native app on Linux (Windows features kept; macOS stays no-op):
- power.rs: no-sleep during calls on Linux via a zbus org.freedesktop.ScreenSaver
Inhibit/UnInhibit (cookie held in ScreenSaverInhibit managed state).
- set_badge_count: Linux launcher badge via the Unity
com.canonical.Unity.LauncherEntry.Update D-Bus signal (best-effort; app_uri
= cinny.desktop per Tauri's mainBinaryName naming).
- tauri-plugin-autostart registered (+ autostart:allow-enable/disable/is-enabled
capabilities); web toggles it from Settings.
- Tray "Do Not Disturb" CheckMenuItem → emits lotus-dnd-changed to the web,
which ORs it into the notification quiet-gate.
zbus 5 (Linux target dep; blocking-api default). CI-compile-verified
(windows+linux); reviewer confirmed no build-breakers. Runtime to check on
Linux: DND toggle polarity, badge .desktop id.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`app.security.__csp_notes` failed `tauri.conf.json` schema validation
("Additional properties are not allowed") on BOTH platforms before any
compile. JSON can't hold comments and Tauri forbids extra keys, so the
rationale lives here instead:
CSP rationale (audit 2026-07): tightened from the fully-open policy.
- 'unsafe-eval' MUST stay: the native→web bridge (forward_deeplink /
emit_to_web) uses window.eval, governed by page CSP; also covers crypto wasm.
- The sha256 hash allowlists the single inline `window.global ||= window;`
shim in cinny's index.html (~line 96). If that snippet or its indentation
changes, recompute the hash or the shim is silently blocked.
- connect-src / img-src / media-src keep http: (plain-http homeservers).
- Review-added: Google Fonts (VT323) + OpenStreetMap iframe (m.location).
- style-src keeps 'unsafe-inline' for React style attributes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Repairs the CI Windows compile (first build to reach the Rust after the web/
case-collision failures cleared): these two COM interfaces live in
windows::Win32::UI::Shell::Common (feature Win32_UI_Shell_Common), not
System::Com nor Shell. Added the feature; corrected the import.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
script-src drops unsafe-inline/blob/data/http/https (any-origin script exec is
gone); the single inline shim in index.html is hash-pinned; object-src 'none',
base-uri 'self'. Kept deliberately: 'unsafe-eval' (the window.eval native→web
bridge + crypto wasm), broad connect-src (arbitrary homeservers), http: in
img/media (plain-http homeservers), and review-added allowances for Google
Fonts (VT323) and the OpenStreetMap location iframe.
NEEDS RUNTIME SMOKE ON WINDOWS before release (CI can't catch CSP breakage):
boot, avatars/media, VT323 renders, location map embeds, calls connect, deep
links navigate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From the deep-audit wave (reviewer-verified: capability identifiers valid, no
removed-crate references, GDI free ordering correct):
- Removed 8 never-registered plugins (clipboard-manager, fs, shell, http,
process, os, dialog, global-shortcut) from Cargo.toml AND their capability
grants (shell:allow-execute, unscoped fs writes, http:default, …) — verified
the web never invokes any of them. A latent RCE-class surface is gone.
- on_new_window: only http/https/mailto reach the OS opener (file:///custom
schemes previously bypassed the opener capability scope entirely).
- set_badge_count: freed hdc + hdc_screen on all three GDI error paths
(leaked per badge update in a long-running tray app).
- 8s reveal failsafe gated by an AtomicBool: no longer re-shows a window the
user closed to tray; page-load reveal now fires once only (logout reloads
don't re-surface a tray-hidden window); recovery for a missed page-load
event preserved.
- toast.rs: store pruned on Activated too + capped at 20 (was unbounded).
- Startup no longer panics when the bundled icon is missing (tray skipped
gracefully); msSmartScreenProtection no longer disabled (throttling
disables kept); rust-version corrected to 1.77.2.
- release.yml update-manifest: fails on empty signatures (was: could publish
a manifest that traps Windows users in a failed-update loop); partial-
failure window documented. Deleted the stale upstream tauri.yml workflow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Four unresolved-import/type errors from the release build (first real compile):
- toast.rs: generic IMap moved to the windows-collections crate; read the reply
from the ValueSet returned by UserInput() directly (HasKey/Lookup are exposed
on the class).
- jumplist.rs: PROPVARIANT lives in Win32::System::Com::StructuredStorage (not
windows::core); IObjectArray/IObjectCollection in Win32::System::Com (not
UI::Shell); PKEY_Title in Win32::Storage::EnhancedStorage (feature added);
build the title PROPVARIANT via From<&str> (VT_LPWSTR).
- smtc.rs: event registrations return a plain i64 token in windows 0.61 (the
EventRegistrationToken newtype is gone).
- thumbbar.rs: HICON was imported inside the fn body but used in its signature —
fully qualify the return type.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause: lib.rs applies a Mica backdrop to the main window at startup;
set_custom_chrome then stripped the frame with set_decorations(false), and
Mica + frameless is a broken combination on Windows (DWM backdrop glitches
the whole surface).
- set_custom_chrome: clear_mica() before undecorating, re-apply_mica() when
restoring the native frame; set_shadow(true) so the frameless window keeps
its drop shadow + resize borders.
- window-state plugin: exclude StateFlags::DECORATIONS — the chrome toggle
owns the decorated flag; restoring a saved decorated=false at startup would
recreate the Mica-on-frameless glitch before the web side loads.
Pairs with the web-side TitleBar drag fix (explicit window_start_drag on
mousedown instead of data-tauri-drag-region) in the cinny repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add WebView2 additional_browser_args to disable Chromium background throttling
(--disable-background-timer-throttling / -renderer-backgrounding /
-backgrounding-occluded-windows) so the existing JS Matrix /sync loop and
notifications keep running full-speed when the app is closed to the tray, instead
of standing up a second headless Rust sync client. Tauri's default WebView2 args
are preserved (setting this overrides them). Windows/WebView2 only; does not block
system sleep (that's P5-46, calls-only). CI Windows compile pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- toast.rs: Windows.UI.Notifications rich toast (reply input + Send action);
in-process Activated event → emit lotus-notification-activate {path} (click) /
lotus-notification-reply {roomId,text}. Falls back to tauri-plugin-notification
(WinRT error / non-Windows). The NOTIFICATION_BRIDGE now routes notifications
carrying a roomId (tag) to show_rich_toast. Features: UI_Notifications,
Data_Xml_Dom, Foundation_Collections.
- focus_assist.rs: SHQueryUserNotificationState poll thread → emit
focus-assist-changed {active} on QUNS_QUIET_TIME/PRESENTATION/D3D_FULLSCREEN/BUSY.
No new Cargo features.
CI Windows compile pending (no local Rust toolchain). Runtime caveat: WinRT toasts
need a Start-menu shortcut + matching AppUserModelID (org.lotusguild.lotus-chat);
without it CreateToastNotifier errors and the code falls back to the plugin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a `native/` module system (each feature = its own module exposing
`#[tauri::command]`s + optional `setup`; `emit_to_web` pushes DOM CustomEvents to
the web like `forward_deeplink`). Wired into generate_handler! + native::setup;
windows-crate feature union added to Cargo.toml.
- power.rs (P5-46): SetThreadExecutionState held on the main thread while a call
is active; released on end. Cross-platform (no-op off Windows).
- jumplist.rs (P5-36): ICustomDestinationList "Recent Rooms" of IShellLink tasks
launching the exe with a matrix: arg (existing deep-link handler opens the room).
- thumbbar.rs (P5-44): ITaskbarList3 ThumbBar Mute/Deafen/End (GDI HICONs) + a
window subclass catching THBN_CLICKED → emit thumbbar-action.
- smtc.rs (P5-43): WinRT SystemMediaTransportControls via GetForWindow; ButtonPressed
→ smtc-action; call-state command. (Experimental for a non-media app.)
- network.rs (P5-49): INetworkListManager poll thread → emit network-changed.
- chrome.rs (P5-47): cross-platform window-control commands + set_custom_chrome
(set_decorations) for the opt-in TDS titlebar.
NOT compile-verified locally (no Rust/Windows toolchain on the dev box) — this is
for the CI Windows compile pass (GitHub test.yml / Gitea windows runner). Expect a
possible fixup round (windows-crate feature/namespace paths, e.g. subclass APIs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The injected notification bridge defined `permission` as a getter-only property.
When the notification plugin / a polyfill assigned `Notification.permission`, it
threw "Cannot set property permission of function TauriNotification ... which has
only a getter" at page load. Add a no-op setter so it still reads 'granted' but
assignment can't crash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CreateDIBSection does not guarantee zeroed memory. Uninitialized bytes
with non-zero RGB but zero alpha were getting alpha=255 set by the
existing pixel loop, causing a black square around the badge circle.
Zeroing with write_bytes before GDI drawing ensures only explicitly
painted pixels are opaque.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>