Uh oh!
There was an error while loading. Please reload this page.
[audit] 7-day cache TTL + top-of-page re-audit bar - #428
Conversation
Both audit caches now enforce a hard 7-day TTL (per-transcript cache bumps schema 2→3 to add `cachedAt`; dashboard cache reuses the existing `isCacheStale` helper). Adds a `TopAuditBar` above the IdentitySection with last-audit time, an amber "expires in Xh" chip inside the last 24h of the window, and a `[ re-audit ]` button; an expired-cache banner takes its place when the user lands past the TTL. Lifts the rerun state from `return-section.tsx` into `audit-dashboard.tsx` so the top bar and bottom button share state and the success path soft-refreshes the dashboard cache instead of `window.location.reload()`. Adds a sticky `AuditProgressStrip` with elapsed timer + RerunError-keyed error copy during the run. Uses the existing `.share-btn`, `--accent-pink`, `--amber`, dashed-border vocabulary throughout; no new fonts or colours. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Updates docs/cli/audit.mdx and docs/dashboard.mdx to reflect the
behaviour change: both audit caches reject on read past 7 days; new
top-of-page [ re-audit ] bar with last-audit timestamp and amber
"expires in Xh" chip; sticky pink progress strip during the run with
error-keyed copy on failure; soft-refresh on success (no full reload).
The translated docs/{zh,pt-br,it,he,hi,…} mirrors will pick this up
through the existing translation job (see #427).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughImplements 7-day TTL cache invalidation for per-transcript and dashboard caches, exposes dashboard cache metadata to distinguish expired vs first-run empty states, centralizes rerun orchestration (TopAuditBar + AuditProgressStrip + dashboard wiring), updates fetch timeout signal handling, and adds tests, styles, docs, and changelog entries. ChangesAudit cache TTL with rerun UI refactor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
docs/cli/audit.mdxESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. docs/dashboard.mdxESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. lib/fetch-with-timeout.tsESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.
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. Comment |
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
…meout Replace manual AbortController + setTimeout with platform AbortSignal.timeout() (typed TimeoutError reason) composed with AbortSignal.any() to forward caller-supplied init.signal — the prior spread silently dropped it, so component-unmount cancellations couldn't stop in-flight fetches. Harden isAbortError to duck-type on .name (matches cross-realm errors and jsdom's polyfilled DOMException which is not instanceof Error). New tests cover happy path, composed- signal forwarding, TimeoutError firing, external-signal abort, and already-aborted short-circuit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/dashboard.mdx (1)
62-62:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAudit section still describes a CLI flow that docs now deprecate.
Lines 62 and 71 refer to the
failproofai auditCLI and “supported flags,” which conflicts with the updated/audit-page-first guidance. Please align phrasing so users don’t expect a runnable audit subcommand.Also applies to: 71-71
🤖 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 `@docs/dashboard.mdx` at line 62, Update the copy that references the deprecated CLI command `failproofai audit` and any mention of “supported flags” to instead point users to the web-based `/audit` page; specifically, replace the phrase “Runs the same scan as the `failproofai audit` CLI” with something like “Runs the same scan as the /audit page” and remove or rephrase any “supported flags” wording (also update the second occurrence of `failproofai audit` near the later paragraph) so the dashboard text consistently directs users to the /audit page rather than implying a runnable CLI subcommand.
🧹 Nitpick comments (1)
app/audit/_components/top-audit-bar.tsx (1)
133-138: 💤 Low valueMinor: redundant null check.
The
remainingMs !== nullguard on line 133 is redundant—expiringSoon(computed on lines 73–77) already ensuresremainingMs !== nullwhen true. You can simplify to:-{expiringSoon && remainingMs !== null && (+{expiringSoon && ( <div className="top-audit-bar__center" aria-live="polite">🤖 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/audit/_components/top-audit-bar.tsx` around lines 133 - 138, Remove the redundant null check by using only the expiringSoon guard in the render: when expiringSoon is true it already implies remainingMs !== null (computed earlier), so simplify the conditional from "expiringSoon && remainingMs !== null && (...)" to "expiringSoon && (...)" in the component that renders the expiry message (referencing expiringSoon, remainingMs, timeUntilExpiry and cachedAtMs in top-audit-bar.tsx).
🤖 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 `@CHANGELOG.md`:
- Around line 5-13: The CHANGELOG.md entries under the "Fixes", "Docs", and
"Features" sections (the long-form paragraphs referencing
lib/fetch-with-timeout.ts:25, isAbortError,
__tests__/lib/fetch-with-timeout.test.ts, docs/dashboard.mdx,
src/audit/{cache.ts,dashboard-cache.ts},
app/audit/_components/top-audit-bar.tsx, AuditProgressStrip, and the various
__tests__/audit/* files) must be collapsed into concise single-line bullets and
each appended with the PR number in the format "(`#428`)"; replace each multi-line
paragraph with a one-line summary that mentions the main change (e.g., "Fix:
prevent Next.js 16 dev-overlay 'signal is aborted without reason' warning and
harden isAbortError — use AbortSignal.timeout + AbortSignal.any (`#428`)"),
likewise compress the Docs and Features paragraphs into one-liners referencing
their main effect and append "(`#428`)".
In `@docs/cli/audit.mdx`:
- Line 50: The documentation incorrectly lists cachedAt as part of the cache
key; update the sentence in docs/cli/audit.mdx so the per-transcript cache is
described as keyed by (mtime, size, engineVersion, detectorVersion) and state
that cachedAt is stored as TTL/expiration metadata (used to reject entries older
than 7 days) rather than as a key component, ensuring the wording no longer
implies cache invalidation happens due to cachedAt changes.
In `@docs/dashboard.mdx`:
- Line 68: Update the example command string shown as "$ failproof policy add
<slug>" to use the correct executable name "failproofai" so the docs match the
rest of the documentation and the command will work for users; locate the
occurrence of the literal command string in docs/dashboard.mdx (the Findings
card example) and replace it accordingly.
In `@lib/fetch-with-timeout.ts`:
- Around line 10-22: Update the runtime compatibility note in
lib/fetch-with-timeout.ts: replace the incorrect “Both APIs… (Node 20.3+)” claim
with accurate minima for the two APIs (for example state that
AbortSignal.timeout is available in Node 20.3+ while AbortSignal.any was
backported to Node 18.17+, or simply say “Node 18.17+/20.3+ depending on API”),
and remove the outdated “Next.js 15 / Node 18” wording—ensure the text
references the exact API names AbortSignal.timeout and AbortSignal.any and the
repo’s Next.js target (Next.js 16 requiring Node >=20.9.0) so the doc matches
the project requirements.
In `@src/audit/cache.ts`:
- Line 107: Replace the loose typeof check for cachedAt with a finite-number
check: ensure entry.cachedAt is a finite numeric value (use
Number.isFinite(entry.cachedAt)) and still verify TTL (Date.now() -
entry.cachedAt > CACHE_TTL_MS) so Infinity/NaN are rejected; update the
conditional that currently reads the typeof check to use Number.isFinite for
entry.cachedAt and keep the CACHE_TTL_MS comparison.
In `@src/audit/dashboard-cache.ts`:
- Around line 135-137: readDashboardCacheMeta is only checking cachedAt and can
let schema-invalid entries pass through; update the logic in the
readDashboardCacheMeta (src/audit/dashboard-cache.ts) to validate the parsed
object against the full DashboardCacheEntry schema (e.g., using an existing type
guard/validator or add a new isValidDashboardCacheEntry function) before
returning { cachedAt: entry.cachedAt }; if the object fails the full-schema
check return null so downstream logic (app/actions/get-audit-result.ts) cannot
misclassify invalid entries as expired.
---
Outside diff comments:
In `@docs/dashboard.mdx`:
- Line 62: Update the copy that references the deprecated CLI command
`failproofai audit` and any mention of “supported flags” to instead point users
to the web-based `/audit` page; specifically, replace the phrase “Runs the same
scan as the `failproofai audit` CLI” with something like “Runs the same scan as
the /audit page” and remove or rephrase any “supported flags” wording (also
update the second occurrence of `failproofai audit` near the later paragraph) so
the dashboard text consistently directs users to the /audit page rather than
implying a runnable CLI subcommand.
---
Nitpick comments:
In `@app/audit/_components/top-audit-bar.tsx`:
- Around line 133-138: Remove the redundant null check by using only the
expiringSoon guard in the render: when expiringSoon is true it already implies
remainingMs !== null (computed earlier), so simplify the conditional from
"expiringSoon && remainingMs !== null && (...)" to "expiringSoon && (...)" in
the component that renders the expiry message (referencing expiringSoon,
remainingMs, timeUntilExpiry and cachedAtMs in top-audit-bar.tsx).
🪄 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: 5eadd1bc-62e8-48ce-8cb9-b77352b3456d
📒 Files selected for processing (18)
CHANGELOG.md__tests__/audit/cache.test.ts__tests__/audit/dashboard-cache.test.ts__tests__/audit/top-audit-bar.test.tsx__tests__/lib/fetch-with-timeout.test.tsapp/actions/get-audit-result.tsapp/audit/_components/audit-dashboard.tsxapp/audit/_components/audit-progress-strip.tsxapp/audit/_components/rerun-button.tsxapp/audit/_components/return-section.tsxapp/audit/_components/top-audit-bar.tsxapp/audit/audit-styles.cssapp/audit/page.tsxdocs/cli/audit.mdxdocs/dashboard.mdxlib/fetch-with-timeout.tssrc/audit/cache.tssrc/audit/dashboard-cache.ts
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- src/audit/cache.ts: use Number.isFinite(cachedAt) so malformed JSON Infinity/NaN can't bypass the 7d TTL and pin a stale entry forever. - src/audit/dashboard-cache.ts: readDashboardCacheMeta now gates on DASHBOARD_CACHE_SCHEMA_VERSION + parseable timestamp before surfacing the entry as "expired"; schema-incompatible caches now fall through to the first-run empty copy instead of triggering the wrong banner. - docs/cli/audit.mdx: describe cachedAt as TTL metadata rather than a cache-key component. - docs/dashboard.mdx: fix the Findings example command "$ failproof" → "$ failproofai". - lib/fetch-with-timeout.ts: tighten the runtime compatibility note to reference the actual AbortSignal.timeout (Node 17.3+) and AbortSignal.any (Node 20.3+, backported to 18.17) minima against Next.js 16's Node >= 20.9 floor. - CHANGELOG.md: append (#428) to all three 0.0.11-beta.6 entries (Fixes, Docs, Features) per the changelog convention. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
Summary
This PR bundles three related changes to the audit page:
1. 7-day cache TTL on both audit caches
src/audit/cache.ts) gains acachedAtfield and aCACHE_TTL_MS = 7dreject-on-read check; schema bumps2 → 3so v2 entries (nocachedAt) force a clean re-scan.Number.isFiniteguards the timestamp so a malformed JSONInfinity/NaNcan't pin a stale entry forever.src/audit/dashboard-cache.ts) reuses the existingisCacheStale(cachedAt, 7d)helper on the read path.getAuditResultAction()already mapsnullto{status: "empty"}, so/auditfalls through to the empty state past expiry. NewreadDashboardCacheMeta()helper bypasses the TTL but gates on schema version + parseable timestamp first — schema-incompatible caches fall through to the first-run empty copy instead of triggering the wrong banner.2. Top-of-page re-audit bar + sticky progress strip
TopAuditBar(app/audit/_components/top-audit-bar.tsx) renders as the first child of.report— three modes (cached, expired, empty). Shows relative-time (audited 3d ago), an amberexpires in 14h — re-audit to refreshchip when within 24h of the TTL boundary, and a[ re-audit ]button reusing.share-btn.return-section.tsxintoaudit-dashboard.tsx— the top bar and bottom button share a singlestartRerun(source)handler with PostHogsourcetagging (top_bar/return_section/empty_state); concurrent clicks impossible.AuditProgressStripis pinned to the top of the viewport during a run (pink hard-offset shadow matching.share-btn:hover, mm:ss elapsed timer, CSS-only edge pulse). Red error variant onRerunError.kind(timeout/network/post_failed) with a[ dismiss ]chip.getAuditResultAction()— no morewindow.location.reload().--accent-pink,--amber,--accent-green, dashed--linedividers,━━glyphs, lowercase mono, bracketed[ ... ]button labels. All new motion respectsprefers-reduced-motion: reduce.3. fetch-with-timeout: silence the dev-overlay AbortError warning
AbortController + setTimeoutinlib/fetch-with-timeout.tswith platformAbortSignal.timeout()(typedTimeoutErrorreason) composed withAbortSignal.any()to forward caller-suppliedinit.signal. Fixes two bugs at once:controller.abort()ran with no reason argument.{ ...init, signal: controller.signal }was silently dropping any caller-suppliedinit.signal, so component-unmount cancellations couldn't stop in-flight fetches.AbortSignal.anyalso backported to Node 18.17 — and Chrome 116+ / Firefox 124+ / Safari 17.4+).isAbortErroris hardened to duck-type on.nameso it correctly classifies cross-realm errors and jsdom's polyfilled DOMException (which is notinstanceof Error). Production callers (requestLoginCode,triggerRun,auth-dialog.tsx) keep their existing timeout-vs-network discrimination.CodeRabbit review feedback (addressed in c0a4c41)
Number.isFinite(cachedAt)guard added to the per-transcript cache.readDashboardCacheMetabefore surfacing "expired".cachedAtreworded as TTL metadata (not cache-key) indocs/cli/audit.mdx.failproof policy add→failproofai policy addtypo fixed indocs/dashboard.mdx.lib/fetch-with-timeout.tstightened to reference per-API Node minima against Next.js 16's Node ≥ 20.9 floor.0.0.11-beta.6CHANGELOG entries now end with(#428).Docs updated in
docs/dashboard.mdx+docs/cli/audit.mdx; CHANGELOG.md0.0.11-beta.6entry under### Features,### Fixes, and### Docs. Mergedorigin/main(PR #429 goldfish-classifier fix) — combined CHANGELOG### Fixesnow lists both #429 and #428 entries.Test plan
bun run test:run— 1857 passed (0 failed) across 97 files; new__tests__/audit/cache.test.ts(9),__tests__/audit/top-audit-bar.test.tsx(12),__tests__/lib/fetch-with-timeout.test.ts(13), and +4 cases in__tests__/audit/dashboard-cache.test.ts.bun x tsc --noEmitclean.bun run lintclean (only pre-existing<img>warnings unrelated to this PR).bun run dev→/auditafter a recent run, click the top[ re-audit ], confirm sticky pink strip + soft-refresh (no full reload). Confirm the dev console no longer shows "signal is aborted without reason" while the polling loop runs.~/.failproofai/audit-dashboard.json'scachedAtto 8 days ago, reload/audit, expect empty state +audit expiredbanner./api/audit/runin DevTools → confirm red error strip with the correct copy, no full reload.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests