mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-24 15:21:21 +00:00
## Summary System notifications on Windows: everything about them in one PR. 1. **Windows data profiles never showed system notifications** (`fix(windows): show system notifications from every data profile`). Tauri runs each data profile, such as **dev**, under its own AppUserModelID (`…client.scope.s<hash>`), and its notifications carry that ID; Windows shows a desktop app's notifications only under an ID it knows, and the installer's Start menu shortcut carries the stable ID alone. Windows recorded every notification (100 on the affected machine) and displayed none. Every launch now registers the running ID under `HKCU\Software\Classes\AppUserModelId\<identifier>` (`DisplayName` CodeNomad, `IconUri` the bundled web app icon `resources\server\public\pwa-192x192.png`, which the packaged-resource smoke check requires; `windows_notification_identity.rs`). Electron always uses the stable ID. 2. **The permission was lost at every restart on Windows.** The notification plugin's page script reports *denied* there until the page requests permission, though Windows never asks, so Settings asked again after each restart. The check now asks the native plugin command, which answers granted on desktop: granted once, kept. 3. **Notifications were held back whenever the window was visible** (`fix(notifications): hold system notifications back only while focused`). With **Notify when the app is focused** off (the default), an open window stays visible behind other applications or on another screen, so nothing was sent unless CodeNomad was minimized. Delivery is now skipped only while the window is visible and focused (`document.hasFocus()`). The setting's subtitle and the Settings help say *focused* in all ten languages; `dev-docs/SETTINGS.md` records the rule. Out of scope by decision: several CodeNomad windows open on the same project may each send the event's notification (as they already do when the setting is on), and focus inside a browser preview counts as leaving the page. ## Validation - Affected machine: no profile notification showed before; after registering its ID as above, a notification sent under it showed in the notification centre under CodeNomad. - `cargo test --bins -- windows_notification_identity` (writes the key, reads back both values). - `tests/browser/system-notification-focus.test.ts`: real routed notification from a visible page; nothing while focused, one once focus is lost, one while focused with the setting on; red on the previous visibility check. - `npm run typecheck --workspace @codenomad/ui`, `npm run test:help`.
9.1 KiB
9.1 KiB
Settings and Preferences
Settings rows and actions, the native Preferences window and its project context, Providers/Web search, Stats, notifications and bug reports.
Agent conventions
- Settings → Providers separates Models and Web search into two shared
.settings-disclosuregroups (no icons): Models starts open, Web search folded, and the folded Web search mounts nothing, so its connection metadata is read only once opened. Each starts with a provider selector, Connect action and configured-provider list; Web search then shows its global default and project override. Reuse provider rows, selectors, inputs and action styles; do not add an API-key disclosure or a separate card design. Search-only integrations stay out of the Models group. Web search configuration lives entirely here. Read connection metadata while this Settings surface is visible, never credential secret values; keep API-key entry explicit and clear drafts on context changes. Use the authorized Global/Project document operations shared with plugin controls andopencode/native-setting-document.tsfor targeted JSONC edits. Preserve unrelated fields, native discovery roots, WSL atomic writes and conflict/connection/deletion fences. Group/search styling lives instyles/components/websearch-settings.css; credentials use native integration methods and remain global. - Stats preferences explicitly aggregate the entire connected OpenCode service via
opencode/service-usage.ts. Reserve the Usage label for the provider subscription panel. Native project IDs span independent clones, so never label nativesession.statsas physical-folder/Git-family scoped. Keep connection/workspace fences,tools: "none"and periods bounded to 366 days; never load transcripts for statistics or imply billing/quota precision. Last snapshots and period choices restore through backend-ownedstate.ui.serviceUsage(eight each/1 MiB, validated service/channel/folder identity), never a Preferences writer to main-window client state or a workspace-UUID cache. Native aggregation must not await optional discovery/cache reads; cached responses cannot replace fresh data. Backend authorized admission order, not completion order or client time, chooses the period across windows; optional publication is bounded and shutdown-fenced. Keep last-known-owner totals explicitly stale through disconnect/errors and revoke them on verified owner/discovery changes. Read while mounted and document-visible; hide cancels queued/trailing demand, activation revalidates once and terminal hints use the shared activity cooldown. Never treat document visibility as native minimize/occlusion detection or equate blur with hidden. Seedev-docs/CACHE_REFRESH_CONVENTIONS.mdfor the remaining native scope. Styles live instyles/components/usage-dashboard.css. - Settings rows keep only a title unless a caption is essential; secondary explanations go behind
components/settings/settings-info-tip.tsx(a focusable "i" with a hover/focus tooltip that a tap toggles on touch screens, a span so it never becomes a row label's control or toggles it), styled instyles/components/settings-info-tip.css. Behavior settings opt in withinfoKeyinstead ofsubtitleKey. Folded settings groups use the one.settings-disclosure(styles/components/settings-disclosure.css; a summary may hold an inline heading that keeps the summary's look): Missions gathers every agent/model choice under "Agents and models" with the current request in its summary, and Advanced ends with the environment-variable editor folded under a single title. - Settings update/install actions (OpenCode and Info), OpenCode's post-update service restart, and Remote Control start/pairing-link actions use the shared
.selector-button-primaryaccent treatment. The sharedOpenCodeSetupPanelkeeps those same primary actions in startup recovery. Remote Control's header refresh is an icon-only.window-icon-button, retaining its localized accessible name, tooltip, disabled state and loading spinner. - The native Preferences window never owns client state. Its General startup card (
settings/opener-startup-state-settings-card.tsx) targets the exact local window that opened Preferences: the host records that opener and an epoch (ElectronPreferencesWindowRegistry, TauriPreferencesWindow), forwards only the fixed commands inlib/native/startup-state-command.tsto that renderer, which runs them through its ownstores/client-statetransitions. Mutations carry the displayed epoch and degrade to a read after the opener changes. Startup state is per window: when the opener is unknown (Preferences restored at launch) or closed, the host adopts the most recently used local window as the opener (startupStateTargetId+adoptOpener/startup_state_target+set_opener), rotating the epoch so a change prepared for another window is only a read. The card re-reads a few times while the adopted window starts and stays disabled only when no local window can answer. - Native Preferences resolves its project context through
settings/preferences-project-context.tsbefore Providers, Missions or Stats read project data. Backend workspace IDs are ephemeral: use the exact requested record while it exists; otherwise bind only one unambiguous exact-folder match from the backend's metadata-only inventory. Requests persist the opened backend-host folder asworkspacePath, separate from the selected nativelocation.directory(WSL/worktrees); keep the native directory for requests and drop any stale native workspace selector when rebinding. Requests without the backend folder match only their exact directory, never through guessed WSL translations or ancestors. A starting, missing or ambiguous project stays inactive. Read only on section demand, matching workspace lifecycle events or reconnect; never hydrate other workspaces, discover worktrees, create projects or poll. Record all deletion tombstones before filtering by the current binding so late inventory responses cannot publish deleted candidates, and revalidate when deletion resolves an ambiguity. Invalidation and newer native requests fence late reads, explicitly deleted occurrences never turn into sibling tabs, and rebinding persists through the existing Preferences request queue without resetting the section or newer scrolling. The shared renderer and request contract cover both Electron and Tauri; startup regressions live intests/browser/preferences-startup.test.ts. - Settings → Info's Report a bug opens the
bug_report.ymlissue form with its text fields prefilled by id (BUG_REPORT_FIELDSinlib/bug-report.ts). When the template changes, keep those ids and their input/textarea types in sync:lib/bug-report.test.tschecks the repository's template. The section warms a bounded read of the form published on the default branch; the click never waits for it (WebKit drops user activation after about 1 s), using the settled assessment or else the form while a background read retries. Only a proven incompatible form (missing, unparsable, prefilled field removed or retyped) falls back to a blank issue carrying the same Markdown report; an unreachable read keeps the form. Never prefill listeners, addresses, paths, credentials or conversation data. - Notification icon badges mirror the existing per-window bell/history unread count, never session status or native OS notification delivery. The browser favicon uses a dot; native Electron/Tauri hosts aggregate live primary-window counts and clear contributions on reload/close, keeping preferences and preview guests outside the badge authority. Keep UI projection in
lib/notification-icon-badge.tsand host dispatch inlib/native/notification-badge.ts; do not add discovery scans or polling. - Notifications have one routing table (
lib/notification-routing.ts): everyshowToastNotificationdeclares a category, and Settings → Notifications chooses per category On screen / History / System (components/settings/notification-routing-table.tsx,styles/components/notification-routing.css). The history, and therefore the icon badges, holds only what is routed there; errors always reach the screen and the history;transientlive feedback (progress, retry countdowns, "up to date") stays on screen only. The routing table is the only system-notification control: checking a System box requests permission, and savedosNotificationsEnabled,notifyOnNeedsInputandnotifyOnIdlepreferences only seed the routing until it is first saved. The bell (tab bar, or title bar in the macOS desktop app) is the history, independent of system notifications. System delivery is held back only while the window has focus (visible anddocument.hasFocus()), unless Notify when the app is focused (osNotificationsAllowWhenVisible) is on; a window left open behind another application or on another screen still notifies (tests/browser/system-notification-focus.test.ts). Classify new notifications by what the user would want to find later, not by their source file; a warning that must be seen once by default (the incomplete-recovery warning) uses a category that shows a bubble (projects), neverneedsInput, whose default is System only.