Skip to content

Stabilize account secret detail refresh - #75

Merged
kentcdodds merged 6 commits into
mainfrom
cursor/secret-detail-view-update-03be
Mar 28, 2026
Merged

kentcdodds merged 6 commits into
mainfrom
cursor/secret-detail-view-update-03be

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Mar 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • make account-secret detail refresh resilient to in-flight navigation changes by tracking the active request separately from the last successfully loaded location
  • only mark a secret-detail location as loaded after a successful fetch, so failed requests keep the page eligible to retry instead of freezing on stale state
  • add a focused Playwright regression that verifies switching secrets updates the detail pane without a full document reload

Testing

  • PLAYWRIGHT_BASE_URL=http://127.0.0.1:3742 npx playwright test e2e/account-secrets.spec.ts
  • manual browser verification switching between multiple secrets on /account/secrets
Open in Web Open in Cursor 

Summary by CodeRabbit

  • Tests

    • Added an end-to-end test that verifies switching between account secrets updates the detail view without a full page reload and preserves in-page state.
  • Bug Fixes

    • Improved data-loading and staleness handling for account secrets: prevents redundant reloads, tracks failures with delayed retry, and keeps loading/error indicators consistent during rapid switches.

@coderabbitai

coderabbitai Bot commented Mar 28, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Playwright E2E spec that verifies switching between user-scoped secret detail views updates the UI without a full page reload, and refactors the account-secrets route to use monotonic request IDs, loading/failure keys, and retry logic to ignore stale fetches and avoid redundant loads.

Changes

Cohort / File(s) Summary
E2E Test
e2e/account-secrets.spec.ts
Adds a Playwright spec that creates two user-scoped secrets via POST /account/secrets.json, navigates to the first secret detail, switches to the second via the UI, and asserts UI updates without a full page reload by checking a window.__secretRouteMarker and UI fields.
Account Secrets Route
packages/worker/client/routes/account-secrets.tsx
Reworks loadAccountSecrets to use a monotonic loadRequestId, loadingDataKey, lastFailedDataKey, and retryTimeout; adds staleness guards returning early for outdated responses; schedules a 3s retry on failure; refines queueing to avoid redundant loads when a load is in-flight or already failed.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(55,125,255,0.5)
    participant Browser as Browser (UI)
    participant Route as AccountSecrets Route
    participant Queue as Render Queue / Scheduler
    participant Server as API Server
  end

  Browser->>Route: navigate/select secret (dataKey A)
  Route->>Queue: queueTask(loadAccountSecrets, dataKeyA)
  Queue->>Route: run loadAccountSecrets (requestId 1)
  Route->>Server: fetch /account/secrets.json?dataKey=A
  Server-->>Route: respond with secrets A
  alt requestId == latest && dataKey matches
    Route->>Browser: update UI with secret A
    Route->>Route: set lastLoadedDataKey = dataKeyA
  else stale response
    Route-->>Route: ignore response
  end

  Browser->>Route: select secret B
  Route->>Queue: queueTask(loadAccountSecrets, dataKeyB)
  Queue->>Route: run loadAccountSecrets (requestId 2)
  Route->>Server: fetch /account/secrets.json?dataKey=B
  Server-->>Route: respond with secrets B
  alt requestId == latest && dataKey matches
    Route->>Browser: update UI with secret B (no full reload)
    Route->>Route: set lastLoadedDataKey = dataKeyB
  else failure
    Route->>Route: record lastFailedDataKey and schedule retry
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇
I hopped between two secret doors,
Saved whispers in their little drawers,
A tiny marker stayed so near,
Views swapped quick — no reload here,
I twitched my nose and clapped my paws.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Stabilize account secret detail refresh' clearly and concisely summarizes the main purpose of the PR: improving resilience and reliability of the account secret detail refresh mechanism.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/secret-detail-view-update-03be

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.

@cursor cursor Bot changed the title Fix account secret detail routing Stabilize account secret detail refresh Mar 28, 2026
@kentcdodds
kentcdodds marked this pull request as ready for review March 28, 2026 05:03
cursoragent and others added 2 commits March 28, 2026 05:05
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@cursor
cursor Bot force-pushed the cursor/secret-detail-view-update-03be branch from 8e6f687 to 8271f76 Compare March 28, 2026 05:05
@github-actions

github-actions Bot commented Mar 28, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-75.kentcdodds.workers.dev

Worker: kody-pr-75
D1: kody-pr-75-db
KV: kody-pr-75-oauth-kv

Mocks:

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

🧹 Nitpick comments (2)
e2e/account-secrets.spec.ts (2)

63-65: Static analysis false positive - but could use exact string matching.

The static analysis flagged potential ReDoS risk. In this case, secondSecret.name contains only alphanumeric characters and hyphens (from the Date.now().toString(36) nonce), so it's safe. However, you could avoid the regex entirely for cleaner assertions.

♻️ Alternative using exact string match
-	await expect(page).toHaveURL(
-		new RegExp(`/account/secrets/user/${secondSecret.name}$`),
-	)
+	await expect(page).toHaveURL(`/account/secrets/user/${secondSecret.name}`)

Note: toHaveURL with a string performs substring matching by default. If you need exact path matching, keeping the regex with $ anchor is fine given the controlled input.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/account-secrets.spec.ts` around lines 63 - 65, Replace the regex-based
URL assertion in the toHaveURL call to avoid the ReDoS false positive by using
an exact string match built from the known-safe secret name; locate the test
using page.toHaveURL(...) that references secondSecret.name and change it to
assert the exact path string (e.g.,
`/account/secrets/user/${secondSecret.name}`) so the matcher performs a direct
string comparison instead of a RegExp.

3-27: Consider using Playwright's idiomatic response assertion.

The saveSecret helper works correctly. Minor improvement: Playwright provides a built-in toBeOK() matcher for response assertions.

♻️ Suggested change
-	expect(response.ok()).toBeTruthy()
+	await expect(response).toBeOK()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/account-secrets.spec.ts` around lines 3 - 27, The helper saveSecret uses
a raw assertion expect(response.ok()).toBeTruthy(); replace this with
Playwright's idiomatic response matcher by asserting the Response object
directly (await expect(response).toBeOK()) after the page.request.post call;
update the assertion in saveSecret to use expect(response).toBeOK() so
Playwright provides better diagnostics and timing, keeping the function name
saveSecret and the page.request.post call unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@e2e/account-secrets.spec.ts`:
- Around line 63-65: Replace the regex-based URL assertion in the toHaveURL call
to avoid the ReDoS false positive by using an exact string match built from the
known-safe secret name; locate the test using page.toHaveURL(...) that
references secondSecret.name and change it to assert the exact path string
(e.g., `/account/secrets/user/${secondSecret.name}`) so the matcher performs a
direct string comparison instead of a RegExp.
- Around line 3-27: The helper saveSecret uses a raw assertion
expect(response.ok()).toBeTruthy(); replace this with Playwright's idiomatic
response matcher by asserting the Response object directly (await
expect(response).toBeOK()) after the page.request.post call; update the
assertion in saveSecret to use expect(response).toBeOK() so Playwright provides
better diagnostics and timing, keeping the function name saveSecret and the
page.request.post call unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7719959d-e08f-4b79-8182-edf1d5fc6ec3

📥 Commits

Reviewing files that changed from the base of the PR and between 290fe9f and 8e6f687.

📒 Files selected for processing (2)
  • e2e/account-secrets.spec.ts
  • packages/worker/client/routes/account-secrets.tsx

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/worker/client/routes/account-secrets.tsx`:
- Around line 438-445: The code is incorrectly marking failed fetches as loaded
by setting lastLoadedDataKey in the catch path; remove or move the assignment to
lastLoadedDataKey so it is only set on successful loads (e.g., after the
successful fetch/processing code path), keep setting status = 'error' inside the
catch, and ensure the requestId/loadRequestId and
getDataRefreshKey(getCurrentHref()) checks remain to avoid racing updates
(referencing lastLoadedDataKey, status, requestId, loadRequestId,
getDataRefreshKey, getCurrentHref, dataKey).
🪄 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

