Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ section to the released version and opens a fresh empty one (see `docs/RELEASING
- **Five more UI locales with follow-system language (#984).** The webview ships Spanish, Italian, Polish, Brazilian Portuguese (`pt`, with the regional tag `pt-BR` resolving to it longest-tag-first) and Dutch alongside en/de/fr — the shared `Dict` type keeps all eight at key parity, and the Rust tray/menu tables carry the same eight so native surfaces render the same language. First-run detection prefix-matches every supported code (so `es-ES` no longer falls back to English), and a Settings "Follow the system language" toggle re-resolves from the OS language on every boot and on change. `en` stays the documented fallback for unknown tags. Provenance: the five new locales are model-written with human review pending (see CONTRIBUTING) — usable, but no native-speaker read yet.

### Changed
- **Tray module split into one file per concern (#756).** `src-tauri/src/tray.rs` becomes `src-tauri/src/tray/` with `mod.rs` (menu ids, `handle_menu_event`, `setup_tray`, `rebuild_tray_menu`/`update_tray_menu`, badge, icon), `cache.rs` (device/queue caches, throttle, fetch mode), `dedup.rs` (state snapshot, playback-mode atoms, window visibility), `snooze.rs` (snooze/profile/manual-status builders and writers), `devices.rs` (device/volume/seek selection and builders), `actions.rs` (player actions, repaint/refresh, write lock), plus `testkit.rs` (shared source-scan helpers). All statics stay in place; the 35 tray tests move next to the arms they guard with identical assertions. No behaviour change.
- **Settings split into per-card components, slice 1 (#750-PARTIAL).** New `src/lib/components/settings/` owns the repeated card shell (`SettingsCard`: `.card pane-card` + section header + optional Reset) plus the first four extracted cards — `RulesCard` (quiet hours, track rules, work-hours import, dry-run tester, manual-status pair), `LoggingCard` (`logging.*`), `BackupCard` (export/import), `ShortcutsCard` (capture, validation, registration status). Each takes a `$bindable()` config slice plus `onreset`/`onchange` callbacks; `Settings.svelte` keeps `localConfig`/`isDirty`/save-discard/`pendingNav` and the `.actions` footer. Shared `normalizeShortcutReason`/`shortcutReasonLabel`/`validateShortcutBinding` move to `src/lib/utils/shortcuts.ts`. Remaining: Spotify, Teams, Presence, StatusFormat, Polling, Notifications, Appearance, Profiles, Updates cards. No markup or computed-value change.
- **Rust emit payloads are ts-rs-typed structs (#762).** New `src-tauri/src/events.rs` owns every structured Tauri event payload (`ErrorEvent` + `ErrorSeverity`/`ErrorRecovery`, `PresencePaused`, `PlaybackStateChanged`, `PresenceGated`, `PresenceUpdated`, `PresenceCleared`, `PresenceAvailabilityUpdated`, `PreferredPresenceUpdated`, `SyncStopped`, `ManualStatusUpdated`, `SpotifySecretConflict`, `ConfigChanged`, `TeamsReconnectRequired`, `AuthPersistWarning`) with `#[ts(export)]` into `src/lib/types-generated/`; all emit sites build the structs instead of inline `json!`, the tray parses `playback-state-changed` through the shared type, and `src/lib/types.ts` re-exports the generated types (the hand-written `ErrorEventPayload` and Dashboard-local `ManualStatusUpdatedPayload` are deleted). `test_event_payload_shapes_are_pinned` now pins all three canonical payloads including the previously unasserted `error` envelope with exact JSON. No wire-shape change.
- **Polling sync flag hardcodes its Acquire/Release ordering (#759).** `Polling::is_syncing()` and `Polling::set_syncing(value)` no longer take an `Ordering` parameter — the Acquire load / Release store pair lives inside the type, so no call site can silently drop the poller-exit → tray/UI happens-before chain with a weaker ordering. `try_claim()` keeps its AcqRel CAS. No behaviour change.
Expand Down
12 changes: 6 additions & 6 deletions docs/STATE-OF-FEATURES.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/architecture/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ PresenceJam-Desktop/
│ │ ├── profanity.rs # Curated profanity word list
│ │ ├── spotify.rs # PKCE OAuth client + Web API (ts-rs TS)
│ │ ├── teams.rs # Device-code + MS Graph (ts-rs TS)
│ │ ├── tray.rs # System tray + dedup snapshot (native CheckMenuItem Play/Pause + live tooltip, v4.0)
│ │ ├── tray/ # System tray menu (mod + cache/dedup/snooze/devices/actions, #756) (native CheckMenuItem Play/Pause + live tooltip, v4.0)
│ │ ├── updater_bg.rs # Background update checks + stage_deferred_update / PendingUpdate (v4.0)
│ │ ├── diagnostics.rs # Telemetry-free get_diagnostics_snapshot (v4.0)
│ │ ├── menu.rs # macOS / Windows app menu bar
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ graph TD
SpotifyAPI["spotify.rs<br/>Spotify Web API (Authorization Code + PKCE)"]
TeamsAPI["teams.rs<br/>Microsoft Graph (device code)"]
Keychain["keychain.rs<br/>OS keychain wrapper<br/>(Secret Service on Linux)"]
Tray["tray.rs / menu.rs<br/>system tray + app menu"]
Tray["tray/ / menu.rs<br/>system tray + app menu"]
end

subgraph Storage ["Storage"]
Expand Down
8 changes: 4 additions & 4 deletions docs/architecture/tray-and-shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ The UI is localized to **English, German, and French** via the i18n barrel
Detached Logs/Settings windows own independent locale instances, so a `storage`
listener converges them on the main window's write (the same pattern as the
`#423` theme listener, with a same-value guard that stops a write loop) (#620).
- **Native surfaces (4.7.0, #674):** the tray menu (`tray.rs`) and the native
- **Native surfaces (4.7.0, #674):** the tray menu (`tray/`) and the native
application menu (`menu.rs`) render from a Rust string table
(`src-tauri/src/i18n.rs`: one `Strings` field per literal, with `EN`/`DE`/`FR`
tables). An unknown `locale` falls back to English and is logged, and a Rust
parity test fails when the three tables drift apart or a label is hard-coded
back into `tray.rs`/`menu.rs`.
back into `tray/`/`menu.rs`.
- Known limitation: Rust-side error strings surfaced through `invoke()`
rejections and event payloads remain English, as does the app name.

Expand Down Expand Up @@ -195,7 +195,7 @@ matrix builds **aarch64 macOS only** — Intel Macs never receive updates

## System Tray (v4.6)

`tray.rs` builds the menu natively, from in-process state:
`tray/mod.rs` builds the menu natively, from in-process state:

- **Shuffle / Repeat are real toggles (#582).** Both are
`CheckMenuItemBuilder` items; Shuffle's mark reads `LAST_SHUFFLE_STATE` and
Expand All @@ -222,7 +222,7 @@ matrix builds **aarch64 macOS only** — Intel Macs never receive updates
Devices/Queue listings; those are display fetches, not playback commands.
- **Refresh cadence:** the polling loop calls `update_tray_menu` after every
iteration, behind a dedup key built by `tray_snapshot_for`
(`src-tauri/src/tray.rs::tray_snapshot_for`) from `(is_syncing, is_window_visible,
(`src-tauri/src/tray/dedup.rs::tray_snapshot_for`) from `(is_syncing, is_window_visible,
"artist|title|is_playing", shuffle, repeat, snooze deadline + minute bucket)`.
The mode atoms and the snooze key are in the key on purpose: a Shuffle/Repeat
change made from another Spotify client has to force a rebuild, otherwise the
Expand Down
9 changes: 7 additions & 2 deletions src-tauri/src/i18n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! `tables_carry_an_identical_field_set` parses the struct declaration out of
//! this file and fails when a table misses a field or falls out of order, and
//! `no_user_visible_literal_stays_hard_coded` scans production literals in
//! `tray.rs`/`menu.rs` and fails when user-visible copy is not in these tables.
//! `tray/`/`menu.rs` and fails when user-visible copy is not in these tables.
//! Deliberate exceptions, mirroring the frontend's documented limitation:
//! error strings surfaced through `invoke()` rejections or event payloads
//! (e.g. `playback-error`) stay English, and the app name ("PresenceJam",
Expand Down Expand Up @@ -1311,7 +1311,12 @@ mod tests {
fn no_user_visible_literal_stays_hard_coded() {
let mut offenders = Vec::new();
for (module, src) in [
("tray.rs", include_str!("tray.rs")),
("tray/mod.rs", include_str!("tray/mod.rs")),
("tray/cache.rs", include_str!("tray/cache.rs")),
("tray/dedup.rs", include_str!("tray/dedup.rs")),
("tray/snooze.rs", include_str!("tray/snooze.rs")),
("tray/devices.rs", include_str!("tray/devices.rs")),
("tray/actions.rs", include_str!("tray/actions.rs")),
("menu.rs", include_str!("menu.rs")),
] {
offenders.extend(
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ mod tests {
// (`tray::handle_menu_event`) — this handler keeps only the
// window-menu-only arms, so the graceful-shutdown routing is pinned
// from the dispatcher side instead.
let src = include_str!("tray.rs");
let src = include_str!("tray/mod.rs");
let sig_idx = src
.find("pub fn handle_menu_event(")
.expect("handle_menu_event must exist");
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/polling/poll_once.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11871,7 +11871,7 @@ mod tests {
sync_source.contains("crate::polling::run_oneshot("),
"refresh_status must route its one-shot through run_oneshot so the pause applies (#793)"
);
let tray_source = include_str!("../tray.rs");
let tray_source = include_str!("../tray/actions.rs");
assert!(
tray_source.contains("crate::polling::run_oneshot("),
"the tray catch-up must route its one-shot through run_oneshot so the pause applies (#793)"
Expand Down
7 changes: 6 additions & 1 deletion src-tauri/src/redact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ mod tests {
"commands/spotify_auth.rs",
include_str!("commands/spotify_auth.rs"),
),
("tray.rs", include_str!("tray.rs")),
("tray/mod.rs", include_str!("tray/mod.rs")),
("tray/cache.rs", include_str!("tray/cache.rs")),
("tray/dedup.rs", include_str!("tray/dedup.rs")),
("tray/snooze.rs", include_str!("tray/snooze.rs")),
("tray/devices.rs", include_str!("tray/devices.rs")),
("tray/actions.rs", include_str!("tray/actions.rs")),
("config.rs", include_str!("config.rs")),
("teams.rs", include_str!("teams.rs")),
("spotify.rs", include_str!("spotify.rs")),
Expand Down
Loading
Loading