Skip to content

feat(browser): PR 9 — bookmarks + site permissions + downloads (backends)#309

Merged
jaylfc merged 5 commits into
masterfrom
feat/browser-pr-9-downloads-bookmarks-permissions
May 5, 2026
Merged

feat(browser): PR 9 — bookmarks + site permissions + downloads (backends)#309
jaylfc merged 5 commits into
masterfrom
feat/browser-pr-9-downloads-bookmarks-permissions

Conversation

@jaylfc
Copy link
Copy Markdown
Owner

@jaylfc jaylfc commented May 5, 2026

Summary

Three browser conveniences delivered as backends + the highest-impact frontend (bookmark star toggle in URL bar). Frontend management UX (BookmarksBar, SitePermissionsPanel) is deferred to follow-ups since the data model + APIs land here and the existing AgentCapabilitiesPanel pattern from PR 7 already proves the management surface works.

What ships

  1. Bookmarks: GET/POST/DELETE /api/desktop/browser/bookmarks with per-(user, profile) keying, info-hide DELETE, opaque IDs. URL-bar star button toggles bookmark for current tab; cache populated on mount via listBookmarks.
  2. Site permissions: new site_permissions table keyed on (user_id, profile_id, host_pattern, permission) with state ∈ {allow, deny}. Permissions: notifications / clipboard-read/write / geolocation / camera / microphone. check_site_permission reuses the same host_pattern matching as agent capabilities (* / *.example.com / exact). HTTP CRUD endpoints + 35 store/route tests.
  3. Downloads: streaming GET /api/desktop/browser/download endpoint. Auth + per-hop SSRF (matches extract.py post-fix) + cookie jar (so authenticated downloads work). Inferred filename from URL path; Content-Disposition: attachment; filename="…"; filename*=UTF-8''… so the browser shows save dialog. Path-traversal sanitisation on caller-supplied filename.

Files (8 changed, ~1900 lines)

Backend (7 files): new bookmark_routes.py + site_permission_routes.py + download.py; extensions to store.py (4 bookmark methods + 4 site-permission methods); schema.py (new site_permissions table); __init__.py registrations.

Frontend (3 files): new browser-bookmarks-api.ts; modified AddressBar.tsx (star toggle + Star icon, hidden on about:blank); extended tests.

Test plan

  • Backend: pytest tests/routes/desktop_browser/ -q → 427 passed
  • Frontend: cd desktop && npx vitest run → 715 passed across 103 files
  • TypeScript: cd desktop && npx tsc --noEmit → zero errors

Atomic invariants

  • Multi-user + multi-profile isolation across all three subsystems
  • DELETE returns 204 unconditionally (info-hide)
  • ValueError → 400 at HTTP boundary
  • Per-hop SSRF re-validation on download redirects (5-hop cap)
  • Filename sanitisation: os.path.basename + control-char/quote stripping

Deferred to follow-ups

  • BookmarksBar frontend component (chips below tab strip) — backend complete
  • SitePermissionsPanel in Settings (table view of grants with revoke) — backend complete; frontend pattern established by PR 7's AgentCapabilitiesPanel
  • Permission prompt modal (when proxied page calls e.g. Notification.requestPermission()) — pattern matches PR 7 capability flow
  • Real FilesApp integration of downloads — separate brainstorm (file system writes)
  • Bookmark folders / drag-reorder / import-export
  • Download history panel + resume

Spec / plan references

  • Spec: `docs/superpowers/specs/2026-05-03-browser-app-v2-design.md` §10 (PR 9 row), §3 (data model)
  • Plan: `docs/superpowers/plans/2026-05-05-browser-app-v2-pr-9-downloads-bookmarks-permissions.md`

Summary by CodeRabbit

Release Notes

  • New Features
    • Added bookmarks feature: save and manage bookmarks with a star button in the address bar; bookmarks persist per profile
    • Added site permissions management: configure allow/deny permissions for websites on a per-profile basis
    • Added download functionality: download files through the browser with built-in security protections

jaylfc added 4 commits May 5, 2026 09:08
Adds GET/POST/DELETE routes for /api/desktop/browser/bookmarks with
per-user/per-profile isolation, 204 info-hide on DELETE, and 16 tests.
Adds GET /api/desktop/browser/download that proxies an upstream file
to the client as a browser save-dialog download.  Mirrors the security
pattern from proxy.py and extract.py: auth guard, SSRF validation on
the initial URL, manual redirect walk with per-hop SSRF re-validation
(blocks redirect-bypass attacks), and cookie-jar integration so
authenticated downloads from sites the user is logged into work.

