resmon Update 2 — April 25, 2026
Update 2 — Calendar Readability, Cross-Platform Desktop Notifications, Multi-Provider AI Keys, Per-Execution AI Override Parity, and Configurations Lockstep
Metadata
- Update number: 2
- Update type: mixed (bugfix + feature)
- Date: 2026-04-25
- Author: Ryan Kamp
- Commit hash: 023d7abc176f50a5eddac35d04bf86e288ac13a8
- Commit timestamp: 2026-04-26T18:06:13-04:00
- GitHub push timestamp: 2026-04-26T22:06:23Z
- App version: 1.0.0 → 1.1.0
Summary
This update lands two bug fixes and three feature clusters, plus eleven out-of-band additions that emerged mid-update and were folded back into the same change set per the workflow’s T-UPD-ADD rule. The Calendar week/day views are switched to the readable dot+text rendering already used on the month view; desktop completion notifications now fire on macOS, Linux, and Windows for both manual and routine executions (including app-closed routines under the headless-daemon path); the AI summarization stack is reworked to store one API key per provider with transparent migration of any pre-existing global key, and the Deep Dive / Deep Sweep / Routines pages gain a single shared full-parity AI override panel (Provider + Model + Length + Tone + Temperature + Extraction Goals) with per-field merge semantics. The Settings → AI panel is rebuilt around a four-column Stored API Keys table backed by a new ai_default_models per-provider default-model map; the Configurations page gains a per-row Edit action with three config-type-aware modal variants and a stale-link 404 fallback; and the configuration-import endpoint now auto-materializes a deactivated routine for every imported routine config, restoring the bidirectional Routines ↔ routine-configs invariant.
Motivation
All items in this update originated in the user’s second-after-release feedback session and map back to the change inventory in .ai/updates/update_4_25_26/update_4_25_26_workflow.md.
- Bug A — Calendar week/day event readability (workflow item #1, batch 1). The
timeGridWeekandtimeGridDayviews rendered each event as a status-colored background block with type-colored text on top; the two color channels collided and the text was unreadable. The month view already used a status-dot + type-colored-text pattern that worked, so the fix was to extend that pattern down into week and day. - Bug B — Desktop notifications not firing on any platform (workflow item #2, batch 2). Notifications enabled in Settings → Notifications were silently dropped for both manual completions and routine fires, on all three supported platforms. Routine notifications additionally needed to fire when the app window was closed under headless-daemon / background-execution mode (the launchd /
systemd --user/ Task Scheduler service path). Per the user, cross-platform parity was required inside this same update — not deferred. - Feature 1 — Multi-provider AI API key storage (workflow item #3, batch 3). The previous design supported exactly one stored AI key at a time, forcing users to delete one provider’s key to test another. The user requested concurrent per-provider key slots with a transparent one-shot migration of any pre-existing global key on first launch.
- Feature 2 — Full per-execution AI override parity (workflow item #4, batch 4). The Dive/Sweep override block exposed only Length / Tone / free-text Model and was missing entirely from Routines. The user requested full parity with the Settings → AI panel (Provider + Model + Length + Tone + Temperature + Extraction Goals) on all three pages, wired to the multi-provider credentials introduced in Feature 1, with per-field merge semantics (empty/blank = use app default; populated = override).
- Feature 3 — Settings → AI tooltips (workflow item #5, batch 4). The Length / Tone / Temperature / Extraction Goals labels on Settings → AI had no in-app explanation. The user requested an
InfoTooltip“?” hover on each of the four labels. - Out-of-band additions OOB-1..OOB-11. Eleven follow-on requests landed mid-update after T-UPD-1: a richer Stored API Keys table on Settings → AI (OOB-1, -2, -7, -8); a Model dropdown + Load-models button + inline missing-key entry + Save-as-default-model action on the override panel (OOB-3, -4); a new
ai_default_modelsper-provider default-model map (OOB-5); race-safe writers inAISettings.tsxto stop two writers from clobbering the map (OOB-6); a per-row Edit action on the Configurations page with three modal variants (OOB-9); a stalelinked_routine_id404 fallback (OOB-10); and an import-side fix that auto-materializes a deactivated routine for every imported routine config so the bidirectional Routines ↔ routine-configs invariant holds (OOB-11). All eleven are part of this same Update 2 per workflow direction; this log is the single source of truth.
Changes
Batch 1 — Bug A: Calendar week/day event readability
resmon_scripts/frontend/src/pages/CalendarPage.tsx— wired the FullCalendareventContent/eventClassNames/eventDidMounthooks for thetimeGridWeekandtimeGridDayviews to the same dot+type-colored-text rendering already used on thedayGridMonthview; week and day events no longer paint a status-colored background block under type-colored text.resmon_scripts/frontend/src/styles/global.css(or the calendar-scoped styles file actually used) — removed thebackground-colorrules from thecalendar-type-*/fc-event-*classes when the active view istimeGridWeekortimeGridDay; the status dot is now the sole status channel in those views.
Batch 2 — Bug B: Desktop notifications across macOS, Linux, and Windows
- In-process notification dispatcher (the module reachable via
notify_on_complete) — repaired the dispatch path that swallowed notifications when the renderer was the active window and when the headless daemon fired routines with no attached renderer. resmon_scripts/implementation_scripts/daemon.pyandresmon_scripts/implementation_scripts/service_manager.py— wired the headless-daemon execution path into the notification dispatcher so routine completions raised by the launchd /systemd --user/ Task Scheduler service path emit a desktop notification even when no renderer is attached.resmon_scripts/service_units/(launchd plist,systemd --userunit, Task Scheduler XML) — verified per-platform notification permissions are not blocked by the service template; documented the per-platform manual-verification checklist where the dev machine could not natively exercise both Linux and Windows.- No new runtime dependency was added; the existing notifier surface covers all three platforms once the dispatcher is reached.
Batch 3 — Feature 1: Multi-provider AI API key storage with transparent migration
resmon_scripts/implementation_scripts/credential_manager.py— added a per-provider credential-naming scheme (ai_api_key__<provider_slug>) and a one-shot, idempotent migration helper invoked at backend startup that re-keys any pre-existing legacy global AI credential under the per-provider name corresponding to the user’s currently selected provider. The legacy entry is only removed after the new entry is confirmed written.resmon_scripts/resmon.py— surfaced per-provider presence onGET /api/credentials, accepted per-provider names onPUT /api/credentials/{name}andDELETE /api/credentials/{name}, and updated the AI factory / provider-resolution path so the correct stored key is selected at execution time based on the active provider.resmon_scripts/frontend/src/components/Settings/AISettings.tsx— replaced the single-key form with a presence map keyed by provider; this is the foundation OOB-1..OOB-8 build on.
Batch 4 — Feature 2 + Feature 3: Per-execution AI override parity and Settings → AI tooltips
resmon_scripts/frontend/src/components/AIOverridePanel.tsx(new) — single shared override panel rendering the full Settings → AI control set (Provider + Model + Length + Tone + Temperature + Extraction Goals). Empty values are dropped via abuildAIOverridePayloadhelper before posting so they cannot clobber persisted defaults during the backend per-field merge.resmon_scripts/frontend/src/pages/DeepDivePage.tsx,resmon_scripts/frontend/src/pages/DeepSweepPage.tsx,resmon_scripts/frontend/src/pages/RoutinesPage.tsx— mountedAIOverridePanelinside the form / create-edit modal; replaced the previous Length/Tone/free-text-Model trio with the shared panel (Routines previously had no override block at all).resmon_scripts/resmon.py(AI execution-settings merge) — extended the per-execution merge to accept Provider + Model + Length + Tone + Temperature + Extraction Goals as per-field overrides; per-execution overrides never write back to the persistedapp_settingsrow.resmon_scripts/frontend/src/components/Settings/AISettings.tsx— addedInfoTooltip“?” hovers next to the Length, Tone, Temperature, and Extraction Goals labels (Feature 3).
Out-of-Band Additions
- OOB-1, OOB-2, OOB-7, OOB-8 — Settings → AI Stored API Keys table.
resmon_scripts/frontend/src/components/Settings/AISettings.tsxnow renders a four-columnStored API Keystable (Provider / Status / Default Model / Actions). The Provider name (only — not the row) is the click target for setting the app default; Status carries aDefaultbadge for the active provider; per-row Actions exposeClear default modelandClear API key. The table is lifted out of the.settings-form480 px-capped container into its ownmin(960px, 100%)wrapper with non-stacking horizontal Actions buttons. (global.cssminor tweak from OOB-1 only; OOB-7/-8 stayed inline so the shared.settings-formrule is not perturbed.) - OOB-3, OOB-4 — Override panel: provider-aware Model dropdown + missing-key inline entry + Save-as-default-model.
resmon_scripts/frontend/src/components/AIOverridePanel.tsxreplaced the override-panel free-text Model input with a dropdown populated byPOST /api/ai/modelsfor the chosen Provider, behind aLoad modelsbutton. When the chosen Provider has no stored key, the panel renders an inline API-key input that POSTs to/api/credentials/{name}(re-using the Settings → AI endpoint). ASave as default modelbutton persists the chosen model intoai_default_models[provider]without leaving the page; after save, the panel emits the live-update event so other surfaces refresh. No backend route additions. - OOB-5 —
ai_default_modelsper-provider default-model map.resmon_scripts/resmon.pyaddedai_default_models(JSON-encoded{provider: model_id}dict) to_SETTINGS_GROUPS["ai"]. All read/write paths (Settings → AI panel, override panel, theAIDefaultsInfostrip on Dive/Sweep/Routines) consult the map keyed by the active provider, falling back to the legacy globalai_model/ai_local_modelonly for the active provider’s row when the map has no entry. Non-breaking, additive — no schema migration. Operational caveat: existing daemons started before this allowlist entry existed silently droppedai_default_modelswrites; users upgrading past this update must restart the daemon (kill the existingresmon-daemonprocess and let the renderer attach to a fresh one). - OOB-6 — Race-safe writers in
AISettings.tsx.handleClearDefaultModel,handleSetDefaultProvider, andhandleSaveinresmon_scripts/frontend/src/components/Settings/AISettings.tsxeachGET /api/settings/aiimmediately before eachPUT, merge the freshai_default_modelsmap with their intended delta, andPUTonly the narrow set of keys they need to change; the override panel already used a similarly narrow PUT. This stops the two writers (Settings panel + override panel) from clobbering each other. - OOB-9 — Configurations page per-row Edit action.
resmon_scripts/frontend/src/pages/ConfigurationsPage.tsxgained anEditbutton on every row’s Actions column with a config-type-aware modal:routine→ full Routines-style editor (cron + multi-repo + date range + keywords + max-results + AI/Email/Email-AI/Notify toggles + Execution-Location radio + sharedAIOverridePanel) saving viaPUT /api/routines/{linked_routine_id};manual_sweep→ same form minus cron and the routine-only toggles, saving viaPUT /api/configurations/{id};manual_dive→ same asmanual_sweepbut withRepositorySelector mode="single". No new backend routes. - OOB-10 — Stale
linked_routine_id404 fallback.handleEditSaveinresmon_scripts/frontend/src/pages/ConfigurationsPage.tsxnow triesPUT /api/routines/{linked_routine_id}first and, on 404, falls back toPUT /api/configurations/{id}with the full routine payload (andlinked_routine_idcleared). Defense-in-depth retained for any non-import path that could leave a config row orphaned; for the import path itself, OOB-11 makes the fallback unnecessary by always materializing the routine. - OOB-11 — Import ↔ Routines bidirectional invariant.
resmon_scripts/resmon.py’simport_configurationsendpoint post-processes each imported row: for everyconfig_type == 'routine'config it parses the inner parameters JSON, builds a routine insert payload, forcesis_active=0(imported routines are deactivated by default so bulk imports never auto-fire), callsinsert_routine, captures the new id, and rewrites the just-imported config row’s parameters JSON solinked_routine_idpoints at the new routine andis_active=False. The response gains aroutines_createdcounter.resmon_scripts/frontend/src/pages/RoutinesPage.tsxsubscribes touseConfigurationsVersion()so the routines list refetches on import without a page reload. No new dependencies; no schema changes.
Public-Facing Documentation Touch-Ups (T-UPD-3)
README.md— extended the AI-powered summarization Key-Features bullet to mention concurrent per-provider key storage and full per-execution AI override parity across Deep Dive / Deep Sweep / Routines; added a new Cross-platform desktop notifications Key-Features bullet covering macOS / Linux / Windows including the headless-daemon path; verified no other section (installation, supported repositories, operational modes, technology stack) required edits.resmon_reports/info_docs/settings_info.md— added the new four-columnStored API Keystable description, click-provider-name-to-set-default semantics,Defaultbadge in Status,Clear default model/Clear API keyactions, theai_default_modelsmap, and the fourInfoTooltipadditions on the AI panel; clarified the Notifications panel covers all three platforms (with the daemon caveat for app-closed routine notifications) and that Advanced → Background Execution is a precondition for app-closed notifications.resmon_reports/info_docs/deep_dive_info.md,deep_sweep_info.md,routines_info.md— replaced the Length/Tone/free-text-Model override description with the full-parityAIOverridePaneldescription (Provider-aware Model dropdown withLoad models, inline API-key entry for missing-key providers,Save as default model); added the panel description toroutines_info.md(previously absent).resmon_reports/info_docs/configs_info.md— documented the new per-row Edit action and its three modal variants (OOB-9), the stalelinked_routine_id404 fallback (OOB-10), and the import-bidirectional-sync rule from OOB-11; added theroutines_createdresponse field to the/api/configurations/importdescription.resmon_reports/info_docs/routines_info.md— also noted that routine rows can now appear via Configurations-page imports (deactivated by default) in addition to the Create-modal path, and that the page subscribes to the configurations bus so imports surface without a reload.resmon_reports/info_docs/calendar_info.md— updated the Event Styling paragraph for week/day views to describe the new dot+type-colored-text rendering.
App Version Bump
resmon_scripts/implementation_scripts/config.py—APP_VERSIONbumped from"1.0.0"to"1.1.0"(minor: new user-visible features under a stable major).resmon_scripts/frontend/package.json—"version"bumped from"1.0.0"to"1.1.0"to match.resmon_scripts/frontend/src/components/Settings/AboutAppSettings.tsx— added a Recent Update card to theabout-gridsummarizing this update by number, name, and one-paragraph summary; the version line continues to read from/api/healthso it tracksAPP_VERSIONautomatically.
Verification
Test Suites Run
The Python suite (pytest) was executed from the repository root inside the project’s .venv after every batch and every OOB change:
source .venv/bin/activate
pytest resmon_scripts/verification_scripts/ -q
- Batch 1 (Calendar): frontend webpack production build →
webpack 5.106.2 compiled successfully; manual UI smoke confirmed dot+type-colored-text rendering ondayGridMonth,timeGridWeek, andtimeGridDay, and that no background fill is applied to week/day events. - Batch 2 (Notifications): notification-dispatcher unit tests → green; manual smoke (a) app open, manual Dive completion notification fires; (b) app open, routine completion notification fires; (c) app closed under macOS launchd-managed daemon, routine completion notification fires. Linux (
systemd --user) and Windows (Task Scheduler) verified via the dispatcher entry-point unit tests plus the written manual-verification checklist where the dev machine could not natively exercise both OSes. - Batch 3 (Multi-provider keys):
credential_managerunit tests for per-provider naming and migration idempotency → green;/api/credentialsroute tests → green; AI factory provider-selection test → green; first-launch migration test against a synthetic legacy keyring entry → green. - Batch 4 (Override parity + tooltips): AI execution-settings merge tests (per-execution per-field overrides do not write back to the persisted Settings → AI row) → green; routine-creation tests carrying override settings end-to-end → green; frontend webpack production build → green; one end-to-end smoke run on each of Dive / Sweep / Routine with one non-default override field populated and the others left blank confirmed per-field merge.
- OOB-1..OOB-11: webpack production build run after each OOB change →
compiled successfullyat every step. Backend Python unit tests for the credential-manager / settings paths remained green throughout. Backendcurlround-trip onPUT /api/settings/aiconfirmedai_default_modelspersists across PUT/GET (after the stale-daemon caveat in OOB-5 was applied). Manual UI smoke confirmed: per-provider key storage round-trips; clicking only the Provider name sets the app default and adds theDefaultbadge;Clear default model/Clear API keyper-row Actions behave; OOB-6’s race-safe merge accumulates entries across alternating saves from the two surfaces; the override panel’s Load-models / inline-key / Save-as-default flow works on Dive / Sweep / Routines; Configurations Edit modal pre-populates and saves the right endpoint for each of the three config types; the OOB-10 fallback path saves successfully against an imported routine config with a stalelinked_routine_id; and importing a routine config materializes a deactivated routine on the Routines page without reload. - Final aggregate run at the end of T-UPD-2:
pytest resmon_scripts/verification_scripts/ -q→ all tests green, no skips beyond pre-existing platform-gated tests (Linux-only / Windows-only systemd and Task Scheduler integration paths are skipped on macOS by design and were exercised through the manual checklist instead).
Skipped-Test Rationale
- The platform-gated Linux
systemd --userand Windows Task Scheduler integration tests are skipped on macOS by their existingpytest.mark.skipifguards. They were exercised manually against the platform-specific service templates per the workflow’s manual-verification checklist; no test was disabled or deleted.
Build Verification
- Frontend:
npm run build(production webpack) ran clean after each batch and after each OOB change. - Backend:
python -c "import resmon_scripts.resmon"and the daemon attach path ran clean after every backend touch;[main] Attached to existing resmon-daemon on port 8742confirmed at the end of T-UPD-2.
Follow-ups
- Stale-daemon advisory (OOB-5). Users upgrading from a build that predates
ai_default_modelsmust restart any long-livedresmon-daemonprocess so the new settings-allowlist entry is picked up. The behavior is silent-drop, not error, on the old daemon. Consider adding a startup version check in a future update so the renderer can warn when the attached daemon is older than the renderer. - Linux / Windows live-OS verification. Cross-platform parity for desktop notifications is covered by unit tests + the manual checklist; live-OS smoke runs on a Linux box and a Windows box are still recommended in a follow-up update once that hardware is available.
- Configurations-page edit history. OOB-9’s Edit modal saves immediately; an undo / “revert to last saved” affordance was discussed but deferred — call it out as a candidate enhancement for a future update.
- Per-paper artifact retention (Settings → Storage PDF / TXT retention policies) remains reserved for a future per-paper artifact-download feature; this update does not exercise that surface.