Run ID: 86646ee8-d442-4e8d-ade1-978ffdc18edd

📥 Commits

Reviewing files that changed from the base of the PR and between 8e6f687 and 8271f76.

📒 Files selected for processing (2)
  • e2e/account-secrets.spec.ts
  • packages/worker/client/routes/account-secrets.tsx

Comment thread packages/worker/client/routes/account-secrets.tsx
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

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

Caution

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

⚠️ Outside diff range comments (1)
packages/worker/client/routes/account-secrets.tsx (1)

430-436: ⚠️ Potential issue | 🟠 Major

Re-check staleness after JSON parse before applying payload.

There’s still a race window after await readJson(...): if navigation changes during parse, stale data can be applied because no guard runs between parse completion and applyPayload(...).

💡 Suggested fix
 			const payload = await readJson<AccountSecretsPayload>(response)
+			if (
+				requestId !== loadRequestId ||
+				getDataRefreshKey(getCurrentHref()) !== dataKey
+			)
+				return
 			if (!response.ok || !payload?.ok) {
 				throw new Error('Unable to load your secrets.')
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/client/routes/account-secrets.tsx` around lines 430 - 436,
After awaiting readJson(response) there is a race where navigation could change
and stale data might be applied; after parsing the payload re-check that
response.ok and the current dataKey still match lastLoadedDataKey/current
selection before calling applyPayload. Specifically, keep the existing variables
(response, dataKey, lastLoadedDataKey, payload, selection) and, once payload is
parsed, validate response.ok and that dataKey still equals the expected current
key (or that lastLoadedDataKey hasn't changed) and payload?.ok; only then call
applyPayload(payload, selection, null), otherwise abort/return to avoid applying
stale data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/worker/client/routes/account-secrets.tsx`:
- Around line 701-707: The refresh gate currently treats any change between
currentDataKey and lastLoadedDataKey as eligible for retry, causing tight loops
after failures; add a failure-tracking guard (e.g., lastFailedDataKey or
failedDataKeyWithTimestamp) and update it in the fetch failure path so the retry
condition for isRefreshingForLocationChange only returns true if the
currentDataKey differs from lastLoadedDataKey AND is not the recently failed key
(or sufficient backoff time has elapsed). Concretely: change the computed guard
around status/currentDataKey/lastLoadedDataKey/loadingDataKey (the lines that
define isRefreshingForLocationChange and the if that checks
status/isRefreshingForLocationChange/isLoadingCurrentLocation) to consult the
new failure marker, and in the fetch logic set that failure marker on error and
clear it on success (and optionally record a timestamp to implement backoff).

---

Outside diff comments:
In `@packages/worker/client/routes/account-secrets.tsx`:
- Around line 430-436: After awaiting readJson(response) there is a race where
navigation could change and stale data might be applied; after parsing the
payload re-check that response.ok and the current dataKey still match
lastLoadedDataKey/current selection before calling applyPayload. Specifically,
keep the existing variables (response, dataKey, lastLoadedDataKey, payload,
selection) and, once payload is parsed, validate response.ok and that dataKey
still equals the expected current key (or that lastLoadedDataKey hasn't changed)
and payload?.ok; only then call applyPayload(payload, selection, null),
otherwise abort/return to avoid applying stale data.
🪄 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

Run ID: d728e1d2-e45b-4de3-9e9a-0991c4de3290

📥 Commits

Reviewing files that changed from the base of the PR and between 8271f76 and f7176dd.

📒 Files selected for processing (1)
  • packages/worker/client/routes/account-secrets.tsx

Comment thread packages/worker/client/routes/account-secrets.tsx
Comment thread packages/worker/client/routes/account-secrets.tsx
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Comment thread packages/worker/client/routes/account-secrets.tsx Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Retry timeout race loses auto-retry for second failure
    • Cleared any existing retry timeout before scheduling a new one so the latest failure always triggers an auto-retry.

Comment thread packages/worker/client/routes/account-secrets.tsx
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/worker/client/routes/account-secrets.tsx`:
- Around line 721-730: The current boolean folds failed-key suppression into
isRefreshingForLocationChange causing the route to stop showing as refreshing
after a failed switch; split the logic: introduce isStaleForCurrentLocation =
status !== 'loading' && currentDataKey !== lastLoadedDataKey (do NOT check
lastFailedDataKey) and keep isRefreshingForLocationChange as the variant that
additionally checks currentDataKey !== lastFailedDataKey (and loadingDataKey
check stays the same). Replace uses that guard stale-UI or stale-detail actions
to use isStaleForCurrentLocation (while leaving retry-suppression/refresh logic
using isRefreshingForLocationChange) so stale detail content is disabled until a
successful refresh re-runs; reference getDataRefreshKey, currentHref, status,
lastLoadedDataKey, lastFailedDataKey, loadingDataKey, currentDataKey,
isRefreshingForLocationChange and new isStaleForCurrentLocation 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c572b47-409e-4adc-8030-9e0c2d30d39e

📥 Commits

Reviewing files that changed from the base of the PR and between dbba38d and 7eee19d.

📒 Files selected for processing (1)
  • packages/worker/client/routes/account-secrets.tsx

Comment on lines +721 to +730
const currentDataKey = getDataRefreshKey(currentHref)
const isRefreshingForLocationChange =
status !== 'loading' &&
getDataRefreshKey(currentHref) !== lastLoadedDataKey
if (status === 'loading' || isRefreshingForLocationChange) {
currentDataKey !== lastLoadedDataKey &&
currentDataKey !== lastFailedDataKey
const isLoadingCurrentLocation = loadingDataKey === currentDataKey
if (
(status === 'loading' || isRefreshingForLocationChange) &&
!isLoadingCurrentLocation
) {

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

Split “stale view” from “retry suppression” to avoid stale-detail actions after failed switch.

At Line 724-Line 725, failed-key suppression is folded into isRefreshingForLocationChange. That makes the route appear “not refreshing” immediately after a failed switch, even though currentDataKey !== lastLoadedDataKey. This can leave stale detail content actionable until retry re-runs.

💡 Suggested fix
-		const isRefreshingForLocationChange =
-			status !== 'loading' &&
-			currentDataKey !== lastLoadedDataKey &&
-			currentDataKey !== lastFailedDataKey
+		const isStaleForCurrentLocation = currentDataKey !== lastLoadedDataKey
+		const isRetrySuppressedForFailedKey = currentDataKey === lastFailedDataKey
+		const isRefreshingForLocationChange =
+			status !== 'loading' &&
+			isStaleForCurrentLocation &&
+			!isRetrySuppressedForFailedKey
 		const isLoadingCurrentLocation = loadingDataKey === currentDataKey

Then use isStaleForCurrentLocation (not isRefreshingForLocationChange) for stale-UI guards where needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/client/routes/account-secrets.tsx` around lines 721 - 730,
The current boolean folds failed-key suppression into
isRefreshingForLocationChange causing the route to stop showing as refreshing
after a failed switch; split the logic: introduce isStaleForCurrentLocation =
status !== 'loading' && currentDataKey !== lastLoadedDataKey (do NOT check
lastFailedDataKey) and keep isRefreshingForLocationChange as the variant that
additionally checks currentDataKey !== lastFailedDataKey (and loadingDataKey
check stays the same). Replace uses that guard stale-UI or stale-detail actions
to use isStaleForCurrentLocation (while leaving retry-suppression/refresh logic
using isRefreshingForLocationChange) so stale detail content is disabled until a
successful refresh re-runs; reference getDataRefreshKey, currentHref, status,
lastLoadedDataKey, lastFailedDataKey, loadingDataKey, currentDataKey,
isRefreshingForLocationChange and new isStaleForCurrentLocation when making the
change.

@kentcdodds
kentcdodds merged commit 81255f7 into main Mar 28, 2026
9 checks passed
@kody-bot
kody-bot deleted the cursor/secret-detail-view-update-03be branch March 29, 2026 20:43
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