Skip to content

feat(frontend/recs): auto-refresh on page open when stale; drop freshness indicator + Refresh button (closes #284)#285

Merged
cristim merged 4 commits into
feat/multicloud-web-frontendfrom
feat/recs-autorefresh-284
May 5, 2026
Merged

feat(frontend/recs): auto-refresh on page open when stale; drop freshness indicator + Refresh button (closes #284)#285
cristim merged 4 commits into
feat/multicloud-web-frontendfrom
feat/recs-autorefresh-284

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 5, 2026

Summary

Closes #284.

The Recommendations page now auto-refreshes its cached data on every page open when the cache is cold (never collected) or older than 24 hours. The freshness timestamp badge and the explicit Refresh button are removed; a toast is the only user-visible feedback channel.

Behaviour change

Before After
Static freshness pill ("Data from 3h ago") above the table Removed
Explicit "Refresh" button inside the freshness bar Removed
User had to notice staleness and click Refresh manually Auto-refresh fires on load when cache is ≥24h stale
Collection errors surfaced via dismissible banner Surfaced as an error toast on cold/stale page loads

Why drop the indicator + button

The freshness badge is UX overhead the user doesn't want to think about — the right behaviour is for the data to be fresh automatically. The auto-refresh with a toast ("Refreshing recommendations…") gives the user enough signal that numbers may move in a moment, without requiring any action.

The 24h threshold (STALE_THRESHOLD_MS) is a named constant at module scope with a doc comment. Operators wanting to tune it can expose it via the /api/public-info endpoint in a follow-up (noted as a TODO in the code).

What's kept

  • refreshRecommendations() stays exported from frontend/src/api/recommendations.ts — the auto-refresh logic and any future programmatic callers still need it.
  • freshness.ts and renderFreshness() are untouched — the Dashboard page still uses them via dashboard-freshness.

Test coverage

Five new tests in describe('auto-refresh on page open (#284)'):

  1. Cold cachelast_collected_at: null → refresh fires + in-flight toast
  2. Stale cache (25h ago) → refresh fires + in-flight toast
  3. Fresh cache (1h ago) → refresh does NOT fire + no toast
  4. Cold + collection error → error toast surfaces the message
  5. Refresh failure → error toast with message

Pre-existing tests are unprotected against the new toast calls by resetting the freshness mock to "fresh (1h ago)" in beforeEach, so no existing test needed changing.

Test plan

  • npm run typecheck — clean
  • npm test -- --testPathPattern=recommendations — 152 tests pass (5 new)
  • npm test (full suite, 40 suites / 1466 tests) — all pass

Summary by CodeRabbit

  • New Features

    • Recommendations now auto-refresh in the background when cached data is missing or stale; a persistent “Refreshing recommendations…” info toast appears during refresh and is replaced by success or error toasts.
  • Removed

    • The freshness indicator element was removed from the Recommendations tab.
  • Tests

    • Added tests for auto-refresh behavior, toast content/persistence, deduplication of concurrent refreshes, and improved test harness stability.

…ness indicator + Refresh button (closes #284)

- Add `STALE_THRESHOLD_MS = 24h` constant at module scope
- Add `triggerAutoRefreshIfStale()` helper: checks freshness API and
  fires `refreshRecommendationsAPI()` as fire-and-forget when cache is
  null or older than 24h; shows in-flight / success / failure toasts
- Wire the helper into `loadRecommendations()` replacing the old
  `renderFreshness('recommendations-freshness', loadRecommendations)` call
- Remove `<section id="recommendations-freshness">` DOM node from index.html
  (dashboard-freshness section remains; freshness.ts is kept for it)
- Import `getRecommendationsFreshness` and `refreshRecommendations` from
  `./api/recommendations` directly; drop the unused `renderFreshness` import
- Add 5 new tests in `describe('auto-refresh on page open (#284)')`:
  cold cache, stale cache, fresh cache, cold+error, refresh failure
- Mock `../toast` in the test file; reset freshness mock to "fresh" in
  beforeEach so existing tests are unaffected by the new toast calls
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a20731a8-ae75-443d-bd7d-01cc9345a379

📥 Commits

Reviewing files that changed from the base of the PR and between 979d774 and 5093c1e.

📒 Files selected for processing (1)
  • frontend/src/recommendations.ts

📝 Walkthrough

Walkthrough

Auto-refreshes recommendations on page open when cached data is >24h or absent, shows in-flight and result toasts (including collection errors), removes the freshness UI element, and adds tests and a test helper to control the auto-refresh in-flight guard. (≤50 words)

Changes

Auto-Refresh on Page Open

Layer / File(s) Summary
Configuration
frontend/src/recommendations.ts
Add export const STALE_THRESHOLD_MS (24h) and autoRefreshInFlight dedupe guard.
Core Logic
frontend/src/recommendations.ts
Add triggerAutoRefreshIfStale() that calls getRecommendationsFreshness(), treats null or age > STALE_THRESHOLD_MS as stale, surfaces last_collection_error, and fire-and-forget calls refreshRecommendationsAPI() with in-flight/success/error toast handling and dedupe.
Test Hook
frontend/src/recommendations.ts
Export resetAutoRefreshInFlight() to clear the dedupe guard for tests.
Integration / Loader
frontend/src/recommendations.ts
Replace prior renderFreshness(...) usage in loadRecommendations() with void triggerAutoRefreshIfStale() after rendering the list.
UI Cleanup
frontend/src/index.html
Remove #recommendations-freshness DOM element (freshness indicator).
Tests / Mocks
frontend/src/__tests__/recommendations.test.ts
Update ../api/recommendations mock to default to a fresh last_collected_at (1h ago); add ../toast mock (mockShowToast + dismiss handle); import/export recsApi and resetAutoRefreshInFlight; add tests for cold/stale/fresh paths, collection errors, refresh failures, dedupe, and toast persistence; reset mocks in beforeEach.

Sequence Diagram

sequenceDiagram
    participant User
    participant Page as Recommendations Page
    participant Toast as Toast System
    participant API as Recommendations API

    User->>Page: open recommendations
    Page->>API: getRecommendationsFreshness()
    API-->>Page: last_collected_at, last_collection_error

    alt cold or stale (>24h)
        Page->>Toast: show "Refreshing recommendations…" (timeout: null)
        Page->>API: refreshRecommendations() [async, deduped]
        alt refresh succeeds
            API-->>Page: refreshed results
            Page->>Toast: show success summary
            Page->>Page: reload recommendations list
        else refresh fails
            API-->>Page: rejection
            Page->>Toast: show error (includes rejection message)
        end
    else fresh (<24h)
        Note over Page: no refresh, no toast
    end

    alt last_collection_error present (cold/stale)
        Page->>Toast: show error toast with collection error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • LeanerCloud/CUDly#276: Modifies frontend/src/recommendations.ts / rendering behavior; overlaps with refresh/load flow changes.

Poem

🐰 I hop on the page and sniff the air,
Old crumbs? I nudge them with a caring flair.
A toast pops up: "Refreshing, hold tight!"
Then numbers bloom fresh — what a delight.
No badge, just carrots and code running right.

🚥 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 accurately describes the main changes: auto-refresh on stale cache, removal of freshness indicator and Refresh button, and links to the related issue #284.
Linked Issues check ✅ Passed All coding requirements from issue #284 are met: auto-refresh with 24h threshold, freshness indicator/button removal, collection error toast surfacing, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #284 requirements; no unrelated modifications to other features or components detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 feat/recs-autorefresh-284

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

@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/m Days type/feat New capability labels May 5, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@frontend/src/recommendations.ts`:
- Around line 149-153: The in-flight toast created by showToast (stored in
inFlight) should remain visible until refreshRecommendationsAPI() completes;
remove the fixed timeout (do not pass timeout: 4_000) or set timeout to
null/undefined so it doesn't auto-dismiss, and explicitly dismiss the toast
after the refresh settles by awaiting refreshRecommendationsAPI() and calling
inFlight.dismiss() (or the toast API's close method) in both success and error
paths; update the code around the inFlight variable and the
refreshRecommendationsAPI() call to ensure the toast is always cleaned up.
- Around line 125-177: triggerAutoRefreshIfStale can start multiple parallel
refreshes; add a module-scoped guard (e.g., let refreshInFlight: Promise<void> |
null) and use it inside triggerAutoRefreshIfStale to deduplicate: if
refreshInFlight exists reuse it instead of calling refreshRecommendationsAPI
again, otherwise set refreshInFlight = refreshRecommendationsAPI(), attach
.then/.catch/.finally to dismiss toasts and clear refreshInFlight when done;
update references to the in-flight toast (inFlight) so only one
info/success/error toast is shown per logical refresh and ensure refreshInFlight
is cleared in finally so future refreshes can proceed.
🪄 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: 8330a0bb-f154-405e-ba13-9fdf4efc9b99

📥 Commits

Reviewing files that changed from the base of the PR and between bb619f2 and e4218c9.

📒 Files selected for processing (3)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/index.html
  • frontend/src/recommendations.ts
💤 Files with no reviewable changes (1)
  • frontend/src/index.html

Comment thread frontend/src/recommendations.ts
Comment thread frontend/src/recommendations.ts
…dedup + persistent in-flight toast

Two CR findings addressed:

1. Dedup concurrent stale-load refreshes (#285, major): add a module-level
   `autoRefreshInFlight: Promise<void> | null` guard. If a background refresh
   is already running when a second stale load fires, the second call skips
   the API call (and its toast) entirely. The guard is cleared in `.finally()`
   so the next page open after the refresh settles works normally.

2. Persistent in-flight toast (#285, minor): the "Refreshing recommendations…"
   toast previously had `timeout: 4_000` and disappeared after 4 s even when
   the refresh was still running. Changed to `timeout: null` — the
   `.then()`/`.catch()` paths already call `inFlight.dismiss()` so the toast
   stays visible until the refresh actually settles.

Tests: two new cases in the `auto-refresh on page open (#284)` describe block —
one asserting `refreshRecommendations` is called exactly once when two stale
loads race, one asserting the in-flight toast has `timeout: null`.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 5, 2026

CodeRabbit findings addressed — commit e1d924b

Finding 1 (Major) — Dedup concurrent stale-load refreshes

Added a module-level let autoRefreshInFlight: Promise<void> | null = null; guard in frontend/src/recommendations.ts. triggerAutoRefreshIfStale() now returns early if a refresh is already in flight. The guard is cleared in .finally() so the next page open after the refresh settles works normally. This prevents parallel refreshRecommendationsAPI() calls and duplicate toasts when the user reopens the page before the first refresh completes.

Finding 2 (Minor) — Persistent in-flight toast

Changed the in-flight "Refreshing recommendations…" toast from timeout: 4_000 to timeout: null. The .then()/.catch() paths already call inFlight.dismiss() explicitly, so the toast stays visible for the full duration of the refresh (which can exceed 28 s in practice).

Tests

Two new tests in the auto-refresh on page open (#284) describe block:

  • Dedup test: mocks a pending refresh promise, fires two stale loadRecommendations() calls without awaiting the first, asserts refreshRecommendations was called exactly once.
  • Persistent toast test: asserts the in-flight showToast call has timeout: null, not a numeric value.

All 154 tests pass; npm run typecheck clean.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Tip

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

@cristim: Both fixes look solid:

  • Dedup guard — The autoRefreshInFlight module-level sentinel + .finally() cleanup is the right pattern here. It prevents duplicate API calls and double toasts on rapid page-switch/reopen before a refresh settles.
  • Persistent toasttimeout: null is the correct choice given the refresh can run well beyond 4 s; explicit .dismiss() in .then()/.catch() ensures it's always cleaned up regardless of outcome.

Triggering a fresh review of the updated commits now.

(◕‿◕✿)

[review]

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@frontend/src/recommendations.ts`:
- Around line 149-152: The current isStale calculation can treat malformed
timestamps as non-stale because new Date(freshness.last_collected_at).getTime()
returns NaN; change the logic in the block that computes isStale (using
freshness.last_collected_at and STALE_THRESHOLD_MS) to first parse the timestamp
into a numeric value (e.g., const ts = new
Date(freshness.last_collected_at).getTime() or Date.parse(...)) and then treat
the entry as stale if freshness.last_collected_at is null OR Number.isNaN(ts) OR
Date.now() - ts > STALE_THRESHOLD_MS so malformed/invalid timestamps are
considered stale.
🪄 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: a61dc3d9-fd9a-40a7-bc8a-bd1c4bd20196

📥 Commits

Reviewing files that changed from the base of the PR and between e4218c9 and e1d924b.

📒 Files selected for processing (2)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/recommendations.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/tests/recommendations.test.ts

Comment thread frontend/src/recommendations.ts Outdated
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@frontend/src/recommendations.ts`:
- Around line 179-186: The success branch for autoRefreshInFlight currently only
calls inFlight.dismiss() and showToast after refreshRecommendationsAPI()
resolves, but it must also reload the UI data so users see fresh
recommendations: after refreshRecommendationsAPI() resolves, call the existing
client-side fetch that populates the recommendations list (e.g., invoke the
function used elsewhere to load recommendations such as fetchRecommendations or
loadRecommendationsForCurrentView) and await or chain it before dismissing the
inFlight state / showing the toast (or optionally poll the refresh status then
call that loader); update both occurrences (the block around
refreshRecommendationsAPI() at the shown lines and the similar block at 263-265)
to trigger the same reload call so the UI reflects the refreshed cache.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

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: c7c05afb-4ee4-4e5d-85fb-8b0502d30076

📥 Commits

Reviewing files that changed from the base of the PR and between e1d924b and 979d774.

📒 Files selected for processing (1)
  • frontend/src/recommendations.ts

Comment thread frontend/src/recommendations.ts
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 1 unresolved review comment.

Files modified:

  • frontend/src/recommendations.ts

Commit: 5093c1ee79f203823362089e33e0f408f170ee63

The changes have been pushed to the feat/recs-autorefresh-284 branch.

Time taken: 4m 33s

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⚠️ Branch updated during autofix.

The branch was updated while autofix was in progress. Please try again.

Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim cristim merged commit 9f8c739 into feat/multicloud-web-frontend May 5, 2026
6 checks passed
@cristim cristim deleted the feat/recs-autorefresh-284 branch May 5, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant