Skip to content

feat(browser): PR 10 — Web Push (browser-scope v1)#311

Merged
jaylfc merged 14 commits into
masterfrom
feat/browser-pr-10-web-push
May 5, 2026
Merged

feat(browser): PR 10 — Web Push (browser-scope v1)#311
jaylfc merged 14 commits into
masterfrom
feat/browser-pr-10-web-push

Conversation

@jaylfc
Copy link
Copy Markdown
Owner

@jaylfc jaylfc commented May 5, 2026

Summary

Closes the BrowserApp v2 epic by laying the Web Push platform primitive scoped to BrowserApp v1 triggers. After this PR: cross-device push works for the three v1 trigger sites (chat / drive-started / download-finished), and the primitive is reusable for the cross-app aggregator follow-up.

What landed

Backend

  • pywebpush dependency + VAPID keypair bootstrap (vapid.py, persisted to data/vapid.pem mode 0600, lazy idempotent generation)
  • push_subscriptions store methods (upsert/list/delete/delete-by-endpoint with multi-user isolation)
  • HTTP CRUD: GET /push/vapid-public-key (public), POST /push/subscribe, GET /push/subscriptions (strips p256dh/auth keys), DELETE /push/subscriptions/{device_id}, GET/PUT /push/mutes
  • push.send(user_id, payload, *, devices, store, vapid) with 30/min/user sliding-window rate limit, 410-Gone subscription cleanup, per-endpoint VAPID claims, 5s send timeout
  • push_mutes table keyed on (user_id, agent_id, kind) where kind ∈ {chat, drive-started, download-finished}
  • VAPID singleton wired at app startup (app.state.vapid_keypair)

Service Worker (extends sw.js from PR 8 — no second SW)

  • push handler: parse JSON payload, fall back to generic title/body, event.waitUntil(showNotification(...))
  • notificationclick handler: focus matching same-origin window + postMessage taos-push:click, fallback openWindow('/')

Frontend

  • browser-push-api.ts — six wrappers
  • browser-push-bootstrap.ts — idempotent subscription flow (gated by Notification.permission, persistent device_id in localStorage, re-POSTs existing subscriptions to refresh last_seen_at)
  • BrowserApp.tsx calls bootstrap after SW registration (fire-and-forget)
  • AgentPanel.tsx notifications section with three toggles per pinned agent, optimistic update + revert on failure
  • SettingsPanel.tsx tri-state Enable Notifications button (granted / denied / default)
  • TabRenderer.tsx postMessages taos-copilot:tab-focus to iframes on active-tab change

Triggers (v1)

  • Chat: agent chat-message broadcast in copilot_agent_ws.py_maybe_send_chat_push (mute + tab-focus gated)
  • Drive-started: first drive op of a session in copilot_agent_ws.py_maybe_send_drive_push
  • Download-finished: successful stream completion in download.py → push.send via asyncio.create_task
  • Tab-focus tracking via CopilotHub.set_focused_tab / get_focused_tab, tab-focus WS event, parent-shell postMessage from TabRenderer.tsx

Out of scope (deferred)

  • Cross-app push aggregator (notifications.py) — Foundations brainstorm
  • DND mode + channel categories
  • Native iOS/Android wrapper integration
  • Cluster-wide rate limiting (currently single-process in-memory)
  • VAPID key rotation UX

Test Plan

  • Backend: pytest tests/routes/desktop_browser/ → 504/504 passing
  • Frontend: npm test -- --run → 775/775 passing, tsc --noEmit clean
  • Multi-user isolation: every endpoint and trigger derives user_id from session; never from request body
  • p256dh/auth_key NEVER returned from list endpoint; NEVER logged
  • VAPID private key file mode 0600, atomic via O_EXCL
  • 410-Gone cleanup verified
  • Rate limit verified (30/min/user, FIFO denial counted as failed without upstream calls)
  • Service Worker: no clients.claim() (preserves PR 8 invariant)
  • Focus suppression: tab-id fallback when window_id unavailable at the agent WS

After this PR

This closes the BrowserApp v2 epic per spec §10:

After PR 10:  cross-device push (agent pings your phone).    (FOUNDATIONS PRIMITIVE)

Summary by CodeRabbit

  • New Features

    • Enable browser notifications in Settings to receive push notifications for chat messages, drive operations, and download completions.
    • Mute notifications per-agent and per-notification type directly in the Agent Panel.
    • Smart notification suppression: notifications won't fire when you're actively viewing the relevant browser tab.
  • Tests

    • Added comprehensive test coverage for push notification endpoints, subscriptions, mutes, and delivery.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Warning

