Skip to content

feat(console): wire payroll to the real CSV run lifecycle#67

Merged
hitakshiA merged 1 commit into
mainfrom
codex/66-payroll
Jul 11, 2026
Merged

feat(console): wire payroll to the real CSV run lifecycle#67
hitakshiA merged 1 commit into
mainfrom
codex/66-payroll

Conversation

@hitakshiA

@hitakshiA hitakshiA commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Epic: #58 · Step E (Payroll — flagship) · Closes #66

Rewires the Payroll screen off its demo-era model (contractors + rate cards + 4 prove-* steps) onto the backend's real CSV run → start → live progress lifecycle. This is the "encrypted payroll, live on our L1" path.

What changed

  • lib/api.tscreatePayrollRun(orgId, {csv, token?})POST /orgs/:id/payroll; getPayrollRun(runId)GET /payroll/:runId; startPayrollRun/pausePayrollRun/resumePayrollRun(runId)POST /payroll/:runId/{start,pause,resume}; subscribePayrollProgress(runId, …) — 2s polling of getPayrollRun (the server's dual-mode SSE can't set Accept from EventSource; polling returns the same progress counts), stopping on terminal status. Removed the demo-era fns (payrolls list, createPayroll, approvePayroll, the four prove-*, payslips) — zero dead references.
  • lib/store.tsx — dropped the payrolls batch read (the backend has no list-runs endpoint); the Payroll screen owns its run state.
  • screens/Payroll.tsx — compose (CSV / rows) → validate (createPayrollRun, shows summary + per-row resolved/failed) → start → live progress (pending → proving → submitted → confirmed, with pause/resume) → complete. Handles treasury_underfunded (amount short + link to Treasury). Persists the active run id per org and reattaches on reload. Trimmed ~1200 → ~540 lines.
  • packages/typesPayrollRun, PayrollProgressCounts, run-create + item types matching the backend; dropped the demo-era PayrollBatch/PayrollLine/proof types from the real path.
  • Dashboard — adjusted to summary data (no longer reads the removed payroll list).
  • DemoVITE_DEMO_MODE=1 validates a seeded CSV and animates the progress counts to completion.

Verification (all green)

typecheck · test (70) · build · VITE_DEMO_MODE=1 build · biome lint .

Summary by CodeRabbit

  • New Features

    • Added a CSV-based payroll run workflow supporting USDC and EURC.
    • Validate payroll details and review recipient-level statuses before starting.
    • Start, pause, resume, and monitor payroll runs with live progress updates.
    • Automatically reconnect to in-progress runs after returning to the page.
    • Added clear progress summaries, status indicators, and treasury funding alerts.
  • Bug Fixes

    • Improved API error messaging, including actionable underfunded treasury details.
    • Updated dashboard payroll setup indicators to reflect scheduled and recent runs.

Greptile Summary

This PR rewires console payroll to the CSV run lifecycle. The main changes are:

  • New payroll run API methods for create, fetch, start, pause, resume, and progress polling.
  • A rebuilt Payroll screen for CSV validation, persisted active runs, and live progress.
  • Updated shared payroll types for runs, items, progress counts, and underfunded errors.
  • Store and dashboard updates for the removed payroll list.
  • Demo-mode payroll data and timers for the new run flow.

Confidence Score: 4/5

The payroll flow is close, but demo pause handling can put completed runs back into an active state.

  • A late pause can overwrite a completed demo run with paused.
  • Comma-formatted demo amounts are rejected before normalization.
  • The dashboard setup check can miss completed demo payroll runs.

apps/console/src/demo/api.ts, apps/console/src/screens/Dashboard.tsx

Important Files Changed

Filename Overview
apps/console/src/screens/Payroll.tsx Rebuilds payroll around CSV validation, active run persistence, progress polling, and start/pause/resume controls.
apps/console/src/lib/api.ts Adds payroll run endpoints, structured API errors, underfunded detection, and polling-based progress subscriptions.
apps/console/src/demo/api.ts Adds demo CSV parsing and timer-driven payroll progress, with issues in pause state checks and comma amount parsing.
apps/console/src/screens/Dashboard.tsx Updates setup completion to use dashboard summary and activity instead of the removed payroll list.
packages/types/src/payroll.ts Replaces demo payroll batch/proof types with run, item, token, progress, and underfunded error types.
packages/types/src/api.ts Updates shared endpoint metadata for the new payroll run lifecycle.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
  participant User
  participant PayrollScreen
  participant API
  participant Backend
  User->>PayrollScreen: Paste CSV and select token
  PayrollScreen->>API: createPayrollRun(orgId, csv, token)
  API->>Backend: POST /orgs/:orgId/payroll
  Backend-->>API: runId, summary, items
  API-->>PayrollScreen: validation result
  PayrollScreen->>API: startPayrollRun(runId)
  API->>Backend: POST /payroll/:runId/start
  Backend-->>API: running progress
  loop Until complete or failed
    API->>Backend: GET /payroll/:runId
    Backend-->>API: run and progress counts
    API-->>PayrollScreen: progress event
  end
  User->>PayrollScreen: Pause or resume
  PayrollScreen->>API: POST /payroll/:runId/pause or resume
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
  participant User
  participant PayrollScreen
  participant API
  participant Backend
  User->>PayrollScreen: Paste CSV and select token
  PayrollScreen->>API: createPayrollRun(orgId, csv, token)
  API->>Backend: POST /orgs/:orgId/payroll
  Backend-->>API: runId, summary, items
  API-->>PayrollScreen: validation result
  PayrollScreen->>API: startPayrollRun(runId)
  API->>Backend: POST /payroll/:runId/start
  Backend-->>API: running progress
  loop Until complete or failed
    API->>Backend: GET /payroll/:runId
    Backend-->>API: run and progress counts
    API-->>PayrollScreen: progress event
  end
  User->>PayrollScreen: Pause or resume
  PayrollScreen->>API: POST /payroll/:runId/pause or resume
Loading

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
apps/console/src/demo/api.ts:475-476
**Terminal Runs Become Paused**

When a pause request resolves after the scheduled timers have already confirmed the last item, this path still clears timers and changes the completed run back to `paused`. The UI can then offer Resume for an already-settled demo run, and the dashboard counts it as scheduled again.

```suggestion
    if (run.status !== "running") throw new Error("Run is not running.");
    clearPayrollTimers(runId);
    run.status = "paused";
```

### Issue 2 of 3
apps/console/src/demo/api.ts:191
**Comma Amounts Fail Parsing**

`normalizePayrollAmount` accepts comma-formatted amounts, but this split runs before normalization. A pasted row such as `@aisha,8,500` or `@aisha,"8,500"` produces more than two cells and is marked invalid instead of validating as `8500`.

### Issue 3 of 3
apps/console/src/screens/Dashboard.tsx:86
**Completed Payroll Looks Unrun**

This first-run check now depends on scheduled payrolls or payroll activity, but the new demo lifecycle counts only `ready`/`running`/`paused` runs and does not add payroll activity on completion. After a successful demo run completes, the count drops to zero and the setup checklist can show payroll as not done.

Reviews (1): Last reviewed commit: "feat(console): wire payroll to the real ..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Rewrite the Payroll screen around the backend's real lifecycle: compose a CSV
(handles/addresses + amounts) -> validate (POST /orgs/:id/payroll, showing the
summary + per-row resolved/failed items) -> start (POST /payroll/:runId/start)
-> live progress via 2s polling of GET /payroll/:runId (pending -> proving ->
submitted -> confirmed counts) with pause/resume, and clear treasury-underfunded
guidance. Persists the active run per org and reattaches on reload. Removed the
demo-era contractor/rate-card + 4-proof model and the nonexistent payroll list
read. Demo mode animates a full run.

Epic #58 step E. Closes #66.
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The console replaces legacy payroll batches and proof steps with CSV-based payroll runs, typed lifecycle endpoints, progress subscriptions, pause/resume controls, demo simulation, persisted run hydration, and updated dashboard and status rendering.

Changes

Payroll run lifecycle

Layer / File(s) Summary
Payroll run contracts and routes
packages/types/src/payroll.ts, packages/types/src/api.ts
Introduces payroll-run, progress, lifecycle response, and treasury-underfunded types while replacing legacy payroll routes and aliases.
Demo payroll run simulation
apps/console/src/demo/data.ts, apps/console/src/demo/api.ts
Adds run-based demo storage, CSV validation, recipient resolution, progress tracking, timed transitions, lifecycle controls, and completed-run totals.
Client API and console state integration
apps/console/src/lib/api.ts, apps/console/src/lib/store.tsx, apps/console/src/lib/api.test.ts, apps/console/src/lib/store.test.tsx
Adds typed payroll-run requests, structured errors, polling progress subscriptions, lifecycle methods, and removes the payroll batch read model from console state.
Payroll run screen and dashboard integration
apps/console/src/screens/Payroll.tsx, apps/console/src/screens/Payroll.test.tsx, apps/console/src/screens/Dashboard.tsx, apps/console/src/screens/Dashboard.test.tsx, apps/console/src/ui/primitives.tsx
Replaces proof and approval UI with CSV validation, persisted runs, live progress, pause/resume controls, underfunded alerts, item tables, and updated dashboard/status presentation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PayrollScreen
  participant api
  participant PayrollBackend
  PayrollScreen->>api: createPayrollRun(orgId, csv, token)
  api->>PayrollBackend: POST /orgs/:id/payroll
  PayrollBackend-->>PayrollScreen: runId and validation summary
  PayrollScreen->>api: startPayrollRun(runId)
  api->>PayrollBackend: POST /payroll/:runId/start
  PayrollScreen->>api: subscribePayrollProgress(runId)
  api->>PayrollBackend: poll GET /payroll/:runId
  PayrollBackend-->>PayrollScreen: progress counts
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the run-based payroll API, live progress, persistence, treasury-underfunded handling, and demo updates required by #66.
Out of Scope Changes check ✅ Passed The changes stay focused on payroll-run workflow, API/type updates, demo behavior, and related UI/test adjustments.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: wiring the console payroll flow to the CSV run lifecycle.
✨ 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 codex/66-payroll

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

Comment on lines +475 to +476
clearPayrollTimers(runId);
run.status = "paused";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Terminal Runs Become Paused

When a pause request resolves after the scheduled timers have already confirmed the last item, this path still clears timers and changes the completed run back to paused. The UI can then offer Resume for an already-settled demo run, and the dashboard counts it as scheduled again.

Suggested change
clearPayrollTimers(runId);
run.status = "paused";
if (run.status !== "running") throw new Error("Run is not running.");
clearPayrollTimers(runId);
run.status = "paused";
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/console/src/demo/api.ts
Line: 475-476

Comment:
**Terminal Runs Become Paused**

When a pause request resolves after the scheduled timers have already confirmed the last item, this path still clears timers and changes the completed run back to `paused`. The UI can then offer Resume for an already-settled demo run, and the dashboard counts it as scheduled again.

```suggestion
    if (run.status !== "running") throw new Error("Run is not running.");
    clearPayrollTimers(runId);
    run.status = "paused";
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

.filter((row) => row.line.length > 0);
const dataRows = rows[0] && /^recipient\s*,\s*amount/i.test(rows[0].line) ? rows.slice(1) : rows;
const items = dataRows.map(({ line, rowIndex }) => {
const cells = line.split(",").map((cell) => cell.trim());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Comma Amounts Fail Parsing

normalizePayrollAmount accepts comma-formatted amounts, but this split runs before normalization. A pasted row such as @aisha,8,500 or @aisha,"8,500" produces more than two cells and is marked invalid instead of validating as 8500.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/console/src/demo/api.ts
Line: 191

Comment:
**Comma Amounts Fail Parsing**

`normalizePayrollAmount` accepts comma-formatted amounts, but this split runs before normalization. A pasted row such as `@aisha,8,500` or `@aisha,"8,500"` produces more than two cells and is marked invalid instead of validating as `8500`.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

const hasPolicy = policies.length > 0;
const hasContractor = counterparties.some((c) => c.type === "contractor");
const ranPayroll = payrolls.length > 0;
const ranPayroll = (dashboard?.scheduledPayrolls ?? 0) > 0 || !!dashboard?.recentActivity.some((a) => a.kind === "payroll");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Completed Payroll Looks Unrun

This first-run check now depends on scheduled payrolls or payroll activity, but the new demo lifecycle counts only ready/running/paused runs and does not add payroll activity on completion. After a successful demo run completes, the count drops to zero and the setup checklist can show payroll as not done.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/console/src/screens/Dashboard.tsx
Line: 86

Comment:
**Completed Payroll Looks Unrun**

This first-run check now depends on scheduled payrolls or payroll activity, but the new demo lifecycle counts only `ready`/`running`/`paused` runs and does not add payroll activity on completion. After a successful demo run completes, the count drops to zero and the setup checklist can show payroll as not done.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

@hitakshiA

Copy link
Copy Markdown
Contributor Author

The three Greptile findings are all demo-mode only, so they don't affect the real L1 path:

  • demo/api.ts:191 (comma amounts) + :476 (pause race) are in the demo simulation of payroll — the real path posts the raw CSV to POST /orgs/:id/payroll and the backend parses/validates it; the console does no client-side CSV parsing in real mode.
  • Dashboard.tsx:86 (completed-run first-run heuristic) — Dashboard is a no-backend screen, demo-only in real mode after step B.

Real payroll (validate → start → poll progress → pause/resume) is unaffected. Tracking these as demo-polish. Merging.

@hitakshiA
hitakshiA merged commit 977dca9 into main Jul 11, 2026
3 checks passed
@hitakshiA
hitakshiA deleted the codex/66-payroll branch July 11, 2026 03:58

@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 (1)
apps/console/src/lib/api.ts (1)

393-397: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider bounding the polling error retries.

On a persistent getPayrollRun failure (e.g., a 404 after a run is deleted, or repeated 5xx), the poll reschedules unconditionally every 2s and re-invokes onError each time. Since the run status never advances, the subscription only stops when the effect unmounts/closes it, so failing requests and error-state churn continue for the lifetime of the screen. A consecutive-failure cap (or backoff) would prevent an unbounded failing-request loop. The demo subscribePayrollProgress shares the same pattern.

♻️ Example: cap consecutive failures
   let closed = false;
   let pollTimer: number | null = null;
+  let failures = 0;
+  const MAX_FAILURES = 5;
...
       (error) => {
         if (closed) return;
         onError?.(error instanceof Error ? error : new Error("Payroll progress polling failed."));
-        pollTimer = window.setTimeout(poll, 2_000);
+        if (++failures >= MAX_FAILURES) { close(); return; }
+        pollTimer = window.setTimeout(poll, 2_000);
       },
🤖 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 `@apps/console/src/lib/api.ts` around lines 393 - 397, Bound consecutive
polling failures in both the production and demo subscribePayrollProgress
implementations: track consecutive getPayrollRun errors, stop polling after a
reasonable maximum (or apply bounded backoff), and reset the counter after a
successful status response. Preserve closed/unsubscribe checks and notify
onError appropriately without scheduling unbounded retries.
🤖 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.

Nitpick comments:
In `@apps/console/src/lib/api.ts`:
- Around line 393-397: Bound consecutive polling failures in both the production
and demo subscribePayrollProgress implementations: track consecutive
getPayrollRun errors, stop polling after a reasonable maximum (or apply bounded
backoff), and reset the counter after a successful status response. Preserve
closed/unsubscribe checks and notify onError appropriately without scheduling
unbounded retries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dc9ce4ca-4f20-4dab-bb5c-31a13f8de1cc

📥 Commits

Reviewing files that changed from the base of the PR and between 04a3b80 and 0dbe55d.

📒 Files selected for processing (13)
  • apps/console/src/demo/api.ts
  • apps/console/src/demo/data.ts
  • apps/console/src/lib/api.test.ts
  • apps/console/src/lib/api.ts
  • apps/console/src/lib/store.test.tsx
  • apps/console/src/lib/store.tsx
  • apps/console/src/screens/Dashboard.test.tsx
  • apps/console/src/screens/Dashboard.tsx
  • apps/console/src/screens/Payroll.test.tsx
  • apps/console/src/screens/Payroll.tsx
  • apps/console/src/ui/primitives.tsx
  • packages/types/src/api.ts
  • packages/types/src/payroll.ts
💤 Files with no reviewable changes (1)
  • apps/console/src/lib/store.test.tsx

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.

feat(console): payroll run lifecycle onto real /orgs/:id/payroll + /payroll/:runId

1 participant