Sets Content-Disposition: attachment with RFC 5987 filename* encoding.
Filename sanitisation strips path-traversal (os.path.basename) and
characters that would break Content-Disposition header parsing.
When no filename is provided it is inferred from the URL path.

16 new tests; full desktop_browser suite 427/427 green.
@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 31 minutes and 59 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: f8ef9473-c80e-4137-9a96-174e5de686a1

📥 Commits

Reviewing files that changed from the base of the PR and between a500228 and 4b37c48.

📒 Files selected for processing (6)
  • desktop/src/apps/BrowserApp/AddressBar.test.tsx
  • desktop/src/apps/BrowserApp/AddressBar.tsx
  • tests/routes/desktop_browser/test_download.py
  • tests/routes/desktop_browser/test_site_permissions.py
  • tinyagentos/routes/desktop_browser/download.py
  • tinyagentos/routes/desktop_browser/store.py
📝 Walkthrough

Walkthrough

The PR introduces bookmark management and site permission controls for a desktop browser, adding frontend UI in AddressBar with a star button, TypeScript API clients, FastAPI endpoints for bookmark CRUD (GET/POST/DELETE), site permission management (GET/POST/DELETE), and a download proxy endpoint with SSRF protection and redirect handling. Database schema and store methods support bookmarks indexed by (user_id, profile_id, bookmark_id) and site permissions by (user_id, profile_id, host_pattern, permission).

Changes

Bookmark and Permission Management

Layer / File(s) Summary
Database Schema
tinyagentos/routes/desktop_browser/schema.py
Adds site_permissions table with composite key (user_id, profile_id, host_pattern, permission) storing permission state and granted_at.
Store Implementation
tinyagentos/routes/desktop_browser/store.py
Adds bookmark CRUD methods (list_bookmarks_for_profile, create_bookmark, delete_bookmark, find_bookmark_by_url) and site permission methods (set_site_permission, list_site_permissions, remove_site_permission, check_site_permission) with pattern-matching logic for host validation.
Data Models & Routes
tinyagentos/routes/desktop_browser/bookmark_routes.py, site_permission_routes.py
Defines BookmarkRequest and SitePermissionRequest Pydantic models; implements GET/POST/DELETE endpoints for bookmarks (/api/desktop/browser/bookmarks) and site permissions (/api/desktop/browser/site-permissions) with user authentication and error handling.
Client API
desktop/src/lib/browser-bookmarks-api.ts
Exports Bookmark interface and typed wrappers (listBookmarks, addBookmark, removeBookmark) that wrap fetch calls with error recovery (fallback to []/null/false).
Frontend Component
desktop/src/apps/BrowserApp/AddressBar.tsx
Adds state (bookmarked, bookmarksRef cache) and effects to load bookmarks on mount and sync when active tab URL changes; renders a Star button for non-about:blank URLs with toggleBookmark() handler and aria-pressed state binding.
Tests
tests/routes/desktop_browser/test_bookmark_routes.py, test_site_permission_routes.py, tests/routes/desktop_browser/test_site_permissions.py, desktop/src/apps/BrowserApp/AddressBar.test.tsx, desktop/src/lib/browser-bookmarks-api.test.ts
Comprehensive test suites covering auth, CRUD operations, error handling, isolation across users/profiles, and pattern-matching semantics.
Router Registration
tinyagentos/routes/desktop_browser/__init__.py
Registers bookmark and site-permission route modules as side effects.

Download Endpoint with SSRF Protection

Layer / File(s) Summary
Download Endpoint
tinyagentos/routes/desktop_browser/download.py
Implements GET /api/desktop/browser/download that validates SSRF on initial URL and every redirect hop, manually walks redirects up to a limit, loads/persists browser cookies, sanitizes filenames, and streams the final response with Content-Disposition headers (including RFC 5987 UTF-8 encoding).
Download Tests
tests/routes/desktop_browser/test_download.py
Validates authentication, SSRF blocking (loopback, RFC1918, file://), content-type handling, Content-Disposition header behavior, filename sanitization, redirect logic with re-validation, and cookie forwarding.

Sequence Diagram

sequenceDiagram
    participant User
    participant AddressBar
    participant bookmarksAPI as Bookmarks API<br/>(Client)
    participant Server as FastAPI<br/>Backend
    participant DB as BrowserStore<br/>(SQLite)

    User->>AddressBar: Page loads / Tab changes
    AddressBar->>bookmarksAPI: listBookmarks(profileId)
    bookmarksAPI->>Server: GET /api/desktop/browser/bookmarks
    Server->>DB: list_bookmarks_for_profile(user_id, profile_id)
    DB-->>Server: [bookmark_id, url, title, created_at]
    Server-->>bookmarksAPI: {bookmarks: [...]}
    bookmarksAPI-->>AddressBar: Bookmark[] or []
    AddressBar->>AddressBar: Sync bookmarked state for active URL

    User->>AddressBar: Click star button
    alt Bookmarked
        AddressBar->>bookmarksAPI: removeBookmark(profileId, bookmarkId)
        bookmarksAPI->>Server: DELETE /api/desktop/browser/bookmarks/{id}
        Server->>DB: delete_bookmark(user_id, profile_id, bookmark_id)
        DB-->>Server: true
        Server-->>bookmarksAPI: 204 No Content
    else Not bookmarked
        AddressBar->>bookmarksAPI: addBookmark(profileId, url, title)
        bookmarksAPI->>Server: POST /api/desktop/browser/bookmarks
        Server->>DB: create_bookmark(user_id, profile_id, url, title)
        DB-->>Server: bookmark_id
        Server-->>bookmarksAPI: {bookmark_id: "..."}
    end
    bookmarksAPI-->>AddressBar: Success
    AddressBar->>AddressBar: Update bookmarked state + aria-pressed
Loading
sequenceDiagram
    participant User
    participant Frontend
    participant permAPI as Site Permissions API<br/>(HTTP)
    participant Server as FastAPI<br/>Backend
    participant DB as BrowserStore<br/>(SQLite)

    User->>Frontend: Navigate to settings / Change site permission
    Frontend->>permAPI: POST /api/desktop/browser/site-permissions
    permAPI->>Server: {profile_id, host_pattern, permission, state}
    Server->>DB: set_site_permission(user_id, profile_id, host_pattern, permission, state)
    DB->>DB: Validate permission + state
    DB->>DB: UPSERT into site_permissions
    DB-->>Server: Success / ValueError
    Server-->>permAPI: 200 {granted: true} or 400 {error}
    permAPI-->>Frontend: Confirmation

    Browser->>Server: Request to restricted host
    Server->>DB: check_site_permission(user_id, profile_id, host, permission)
    DB->>DB: Match host against patterns (* wildcard, *.domain)
    DB-->>Server: "allow" or "deny" or None
    Server->>Server: Enforce permission state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • jaylfc/tinyagentos#304: Both PRs modify the same AddressBar component and its tests, with overlapping code paths in AddressBar.tsx and AddressBar.test.tsx.
  • jaylfc/tinyagentos#300: The main PR implements bookmark APIs and routes that build on and extend the same desktop_browser backend components (store.py, schema.py, router) introduced in PR #300.
  • jaylfc/tinyagentos#302: Both PRs modify BrowserStore in tinyagentos/routes/desktop_browser/store.py; this PR adds bookmark and site-permission methods while the retrieved PR adds profile/cookie/proxy-related store behaviors.

Poem

🌟 A rabbit hops with glee,
Bookmarks saved for all to see!
Star buttons shine, permissions gleam,
Downloads protected by our SSRF dream.
Hop, hop, hop! No more lost ground,
Where every page can now be found. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.84% 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 accurately summarizes the main changes: adding bookmarks, site permissions, and downloads support to the browser backend.
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-9-downloads-bookmarks-permissions

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: Issues Resolved | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Resolved Issues
  • desktop/src/apps/BrowserApp/AddressBar.tsx line 135: Bookmark cache inconsistency across tabs - Fixed by adding cross-window sync via custom events.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
tinyagentos/routes/desktop_browser/download.py N/A Potential issue (from existing comments)
tinyagentos/routes/desktop_browser/store.py N/A Potential issue (from existing comments)
Files Reviewed (6 files)
  • desktop/src/apps/BrowserApp/AddressBar.test.tsx - No new issues
  • desktop/src/apps/BrowserApp/AddressBar.tsx - Previous warning resolved
  • tests/routes/desktop_browser/test_download.py - No issues
  • tests/routes/desktop_browser/test_site_permissions.py - No issues
  • tinyagentos/routes/desktop_browser/download.py - No issues
  • tinyagentos/routes/desktop_browser/store.py - No issues

Reviewed by grok-code-fast-1:optimized:free · 160,826 tokens

const id = await addBookmark(win.profileId, activeTab.url, activeTab.title || activeTab.url);
if (id) {
bookmarksRef.current.set(activeTab.url, id);
setBookmarked({ id });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: The bookmark cache (bookmarksRef) is local to each AddressBar component instance. This means toggling a bookmark in one tab will not update the star button state in other open tabs until the component remounts. This could lead to stale UI state across multiple tabs.

Consider making the bookmark cache global (e.g., via a React context or Zustand store) or refreshing the cache when switching tabs to ensure consistency.

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: 5

🧹 Nitpick comments (1)
desktop/src/lib/browser-bookmarks-api.test.ts (1)

135-144: ⚡ Quick win

Cover the real success status for DELETE here.

The route contract for bookmark deletion is 204 No Content, but this happy-path test only exercises 200. Adding a 204 success case will keep the client wrapper aligned with the backend behavior.

🤖 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 `@desktop/src/lib/browser-bookmarks-api.test.ts` around lines 135 - 144, Add a
test exercising the actual backend success status 204 for removeBookmark: update
or add an it block that mocks global.fetch to resolve with { ok: true, status:
204 } and asserts removeBookmark("profile-1", "bm-1") returns true, and still
verifies the request URL and opts.method === "DELETE" (referencing the
removeBookmark call and the existing fetchMock usage to locate where to
change/add the test).
🤖 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/AddressBar.tsx`:
- Around line 123-138: The toggleBookmark flow can trigger duplicate mutations
because bookmarked remains null while addBookmark is pending; add a local
pending state (e.g., isBookmarkPending) and use it to short-circuit early in
toggleBookmark and to disable the bookmark button; set isBookmarkPending = true
before calling addBookmark or removeBookmark and clear it in finally, and update
bookmarksRef and setBookmarked only after the mutation succeeds; update any UI
that reads bookmarked (the button render) to also disable when isBookmarkPending
is true so double-clicks cannot send concurrent requests (apply same pattern for
the remove path and the similar code at the other location referenced).
- Around line 95-109: The bookmarks cache is never cleared when the profile
changes, so bookmarksRef.current (keyed by URL) can retain entries from a
previous profile and cause setBookmarked to report bookmarks for the wrong
profile; modify the useEffect that calls listBookmarks (the effect watching
win?.profileId) to clear bookmarksRef.current (e.g.,
bookmarksRef.current.clear()) before repopulating, and add a
cancellation/sequence guard so late responses from an earlier listBookmarks call
are ignored (e.g., capture a local requestId or use an aborted flag and verify
it before writing to bookmarksRef.current or calling setBookmarked), referencing
the existing useEffect, listBookmarks, bookmarksRef, win?.profileId, activeTab,
and setBookmarked symbols.

In `@tinyagentos/routes/desktop_browser/download.py`:
- Around line 138-141: Move the filename resolution to after the final HTTP
response (after redirects) so that upstream headers and the final URL can
influence the name: call _safe_filename(...) only once you have the response
object, prefer a Content-Disposition filename* value, then Content-Disposition
filename, then derive from response.url using _filename_from_url, and finally
fall back to the incoming filename parameter if provided; update the code that
assigns final_name (currently using _safe_filename and _filename_from_url) to
run after the fetch/response and use response.headers and response.url for this
logic.
- Around line 76-81: The current code uses await http.get(fetch_url) which
buffers the entire body; change it to use http.stream("GET", fetch_url, ...) so
the response body is not read into memory. Replace the await http.get(...) call
inside the for _hop loop with an async with http.stream("GET", fetch_url,
follow_redirects=False, timeout=_FETCH_TIMEOUT, cookies=cookies) as response and
then feed response.aiter_bytes() (or response.aiter_raw()) into the
StreamingResponse instead of accessing response.content; keep using the same
variables (http, fetch_url, response, StreamingResponse) and preserve existing
hop/redirect handling and headers.

In `@tinyagentos/routes/desktop_browser/store.py`:
- Around line 1007-1020: check_site_permission currently returns the first
matching rule from list_site_permissions (oldest-first), letting older wildcards
override newer specific rules; fix by collecting matches while iterating the
permissions (iterate rows in reverse to prefer newest on ties) and pick by
specificity: prefer exact matches (row["host_pattern"] == host), then subdomain
wildcard matches (pattern starting with "*." where host equals domain or
endswith ".domain"), then global "*" matches; return the state of the
highest-specificity match (use the reversed iteration as the tie-breaker when
multiple rules have same specificity). Ensure you update the logic in the
function that calls list_site_permissions (check_site_permission) and reference
row["host_pattern"], row["state"], list_site_permissions, and host/permission
variables when implementing.

---

Nitpick comments:
In `@desktop/src/lib/browser-bookmarks-api.test.ts`:
- Around line 135-144: Add a test exercising the actual backend success status
204 for removeBookmark: update or add an it block that mocks global.fetch to
resolve with { ok: true, status: 204 } and asserts removeBookmark("profile-1",
"bm-1") returns true, and still verifies the request URL and opts.method ===
"DELETE" (referencing the removeBookmark call and the existing fetchMock usage
to locate where to change/add the test).
🪄 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: d951098d-3400-425b-aed8-1a966eb0a37a

📥 Commits

Reviewing files that changed from the base of the PR and between f9b2b1e and a500228.

📒 Files selected for processing (14)
  • desktop/src/apps/BrowserApp/AddressBar.test.tsx
  • desktop/src/apps/BrowserApp/AddressBar.tsx
  • desktop/src/lib/browser-bookmarks-api.test.ts
  • desktop/src/lib/browser-bookmarks-api.ts
  • tests/routes/desktop_browser/test_bookmark_routes.py
  • tests/routes/desktop_browser/test_download.py
  • tests/routes/desktop_browser/test_site_permission_routes.py
  • tests/routes/desktop_browser/test_site_permissions.py
  • tinyagentos/routes/desktop_browser/__init__.py
  • tinyagentos/routes/desktop_browser/bookmark_routes.py
  • tinyagentos/routes/desktop_browser/download.py
  • tinyagentos/routes/desktop_browser/schema.py
  • tinyagentos/routes/desktop_browser/site_permission_routes.py
  • tinyagentos/routes/desktop_browser/store.py

Comment thread desktop/src/apps/BrowserApp/AddressBar.tsx Outdated
Comment thread desktop/src/apps/BrowserApp/AddressBar.tsx
Comment thread tinyagentos/routes/desktop_browser/download.py Outdated
Comment thread tinyagentos/routes/desktop_browser/download.py Outdated
Comment on lines +1007 to +1020
rows = await self.list_site_permissions(user_id=user_id, profile_id=profile_id)
for row in rows:
if row["permission"] != permission:
continue
pattern = row["host_pattern"]
if pattern == "*":
matched = True
elif pattern.startswith("*."):
domain = pattern[2:]
matched = host == domain or host.endswith("." + domain)
else:
matched = host == pattern
if matched:
return row["state"]
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 | 🟠 Major | ⚡ Quick win

Don't let wildcard site-permission rules win just because they were inserted first.

check_site_permission() returns the first match from list_site_permissions(), and that list is ordered oldest-first. An older * -> allow therefore beats a newer example.com -> deny, so the result depends on insertion order instead of match specificity. Prefer exact > subdomain wildcard > *, with newest grant as the tie-breaker.

🤖 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 `@tinyagentos/routes/desktop_browser/store.py` around lines 1007 - 1020,
check_site_permission currently returns the first matching rule from
list_site_permissions (oldest-first), letting older wildcards override newer
specific rules; fix by collecting matches while iterating the permissions
(iterate rows in reverse to prefer newest on ties) and pick by specificity:
prefer exact matches (row["host_pattern"] == host), then subdomain wildcard
matches (pattern starting with "*." where host equals domain or endswith
".domain"), then global "*" matches; return the state of the highest-specificity
match (use the reversed iteration as the tie-breaker when multiple rules have
same specificity). Ensure you update the logic in the function that calls
list_site_permissions (check_site_permission) and reference row["host_pattern"],
row["state"], list_site_permissions, and host/permission variables when
implementing.

…reaming + filename; site-permissions specificity (CR feedback)
@jaylfc
Copy link
Copy Markdown
Owner Author

jaylfc commented May 5, 2026

@coderabbitai full review — please re-review at 4b37c48. Addressed all 6 round-1 findings: (1) bookmark cache resets on profile change, (2) single-flight bookmark toggle, (3) cross-tab bookmark sync via window event, (4) true streaming download (httpx.stream + manual redirect walk), (5) filename from final response with upstream Content-Disposition honored, (6) most-specific site permission match wins (exact > *.suffix > *).

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Tip

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

@jaylfc: Acknowledged — kicking off a full re-review at 4b37c48. I'll verify all six round-1 items (bookmark cache reset on profile change, single-flight toggle, cross-tab sync via window event, true streaming download with manual redirect walk, filename inferred from the final response with upstream Content-Disposition honoured, and most-specific site-permission match exact > *.suffix > *) along with a fresh pass over the rest of the diff.

[full_review]
Re-review requested at commit 4b37c48. Verify all 6 round-1 findings are addressed: (1) bookmark cache resets on profile change, (2) single-flight bookmark toggle, (3) cross-tab bookmark sync via window event, (4) true streaming download (httpx.stream + manual redirect walk), (5) filename from final response with upstream Content-Disposition honored, (6) most-specific site permission match wins (exact > *.suffix > *).

@jaylfc jaylfc merged commit 61292f1 into master May 5, 2026
12 of 13 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