Rate limit exceeded

@jaylfc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 46 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d0d3b2bc-205e-4acf-8b14-2eccf611e423

📥 Commits

Reviewing files that changed from the base of the PR and between 4141ca4 and 7d170fc.

📒 Files selected for processing (11)
  • desktop/src/apps/BrowserApp/TabRenderer.test.tsx
  • tests/routes/desktop_browser/test_push_routes.py
  • tests/routes/desktop_browser/test_push_subscriptions.py
  • tests/routes/desktop_browser/test_push_triggers.py
  • tinyagentos/app.py
  • tinyagentos/routes/desktop_browser/copilot_agent_ws.py
  • tinyagentos/routes/desktop_browser/copilot_ws.py
  • tinyagentos/routes/desktop_browser/download.py
  • tinyagentos/routes/desktop_browser/push_routes.py
  • tinyagentos/routes/desktop_browser/store.py
  • tinyagentos/routes/desktop_browser/vapid.py
📝 Walkthrough

Walkthrough

This PR introduces comprehensive browser push notification support across desktop and backend systems. It adds VAPID keypair management, push subscription persistence with muting controls, service-worker-based notification delivery, tab-focus tracking, and per-agent notification toggles in the UI. The system integrates with chat, drive-started, and download-finished events to trigger notifications when the user is not already viewing the relevant tab.

Changes

Browser Push Notification System

Layer / File(s) Summary
Data & Persistence Schema
tinyagentos/routes/desktop_browser/schema.py, tinyagentos/routes/desktop_browser/store.py
New push_mutes table keyed by (user_id, agent_id, kind) stores mute state with muted_at timestamp. BrowserStore adds subscription upsert/list/delete APIs and mute state methods (set_push_mute, list_push_mutes, is_push_muted).
VAPID Key Management
tinyagentos/routes/desktop_browser/vapid.py, tinyagentos/app.py
New load_or_create_vapid_keypair(data_dir) function generates and persists P-256 VAPID keypairs to disk (mode 0600) on first use, with idempotent retrieval. App startup initializes the keypair and stores it in app.state.vapid_keypair.
Push Delivery Engine
tinyagentos/routes/desktop_browser/push.py
New send() coroutine manages delivery to subscribed devices with per-user sliding-window rate limiting (30/min), concurrent sending via pywebpush, and automatic subscription cleanup on HTTP 410 (gone). Aggregates results as {"sent", "failed", "removed"}.
REST Endpoints & Request Models
tinyagentos/routes/desktop_browser/push_routes.py
Registers unauthenticated VAPID public-key GET, authenticated CRUD endpoints for subscriptions (POST/GET/DELETE), and authenticated mute listing/setting endpoints (GET/PUT). Validates HTTPS endpoints and non-empty fields via Pydantic models.
Client Push API Wrappers
desktop/src/lib/browser-push-api.ts
New typed fetch wrappers for /api/desktop/browser/push/* endpoints. Exports PushSubscriptionInfo and PushMute interfaces; functions gracefully return [] on network errors or non-OK responses (except write operations which throw).
Client Push Bootstrap
desktop/src/lib/browser-push-bootstrap.ts
New bootstrapPushSubscription() handles: API support checks, stable device_id in localStorage, permission state detection without prompting, subscription creation/refresh via service worker, base64url encoding of P-256 keys, and error capture. Returns status object ("subscribed", "no-permission", "unsupported", or "error").
Notification Triggering - Chat & Drive
tinyagentos/routes/desktop_browser/copilot_ws.py, tinyagentos/routes/desktop_browser/copilot_agent_ws.py
New _maybe_send_chat_push() helper checks mute state and suppresses notifications when the user's focused tab matches the pinned agent's tab. CopilotHub tracks focused (window_id, tab_id) per user via set_focused_tab()/get_focused_tab(). Chat message ops and first drive ops trigger async push sends via asyncio.create_task.
Notification Triggering - Download
tinyagentos/routes/desktop_browser/download.py
Download endpoint now accepts optional window_id/tab_id parameters, tracks streaming success, and conditionally sends "download-finished" push notifications (guarded by per-agent mute check) after successful byte transfer.
Tab Focus Tracking
tinyagentos/routes/desktop_browser/copilot.js, desktop/src/apps/BrowserApp/TabRenderer.tsx, tinyagentos/routes/desktop_browser/copilot_ws.py
Frontend sends taos-copilot:tab-focus postMessages when the active tab changes, with window_id, tab_id, and focused flag. Copilot.js caches these values and broadcasts tab-focus events over WebSockets. Backend websocket loop updates CopilotHub.set_focused_tab() when events arrive.
Service Worker Push & Click Handling
tinyagentos/routes/desktop_browser/sw.js
New push event listener parses payload as JSON, applies defaults, and displays notification via showNotification(). New notificationclick listener closes notification, posts click data to a same-origin client, and focuses or opens a new window.
Browser UI Controls
desktop/src/apps/BrowserApp/SettingsPanel.tsx, desktop/src/apps/BrowserApp/AgentPanel.tsx, desktop/src/apps/BrowserApp/BrowserApp.tsx
SettingsPanel adds "Enable browser notifications" button that requests permission and calls bootstrapPushSubscription() on grant. AgentPanel adds per-pinned-agent notification-type toggles (Chat, Drive, Download) wired to mute API with optimistic UI and rollback on failure. BrowserApp startup calls bootstrapPushSubscription() after service-worker registration.
Module Wiring & Auth
tinyagentos/routes/desktop_browser/__init__.py, tinyagentos/auth_middleware.py
Added side-effect import of push_routes to register endpoints. Exempted /api/desktop/browser/push/vapid-public-key from auth middleware to allow public key retrieval.
Dependencies
pyproject.toml
Added pywebpush>=1.14 for server-side web-push delivery.
Frontend Tests
desktop/src/apps/BrowserApp/*.test.tsx, desktop/src/lib/browser-push-*.test.ts
Comprehensive test coverage: AgentPanel.notifications.test.tsx validates mute toggles, loading state, optimistic updates, and rollback. AgentPanel.test.tsx updated to pass profileId to component. TabRenderer.test.tsx verifies tab-focus postMessage. browser-push-api.test.ts tests all API wrappers. browser-push-bootstrap.test.ts covers subscription lifecycle, permission states, and error handling. SettingsPanel.test.tsx validates notification permission request and bootstrap integration.
Backend Tests
tests/routes/desktop_browser/test_*.py
Comprehensive backend test suites: test_push_routes.py validates VAPID key, subscription CRUD, and multi-user isolation. test_push_subscriptions.py tests store upsert/list/delete with isolation and ordering. test_push_mutes.py tests mute state CRUD and isolation. test_push_send.py validates delivery, rate limiting, error handling, and device filtering. test_push_triggers.py covers chat/drive/download trigger logic, mute suppression, focus-aware suppression, and non-blocking behavior. test_vapid.py tests keypair generation, idempotency, and file permissions.

Sequence Diagram

sequenceDiagram
    participant User
    participant Client as Browser Client
    participant SW as Service Worker
    participant Server as Backend
    participant Store as Push Store
    participant Sender as pywebpush

    User->>Client: Click "Enable Notifications"
    Client->>Client: Request Notification.permission
    Client->>Server: bootstrapPushSubscription()
    Server->>Server: Load/Create VAPID keypair
    Client->>Client: Create service worker subscription
    Client->>Server: POST /subscribe with endpoint & keys
    Server->>Store: Upsert subscription
    Store-->>Server: ✓
    Server-->>Client: {"ok": true}

    User->>Client: Focus different tab
    Client->>SW: postMessage tab-focus
    SW->>Server: WebSocket: tab-focus event
    Server->>Server: CopilotHub.set_focused_tab()

    Client->>Server: Chat message on pinned agent
    Server->>Server: _maybe_send_chat_push()
    alt User not viewing that tab
        Server->>Store: is_push_muted(user, agent, "chat")?
        alt Not muted
            Server->>Sender: webpush(subscription, payload)
            Sender-->>SW: Push delivery
            SW->>SW: Show notification
            User->>User: Sees notification
        end
    else User already viewing tab
        Server->>Server: Suppress (focused_tab match)
    end

    User->>SW: Click notification
    SW->>Client: postMessage taos-push:click
    Client->>Client: Focus/open window
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

This PR introduces a substantial, multi-layer feature spanning frontend and backend with dense logic in push delivery (rate limiting, concurrent sending, error handling), complex triggering logic (focus-aware suppression, optimistic UI with rollback), and deep integration with WebSockets, service workers, and storage. The changes are heterogeneous across many files but tightly coupled semantically, requiring careful review of coordination between client subscription, server delivery, storage persistence, and UI state management. The comprehensive test suites mitigate some risk but the sheer scope and novelty of push infrastructure demands careful scrutiny.

Possibly related PRs

  • jaylfc/tinyagentos#300: Introduces the BrowserStore foundation and desktop-browser route scaffolding; this PR extends that skeleton with push-specific schema, store methods, and endpoint registration.
  • jaylfc/tinyagentos#307: Modifies CopilotHub and copilot_ws.py for agent routing and session state; this PR further extends those same classes with focused-tab tracking and chat push helpers.
  • jaylfc/tinyagentos#302: Bootstraps BrowserStore and other browser-infrastructure initialization in app.py lifespan; this PR adds VAPID keypair initialization in the same startup sequence.

Poem

🐰 Hops bounce through the wires
Notifications leap from server to screen,
Tab-focused whispers keep interruptions lean.
A push ecosystem blooms—subscriptions bloom!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(browser): PR 10 — Web Push (browser-scope v1)' clearly summarizes the main change: adding Web Push functionality to the browser application.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/browser-pr-10-web-push

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented May 5, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (11 files changed since last review)
  • desktop/src/apps/BrowserApp/TabRenderer.test.tsx - Test cleanup
  • tests/routes/desktop_browser/test_push_routes.py - Removed unused fixture
  • tests/routes/desktop_browser/test_push_subscriptions.py - Added endpoint deduplication tests
  • tests/routes/desktop_browser/test_push_triggers.py - Improved exception handling in tests
  • tinyagentos/app.py - Centralized VAPID keypair loading
  • tinyagentos/routes/desktop_browser/copilot_agent_ws.py - Added background task lifecycle management
  • tinyagentos/routes/desktop_browser/copilot_ws.py - Added stale focus state cleanup
  • tinyagentos/routes/desktop_browser/download.py - Added background task lifecycle management
  • tinyagentos/routes/desktop_browser/push_routes.py - Removed module-level cache, use app state
  • tinyagentos/routes/desktop_browser/store.py - Added endpoint deduplication logic
  • tinyagentos/routes/desktop_browser/vapid.py - Improved atomic file writing with race condition handling

Reviewed by grok-code-fast-1:optimized:free · 170,000 tokens

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@desktop/src/apps/BrowserApp/TabRenderer.test.tsx`:
- Around line 334-335: Remove the dead-variable declarations in the test: delete
the unused consts messages and origPostMessage from TabRenderer.test.tsx (the
lines declaring const messages: any[] = []; and const origPostMessage =
window.postMessage.bind(window);). Ensure no other code references messages or
origPostMessage before removing, and run the tests to confirm no regressions.

In `@tests/routes/desktop_browser/test_push_subscriptions.py`:
- Line 75: Replace blocking time.sleep calls inside async test coroutines with
non-blocking sleeps: add an import for the asyncio module at the top of the test
file and change each use of time.sleep(1) found inside async def tests to use an
awaited asyncio sleep (i.e., await asyncio.sleep(1)) so the event loop is not
blocked.

In `@tests/routes/desktop_browser/test_push_triggers.py`:
- Around line 544-547: In the test cleanup blocks where a background coroutine
variable named "task" is awaited, remove the broad exception tuple and only
catch asyncio.CancelledError (i.e., change "except (asyncio.CancelledError,
Exception): pass" to "except asyncio.CancelledError: pass") so other exceptions
(test failures) are not swallowed; update both occurrences in
tests/routes/desktop_browser/test_push_triggers.py that use await task in
teardown/cleanup.

In `@tinyagentos/routes/desktop_browser/copilot_agent_ws.py`:
- Around line 226-240: The spawned tasks created with
asyncio.create_task(_maybe_send_chat_push(...)) and
asyncio.create_task(_maybe_send_drive_push(...)) can raise exceptions after
scheduling that aren’t caught by the surrounding try/except; wrap the call body
in a small coroutine wrapper (e.g., async def _safe_send_chat_push(...): try:
await _maybe_send_chat_push(...); except Exception: _logger.warning("chat push
failed", exc_info=True)) and schedule the wrapper instead (or attach a done
callback that logs exceptions from the Task); update both call sites that
currently call asyncio.create_task(...) to schedule the safe wrapper so
exceptions from push.send (or other awaits inside _maybe_send_* functions) are
caught and logged.

In `@tinyagentos/routes/desktop_browser/copilot_ws.py`:
- Around line 168-181: The focused-tab cache (_focused_tabs) currently never
gets cleared so suppression can persist after a tab disconnects; modify the code
to track liveness and clear stale focus entries: add a
clear_focused_tab(user_id, window_id, tab_id) method (or a general
clear_focused_tab(user_id)) and call it from the iframe disconnect handler when
the disconnecting (window_id, tab_id) matches the stored tuple, or instead store
a last_seen timestamp in _focused_tabs and update _maybe_send_chat_push to
ignore entries older than a short TTL; update set_focused_tab, get_focused_tab
and any disconnect handler that handles iframe websocket close to remove the
entry when appropriate, and ensure _maybe_send_chat_push uses the new liveness
check rather than relying on a permanent tuple.

In `@tinyagentos/routes/desktop_browser/push_routes.py`:
- Around line 22-26: The code uses a module-global _vapid_cache which leaks
VAPID keys across FastAPI app instances; replace this with per-app state:
remove/stop using the global _vapid_cache and instead store the keypair on the
FastAPI app state (e.g., app.state.vapid_keypair). Change _get_vapid to accept
the FastAPI app (or a Request) and check app.state.vapid_keypair, calling
load_or_create_vapid_keypair(data_dir) only if absent, then return that value;
make the analogous change for the other cached accessor (the one on lines 34-39)
so all VAPID accessors read/write app.state.vapid_keypair instead of the
module-global.

In `@tinyagentos/routes/desktop_browser/store.py`:
- Around line 1036-1068: In upsert_push_subscription, ensure you deduplicate by
endpoint: before inserting, delete any other rows for the same user that have
the same endpoint but a different device_id (e.g. execute "DELETE FROM
push_subscriptions WHERE user_id = ? AND endpoint = ? AND device_id != ?" with
params (user_id, endpoint, device_id)), then proceed with the existing INSERT
... ON CONFLICT(user_id, device_id) ... (so created_at is preserved on
conflict). Use the existing function upsert_push_subscription and table/columns
push_subscriptions, user_id, device_id, endpoint to locate where to add the
DELETE.

In `@tinyagentos/routes/desktop_browser/vapid.py`:
- Around line 36-48: The code that atomically creates the VAPID PEM using
os.open(..., os.O_EXCL) can raise FileExistsError if another process created the
file between pem_path.exists() and os.open; update the block that uses Vapid01,
pem_path and os.open to catch FileExistsError around the os.open call: on
FileExistsError fall back to loading the existing key via
Vapid01.from_pem(pem_path.read_bytes()), otherwise proceed to write pem_bytes to
the newly-opened fd (ensuring fd is closed in finally) and set vapid = the new
Vapid01 instance; this prevents the process from crashing on concurrent startup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 13c8d037-114b-41ab-baf0-23acd2b37dca

📥 Commits

Reviewing files that changed from the base of the PR and between 61292f1 and 4141ca4.

📒 Files selected for processing (32)
  • desktop/src/apps/BrowserApp/AgentPanel.notifications.test.tsx
  • desktop/src/apps/BrowserApp/AgentPanel.test.tsx
  • desktop/src/apps/BrowserApp/AgentPanel.tsx
  • desktop/src/apps/BrowserApp/BrowserApp.tsx
  • desktop/src/apps/BrowserApp/SettingsPanel.test.tsx
  • desktop/src/apps/BrowserApp/SettingsPanel.tsx
  • desktop/src/apps/BrowserApp/TabRenderer.test.tsx
  • desktop/src/apps/BrowserApp/TabRenderer.tsx
  • desktop/src/lib/browser-push-api.test.ts
  • desktop/src/lib/browser-push-api.ts
  • desktop/src/lib/browser-push-bootstrap.test.ts
  • desktop/src/lib/browser-push-bootstrap.ts
  • pyproject.toml
  • tests/routes/desktop_browser/test_push_mutes.py
  • tests/routes/desktop_browser/test_push_routes.py
  • tests/routes/desktop_browser/test_push_send.py
  • tests/routes/desktop_browser/test_push_subscriptions.py
  • tests/routes/desktop_browser/test_push_triggers.py
  • tests/routes/desktop_browser/test_vapid.py
  • tinyagentos/app.py
  • tinyagentos/auth_middleware.py
  • tinyagentos/routes/desktop_browser/__init__.py
  • tinyagentos/routes/desktop_browser/copilot.js
  • tinyagentos/routes/desktop_browser/copilot_agent_ws.py
  • tinyagentos/routes/desktop_browser/copilot_ws.py
  • tinyagentos/routes/desktop_browser/download.py
  • tinyagentos/routes/desktop_browser/push.py
  • tinyagentos/routes/desktop_browser/push_routes.py
  • tinyagentos/routes/desktop_browser/schema.py
  • tinyagentos/routes/desktop_browser/store.py
  • tinyagentos/routes/desktop_browser/sw.js
  • tinyagentos/routes/desktop_browser/vapid.py

Comment thread desktop/src/apps/BrowserApp/TabRenderer.test.tsx Outdated
original_last_seen = rows[0]["last_seen_at"]

# Advance wall clock by at least 1 second so last_seen_at changes
time.sleep(1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate blocking sleeps in async tests.
rg -n "time\\.sleep\\(" tests/routes/desktop_browser/test_push_subscriptions.py -C 2

Repository: jaylfc/tinyagentos

Length of output: 300


🏁 Script executed:

cat -n tests/routes/desktop_browser/test_push_subscriptions.py | head -100

Repository: jaylfc/tinyagentos

Length of output: 4205


🏁 Script executed:

tail -n +200 tests/routes/desktop_browser/test_push_subscriptions.py | head -30

Repository: jaylfc/tinyagentos

Length of output: 679


🏁 Script executed:

head -20 tests/routes/desktop_browser/test_push_subscriptions.py

Repository: jaylfc/tinyagentos

Length of output: 510


Use async sleep in async tests to avoid blocking the event loop.

Lines 75 and 206 call time.sleep(1) inside async def tests. This blocks the event loop; use await asyncio.sleep(...) instead.

Add the import:

import asyncio

Replace both calls:

await asyncio.sleep(1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/routes/desktop_browser/test_push_subscriptions.py` at line 75, Replace
blocking time.sleep calls inside async test coroutines with non-blocking sleeps:
add an import for the asyncio module at the top of the test file and change each
use of time.sleep(1) found inside async def tests to use an awaited asyncio
sleep (i.e., await asyncio.sleep(1)) so the event loop is not blocked.

Comment thread tests/routes/desktop_browser/test_push_triggers.py
Comment thread tinyagentos/routes/desktop_browser/copilot_agent_ws.py Outdated
Comment thread tinyagentos/routes/desktop_browser/copilot_ws.py
Comment thread tinyagentos/routes/desktop_browser/push_routes.py Outdated
Comment thread tinyagentos/routes/desktop_browser/store.py
Comment thread tinyagentos/routes/desktop_browser/vapid.py Outdated
@jaylfc
Copy link
Copy Markdown
Owner Author

jaylfc commented May 5, 2026

@coderabbitai full review please — addressed all 8 actionable findings from your initial pass at 7d170fc:

  1. Fire-and-forget push tasks now tracked in a module-level set with done-callbacks logging exceptions
  2. _focused_tabs cleared on iframe disconnect via new clear_focused_tab_if_matches
  3. VAPID cache moved from module-global to request.app.state (per-app, removed leakage)
  4. Push subscription dedup by endpoint within upsert_push_subscription
  5. FileExistsError race in vapid.py handled with fallback-load
  6. Dead vars in TabRenderer test removed
  7. Narrowed except in test_push_triggers cleanup blocks to asyncio.CancelledError only

Backend 290/290, frontend 775/775, tsc 0 errors all green.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Tip

For best results, initiate chat on the files or code changes.

@jaylfc Thanks for the detailed summary of all the fixes! I'll kick off a full re-review now, taking into account the 7 addressed findings (fire-and-forget task tracking, _focused_tabs lifecycle cleanup, VAPID cache scoped to app.state, subscription dedup by endpoint, FileExistsError fallback in vapid.py, dead var removal in TabRenderer test, and narrowed asyncio.CancelledError except clauses).

[full_review]
User feedback: Re-review after addressing 8 actionable findings from initial pass at 7d170fc: (1) fire-and-forget push tasks tracked in module-level set with done-callbacks, (2) _focused_tabs cleared on iframe disconnect via clear_focused_tab_if_matches, (3) VAPID cache moved to request.app.state, (4) push subscription dedup by endpoint in upsert_push_subscription, (5) FileExistsError race handled in vapid.py, (6) dead vars removed from TabRenderer test, (7) narrowed except to asyncio.CancelledError in test_push_triggers. All tests green: Backend 290/290, frontend 775/775, tsc 0 errors.

@jaylfc jaylfc merged commit 1e17272 into master May 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant