Skip to content

feat: comprehensive security, performance, and UX improvements#37

Merged
31b4 merged 18 commits intomainfrom
feature/comprehensive-improvements
Apr 18, 2026
Merged

feat: comprehensive security, performance, and UX improvements#37
31b4 merged 18 commits intomainfrom
feature/comprehensive-improvements

Conversation

@31b4
Copy link
Copy Markdown
Member

@31b4 31b4 commented Apr 18, 2026

Summary

This PR implements a broad set of improvements across security hardening, performance optimization, code quality, and UI/UX enhancements. All changes are committed individually and can be reviewed per-commit.


What was implemented

Security

Zod input validation (api/src/validators/, api/src/middlewares/validate.middleware.ts)
All POST/PUT mutation endpoints now validate the request body via Zod schemas before any business logic runs. Invalid payloads return 400 { error, code: "VALIDATION_ERROR", details: [{field, message}] }. Schemas cover accounts, transactions, categories, budgets, transfers, and recurring schedules.

Structured error codes (api/src/errors/codes.ts)
Replaced ad-hoc error strings with a typed AppError class and ErrorCode constant. All errors now return { error, code } JSON. Static factories: AppError.notFound(), AppError.validation(), AppError.internal().

Rate limiting (api/src/middlewares/rate-limit.middleware.ts)
Sliding window per-IP rate limiter (default: 300 req / 60 s). Uses CF-Connecting-IP header. Returns 429 { error, code: "RATE_LIMITED" } when exceeded. Note: in-memory per Worker instance — use Cloudflare's native rate limiting for global enforcement.

CSP & security headers (client/public/_headers)
Cloudflare Pages _headers file now sets Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy, and Permissions-Policy on all responses.

Audit log (api/migrations/004-audit.sql, api/src/repositories/audit.repository.ts)
New audit_log D1 table records every create/update/delete on accounts and transactions with action, entity type, entity ID, and timestamp.

Structured JSON logging (api/src/utils/logger.ts)
All console.log calls replaced with a typed logger outputting { level, message, timestamp, ...context } JSON for queryable logs in Cloudflare dashboard.


Performance

DB indexes (api/migrations/003-indexes.sql)
8 new indexes on hot query columns: transactions(account_id), transactions(date), transactions(category_id), transactions(linked_transaction_id), investment_transactions(account_id), investment_transactions(date), recurring_schedules(is_active), recurring_schedules(account_id).

Type safety (api/src/repositories/)
Removed all any[] in repository query parameter arrays. All D1 prepare().bind() calls now use (string | number | null)[] (D1Value alias). Added RawTransactionRow and RawExpenseRow types to correctly type D1 integer booleans before mapping.


UI/UX

Transaction search (client/src/components/dashboard-module/TransactionList.tsx)
Real-time search bar above the filter row. Filters by description, category name, and account name simultaneously. Works alongside existing category filter and sort options.

Skeleton loaders (client/src/components/dashboard-module/TransactionList.tsx)
Replaced static spinner with 6 per-row animated skeleton rows. Each row has a staggered animation delay for a polished loading feel.

Mobile bottom navigation (client/src/App.tsx)
Fixed bottom nav bar appears on < lg screens. Desktop retains the sidebar layout (hidden lg:flex). Root div gets pb-16 lg:pb-0 to prevent content from being hidden behind the nav bar.


Code quality

useFinanceData hook (client/src/hooks/useFinanceData.ts)
Extracted all data fetching, state management, and mutation callbacks out of App.tsx into a dedicated custom hook. App.tsx reduced from ~760 to ~280 lines.

Vitest test suites

  • api/src/tests/validators.test.ts — ~30 tests across all Zod validators (account, transaction, category, transfer, budget)
  • api/src/tests/errors.test.ts — 8 tests for AppError and ErrorCode
  • client/src/test/useFinanceData.test.ts — 3 tests for the useFinanceData hook with mocked apiFetch

Docs

AGENT.md — Developer guide for Claude / AI-assisted sessions. Covers architecture layers, rules (don't touch investment_transactions), error handling patterns, logging conventions, testing setup, migration workflow, and a step-by-step guide for adding new entities.

README.md — Updated with a "What's New" section, updated project structure, security model additions, and a Running Tests section.


How to test

Validate input validation

# Should return 400 with per-field errors
curl -X POST https://your-api/accounts \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"name": "", "type": "invalid"}'

Validate rate limiting

# Run >300 requests in 60s from the same IP — 301st returns 429
for i in {1..301}; do curl -s -o /dev/null -w "%{http_code}\n" \
  -H "X-API-Key: your-key" https://your-api/accounts; done | tail -5

Validate security headers

curl -I https://your-client-domain.pages.dev | grep -E "Content-Security|X-Frame|X-Content"

Run unit tests

cd api && npm test        # validators + error codes
cd client && npm test     # useFinanceData hook

Check audit log

After creating/updating/deleting an account or transaction, query:

SELECT * FROM audit_log ORDER BY created_at DESC LIMIT 10;

via npx wrangler d1 execute finance-db --remote --command "SELECT * FROM audit_log ORDER BY created_at DESC LIMIT 10;".

Apply new migrations

npx wrangler d1 execute finance-db --remote --file=api/migrations/003-indexes.sql
npx wrangler d1 execute finance-db --remote --file=api/migrations/004-audit.sql

🤖 Generated with Claude Code

31b4 and others added 18 commits April 18, 2026 16:37
- Added RawTransactionRow type for D1 boolean mapping in transaction repo
- Added D1Value type alias (string | number | null) for query param arrays
- Replaced all any[] param arrays in category, budget, recurring-schedule repos
- Added typed RawExpenseRow for JOIN query results

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added AppError class with typed error codes in api/src/errors/codes.ts
- Added global onError handler to Hono app that returns {error, code} JSON
- Updated account service to throw AppError.notFound instead of implicit null
- All unhandled errors now return {error: "...", code: "INTERNAL_ERROR"} 500

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- transactions: account_id, date, category_id, linked_transaction_id
- investment_transactions: account_id, date
- recurring_schedules: is_active, account_id

Speeds up dashboard queries, date-range filtering, and cron processing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added audit_log table (migration 004) with entity/action/details columns
- Added AuditLog model and AuditRepository with log/findByEntity/findRecent
- Wired audit logging into account controller (create/update/delete)
- Wired audit logging into transaction controller (create/update/delete)
- Indexes on entity+entity_id and created_at for fast lookups

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Installed zod for schema validation in Cloudflare Workers
- Created validators for: account, transaction, category, budget, transfer, recurring-schedule
- Added validateBody() middleware that returns 400 {error, code, details[]} on failure
- Wired validators into all POST/PUT routes in index.ts
- Validation errors return field-level messages: [{field, message}]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added rateLimitMiddleware with per-IP sliding window tracking
- Default: 300 requests / 60 seconds per IP address
- Returns 429 {error, code: "RATE_LIMITED"} when exceeded
- Uses CF-Connecting-IP header (set by Cloudflare) for accurate client ID
- Memory-safe: evicts stale keys when map exceeds 10k entries
- Note: per-Worker-instance; for global limits use CF native rate limiting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Content-Security-Policy: restricts scripts/styles/connections to trusted origins
- X-Content-Type-Options: nosniff — prevents MIME-type sniffing
- X-Frame-Options: DENY — blocks clickjacking via iframes
- Referrer-Policy: strict-origin-when-cross-origin — limits referrer leakage
- Permissions-Policy: blocks camera/microphone/geolocation APIs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Created logger util (info/warn/error/debug) outputting JSON lines
- Format: {level, message, timestamp, ...context}
- Replaced console.log/error in index.ts with logger calls
- Structured logs work with Cloudflare Workers Logpush / Axiom integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extracted all data-fetching state into useFinanceData hook (~160 lines)
- App.tsx reduced from 759 lines to ~280 lines (63% reduction)
- Hook encapsulates: fetchData, fetchAllTransactions, fetchInvestmentValue, fetchApiVersion
- Added mobile bottom navigation bar (fixed, visible on < lg screens)
- Desktop nav unchanged; mobile users now have thumb-friendly tab bar
- Added pb-16 lg:pb-0 to body to prevent content hidden behind mobile nav

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Search:
- Added search bar to TransactionList header with live filtering
- Matches against description, category name, and account name
- Clear button (X) to reset search instantly
- Search integrates with existing category filter and sort

Skeleton loaders:
- Replaced single-block placeholder with 6 per-row skeleton items
- Each skeleton row shows icon + title + subtitle + amount placeholders
- Staggered animation delays for a smoother loading feel
- Shows while loading=true and transactions are empty (not on empty state)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent hooks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…reference

- Exclude src/test/** from tsconfig.app.json so test files don't
  break the production TypeScript build
- Change triple-slash reference to vitest/config (vitest v2+ API)
- Remove unused date-fns imports from useFinanceData.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lightningcss-darwin-arm64 was missing from node_modules causing the
Tailwind/PostCSS build to fail. Resolved by force-reinstalling lightningcss,
which pulled in the platform-specific binary companion package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Runs on every PR to main and on pushes to main:
- API unit tests (vitest)
- Client unit tests (vitest + jsdom)
- Client production build (tsc + vite)
- API TypeScript typecheck

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- validate.middleware: use .issues (Zod v4 renamed .errors → .issues)
  and .map(String) to handle PropertyKey[] path type
- transaction.controller: guard investment_transaction result.id with
  'id' in result check before passing to audit log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR delivers a broad hardening/quality pass across the Cloudflare Workers API and React client, adding validation + structured errors/logging, operational safeguards, performance-focused DB/index updates, and UX improvements (search/skeletons/mobile nav), backed by new unit tests and CI.

Changes:

  • API: added Zod validation middleware/schemas, structured AppError/ErrorCode, rate limiting, audit logging + migration, typed D1 repository bindings, and JSON logger.
  • Client: introduced useFinanceData hook, added transaction search + skeleton loaders + mobile bottom nav, and added Vitest + Testing Library configuration/tests.
  • Tooling/docs: CI workflow for tests/build/typecheck, deployment preflight improvements, and updated README/AGENT guide.

Reviewed changes

Copilot reviewed 38 out of 39 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
package-lock.json Updates workspace lockfile for new deps (Zod/Vitest/Testing Library/etc.) and CSS tooling.
deploy.sh Adds a wrangler/workerd preflight and repair attempt before migrations/deploy.
client/vite.config.ts Adds Vitest configuration (jsdom, setup file, test globs).
client/tsconfig.app.json Excludes test files from the app TS build.
client/src/test/useFinanceData.test.ts Adds hook tests with mocked apiFetch and exchange-rate fetch.
client/src/test/setup.ts Adds Testing Library jest-dom setup.
client/src/hooks/useFinanceData.ts Extracts data fetching/state/mutations from App.tsx into a dedicated hook.
client/src/components/dashboard-module/TransactionList.tsx Adds real-time search, refactors filtering, and replaces spinner with skeleton rows.
client/src/App.tsx Refactors to use useFinanceData and adds mobile bottom navigation + layout padding.
client/public/_headers Adds CSP + security headers for Cloudflare Pages responses.
client/package.json Adds test scripts and dev deps for Vitest + Testing Library + jsdom.
api/src/validators/account.validator.ts Introduces Zod schemas for account create/update payloads.
api/src/validators/transaction.validator.ts Introduces Zod schemas for transaction create/update payloads.
api/src/validators/category.validator.ts Introduces Zod schemas for category create/update payloads.
api/src/validators/budget.validator.ts Introduces Zod schemas for budget create/update payloads.
api/src/validators/transfer.validator.ts Introduces Zod schema for transfer create payloads (incl. account inequality refine).
api/src/validators/recurring-schedule.validator.ts Introduces Zod schemas for recurring schedule create/update payloads.
api/src/utils/logger.ts Adds structured JSON logger utility.
api/src/tests/validators.test.ts Adds Vitest unit tests for validator schemas.
api/src/tests/errors.test.ts Adds Vitest unit tests for AppError/ErrorCode.
api/src/services/account.service.ts Switches unsafe non-null return to AppError.notFound on missing updated account.
api/src/repositories/transaction.repository.ts Adds typed raw-row mapping and typed D1 bind parameter arrays.
api/src/repositories/recurring-schedule.repository.ts Replaces any[] bind arrays with typed `(string
api/src/repositories/category.repository.ts Replaces any[] bind arrays with typed `(string
api/src/repositories/budget.repository.ts Replaces any[] bind arrays with typed `(string
api/src/repositories/audit.repository.ts Adds repository to write/query audit log entries.
api/src/models/AuditLog.ts Adds audit log model + action/entity enums.
api/src/middlewares/validate.middleware.ts Adds Zod request-body validation middleware with structured 400 details.
api/src/middlewares/rate-limit.middleware.ts Adds in-memory sliding-window rate limiting middleware.
api/src/index.ts Wires validation + rate limiting, adds global AppError handler, replaces some console logs with logger.
api/src/errors/codes.ts Adds ErrorCode constants and AppError class.
api/src/controllers/transaction.controller.ts Adds audit logging for transaction CRUD and binds to typed env context.
api/src/controllers/account.controller.ts Adds audit logging for account CRUD and binds to typed env context.
api/package.json Adds Zod + Vitest and test scripts for API workspace.
api/migrations/003-indexes.sql Adds indexes for hot query columns.
api/migrations/004-audit.sql Adds audit_log table + indexes migration.
README.md Documents new security/perf/UX/testing additions and updated structure.
AGENT.md Adds a developer/AI guide covering conventions and architecture.
.github/workflows/ci.yml Adds CI jobs for API tests/typecheck and client tests/build.
Comments suppressed due to low confidence (1)

api/src/controllers/transaction.controller.ts:48

  • This controller’s error responses are still ad-hoc ({error}, {error, details}) and omit the structured { error, code } shape described in the PR (and implemented by the global app.onError handler for AppError). Consider throwing AppError from services/controllers (or mapping errors to AppError here) and letting app.onError format responses consistently; also prefer logger over console.error for structured logs.
    } catch (error: any) {
      console.error('Transaction creation error:', error)
      
      if (error.message.includes('rate-limiting')) {
        return c.json({ 
          error: error.message,
          details: 'Rate limited by Yahoo Finance API'
        }, 429)
      }
      
      if (error.message.includes('not found')) {
        return c.json({ error: error.message }, 404)
      }
      
      return c.json({ 
        error: error.message || 'Failed to create transaction', 
        details: error.toString() 
      }, 500)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/src/middlewares/validate.middleware.ts
Comment thread api/src/index.ts
Comment thread api/src/middlewares/rate-limit.middleware.ts
Comment thread client/src/test/useFinanceData.test.ts
Comment thread api/src/controllers/transaction.controller.ts
@31b4 31b4 merged commit 8df1de6 into main Apr 18, 2026
8 checks passed
@31b4 31b4 deleted the feature/comprehensive-improvements branch April 18, 2026 23:17
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.

2 participants