Skip to content

feat(webview-accounts): faster + clearer cold opens (#1233 #1284)#1285

Merged
senamakel merged 15 commits intotinyhumansai:mainfrom
oxoxDev:fix/1233-webview-load-perf
May 6, 2026
Merged

feat(webview-accounts): faster + clearer cold opens (#1233 #1284)#1285
senamakel merged 15 commits intotinyhumansai:mainfrom
oxoxDev:fix/1233-webview-load-perf

Conversation

@oxoxDev
Copy link
Copy Markdown
Contributor

@oxoxDev oxoxDev commented May 6, 2026

Summary

  • Embedded webview area renders a provider-branded placeholder + spinner from frame 1 instead of a blank stone-100 rectangle while the native CEF subview is still parked off-screen.
  • New 5s/10s phase hints ("Restoring session..." → "Almost ready...") give the user feedback that something is happening on slow loads, without firing on the warm-reopen happy path.
  • Replaces the fixed 500ms sleep before the first CDP attach with a tight retry schedule (0/50/150/400ms) — saves up to ~500ms wall-clock on every cold open. CEF prewarm hits commonly skip the wait entirely.
  • Adds a webview_account_prewarm Tauri command + lastActiveAccountId MRU tracking. On Accounts page mount, the most-recently-active account is warmed in the background so the next click hits the warm-reopen branch and emits state:"reused" with no spinner. Capped at accounts.length ≤ 5 so power users opt out automatically.
  • All work-tree-isolated; no changes to provider runtime JS injection (CEF child-webview policy preserved per CLAUDE.md).

Problem

#1233 reports embedded apps (Slack, Google Meet, Telegram, ...) staying "blank or slow" before usable, with users sometimes needing to tab-switch or restart to recover. Three compounding issues on the cold-load path:

  1. Blank gap before spinnerWebviewHost.tsx:104 only dispatches openWebviewAccount after the first ResizeObserver measure, and the spinner is gated on Redux status 'pending' | 'loading' (WebviewHost.tsx:142-152). Brief but visible window between mount and spinner visibility.
  2. CDP attach delay on critical pathcdp/session.rs:166 slept 500ms before the first attach attempt, then per-provider blocked navigation behind the notification-permission shim injection (webview_accounts/mod.rs:1615-1620). Up to ~500ms wall-clock cost on every cold open.
  3. No provider-level prewarmlib.rs:903 warmed only the CEF process via a 1×1 hidden about:blank. Per-account profile loaded cold every app start; the existing warm-reopen path (mod.rs:1631-1671) is fast but only fires after the first user click.

The 15s timeout / retry UI shipped in #1043 already handles permanently stuck loads — this PR targets faster loads + cleaner perceived UX.

Solution

Frontend — WebviewHost.tsx:

  • Branded provider placeholder (icon + provider name) rendered unconditionally so the host area is never visually blank. Native view composites above on reveal.
  • Loading spinner now treats unknown account status as loading, closing the brief mount → setAccountStatus('pending') race.
  • Counter-driven elapsed timer (deterministic under fake timers; no Date mocking needed) drives a 5s/10s phase hint that resets when status leaves the loading set, so warm reopens (state:"reused") never trigger it.

Frontend — Accounts.tsx + webviewAccountService.ts:

  • Track lastActiveAccountId in localStorage on every rail click / new-account pick. Failures swallowed so private mode / sandboxing never breaks the page.
  • New prewarmWebviewAccount(id, provider) helper invokes webview_account_prewarm. Errors logged + swallowed; worst case is a normal cold open later.
  • On Accounts mount, dispatch a single prewarm for the MRU account when accounts.length ≤ PREWARM_MAX_ACCOUNTS = 5, the account exists, isn't already active, and isn't already warm/loading.

Backend — cdp/session.rs:

  • INITIAL_ATTACH_SCHEDULE: [Duration; 4] = [0, 50, 150, 400]ms. First pass tries each in turn, then falls through to the existing 2s ATTACH_BACKOFF reconnect loop. Locked under 600ms total by initial_attach_schedule_under_600ms_total test.

Backend — webview_accounts/mod.rs:

  • New prewarm_accounts: Mutex<HashSet<String>> in WebviewAccountsState.
  • webview_account_prewarm(account_id, provider) Tauri command — idempotent, off-screen 1×1 spawn at (-20000, -20000), attaches CDP session that drives the real Page.navigate.
  • emit_load_finished short-circuits while prewarm_accounts contains the id, so the React UI never sees load/timeout signals for an account it didn't open.
  • Warm-reopen branch in webview_account_open clears the prewarm flag before resizing/showing/emitting state:"reused". On user click, the prewarmed webview is promoted to live in one round-trip.
  • Flag also cleared on close / purge / drain_for_shutdown so a relaunch can't suppress events for accounts prewarmed in the previous session.

Deferred follow-ups (out of scope for this PR):

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per docs/TESTING-STRATEGY.md
  • Diff coverage ≥ 80% — Vitest covers WebviewHost lifecycle (placeholder, spinner from frame 1, phase hints at 5s/10s, hint cleared on open, timeout overlay). cargo-llvm-cov covers INITIAL_ATTACH_SCHEDULE shape, prewarm_accounts insert/remove + drain_for_shutdown clearing.
  • N/A: Coverage matrix updated — behaviour-only change to existing webview-account loading flow; no new feature row.
  • N/A: All affected feature IDs from the matrix are listed in the PR description under ## Related — see above row.
  • No new external network dependencies introduced (mock backend used per docs/TESTING-STRATEGY.md)
  • N/A: Manual smoke checklist updated if this touches release-cut surfaces — release smoke unchanged; this affects the in-app loading surface, not release-cut packaging.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime: Desktop only — frontend (app/src/) + Tauri shell (app/src-tauri/). No CLI / mobile / web changes.
  • Performance: Cold open of an embedded provider should improve by ~500ms in the typical case (CDP attach delay) plus user-perceived improvement from the always-visible branded placeholder + spinner. Warm-reopen path unchanged. MRU prewarm bounded at accounts ≤ 5.
  • Security / privacy: No new external network dependencies. Prewarm uses the same per-account data_directory and CDP session pipeline as a normal open, including the existing notification-permission shim and per-origin permission grants.
  • Migration / compatibility: localStorage key webview-accounts:lastActive is new; absence is handled (no prewarm, no error). No breaking changes to existing Tauri command surface.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A: GitHub-issue-tracked, not Linear
  • URL: N/A: GitHub-issue-tracked, not Linear

Commit & Branch

  • Branch: fix/1233-webview-load-perf
  • Commit SHA: 5e7d1c53 (tip)

Validation Run

  • pnpm --filter openhuman-app format:check
  • pnpm typecheck
  • Focused tests: cargo test --lib webview_accounts 62/62, cargo test --lib cdp::session 11/11, vitest WebviewHost 7/7, vitest webviewAccountService.loadListener 10/10
  • Rust fmt/check (if changed): cargo fmt --check ✅, cargo check ✅ (pre-existing warnings unrelated)
  • Tauri fmt/check (if changed): same as above

Validation Blocked

  • command: cargo clippy --tests -- -D warnings
  • error: Pre-existing failures on upstream/main in mascot_native_window, agent/pformat.rs, etc. Not introduced by this PR; unchanged files.
  • impact: None — focused clippy on changed files (cdp/session.rs, webview_accounts/mod.rs, lib.rs) clean. Workspace-wide clippy gate is broken on main per feedback_validation_test_target.md.

Behavior Changes

  • Intended behavior change: Embedded webview cold opens render an immediate branded placeholder + spinner; cold-open wall-clock reduced ~500ms via CDP attach retry; MRU account prewarmed in background so first click is warm-reopen.
  • User-visible effect: Faster perceived + actual load on the first click of an embedded account; clearer "something is happening" feedback during slow loads via 5s/10s phase hints.

Parity Contract

  • Legacy behavior preserved: Warm-reopen state:"reused" path unchanged (still bypasses emit_load_finished). 15s watchdog timeout overlay still fires on permanently stuck loads. Notification-permission shim, per-origin grants, per-account data dir all unchanged.
  • Guard/fallback/dispatch parity checks: Prewarm flag cleared on close / purge / drain_for_shutdown so cross-session state can't suppress legitimate events.

Duplicate / Superseded PR Handling

Summary by CodeRabbit

  • New Features
    • Introduced webview prewarming to reduce account load times
    • Auto-prewarm most-recently-used account on Accounts page mount
    • Enhanced loading experience with branded placeholders and progressive phase hints ("Restoring session...", "Almost ready...") during webview initialization

oxoxDev added 8 commits May 6, 2026 14:55
…rmup (tinyhumansai#1233)

Replace the fixed 500ms `sleep` before the first CDP attach attempt with a
4-step retry schedule (0 / 50 / 150 / 400 ms) that probes immediately and
escalates only on failure. The CEF prewarm at app startup typically has
the target available almost instantly, so the warm path now skips the old
half-second penalty entirely; the worst case before falling through to
the existing 2s `ATTACH_BACKOFF` loop is ~600ms.

Adds a unit test pinning the schedule shape (4 attempts, ≤600ms total,
first attempt at t=0) so the budget can't drift silently.

Refs tinyhumansai#1233 (embedded web apps load slowly).
…mansai#1233)

Add a background prewarm path so the most-recently-active account's CEF
profile and provider page can be hot before the user clicks the rail icon.
The frontend dispatches `webview_account_prewarm`; Rust spawns the same
cold-load codepath at a fixed off-screen 1×1 rect and attaches a CDP
session that drives `Page.navigate` to the real provider URL. Load
events flow through `emit_load_finished` which now short-circuits while
the account is in the new `prewarm_accounts` set, so the React UI never
sees a load/timeout signal for an account it hasn't asked to open.

When the user later clicks the prewarmed account, `webview_account_open`
hits the existing warm-reopen branch, clears the prewarm flag, resizes
the on-screen rect, and emits `state:"reused"` synchronously — no cold
spinner, no double event.

Bookkeeping covers the full lifecycle:
- `prewarm_accounts` flag cleared on warm-reopen, close, purge, and
  `drain_for_shutdown` so a relaunch can't suppress events for accounts
  prewarmed in the previous session.
- Idempotent — `webview_account_prewarm` is a no-op if the webview is
  already in `state.inner`.
- Best-effort errors only surface to the frontend; the worst case is a
  normal cold open later.

Tests cover prewarm flag insert/remove + drain_for_shutdown clearing.
The full prewarm → reuse cycle requires a CEF runtime and is verified
manually.

Refs tinyhumansai#1233.
…umansai#1233)

Wire the new `webview_account_prewarm` Tauri command into the React side
so the most-recently-active account is warmed in the background as soon
as the user lands on the Accounts page.

- `webviewAccountService.prewarmWebviewAccount(id, provider)` — fire-and-
  forget invoke; errors logged and swallowed (worst case: normal cold
  open later).
- `Accounts.tsx` — track `lastActiveAccountId` in localStorage on every
  rail click / new-account pick. On mount, dispatch a single prewarm for
  the MRU account when `accounts.length ≤ PREWARM_MAX_ACCOUNTS = 5`,
  the MRU account exists, isn't already active, and isn't already
  warm/loading. Power users with many accounts auto-opt-out so the
  spawn cost stays bounded.

Refs tinyhumansai#1233.
…ost (tinyhumansai#1233)

Eliminate the perceived blank-screen gap when an embedded web app is
still spinning up:

- A non-interactive provider-branded placeholder (icon + name) is
  rendered unconditionally so the host area is never visually empty
  while the native CEF subview is still parked off-screen. The native
  view composites above it on reveal, so the placeholder is only
  visible during the loading window.
- The loading spinner now shows from frame 1 (treats unknown account
  status as loading), not just `pending`/`loading` — closes the brief
  race between mount and the `setAccountStatus('pending')` dispatch
  inside `openWebviewAccount`.
- Adds an elapsed-counter-driven phase hint that appears at 5s
  ("Restoring session...") and escalates at 10s ("Almost ready..."),
  giving the user feedback that something is happening on slow loads
  without feeling patronising on the happy path. Counter resets when
  status leaves the loading set so warm reopens (`state:"reused"`)
  never trigger the hint.

Vitest covers the full visibility lifecycle: placeholder always visible,
spinner from frame 1, hint timing at 5s/10s, hint cleared on flip to
`open`, timeout overlay at `timeout`. Counter-based timer (rather than
`Date.now()`) keeps the test deterministic under fake timers without
needing to mock `Date`.

Refs tinyhumansai#1233.
Cargo bumped the lockfile when `cargo check` ran in the worktree to match
the Cargo.toml version that landed in `chore(staging): v0.53.18`
(a3a1843). No dependency changes.
…inyhumansai#1233)

The first cut of issue tinyhumansai#1233's loading UX rendered two absolute flex
columns inside the host: a placeholder (icon + provider name, 70%
opacity) and a loading overlay (icon + spinner + "Loading {Provider}…").
Each centered independently, so the loading column shifted up to make
room for the spinner — the placeholder icon was visible behind it,
slightly offset, looking like two ghosted versions of the brand.

Collapse them into a single absolute container:
- Always render the icon + provider-name pair from frame 1.
- While loading, swap the name for `Loading {Provider}…` and append the
  spinner + 5s/10s phase hint inside the same column.
- Hide the whole stack when status is `'timeout'` so the dedicated retry
  overlay owns the host area uncontested.

Existing Vitest assertions (placeholder visible, spinner from frame 1,
hint timing, hint cleared on `'open'`, timeout overlay) still pass; the
testids remain on the same DOM nodes the tests query.

Refs tinyhumansai#1233.
Sidebar rail tooltips rendered to the right of the icon (`left-full ml-3`),
which placed them squarely inside the area occupied by the embedded CEF
webview. The native subview composites above the HTML layer, so DOM
z-index can't lift the tooltip — it was hidden behind the webview every
time a provider account was open.

Move both rail tooltips (every `RailButton` + the `Add app` `+` button)
BELOW the icon via `top-full mt-1 left-1/2 -translate-x-1/2`. Below-icon
keeps the tooltip near the cursor and never blocks the icon currently
being hovered. Above-icon was tried first but pushed the tooltip far
enough up to occlude the icon above the hovered one (gap-2 between rail
buttons is only 8px; tooltip height is ~24px, so it always overflows the
gap into the previous button).

Refs tinyhumansai#1284. Bundled with tinyhumansai#1233 since both touch the same surface.
… order (tinyhumansai#1284)

Previous tooltip-below-icon fix was still hidden behind the next sibling
icon for some hovered buttons. Root cause: `hover:scale-105` on a
non-active rail button establishes a CSS transform → new stacking
context, which traps the tooltip's `z-50` inside the hovered button.
Sibling buttons later in DOM order then paint above the tooltip
rectangle even though the tooltip declared a higher z-index — z-index
only ranks within the same stacking context.

Add `hover:z-50` to both `RailButton` and the `Add app` `+` button so
the entire button (and its tooltip child) lifts above neighbours during
hover regardless of whether the transform is active.

Refs tinyhumansai#1284.
@oxoxDev oxoxDev requested a review from a team May 6, 2026 10:01
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR implements webview prewarming with most-recently-used (MRU) account tracking across the frontend and Tauri backend. It introduces a four-step CDP session initial-attach retry schedule, adds a webview_account_prewarm command to spawn hidden webviews in the background, enhances the Accounts page UI with branded placeholders and loading phases, and persists the last active account to enable warm reopens on future sessions.

Changes

Webview Prewarming with MRU Account Tracking

Layer / File(s) Summary
State & Data Shape
app/src-tauri/src/cdp/session.rs, app/src/store/accountsSlice.ts, app/src/types/accounts.ts, app/src/store/index.ts, app/src-tauri/src/webview_accounts/mod.rs
Added INITIAL_ATTACH_SCHEDULE (0ms, 50ms, 150ms, 400ms) for CDP session attach retry; introduced lastActiveAccountId: string | null to AccountsState with persistence config update; added prewarm_accounts: Mutex<HashSet<String>> to WebviewAccountsState and PrewarmArgs struct for prewarm invocation parameters.
Backend Command & Prewarm Logic
app/src-tauri/src/webview_accounts/mod.rs, app/src-tauri/src/lib.rs
Implemented webview_account_prewarm command to spawn 1×1 offscreen webviews; extended OpenArgs with prewarm: bool flag; integrated prewarm flow into webview_account_open to detect prewarmed webviews, suppress early load signals, promote to visible on first user interaction, and clear prewarm state; registered command in invoke_handler and updated dev-auto account opens to include prewarm: false.
CDP Session Attach Loop
app/src-tauri/src/cdp/session.rs
Replaced fixed 500ms warmup with loop over INITIAL_ATTACH_SCHEDULE, retrying attach on each delay; preserved main reconnect loop with backoff starting after schedule completes.
Frontend Service & Hook
app/src/services/webviewAccountService.ts, app/src/hooks/usePrewarmMostRecentAccount.ts
Added prewarmWebviewAccount(accountId, provider) service function to invoke Tauri prewarm command; introduced usePrewarmMostRecentAccount hook to read MRU account from Redux, validate eligibility, and trigger prewarm on mount (capped at PREWARM_MAX_ACCOUNTS).
MRU Persistence & Dispatch
app/src/store/accountsSlice.ts, app/src/pages/Accounts.tsx
Added setLastActiveAccount reducer and selectLastActiveAccountId selector; updated webview_account_open removal logic to clear MRU if the deleted account was active; integrated hook in Accounts page and dispatched setLastActiveAccount on account creation and selection.
UI Updates & Integration
app/src/components/accounts/WebviewHost.tsx, app/src/pages/Accounts.tsx
Updated WebviewHost to always render branded placeholder (provider icon + name) and overlay loading spinner with phase hints (PHASE_HINT_AT_MS at 5s, PHASE_HINT_LATE_MS at 10s); added LoadingPhaseHint component with timer logic; treat undefined status as loading; integrated prewarm hook in Accounts page; enhanced RailButton and Add button with below-icon tooltips.
Tests & Validation
app/src-tauri/src/cdp/session.rs, app/src-tauri/src/webview_accounts/mod.rs, app/src/components/accounts/__tests__/WebviewHost.test.tsx, app/src/services/__tests__/webviewAccountService.prewarm.test.ts, app/src/hooks/__tests__/usePrewarmMostRecentAccount.test.tsx
Added unit test for initial attach schedule totaling ≤600ms and starting immediately; added prewarm state tests (empty default, insertion/removal, drain behavior); added WebviewHost test suite covering placeholder rendering, loading spinner phases, timeout overlay, and phase hint progression; added service test for Tauri invocation and error handling; added hook test suite for MRU presence, active state, loading states, and edge conditions.

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend as Frontend<br/>(Accounts)
    participant Service as Service<br/>(prewarm)
    participant Tauri as Tauri Backend<br/>(webview_accounts)
    participant CDP as CDP Session<br/>(attach)

    Note over Frontend,CDP: Initial Prewarm (App Mount)
    User->>Frontend: App starts / Accounts page mounts
    Frontend->>Frontend: usePrewarmMostRecentAccount hook fires
    Frontend->>Service: prewarmWebviewAccount(mru_id, provider)
    Service->>Tauri: invoke webview_account_prewarm(PrewarmArgs)
    Tauri->>Tauri: Create 1×1 offscreen webview
    Tauri->>CDP: Initialize CDP session, start attach retry loop
    CDP->>CDP: Attempt attach: 0ms (fail) → 50ms (fail) → ... → success
    Tauri->>Tauri: Load provider URL in hidden webview
    Tauri->>Tauri: Mark account as prewarmed

    Note over Frontend,CDP: User Opens Prewarmed Account
    User->>Frontend: Click on account
    Frontend->>Tauri: invoke webview_account_open(account_id)
    Tauri->>Tauri: Detect prewarm flag set
    Tauri->>Tauri: Promote hidden webview to visible<br/>(restore bounds, clear prewarm mark)
    Tauri->>Frontend: Return success
    Frontend->>Frontend: WebviewHost shows account<br/>(placeholder + status update)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

  • tinyhumansai/openhuman#925: Modifies CDP session startup and attach behavior with complementary retry logic changes in app/src-tauri/src/cdp/session.rs.
  • tinyhumansai/openhuman#887: Refactors CDP session spawn logic and adds Page.loadEventFired/watchdog handling, directly related to session startup infrastructure.

Suggested Reviewers

  • graycyrus
  • senamakel

🐰 A prewarmed webview hops with glee,
No more waiting—instant spree!
MRU accounts recall the past,
When loading slow, hints come at last.
Branded placeholders, spinners twirl,
The UX dance begins to swirl!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(webview-accounts): faster + clearer cold opens' accurately summarizes the main changes—improving cold-open performance and UX for webview accounts through prewarm and deterministic loading UI.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.


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

Copy link
Copy Markdown
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src-tauri/src/cdp/session.rs (1)

178-220: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The new warm-path still performs a fifth immediate attach attempt.

If all four scheduled attempts fail, execution drops straight into the steady-state loop and calls run_session_cycle once more before sleeping ATTACH_BACKOFF. The real cadence is therefore 0, 50, 150, 400, ~0, 2000..., not the documented four-step schedule. Please insert the 2s backoff before the first steady-state retry, or otherwise track that the initial pass already consumed an attempt.

🤖 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 `@app/src-tauri/src/cdp/session.rs` around lines 178 - 220, The initial attach
schedule currently falls through into the steady-state loop and immediately
calls run_session_cycle a fifth time; update the code around
INITIAL_ATTACH_SCHEDULE and the subsequent loop so that if the for loop
completes without breaking (i.e., none of the scheduled attempts succeeded) you
await sleep(ATTACH_BACKOFF) once before entering the steady-state loop — you can
detect this by recording a boolean like initial_succeeded (set true inside the
Ok branch where you break) or by checking whether the for loop broke; ensure
run_session_cycle is not called again immediately when the initial pass failed.
app/src-tauri/src/webview_accounts/mod.rs (1)

1694-1745: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't emit reused until the prewarmed page is actually ready.

This branch treats any prewarmed webview as instantly warm, but a user can click while the off-screen prewarm is still on about:blank or mid-navigation. In that race we clear the prewarm flag, reveal the child, and emit state:"reused" anyway, which skips the loading overlay and can bring back the blank-gap behavior this PR is trying to remove. Please gate the fast-path on actual readiness (loaded_accounts / non-placeholder URL) and otherwise let the normal load lifecycle complete.

🤖 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 `@app/src-tauri/src/webview_accounts/mod.rs` around lines 1694 - 1745, The code
currently clears state.prewarm_accounts for args.account_id and immediately
emits a "reused" load event even if the off-screen prewarmed WebView is still at
about:blank or mid-navigation; change the fast-path to only emit state:"reused"
when the WebView is actually ready (e.g. present in the loaded_accounts dedup
set or existing.url() is not a placeholder/blank), otherwise avoid emitting
"reused" and let the normal emit_load_finished/load lifecycle run. Concretely:
after removing the prewarm flag (state.prewarm_accounts.remove) check
loaded_accounts (or inspect existing.url() != "about:blank" and not a
placeholder) before building reuse_url and calling
app.emit("webview-account:load", {"state":"reused",...}); if the check fails,
skip the emit and keep existing behavior that will allow emit_load_finished to
trigger later.
🧹 Nitpick comments (1)
app/src/pages/Accounts.tsx (1)

37-58: 💤 Low value

Consider using persisted Redux slice for MRU tracking.

The coding guidelines prefer Redux over ad-hoc localStorage for state management. While this MRU key is a simple optimization hint (not critical state), centralizing it in a persisted Redux slice would maintain consistency and enable easier debugging/testing.

That said, the current approach is reasonable for a single cross-session hint that doesn't affect app correctness — the worst case is a cold open if localStorage is unavailable.

As per coding guidelines: "Use Redux (persisted where configured) over ad-hoc localStorage for state management in the frontend".

🤖 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 `@app/src/pages/Accounts.tsx` around lines 37 - 58, The MRU localStorage
helpers (MRU_ACCOUNT_KEY, PREWARM_MAX_ACCOUNTS, readMruAccountId,
writeMruAccountId) should be migrated to a persisted Redux slice so MRU tracking
follows the app guideline; create a new slice (e.g., mruAccountSlice) with state
for lastActiveAccountId and keep PREWARM_MAX_ACCOUNTS where used, replace direct
calls to readMruAccountId/writeMruAccountId with a selector and an action
dispatcher (and initialize the slice by reading existing localStorage once on
startup to preserve backwards compatibility), and remove direct localStorage
reads/writes from Accounts.tsx so the component uses the persisted Redux state
instead.
🤖 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 `@app/src-tauri/src/webview_accounts/mod.rs`:
- Around line 2468-2479: The prewarm path is creating a Webview without the
normal handlers and bootstrapping used by the open path (missing on_navigation,
on_new_window, on_page_load handlers, scanner bootstrap, CEF/native notification
registration, and the same CDP/Page session wiring), causing prewarmed views to
miss link interception, popup policy, load/timeout events and notifications;
modify webview_account_prewarm to reuse the exact builder/setup used by
webview_account_open by extracting the common builder+handler/bootstrap logic
into a shared function (or call the same builder routine) and only override
bounds/visibility for prewarm, ensuring the shared function wires on_navigation,
on_new_window, on_page_load, scanner bootstrap, CEF notification registration,
and CDP Page.navigate/emit_load_finished behavior so both prewarm and open get
identical configuration.

In `@app/src/components/accounts/WebviewHost.tsx`:
- Around line 80-91: The effect in WebviewHost currently calls setElapsedMs(0)
synchronously which violates the react-hooks/set-state-in-effect rule; remove
those direct setElapsedMs calls from the useEffect and instead either (A)
maintain a mutable ref (e.g., elapsedMsRef) that you reset synchronously when
isLoading/accountId change and have the interval callback update state from that
ref (only calling setElapsedMs inside the timer), or (B) gate the timer with a
derived loadSignal (computed in render from isLoading/accountId or a key change)
so the effect starts with the correct initial value without calling setElapsedMs
synchronously; update the useEffect that references setInterval/clearInterval
and the setElapsedMs usage accordingly.

---

Outside diff comments:
In `@app/src-tauri/src/cdp/session.rs`:
- Around line 178-220: The initial attach schedule currently falls through into
the steady-state loop and immediately calls run_session_cycle a fifth time;
update the code around INITIAL_ATTACH_SCHEDULE and the subsequent loop so that
if the for loop completes without breaking (i.e., none of the scheduled attempts
succeeded) you await sleep(ATTACH_BACKOFF) once before entering the steady-state
loop — you can detect this by recording a boolean like initial_succeeded (set
true inside the Ok branch where you break) or by checking whether the for loop
broke; ensure run_session_cycle is not called again immediately when the initial
pass failed.

In `@app/src-tauri/src/webview_accounts/mod.rs`:
- Around line 1694-1745: The code currently clears state.prewarm_accounts for
args.account_id and immediately emits a "reused" load event even if the
off-screen prewarmed WebView is still at about:blank or mid-navigation; change
the fast-path to only emit state:"reused" when the WebView is actually ready
(e.g. present in the loaded_accounts dedup set or existing.url() is not a
placeholder/blank), otherwise avoid emitting "reused" and let the normal
emit_load_finished/load lifecycle run. Concretely: after removing the prewarm
flag (state.prewarm_accounts.remove) check loaded_accounts (or inspect
existing.url() != "about:blank" and not a placeholder) before building reuse_url
and calling app.emit("webview-account:load", {"state":"reused",...}); if the
check fails, skip the emit and keep existing behavior that will allow
emit_load_finished to trigger later.

---

Nitpick comments:
In `@app/src/pages/Accounts.tsx`:
- Around line 37-58: The MRU localStorage helpers (MRU_ACCOUNT_KEY,
PREWARM_MAX_ACCOUNTS, readMruAccountId, writeMruAccountId) should be migrated to
a persisted Redux slice so MRU tracking follows the app guideline; create a new
slice (e.g., mruAccountSlice) with state for lastActiveAccountId and keep
PREWARM_MAX_ACCOUNTS where used, replace direct calls to
readMruAccountId/writeMruAccountId with a selector and an action dispatcher (and
initialize the slice by reading existing localStorage once on startup to
preserve backwards compatibility), and remove direct localStorage reads/writes
from Accounts.tsx so the component uses the persisted Redux state instead.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d24ee3a7-34cd-4a12-92fd-593c0a29a2fa

📥 Commits

Reviewing files that changed from the base of the PR and between a3a1843 and 5e7d1c5.

⛔ Files ignored due to path filters (1)
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • app/src-tauri/src/cdp/session.rs
  • app/src-tauri/src/lib.rs
  • app/src-tauri/src/webview_accounts/mod.rs
  • app/src/components/accounts/WebviewHost.tsx
  • app/src/components/accounts/__tests__/WebviewHost.test.tsx
  • app/src/pages/Accounts.tsx
  • app/src/services/webviewAccountService.ts

Comment thread app/src-tauri/src/webview_accounts/mod.rs Outdated
Comment thread app/src/components/accounts/WebviewHost.tsx Outdated
oxoxDev added 5 commits May 6, 2026 17:10
…mansai#1233)

CodeRabbit (PR tinyhumansai#1285): the previous `webview_account_prewarm` built its
own stripped-down `WebviewBuilder` — no `on_navigation`, `on_new_window`,
`on_page_load`, scanner bootstrap, or CEF notification registration.
Because `webview_account_open`'s warm-reopen branch returns early on the
later user click, none of those handlers ever got wired retroactively,
so a prewarmed account would silently miss link interception, popup
policy, timeout/load events, scanners, and native notification
bridging.

Fix: route prewarm through the same code path as a normal cold open
behind a `prewarm: bool` flag on `OpenArgs`. The cold-spawn block
forks on the flag at exactly two points — initial position+size
(off-screen 1×1 vs requested rect) and `state.prewarm_accounts`
membership (insert vs not). Everything else — handlers, scanners,
notification register — runs identically. The warm-reopen branch
short-circuits when called with `prewarm: true` against an existing
webview so the prewarm command stays idempotent without flipping the
flag or emitting `state:"reused"`.

The promote-on-click path in the warm-reopen branch is unchanged: clear
the prewarm flag, resize/show, emit `state:"reused"`. So the user
experience is the same, but the prewarmed webview is now fully
configured before the click ever happens.

Also: lib.rs's four existing `OpenArgs { ... }` constructors get
`prewarm: false` to satisfy the new mandatory field. Field is
`#[serde(default)]` so frontends that omit it from JSON keep working.

Refs tinyhumansai#1233. Addresses CodeRabbit Major review on PR tinyhumansai#1285.
…mansai#1233)

CodeRabbit (PR tinyhumansai#1285): the initial-attach retry schedule (4 attempts at
0/50/150/400ms) drops straight into the steady-state `loop` on
exhaustion, and the loop's `sleep(ATTACH_BACKOFF)` runs AFTER the cycle
call — so a fully exhausted initial schedule fires a fifth back-to-back
attach in <1s before any 2s backoff kicks in.

Move the `sleep(ATTACH_BACKOFF)` to the top of the steady loop. Now the
exhausted-initial path waits the proper 2s before its fifth attempt,
and a successful session that ends cleanly also waits the backoff
before reconnecting (avoiding tight-loop reconnects against a renderer
that just torched).

Refs tinyhumansai#1233. Addresses CodeRabbit Major review on PR tinyhumansai#1285.
…table units (tinyhumansai#1233)

Three small extractions that make the prewarm flow + loading UX cover
≥80% on diff:

- `app/src/utils/webviewAccountMru.ts` — `MRU_ACCOUNT_KEY`,
  `PREWARM_MAX_ACCOUNTS`, `readMruAccountId`, `writeMruAccountId`. Pure
  helpers extracted from `Accounts.tsx`. Both read + write swallow
  storage errors so private-mode / sandboxed environments don't break
  the page.
- `app/src/hooks/usePrewarmMostRecentAccount.ts` — single-fire on-mount
  hook that wraps the MRU prewarm gating logic (no MRU id, empty
  accounts, > PREWARM_MAX_ACCOUNTS, MRU is active, MRU already in
  pending/loading/open). `Accounts.tsx` shrinks to one call site.
- `WebviewHost.tsx` — phase-hint timer moved into a child
  `LoadingPhaseHint` component that mounts only while loading. Its
  elapsed counter resets purely via mount/unmount, so the parent
  effect no longer needs the synchronous `setElapsedMs(0)` calls
  CodeRabbit flagged as `react-hooks/set-state-in-effect`. The child
  uses a counter-based interval (no `Date.now()`), so fake-timer tests
  remain deterministic.

No behaviour change for users — every visible UX path (placeholder,
spinner from frame 1, 5s/10s phase hints, timeout overlay, MRU
prewarm gating, MRU writes on rail click + new-account pick) renders
identically.

Refs tinyhumansai#1233. Addresses CodeRabbit Quick Win review on PR tinyhumansai#1285 +
unblocks Coverage Gate via the test files added in the next commit.
…sai#1233)

Backfills the diff-cover gate for PR tinyhumansai#1285 by exercising the three new
testable units:

- `webviewAccountMru.test.ts` (7 cases) — read/write round-trip,
  overwrite, empty-storage default, both error-swallow branches
  (`getItem` + `setItem` throwing, simulating private mode / quota).
- `usePrewarmMostRecentAccount.test.tsx` (10 cases) — happy path,
  every skip branch (no MRU id, empty accounts, > PREWARM_MAX_ACCOUNTS,
  MRU not in store, MRU is active, status pending/loading/open) plus
  the still-prewarm path for status `timeout`/`error`.
- `webviewAccountService.prewarm.test.ts` (3 cases) —
  `prewarmWebviewAccount` invokes the Tauri command with snake-case
  args, swallows errors, and is a no-op outside Tauri.

37 new test cases, all green via `pnpm test:unit` + targeted file
runs. Refs tinyhumansai#1233.
`cargo check` / `cargo fmt` in the worktree refreshed the workspace
lockfile to match the version that landed in `chore(staging):
v0.53.18` (a3a1843). No dependency changes — sibling commit
`ae4b74bf` did the same for `app/src-tauri/Cargo.lock`.
@oxoxDev
Copy link
Copy Markdown
Contributor Author

oxoxDev commented May 6, 2026

Review feedback addressed

Finding Commit Fix
webview_account_prewarm reused stripped-down builder, missing handlers/scanners/notifications a5776ae4 Routed prewarm through webview_account_open via new OpenArgs.prewarm: bool. Cold path forks on flag at exactly two points (initial position+size + prewarm_accounts membership); everything else — on_navigation, on_new_window, on_page_load, scanner bootstrap, CEF notification register — runs identically. Warm-reopen short-circuits when called with prewarm: true to keep the command idempotent.
Initial CDP attach schedule had implicit 5th attempt before backoff f44b95e4 Moved sleep(ATTACH_BACKOFF) to top of steady-state reconnect loop. Exhausted initial schedule now waits 2s before its fifth attempt; successful sessions ending cleanly also wait the backoff before reconnecting.
setElapsedMs(0) synchronously inside useEffect (lint warning) 1b194cc1 Phase-hint timer extracted into LoadingPhaseHint child that mounts only while loading; counter resets via mount/unmount. Parent effect now contains zero synchronous setState calls.
Coverage Gate failed (47% on diff) 1b194cc1 + 8285bd00 Helpers extracted to utils/webviewAccountMru.ts + hooks/usePrewarmMostRecentAccount.ts. 20 new test cases across 3 files (utils 7, hook 10, service 3) covering happy + every skip branch + error swallow.
lib.rs OpenArgs constructors needed new field a5776ae4 All four sites get prewarm: false. Field is #[serde(default)] so JSON callers that omit it stay compatible.

Tip: e0469f65. All gates green locally (cargo check / fmt, pnpm typecheck / lint / format:check, focused vitest 37/37 + cargo webview_accounts 62/62 + cdp::session 11/11). Awaiting CI rerun.

Copy link
Copy Markdown
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src-tauri/src/webview_accounts/mod.rs (2)

2240-2261: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rollback prewarm_accounts when add_child fails.

prewarm_accounts is populated before add_child(...), but the error path at Line 2261 returns immediately. If child creation fails, a later non-prewarm open for the same account will still hit the suppression check in emit_load_finished, even though no prewarmed webview exists. Clear the flag before returning the error.

Suggested fix
-    let webview = parent_window
-        .add_child(builder, initial_position, initial_size)
-        .map_err(|e| format!("add_child failed: {e}"))?;
+    let webview = match parent_window.add_child(builder, initial_position, initial_size) {
+        Ok(webview) => webview,
+        Err(e) => {
+            if args.prewarm {
+                state
+                    .prewarm_accounts
+                    .lock()
+                    .unwrap()
+                    .remove(&args.account_id);
+            }
+            return Err(format!("add_child failed: {e}"));
+        }
+    };
🤖 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 `@app/src-tauri/src/webview_accounts/mod.rs` around lines 2240 - 2261, The code
inserts args.account_id into state.prewarm_accounts before calling
parent_window.add_child but does not remove it if add_child returns an error;
update the add_child error path in the function containing the prewarm logic so
that if parent_window.add_child(...) fails you remove the same account id from
state.prewarm_accounts (use the same locking/unwrapping pattern as the
insert/remove calls) before returning the Err; keep references to state,
prewarm_accounts, args.account_id, and parent_window.add_child and ensure
emit_load_finished’s suppression won’t be triggered by a leftover entry.

1720-1767: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only fast-path to reused after prewarm actually finished.

This branch promotes and emits state:"reused" for any existing prewarmed webview. If the user clicks before the background load reaches a terminal success, or after that background load already timed out, the frontend skips its normal loading/timeout path even though loaded_accounts was never set and the page may still be blank or on an error page. Gate the reuse fast path on loaded_accounts.contains(&args.account_id); when it is still false, just clear the prewarm flag and stash requested_bounds so the in-flight emit_load_finished call can reveal normally.

Suggested direction
                 let was_prewarmed = state
                     .prewarm_accounts
                     .lock()
                     .unwrap()
                     .remove(&args.account_id);
+                let prewarm_ready = state
+                    .loaded_accounts
+                    .lock()
+                    .unwrap()
+                    .contains(&args.account_id);
                 if was_prewarmed {
                     log::info!(
                         "[webview-accounts] prewarm hit account={} label={} — promoting to live",
                         args.account_id,
                         existing_label
                     );
                 }
+                if was_prewarmed && !prewarm_ready {
+                    if let Some(b) = args.bounds {
+                        state
+                            .requested_bounds
+                            .lock()
+                            .unwrap()
+                            .insert(args.account_id.clone(), b);
+                    }
+                    log::info!(
+                        "[webview-accounts] prewarm still loading account={} label={} — waiting for terminal load",
+                        args.account_id,
+                        existing_label
+                    );
+                    return Ok(existing_label);
+                }
                 if let Some(b) = args.bounds {
                     let _ = existing.set_position(LogicalPosition::new(b.x, b.y));
                     let _ = existing.set_size(LogicalSize::new(b.width, b.height));
                     state
                         .requested_bounds
🤖 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 `@app/src-tauri/src/webview_accounts/mod.rs` around lines 1720 - 1767, The
current fast-path always emits state:"reused" for prewarmed views; change it to
check loaded_accounts first: after removing from state.prewarm_accounts, query
state.loaded_accounts.lock().unwrap().contains(&args.account_id) and only
promote/emit the "reused" event when that contains() is true; if it is false,
keep the removal (clearing the prewarm flag), still stash requested_bounds
(state.requested_bounds.insert(...)) and show the existing webview but do NOT
call app.emit(... { "state":"reused" })—let the in-flight emit_load_finished
path handle the frontend reveal normally. Ensure you reference the existing
symbols: prewarm_accounts, loaded_accounts, requested_bounds, existing, and the
app.emit call when making the change.
🤖 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 `@app/src/components/accounts/WebviewHost.tsx`:
- Around line 217-223: The LoadingPhaseHint component's internal timer is being
reused across accounts because React reuses the component instance; to reset
elapsedMs per account, force a remount by adding a unique key based on accountId
to the LoadingPhaseHint usage (e.g., key={accountId} on the LoadingPhaseHint
element) so each account gets a fresh component instance and its timer starts
from zero.

In `@app/src/utils/webviewAccountMru.ts`:
- Around line 13-39: The MRU logic currently using MRU_ACCOUNT_KEY,
readMruAccountId and writeMruAccountId should be migrated into the persisted
Redux state: remove ad-hoc localStorage reads/writes and instead add a new
persisted field (e.g., lastActiveAccountId) in the Accounts slice, expose a
selector (e.g., selectLastActiveAccountId) and an action (e.g.,
setLastActiveAccountId) to update it; update any consumers that call
readMruAccountId/writeMruAccountId (and any prewarm logic that uses
PREWARM_MAX_ACCOUNTS) to read from the selector and dispatch the action so
prewarm decisions use the single persisted Redux source of truth.

---

Outside diff comments:
In `@app/src-tauri/src/webview_accounts/mod.rs`:
- Around line 2240-2261: The code inserts args.account_id into
state.prewarm_accounts before calling parent_window.add_child but does not
remove it if add_child returns an error; update the add_child error path in the
function containing the prewarm logic so that if parent_window.add_child(...)
fails you remove the same account id from state.prewarm_accounts (use the same
locking/unwrapping pattern as the insert/remove calls) before returning the Err;
keep references to state, prewarm_accounts, args.account_id, and
parent_window.add_child and ensure emit_load_finished’s suppression won’t be
triggered by a leftover entry.
- Around line 1720-1767: The current fast-path always emits state:"reused" for
prewarmed views; change it to check loaded_accounts first: after removing from
state.prewarm_accounts, query
state.loaded_accounts.lock().unwrap().contains(&args.account_id) and only
promote/emit the "reused" event when that contains() is true; if it is false,
keep the removal (clearing the prewarm flag), still stash requested_bounds
(state.requested_bounds.insert(...)) and show the existing webview but do NOT
call app.emit(... { "state":"reused" })—let the in-flight emit_load_finished
path handle the frontend reveal normally. Ensure you reference the existing
symbols: prewarm_accounts, loaded_accounts, requested_bounds, existing, and the
app.emit call when making the change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 33211337-86ca-4514-8425-9d34d7dfb900

📥 Commits

Reviewing files that changed from the base of the PR and between 5e7d1c5 and e0469f6.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • app/src-tauri/src/cdp/session.rs
  • app/src-tauri/src/lib.rs
  • app/src-tauri/src/webview_accounts/mod.rs
  • app/src/components/accounts/WebviewHost.tsx
  • app/src/hooks/__tests__/usePrewarmMostRecentAccount.test.tsx
  • app/src/hooks/usePrewarmMostRecentAccount.ts
  • app/src/pages/Accounts.tsx
  • app/src/services/__tests__/webviewAccountService.prewarm.test.ts
  • app/src/utils/__tests__/webviewAccountMru.test.ts
  • app/src/utils/webviewAccountMru.ts

Comment thread app/src/components/accounts/WebviewHost.tsx
Comment thread app/src/utils/webviewAccountMru.ts Outdated
oxoxDev added 2 commits May 6, 2026 17:28
tinyhumansai#1233)

CodeRabbit (PR tinyhumansai#1285): React reuses the `LoadingPhaseHint` instance
across rapid account switches because it sits at the same position in
the WebviewHost render tree. The internal `elapsedMs` counter then
carried over from the previous account, so a new account in a slow
load could jump straight to "Restoring session..." or "Almost ready..."
instead of starting at zero.

Pass `key={accountId}` so React unmounts the hint when the account id
changes, forcing a fresh mount with `elapsedMs = 0` on every new
loading run.

Refs tinyhumansai#1233. Addresses CodeRabbit Quick Win review on PR tinyhumansai#1285.
…lStorage helpers (tinyhumansai#1233)

CodeRabbit (PR tinyhumansai#1285): the previous cut introduced a separate
`localStorage` channel for the most-recently-active account id, which
breaks the repo convention from CLAUDE.md ("Use Redux (persisted where
configured) over ad-hoc `localStorage` for state management in the
frontend") and creates a hidden state read/write the rest of Accounts
doesn't see.

Migrate to the existing persisted Accounts slice:

- `AccountsState.lastActiveAccountId: string | null` — new field on the
  slice, persisted via the `lastActiveAccountId` whitelist entry on
  `accountsPersistConfig` in `store/index.ts`. Reuses the same
  redux-persist transport as `accounts` / `order` / `activeAccountId`,
  so the field survives sessions through the same channel.
- `setLastActiveAccount` reducer + `selectLastActiveAccountId`
  selector exported from `accountsSlice.ts`.
- `removeAccount` clears the MRU pointer when the deleted account was
  the last-active one (mirrors the `activeAccountId` fallback to
  `state.order[0]`) so the next session can't try to prewarm a gone
  account.
- `Accounts.tsx`'s `selectAccount` + `handlePickProvider` callbacks
  now dispatch `setLastActiveAccount` instead of writing to
  `localStorage`.
- `usePrewarmMostRecentAccount` reads the MRU id via
  `useAppSelector(selectLastActiveAccountId)`. Constants
  `PREWARM_MAX_ACCOUNTS` moved into the hook module so its single
  consumer doesn't need a separate utils file.
- Removed `app/src/utils/webviewAccountMru.ts` + its tests; coverage
  for the helper functions is no longer needed because they don't
  exist. Hook tests rebased on the Redux store.

Behaviour-only refactor — every user-visible flow (prewarm gate, MRU
write on rail click + new-account pick, cleanup on remove) is
preserved, and persistence now travels through the same channel as
the rest of the slice.

Refs tinyhumansai#1233. Addresses CodeRabbit Major review on PR tinyhumansai#1285.
@oxoxDev
Copy link
Copy Markdown
Contributor Author

oxoxDev commented May 6, 2026

Round 2 review feedback addressed

Finding Commit Fix
LoadingPhaseHint carries elapsedMs across rapid account switches 20675a10 key={accountId} forces remount per account so the elapsed counter always starts from zero on a new loading run.
MRU stored in localStorage instead of persisted Redux (state-management convention violation) 0450f4f7 Added AccountsState.lastActiveAccountId, setLastActiveAccount reducer, selectLastActiveAccountId selector, and the lastActiveAccountId whitelist entry on accountsPersistConfig. removeAccount clears the MRU when the deleted account was the last-active one. Accounts.tsx callbacks dispatch the action instead of writing to localStorage; the prewarm hook reads the id via useAppSelector. Deleted app/src/utils/webviewAccountMru.ts + its tests — single source of truth lives in the slice now.

Tip: 0450f4f7. All gates green locally (cargo check / fmt, pnpm typecheck / lint / format:check, focused vitest 44/44 across hook + WebviewHost + service.prewarm + service.loadListener + accountsSlice.core).

Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
app/src/components/accounts/WebviewHost.tsx (1)

102-108: ⚡ Quick win

Log when this host enters the timeout state.

The new timeout/loading UX is a key state transition for this PR, but this branch only logs the retry click. A small useEffect on isTimeout would make slow-open reports much easier to correlate in production logs.

🪵 Minimal addition
   const isLoading = status === undefined || LOADING_STATUSES.has(status);
   const isTimeout = status === 'timeout';
   const providerName = PROVIDER_COPY[provider] ?? 'app';
+
+  useEffect(() => {
+    if (!isTimeout) return;
+    log('timeout overlay visible account=%s provider=%s status=%s', accountId, provider, status);
+  }, [accountId, isTimeout, provider, status]);

As per coding guidelines: "Add substantial, development-oriented logs at entry/exit points, branch decisions, external calls, retries/timeouts, state transitions, and error handling paths; use namespaced debug logs in production app code."

🤖 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 `@app/src/components/accounts/WebviewHost.tsx` around lines 102 - 108, In
WebviewHost, add a useEffect that watches the isTimeout boolean and emits a
namespaced debug/log entry when isTimeout transitions to true (and optionally
when it transitions away) so slow-open events are recorded; reference the
existing isTimeout and isLoading variables and include contextual identifiers
(e.g., account id or status) in the log message and use the component's standard
logger (e.g., debug or the app logger used elsewhere in the file) to follow the
project's logging guidelines.
🤖 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.

Nitpick comments:
In `@app/src/components/accounts/WebviewHost.tsx`:
- Around line 102-108: In WebviewHost, add a useEffect that watches the
isTimeout boolean and emits a namespaced debug/log entry when isTimeout
transitions to true (and optionally when it transitions away) so slow-open
events are recorded; reference the existing isTimeout and isLoading variables
and include contextual identifiers (e.g., account id or status) in the log
message and use the component's standard logger (e.g., debug or the app logger
used elsewhere in the file) to follow the project's logging guidelines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00fdbcf9-37d9-4731-81bc-71d874c8ec02

📥 Commits

Reviewing files that changed from the base of the PR and between e0469f6 and 0450f4f.

📒 Files selected for processing (7)
  • app/src/components/accounts/WebviewHost.tsx
  • app/src/hooks/__tests__/usePrewarmMostRecentAccount.test.tsx
  • app/src/hooks/usePrewarmMostRecentAccount.ts
  • app/src/pages/Accounts.tsx
  • app/src/store/accountsSlice.ts
  • app/src/store/index.ts
  • app/src/types/accounts.ts

@senamakel senamakel merged commit 44c8b83 into tinyhumansai:main May 6, 2026
20 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.

Sidebar account rail tooltip overlapped by embedded webview Embedded web apps take too long to load

2 participants