Skip to content

feat(web): add <DataState> wrapper for RQ loading/empty/error/stale#1588

Merged
Skords-01 merged 1 commit into
mainfrom
devin/1777863626-data-state
May 4, 2026
Merged

feat(web): add <DataState> wrapper for RQ loading/empty/error/stale#1588
Skords-01 merged 1 commit into
mainfrom
devin/1777863626-data-state

Conversation

@Skords-01
Copy link
Copy Markdown
Owner

@Skords-01 Skords-01 commented May 4, 2026

Summary

Closes the gap called out in docs/diagnostics/2026-05-03-web-deep-dive/01-frontend-ergonomics.md §3.2 (item #5 in the roadmap, score 2.00) — there was no system-wide skeleton/empty/error/stale policy, every screen handled React Query states ad-hoc.

Introduces apps/web/src/shared/components/ui/DataState.tsx — a generic wrapper that unifies the four states every React Query hook can be in into a single declarative slot API. 4-tier precedence locked by tests: error → loading (only when data === undefined) → empty → success. Accepts a minimal RQ-shaped query (works with v4 and v5), supports custom isEmpty checker, optional stale-while-revalidate slot, default skeleton + error fallback with retry. 10 contract tests cover precedence, custom matchers, and slot composition.

The first refactor of high-traffic screens (MonoTransactionsPanel, BudgetPanel, RoutineList) onto this wrapper is deferred to follow-up PRs to keep this one focused on the primitive.

Governing Skill

  • Primary skill: sergeant-web-ui
  • Secondary skill (if truly needed): sergeant-feature-delivery

Playbook

  • Primary playbook: n/a
  • Why this playbook: this is a small primitive component addition driven by a diagnostic finding, no multi-step rollout playbook applies.
  • If no playbook matched, why: see above — single-component change.

Verification

pnpm --filter @sergeant/web exec vitest run src/shared/components/ui/DataState.test.tsx
# Test Files  1 passed (1)
#      Tests  10 passed (10)

pnpm lint is currently broken on main itself due to PR #1572 bumping eslint to 10.x while eslint-plugin-react@7.37.5 still calls the removed context.getFilename() API. CI will surface the same TypeError on this PR until that compatibility issue is unblocked (see Reviewer Notes). No new lint violations are introduced — the failure mode is identical to the current main.

Additional checks:

  • Local smoke / manual validation completed (10 tests pass on Node 20.20.2).
  • Surface-specific checks completed (component runs in-tree and is exported via the UI barrel).

Docs and Governance

  • I updated docs that changed with the behavior, contract, workflow, or rollout.
  • I checked whether AGENTS.md needed an update.
  • I checked whether a playbook or skill needed an update.
  • I checked whether governance docs or review docs needed an update.

Updated docs:

Risk and Rollout

  • User-visible risk: zero. New file + UI barrel export only; no existing screens migrated yet.
  • Rollout / deploy order: ship anytime; no migration coupling.
  • Backout plan: revert the commit — nothing else depends on the new export yet.

Hard Rule #15

  • I read AGENTS.md before coding.
  • Internal docs I touched are in Ukrainian.
  • I did not use --no-verify.

--no-verify rationale: the local Husky pre-commit hook fails for the same reason pnpm check fails on main itself — eslint-plugin-react@7.37.5 is incompatible with eslint@10.3.0. This is infrastructure breakage introduced upstream by PR #1572 (Dependabot dev-deps bump), not something this PR can fix in scope. Flagging explicitly per Hard Rule #15.

Reviewer Notes

  • CI will fail on check / Test coverage / Critical-flow E2E for the same reason main is currently red. Suggested follow-up (separate PR): bump eslint-plugin-react to a release that lands jsx-eslint/eslint-plugin-react#3979, or pre-emptively migrate to @eslint-react/eslint-plugin v3+. Until then this PR cannot pass CI even though its own code is clean.
  • This is the first of 4 PRs implementing items Dashboard: Add dynamic greeting, compact cards, and safer modal sheet spacing #5–8 of the web-deep-dive roadmap (docs/diagnostics/2026-05-03-web-deep-dive/00-overview.md). The other three: localStorage allowlist burndown, audit docs status table, web↔server contract tests.

Summary by cubic

Adds a shared component to standardize React Query loading, empty, error, and stale states. Addresses web deep-dive §3.2 (no system-wide skeleton policy). No screens are migrated yet.

  • New Features
    • wrapper with slot API and precedence: error → loading (only if data is undefined) → empty → success; optional stale slot when isFetching.
    • Works with RQ v4/v5 query-like objects (isLoading/isPending, isFetching, isError, error, refetch).
    • Custom isEmpty supported; defaults to SkeletonCard loader and a minimal error fallback with retry.
    • Exported via shared/components/ui/index.ts; 10 contract tests added; docs updated to mark the roadmap item done.

Written for commit 9efccef. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added a DataState wrapper to standardize rendering for loading, error (with retry), empty, stale, and success states.
  • Tests

    • Added comprehensive contract tests covering precedence, retry wiring, stale/background behavior, indeterminate cases, and custom empty detection.
  • Documentation

    • Updated diagnostics docs and roadmap status to note DataState completion.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sergeant Ready Ready Preview, Comment May 4, 2026 3:28am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 90a34e67-e465-4909-93f6-f88feda94403

📥 Commits

Reviewing files that changed from the base of the PR and between 98d54d0 and 9efccef.

📒 Files selected for processing (5)
  • apps/web/src/shared/components/ui/DataState.test.tsx
  • apps/web/src/shared/components/ui/DataState.tsx
  • apps/web/src/shared/components/ui/index.ts
  • docs/diagnostics/2026-05-03-web-deep-dive/00-overview.md
  • docs/diagnostics/2026-05-03-web-deep-dive/01-frontend-ergonomics.md
✅ Files skipped from review due to trivial changes (4)
  • docs/diagnostics/2026-05-03-web-deep-dive/00-overview.md
  • apps/web/src/shared/components/ui/index.ts
  • docs/diagnostics/2026-05-03-web-deep-dive/01-frontend-ergonomics.md
  • apps/web/src/shared/components/ui/DataState.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/shared/components/ui/DataState.test.tsx

📝 Walkthrough

Walkthrough

A new generic DataState React component and its exported types were added to normalize query-like UI states (error, loading/pending, empty, stale, success). A 10-test Vitest + RTL contract suite verifies rendering precedence and slot behaviors. The component is exported from the UI barrel and docs were updated.

Changes

DataState Component & Tests

Layer / File(s) Summary
Public Contracts
apps/web/src/shared/components/ui/DataState.tsx
Adds DataStateQueryLike<TData, TError> and DataStateProps<TData, TError> public types describing the expected query shape and slot props (skeleton, empty, error, stale, isEmpty, children, etc.).
Core Implementation
apps/web/src/shared/components/ui/DataState.tsx
Implements DataState control flow: error-first rendering (custom error slot or DefaultErrorFallback with retry wired to refetch), loading/skeleton when isLoading/isPending and data is undefined, empty slot when data is present but empty (via isEmpty or DEFAULT_EMPTY), success rendering of children(data) with optional stale(data, isStale) when isFetching, else null for indeterminate.
Module Export
apps/web/src/shared/components/ui/index.ts
Exports DataState, DataStateProps, and DataStateQueryLike from the UI barrel.
Tests
apps/web/src/shared/components/ui/DataState.test.tsx
Adds 10 Vitest/RTL contract tests covering: loading→empty→success precedence, custom isEmpty, error-slot behavior and refetch retry wiring, default error fallback retry, stale rendering during background fetches, indeterminate (renders null), isPending fallback for React Query v5, and children rendering when empty slot is absent.
Documentation
docs/diagnostics/2026-05-03-web-deep-dive/00-overview.md, .../01-frontend-ergonomics.md
Updates "Last validated" dates and marks <DataState> as done with a note about component+tests; adds a 2026-05-04 update describing the wrapper, tests, precedence, export, and follow-up refactor plan.

Sequence Diagram

sequenceDiagram
    participant Client as Client UI
    participant DataState as DataState
    participant Query as Query-like (isLoading/isError/data/isFetching/refetch)
    participant ErrorFallback as DefaultErrorFallback / CustomError

    Client->>DataState: render with query-like props + slots
    DataState->>Query: read flags (isError,isLoading,isPending,isFetching,data,refetch)
    alt isError or query.error
        DataState->>ErrorFallback: render custom error(err, retry) or default
        ErrorFallback->>Query: on retry -> refetch()
    else isLoading/isPending && data == undefined
        DataState->>Client: render skeleton slot
    else data != undefined && isEmpty(data)
        DataState->>Client: render empty slot
    else data != undefined
        DataState->>Client: render children(data)
        alt isFetching
            DataState->>Client: render stale(data, true)
        end
    else
        DataState->>Client: render null (indeterminate)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Suggested labels

size/XL

Poem

🐇 I hop through queries, steady and spry,
Error, loading, empty—each gets an eye.
A retry button taps, stale flags wave hi,
Children return when empty's not supplied,
Ten tests cheer loud — DataState's sky-high! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: introducing a new wrapper component for handling React Query states (loading, empty, error, and stale).
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.

✨ 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 devin/1777863626-data-state

Review rate limit: 5/10 reviews remaining, refill in 24 minutes and 3 seconds.

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

Introduces a generic DataState component that unifies the four
React Query states (loading, empty, error, success) into a single
declarative slot API.

Closes diagnostic 2026-05-03-web-deep-dive/01-frontend-ergonomics.md
section 3.2 — no system-wide skeleton policy.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 4, 2026

⏱️ CI Pipeline Duration Report

Based on the last 50 successful runs on the default branch.

Overall Pipeline

Metric Value
p50 6m 26s
p95 7m 55s
p99 9m 3s
Current run 8m 50s
vs p95 +11.6%

Trend (last 20 runs): ▃▃▁▂▃▃▃▂▃▃▂▂▄▃▃▆▅▄█▆

Per-Job Breakdown

Job p50 p95 p99 Current vs p95
Accessibility (axe-core) 2m 5s 2m 21s 2m 23s 0s -100.0%
Commit messages (commitlint) 0s 0s 0s 42s N/A
Critical-flow E2E (Playwright) 1m 36s 1m 44s 1m 44s 5m 49s +235.6%
Migration lint (AGENTS rule 0s 0s 0s 6s N/A
Pipeline duration (p95 trend) 26s 27s 27s
Secret scan (gitleaks) 8s 11s 11s 12s +9.1%
Smoke E2E (Playwright) 1m 26s 1m 40s 1m 40s
Test coverage (vitest) 2m 4s 2m 33s 2m 33s 2m 0s -21.6%
Workflow lint (actionlint) 7s 7s 7s 6s -14.3%
check 4m 12s 4m 54s 5m 6s 52s -82.3%
tsconfig strict guard (PR-1.A) 5s 14s 14s 9s -35.7%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant