Skip to content

fix: harden desktop lifecycle boundaries - #208

Merged
scotej merged 1 commit into
mainfrom
agent/address-review-findings
Aug 10, 2026
Merged

fix: harden desktop lifecycle boundaries#208
scotej merged 1 commit into
mainfrom
agent/address-review-findings

Conversation

@scotej

@scotej scotej commented Aug 10, 2026

Copy link
Copy Markdown
Owner

What changed and why

Hardens the desktop boundary and lifecycle paths identified in review: custom Tauri commands are now explicitly window-scoped; model downloads, sidecars, engine replacement, and friend backups are safer under failure/cancellation; invite and break flows serialize correctly; and Settings/AI-dialog behavior stays visually and thematically consistent. The PR also adds a 1.11.1 changelog section for the release workflow without bumping version files early.

Manual test

  • npm run tauri dev launched and the changed surface behaves as described — n-a: this Linux container has no desktop host
  • Checked in both themes and at reduced-motion — n-a: no desktop host

Compatibility surfaces

  • Peer wire format (trystero payloads, session/pomodoro/AI-alert messages) — no payload schema changed; retry policy now waits for the existing verified recipient acknowledgement.

Gates

  • npm run build && npm run lint && npm run test — build and lint passed via Bun; the full Vitest worker runner is unavailable in this container.
  • npm run check-tokens && npm run check-strings && npm run check-contrast
  • npm run check-migrations && npm run check-stories
  • npm run build-storybook && npm run check-a11y — unavailable in this container.
  • cd src-tauri && cargo fmt --check && cargo clippy && cargo test — CI is the first Rust compiler for this box.
  • cd src-tauri && cargo deny check — CI is the first Rust toolchain for this box.

CI-only gates remain pending.

Merge style

  • Squash

Summary by CodeRabbit

  • New Features
    • Added automatic theme synchronization with system preferences, including the floating AI dialog.
    • Added safer model download, installation, removal, and recovery handling.
  • Bug Fixes
    • Improved floating AI window behavior and prevented stale session actions.
    • Fixed invite retry and cancellation behavior.
    • Improved break-request handling during rapid session changes.
    • Added protection against oversized friends backup files and invalid model downloads.
  • Security
    • Restricted native commands available to the floating AI dialog.

Copilot AI lite review requested due to automatic review settings August 10, 2026 04:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@scotej, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b14221e1-a179-4c84-a71c-238a1c241e32

📥 Commits

Reviewing files that changed from the base of the PR and between 2ef6f55 and 66a1098.

📒 Files selected for processing (1)
  • src-tauri/src/commands/friends.rs
📝 Walkthrough

Walkthrough

The PR adds window-scoped Tauri command permissions, transactional engine installation, coordinated model and sidecar operations, bounded backup handling, theme boot synchronization, and cancellation-aware invite and break workflows with tests.

Changes

Application safety and lifecycle

Layer / File(s) Summary
Tauri command ACL generation
.gitignore, ARCHITECTURE.md, CHANGELOG.md, src-tauri/build.rs, src-tauri/capabilities/*, src-tauri/permissions/window-commands.toml, src-tauri/src/commands/applog.rs
The build generates explicit command permissions. The main window receives the full allowlist. The AI dialog receives only app_log_append and sidecar_status.
Transactional engine promotion
src-tauri/src/commands/engine.rs
Engine replacement uses rollback directories and recovers interrupted promotions. Tests cover replacement, rollback, and recovery.
Managed model and sidecar coordination
src-tauri/src/commands/models.rs, src-tauri/src/commands/sidecar.rs, src-tauri/src/lib.rs
Model URLs and paths are validated. Per-model gates coordinate downloads, removals, engine access, and sidecar lifecycle operations.
Bounded friends backup handling
src-tauri/src/commands/friends.rs, src-tauri/Cargo.toml
Backup export and import enforce a 16 MiB limit and reject invalid file types and oversized files.
Theme boot synchronization
src/components/ui/dialog.tsx, src/design/*, src/features/ai/ai-dialog-main.tsx, tests/unit/theme.test.ts
Theme resolution is shared across windows. The AI dialog applies the localStorage boot cache before rendering. Dialog z-index values use --z-modal.
Invite delivery acknowledgement and cancellation
src/features/friends/*, tests/integration/invite.test.ts, tests/unit/inviteRetry.test.ts
Invite delivery requires verified acknowledgements. Abort signals cancel active sends and retries. Unacknowledged deliveries remain retryable.
Serialized break requests and audit cancellation
src/features/session/SessionView.tsx, src/features/session/break.ts, tests/unit/break-rules.test.ts
Break requests use FIFO serialization, abort-aware audit operations, tokenized timers, and session-bound cleanup.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModelDownload
  participant ModelOperationState
  participant Sidecar
  participant Filesystem
  ModelDownload->>ModelOperationState: acquire model operation gate
  ModelDownload->>Sidecar: stop serving managed model
  ModelDownload->>Filesystem: update model files
  Sidecar->>ModelOperationState: coordinate startup and engine access
Loading
sequenceDiagram
  participant SessionView
  participant BreakQueue
  participant AuditPipeline
  participant BreakOrchestrator
  SessionView->>BreakQueue: submit break request with AbortSignal
  BreakQueue->>AuditPipeline: append request audit
  AuditPipeline-->>BreakQueue: complete or abort
  BreakQueue->>BreakOrchestrator: process session state
  BreakOrchestrator-->>SessionView: return verdict or AbortError
Loading

Possibly related PRs

  • scotej/studyvis#45: Both changes harden model-download behavior in src-tauri/src/commands/models.rs.
  • scotej/studyvis#75: Both changes extend model removal and sidecar coordination.
  • scotej/studyvis#88: Both changes modify engine installation and sidecar lifecycle code.

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.26% 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
Title check ✅ Passed The title clearly summarizes the PR’s primary focus on hardening desktop lifecycle boundaries.
Description check ✅ Passed The description covers the required changes, testing, compatibility impact, gates, and merge style.
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.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch agent/address-review-findings

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
tests/integration/invite.test.ts (1)

505-514: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The join assertion depends on the exact microtask count before joinTopic.

A single await Promise.resolve() is enough today, because sendInviteEnvelope queues prev.then(...) before the test yields, and joinTopic runs synchronously inside that callback. If a later change adds one await before joinTopic in sendInviteEnvelopeNow, this assertion reads 0 joins and fails for a reason unrelated to abort behavior.

Consider vi.waitFor so the test waits for the join instead of assuming the hop count.

♻️ Proposed robustness change
-    await Promise.resolve()
     const trystero = (await import('`@/lib/trystero`')) as unknown as {
       __getEvents: () => Array<{ type: 'join' | 'leave'; topic: string }>
     }
-    expect(
-      trystero.__getEvents().filter((e) => e.type === 'join')
-    ).toHaveLength(1)
+    await vi.waitFor(() => {
+      expect(
+        trystero.__getEvents().filter((e) => e.type === 'join')
+      ).toHaveLength(1)
+    })
     controller.abort()
🤖 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/integration/invite.test.ts` around lines 505 - 514, Update the join
assertion in the invite cancellation test to use vi.waitFor around the
trystero.__getEvents join-count check, waiting until one join is observed
instead of relying on a single Promise.resolve microtask yield. Preserve the
existing expectation that exactly one join occurs.
src/features/friends/invite.ts (1)

177-212: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move shared abort helpers to one module.

src/features/friends/invite.ts and src/features/session/break.ts both define createAbortError, throwIfAborted, and withAbort locally, and only invite.ts has waitForAbortableDelay. Centralize these helpers and import them from both files to keep abort semantics consistent.

🤖 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 `@src/features/friends/invite.ts` around lines 177 - 212, Centralize
createAbortError, throwIfAborted, withAbort, and waitForAbortableDelay in a
shared abort-helper module, then remove their local definitions from invite.ts
and the corresponding duplicated helpers from break.ts. Import and reuse the
shared symbols in both feature files, preserving the existing abort behavior and
delay handling.
src-tauri/src/commands/models.rs (1)

1087-1099: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding a short-path case to the rejection table.

The rejection list covers scheme, host, port, path kind, userinfo, and query. It does not cover a URL with fewer than five path segments. That case exercises the segments.len() >= 5 rule, which is the only guard against a resolve URL without a filename.

♻️ Proposed additional cases
             "https://token@huggingface.co/ggml-org/model/resolve/rev/model.gguf",
             "https://huggingface.co/ggml-org/model/resolve/rev/model.gguf?download=true",
+            "https://huggingface.co/ggml-org/model/resolve/rev",
+            "https://huggingface.co/ggml-org/model/resolve/rev/",
         ] {
🤖 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 `@src-tauri/src/commands/models.rs` around lines 1087 - 1099, Extend the
rejection table in
trusted_model_url_rejects_non_hugging_face_or_non_resolve_urls with a valid
HTTPS Hugging Face resolve URL whose path has fewer than five segments, such as
one lacking the filename. Keep the assertion against trusted_hugging_face_url
unchanged so the segments.len() >= 5 guard is explicitly covered.
tests/unit/break-rules.test.ts (1)

473-499: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the aborted-signal branch inside the timer callback.

This test drives the token mismatch path. The stale callback returns at the token check in src/features/session/break.ts Line 378 and never reaches the deps.signal?.aborted check at Line 380.

That second guard is reachable in production. SessionView aborts the controller in the AI-dialog effect cleanup at Line 1487, while cancelActiveBreakTimer runs only in the separate reset effect cleanup at Line 1094. The two effects have different dependency arrays, so a signal can abort while the timer record is still armed and still owns its token. Without a test, a future change to the guard order would pass silently.

💚 Proposed additional test
+  test('an aborted session timer does not end the break when its record is still armed', async () => {
+    const controller = new AbortController()
+    const input = {
+      requestedDurationSec: 300,
+      aiRecommendation: 'approve' as const,
+      aiReasoning: '',
+      now: 1_700_000_000_000,
+    }
+    const deps = buildDeps(state())
+    await requestBreak(input, { ...deps, signal: controller.signal })
+    const timer = scheduledTimers[0]!
+
+    // Abort only. The timer record keeps its own token, so the callback
+    // reaches the signal guard rather than the token guard.
+    controller.abort()
+    timer.handler()
+
+    expect(endBreakCalls).toHaveLength(0)
+  })

As per path instructions: "Use Vitest for unit and integration tests".

🤖 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/unit/break-rules.test.ts` around lines 473 - 499, Add a unit test
covering the timer callback’s aborted-signal guard when the callback still owns
the active timer token: abort the controller without canceling the timer, invoke
its handler, and assert the break does not end while the timer record remains
consistent. Keep the existing stale-token coverage unchanged and use the current
break-rule test helpers and Vitest assertions.

Source: Path instructions

🤖 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 `@src-tauri/src/commands/friends.rs`:
- Around line 109-116: Update read_backup_bounded_with_limit to reject FIFO and
other non-regular paths before any potentially blocking read open. Use a
race-safe Unix-appropriate non-blocking/open-and-validate flow, while preserving
the existing "{} is not a regular file" error and bounded-read behavior for
regular files.

In `@src-tauri/src/commands/sidecar.rs`:
- Around line 153-165: Update kill_blocking’s try_lock spin to use a bounded
deadline, such as an Instant several seconds in the future, and stop retrying
once it expires. Preserve the existing lock acquisition and cleanup behavior
while ensuring the blocking exit handler can return instead of waiting
indefinitely.

In `@src/features/session/break.ts`:
- Around line 337-383: Move the break-end timer setup, including clearing any
existing activeBreakTimer and assigning the new timer token/handle, to
immediately after startApprovedBreak() and before the emitAudit()/withAbort()
broadcast. Preserve the callback’s token, abort, and endBreak checks, while
leaving the broadcast as best-effort and retaining its existing error handling.

In `@tests/integration/invite.test.ts`:
- Around line 150-158: Reset the invite retry manager’s delivered cache during
integration-test teardown, not only its pending entries. Update the cleanup
around inviteRetryManager.cancelAll() to invoke the manager’s
reset/clear-delivery mechanism, ensuring SAMPLE_SESSION recipient registrations
do not persist across tests.

---

Nitpick comments:
In `@src-tauri/src/commands/models.rs`:
- Around line 1087-1099: Extend the rejection table in
trusted_model_url_rejects_non_hugging_face_or_non_resolve_urls with a valid
HTTPS Hugging Face resolve URL whose path has fewer than five segments, such as
one lacking the filename. Keep the assertion against trusted_hugging_face_url
unchanged so the segments.len() >= 5 guard is explicitly covered.

In `@src/features/friends/invite.ts`:
- Around line 177-212: Centralize createAbortError, throwIfAborted, withAbort,
and waitForAbortableDelay in a shared abort-helper module, then remove their
local definitions from invite.ts and the corresponding duplicated helpers from
break.ts. Import and reuse the shared symbols in both feature files, preserving
the existing abort behavior and delay handling.

In `@tests/integration/invite.test.ts`:
- Around line 505-514: Update the join assertion in the invite cancellation test
to use vi.waitFor around the trystero.__getEvents join-count check, waiting
until one join is observed instead of relying on a single Promise.resolve
microtask yield. Preserve the existing expectation that exactly one join occurs.

In `@tests/unit/break-rules.test.ts`:
- Around line 473-499: Add a unit test covering the timer callback’s
aborted-signal guard when the callback still owns the active timer token: abort
the controller without canceling the timer, invoke its handler, and assert the
break does not end while the timer record remains consistent. Keep the existing
stale-token coverage unchanged and use the current break-rule test helpers and
Vitest assertions.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e02cde1-751f-4f0c-8c34-342292e5d19b

📥 Commits

Reviewing files that changed from the base of the PR and between caa902d and 4b86858.

📒 Files selected for processing (27)
  • .gitignore
  • ARCHITECTURE.md
  • CHANGELOG.md
  • src-tauri/build.rs
  • src-tauri/capabilities/ai-dialog.json
  • src-tauri/capabilities/default.json
  • src-tauri/permissions/window-commands.toml
  • src-tauri/src/commands/applog.rs
  • src-tauri/src/commands/engine.rs
  • src-tauri/src/commands/friends.rs
  • src-tauri/src/commands/models.rs
  • src-tauri/src/commands/sidecar.rs
  • src-tauri/src/lib.rs
  • src/components/ui/dialog.tsx
  • src/design/theme-resolution.ts
  • src/design/theme.tsx
  • src/features/ai/ai-dialog-main.tsx
  • src/features/friends/invite.ts
  • src/features/friends/inviteRetry.ts
  • src/features/friends/pendingInvitesStore.ts
  • src/features/session/SessionView.tsx
  • src/features/session/break.ts
  • tests/integration/invite.test.ts
  • tests/unit/ai-sidecar.test.ts
  • tests/unit/break-rules.test.ts
  • tests/unit/inviteRetry.test.ts
  • tests/unit/theme.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: Installer (macOS)
  • GitHub Check: Installer (Windows)
  • GitHub Check: Frontend
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,rs}: Never add telemetry; the application is local-only. Never instruct users to paste model files or BIP39 mnemonics into an AI chat service.
Add comments only when the reason is non-obvious; identifiers should carry the meaning and code should read top-to-bottom.
Maintain scope discipline: do not refactor adjacent code while implementing a feature, add abstractions for hypothetical needs, or expand a bug fix beyond the bug.

Files:

  • tests/unit/theme.test.ts
  • src-tauri/src/lib.rs
  • src/components/ui/dialog.tsx
  • src/features/friends/pendingInvitesStore.ts
  • tests/unit/ai-sidecar.test.ts
  • src/design/theme-resolution.ts
  • src/features/ai/ai-dialog-main.tsx
  • src-tauri/src/commands/applog.rs
  • src-tauri/build.rs
  • tests/unit/break-rules.test.ts
  • tests/integration/invite.test.ts
  • src-tauri/src/commands/engine.rs
  • src/design/theme.tsx
  • src-tauri/src/commands/friends.rs
  • tests/unit/inviteRetry.test.ts
  • src/features/friends/invite.ts
  • src/features/session/break.ts
  • src/features/friends/inviteRetry.ts
  • src/features/session/SessionView.tsx
  • src-tauri/src/commands/models.rs
  • src-tauri/src/commands/sidecar.rs
tests/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Vitest for unit and integration tests; component tests are not currently supported because the harness uses node-env without RTL/jsdom, so component behavior belongs in Storybook and axe-core checks.

Files:

  • tests/unit/theme.test.ts
  • tests/unit/ai-sidecar.test.ts
  • tests/unit/break-rules.test.ts
  • tests/integration/invite.test.ts
  • tests/unit/inviteRetry.test.ts
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Use one focused change per commit, with a Conventional Commit subject such as feat:, fix:, chore:, docs:, or ci:; PRs are squash-merged.

Files:

  • tests/unit/theme.test.ts
  • src-tauri/src/lib.rs
  • src/components/ui/dialog.tsx
  • src/features/friends/pendingInvitesStore.ts
  • CHANGELOG.md
  • tests/unit/ai-sidecar.test.ts
  • src-tauri/capabilities/default.json
  • src/design/theme-resolution.ts
  • src-tauri/capabilities/ai-dialog.json
  • src/features/ai/ai-dialog-main.tsx
  • src-tauri/src/commands/applog.rs
  • src-tauri/build.rs
  • tests/unit/break-rules.test.ts
  • tests/integration/invite.test.ts
  • src-tauri/permissions/window-commands.toml
  • ARCHITECTURE.md
  • src-tauri/src/commands/engine.rs
  • src/design/theme.tsx
  • src-tauri/src/commands/friends.rs
  • tests/unit/inviteRetry.test.ts
  • src/features/friends/invite.ts
  • src/features/session/break.ts
  • src/features/friends/inviteRetry.ts
  • src/features/session/SessionView.tsx
  • src-tauri/src/commands/models.rs
  • src-tauri/src/commands/sidecar.rs
src-tauri/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Rust changes must pass cargo test, cargo fmt --check, and cargo clippy; dependency and supply-chain changes must pass cargo deny check.

Files:

  • src-tauri/src/lib.rs
  • src-tauri/src/commands/applog.rs
  • src-tauri/build.rs
  • src-tauri/src/commands/engine.rs
  • src-tauri/src/commands/friends.rs
  • src-tauri/src/commands/models.rs
  • src-tauri/src/commands/sidecar.rs
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{ts,tsx}: All design-token values—colors, spacing, fonts, radii, shadows, motion, and z-indexes—must come from src/design/tokens.ts; do not use raw hex values, arbitrary px, or inline cubic-bezier values.
User-facing toast and notification copy must live in src/strings.ts; prefer centralized strings for JSX text and aria-label values.
Maintain WCAG AA contrast for every text/background pairing in both themes, do not convey information by color alone, and honor the global reduced-motion kill switch; new motion must be gated by default.
Treat peer wire formats and identity derivation as cross-version contracts; coordinate changes so older builds and existing stored data remain compatible.
Every component and feature component must have a Storybook story.

Files:

  • src/components/ui/dialog.tsx
  • src/features/friends/pendingInvitesStore.ts
  • src/design/theme-resolution.ts
  • src/features/ai/ai-dialog-main.tsx
  • src/design/theme.tsx
  • src/features/friends/invite.ts
  • src/features/session/break.ts
  • src/features/friends/inviteRetry.ts
  • src/features/session/SessionView.tsx
src/components/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/components/ui/ is the only location where Radix or shadcn primitives may be imported.

Files:

  • src/components/ui/dialog.tsx
src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Components under src/components/ should compose from ui/, src/design/, and shared utilities; reverse imports are prohibited.

Files:

  • src/components/ui/dialog.tsx
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Do not create new documentation files unless explicitly requested; update canonical documentation, CHANGELOG.md, or ISSUES.md when justified.

Files:

  • CHANGELOG.md
  • ARCHITECTURE.md
🔇 Additional comments (69)
.gitignore (1)

33-34: LGTM!

ARCHITECTURE.md (1)

687-687: LGTM!

Also applies to: 714-719

CHANGELOG.md (1)

3-27: LGTM!

src-tauri/capabilities/ai-dialog.json (1)

4-10: LGTM!

src-tauri/capabilities/default.json (1)

8-8: LGTM!

src-tauri/permissions/window-commands.toml (1)

1-82: LGTM!

src-tauri/src/commands/applog.rs (1)

27-28: LGTM!

Also applies to: 136-138

src-tauri/build.rs (1)

8-79: 🔒 Security & Privacy

Run the required Rust gates before merge.

APP_COMMANDS and window-commands.toml stay synchronized, and there are no unrecognized invoke handlers. If these changes include Rust src-tauri updates, run cargo test, cargo fmt --check, and cargo clippy in CI.

src/components/ui/dialog.tsx (1)

42-42: LGTM!

Also applies to: 64-64

src/design/theme-resolution.ts (1)

1-12: LGTM!

src/design/theme.tsx (1)

11-15: LGTM!

Also applies to: 28-100, 120-126

src/features/ai/ai-dialog-main.tsx (1)

10-10: LGTM!

Also applies to: 62-62

tests/unit/theme.test.ts (1)

1-15: LGTM!

src/features/friends/invite.ts (7)

24-36: LGTM!


118-118: LGTM!


268-278: LGTM!


288-289: LGTM!


349-359: LGTM!

Also applies to: 372-376


391-401: LGTM!


420-436: LGTM!

src/features/friends/inviteRetry.ts (6)

3-12: LGTM!


25-44: LGTM!


63-71: LGTM!


101-143: LGTM!


145-208: LGTM!


210-222: LGTM!

tests/integration/invite.test.ts (3)

249-256: LGTM!


335-339: LGTM!


486-503: LGTM!

Also applies to: 515-521

tests/unit/inviteRetry.test.ts (4)

14-14: LGTM!

Also applies to: 46-46, 63-63, 73-73, 83-83, 94-94, 108-109, 122-122, 135-135, 165-165, 241-241


26-43: LGTM!


147-149: LGTM!


179-238: LGTM!

src/features/friends/pendingInvitesStore.ts (1)

58-61: 📐 Maintainability & Code Quality

Keep the defaults option as-is.

@tauri-apps/plugin-store 2.4.4 defines defaults on StoreOptions, so the comment accurately describes the library requirement.

			> Likely an incorrect or invalid review comment.
src-tauri/src/commands/friends.rs (5)

7-9: LGTM!


278-283: 🚀 Performance & Scalability

Confirm the scope of the export size limit.

Line 278 runs after encode_backup has serialized all rows, sealed the full plaintext, and allocated the output vector. This caps the file written to disk, but it does not cap peak memory. If friends::list or friend fields are not bounded elsewhere, a large local database can allocate far above 16 MiB before returning the error.

Confirm those bounds or enforce a pre-encoding size budget.


302-302: LGTM!


516-545: LGTM!


516-545: 📐 Maintainability & Code Quality

Complete the required Rust checks before merge.

As per coding guidelines, Rust changes must pass cargo test, cargo fmt --check, and cargo clippy. The PR context marks these gates as pending or unavailable, so verify them before merging.

Source: Coding guidelines

src-tauri/src/commands/engine.rs (4)

256-260: LGTM!


357-396: LGTM!


398-454: LGTM!


804-852: LGTM!

src-tauri/src/commands/models.rs (7)

100-141: LGTM!


143-207: LGTM!


209-245: LGTM!


314-332: LGTM!


381-408: LGTM!


483-489: LGTM!

Also applies to: 677-684


551-612: LGTM!

src-tauri/src/commands/sidecar.rs (7)

108-111: LGTM!


227-262: LGTM!


293-332: LGTM!


381-381: LGTM!

Also applies to: 419-419, 446-474


863-865: LGTM!

Also applies to: 929-929


993-1046: LGTM!


1091-1133: LGTM!

src-tauri/src/lib.rs (1)

55-55: LGTM!

Also applies to: 324-324

tests/unit/ai-sidecar.test.ts (1)

725-725: LGTM!

Also applies to: 764-779

src/features/session/SessionView.tsx (4)

165-165: LGTM!

Also applies to: 401-401, 412-412


824-843: LGTM!


845-876: LGTM!


1446-1480: 🩺 Stability & Availability

No change needed. The AI dialog clears pending break request nonces on cleanup and resolves a missing main-window response through BREAK_REQUEST_TIMEOUT_MS.

src/features/session/break.ts (4)

154-165: LGTM!

Also applies to: 167-185


193-253: LGTM!


255-273: LGTM!


391-404: LGTM!

tests/unit/break-rules.test.ts (2)

336-383: LGTM!


385-471: LGTM!

Comment thread src-tauri/src/commands/friends.rs
Comment on lines 153 to 165
pub fn kill_blocking<R: Runtime>(app: &AppHandle<R>) {
let Some(state) = app.try_state::<SidecarState>() else {
return;
};
let arc = state.0.clone();
let deadline = Instant::now() + Duration::from_millis(500);
let mut guard = loop {
match arc.try_lock() {
Ok(g) => break g,
Err(_) => {
if Instant::now() >= deadline {
return;
}
std::thread::sleep(Duration::from_millis(10));
std::thread::sleep(Duration::from_millis(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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find every SidecarInner lock acquisition and check for awaits held across the guard.
set -euo pipefail

fd -e rs . src-tauri/src --exec rg -n -C 12 'lock\(\)\.await|try_lock\(\)' {} \; \
  | rg -n -C 6 'SidecarInner|state\.lock|arc\.lock' || true

# Locate every kill_blocking call site and the RunEvent arms that reach it.
rg -nP -C 8 '\bkill_blocking\s*\(' src-tauri/src

Repository: scotej/studyvis

Length of output: 12561


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== sidecar relevant ranges =="
wc -l src-tauri/src/commands/sidecar.rs src-tauri/src/lib.rs
sed -n '1,180p' src-tauri/src/commands/sidecar.rs
sed -n '286,430p' src-tauri/src/commands/sidecar.rs
sed -n '430,500p' src-tauri/src/commands/sidecar.rs
sed -n '880,1065p' src-tauri/src/commands/sidecar.rs
sed -n '328,356p' src-tauri/src/lib.rs

echo "== all await in src-tauri/src/commands/sidecar.rs =="
python3 - <<'PY'
import re, pathlib
p=pathlib.Path('src-tauri/src/commands/sidecar.rs')
text=p.read_text()
for i,line in enumerate(text.splitlines(),1):
    if '.await' in line or 'lock().await' in line:
        print(f"{i}: {line.rstrip()}")
PY

echo "== deterministic search for lock spans with await =="
python3 - <<'PY'
from pathlib import Path
import re

src = Path('src-tauri/src/commands/sidecar.rs').read_text()
lines = src.splitlines()
for idx, line in enumerate(lines):
    if 'lock().await' in line or 'try_lock()' in line:
        func = None
        for fk, fl in enumerate(lines[:idx][::-1], 1):
            if re.match(r'async fn \w+|fn \w+|impl .*SidecarState|impl .*Engine', fl):
                func = fl.strip()
                break
        print(f"line {idx+1}: {line.strip()} <= function {func or '<top-level/unknown>'}")
    if 'await' in line:
        # Show concise context around all awaits
        start=max(1, idx-3); end=min(len(lines), idx+3)
        print(f"await around line {idx+1}:")
        for n in range(start, end+1):
            print(f"{n}: {lines[n-1].rstrip()}")
PY

Repository: scotej/studyvis

Length of output: 34384


Add a bounded deadline to kill_blocking’s lock spin.

kill_blocking is called from the blocking RunEvent::Exit* handler, so this thread can only quit after it returns. If a watch task is parked at await while holding state.lock().await, the unbounded 1 ms try_lock() loop can wait forever. Add an escalation deadline (for example, several seconds) so the exit path can terminate even during an asynchronous lock holder.

🤖 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 `@src-tauri/src/commands/sidecar.rs` around lines 153 - 165, Update
kill_blocking’s try_lock spin to use a bounded deadline, such as an Instant
several seconds in the future, and stop retrying once it expires. Preserve the
existing lock acquisition and cleanup behavior while ensuring the blocking exit
handler can return instead of waiting indefinitely.

Comment thread src/features/session/break.ts Outdated
Comment thread tests/integration/invite.test.ts
@scotej
scotej force-pushed the agent/address-review-findings branch from 2ef6f55 to 66a1098 Compare August 10, 2026 06:37
@scotej
scotej merged commit cf48f4c into main Aug 10, 2026
19 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.

2 participants