Repository navigation
fix(frontend): show a first-run empty state instead of "no rows match" - #1389
Conversation
… these filters" With no accounts and no recommendations, Opportunities rendered an empty 15-column table with "No rows match these filters.", $0 summary cards and a Purchase bar that could never enable, and Home had no pointer anywhere to connecting an account. New users were told to adjust filters they never set. Opportunities now tells "nothing loaded" apart from "filtered to zero". When nothing loaded and no column filter narrowed the fetch, a shared .empty-state card replaces the table (summary cards, filter bar and action bar are hidden): - no accounts: "No cloud accounts connected yet" with a "Connect a cloud account" button to Admin > Accounts for admins, or a pointer to an admin for everyone else; - accounts but no recommendations: "No opportunities yet" with "Refresh recommendations" and "Manage cloud accounts"; - topbar provider/account filter active: explains the empty scope. The card shows the last collection time (or that one is running / never ran, plus any collection error) from the freshness endpoint. Filtered-to-zero keeps the table hint and adds a "Clear filters" button. The shared clear-filters helper re-fetches when a server-pushed-down column (service/region/account) was filtered, since the loaded set itself was narrowed; the existing Clear filters badge uses it too. Home shows the same card above the KPI tiles when no recommendations loaded and nothing is committed. Navigation to Admin > Accounts is injected from app.ts so the new module does not import navigation.ts (import cycle). Refs LeanerCloud#756 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hint Unit tests for the shared card (copy per account/scope state, admin vs non-admin actions, freshness line, escaping) and for the Opportunities wiring: chrome hidden on a first run and restored once rows load, account fetch failure not reported as "no accounts", and Clear filters rerendering for client-side filters but re-fetching for pushed-down ones. The old "no recommendations" test now expects the onboarding card. Refs LeanerCloud#756 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home: card shown on a first run, hidden when recommendations or commitments exist or when the recommendations fetch failed, refresh reloads Home. Playwright: first-run card on Opportunities and Home, Connect a cloud account lands on /admin/accounts, and filtered-to-zero keeps the table hint with a working Clear filters button. The existing zero-result spec now expects the hint to carry that button. Refs LeanerCloud#756 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 13 billable files and costs up to $3.25. Or wait 59 minutes for your next included review. View limit details
Comment |
Conflicts: - frontend/src/app.ts (imports): kept both, LeanerCloud#756's setOpenAccountsHandler from first-run-empty-state and main's renderInitError from init-error (LeanerCloud#1338). The setOpenAccountsHandler call in setupEventListeners and main's renderInitError in init()'s catch merged cleanly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - frontend/src/app.ts: import block only. This branch imports setOpenAccountsHandler (first-run card's Admin > Accounts navigation); main's LeanerCloud#1336 imports initPurchasingNav. Kept both imports; both call sites merged cleanly and are unchanged. Also checked: main's LeanerCloud#1397 envelope typing for getUpcomingPurchases sits next to this branch's 4th allSettled entry (listAccountsMinimal) in loadDashboard and merged cleanly. LeanerCloud#1368's "Select none" rename is the per-column popover button, separate from this branch's "Clear filters" (clearAllColumnFilters) action. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - frontend/src/app.ts: import block. Kept this branch's setOpenAccountsHandler import (first-run empty state, LeanerCloud#756) and main's setupMobileNav import from nav/mobile-nav (LeanerCloud#1340). The rest of app.ts merged cleanly; main's removal of the inline drawer code stands and the setOpenAccountsHandler wiring is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE Blocking:
|
|
Local verification at
|
|
Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at |
Brings in LeanerCloud#1365, LeanerCloud#1381, LeanerCloud#1378 and LeanerCloud#1389. No conflicts; index.html auto-merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - frontend/src/app.ts: adjacent imports; kept both this branch's wireTablistKeyboard import and main's setOpenAccountsHandler import (LeanerCloud#1389 first-run empty state). - frontend/src/index.html: #home-tab keeps this branch's markup (no role=tabpanel/aria-labelledby, hidden page heading as the first child) and main's #home-onboarding section (LeanerCloud#1389) right after the heading, before #summary. The new "open Accounts" handler from main switches pages programmatically (no focusContent), the same as the existing RI Exchange settings shortcut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- frontend/src/dashboard.ts:
- imports: kept this branch's filterByEnabledProviders import and all of
main's new imports (upcoming-scope LeanerCloud#1365, sparse-trend helpers LeanerCloud#1334,
chart-a11y LeanerCloud#1381, first-run empty state LeanerCloud#1389).
- loadDashboard Promise.allSettled: combined both sides into five
fetches; this branch's loadEnabledProviders() (LeanerCloud#180) plus main's
listAccountsMinimal() for the first-run onboarding card (LeanerCloud#1389).
- call site: kept renderDashboardSummary(summaryData, recs, recsCount)
and main's renderHomeOnboarding(...). The onboarding check uses the
Enabled-Providers-filtered recs, matching the Opportunities first-run
check, which also runs on the preference-filtered list.
- helpers: kept both loadEnabledProviders() and main's
renderHomeOnboarding(); renderDashboardSummary keeps this branch's
recsCount parameter.
- savings tile detail: this branch's filtered count with main's
NO_VALUE placeholder (LeanerCloud#1376): recsCount ?? total_recommendations ??
NO_VALUE; Active Commitments takes main's NO_VALUE.
- frontend/src/recommendations.ts: import block; main's NO_VALUE added to
the utils import, kept this branch's enabled-providers import.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved: - frontend/src/app.ts: import block only. Kept this branch's fanout-retry import and main's wireTablistKeyboard (LeanerCloud#730/LeanerCloud#1371) and setOpenAccountsHandler (LeanerCloud#1389) imports; the code using each merged cleanly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a fresh install (no accounts, no recommendations), Opportunities showed an empty 15-column table with "No rows match these filters.", $0 summary cards and a Purchase bar, and Home showed
--tiles with nothing pointing at connecting an account. This PR tells "nothing loaded" apart from "filtered to zero": the first case gets an onboarding card on Opportunities and Home, and the second keeps the table hint and adds a "Clear filters" button.Refs #756
Partial: the card shows the last collection time (or a running collection, or the last error) but not the next collection time the issue asks for, because the API has no field for it yet.
What changed
frontend/src/first-run-empty-state.tsbuilds one.empty-statecard used on both pages. Copy depends on state:It also shows the last collection time, a running collection, or the last collection error from
GET /api/recommendations/freshness.frontend/src/recommendations.ts:renderRecommendationsListshows the card when nothing loaded and no column filter is set, and hides the summary cards, filter bar and action box (they are still rendered, so state is correct once rows arrive). When filters leave zero rows, the old hint stays and gets a "Clear filters" button. A sharedclearAllColumnFiltersre-fetches when a server-pushed-down column (service, region, account) was filtered; the existing Clear filters badge uses it too.frontend/src/dashboard.tsandindex.html:loadDashboardalso fetches the minimal account list (a failure is treated as unknown) and fills a new#home-onboardingsection above the KPI tiles when no recommendations loaded and nothing is committed.frontend/src/app.tsinjects the Admin > Accounts navigation (sameswitchTab+switchSettingsSubTabpattern as the RI Exchange settings shortcut).styles/base.cssandstyles/layout.cssfor the action row and spacing.__tests__/first-run-empty-state.test.ts,__tests__/recommendations-first-run.test.ts,__tests__/dashboard-onboarding.test.ts,tests-e2e/first-run-empty-state.spec.ts;recommendations.test.tsandtests-e2e/recommendations.spec.tsupdated.Why this approach
"Nothing loaded" is distinguished from "filtered to zero" by the loaded count plus whether any column filter is set. A pushed-down service or region filter can make the loaded count 0, so it must not trigger onboarding. The Purchase bar and $0 cards are hidden rather than removed, which keeps the existing permission-gating code and tests valid. Account presence comes from the existing
/accounts/list, so no new endpoints are needed, and a failed account fetch is treated as unknown so nobody is told "no accounts" because of a transient error. Navigation is injected fromapp.tsbecausenavigation.tsalready importsdashboardandrecommendations; importing it would create a cycle and pull the whole navigation graph into jest suites that mock those modules.Screenshots
Before is main (
a14dc8f2), after is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API, with accounts[]and recommendations[]for the first-run views.--tiles and empty charts. On this branch, the same onboarding card above the KPI tiles.Verification
Run on this branch's head
b6b6afeaon top of maina14dc8f2:npm run lint: 0 errors (142 pre-existing warnings). Typecheck and build pass.--maxWorkers=2): 112 suites, 3514 passed, 1 skipped, 0 failed. The 3 new suites (28 tests: 15 + 7 + 6) also pass with--randomize.e2e-local.sh: 159 passed, 1 failed (therecommendations.speczero-result test expected the exact old hint text). That spec was updated to expect the new Clear filters button, andrecommendations.specwas re-run: 4/4 passed. The 3 new e2e tests cover the Opportunities card with "Connect a cloud account" landing on/admin/accounts, the Home card, and filtered-to-zero Clear filters.scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.Notes for review
last_collected_at,last_collection_started_atandlast_collection_error, andcollection_scheduleis just a word such as "daily". A next-run time needs a backend field.active_commitmentsis 0, so a failed fetch never shows onboarding. If theenabled_providerssetting hides every row, the card says "No opportunities yet".recommendations.tsandapp.tswere already far over the 500-line rule, so the new logic is in its own module.Overlaps
loadDashboard/renderDashboardSummaryindashboard.ts; fix(frontend): three money formatters, mixed placeholders, left-aligned money columns #739 changesrenderDashboardSummary.renderRecommendationsListinrecommendations.ts.app.tsinit near where the accounts navigator is registered.index.html; this branch adds one section at the top of#home-tab.styles/base.css.Note for the UI owner
This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.
🤖 Generated with Claude Code