Skip to content

feat: core bookkeeping UI + property management#46

Merged
chitcommit merged 5 commits intomainfrom
feat/core-bookkeeping-ui
Mar 12, 2026
Merged

feat: core bookkeeping UI + property management#46
chitcommit merged 5 commits intomainfrom
feat/core-bookkeeping-ui

Conversation

@chitcommit
Copy link
Contributor

@chitcommit chitcommit commented Mar 12, 2026

Summary

  • Transactions page: Full table with filters (date, category, account, property, reconciled), search, column sorting, add dialog, CSV export, reconcile toggle, pagination (50/page), quick stats (income/expenses/net)
  • Accounts page: Account cards grouped by type (checking, savings, credit, investment, mortgage, loan, tax_liability), assets/liabilities/net worth summary, detail drill-down with account transactions, add account dialog, sync button
  • Reports page: Consolidated P&L consuming /api/reports/consolidated, by-entity and by-state breakdowns, data quality metrics (uncategorized, unreconciled, no-state, future-dated), tax readiness checklist with pass/warn/fail badges, tax automation trigger with AI review
  • Integrations page: Sync status cards per connected service, Wave accounting sync, TurboTenant CSV import with account selector, recurring charges table, optimization recommendations with potential savings
  • Backend: POST /api/transactions, PATCH /api/transactions/:id, POST /api/accounts, SystemStorage.updateTransaction()
  • Navigation: 4 new sidebar items with role-based visibility (Transactions/Accounts = bookkeeper+, Reports/Integrations = accountant+)
  • Property management (prior session): AddUnit, AddLease, EditProperty dialogs, CommsPanel, WorkflowBoard, WorkspaceTab with Google Workspace embeds, comms/workflow routes

Test plan

  • npx tsc --noEmit — clean
  • npx vitest run — 135/135 tests passing
  • Navigate to /transactions → see transaction list → add transaction → filter → reconcile
  • Navigate to /accounts → see grouped accounts → click for detail → add account
  • Navigate to /reports → select date range → see P&L → check quality metrics → run tax automation
  • Navigate to /integrations → see sync status → trigger Wave sync → view charge optimizations

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • New pages: Transactions, Accounts, Reports, Integrations.
    • Property improvements: Edit Property, Add Unit, Add Lease, Workspace tab (Calendar/Sheets/Drive embeds & event creation), Communications panel (SMS/email templates, send & history), Workflow board and Workflows tab.
  • API & Services

    • New backend APIs for Google Workspace, communications (SMS/email), workflows, accounts, and transactions; SendGrid/Twilio integration support and Google OAuth flows.
  • Chores

    • Storage and schema extended to persist comms logs, workflows, and transaction updates.

Add four new finance pages consuming existing backend APIs:
- Transactions: full table with filters, search, sort, add dialog,
  CSV export, reconcile toggle, pagination (50/page)
- Accounts: cards grouped by type, balance summary (assets/liabilities/
  net worth), detail drill-down with account transactions, add dialog
- Reports: consolidated P&L, by-entity and by-state breakdowns,
  quality metrics, tax readiness checklist, tax automation trigger
- Integrations: sync status cards, Wave sync, TurboTenant CSV import,
  recurring charges with optimization recommendations

Backend additions:
- POST /api/transactions (create)
- PATCH /api/transactions/:id (update/reconcile)
- POST /api/accounts (general account creation)
- SystemStorage.updateTransaction() method

Property management (prior session):
- AddUnit, AddLease, EditProperty dialogs
- CommsPanel (SMS/email via Twilio/SendGrid)
- WorkflowBoard (maintenance/expense approvals)
- WorkspaceTab with Google Workspace embeds
- Google OAuth, comms, and workflow routes
- comms_log and workflows schema tables

Navigation: 4 new sidebar items with role-based visibility
(Transactions/Accounts = bookkeeper+, Reports/Integrations = accountant+)

Verified: tsc --noEmit clean, 135/135 tests passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 12, 2026 07:21
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 12, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
chittyfinance 9289839 Mar 12 2026, 11:03 AM

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Google Workspace, Twilio, and SendGrid integrations; new client pages (Transactions, Accounts, Reports, Integrations); expanded property features (units, leases, workspace embeds, comms, workflows); new client hooks; new server routes/libs for Google/comms/workflows; DB schema additions for comms logs and workflows.

Changes

Cohort / File(s) Summary
App Routing & Pages
client/src/App.tsx, client/src/pages/Transactions.tsx, client/src/pages/Accounts.tsx, client/src/pages/Reports.tsx, client/src/pages/Integrations.tsx, client/src/pages/Connections.tsx
Registered new routes and added full-page implementations for Transactions, Accounts, Reports, Integrations; Connections extended for Google Workspace OAuth (authorize/status/callback) and connect flow.
Navigation
client/src/components/layout/Sidebar.tsx
Added sidebar entries for Transactions, Accounts, Reports, Integrations with new icon imports and role gating.
Property UI & Tabs
client/src/components/property/PropertyDetailPanel.tsx, client/src/pages/PropertyDetail.tsx
Integrated Workspace/Communications/Workflows tabs; added header actions to open Add Unit/Add Lease/Edit Property dialogs and wired dialog state.
Property Dialogs
client/src/components/property/AddUnitDialog.tsx, client/src/components/property/AddLeaseDialog.tsx, client/src/components/property/EditPropertyDialog.tsx
New modal dialogs for adding units/leases and editing property with form state, validation, and mutation calls.
Property Embeds & Panels
client/src/components/property/GoogleCalendarEmbed.tsx, client/src/components/property/GoogleDriveEmbed.tsx, client/src/components/property/GoogleSheetsEmbed.tsx, client/src/components/property/WorkspaceTab.tsx, client/src/components/property/CommsPanel.tsx, client/src/components/property/WorkflowBoard.tsx
Google Calendar/Sheets/Drive embed components (iframe + event/create flows), CommsPanel for SMS/email composition, history and templates, and WorkflowBoard for approval workflows with create/advance actions.
Client Hooks
client/src/hooks/use-accounts.ts, client/src/hooks/use-transactions.ts, client/src/hooks/use-integrations.ts, client/src/hooks/use-reports.ts
Added tenant-scoped React Query hooks: accounts, transactions, integrations/imports, recurring charges/optimizations, consolidated reports and tax automation, with invalidation logic.
Client Feature Pages
client/src/pages/Accounts.tsx, client/src/pages/Integrations.tsx, client/src/pages/Reports.tsx, client/src/pages/Transactions.tsx
Full-featured pages for accounts dashboard, integrations (imports, optimizations), consolidated reports (CSV/export, tax automation), and transactions (filtering, create/update, CSV export).
Database Schema
database/system.schema.ts
Added comms_log and workflows tables with insert schemas, inferred types, and indexes.
Server Env & App Mounts
server/env.ts, server/app.ts
Added env vars for Google/Twilio/SendGrid; mounted google, comms, and workflows routes and added protected prefixes (/api/google, /api/comms, /api/workflows).
Server Libraries
server/lib/google-api.ts, server/lib/twilio.ts, server/lib/sendgrid.ts
New Google API client (OAuth, token exchange, calendar/drive helpers and embed URL builders), Twilio SMS client and templates, SendGrid email client and templates.
Server Routes
server/routes/google.ts, server/routes/comms.ts, server/routes/workflows.ts, server/routes/transactions.ts, server/routes/accounts.ts
New googleRoutes & callback (authorize/status/callback, calendar/drive endpoints), comms endpoints (send, template, history, status), workflow CRUD and state endpoints, transactions POST/PATCH, and enhanced accounts create/liability upsert.
Server Storage
server/storage/system.ts
Added session shims, transaction update, comms log create/get, and workflow get/create/update with metadata merge and timestamps.
Routing Middleware Shim
server/routes.ts
Replaced certain middleware with no-op shims for legacy/Express compatibility in system-mode.
Charge Detection & Automation
server/routes/charges.ts, server/lib/chargeAutomation.ts
Reworked recurring charge detection to derive from transaction history, updated RecurringCharge model (frequency, occurrences), and updated optimization generation and manage APIs.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Client as Client (Browser)
  participant Server as App Server
  participant Google as Google OAuth/API
  participant Storage as DB/Storage

  User->>Client: Click "Connect Google Workspace"
  Client->>Server: GET /api/integrations/google/authorize
  Server->>Google: Request auth URL (client id, scopes, redirect)
  Google-->>Server: auth URL
  Server-->>Client: { authUrl }
  Client->>Google: Browser -> Google OAuth (user consents)
  Google-->>Client: Redirect to /api/integrations/google/callback?code=...
  Client->>Server: GET /api/integrations/google/callback?code=...
  Server->>Google: Exchange code for tokens
  Google-->>Server: access_token/refresh_token
  Server->>Storage: persist integration credentials
  Storage-->>Server: stored
  Server-->>Client: Redirect to app (connected)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 I hopped through routes and hooks today,
Calendars, drives, and messages in play,
Leases, units, workflows too,
Syncs and imports—what a queue!
Hop on, review, and let features stay!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: core bookkeeping UI + property management' directly aligns with the main objectives of the changeset, which includes new Transactions, Accounts, Reports pages, property management dialogs, and integration features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/core-bookkeeping-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@claude
Copy link

claude bot commented Mar 12, 2026

PR Review: feat: core bookkeeping UI + property management

This is a substantial PR (+3960/-20 lines) adding Transactions, Accounts, Reports, and Integrations pages plus Google Workspace, Twilio, SendGrid integrations and approval workflows. Overall the architecture is consistent with the codebase and the component patterns are clean. Below are findings ranging from blocking security issues to minor improvements.


🔴 Blocking: Security Issues

1. Workflow tenant isolation is broken

storage/system.tsupdateWorkflow and getWorkflow filter only by id, not by tenantId. Any authenticated user who knows a workflow UUID can update a workflow belonging to another tenant:

// system.ts — getWorkflow has no tenant filter
async getWorkflow(id: string) {
  const [row] = await this.db.select().from(schema.workflows).where(eq(schema.workflows.id, id));
  return row;
}

The route at PATCH /api/workflows/:id also never passes tenantId to the update:

const workflow = await storage.updateWorkflow(id, { status: body.status, ... });
// tenantId is available via c.get('tenantId') but is never passed

Compare with updateTransaction which correctly scopes by tenant:

.where(and(eq(schema.transactions.id, id), eq(schema.transactions.tenantId, tenantId)))

updateWorkflow should accept and enforce tenantId.


2. SMS/email to field is unvalidated — free message relay risk

server/routes/comms.ts — The to recipient is passed directly to Twilio/SendGrid with no validation. An authenticated user can send SMS to arbitrary phone numbers or emails, creating an abuse vector:

commsRoutes.post('/api/comms/send', async (c) => {
  const { channel, to, message } = body;
  // No format validation on `to`
  const result = await twilio.sendSms(to, message);

At minimum, validate E.164 format for phone numbers and RFC 5321 for emails. Consider rate-limiting per tenant.


3. Google Drive query is constructed via string interpolation

server/lib/google-api.ts:497:

const q = `'${folderId}' in parents and trashed = false`;

If folderId is ever sourced from user input and contains a single quote, this breaks the query. The folderId from c.req.query('folderId') in the route is user-controlled. Use encodeURIComponent or validate the ID format (alphanumeric/dashes only) before interpolating.


4. Google OAuth: expired tokens are used without refresh

server/routes/google.tsexpires_at is stored in credentials but never checked before making API calls:

const creds = integration.credentials as Record<string, string>;
const result = await createCalendarEvent(creds.access_token, ...);

Stale tokens will fail with a 401 from Google. The GoogleAPIClient.refreshToken() method exists but is never called. Add token expiry checking before each Google API call (same pattern as the Wave integration).


🟡 Bugs

5. React Hooks rule violation in Accounts.tsx

client/src/pages/Accounts.tsx:1845useMemo is called after a conditional early return, violating the Rules of Hooks:

if (!tenantId) {
  return <div>Select a tenant...</div>;  //  early return
}

// ❌ Hook called conditionally — will crash at runtime
const grouped = useMemo(() => { ... }, [accounts]);

Move the useMemo (and the balance summary calculations) before the if (!tenantId) guard, or restructure as a separate child component.


6. Missing required field validation in POST /api/workflows

server/routes/workflows.ts:38body.title is used without a presence check:

const workflow = await storage.createWorkflow({
  tenantId,
  title: body.title,   // ← could be undefined

The schema column is notNull(), so this will throw an unhandled DB error instead of a clean 400. Add:

if (!body.title) return c.json({ error: 'title is required' }, 400);

7. CommsPanel: channel switch doesn't clear recipient

client/src/components/property/CommsPanel.tsx — When a tenant is quick-selected (which sets recipient to phone or email based on channel), switching the channel leaves the wrong contact type in the recipient field. A phone number will remain as the email address or vice versa.

Clear recipient on channel change:

onValueChange={v => { setChannel(v as 'sms' | 'email'); setRecipient(''); }}

8. Dead code in CommsPanel.handleSend

message: channel === 'email' ? message : message,

Both branches are identical. The ternary can be removed.


9. WorkflowBoard doesn't show rejected workflows

STATUSES = ['requested', 'approved', 'in_progress', 'completed'] — rejected workflows will simply disappear from the Kanban board. Consider adding a rejected column or a separate list.


10. Unused propertyId prop in GoogleDriveEmbed and GoogleSheetsEmbed

Both components declare propertyId in their interface but destructure it away:

export default function GoogleDriveEmbed({ folderId, connected }: GoogleDriveEmbedProps) {
//                                         ^^^^^^^^ propertyId is silently dropped

Either use it or remove it from the interface.


11. WorkspaceTab queries sheetId but the status endpoint doesn't return it

WorkspaceTab.tsx references status?.sheetId but GET /api/integrations/google/status only returns { connected, calendarId, driveFolderId }. The sheets embed will always get undefined.


🔵 Code Quality

12. formatTransaction uses any

function formatTransaction(t: any) {

Use typeof schema.transactions.$inferSelect to get compile-time safety.


13. data: any in mutation callbacks (Integrations.tsx)

onSuccess: (data: any) => toast({ title: `Wave sync: ${data.imported ?? 0} transactions imported` }),

Type the mutation return or assert the shape inline.


14. Default fallback OAuth secret should warn louder

server/routes/google.ts:3980:

const secret = c.env.OAUTH_STATE_SECRET || 'default-secret-change-in-production';

Same pattern exists in the Wave route. Consider logging a console.warn when the default is used, to make misconfigured deployments more visible.


📋 Performance Note

15. All transactions fetched client-side

Transactions.tsx loads all transactions and filters/sorts/paginates on the client. For the current scale this is fine, but the existing GET /api/transactions?limit=N parameter is ignored by the page. As transaction counts grow, consider pushing filter params to the server query.


✅ What's done well

  • Consistent shadcn/ui + Tailwind patterns across all new pages
  • Proper useQuery/useMutation patterns with queryClient.invalidateQueries on success
  • Edge-compatible Twilio/SendGrid/Google clients (no Node.js deps)
  • updateTransaction correctly enforces tenant isolation — that pattern should be replicated for updateWorkflow
  • Role-based sidebar visibility (bookkeeper+ for Transactions/Accounts, accountant+ for Reports/Integrations)
  • Metadata merging in updateWorkflow is a thoughtful design

Summary of blocking items

# File Issue
1 server/storage/system.ts Workflow update missing tenant isolation
2 server/routes/comms.ts Unvalidated SMS/email recipients
3 server/lib/google-api.ts Drive query string injection risk
4 server/routes/google.ts Google tokens used without expiry check
5 client/src/pages/Accounts.tsx Hooks rule violation (runtime crash)

🤖 Reviewed with Claude Code

Copy link
Contributor

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

Adds a core bookkeeping UI (transactions/accounts/reports/integrations) and expands property management with communications, workflows, and Google Workspace embeds, backed by new API routes and storage methods.

Changes:

  • Implement Transactions/Accounts/Reports/Integrations pages with new React Query hooks and sidebar/nav wiring.
  • Add backend endpoints for creating/updating transactions, creating accounts, and new comms/workflow/google integration routes.
  • Extend system storage and DB schema to support comms logs and workflow records, plus Google/Twilio/SendGrid edge clients.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
server/storage/system.ts Adds updateTransaction, comms log storage, and workflow CRUD helpers
server/routes/workflows.ts New workflows API (list/create/update/approve/complete)
server/routes/transactions.ts Adds POST + PATCH transaction endpoints; refactors response formatting
server/routes/google.ts New Google OAuth + Calendar/Drive API proxy routes
server/routes/comms.ts New comms send/template/history/status routes using Twilio/SendGrid
server/routes/accounts.ts Adds generic POST /api/accounts endpoint
server/lib/twilio.ts Edge-compatible Twilio SMS client + SMS templates
server/lib/sendgrid.ts Edge-compatible SendGrid client + email templates
server/lib/google-api.ts Edge-compatible Google OAuth + Calendar/Drive REST helpers + embed URL helpers
server/env.ts Adds env vars for Google/Twilio/SendGrid configuration
server/app.ts Mounts new route groups and protects new API prefixes
database/system.schema.ts Adds comms_log and workflows tables + Zod schemas/types
client/src/pages/Transactions.tsx New transactions table UI with filters/sort/pagination/create/reconcile + CSV export
client/src/pages/Accounts.tsx New accounts overview + detail drill-down + create/sync actions
client/src/pages/Reports.tsx New consolidated P&L + data quality metrics + tax automation trigger
client/src/pages/Integrations.tsx New integrations status, Wave sync, TurboTenant import, recurring charges, optimizations UI
client/src/pages/PropertyDetail.tsx Adds property dialogs + workspace/comms/workflows tabs
client/src/pages/Connections.tsx Adds Google Workspace integration card + OAuth connect handler
client/src/components/layout/Sidebar.tsx Adds sidebar nav items for Transactions/Accounts/Reports/Integrations with role gating
client/src/App.tsx Registers new routes for the new pages
client/src/hooks/use-transactions.ts New hooks for listing/creating/updating transactions
client/src/hooks/use-accounts.ts New hooks for accounts list/create/sync + account transactions
client/src/hooks/use-reports.ts New consolidated report hook + tax automation mutation
client/src/hooks/use-integrations.ts New hooks for integrations, charges, optimizations, Wave sync, TurboTenant import
client/src/components/property/WorkspaceTab.tsx Property workspace tab using Google integration status + embeds
client/src/components/property/GoogleCalendarEmbed.tsx Calendar iframe + “add event” dialog hitting /api/google/calendar/events
client/src/components/property/GoogleSheetsEmbed.tsx Sheets iframe embed panel
client/src/components/property/GoogleDriveEmbed.tsx Drive folder iframe embed panel
client/src/components/property/CommsPanel.tsx Property comms UI (send + history)
client/src/components/property/WorkflowBoard.tsx Property workflow kanban UI + create/update actions
client/src/components/property/AddUnitDialog.tsx Add-unit dialog
client/src/components/property/AddLeaseDialog.tsx Add-lease dialog
client/src/components/property/EditPropertyDialog.tsx Edit-property dialog
client/src/components/property/PropertyDetailPanel.tsx Adds unit/lease/edit dialogs to the side panel

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

Copy link
Contributor

@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: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (12)
client/src/pages/Connections.tsx-124-135 (1)

124-135: ⚠️ Potential issue | 🟡 Minor

Clear the Google pending state when no auth URL comes back.

If Line 127 returns non-OK or the payload omits authUrl, Lines 129-130 fall through without resetting connectingType. The Google card then stays stuck on “Connecting...” until refresh.

Suggested fix
   const connectGoogle = async () => {
     setConnectingType('google');
     try {
       const response = await fetch('/api/integrations/google/authorize');
+      if (!response.ok) {
+        throw new Error('Failed to start Google authorization');
+      }
       const data: { authUrl?: string } = await response.json();
-      if (data.authUrl) {
-        window.location.href = data.authUrl;
-      }
+      if (!data.authUrl) {
+        throw new Error('Missing Google auth URL');
+      }
+      window.location.href = data.authUrl;
     } catch (error) {
       console.error('Failed to start Google authorization:', error);
       setConnectingType(null);
     }
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/pages/Connections.tsx` around lines 124 - 135, The connectGoogle
function can leave connectingType set when the fetch returns non-OK or the
response lacks authUrl; update connectGoogle to check response.ok and whether
data.authUrl exists and call setConnectingType(null) before returning when
either case fails (and also in any early-return path), so the Google card
doesn't stay stuck; reference the connectGoogle function and the
setConnectingType state updater to implement these checks and ensure
connectingType is cleared on all failure or missing-authUrl paths.
client/src/components/property/WorkflowBoard.tsx-57-59 (1)

57-59: ⚠️ Potential issue | 🟡 Minor

Don’t show the empty-state copy while workflows are still loading or errored.

Because data defaults to [], each column renders None until the request resolves and also after fetch failures. That makes populated properties look empty and hides the real failure mode.

As per coding guidelines, client/src/**/*.{ts,tsx}: Use TanStack React Query for all API data fetching; handle loading and error states with appropriate UI feedback.

Also applies to: 97-107

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/property/WorkflowBoard.tsx` around lines 57 - 59, The
component currently defaults `data` to `[]` in the useQuery call which makes
columns render "None" during loading or on error; instead remove the `= []`
default and use the returned flags from useQuery (isLoading, isError, error)
alongside `data` (e.g., the `workflows` variable from useQuery) to show a
loading indicator while isLoading is true and an error UI when isError is true,
only falling back to an empty array for rendering columns after a successful
fetch (or use `data ?? []` after checking status). Update the render logic
around where columns are built (the code around lines with `workflows` and the
columns at 97-107) to check these flags before displaying the empty-state copy.
client/src/components/property/PropertyDetailPanel.tsx-104-106 (1)

104-106: ⚠️ Potential issue | 🟡 Minor

Give the edit icon button an accessible name.

Line 104 renders only a Pencil icon, so assistive tech will announce a generic button. Add aria-label="Edit property" or visible text.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/property/PropertyDetailPanel.tsx` around lines 104 -
106, The edit icon button in PropertyDetailPanel currently renders only <Button
... onClick={() => setEditPropertyOpen(true)}> with a <Pencil /> child, which is
not accessible; update that Button (the one that calls setEditPropertyOpen in
PropertyDetailPanel) to include an accessible name — e.g., add aria-label="Edit
property" or include visually hidden text alongside the <Pencil /> icon so
screen readers announce "Edit property". Ensure the attribute is placed on the
Button component (not the icon) so assistive tech receives the label.
client/src/pages/Integrations.tsx-38-43 (1)

38-43: ⚠️ Potential issue | 🟡 Minor

Add loading/error UI for the other query-backed sections.

Only useIntegrations() contributes isLoading. charges, optimizations, and accounts default to [], so this page renders empty-state copy or an empty picker while those requests are still pending or failed.

As per coding guidelines, "Use TanStack React Query for all API data fetching; handle loading and error states with appropriate UI feedback."

Also applies to: 152-179, 183-215, 223-229

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/pages/Integrations.tsx` around lines 38 - 43, The page is only
handling loading for useIntegrations(); update the other query
hooks—useRecurringCharges, useChargeOptimizations, useAccounts, useWaveSync, and
useTurboTenantImport—to destructure and use their isLoading/isFetching and
isError (plus error) flags instead of silently defaulting to [] so the UI can
show proper loading spinners and error states; specifically, change the const
lines that set charges, optimizations, and accounts to also extract isLoading
and isError (e.g., const { data: charges, isLoading: chargesLoading, isError:
chargesError, error: chargesErrorObj } = useRecurringCharges()), then in the
matching sections for charges, optimizations, accounts, waveSync and ttImport
render a spinner/placeholder when loading and an error banner/message when
isError using the error object, keeping empty-state copy only when data is
loaded and empty.
client/src/hooks/use-integrations.ts-60-69 (1)

60-69: ⚠️ Potential issue | 🟡 Minor

Refresh the charge-analysis caches after imports and syncs.

Both mutations change transaction data, but neither invalidates ['/api/charges/recurring', tenantId] or ['/api/charges/optimizations', tenantId]. The Integrations page will keep showing pre-sync recommendations until a manual reload.

Suggested fix
 export function useWaveSync() {
   const qc = useQueryClient();
   const tenantId = useTenantId();
   return useMutation({
     mutationFn: () =>
       apiRequest('POST', '/api/import/wave-sync', {}).then(r => r.json()),
     onSuccess: () => {
       qc.invalidateQueries({ queryKey: ['/api/transactions', tenantId] });
       qc.invalidateQueries({ queryKey: ['/api/integrations', tenantId] });
+      qc.invalidateQueries({ queryKey: ['/api/charges/recurring', tenantId] });
+      qc.invalidateQueries({ queryKey: ['/api/charges/optimizations', tenantId] });
     },
   });
 }
@@
 export function useTurboTenantImport() {
   const qc = useQueryClient();
   const tenantId = useTenantId();
   return useMutation({
@@
     onSuccess: () => {
       qc.invalidateQueries({ queryKey: ['/api/transactions', tenantId] });
+      qc.invalidateQueries({ queryKey: ['/api/charges/recurring', tenantId] });
+      qc.invalidateQueries({ queryKey: ['/api/charges/optimizations', tenantId] });
     },
   });
 }

Also applies to: 73-92

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/hooks/use-integrations.ts` around lines 60 - 69, The onSuccess
handler in useWaveSync (and the other integration mutation block around lines
73-92) fails to invalidate charge-analysis caches, so add qc.invalidateQueries
calls for the two charge query keys; specifically, in useWaveSync's onSuccess
(where qc and tenantId are available) call qc.invalidateQueries({ queryKey:
['/api/charges/recurring', tenantId] }) and qc.invalidateQueries({ queryKey:
['/api/charges/optimizations', tenantId] }), and make the same additions to the
onSuccess of the other integration mutation mentioned (so both mutations refresh
charge-analysis data after imports/syncs).
client/src/hooks/use-accounts.ts-67-75 (1)

67-75: ⚠️ Potential issue | 🟡 Minor

Invalidate the account-transactions query after sync.

useSyncAccount() only refreshes the account list. If the sync imports new ledger entries, the drill-down backed by useAccountTransactions() keeps showing stale data until a reload.

Suggested fix
 export function useSyncAccount() {
   const qc = useQueryClient();
   const tenantId = useTenantId();
   return useMutation({
     mutationFn: ({ id, ...data }: { id: string } & Record<string, unknown>) =>
       apiRequest('POST', `/api/accounts/${id}/sync`, data).then(r => r.json()),
-    onSuccess: () => {
+    onSuccess: (_data, { id }) => {
       qc.invalidateQueries({ queryKey: ['/api/accounts', tenantId] });
+      qc.invalidateQueries({ queryKey: [`/api/accounts/${id}/transactions`, tenantId] });
     },
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/hooks/use-accounts.ts` around lines 67 - 75, useSyncAccount
currently only invalidates the account list and misses refreshing per-account
transactions; update the mutation's onSuccess to accept the mutation variables
(e.g. onSuccess: (_data, variables) => { ... }) and after invalidating
['/api/accounts', tenantId] also call qc.invalidateQueries for the account
transactions query using the synced account id, e.g. invalidateQueries({
queryKey: ['/api/account-transactions', tenantId, variables.id] }) so
useAccountTransactions-backed UI refreshes after a sync.
server/routes/google.ts-155-167 (1)

155-167: ⚠️ Potential issue | 🟡 Minor

Missing Zod validation for folder creation endpoint.

Same issue as the calendar events endpoint - the request body should be validated with Zod.

🛡️ Proposed fix: Add Zod validation
+const createFolderSchema = z.object({
+  name: z.string().min(1),
+  parentId: z.string().optional(),
+});

 // POST /api/google/drive/folders — Create Drive folder for property
 googleRoutes.post('/api/google/drive/folders', async (c) => {
   const storage = c.get('storage');
   const tenantId = c.get('tenantId');
   const integration = (await storage.getIntegrations(tenantId)).find(i => i.serviceType === 'google');
   if (!integration?.connected) return c.json({ error: 'Google not connected' }, 400);

   const creds = integration.credentials as Record<string, string>;
-  const body = await c.req.json();
+  const parseResult = createFolderSchema.safeParse(await c.req.json());
+  if (!parseResult.success) {
+    return c.json({ error: 'Invalid request body', details: parseResult.error.flatten() }, 400);
+  }
+  const body = parseResult.data;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/google.ts` around lines 155 - 167, The POST
/api/google/drive/folders route lacks request-body validation; add a Zod schema
(e.g., FolderCreateSchema) and validate the parsed body before calling
createDriveFolder so body.name and optional body.parentId are type-safe. If
validation fails return a 400 with the validation errors; on success use the
parsed value when calling createDriveFolder(creds.access_token, parsed.name,
parsed.parentId). Update the route handler where
googleRoutes.post('/api/google/drive/folders', ...) is defined and ensure the
validated type replaces the raw body variable.
client/src/pages/Reports.tsx-265-270 (1)

265-270: ⚠️ Potential issue | 🟡 Minor

Potential runtime issue if c.status has unexpected values.

The STATUS_ICON object only has keys for 'pass', 'warn', and 'fail'. If c.status contains any other value, STATUS_ICON[c.status] would be undefined, rendering nothing silently.

💡 Add fallback for unexpected status values
                   {checks.map(c => (
                     <div key={c.id} className="flex items-center gap-2 py-1">
-                    {STATUS_ICON[c.status]}
+                    {STATUS_ICON[c.status as keyof typeof STATUS_ICON] ?? <AlertTriangle className="w-3.5 h-3.5 text-gray-400" />}
                     <span className="text-xs text-[hsl(var(--cf-text-secondary))]">{c.message}</span>
                   </div>
                 ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/pages/Reports.tsx` around lines 265 - 270, STATUS_ICON lookup can
return undefined when c.status is not 'pass'|'warn'|'fail'; update the rendering
inside checks.map (the block using checks.map, c.status, and STATUS_ICON) to
provide a safe fallback icon/component when STATUS_ICON[c.status] is undefined
(e.g., use a default icon entry in STATUS_ICON like 'unknown' or fall back to
STATUS_ICON['warn'] / a placeholder JSX element) so the UI never renders nothing
for unexpected status values.
server/routes/google.ts-105-123 (1)

105-123: ⚠️ Potential issue | 🟡 Minor

Missing Zod validation for POST request body.

The request body is used directly without Zod schema validation. This violates the coding guidelines and could lead to unexpected errors or security issues if malformed data is passed.

🛡️ Proposed fix: Add Zod validation
+import { z } from 'zod';
+
+const createEventSchema = z.object({
+  calendarId: z.string().optional(),
+  summary: z.string().min(1),
+  description: z.string().optional(),
+  startDateTime: z.string(),
+  endDateTime: z.string(),
+});

 // POST /api/google/calendar/events — Create calendar event
 googleRoutes.post('/api/google/calendar/events', async (c) => {
   const storage = c.get('storage');
   const tenantId = c.get('tenantId');
   const integration = (await storage.getIntegrations(tenantId)).find(i => i.serviceType === 'google');
   if (!integration?.connected) return c.json({ error: 'Google not connected' }, 400);

   const creds = integration.credentials as Record<string, string>;
-  const body = await c.req.json();
+  const parseResult = createEventSchema.safeParse(await c.req.json());
+  if (!parseResult.success) {
+    return c.json({ error: 'Invalid request body', details: parseResult.error.flatten() }, 400);
+  }
+  const body = parseResult.data;
   const calendarId = body.calendarId || 'primary';

As per coding guidelines: "Use Zod schemas for input validation on all API endpoints."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/google.ts` around lines 105 - 123, The POST handler
googleRoutes.post('/api/google/calendar/events', ...) uses the request body
without validation; add a Zod schema (e.g., CreateCalendarEventSchema using
z.object) that validates calendarId as optional string, summary as nonempty
string, description as optional string, and startDateTime/endDateTime as ISO
datetime strings (z.string().refine or z.preprocess with Date if you prefer),
import z from 'zod', parse the incoming body with schema.safeParse/parse and
return a 400 JSON error on validation failure, then use the validated payload to
call createCalendarEvent so the handler only operates on typed/validated input.
client/src/components/property/CommsPanel.tsx-80-91 (1)

80-91: ⚠️ Potential issue | 🟡 Minor

Redundant ternary and potential UX issue with channel switching.

  1. Line 85: channel === 'email' ? message : message always returns message - this is redundant.
  2. When a user selects a tenant, then switches channels, the recipient contact (phone vs email) doesn't update to match the new channel.
🧹 Proposed fixes
   function handleSend() {
     if (!recipient || !message) return;
     sendMutation.mutate({
       channel,
       to: recipient,
-      message: channel === 'email' ? message : message,
+      message,
       subject: channel === 'email' ? (subject || 'ChittyFinance Notification') : undefined,
       recipientName,
       propertyId,
       template: template !== 'custom' ? template : undefined,
     });
   }

For the channel switching issue, consider storing the selected lease ID and updating recipient when channel changes:

+  const [selectedLeaseId, setSelectedLeaseId] = useState<string | null>(null);
+
+  // Update recipient when channel changes if a lease is selected
+  useEffect(() => {
+    if (selectedLeaseId) {
+      selectTenant(selectedLeaseId);
+    }
+  }, [channel]);

   function selectTenant(leaseId: string) {
     const lease = leases.find(l => l.id === leaseId);
     if (!lease) return;
+    setSelectedLeaseId(leaseId);
     setRecipientName(lease.tenantName);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/property/CommsPanel.tsx` around lines 80 - 91, The send
handler contains a redundant ternary and doesn't update recipient when channel
changes; in handleSend replace the unnecessary channel === 'email' ? message :
message with just message and ensure subject is only passed for email (keep
subject logic), and implement logic to track selected lease/contact (e.g.,
selectedLeaseId or selectedContact) and update the recipient field when channel
changes (listen for channel changes in the component and switch recipient to the
lease's email or phone accordingly) so the recipient always matches the current
channel before calling sendMutation.mutate.
client/src/pages/Reports.tsx-65-89 (1)

65-89: ⚠️ Potential issue | 🟡 Minor

CSV export has potential issues with special characters and missing data sanitization.

The CSV export doesn't escape quotes or commas in entity names, which could corrupt the CSV if names contain these characters. Also, if report becomes undefined between the check and usage (unlikely but possible with async state), it could cause issues.

🛡️ Proposed fix for CSV escaping
   const exportCsv = () => {
     if (!report) return;
+    const escapeCell = (val: string | number) => {
+      const str = String(val);
+      return str.includes(',') || str.includes('"') ? `"${str.replace(/"/g, '""')}"` : str;
+    };
     const rows = [
       'Consolidated P&L Report',
       `Period: ${report.scope.startDate} to ${report.scope.endDate}`,
       '',
       'TOTALS',
       `Income,${report.totals.income}`,
       `Expenses,${report.totals.expenses}`,
       `Deductible Expenses,${report.totals.deductibleExpenses}`,
       `Net Income,${report.totals.net}`,
       '',
       'BY ENTITY',
       'Entity,Type,Income,Expenses,Net',
-      ...report.byEntity.map(e => `"${e.tenantName}",${e.tenantType},${e.income},${e.expenses},${e.net}`),
+      ...report.byEntity.map(e => `${escapeCell(e.tenantName)},${escapeCell(e.tenantType)},${e.income},${e.expenses},${e.net}`),
       '',
       'BY STATE',
       'State,Income,Expenses,Taxable Income,Estimated Tax',
-      ...report.byState.map(s => `${s.state},${s.income},${s.expenses},${s.taxableIncome},${s.estimatedTax}`),
+      ...report.byState.map(s => `${escapeCell(s.state)},${s.income},${s.expenses},${s.taxableIncome},${s.estimatedTax}`),
     ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/pages/Reports.tsx` around lines 65 - 89, The exportCsv function
should defensively snapshot report at the start and properly escape/sanitize all
CSV fields: capture const rpt = report at top of exportCsv to avoid it becoming
undefined mid-run, then when building rows ensure every field from rpt.totals,
rpt.byEntity and rpt.byState is normalized (use default empty string or 0) and
string fields are CSV-escaped by wrapping them in double quotes and doubling any
internal double quotes (e.g., tenantName -> "\"" + tenantName.replace(/"/g,
'""') + "\""); apply this escaping to the Entity row fields and any other string
columns (state, dates), and use the sanitized startDate/endDate (from rpt.scope)
when constructing the download filename before creating the Blob/URL and
triggering the download.
server/routes/google.ts-108-122 (1)

108-122: ⚠️ Potential issue | 🟡 Minor

Add automatic token refresh before Google API calls to prevent 401 failures when tokens expire.

The code stores expires_at in credentials but never checks it before using creds.access_token. When the token expires, all Google API calls (calendar, drive) will fail with 401 errors. Implement token refresh logic similar to the Wave integration: check if expires_at has passed, and if so, call GoogleAPIClient.refreshToken(creds.refresh_token) to obtain a new token before making API requests. This should be done in all routes that use the access token (lines 108–123, 126–139, 142–153, 156–167).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/google.ts` around lines 108 - 122, Check credentials'
expires_at before using creds.access_token in the Google routes (where you call
storage.getIntegrations and use integration.credentials and
createCalendarEvent); if expires_at is past, call
GoogleAPIClient.refreshToken(creds.refresh_token) to get a new access_token (and
updated expires_at), persist the updated credentials back to storage for the
tenant, and then use the refreshed access_token for the API call; apply the same
pattern to all routes that call Google APIs (calendar, drive) to avoid 401s.
🧹 Nitpick comments (9)
server/lib/twilio.ts (1)

26-47: Add timeout to prevent indefinite hanging on network issues.

The fetch call has no timeout configured. If Twilio is slow or unresponsive, this could block the request thread indefinitely.

♻️ Proposed fix - add AbortController timeout
   async sendSms(to: string, body: string): Promise<SendSmsResult> {
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
+
-    const res = await fetch(`${this.baseUrl}/Messages.json`, {
-      method: 'POST',
-      headers: {
-        Authorization: this.authHeader,
-        'Content-Type': 'application/x-www-form-urlencoded',
-      },
-      body: new URLSearchParams({
-        To: to,
-        From: this.config.fromNumber,
-        Body: body,
-      }),
-    });
+    try {
+      const res = await fetch(`${this.baseUrl}/Messages.json`, {
+        method: 'POST',
+        headers: {
+          Authorization: this.authHeader,
+          'Content-Type': 'application/x-www-form-urlencoded',
+        },
+        body: new URLSearchParams({
+          To: to,
+          From: this.config.fromNumber,
+          Body: body,
+        }),
+        signal: controller.signal,
+      });
+
+      clearTimeout(timeoutId);

       if (!res.ok) {
         const err = await res.text();
         throw new Error(`Twilio send failed (${res.status}): ${err}`);
       }

       const data: Record<string, string> = await res.json();
       return { sid: data.sid, status: data.status, to: data.to };
+    } finally {
+      clearTimeout(timeoutId);
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/lib/twilio.ts` around lines 26 - 47, sendSms currently calls fetch
without a timeout which can hang; add an AbortController-based timeout inside
sendSms to abort the fetch after a configurable interval (e.g., 10s), pass
controller.signal to fetch, and clear the timer on success. Specifically, inside
the sendSms method create an AbortController, start a setTimeout that calls
controller.abort(), include signal: controller.signal in the fetch options, and
clearTimeout when the response is received; also handle the abort case by
catching the thrown error and rethrowing a clear error (e.g., "Twilio send timed
out") so callers of sendSms can distinguish timeouts from other failures.
server/routes/transactions.ts (1)

6-20: Avoid any type - use proper typing for transaction parameter.

The formatTransaction function loses type safety by accepting any. Consider using the inferred schema type.

♻️ Proposed fix
+import type { Transaction } from '@shared/schema';
+
-function formatTransaction(t: any) {
+function formatTransaction(t: Transaction) {
   return {
     id: t.id,
     accountId: t.accountId,
     amount: parseFloat(t.amount),
     type: t.type,
     category: t.category || undefined,
     description: t.description,
     date: t.date,
     payee: t.payee || undefined,
     propertyId: t.propertyId || undefined,
     unitId: t.unitId || undefined,
     reconciled: t.reconciled,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/transactions.ts` around lines 6 - 20, The function
formatTransaction currently accepts an untyped parameter (any) which loses type
safety; update formatTransaction to accept the correct transaction input type
from your schema/model (e.g., the inferred type exported by your validation
schema or DB model — e.g., TransactionInput, TransactionDTO, or use
ReturnType<typeof transactionSchema.parse>) so the compiler can validate
properties like amount, id, accountId, etc.; import or derive that type and
replace the parameter type, and adjust any conversions (e.g.,
parseFloat(t.amount)) only if the schema/type allows string amounts to ensure
type correctness and avoid using any.
client/src/components/property/GoogleDriveEmbed.tsx (1)

4-10: Unused propertyId prop - same issue as GoogleSheetsEmbed.

The propertyId is declared in the interface but not used. Consider removing it or documenting its intended future use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/property/GoogleDriveEmbed.tsx` around lines 4 - 10,
GoogleDriveEmbedProps declares a propertyId that is never used in the component;
either remove propertyId from the GoogleDriveEmbedProps interface and from the
component signature or, if it’s intended for future use, add a clear TODO
comment and use it (for example pass it to GoogleDriveEmbed or downstream
logic). Update the interface GoogleDriveEmbedProps and the component function
GoogleDriveEmbed accordingly, and mirror the same fix you applied for
GoogleSheetsEmbed so prop types and usage remain consistent.
client/src/components/property/WorkspaceTab.tsx (1)

18-24: Consider adding loading/error state handling for better UX.

The useQuery hook doesn't handle loading or error states. While the child components may gracefully handle undefined values, showing a loading indicator or error message would improve the user experience.

💡 Optional: Add loading/error handling
 export default function WorkspaceTab({ propertyId }: WorkspaceTabProps) {
-  const { data: status } = useQuery<GoogleStatus>({
+  const { data: status, isLoading, error } = useQuery<GoogleStatus>({
     queryKey: ['/api/integrations/google/status'],
     staleTime: 5 * 60 * 1000,
   });
 
   const connected = status?.connected ?? false;
 
+  if (isLoading) {
+    return <div className="p-4 text-muted-foreground">Loading workspace...</div>;
+  }
+
   return (

As per coding guidelines: "Use TanStack React Query for all API data fetching; handle loading and error states with appropriate UI feedback."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/property/WorkspaceTab.tsx` around lines 18 - 24, The
WorkspaceTab component currently reads only data from useQuery and derives
connected from status; update the useQuery call to destructure isLoading,
isError, and error (e.g., const { data: status, isLoading, isError, error } =
useQuery(...)) and add early-return UI: render a loading indicator when
isLoading is true and render a concise error message (using error.message) when
isError is true before deriving connected or rendering child components — keep
this logic inside WorkspaceTab so downstream components always receive defined
props.
client/src/components/property/GoogleSheetsEmbed.tsx (1)

4-10: Unused propertyId prop.

The propertyId is declared in the interface but not used in the component. Either remove it from the interface or use it (e.g., for a future feature like creating property-specific spreadsheets).

🧹 Remove unused prop
 interface GoogleSheetsEmbedProps {
-  propertyId: string;
   sheetId?: string;
   connected: boolean;
 }

-export default function GoogleSheetsEmbed({ sheetId, connected }: GoogleSheetsEmbedProps) {
+export default function GoogleSheetsEmbed({ sheetId, connected }: GoogleSheetsEmbedProps) {

Note: If removing propertyId, also update the parent WorkspaceTab.tsx to stop passing it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/property/GoogleSheetsEmbed.tsx` around lines 4 - 10,
The interface GoogleSheetsEmbedProps declares propertyId but the component
GoogleSheetsEmbed does not use it; either remove propertyId from
GoogleSheetsEmbedProps and from the GoogleSheetsEmbed parameter list, and update
the caller in WorkspaceTab.tsx to stop passing propertyId, or actually use
propertyId inside GoogleSheetsEmbed (e.g., accept it in the signature and
reference it where needed for future feature hooks/URLs); locate the symbols
GoogleSheetsEmbedProps and function GoogleSheetsEmbed to make the change and
also update the parent component WorkspaceTab.tsx to match the new prop shape.
client/src/components/property/GoogleCalendarEmbed.tsx (2)

113-129: Add htmlFor attributes to Labels for accessibility.

Labels lack htmlFor attributes and inputs lack corresponding id attributes. This breaks the explicit label-input association that screen readers depend on.

♿ Proposed fix for accessibility
           <div className="space-y-2">
-            <Label>Title *</Label>
-            <Input value={summary} onChange={e => setSummary(e.target.value)} required placeholder="e.g. Maintenance inspection" />
+            <Label htmlFor="event-title">Title *</Label>
+            <Input id="event-title" value={summary} onChange={e => setSummary(e.target.value)} required placeholder="e.g. Maintenance inspection" />
           </div>
           <div className="space-y-2">
-            <Label>Description</Label>
-            <Input value={description} onChange={e => setDescription(e.target.value)} placeholder="Optional details" />
+            <Label htmlFor="event-description">Description</Label>
+            <Input id="event-description" value={description} onChange={e => setDescription(e.target.value)} placeholder="Optional details" />
           </div>
           <div className="grid grid-cols-2 gap-3">
             <div className="space-y-2">
-              <Label>Start *</Label>
-              <Input type="datetime-local" value={startDateTime} onChange={e => setStartDateTime(e.target.value)} required />
+              <Label htmlFor="event-start">Start *</Label>
+              <Input id="event-start" type="datetime-local" value={startDateTime} onChange={e => setStartDateTime(e.target.value)} required />
             </div>
             <div className="space-y-2">
-              <Label>End *</Label>
-              <Input type="datetime-local" value={endDateTime} onChange={e => setEndDateTime(e.target.value)} required />
+              <Label htmlFor="event-end">End *</Label>
+              <Input id="event-end" type="datetime-local" value={endDateTime} onChange={e => setEndDateTime(e.target.value)} required />
             </div>
           </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/property/GoogleCalendarEmbed.tsx` around lines 113 -
129, The Labels in GoogleCalendarEmbed.tsx are missing htmlFor attributes and
the corresponding Inputs lack id attributes, breaking screen-reader
associations; fix by adding unique id props to each Input (e.g. summary-input,
description-input, start-datetime-input, end-datetime-input) and set the
matching htmlFor on the corresponding Label components that wrap the summary,
description, startDateTime and endDateTime fields (ensure the id strings match
exactly and remain stable if variables change).

67-76: Unused propertyId parameter in type signature.

propertyId is declared as a required property in the type (line 72) but is not destructured from props (line 68) and never used in the function body. Either remove it from the type signature or destructure and use it if needed for future functionality.

🧹 Proposed fix to remove unused parameter
 function AddEventDialog({
   calendarId,
   open,
   onOpenChange,
 }: {
-  propertyId: string;
   calendarId?: string;
   open: boolean;
   onOpenChange: (open: boolean) => void;
 }) {

Also update the call site if you remove the prop:

       <AddEventDialog
-        propertyId={propertyId}
         calendarId={calendarId}
         open={addEventOpen}
         onOpenChange={setAddEventOpen}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/property/GoogleCalendarEmbed.tsx` around lines 67 - 76,
The AddEventDialog component's props type includes a required propertyId that is
not destructured or used; either remove propertyId from the props type or
destructure and use it where needed. To fix: update the AddEventDialog signature
(the props type object around AddEventDialog) by removing propertyId from the
type definition if it's not needed and adjust any call sites that pass
propertyId, or modify the function to accept ({ propertyId, calendarId, open,
onOpenChange }) and use propertyId in the component logic; ensure the props type
and all callers of AddEventDialog (where it's instantiated) are kept consistent.
server/lib/google-api.ts (2)

98-99: Inconsistent error handling across helper functions.

The class methods (exchangeCode, refreshToken) include the response body in errors, but the helper functions only include the status code. This makes debugging API failures harder since Google often returns detailed error messages in the response body.

🔧 Proposed fix to include response body in errors
-  if (!res.ok) throw new Error(`Calendar API error: ${res.status}`);
+  if (!res.ok) {
+    const err = await res.text();
+    throw new Error(`Calendar API error: ${res.status} - ${err}`);
+  }

Apply the same pattern to createCalendarEvent, listDriveFiles, and createDriveFolder.

Also applies to: 115-116, 127-128, 143-144

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/lib/google-api.ts` around lines 98 - 99, Helper functions
createCalendarEvent, listDriveFiles, and createDriveFolder throw errors only
with the status code, causing inconsistent handling versus class methods like
exchangeCode and refreshToken; update each helper to read the response body when
res.ok is false (e.g., await res.text() or res.json() depending on content), and
include that body in the thrown Error message alongside the status code and a
clear prefix (e.g., "Calendar API error" / "Drive API error") so the error
contains the server's detailed message for debugging.

119-129: Consider validating folderId format before query interpolation.

The folderId is directly interpolated into the query string. While Google Drive IDs are typically alphanumeric, defensive validation would prevent potential query syntax issues if an unexpected value is passed.

🛡️ Proposed defensive validation
 export async function listDriveFiles(accessToken: string, folderId: string) {
+  // Google Drive IDs are alphanumeric with hyphens/underscores
+  if (!/^[\w-]+$/.test(folderId)) {
+    throw new Error('Invalid folder ID format');
+  }
   const q = `'${folderId}' in parents and trashed = false`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/lib/google-api.ts` around lines 119 - 129, The listDriveFiles function
currently interpolates folderId directly into the Drive query string (q), so add
defensive validation for the folderId parameter: ensure folderId is non-empty
and matches an expected Drive ID pattern (e.g., allowed characters like
alphanumeric, hyphen, underscore) and reject/throw a descriptive error if it
fails validation; alternatively sanitize/encode the value before building q to
avoid breaking the query. Update listDriveFiles to perform this check on
folderId and return/throw a clear error when validation fails so malformed IDs
cannot be interpolated into q.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 498a4d4e-0100-43ac-b832-06faf4da3ebc

📥 Commits

Reviewing files that changed from the base of the PR and between b394ce3 and 03942d0.

📒 Files selected for processing (34)
  • client/src/App.tsx
  • client/src/components/layout/Sidebar.tsx
  • client/src/components/property/AddLeaseDialog.tsx
  • client/src/components/property/AddUnitDialog.tsx
  • client/src/components/property/CommsPanel.tsx
  • client/src/components/property/EditPropertyDialog.tsx
  • client/src/components/property/GoogleCalendarEmbed.tsx
  • client/src/components/property/GoogleDriveEmbed.tsx
  • client/src/components/property/GoogleSheetsEmbed.tsx
  • client/src/components/property/PropertyDetailPanel.tsx
  • client/src/components/property/WorkflowBoard.tsx
  • client/src/components/property/WorkspaceTab.tsx
  • client/src/hooks/use-accounts.ts
  • client/src/hooks/use-integrations.ts
  • client/src/hooks/use-reports.ts
  • client/src/hooks/use-transactions.ts
  • client/src/pages/Accounts.tsx
  • client/src/pages/Connections.tsx
  • client/src/pages/Integrations.tsx
  • client/src/pages/PropertyDetail.tsx
  • client/src/pages/Reports.tsx
  • client/src/pages/Transactions.tsx
  • database/system.schema.ts
  • server/app.ts
  • server/env.ts
  • server/lib/google-api.ts
  • server/lib/sendgrid.ts
  • server/lib/twilio.ts
  • server/routes/accounts.ts
  • server/routes/comms.ts
  • server/routes/google.ts
  • server/routes/transactions.ts
  • server/routes/workflows.ts
  • server/storage/system.ts

Backend reads c.req.text() not c.req.formData(), so sending FormData
wraps the CSV in boundary markers that fail to parse.

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

claude bot commented Mar 12, 2026

Code Review

Overall this is a solid, well-structured PR. The code follows existing Hono/Drizzle patterns and the features are well-scoped. A few issues need attention before merging.

Security Critical

1. Workflow mutations lack tenant isolation

In server/routes/workflows.ts, the PATCH /:id/approve and PATCH /:id/complete handlers never verify that the workflow belongs to the current tenant. storage.updateWorkflow() queries by id alone with no tenant filter, so any authenticated user can approve or complete any workflow UUID across tenants. The general PATCH /:id route has the same gap. Fix: pass tenantId into the storage method and add it to the WHERE clause, or verify existing.tenantId === tenantId before writing.

2. /api/comms/sendto field not validated

Any authenticated user can supply arbitrary phone numbers or email addresses. Without validation the endpoint can send messages billed to your Twilio/SendGrid account. Validate to against E.164 for SMS and a basic email pattern for email before forwarding.

Code Quality

3. formatTransaction loses type safety

function formatTransaction(t: any) should use typeof schema.transactions.$inferSelect — consistent with the rest of the codebase.

4. Redundant ternary in CommsPanel.handleSend

message: channel === 'email' ? message : message — both branches return message. Remove the ternary.

5. createWorkflow missing required-field guard

title is notNull() in the schema but the route has no validation. A missing title propagates to Postgres and returns a 500. Add an explicit check and return 400.

6. Template invocation by positional spread is fragile

In /api/comms/template, Object.values(params) is spread into the template function. Object.values order depends on client insertion order. A client sending { endDate, tenantName } instead of { tenantName, endDate } silently renders the template with swapped values. Destructure params by key to match each template signature.

7. Google OAuth callback references wrong state field

generateOAuthState(tenantId, secret) stores the tenantId, but the decoded result is accessed via stateData.userId. Verify the actual field name in oauth-state-edge.ts and align, or a future rename will silently break Google auth.

8. CSV export anchor not appended to DOM

document.createElement('a') followed immediately by .click() may not work in all browsers. Append to body, click, then remove.

Minor Suggestions

9. No rate limiting on comms endpointsPOST /api/comms/send triggers a billable API call on every request. A per-tenant counter in KV (same pattern as webhook idempotency) would limit blast radius from a leaked token.

10. Workflow status not validatedPATCH /:id accepts any string as status. An enum check against the valid values prevents bad data and unintended backward transitions.

What is well done

  • Edge-compatible Twilio and SendGrid clients (fetch + btoa, no Node deps) — consistent with the rest of the stack.
  • updateWorkflow merges metadata rather than overwriting — the correct pattern.
  • Google OAuth follows the same CSRF-protected state token approach as Wave.
  • Comms status endpoint separates config check from actual send — clean.
  • Role-based sidebar visibility is correctly wired for all four new nav items.
  • formatTransaction extracted to a shared helper is a good DRY cleanup.

Items 1 and 2 (workflow tenant isolation, comms to validation) should be blockers. The rest can be addressed here or as fast-follows.

The legacy Express entry point (server/index.ts) imports Hono middleware
that doesn't exist as Express middleware. Add no-op shims for
chittyConnectAuth, serviceAuth, resolveTenant, and isAuthenticated so
the dev server can start. Also add getSessionUser/getSessionContext to
SystemStorage for legacy route compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 12, 2026 09:48
Copy link
Contributor

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/routes.ts (1)

1803-1803: ⚠️ Potential issue | 🔴 Critical

Security: Mercury webhook endpoint is unprotected due to no-op serviceAuth.

The serviceAuth shim on line 8 passes all requests unconditionally. This means the /api/integrations/mercury/webhook endpoint accepts unauthenticated POST requests, allowing attackers to:

  • Inject fake financial events into your system
  • Trigger downstream orchestration to evidence/ledger/chronicle services with malicious payloads
  • Potentially corrupt transaction records

The real serviceAuth middleware (see context snippet 2) validates Bearer tokens against CHITTY_AUTH_SERVICE_TOKEN.

🛡️ Proposed fix - inline validation for webhook
 app.post("/api/integrations/mercury/webhook", serviceAuth, async (req: Request, res: Response) => {
+    // Fallback auth check if using legacy shims
+    const expected = process.env.CHITTY_AUTH_SERVICE_TOKEN;
+    if (expected) {
+      const auth = req.headers['authorization'] ?? '';
+      const token = auth.startsWith('Bearer ') ? auth.slice(7) : '';
+      if (!token || token !== expected) {
+        return res.status(401).json({ error: 'unauthorized' });
+      }
+    }
     try {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes.ts` at line 1803, The route handler for POST
"/api/integrations/mercury/webhook" currently uses a no-op serviceAuth shim that
allows all requests; replace it by enforcing the real token check used
elsewhere: update the serviceAuth middleware (or inline-check in the route) to
read the Authorization header, require a Bearer token, compare it to
process.env.CHITTY_AUTH_SERVICE_TOKEN, and return 401/403 for missing/invalid
tokens before proceeding to the webhook logic so only requests with the
configured service token are accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/routes.ts`:
- Around line 58-59: The isAuthenticated shim is a no-op causing the
/universal-connector/secured handler to receive undefined req.user and crash
when accessing req.user.claims.sub; replace the shim with real auth handling by
either wiring Express-compatible auth middleware that verifies the token and
populates req.user (e.g., decode/verify JWT or call the existing Hono auth
adapter) or, if temporarily needed, have isAuthenticated validate presence of an
authorization header and set req.user = { claims: { sub: <validated-sub> } }
before calling next(); ensure the middleware name is still isAuthenticated so
the secured route sees a populated req.user.claims.sub.
- Around line 5-9: The three no-op shim functions chittyConnectAuth,
resolveTenant, and serviceAuth completely bypass auth and tenant resolution;
replace the stubs with real checks or gate them behind an explicit dev flag:
implement service token validation in serviceAuth, populate req.tenantId (or
context c.set('tenantId')) in resolveTenant so downstream handlers like routes
using c.get('tenantId') get a tenant, and restore user identity checks in
chittyConnectAuth; if you must keep no-ops for local dev, require a boolean flag
(e.g. ALLOW_LEGACY_EXPRESS_SHIMS=true) and add a strong warning log, and throw
an error when NODE_ENV==='production' to prevent accidental deployment.

In `@server/storage/system.ts`:
- Around line 10-13: getSessionUser currently returns the first row from
schema.users with no session check; change it to require explicit session
context (e.g., accept a sessionId or userId parameter) and validate that
parameter against a session or authentication table before returning a user, or
else fail safely (return null or throw an authentication error). Locate
getSessionUser and replace the blind select on schema.users with a query that
joins/filters by the provided session token or user id (or rejects when no valid
session provided) and ensure callers are updated to pass the session context
instead of relying on a no-op shim.

---

Outside diff comments:
In `@server/routes.ts`:
- Line 1803: The route handler for POST "/api/integrations/mercury/webhook"
currently uses a no-op serviceAuth shim that allows all requests; replace it by
enforcing the real token check used elsewhere: update the serviceAuth middleware
(or inline-check in the route) to read the Authorization header, require a
Bearer token, compare it to process.env.CHITTY_AUTH_SERVICE_TOKEN, and return
401/403 for missing/invalid tokens before proceeding to the webhook logic so
only requests with the configured service token are accepted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71bbf307-12b5-444b-87ad-cf3a5b1272a8

📥 Commits

Reviewing files that changed from the base of the PR and between 8c44cec and 4b6cb98.

📒 Files selected for processing (2)
  • server/routes.ts
  • server/storage/system.ts

Copy link
Contributor

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

Copilot reviewed 35 out of 35 changed files in this pull request and generated 9 comments.


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

@claude
Copy link

claude bot commented Mar 12, 2026

test

@claude
Copy link

claude bot commented Mar 12, 2026

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Canonical URI: chittycanon://core/services/chittyfinance | Tier 3 (Service Layer) | finance.chitty.cc

Project Overview

ChittyFinance is a full-stack financial management platform for the ChittyOS ecosystem. It provides intelligent financial tracking, AI-powered advice, recurring charge optimization, and integrations with Mercury Bank, Wave Accounting, and Stripe payments.

Architecture: Hono on Cloudflare Workers (production) + legacy Express (local dev). PostgreSQL (Neon) with Drizzle ORM. React frontend.

Essential Commands

Development

npm install              # Install dependencies
npm run dev              # Auto-detect mode and start dev server on port 5000
npm run dev:standalone   # Start in standalone mode (local development)
npm run dev:system       # Start in system mode (ChittyOS integration)
npm run check            # TypeScript type checking
npm run mode:detect      # Detect and display current mode
npm run mode:setup       # Setup mode configuration (script not yet implemented)

Build & Deployment

npm run build            # Build system mode (default for production)
npm run build:standalone # Build standalone mode (outputs to dist/standalone)
npm run build:system     # Build system mode (outputs to dist/system)
npm run build:both       # Build both modes
npm run start            # Run standalone production build locally
npm run deploy           # Deploy to Cloudflare Workers (system mode)
npm run deploy:staging   # Deploy to staging environment
npm run deploy:production # Deploy to production environment

Database Operations

npm run db:push              # Push schema changes (uses current drizzle.config.ts)
npm run db:push:system       # Push system schema to PostgreSQL
npm run db:push:standalone   # Push standalone schema to SQLite
npm run db:seed              # Seed IT CAN BE LLC entity structure

First-Time Setup (System Mode):

MODE=system npm run db:push:system   # Create tables
npm run db:seed                      # Create tenants and users

First-Time Setup (Standalone Mode):

npm run db:push:standalone   # Create SQLite tables
# No seeding needed - single user mode

Critical:

  • Port 5001 is used in server/index.ts for local Express development
  • Server uses reusePort: true for multiple process support on the same port

Architecture

Dual-Mode Operation

ChittyFinance supports two operational modes (controlled by MODE environment variable):

Standalone Mode (default for local development):

  • SQLite database for quick local development
  • Single-tenant (no multi-tenancy overhead)
  • Simplified schema in database/standalone.schema.ts
  • Build output: dist/standalone/
  • Database file: ./chittyfinance.db
  • Run: npm run dev or npm run dev:standalone

System Mode (production - multi-tenant):

  • PostgreSQL (Neon) with full multi-tenancy
  • Supports IT CAN BE LLC entity structure
  • Complete schema in database/system.schema.ts
  • Build output: dist/system/
  • Cloudflare Workers deployment
  • Run: npm run dev:system
  • Deploy: npm run deploy or npm run deploy:production

Mode Detection:

  • Default: npm run dev runs in standalone mode
  • Explicitly set: MODE=system npm run dev
  • Auto-detection script: npm run mode:detect

Multi-Tenant Architecture (System Mode)

IT CAN BE LLC Entity Structure:

IT CAN BE LLC (holding)
├── JEAN ARLENE VENTURING LLC (personal, 85% owner)
├── ARIBIA LLC (series, 100% owned)
│   ├── ARIBIA LLC - MGMT (management)
│   │   ├── Chicago Furnished Condos (consumer brand)
│   │   └── Chitty Services (vendor/tech services)
│   ├── ARIBIA LLC - CITY STUDIO (property)
│   │   └── 550 W Surf St C211, Chicago IL
│   └── ARIBIA LLC - APT ARLENE (property)
│       └── 4343 N Clarendon #1610, Chicago IL
└── ChittyCorp LLC (holding, pending formation)

Tenant Types:

  • holding - Holding companies (IT CAN BE LLC, ChittyCorp LLC)
  • series - Series LLCs (ARIBIA LLC)
  • property - Property holding entities (City Studio, Apt Arlene)
  • management - Management companies (ARIBIA LLC - MGMT)
  • personal - Personal entities (JEAN ARLENE VENTURING LLC)

Key Features:

  • Each tenant has isolated financial data
  • Inter-company transaction tracking
  • Property-specific rent roll and lease management
  • User access control per tenant (roles: owner, admin, manager, viewer)
  • Consolidated reporting across entities

Tech Stack

  • Frontend: React 18 with TypeScript, Wouter (routing), shadcn/ui (Radix UI components)
  • Backend: Hono (Cloudflare Workers, production) / Express.js (legacy, local dev)
  • Database: Neon PostgreSQL with Drizzle ORM
  • Build: Vite (frontend), esbuild (backend)
  • Styling: Tailwind CSS with tailwindcss-animate
  • State: TanStack React Query for server state
  • Payments: Stripe integration
  • AI: OpenAI GPT-4o for financial advice

Project Structure

chittyfinance/
├── client/                 # React frontend (Vite root)
│   └── src/
│       ├── pages/         # Page components (Dashboard, Settings, Landing)
│       ├── components/    # Reusable UI components (shadcn/ui)
│       ├── hooks/         # Custom React hooks
│       └── lib/           # Client utilities
├── server/                # Hono backend (Cloudflare Workers)
│   ├── app.ts            # Hono app factory with middleware wiring
│   ├── env.ts            # HonoEnv type (Bindings + Variables)
│   ├── worker.ts         # Cloudflare Workers entry point
│   ├── index.ts          # Legacy Express entry (standalone dev)
│   ├── routes.ts         # Legacy Express routes (reference only)
│   ├── routes/            # Hono route modules (19 files)
│   │   ├── health.ts     # /health, /api/v1/status
│   │   ├── docs.ts       # /api/v1/documentation (OpenAPI spec)
│   │   ├── accounts.ts   # /api/accounts
│   │   ├── summary.ts    # /api/summary
│   │   ├── tenants.ts    # /api/tenants
│   │   ├── properties.ts # /api/properties (CRUD + financials + rent roll + P&L)
│   │   ├── transactions.ts # /api/transactions
│   │   ├── integrations.ts # /api/integrations
│   │   ├── tasks.ts      # /api/tasks
│   │   ├── ai.ts         # /api/ai-messages
│   │   ├── mercury.ts    # /api/mercury (via ChittyConnect)
│   │   ├── github.ts     # /api/github
│   │   ├── stripe.ts     # /api/integrations/stripe
│   │   ├── wave.ts       # /api/integrations/wave (OAuth)
│   │   ├── charges.ts    # /api/charges (recurring)
│   │   ├── forensics.ts  # /api/forensics (21 endpoints)
│   │   ├── valuation.ts  # /api/properties/:id/valuation (multi-source AVM)
│   │   ├── import.ts     # /api/import (TurboTenant CSV + Wave sync)
│   │   └── webhooks.ts   # Stripe/Mercury webhooks
│   ├── middleware/        # auth, tenant, error middleware
│   ├── storage/           # SystemStorage (Drizzle queries)
│   ├── db/                # connection.ts (Neon HTTP), schema.ts
│   └── lib/               # Server utilities
│       ├── wave-api.ts           # Wave Accounting GraphQL client
│       ├── oauth-state-edge.ts   # Edge-compatible HMAC OAuth state
│       ├── chargeAutomation.ts   # Recurring charge analysis (stubs)
│       ├── forensicService.ts    # Forensic algorithms (legacy)
│       └── valuation/            # Property valuation providers
│           ├── types.ts          # ValuationProvider interface, AggregatedValuation
│           ├── zillow.ts         # Zillow via RapidAPI
│           ├── redfin.ts         # Redfin via RapidAPI
│           ├── housecanary.ts    # HouseCanary REST API
│           ├── attom.ts          # ATTOM Data Gateway
│           ├── county.ts         # Cook County Assessor (Socrata)
│           └── index.ts          # Provider registry + confidence-weighted aggregation
├── database/              # Schema definitions
│   ├── system.schema.ts   # Multi-tenant PostgreSQL (UUID-based)
│   └── standalone.schema.ts # Single-tenant SQLite
├── shared/                # Shared types and schemas
│   └── schema.ts         # Legacy schema with forensic tables (integer-ID)
└── deploy/
    └── system-wrangler.toml # Cloudflare Workers config

Database Architecture

System Mode Schema (Multi-Tenant PostgreSQL)

Location: database/system.schema.ts

Core Tables:

  • tenants - Legal entities (LLCs, properties, management companies)
  • users - User accounts with email/password + optional ChittyID
  • tenant_users - User access to tenants with role-based permissions
  • accounts - Bank accounts, credit cards (tenant-scoped)
  • transactions - Financial transactions with decimal precision
  • intercompany_transactions - Transfers between tenants

Property Management Tables:

  • properties - Real estate assets
  • units - Rental units (if property has multiple units)
  • leases - Tenant leases with rent and dates
  • property_valuations - Cached AVM estimates from external providers (Zillow, Redfin, HouseCanary, ATTOM, County)

Supporting Tables:

  • integrations - Mercury/Wave/Stripe API connections
  • tasks - Financial tasks
  • ai_messages - AI conversation history

Key Characteristics:

  • UUIDs for primary keys (better for distributed systems)
  • Decimal precision for all monetary amounts (12,2)
  • Full multi-tenancy with tenant isolation
  • Hierarchical tenants (parent-child relationships)
  • Indexed for performance (tenant_id, date, etc.)

Standalone Mode Schema (Single-Tenant SQLite)

Location: database/standalone.schema.ts

Simplified Tables:

  • users, accounts, transactions, properties, tasks, integrations

Key Characteristics:

  • Text IDs (simpler for SQLite)
  • Real (float) for amounts (acceptable for dev)
  • No multi-tenancy (single user)
  • Faster for local development

Database Connection

Mode-Aware Connection (server/db.ts):

The database connection automatically switches based on MODE environment variable:

System Mode (MODE=system):

// PostgreSQL (Neon) with multi-tenant schema
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzleNeon({ client: pool, schema: systemSchema });

Standalone Mode (MODE=standalone):

// SQLite for local development
const sqlite = new Database('./chittyfinance.db');
const db = drizzleBetterSqlite(sqlite, { schema: standaloneSchema });

Environment Variables:

  • System mode: DATABASE_URL (Neon PostgreSQL connection string)
  • Standalone mode: SQLITE_FILE (optional, defaults to ./chittyfinance.db)

Database Seeding (System Mode)

Seed Script: database/seeds/it-can-be-llc.ts

Creates the complete IT CAN BE LLC entity structure:

  1. IT CAN BE LLC (parent holding company)
  2. JEAN ARLENE VENTURING LLC (85% owner, personal income funnel)
  3. ARIBIA LLC (series parent)
  4. ARIBIA LLC - MGMT (management company with two brands)
  5. ARIBIA LLC - CITY STUDIO (property entity)
  6. ARIBIA LLC - APT ARLENE (property entity)
  7. ChittyCorp LLC (pending formation)

Also creates:

  • Property records for City Studio and Apt Arlene
  • User accounts for Nicholas Bianchi and Sharon E Jones
  • Access permissions for each user to appropriate tenants

Run seeding:

npm run db:seed

Note: Only run after pushing the system schema (npm run db:push:system)

Storage Abstraction Layer

Critical Pattern: All database access goes through server/storage.ts. Never write direct Drizzle queries in routes.

⚠️ Important: The current server/storage.ts uses the old shared/schema.ts and needs to be updated to use database/system.schema.ts with tenant-aware queries.

Interface (server/storage.ts:12-42):

export interface IStorage {
  // User operations
  getUser(id: number): Promise<User | undefined>;
  getUserByUsername(username: string): Promise<User | undefined>;
  createUser(user: InsertUser): Promise<User>;

  // Integration operations
  getIntegrations(userId: number): Promise<Integration[]>;
  createIntegration(integration: InsertIntegration): Promise<Integration>;
  updateIntegration(id: number, integration: Partial<Integration>): Promise<Integration | undefined>;

  // Financial summary operations
  getFinancialSummary(userId: number): Promise<FinancialSummary | undefined>;
  createFinancialSummary(summary: InsertFinancialSummary): Promise<FinancialSummary>;

  // Transaction, Task, and AI Message operations...
}

Usage in routes (server/routes.ts):

import { storage } from "./storage";

const user = await storage.getUserByUsername("demo");
const summary = await storage.getFinancialSummary(user.id);

Key Features

1. Demo Authentication

Current implementation: Auto-login as "demo" user (no real authentication).

Pattern (server/routes.ts:21-33):

api.get("/session", async (req: Request, res: Response) => {
  const user = await storage.getUserByUsername("demo");
  // Returns demo user for all requests
});

Note: All API routes assume demo user. Real authentication needs to be implemented for production.

2. Financial Dashboard

  • Real-time financial summary: Cash on hand, revenue, expenses, outstanding invoices
  • Data source: Cached in financialSummaries table, fetched from integrations
  • Demo data: Hardcoded values if no summary exists (server/routes.ts:51-55)

3. AI Financial Advice

Location: server/lib/openai.ts

Model: GPT-4o (hardcoded, comment at line 3 warns against changing)

Functions:

  • getFinancialAdvice() - Conversational financial advice based on financial data
  • generateCostReductionPlan() - AI-generated cost reduction strategies

API endpoint: POST /api/ai/message sends user query to OpenAI with financial context

4. Recurring Charge Automation

Location: server/lib/chargeAutomation.ts

Capabilities:

  • Identify recurring charges from integrated services
  • Generate optimization recommendations (cancel, downgrade, consolidate, negotiate)
  • Calculate potential savings

API endpoints:

  • GET /api/recurring-charges - List all recurring charges
  • GET /api/recurring-charges/:id/optimizations - Get optimization suggestions
  • POST /api/recurring-charges/:id/manage - Execute management action

5. Third-Party Integrations (Phase 3 - COMPLETED)

Mercury Bank (server/lib/financialServices.ts:22-51, via ChittyConnect):

  • Real integration through ChittyConnect backend
  • Multi-account support with account selection
  • Static egress IP for bank compliance
  • Fetches balances and transactions
  • Falls back to minimal data in standalone mode
  • Routes: /api/mercury/accounts, /api/mercury/select-accounts
  • Configuration: Requires CHITTYCONNECT_API_BASE + CHITTYCONNECT_API_TOKEN

Wave Accounting (server/lib/wave-api.ts + server/lib/financialServices.ts:54-116):

  • Real integration via OAuth 2.0 + GraphQL API
  • Complete OAuth flow with CSRF protection (HMAC-signed state tokens)
  • Fetches invoices, expenses, and financial summaries
  • Automatic token refresh support
  • Routes: /api/integrations/wave/authorize, /callback, /refresh
  • Requirements: Wave Pro subscription, WAVE_CLIENT_ID, WAVE_CLIENT_SECRET
  • Security: Uses server/lib/oauth-state.ts for secure state token generation/validation

Stripe (server/lib/stripe.ts):

  • Real integration for payment processing
  • Customer management with tenant metadata
  • Checkout session creation (ad-hoc payments)
  • Webhook verification and idempotent event processing
  • Routes: /api/integrations/stripe/connect, /checkout, /webhook
  • Configuration: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
  • Events stored in webhook_events table (see shared/finance.schema.ts)

DoorLoop (server/lib/financialServices.ts:119-147):

  • Mock data (property management)
  • Returns hardcoded rent roll and maintenance data
  • Real API integration pending

GitHub (server/lib/github.ts):

  • Real GitHub API integration (not mock)
  • Fetches repositories, commits, PRs, issues
  • Used for project cost attribution

Integration Status Monitoring:

  • New endpoint: GET /api/integrations/status (server/routes.ts:122-126)
  • Validates which integrations are properly configured
  • Uses server/lib/integration-validation.ts to check environment variables
  • Returns configuration status for wave, stripe, mercury, openai

Utilities

check_system_operations_duplicates.js

A utility script for analyzing and detecting duplicate operations in system mode. Located at the project root. This script helps maintain code quality when working with ChittyOS integration.

Development Workflows

Adding a New Feature

  1. Update database schema in shared/schema.ts:

    export const newTable = pgTable("new_table", {
      id: serial("id").primaryKey(),
      userId: integer("user_id").notNull().references(() => users.id),
      // ... fields
    });
  2. Run migration: npm run db:push

  3. Add storage methods in server/storage.ts:

    async getNewData(userId: number): Promise<NewData[]> {
      return await db.select().from(newTable).where(eq(newTable.userId, userId));
    }
  4. Create API routes in server/routes.ts:

    api.get("/new-data", async (req: Request, res: Response) => {
      const user = await storage.getUserByUsername("demo");
      const data = await storage.getNewData(user.id);
      res.json(data);
    });
  5. Build frontend in client/src/pages/:

    • Use TanStack Query for data fetching
    • Import shadcn/ui components from @/components/ui/

Working with AI Features

OpenAI Configuration (server/lib/openai.ts):

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const openai = OPENAI_API_KEY ? new OpenAI({ apiKey: OPENAI_API_KEY }) : null;

Best practices:

  • Model is GPT-4o (do not change without user request per comment)
  • Max tokens: 500 for financial advice
  • Include financial context in system prompt
  • Handle API errors gracefully (rate limits, invalid keys)
  • When OPENAI_API_KEY is not set, AI functions return rule-based fallback advice
  • OpenAI client is null when unconfigured — functions guard with if (!openai) early returns

Path Aliases

Configured in tsconfig.json:18-21:

{
  "@/*": ["./client/src/*"],
  "@shared/*": ["./shared/*"]
}

Additional alias in vite.config.ts:25:

"@assets": path.resolve(import.meta.dirname, "attached_assets")

Usage:

import { Button } from "@/components/ui/button";
import { users } from "@shared/schema";
import logo from "@assets/logo.png";

API Endpoints

Authentication

  • GET /api/session - Get current demo user (no auth required)

Financial Data

  • GET /api/financial-summary - Get cached financial summary
  • GET /api/transactions - List transactions with optional filters

Integrations

  • GET /api/integrations - List configured integrations
  • GET /api/integrations/status - Check which integrations are properly configured
  • POST /api/integrations - Add new integration
  • PATCH /api/integrations/:id - Update integration credentials
  • GET /api/integrations/events - List webhook events with optional source filter
  • POST /api/admin/events/replay - Replay webhook events to ChittyOS services

Wave Accounting

  • GET /api/integrations/wave/authorize - Start OAuth flow (returns authorization URL)
  • GET /api/integrations/wave/callback - OAuth callback handler (redirects to /connections)
  • POST /api/integrations/wave/refresh - Refresh expired access token

Stripe

  • POST /api/integrations/stripe/connect - Create/fetch Stripe customer
  • POST /api/integrations/stripe/checkout - Create ad-hoc payment session
  • POST /api/integrations/stripe/webhook - Stripe webhook endpoint (signature verified)

Mercury Bank

  • GET /api/mercury/accounts - List available Mercury accounts via ChittyConnect
  • POST /api/mercury/select-accounts - Select which accounts to sync
  • POST /api/integrations/mercury/webhook - Mercury webhook endpoint (authenticated)

Recurring Charges

  • GET /api/charges/recurring - List recurring charges from integrations
  • GET /api/charges/optimizations - Get optimization recommendations
  • POST /api/charges/manage - Cancel/modify a recurring charge

AI Services

  • POST /api/ai/advice - Get initial AI financial advice
  • POST /api/ai/cost-reduction - Generate cost reduction plan
  • POST /api/ai/message - Conversational AI advice (includes previous context)

GitHub Integration

  • GET /api/github/repositories - List user repositories
  • GET /api/github/repositories/:owner/:repo/commits - Get repository commits
  • GET /api/github/repositories/:owner/:repo/pulls - Get pull requests
  • GET /api/github/repositories/:owner/:repo/issues - Get issues

Forensic Accounting

  • GET /api/forensics/investigations - List investigations
  • GET /api/forensics/investigations/:id - Get investigation
  • POST /api/forensics/investigations - Create investigation
  • PATCH /api/forensics/investigations/:id/status - Update status
  • POST /api/forensics/investigations/:id/evidence - Add evidence
  • GET /api/forensics/investigations/:id/evidence - List evidence
  • POST /api/forensics/evidence/:id/custody - Update chain of custody
  • POST /api/forensics/investigations/:id/analyze - Comprehensive analysis
  • POST /api/forensics/investigations/:id/analyze/duplicates - Duplicate payment detection
  • POST /api/forensics/investigations/:id/analyze/timing - Unusual timing detection
  • POST /api/forensics/investigations/:id/analyze/round-dollars - Round dollar anomalies
  • POST /api/forensics/investigations/:id/analyze/benfords-law - Benford's Law analysis
  • POST /api/forensics/investigations/:id/trace-funds - Trace flow of funds
  • POST /api/forensics/investigations/:id/flow-of-funds - Create flow of funds record
  • GET /api/forensics/investigations/:id/flow-of-funds - Get flow of funds
  • POST /api/forensics/investigations/:id/calculate-damages/direct-loss - Direct loss calculation
  • POST /api/forensics/investigations/:id/calculate-damages/net-worth - Net worth method
  • POST /api/forensics/calculate-interest - Pre-judgment interest calculation
  • POST /api/forensics/investigations/:id/generate-summary - Executive summary
  • POST /api/forensics/investigations/:id/reports - Create forensic report
  • GET /api/forensics/investigations/:id/reports - Get forensic reports

Tasks

  • GET /api/tasks - List financial tasks
  • POST /api/tasks - Create task
  • PATCH /api/tasks/:id - Update task
  • DELETE /api/tasks/:id - Delete task

Property CRUD (Phase 4)

  • POST /api/properties - Create property
  • PATCH /api/properties/:id - Update property
  • POST /api/properties/:id/units - Create unit
  • PATCH /api/properties/:id/units/:unitId - Update unit
  • POST /api/properties/:id/leases - Create lease
  • PATCH /api/properties/:id/leases/:leaseId - Update lease

Property Financial Data (Phase 4)

  • GET /api/properties/:id/financials - NOI, cap rate, cash-on-cash, occupancy
  • GET /api/properties/:id/rent-roll - Unit-level rent roll with payment status
  • GET /api/properties/:id/pnl?start=YYYY-MM-DD&end=YYYY-MM-DD - Property P&L by REI category

Property Valuation (Phase 4)

  • GET /api/properties/:id/valuation - Aggregated multi-source valuation estimates
  • POST /api/properties/:id/valuation/refresh - Fetch fresh estimates from all configured providers
  • GET /api/properties/:id/valuation/history - Historical valuation timeline

Data Import (Phase 4)

  • POST /api/import/turbotenant - Import TurboTenant CSV ledger (requires X-Account-ID header)
  • POST /api/import/wave-sync - Sync Wave transactions via OAuth

Environment Configuration

Required Variables

Database (required):

DATABASE_URL="postgresql://user:pass@host/dbname"

Application:

NODE_ENV="development"                              # or "production"
MODE="standalone"                                   # or "system" (multi-tenant)
PUBLIC_APP_BASE_URL="http://localhost:5000"        # Base URL for OAuth redirects

OAuth Security (required for production):

OAUTH_STATE_SECRET="random-secret-32chars"         # HMAC secret for OAuth state tokens

AI & OpenAI (optional for development, required for AI features):

OPENAI_API_KEY="sk-..."                            # Required for AI financial advice

Wave Accounting (Phase 3 - Real Integration):

WAVE_CLIENT_ID="..."                               # OAuth client ID from Wave Developer Portal
WAVE_CLIENT_SECRET="..."                           # OAuth client secret
WAVE_REDIRECT_URI="http://localhost:5000/api/integrations/wave/callback"  # Optional, defaults to PUBLIC_APP_BASE_URL/api/integrations/wave/callback

Stripe (Phase 3 - Real Integration):

STRIPE_SECRET_KEY="sk_test_..."                    # Stripe secret key (test or live)
STRIPE_PUBLISHABLE_KEY="pk_test_..."               # Stripe publishable key (optional, frontend)
STRIPE_WEBHOOK_SECRET="whsec_..."                  # Webhook signing secret for verification

Mercury Bank (Phase 3 - Real Integration via ChittyConnect):

CHITTYCONNECT_API_BASE="https://connect.chitty.cc"  # ChittyConnect backend URL
CHITTYCONNECT_API_TOKEN="..."                       # Service authentication token
CHITTY_CONNECT_URL="https://connect.chitty.cc"      # Frontend redirect URL (optional)

GitHub (optional):

GITHUB_TOKEN="ghp_..."                             # Required for GitHub integration

Property Valuation (Phase 4 - optional, each enables its provider):

ZILLOW_API_KEY="..."                               # RapidAPI key for Zillow estimates
REDFIN_API_KEY="..."                               # RapidAPI key for Redfin estimates
HOUSECANARY_API_KEY="..."                          # HouseCanary API key
ATTOM_API_KEY="..."                                # ATTOM Data Gateway key

Cook County Assessor (Socrata) is always available — no API key required.

Local Development Setup

  1. Provision Neon database:

  2. Initialize schema:

    npm run db:push
  3. Create demo user (manual step required):

    INSERT INTO users (username, password, display_name, email, role)
    VALUES ('demo', 'hashed_password', 'Demo User', 'demo@example.com', 'user');
  4. Start dev server:

    npm run dev
  5. Access application: http://localhost:5000

Testing

Manual Testing

  1. Start dev server: npm run dev
  2. Navigate to http://localhost:5000
  3. Application auto-logs in as "demo" user
  4. Test dashboard, integrations, AI chat
  5. Check browser console for errors
  6. Monitor server logs in terminal

Testing AI Features

Testing Integrations

  • Mercury Bank: Real integration via ChittyConnect (requires CHITTYCONNECT_API_BASE + token)
  • Wave Accounting: Real integration via OAuth 2.0 (requires WAVE_CLIENT_ID + WAVE_CLIENT_SECRET)
  • Stripe: Real integration (requires STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET)
  • DoorLoop, QuickBooks, Xero, Brex, Gusto: Not yet implemented — return empty data with console.warn
  • Unimplemented integration functions return {} or [], not fabricated data

Common Issues & Solutions

Database Connection Errors

Error: DATABASE_URL must be set

Solutions:

  1. Verify DATABASE_URL environment variable is set
  2. Test connection: psql $DATABASE_URL -c "SELECT 1"
  3. Check Neon dashboard for database status
  4. Ensure WebSocket support (ws package installed)

Port 5000 Already in Use

Error: EADDRINUSE: address already in use :::5000

Solution:

lsof -ti:5000 | xargs kill -9

Note: Port cannot be changed (hardcoded for Replit deployment).

OpenAI API Errors

Error: 401 Unauthorized or 429 Rate Limit

Solutions:

  1. Verify OPENAI_API_KEY is valid
  2. Check API key has credits at https://platform.openai.com/account/billing
  3. Implement rate limiting or caching for AI requests
  4. Handle errors gracefully (see server/lib/openai.ts:58-60)

Demo User Not Found

Error: Demo user not found from /api/session

Solution: Create demo user in database:

INSERT INTO users (username, password, display_name, email, role)
VALUES ('demo', 'any_value', 'Demo User', 'demo@example.com', 'user');

Type Checking Failures

Error: TypeScript errors from npm run check

Common causes:

  1. Schema changes not reflected in types (types are auto-generated from schema)
  2. Missing imports from @shared/schema
  3. Path alias not resolving (check tsconfig.json)

Solution: Verify schema exports match usage, run npm run check to see all errors.

ChittyOS Integration Points

ChittyID Integration (Planned)

  • Replace demo authentication with ChittyID
  • Link financial data to ChittyID for cross-platform identity
  • Each user should have associated ChittyID DID

ChittyConnect Integration (Planned)

  • Expose financial summary as MCP resource
  • Provide AI financial advice as MCP tool
  • Enable cross-service financial queries

ChittyChronicle Integration (Planned)

  • Log all financial transactions to audit trail
  • Track AI advice and outcomes
  • Compliance and forensic analysis

Development Best Practices

Database Changes

  1. Update shared/schema.ts (single source of truth)
  2. Run npm run db:push to apply changes
  3. Test with demo user in development
  4. Types are auto-generated from schema (no manual type updates needed)

API Design

  • Always use storage abstraction layer (never direct Drizzle queries in routes)
  • Validate inputs with Zod schemas from @shared/schema
  • Use consistent error handling pattern
  • Return JSON responses with appropriate status codes

Frontend Development

  • Use shadcn/ui components for consistency (@/components/ui/*)
  • Implement responsive design with Tailwind utilities
  • Use TanStack Query for all API calls (handles caching, loading, errors)
  • Optimize re-renders with proper React patterns (memo, useCallback)

Security Considerations

OAuth Security (Phase 3 implemented):

  • CSRF Protection: OAuth state tokens use HMAC-SHA256 signatures (server/lib/oauth-state-edge.ts)
  • Replay Prevention: State tokens expire after 10 minutes (timestamp validation)
  • Tampering Detection: State includes cryptographic signature verified server-side
  • Production Requirement: Set OAUTH_STATE_SECRET to random 32+ character string

Webhook Security:

  • Stripe: Webhook signatures verified using STRIPE_WEBHOOK_SECRET
  • Mercury: Service authentication via serviceAuth middleware
  • Idempotency: All webhook events deduplicated using KV with 7-day TTL

Integration Validation (server/lib/integration-validation.ts):

  • Validates required environment variables before allowing integration connections
  • Returns 503 Service Unavailable if integration not properly configured
  • Prevents cryptic errors from misconfigured services

General Security:

  • Critical: Replace demo authentication before production (ChittyID integration pending)
  • Never commit API keys (use environment variables)
  • Sanitize financial data in logs (mask account numbers)
  • Validate all user inputs on backend (Zod schemas)
  • Use HTTPS in production (HTTP allowed for local dev only)
  • Credential data stored as JSONB in database (encrypted at rest by Neon)

Known Limitations

  1. No Real Authentication: Service token auth only — no end-user auth (ChittyID integration pending)
  2. DoorLoop Still Mock: DoorLoop integration returns hardcoded data (real API integration pending)
  3. No Migrations: Uses drizzle-kit push (destructive) instead of proper migrations
  4. Forensic Tables Not in System Schema: Forensic tables use integer IDs from shared/schema.ts and may not exist in the production database yet
  5. Frontend Not Updated: React components need to support tenant switching
  6. Legacy Express Code: server/routes.ts, server/storage.ts, server/db.ts are legacy Express code kept for standalone dev reference

Future Enhancements

Phase 1: Complete Multi-Tenant Implementation (COMPLETED)

  • ✅ Database schemas created (system.schema.ts, standalone.schema.ts)
  • ✅ Seeding script for IT CAN BE LLC entities
  • ✅ Mode-aware database connection
  • ✅ Wrangler configuration template with KV/R2 provisioned
  • ✅ SystemStorage with tenant-aware Drizzle queries (server/storage/system.ts)
  • ✅ Service token auth middleware (server/middleware/auth.ts)
  • ✅ Tenant-scoped middleware (server/middleware/tenant.ts)
  • ⏳ Update frontend with tenant switcher

Phase 1.5: Hono Route Migration (COMPLETED)

  • ✅ All 17 route modules migrated from Express to Hono
  • ✅ Edge-compatible: Web Crypto API, Neon HTTP driver, no Node.js dependencies
  • ✅ Per-prefix middleware registration (avoids blocking public routes)
  • ✅ Deployed to Cloudflare Workers at finance.chitty.cc
  • ✅ 30/30 tests passing

Phase 2: ChittyConnect Integration (Partially Completed)

  • ✅ Mercury Bank via ChittyConnect backend (multi-account support)
  • ⏳ Register with ChittyRegistry
  • ⏳ Integrate with ChittyConnect MCP
  • ⏳ Log to ChittyChronicle
  • ⏳ Use ChittyAuth tokens

Phase 3: Real Third-Party Integrations (COMPLETED ✅)

  • Wave Accounting - OAuth 2.0 flow + GraphQL API (server/lib/wave-api.ts)
  • Stripe - Payment processing, checkout, webhooks (server/lib/stripe.ts)
  • Mercury Bank - Multi-account via ChittyConnect (static egress IP)
  • OAuth Security - CSRF-protected state tokens (server/lib/oauth-state.ts)
  • Integration Monitoring - Config validation endpoint (/api/integrations/status)
  • Webhook Infrastructure - Idempotent event processing (webhook_events table)
  • DoorLoop - Real property management API (currently mock)

Phase 4: Property Financial API (Partially Completed)

  • property_valuations table in system schema
  • ✅ Property/unit/lease CRUD in SystemStorage
  • ✅ Financial aggregation methods (NOI, cap rate, cash-on-cash, occupancy, rent roll, P&L)
  • ✅ Property mutation + financial endpoints (10 new routes in properties.ts)
  • ✅ Multi-source valuation providers (Zillow, Redfin, HouseCanary, ATTOM, County)
  • ✅ Confidence-weighted valuation aggregation
  • ✅ Valuation routes (current, refresh, history)
  • ✅ TurboTenant CSV import with deduplication
  • ✅ Wave sync import endpoint
  • ✅ Valuation Console (client/src/pages/ValuationConsole.tsx)
  • ✅ Deployed to Cloudflare Workers (35 tests passing)
  • ⏳ Generalize ValuationConsole to any property (currently hardcoded)
  • ⏳ Integrate ValuationConsole with dashboard
  • ⏳ Lease expiration notifications
  • ⏳ Frontend property management UI

Phase 5: ChittyOS Ecosystem Integration

  • ⏳ Replace demo auth with ChittyID
  • ⏳ Expose financial data as MCP resources
  • ⏳ Log to ChittyChronicle (audit trail)
  • ⏳ Issue ChittyCert certificates for secure connections

Phase 6: Advanced Features

  • Consolidated reporting across all entities
  • Inter-company allocation automation
  • Tax optimization and reporting
  • Advanced AI forecasting (beyond GPT-4o)
  • Mobile app (React Native)
  • Export/import (CSV, QFX, OFX)
  • Multi-currency support

Related Documentation

@claude
Copy link

claude bot commented Mar 12, 2026

ChittyFinance

A full‑stack TypeScript app combining an Express API and a React + Vite client. The server serves both API and client on port 5000.

Quick Start

  • Prerequisites:
    • Node.js 18+ (works on Node 20/22/25)
    • A Postgres database (Neon recommended)
  • Setup:
    1. cp .env.example .env
    2. Set DATABASE_URL in .env to your Postgres connection string
    3. npm install
  • Dev:
    • npm run dev (API + client on http://localhost:5000)
  • Build:
    • npm run build (client to dist/public, server to dist/system)
  • Run (production):
    • npm start (runs node dist/system/index.js)

Environment

Required:

  • DATABASE_URL — Neon/Postgres connection string

Optional (features degrade gracefully if unset):

  • OPENAI_API_KEY — Enable AI assistant via OpenAI
  • STRIPE_SECRET_KEY, MERCURY_API_KEY, WAVE_API_TOKEN — Integrations
  • GITHUB_TOKEN — Enable GitHub repository widgets (stars, PRs, issues)

API Smoke Tests

  • GET /api/session — Demo user
  • GET /api/financial-summary
  • GET /api/tasks, POST /api/tasks
  • POST /api/ai-assistant/query — Body: { "query": "..." }

Deploy

Cloudflare Workers config is in deploy/system-wrangler.toml.

  • Routes:

    • finance.chitty.cc/* → ChittyFinance (this app)
    • get.chitty.cc/* → Registration service (external; not routed here)
    • connect.chitty.cc/* → ChittyConnect (external; this app links to it)
  • Build: npm run build (uses build:system)

  • Deploy: npm run deploy

    • Requires Cloudflare auth (wrangler login) and secrets set:
      • wrangler secret put DATABASE_URL
      • wrangler secret put OPENAI_API_KEY (optional)
      • wrangler secret put CHITTYCONNECT_API_BASE (e.g. https://connect.chitty.cc/api)
      • wrangler secret put CHITTYCONNECT_API_TOKEN (server-to-server auth to ChittyConnect)

ChittyConnect (Mercury static IP + multi-account)

Server uses ChittyConnect when configured to fetch Mercury data via static egress and supports selecting multiple bank accounts.

  • Env:
    • CHITTYCONNECT_API_BASE, CHITTYCONNECT_API_TOKEN, CHITTY_CONNECT_URL
    • CHITTYCONNECT_KEEPALIVE_MINUTES (default 50) — background refresh cadence
  • Endpoints:
    • GET /api/mercury/accounts — list accounts via ChittyConnect
    • POST /api/mercury/select-accounts — persist selected account IDs
    • GET /connect — redirects to CHITTY_CONNECT_URL (default https://connect.chitty.cc)
  • UI:
    • Settings → Integrations → Mercury
      • “Connect” opens ChittyConnect
      • “Manage accounts” scrolls to account selector
    • Tokens are proactively refreshed in the background when configured

Note: The server bundle targets Node ESM. The Workers config enables node_compat, but some Node/Express patterns may not be fully supported on Workers without additional adaptation. For traditional Node deploys, run npm start on a VM or container with Postgres access.

@claude
Copy link

claude bot commented Mar 12, 2026

{
"name": "chittyfinance",
"version": "1.0.0",
"type": "module",
"license": "MIT",
"scripts": {
"dev": "NODE_ENV=development tsx server/index.ts",
"build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist",
"start": "NODE_ENV=production node dist/index.js",
"check": "tsc",
"db:push": "drizzle-kit push",
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@jridgewell/trace-mapping": "^0.3.25",
"@modelcontextprotocol/sdk": "^1.27.1",
"@neondatabase/serverless": "^0.10.4",
"@paypal/paypal-server-sdk": "^1.0.0",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-aspect-ratio": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-context-menu": "^2.2.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-hover-card": "^1.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.2",
"@radix-ui/react-navigation-menu": "^1.2.1",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.1",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
"@stripe/react-stripe-js": "^3.6.0",
"@stripe/stripe-js": "^7.1.0",
"@tanstack/react-query": "^5.60.5",
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0",
"bcryptjs": "^2.4.3",
"better-sqlite3": "^11.0.0",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"connect-pg-simple": "^10.0.0",
"csv-parse": "^6.1.0",
"date-fns": "^3.6.0",
"dotenv": "^17.2.3",
"drizzle-orm": "^0.39.1",
"drizzle-zod": "^0.7.0",
"embla-carousel-react": "^8.3.0",
"express": "^4.21.2",
"express-session": "^1.18.1",
"framer-motion": "^11.18.2",
"hono": "^4.12.2",
"input-otp": "^1.2.4",
"jose": "^6.1.3",
"jsonwebtoken": "^9.0.3",
"lucide-react": "^0.453.0",
"memoizee": "^0.4.17",
"memorystore": "^1.6.7",
"openai": "^4.95.0",
"openid-client": "^6.8.1",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.1",
"react-icons": "^5.4.0",
"react-resizable-panels": "^2.1.4",
"recharts": "^2.13.0",
"stripe": "^18.0.0",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.0",
"wouter": "^3.3.5",
"ws": "^8.18.0",
"zod": "^3.23.8",
"zod-validation-error": "^3.4.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20260302.0",
"@tailwindcss/typography": "^0.5.15",
"@types/better-sqlite3": "^7.6.13",
"@types/connect-pg-simple": "^7.0.3",
"@types/express": "^5.0.6",
"@types/express-session": "^1.18.0",
"@types/memoizee": "^0.4.12",
"@types/node": "^20.16.11",
"@types/passport": "^1.0.16",
"@types/passport-local": "^1.0.38",
"@types/pg": "^8.16.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/ws": "^8.5.13",
"@vitejs/plugin-react": "^4.3.2",
"autoprefixer": "^10.4.20",
"drizzle-kit": "^0.31.9",
"esbuild": "^0.25.0",
"nanoid": "^5.1.6",
"pg": "^8.16.3",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"tsx": "^4.19.1",
"typescript": "5.6.3",
"vite": "^7.2.7",
"vitest": "^4.0.18"
},
"optionalDependencies": {
"bufferutil": "^4.0.8"
},
"pnpm": {
"onlyBuiltDependencies": [
"better-sqlite3",
"bufferutil",
"es5-ext",
"esbuild"
],
"overrides": {
"express-rate-limit": ">=8.2.2",
"@hono/node-server": ">=1.19.10",
"axios": ">=1.13.5",
"rollup": ">=4.59.0"
}
}
}

@claude
Copy link

claude bot commented Mar 12, 2026

testbody

@claude
Copy link

claude bot commented Mar 12, 2026

line1
line2
line3

@claude
Copy link

claude bot commented Mar 12, 2026

#!/usr/bin/env node
// Mode detection script for ChittyFinance
// Determines whether to run in standalone or system mode

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

function detectMode() {
// Check if MODE is explicitly set
if (process.env.MODE) {
console.log(✅ Mode explicitly set: ${process.env.MODE});
return process.env.MODE;
}

// Check for system mode indicators
const hasSystemDatabase = process.env.DATABASE_URL && process.env.DATABASE_URL.includes('neon');
const hasChittyIdToken = !!process.env.CHITTY_ID_SERVICE_TOKEN;
const hasSystemConfig = fs.existsSync(path.join(__dirname, '../deploy/system-wrangler.toml'));

if (hasSystemDatabase || hasChittyIdToken || hasSystemConfig) {
console.log('🔧 Auto-detected: SYSTEM mode (ChittyOS integration)');
return 'system';
}

// Default to standalone
console.log('🔧 Auto-detected: STANDALONE mode (local development)');
return 'standalone';
}

const mode = detectMode();

// Write detected mode to environment
console.log(\n📝 Running in ${mode.toUpperCase()} mode);
console.log( To override, set MODE=${mode === 'system' ? 'standalone' : 'system'}\n);

// Set npm script to run
process.env.npm_lifecycle_event = dev:${mode};

export { detectMode };

@claude
Copy link

claude bot commented Mar 12, 2026

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Canonical URI: chittycanon://core/services/chittyfinance | Tier 3 (Service Layer) | finance.chitty.cc

Project Overview

ChittyFinance is a full-stack financial management platform for the ChittyOS ecosystem. It provides intelligent financial tracking, AI-powered advice, recurring charge optimization, and integrations with Mercury Bank, Wave Accounting, and Stripe payments.

Architecture: Hono on Cloudflare Workers (production) + legacy Express (local dev). PostgreSQL (Neon) with Drizzle ORM. React frontend.

Essential Commands

Development

npm install              # Install dependencies
npm run dev              # Auto-detect mode and start dev server on port 5000
npm run dev:standalone   # Start in standalone mode (local development)
npm run dev:system       # Start in system mode (ChittyOS integration)
npm run check            # TypeScript type checking
npm run mode:detect      # Detect and display current mode
npm run mode:setup       # Setup mode configuration (script not yet implemented)

Build & Deployment

npm run build            # Build system mode (default for production)
npm run build:standalone # Build standalone mode (outputs to dist/standalone)
npm run build:system     # Build system mode (outputs to dist/system)
npm run build:both       # Build both modes
npm run start            # Run standalone production build locally
npm run deploy           # Deploy to Cloudflare Workers (system mode)
npm run deploy:staging   # Deploy to staging environment
npm run deploy:production # Deploy to production environment

Database Operations

npm run db:push              # Push schema changes (uses current drizzle.config.ts)
npm run db:push:system       # Push system schema to PostgreSQL
npm run db:push:standalone   # Push standalone schema to SQLite
npm run db:seed              # Seed IT CAN BE LLC entity structure

First-Time Setup (System Mode):

MODE=system npm run db:push:system   # Create tables
npm run db:seed                      # Create tenants and users

First-Time Setup (Standalone Mode):

npm run db:push:standalone   # Create SQLite tables
# No seeding needed - single user mode

Critical:

  • Port 5001 is used in server/index.ts for local Express development
  • Server uses reusePort: true for multiple process support on the same port

Architecture

Dual-Mode Operation

ChittyFinance supports two operational modes (controlled by MODE environment variable):

Standalone Mode (default for local development):

  • SQLite database for quick local development
  • Single-tenant (no multi-tenancy overhead)
  • Simplified schema in database/standalone.schema.ts
  • Build output: dist/standalone/
  • Database file: ./chittyfinance.db
  • Run: npm run dev or npm run dev:standalone

System Mode (production - multi-tenant):

  • PostgreSQL (Neon) with full multi-tenancy
  • Supports IT CAN BE LLC entity structure
  • Complete schema in database/system.schema.ts
  • Build output: dist/system/
  • Cloudflare Workers deployment
  • Run: npm run dev:system
  • Deploy: npm run deploy or npm run deploy:production

Mode Detection:

  • Default: npm run dev runs in standalone mode
  • Explicitly set: MODE=system npm run dev
  • Auto-detection script: npm run mode:detect

Multi-Tenant Architecture (System Mode)

IT CAN BE LLC Entity Structure:

IT CAN BE LLC (holding)
├── JEAN ARLENE VENTURING LLC (personal, 85% owner)
├── ARIBIA LLC (series, 100% owned)
│   ├── ARIBIA LLC - MGMT (management)
│   │   ├── Chicago Furnished Condos (consumer brand)
│   │   └── Chitty Services (vendor/tech services)
│   ├── ARIBIA LLC - CITY STUDIO (property)
│   │   └── 550 W Surf St C211, Chicago IL
│   └── ARIBIA LLC - APT ARLENE (property)
│       └── 4343 N Clarendon #1610, Chicago IL
└── ChittyCorp LLC (holding, pending formation)

Tenant Types:

  • holding - Holding companies (IT CAN BE LLC, ChittyCorp LLC)
  • series - Series LLCs (ARIBIA LLC)
  • property - Property holding entities (City Studio, Apt Arlene)
  • management - Management companies (ARIBIA LLC - MGMT)
  • personal - Personal entities (JEAN ARLENE VENTURING LLC)

Key Features:

  • Each tenant has isolated financial data
  • Inter-company transaction tracking
  • Property-specific rent roll and lease management
  • User access control per tenant (roles: owner, admin, manager, viewer)
  • Consolidated reporting across entities

Tech Stack

  • Frontend: React 18 with TypeScript, Wouter (routing), shadcn/ui (Radix UI components)
  • Backend: Hono (Cloudflare Workers, production) / Express.js (legacy, local dev)
  • Database: Neon PostgreSQL with Drizzle ORM
  • Build: Vite (frontend), esbuild (backend)
  • Styling: Tailwind CSS with tailwindcss-animate
  • State: TanStack React Query for server state
  • Payments: Stripe integration
  • AI: OpenAI GPT-4o for financial advice

Project Structure

chittyfinance/
├── client/                 # React frontend (Vite root)
│   └── src/
│       ├── pages/         # Page components (Dashboard, Settings, Landing)
│       ├── components/    # Reusable UI components (shadcn/ui)
│       ├── hooks/         # Custom React hooks
│       └── lib/           # Client utilities
├── server/                # Hono backend (Cloudflare Workers)
│   ├── app.ts            # Hono app factory with middleware wiring
│   ├── env.ts            # HonoEnv type (Bindings + Variables)
│   ├── worker.ts         # Cloudflare Workers entry point
│   ├── index.ts          # Legacy Express entry (standalone dev)
│   ├── routes.ts         # Legacy Express routes (reference only)
│   ├── routes/            # Hono route modules (19 files)
│   │   ├── health.ts     # /health, /api/v1/status
│   │   ├── docs.ts       # /api/v1/documentation (OpenAPI spec)
│   │   ├── accounts.ts   # /api/accounts
│   │   ├── summary.ts    # /api/summary
│   │   ├── tenants.ts    # /api/tenants
│   │   ├── properties.ts # /api/properties (CRUD + financials + rent roll + P&L)
│   │   ├── transactions.ts # /api/transactions
│   │   ├── integrations.ts # /api/integrations
│   │   ├── tasks.ts      # /api/tasks
│   │   ├── ai.ts         # /api/ai-messages
│   │   ├── mercury.ts    # /api/mercury (via ChittyConnect)
│   │   ├── github.ts     # /api/github
│   │   ├── stripe.ts     # /api/integrations/stripe
│   │   ├── wave.ts       # /api/integrations/wave (OAuth)
│   │   ├── charges.ts    # /api/charges (recurring)
│   │   ├── forensics.ts  # /api/forensics (21 endpoints)
│   │   ├── valuation.ts  # /api/properties/:id/valuation (multi-source AVM)
│   │   ├── import.ts     # /api/import (TurboTenant CSV + Wave sync)
│   │   └── webhooks.ts   # Stripe/Mercury webhooks
│   ├── middleware/        # auth, tenant, error middleware
│   ├── storage/           # SystemStorage (Drizzle queries)
│   ├── db/                # connection.ts (Neon HTTP), schema.ts
│   └── lib/               # Server utilities
│       ├── wave-api.ts           # Wave Accounting GraphQL client
│       ├── oauth-state-edge.ts   # Edge-compatible HMAC OAuth state
│       ├── chargeAutomation.ts   # Recurring charge analysis (stubs)
│       ├── forensicService.ts    # Forensic algorithms (legacy)
│       └── valuation/            # Property valuation providers
│           ├── types.ts          # ValuationProvider interface, AggregatedValuation
│           ├── zillow.ts         # Zillow via RapidAPI
│           ├── redfin.ts         # Redfin via RapidAPI
│           ├── housecanary.ts    # HouseCanary REST API
│           ├── attom.ts          # ATTOM Data Gateway
│           ├── county.ts         # Cook County Assessor (Socrata)
│           └── index.ts          # Provider registry + confidence-weighted aggregation
├── database/              # Schema definitions
│   ├── system.schema.ts   # Multi-tenant PostgreSQL (UUID-based)
│   └── standalone.schema.ts # Single-tenant SQLite
├── shared/                # Shared types and schemas
│   └── schema.ts         # Legacy schema with forensic tables (integer-ID)
└── deploy/
    └── system-wrangler.toml # Cloudflare Workers config

Database Architecture

System Mode Schema (Multi-Tenant PostgreSQL)

Location: database/system.schema.ts

Core Tables:

  • tenants - Legal entities (LLCs, properties, management companies)
  • users - User accounts with email/password + optional ChittyID
  • tenant_users - User access to tenants with role-based permissions
  • accounts - Bank accounts, credit cards (tenant-scoped)
  • transactions - Financial transactions with decimal precision
  • intercompany_transactions - Transfers between tenants

Property Management Tables:

  • properties - Real estate assets
  • units - Rental units (if property has multiple units)
  • leases - Tenant leases with rent and dates
  • property_valuations - Cached AVM estimates from external providers (Zillow, Redfin, HouseCanary, ATTOM, County)

Supporting Tables:

  • integrations - Mercury/Wave/Stripe API connections
  • tasks - Financial tasks
  • ai_messages - AI conversation history

Key Characteristics:

  • UUIDs for primary keys (better for distributed systems)
  • Decimal precision for all monetary amounts (12,2)
  • Full multi-tenancy with tenant isolation
  • Hierarchical tenants (parent-child relationships)
  • Indexed for performance (tenant_id, date, etc.)

Standalone Mode Schema (Single-Tenant SQLite)

Location: database/standalone.schema.ts

Simplified Tables:

  • users, accounts, transactions, properties, tasks, integrations

Key Characteristics:

  • Text IDs (simpler for SQLite)
  • Real (float) for amounts (acceptable for dev)
  • No multi-tenancy (single user)
  • Faster for local development

Database Connection

Mode-Aware Connection (server/db.ts):

The database connection automatically switches based on MODE environment variable:

System Mode (MODE=system):

// PostgreSQL (Neon) with multi-tenant schema
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzleNeon({ client: pool, schema: systemSchema });

Standalone Mode (MODE=standalone):

// SQLite for local development
const sqlite = new Database('./chittyfinance.db');
const db = drizzleBetterSqlite(sqlite, { schema: standaloneSchema });

Environment Variables:

  • System mode: DATABASE_URL (Neon PostgreSQL connection string)
  • Standalone mode: SQLITE_FILE (optional, defaults to ./chittyfinance.db)

Database Seeding (System Mode)

Seed Script: database/seeds/it-can-be-llc.ts

Creates the complete IT CAN BE LLC entity structure:

  1. IT CAN BE LLC (parent holding company)
  2. JEAN ARLENE VENTURING LLC (85% owner, personal income funnel)
  3. ARIBIA LLC (series parent)
  4. ARIBIA LLC - MGMT (management company with two brands)
  5. ARIBIA LLC - CITY STUDIO (property entity)
  6. ARIBIA LLC - APT ARLENE (property entity)
  7. ChittyCorp LLC (pending formation)

Also creates:

  • Property records for City Studio and Apt Arlene
  • User accounts for Nicholas Bianchi and Sharon E Jones
  • Access permissions for each user to appropriate tenants

Run seeding:

npm run db:seed

Note: Only run after pushing the system schema (npm run db:push:system)

Storage Abstraction Layer

Critical Pattern: All database access goes through server/storage.ts. Never write direct Drizzle queries in routes.

⚠️ Important: The current server/storage.ts uses the old shared/schema.ts and needs to be updated to use database/system.schema.ts with tenant-aware queries.

Interface (server/storage.ts:12-42):

export interface IStorage {
  // User operations
  getUser(id: number): Promise<User | undefined>;
  getUserByUsername(username: string): Promise<User | undefined>;
  createUser(user: InsertUser): Promise<User>;

  // Integration operations
  getIntegrations(userId: number): Promise<Integration[]>;
  createIntegration(integration: InsertIntegration): Promise<Integration>;
  updateIntegration(id: number, integration: Partial<Integration>): Promise<Integration | undefined>;

  // Financial summary operations
  getFinancialSummary(userId: number): Promise<FinancialSummary | undefined>;
  createFinancialSummary(summary: InsertFinancialSummary): Promise<FinancialSummary>;

  // Transaction, Task, and AI Message operations...
}

Usage in routes (server/routes.ts):

import { storage } from "./storage";

const user = await storage.getUserByUsername("demo");
const summary = await storage.getFinancialSummary(user.id);

Key Features

1. Demo Authentication

Current implementation: Auto-login as "demo" user (no real authentication).

Pattern (server/routes.ts:21-33):

api.get("/session", async (req: Request, res: Response) => {
  const user = await storage.getUserByUsername("demo");
  // Returns demo user for all requests
});

Note: All API routes assume demo user. Real authentication needs to be implemented for production.

2. Financial Dashboard

  • Real-time financial summary: Cash on hand, revenue, expenses, outstanding invoices
  • Data source: Cached in financialSummaries table, fetched from integrations
  • Demo data: Hardcoded values if no summary exists (server/routes.ts:51-55)

3. AI Financial Advice

Location: server/lib/openai.ts

Model: GPT-4o (hardcoded, comment at line 3 warns against changing)

Functions:

  • getFinancialAdvice() - Conversational financial advice based on financial data
  • generateCostReductionPlan() - AI-generated cost reduction strategies

API endpoint: POST /api/ai/message sends user query to OpenAI with financial context

4. Recurring Charge Automation

Location: server/lib/chargeAutomation.ts

Capabilities:

  • Identify recurring charges from integrated services
  • Generate optimization recommendations (cancel, downgrade, consolidate, negotiate)
  • Calculate potential savings

API endpoints:

  • GET /api/recurring-charges - List all recurring charges
  • GET /api/recurring-charges/:id/optimizations - Get optimization suggestions
  • POST /api/recurring-charges/:id/manage - Execute management action

5. Third-Party Integrations (Phase 3 - COMPLETED)

Mercury Bank (server/lib/financialServices.ts:22-51, via ChittyConnect):

  • Real integration through ChittyConnect backend
  • Multi-account support with account selection
  • Static egress IP for bank compliance
  • Fetches balances and transactions
  • Falls back to minimal data in standalone mode
  • Routes: /api/mercury/accounts, /api/mercury/select-accounts
  • Configuration: Requires CHITTYCONNECT_API_BASE + CHITTYCONNECT_API_TOKEN

Wave Accounting (server/lib/wave-api.ts + server/lib/financialServices.ts:54-116):

  • Real integration via OAuth 2.0 + GraphQL API
  • Complete OAuth flow with CSRF protection (HMAC-signed state tokens)
  • Fetches invoices, expenses, and financial summaries
  • Automatic token refresh support
  • Routes: /api/integrations/wave/authorize, /callback, /refresh
  • Requirements: Wave Pro subscription, WAVE_CLIENT_ID, WAVE_CLIENT_SECRET
  • Security: Uses server/lib/oauth-state.ts for secure state token generation/validation

Stripe (server/lib/stripe.ts):

  • Real integration for payment processing
  • Customer management with tenant metadata
  • Checkout session creation (ad-hoc payments)
  • Webhook verification and idempotent event processing
  • Routes: /api/integrations/stripe/connect, /checkout, /webhook
  • Configuration: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
  • Events stored in webhook_events table (see shared/finance.schema.ts)

DoorLoop (server/lib/financialServices.ts:119-147):

  • Mock data (property management)
  • Returns hardcoded rent roll and maintenance data
  • Real API integration pending

GitHub (server/lib/github.ts):

  • Real GitHub API integration (not mock)
  • Fetches repositories, commits, PRs, issues
  • Used for project cost attribution

Integration Status Monitoring:

  • New endpoint: GET /api/integrations/status (server/routes.ts:122-126)
  • Validates which integrations are properly configured
  • Uses server/lib/integration-validation.ts to check environment variables
  • Returns configuration status for wave, stripe, mercury, openai

Utilities

check_system_operations_duplicates.js

A utility script for analyzing and detecting duplicate operations in system mode. Located at the project root. This script helps maintain code quality when working with ChittyOS integration.

Development Workflows

Adding a New Feature

  1. Update database schema in shared/schema.ts:

    export const newTable = pgTable("new_table", {
      id: serial("id").primaryKey(),
      userId: integer("user_id").notNull().references(() => users.id),
      // ... fields
    });
  2. Run migration: npm run db:push

  3. Add storage methods in server/storage.ts:

    async getNewData(userId: number): Promise<NewData[]> {
      return await db.select().from(newTable).where(eq(newTable.userId, userId));
    }
  4. Create API routes in server/routes.ts:

    api.get("/new-data", async (req: Request, res: Response) => {
      const user = await storage.getUserByUsername("demo");
      const data = await storage.getNewData(user.id);
      res.json(data);
    });
  5. Build frontend in client/src/pages/:

    • Use TanStack Query for data fetching
    • Import shadcn/ui components from @/components/ui/

Working with AI Features

OpenAI Configuration (server/lib/openai.ts):

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const openai = OPENAI_API_KEY ? new OpenAI({ apiKey: OPENAI_API_KEY }) : null;

Best practices:

  • Model is GPT-4o (do not change without user request per comment)
  • Max tokens: 500 for financial advice
  • Include financial context in system prompt
  • Handle API errors gracefully (rate limits, invalid keys)
  • When OPENAI_API_KEY is not set, AI functions return rule-based fallback advice
  • OpenAI client is null when unconfigured — functions guard with if (!openai) early returns

Path Aliases

Configured in tsconfig.json:18-21:

{
  "@/*": ["./client/src/*"],
  "@shared/*": ["./shared/*"]
}

Additional alias in vite.config.ts:25:

"@assets": path.resolve(import.meta.dirname, "attached_assets")

Usage:

import { Button } from "@/components/ui/button";
import { users } from "@shared/schema";
import logo from "@assets/logo.png";

API Endpoints

Authentication

  • GET /api/session - Get current demo user (no auth required)

Financial Data

  • GET /api/financial-summary - Get cached financial summary
  • GET /api/transactions - List transactions with optional filters

Integrations

  • GET /api/integrations - List configured integrations
  • GET /api/integrations/status - Check which integrations are properly configured
  • POST /api/integrations - Add new integration
  • PATCH /api/integrations/:id - Update integration credentials
  • GET /api/integrations/events - List webhook events with optional source filter
  • POST /api/admin/events/replay - Replay webhook events to ChittyOS services

Wave Accounting

  • GET /api/integrations/wave/authorize - Start OAuth flow (returns authorization URL)
  • GET /api/integrations/wave/callback - OAuth callback handler (redirects to /connections)
  • POST /api/integrations/wave/refresh - Refresh expired access token

Stripe

  • POST /api/integrations/stripe/connect - Create/fetch Stripe customer
  • POST /api/integrations/stripe/checkout - Create ad-hoc payment session
  • POST /api/integrations/stripe/webhook - Stripe webhook endpoint (signature verified)

Mercury Bank

  • GET /api/mercury/accounts - List available Mercury accounts via ChittyConnect
  • POST /api/mercury/select-accounts - Select which accounts to sync
  • POST /api/integrations/mercury/webhook - Mercury webhook endpoint (authenticated)

Recurring Charges

  • GET /api/charges/recurring - List recurring charges from integrations
  • GET /api/charges/optimizations - Get optimization recommendations
  • POST /api/charges/manage - Cancel/modify a recurring charge

AI Services

  • POST /api/ai/advice - Get initial AI financial advice
  • POST /api/ai/cost-reduction - Generate cost reduction plan
  • POST /api/ai/message - Conversational AI advice (includes previous context)

GitHub Integration

  • GET /api/github/repositories - List user repositories
  • GET /api/github/repositories/:owner/:repo/commits - Get repository commits
  • GET /api/github/repositories/:owner/:repo/pulls - Get pull requests
  • GET /api/github/repositories/:owner/:repo/issues - Get issues

Forensic Accounting

  • GET /api/forensics/investigations - List investigations
  • GET /api/forensics/investigations/:id - Get investigation
  • POST /api/forensics/investigations - Create investigation
  • PATCH /api/forensics/investigations/:id/status - Update status
  • POST /api/forensics/investigations/:id/evidence - Add evidence
  • GET /api/forensics/investigations/:id/evidence - List evidence
  • POST /api/forensics/evidence/:id/custody - Update chain of custody
  • POST /api/forensics/investigations/:id/analyze - Comprehensive analysis
  • POST /api/forensics/investigations/:id/analyze/duplicates - Duplicate payment detection
  • POST /api/forensics/investigations/:id/analyze/timing - Unusual timing detection
  • POST /api/forensics/investigations/:id/analyze/round-dollars - Round dollar anomalies
  • POST /api/forensics/investigations/:id/analyze/benfords-law - Benford's Law analysis
  • POST /api/forensics/investigations/:id/trace-funds - Trace flow of funds
  • POST /api/forensics/investigations/:id/flow-of-funds - Create flow of funds record
  • GET /api/forensics/investigations/:id/flow-of-funds - Get flow of funds
  • POST /api/forensics/investigations/:id/calculate-damages/direct-loss - Direct loss calculation
  • POST /api/forensics/investigations/:id/calculate-damages/net-worth - Net worth method
  • POST /api/forensics/calculate-interest - Pre-judgment interest calculation
  • POST /api/forensics/investigations/:id/generate-summary - Executive summary
  • POST /api/forensics/investigations/:id/reports - Create forensic report
  • GET /api/forensics/investigations/:id/reports - Get forensic reports

Tasks

  • GET /api/tasks - List financial tasks
  • POST /api/tasks - Create task
  • PATCH /api/tasks/:id - Update task
  • DELETE /api/tasks/:id - Delete task

Property CRUD (Phase 4)

  • POST /api/properties - Create property
  • PATCH /api/properties/:id - Update property
  • POST /api/properties/:id/units - Create unit
  • PATCH /api/properties/:id/units/:unitId - Update unit
  • POST /api/properties/:id/leases - Create lease
  • PATCH /api/properties/:id/leases/:leaseId - Update lease

Property Financial Data (Phase 4)

  • GET /api/properties/:id/financials - NOI, cap rate, cash-on-cash, occupancy
  • GET /api/properties/:id/rent-roll - Unit-level rent roll with payment status
  • GET /api/properties/:id/pnl?start=YYYY-MM-DD&end=YYYY-MM-DD - Property P&L by REI category

Property Valuation (Phase 4)

  • GET /api/properties/:id/valuation - Aggregated multi-source valuation estimates
  • POST /api/properties/:id/valuation/refresh - Fetch fresh estimates from all configured providers
  • GET /api/properties/:id/valuation/history - Historical valuation timeline

Data Import (Phase 4)

  • POST /api/import/turbotenant - Import TurboTenant CSV ledger (requires X-Account-ID header)
  • POST /api/import/wave-sync - Sync Wave transactions via OAuth

Environment Configuration

Required Variables

Database (required):

DATABASE_URL="postgresql://user:pass@host/dbname"

Application:

NODE_ENV="development"                              # or "production"
MODE="standalone"                                   # or "system" (multi-tenant)
PUBLIC_APP_BASE_URL="http://localhost:5000"        # Base URL for OAuth redirects

OAuth Security (required for production):

OAUTH_STATE_SECRET="random-secret-32chars"         # HMAC secret for OAuth state tokens

AI & OpenAI (optional for development, required for AI features):

OPENAI_API_KEY="sk-..."                            # Required for AI financial advice

Wave Accounting (Phase 3 - Real Integration):

WAVE_CLIENT_ID="..."                               # OAuth client ID from Wave Developer Portal
WAVE_CLIENT_SECRET="..."                           # OAuth client secret
WAVE_REDIRECT_URI="http://localhost:5000/api/integrations/wave/callback"  # Optional, defaults to PUBLIC_APP_BASE_URL/api/integrations/wave/callback

Stripe (Phase 3 - Real Integration):

STRIPE_SECRET_KEY="sk_test_..."                    # Stripe secret key (test or live)
STRIPE_PUBLISHABLE_KEY="pk_test_..."               # Stripe publishable key (optional, frontend)
STRIPE_WEBHOOK_SECRET="whsec_..."                  # Webhook signing secret for verification

Mercury Bank (Phase 3 - Real Integration via ChittyConnect):

CHITTYCONNECT_API_BASE="https://connect.chitty.cc"  # ChittyConnect backend URL
CHITTYCONNECT_API_TOKEN="..."                       # Service authentication token
CHITTY_CONNECT_URL="https://connect.chitty.cc"      # Frontend redirect URL (optional)

GitHub (optional):

GITHUB_TOKEN="ghp_..."                             # Required for GitHub integration

Property Valuation (Phase 4 - optional, each enables its provider):

ZILLOW_API_KEY="..."                               # RapidAPI key for Zillow estimates
REDFIN_API_KEY="..."                               # RapidAPI key for Redfin estimates
HOUSECANARY_API_KEY="..."                          # HouseCanary API key
ATTOM_API_KEY="..."                                # ATTOM Data Gateway key

Cook County Assessor (Socrata) is always available — no API key required.

Local Development Setup

  1. Provision Neon database:

  2. Initialize schema:

    npm run db:push
  3. Create demo user (manual step required):

    INSERT INTO users (username, password, display_name, email, role)
    VALUES ('demo', 'hashed_password', 'Demo User', 'demo@example.com', 'user');
  4. Start dev server:

    npm run dev
  5. Access application: http://localhost:5000

Testing

Manual Testing

  1. Start dev server: npm run dev
  2. Navigate to http://localhost:5000
  3. Application auto-logs in as "demo" user
  4. Test dashboard, integrations, AI chat
  5. Check browser console for errors
  6. Monitor server logs in terminal

Testing AI Features

Testing Integrations

  • Mercury Bank: Real integration via ChittyConnect (requires CHITTYCONNECT_API_BASE + token)
  • Wave Accounting: Real integration via OAuth 2.0 (requires WAVE_CLIENT_ID + WAVE_CLIENT_SECRET)
  • Stripe: Real integration (requires STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET)
  • DoorLoop, QuickBooks, Xero, Brex, Gusto: Not yet implemented — return empty data with console.warn
  • Unimplemented integration functions return {} or [], not fabricated data

Common Issues & Solutions

Database Connection Errors

Error: DATABASE_URL must be set

Solutions:

  1. Verify DATABASE_URL environment variable is set
  2. Test connection: psql $DATABASE_URL -c "SELECT 1"
  3. Check Neon dashboard for database status
  4. Ensure WebSocket support (ws package installed)

Port 5000 Already in Use

Error: EADDRINUSE: address already in use :::5000

Solution:

lsof -ti:5000 | xargs kill -9

Note: Port cannot be changed (hardcoded for Replit deployment).

OpenAI API Errors

Error: 401 Unauthorized or 429 Rate Limit

Solutions:

  1. Verify OPENAI_API_KEY is valid
  2. Check API key has credits at https://platform.openai.com/account/billing
  3. Implement rate limiting or caching for AI requests
  4. Handle errors gracefully (see server/lib/openai.ts:58-60)

Demo User Not Found

Error: Demo user not found from /api/session

Solution: Create demo user in database:

INSERT INTO users (username, password, display_name, email, role)
VALUES ('demo', 'any_value', 'Demo User', 'demo@example.com', 'user');

Type Checking Failures

Error: TypeScript errors from npm run check

Common causes:

  1. Schema changes not reflected in types (types are auto-generated from schema)
  2. Missing imports from @shared/schema
  3. Path alias not resolving (check tsconfig.json)

Solution: Verify schema exports match usage, run npm run check to see all errors.

ChittyOS Integration Points

ChittyID Integration (Planned)

  • Replace demo authentication with ChittyID
  • Link financial data to ChittyID for cross-platform identity
  • Each user should have associated ChittyID DID

ChittyConnect Integration (Planned)

  • Expose financial summary as MCP resource
  • Provide AI financial advice as MCP tool
  • Enable cross-service financial queries

ChittyChronicle Integration (Planned)

  • Log all financial transactions to audit trail
  • Track AI advice and outcomes
  • Compliance and forensic analysis

Development Best Practices

Database Changes

  1. Update shared/schema.ts (single source of truth)
  2. Run npm run db:push to apply changes
  3. Test with demo user in development
  4. Types are auto-generated from schema (no manual type updates needed)

API Design

  • Always use storage abstraction layer (never direct Drizzle queries in routes)
  • Validate inputs with Zod schemas from @shared/schema
  • Use consistent error handling pattern
  • Return JSON responses with appropriate status codes

Frontend Development

  • Use shadcn/ui components for consistency (@/components/ui/*)
  • Implement responsive design with Tailwind utilities
  • Use TanStack Query for all API calls (handles caching, loading, errors)
  • Optimize re-renders with proper React patterns (memo, useCallback)

Security Considerations

OAuth Security (Phase 3 implemented):

  • CSRF Protection: OAuth state tokens use HMAC-SHA256 signatures (server/lib/oauth-state-edge.ts)
  • Replay Prevention: State tokens expire after 10 minutes (timestamp validation)
  • Tampering Detection: State includes cryptographic signature verified server-side
  • Production Requirement: Set OAUTH_STATE_SECRET to random 32+ character string

Webhook Security:

  • Stripe: Webhook signatures verified using STRIPE_WEBHOOK_SECRET
  • Mercury: Service authentication via serviceAuth middleware
  • Idempotency: All webhook events deduplicated using KV with 7-day TTL

Integration Validation (server/lib/integration-validation.ts):

  • Validates required environment variables before allowing integration connections
  • Returns 503 Service Unavailable if integration not properly configured
  • Prevents cryptic errors from misconfigured services

General Security:

  • Critical: Replace demo authentication before production (ChittyID integration pending)
  • Never commit API keys (use environment variables)
  • Sanitize financial data in logs (mask account numbers)
  • Validate all user inputs on backend (Zod schemas)
  • Use HTTPS in production (HTTP allowed for local dev only)
  • Credential data stored as JSONB in database (encrypted at rest by Neon)

Known Limitations

  1. No Real Authentication: Service token auth only — no end-user auth (ChittyID integration pending)
  2. DoorLoop Still Mock: DoorLoop integration returns hardcoded data (real API integration pending)
  3. No Migrations: Uses drizzle-kit push (destructive) instead of proper migrations
  4. Forensic Tables Not in System Schema: Forensic tables use integer IDs from shared/schema.ts and may not exist in the production database yet
  5. Frontend Not Updated: React components need to support tenant switching
  6. Legacy Express Code: server/routes.ts, server/storage.ts, server/db.ts are legacy Express code kept for standalone dev reference

Future Enhancements

Phase 1: Complete Multi-Tenant Implementation (COMPLETED)

  • ✅ Database schemas created (system.schema.ts, standalone.schema.ts)
  • ✅ Seeding script for IT CAN BE LLC entities
  • ✅ Mode-aware database connection
  • ✅ Wrangler configuration template with KV/R2 provisioned
  • ✅ SystemStorage with tenant-aware Drizzle queries (server/storage/system.ts)
  • ✅ Service token auth middleware (server/middleware/auth.ts)
  • ✅ Tenant-scoped middleware (server/middleware/tenant.ts)
  • ⏳ Update frontend with tenant switcher

Phase 1.5: Hono Route Migration (COMPLETED)

  • ✅ All 17 route modules migrated from Express to Hono
  • ✅ Edge-compatible: Web Crypto API, Neon HTTP driver, no Node.js dependencies
  • ✅ Per-prefix middleware registration (avoids blocking public routes)
  • ✅ Deployed to Cloudflare Workers at finance.chitty.cc
  • ✅ 30/30 tests passing

Phase 2: ChittyConnect Integration (Partially Completed)

  • ✅ Mercury Bank via ChittyConnect backend (multi-account support)
  • ⏳ Register with ChittyRegistry
  • ⏳ Integrate with ChittyConnect MCP
  • ⏳ Log to ChittyChronicle
  • ⏳ Use ChittyAuth tokens

Phase 3: Real Third-Party Integrations (COMPLETED ✅)

  • Wave Accounting - OAuth 2.0 flow + GraphQL API (server/lib/wave-api.ts)
  • Stripe - Payment processing, checkout, webhooks (server/lib/stripe.ts)
  • Mercury Bank - Multi-account via ChittyConnect (static egress IP)
  • OAuth Security - CSRF-protected state tokens (server/lib/oauth-state.ts)
  • Integration Monitoring - Config validation endpoint (/api/integrations/status)
  • Webhook Infrastructure - Idempotent event processing (webhook_events table)
  • DoorLoop - Real property management API (currently mock)

Phase 4: Property Financial API (Partially Completed)

  • property_valuations table in system schema
  • ✅ Property/unit/lease CRUD in SystemStorage
  • ✅ Financial aggregation methods (NOI, cap rate, cash-on-cash, occupancy, rent roll, P&L)
  • ✅ Property mutation + financial endpoints (10 new routes in properties.ts)
  • ✅ Multi-source valuation providers (Zillow, Redfin, HouseCanary, ATTOM, County)
  • ✅ Confidence-weighted valuation aggregation
  • ✅ Valuation routes (current, refresh, history)
  • ✅ TurboTenant CSV import with deduplication
  • ✅ Wave sync import endpoint
  • ✅ Valuation Console (client/src/pages/ValuationConsole.tsx)
  • ✅ Deployed to Cloudflare Workers (35 tests passing)
  • ⏳ Generalize ValuationConsole to any property (currently hardcoded)
  • ⏳ Integrate ValuationConsole with dashboard
  • ⏳ Lease expiration notifications
  • ⏳ Frontend property management UI

Phase 5: ChittyOS Ecosystem Integration

  • ⏳ Replace demo auth with ChittyID
  • ⏳ Expose financial data as MCP resources
  • ⏳ Log to ChittyChronicle (audit trail)
  • ⏳ Issue ChittyCert certificates for secure connections

Phase 6: Advanced Features

  • Consolidated reporting across all entities
  • Inter-company allocation automation
  • Tax optimization and reporting
  • Advanced AI forecasting (beyond GPT-4o)
  • Mobile app (React Native)
  • Export/import (CSV, QFX, OFX)
  • Multi-currency support

Related Documentation

@claude
Copy link

claude bot commented Mar 12, 2026

Part 1 - Critical Issues. 1. Hardcoded OAuth fallback secret in server/routes/google.ts - falls back to a hardcoded string when OAUTH_STATE_SECRET is unset, silently degrading CSRF protection to zero. Apply the same 503 pattern used in Wave OAuth. 2. New tables commsLog and workflows are missing from database/standalone.schema.ts. Standalone mode will throw at runtime. Add equivalent tables or add 501 mode guards per route.

@claude
Copy link

claude bot commented Mar 12, 2026

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Canonical URI: chittycanon://core/services/chittyfinance | Tier 3 (Service Layer) | finance.chitty.cc

Project Overview

ChittyFinance is a full-stack financial management platform for the ChittyOS ecosystem. It provides intelligent financial tracking, AI-powered advice, recurring charge optimization, and integrations with Mercury Bank, Wave Accounting, and Stripe payments.

Architecture: Hono on Cloudflare Workers (production) + legacy Express (local dev). PostgreSQL (Neon) with Drizzle ORM. React frontend.

Essential Commands

Development

npm install              # Install dependencies
npm run dev              # Auto-detect mode and start dev server on port 5000
npm run dev:standalone   # Start in standalone mode (local development)
npm run dev:system       # Start in system mode (ChittyOS integration)
npm run check            # TypeScript type checking
npm run mode:detect      # Detect and display current mode
npm run mode:setup       # Setup mode configuration (script not yet implemented)

Build & Deployment

npm run build            # Build system mode (default for production)
npm run build:standalone # Build standalone mode (outputs to dist/standalone)
npm run build:system     # Build system mode (outputs to dist/system)
npm run build:both       # Build both modes
npm run start            # Run standalone production build locally
npm run deploy           # Deploy to Cloudflare Workers (system mode)
npm run deploy:staging   # Deploy to staging environment
npm run deploy:production # Deploy to production environment

Database Operations

npm run db:push              # Push schema changes (uses current drizzle.config.ts)
npm run db:push:system       # Push system schema to PostgreSQL
npm run db:push:standalone   # Push standalone schema to SQLite
npm run db:seed              # Seed IT CAN BE LLC entity structure

First-Time Setup (System Mode):

MODE=system npm run db:push:system   # Create tables
npm run db:seed                      # Create tenants and users

First-Time Setup (Standalone Mode):

npm run db:push:standalone   # Create SQLite tables
# No seeding needed - single user mode

Critical:

  • Port 5001 is used in server/index.ts for local Express development
  • Server uses reusePort: true for multiple process support on the same port

Architecture

Dual-Mode Operation

ChittyFinance supports two operational modes (controlled by MODE environment variable):

Standalone Mode (default for local development):

  • SQLite database for quick local development
  • Single-tenant (no multi-tenancy overhead)
  • Simplified schema in database/standalone.schema.ts
  • Build output: dist/standalone/
  • Database file: ./chittyfinance.db
  • Run: npm run dev or npm run dev:standalone

System Mode (production - multi-tenant):

  • PostgreSQL (Neon) with full multi-tenancy
  • Supports IT CAN BE LLC entity structure
  • Complete schema in database/system.schema.ts
  • Build output: dist/system/
  • Cloudflare Workers deployment
  • Run: npm run dev:system
  • Deploy: npm run deploy or npm run deploy:production

Mode Detection:

  • Default: npm run dev runs in standalone mode
  • Explicitly set: MODE=system npm run dev
  • Auto-detection script: npm run mode:detect

Multi-Tenant Architecture (System Mode)

IT CAN BE LLC Entity Structure:

IT CAN BE LLC (holding)
├── JEAN ARLENE VENTURING LLC (personal, 85% owner)
├── ARIBIA LLC (series, 100% owned)
│   ├── ARIBIA LLC - MGMT (management)
│   │   ├── Chicago Furnished Condos (consumer brand)
│   │   └── Chitty Services (vendor/tech services)
│   ├── ARIBIA LLC - CITY STUDIO (property)
│   │   └── 550 W Surf St C211, Chicago IL
│   └── ARIBIA LLC - APT ARLENE (property)
│       └── 4343 N Clarendon #1610, Chicago IL
└── ChittyCorp LLC (holding, pending formation)

Tenant Types:

  • holding - Holding companies (IT CAN BE LLC, ChittyCorp LLC)
  • series - Series LLCs (ARIBIA LLC)
  • property - Property holding entities (City Studio, Apt Arlene)
  • management - Management companies (ARIBIA LLC - MGMT)
  • personal - Personal entities (JEAN ARLENE VENTURING LLC)

Key Features:

  • Each tenant has isolated financial data
  • Inter-company transaction tracking
  • Property-specific rent roll and lease management
  • User access control per tenant (roles: owner, admin, manager, viewer)
  • Consolidated reporting across entities

Tech Stack

  • Frontend: React 18 with TypeScript, Wouter (routing), shadcn/ui (Radix UI components)
  • Backend: Hono (Cloudflare Workers, production) / Express.js (legacy, local dev)
  • Database: Neon PostgreSQL with Drizzle ORM
  • Build: Vite (frontend), esbuild (backend)
  • Styling: Tailwind CSS with tailwindcss-animate
  • State: TanStack React Query for server state
  • Payments: Stripe integration
  • AI: OpenAI GPT-4o for financial advice

Project Structure

chittyfinance/
├── client/                 # React frontend (Vite root)
│   └── src/
│       ├── pages/         # Page components (Dashboard, Settings, Landing)
│       ├── components/    # Reusable UI components (shadcn/ui)
│       ├── hooks/         # Custom React hooks
│       └── lib/           # Client utilities
├── server/                # Hono backend (Cloudflare Workers)
│   ├── app.ts            # Hono app factory with middleware wiring
│   ├── env.ts            # HonoEnv type (Bindings + Variables)
│   ├── worker.ts         # Cloudflare Workers entry point
│   ├── index.ts          # Legacy Express entry (standalone dev)
│   ├── routes.ts         # Legacy Express routes (reference only)
│   ├── routes/            # Hono route modules (19 files)
│   │   ├── health.ts     # /health, /api/v1/status
│   │   ├── docs.ts       # /api/v1/documentation (OpenAPI spec)
│   │   ├── accounts.ts   # /api/accounts
│   │   ├── summary.ts    # /api/summary
│   │   ├── tenants.ts    # /api/tenants
│   │   ├── properties.ts # /api/properties (CRUD + financials + rent roll + P&L)
│   │   ├── transactions.ts # /api/transactions
│   │   ├── integrations.ts # /api/integrations
│   │   ├── tasks.ts      # /api/tasks
│   │   ├── ai.ts         # /api/ai-messages
│   │   ├── mercury.ts    # /api/mercury (via ChittyConnect)
│   │   ├── github.ts     # /api/github
│   │   ├── stripe.ts     # /api/integrations/stripe
│   │   ├── wave.ts       # /api/integrations/wave (OAuth)
│   │   ├── charges.ts    # /api/charges (recurring)
│   │   ├── forensics.ts  # /api/forensics (21 endpoints)
│   │   ├── valuation.ts  # /api/properties/:id/valuation (multi-source AVM)
│   │   ├── import.ts     # /api/import (TurboTenant CSV + Wave sync)
│   │   └── webhooks.ts   # Stripe/Mercury webhooks
│   ├── middleware/        # auth, tenant, error middleware
│   ├── storage/           # SystemStorage (Drizzle queries)
│   ├── db/                # connection.ts (Neon HTTP), schema.ts
│   └── lib/               # Server utilities
│       ├── wave-api.ts           # Wave Accounting GraphQL client
│       ├── oauth-state-edge.ts   # Edge-compatible HMAC OAuth state
│       ├── chargeAutomation.ts   # Recurring charge analysis (stubs)
│       ├── forensicService.ts    # Forensic algorithms (legacy)
│       └── valuation/            # Property valuation providers
│           ├── types.ts          # ValuationProvider interface, AggregatedValuation
│           ├── zillow.ts         # Zillow via RapidAPI
│           ├── redfin.ts         # Redfin via RapidAPI
│           ├── housecanary.ts    # HouseCanary REST API
│           ├── attom.ts          # ATTOM Data Gateway
│           ├── county.ts         # Cook County Assessor (Socrata)
│           └── index.ts          # Provider registry + confidence-weighted aggregation
├── database/              # Schema definitions
│   ├── system.schema.ts   # Multi-tenant PostgreSQL (UUID-based)
│   └── standalone.schema.ts # Single-tenant SQLite
├── shared/                # Shared types and schemas
│   └── schema.ts         # Legacy schema with forensic tables (integer-ID)
└── deploy/
    └── system-wrangler.toml # Cloudflare Workers config

Database Architecture

System Mode Schema (Multi-Tenant PostgreSQL)

Location: database/system.schema.ts

Core Tables:

  • tenants - Legal entities (LLCs, properties, management companies)
  • users - User accounts with email/password + optional ChittyID
  • tenant_users - User access to tenants with role-based permissions
  • accounts - Bank accounts, credit cards (tenant-scoped)
  • transactions - Financial transactions with decimal precision
  • intercompany_transactions - Transfers between tenants

Property Management Tables:

  • properties - Real estate assets
  • units - Rental units (if property has multiple units)
  • leases - Tenant leases with rent and dates
  • property_valuations - Cached AVM estimates from external providers (Zillow, Redfin, HouseCanary, ATTOM, County)

Supporting Tables:

  • integrations - Mercury/Wave/Stripe API connections
  • tasks - Financial tasks
  • ai_messages - AI conversation history

Key Characteristics:

  • UUIDs for primary keys (better for distributed systems)
  • Decimal precision for all monetary amounts (12,2)
  • Full multi-tenancy with tenant isolation
  • Hierarchical tenants (parent-child relationships)
  • Indexed for performance (tenant_id, date, etc.)

Standalone Mode Schema (Single-Tenant SQLite)

Location: database/standalone.schema.ts

Simplified Tables:

  • users, accounts, transactions, properties, tasks, integrations

Key Characteristics:

  • Text IDs (simpler for SQLite)
  • Real (float) for amounts (acceptable for dev)
  • No multi-tenancy (single user)
  • Faster for local development

Database Connection

Mode-Aware Connection (server/db.ts):

The database connection automatically switches based on MODE environment variable:

System Mode (MODE=system):

// PostgreSQL (Neon) with multi-tenant schema
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzleNeon({ client: pool, schema: systemSchema });

Standalone Mode (MODE=standalone):

// SQLite for local development
const sqlite = new Database('./chittyfinance.db');
const db = drizzleBetterSqlite(sqlite, { schema: standaloneSchema });

Environment Variables:

  • System mode: DATABASE_URL (Neon PostgreSQL connection string)
  • Standalone mode: SQLITE_FILE (optional, defaults to ./chittyfinance.db)

Database Seeding (System Mode)

Seed Script: database/seeds/it-can-be-llc.ts

Creates the complete IT CAN BE LLC entity structure:

  1. IT CAN BE LLC (parent holding company)
  2. JEAN ARLENE VENTURING LLC (85% owner, personal income funnel)
  3. ARIBIA LLC (series parent)
  4. ARIBIA LLC - MGMT (management company with two brands)
  5. ARIBIA LLC - CITY STUDIO (property entity)
  6. ARIBIA LLC - APT ARLENE (property entity)
  7. ChittyCorp LLC (pending formation)

Also creates:

  • Property records for City Studio and Apt Arlene
  • User accounts for Nicholas Bianchi and Sharon E Jones
  • Access permissions for each user to appropriate tenants

Run seeding:

npm run db:seed

Note: Only run after pushing the system schema (npm run db:push:system)

Storage Abstraction Layer

Critical Pattern: All database access goes through server/storage.ts. Never write direct Drizzle queries in routes.

⚠️ Important: The current server/storage.ts uses the old shared/schema.ts and needs to be updated to use database/system.schema.ts with tenant-aware queries.

Interface (server/storage.ts:12-42):

export interface IStorage {
  // User operations
  getUser(id: number): Promise<User | undefined>;
  getUserByUsername(username: string): Promise<User | undefined>;
  createUser(user: InsertUser): Promise<User>;

  // Integration operations
  getIntegrations(userId: number): Promise<Integration[]>;
  createIntegration(integration: InsertIntegration): Promise<Integration>;
  updateIntegration(id: number, integration: Partial<Integration>): Promise<Integration | undefined>;

  // Financial summary operations
  getFinancialSummary(userId: number): Promise<FinancialSummary | undefined>;
  createFinancialSummary(summary: InsertFinancialSummary): Promise<FinancialSummary>;

  // Transaction, Task, and AI Message operations...
}

Usage in routes (server/routes.ts):

import { storage } from "./storage";

const user = await storage.getUserByUsername("demo");
const summary = await storage.getFinancialSummary(user.id);

Key Features

1. Demo Authentication

Current implementation: Auto-login as "demo" user (no real authentication).

Pattern (server/routes.ts:21-33):

api.get("/session", async (req: Request, res: Response) => {
  const user = await storage.getUserByUsername("demo");
  // Returns demo user for all requests
});

Note: All API routes assume demo user. Real authentication needs to be implemented for production.

2. Financial Dashboard

  • Real-time financial summary: Cash on hand, revenue, expenses, outstanding invoices
  • Data source: Cached in financialSummaries table, fetched from integrations
  • Demo data: Hardcoded values if no summary exists (server/routes.ts:51-55)

3. AI Financial Advice

Location: server/lib/openai.ts

Model: GPT-4o (hardcoded, comment at line 3 warns against changing)

Functions:

  • getFinancialAdvice() - Conversational financial advice based on financial data
  • generateCostReductionPlan() - AI-generated cost reduction strategies

API endpoint: POST /api/ai/message sends user query to OpenAI with financial context

4. Recurring Charge Automation

Location: server/lib/chargeAutomation.ts

Capabilities:

  • Identify recurring charges from integrated services
  • Generate optimization recommendations (cancel, downgrade, consolidate, negotiate)
  • Calculate potential savings

API endpoints:

  • GET /api/recurring-charges - List all recurring charges
  • GET /api/recurring-charges/:id/optimizations - Get optimization suggestions
  • POST /api/recurring-charges/:id/manage - Execute management action

5. Third-Party Integrations (Phase 3 - COMPLETED)

Mercury Bank (server/lib/financialServices.ts:22-51, via ChittyConnect):

  • Real integration through ChittyConnect backend
  • Multi-account support with account selection
  • Static egress IP for bank compliance
  • Fetches balances and transactions
  • Falls back to minimal data in standalone mode
  • Routes: /api/mercury/accounts, /api/mercury/select-accounts
  • Configuration: Requires CHITTYCONNECT_API_BASE + CHITTYCONNECT_API_TOKEN

Wave Accounting (server/lib/wave-api.ts + server/lib/financialServices.ts:54-116):

  • Real integration via OAuth 2.0 + GraphQL API
  • Complete OAuth flow with CSRF protection (HMAC-signed state tokens)
  • Fetches invoices, expenses, and financial summaries
  • Automatic token refresh support
  • Routes: /api/integrations/wave/authorize, /callback, /refresh
  • Requirements: Wave Pro subscription, WAVE_CLIENT_ID, WAVE_CLIENT_SECRET
  • Security: Uses server/lib/oauth-state.ts for secure state token generation/validation

Stripe (server/lib/stripe.ts):

  • Real integration for payment processing
  • Customer management with tenant metadata
  • Checkout session creation (ad-hoc payments)
  • Webhook verification and idempotent event processing
  • Routes: /api/integrations/stripe/connect, /checkout, /webhook
  • Configuration: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
  • Events stored in webhook_events table (see shared/finance.schema.ts)

DoorLoop (server/lib/financialServices.ts:119-147):

  • Mock data (property management)
  • Returns hardcoded rent roll and maintenance data
  • Real API integration pending

GitHub (server/lib/github.ts):

  • Real GitHub API integration (not mock)
  • Fetches repositories, commits, PRs, issues
  • Used for project cost attribution

Integration Status Monitoring:

  • New endpoint: GET /api/integrations/status (server/routes.ts:122-126)
  • Validates which integrations are properly configured
  • Uses server/lib/integration-validation.ts to check environment variables
  • Returns configuration status for wave, stripe, mercury, openai

Utilities

check_system_operations_duplicates.js

A utility script for analyzing and detecting duplicate operations in system mode. Located at the project root. This script helps maintain code quality when working with ChittyOS integration.

Development Workflows

Adding a New Feature

  1. Update database schema in shared/schema.ts:

    export const newTable = pgTable("new_table", {
      id: serial("id").primaryKey(),
      userId: integer("user_id").notNull().references(() => users.id),
      // ... fields
    });
  2. Run migration: npm run db:push

  3. Add storage methods in server/storage.ts:

    async getNewData(userId: number): Promise<NewData[]> {
      return await db.select().from(newTable).where(eq(newTable.userId, userId));
    }
  4. Create API routes in server/routes.ts:

    api.get("/new-data", async (req: Request, res: Response) => {
      const user = await storage.getUserByUsername("demo");
      const data = await storage.getNewData(user.id);
      res.json(data);
    });
  5. Build frontend in client/src/pages/:

    • Use TanStack Query for data fetching
    • Import shadcn/ui components from @/components/ui/

Working with AI Features

OpenAI Configuration (server/lib/openai.ts):

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const openai = OPENAI_API_KEY ? new OpenAI({ apiKey: OPENAI_API_KEY }) : null;

Best practices:

  • Model is GPT-4o (do not change without user request per comment)
  • Max tokens: 500 for financial advice
  • Include financial context in system prompt
  • Handle API errors gracefully (rate limits, invalid keys)
  • When OPENAI_API_KEY is not set, AI functions return rule-based fallback advice
  • OpenAI client is null when unconfigured — functions guard with if (!openai) early returns

Path Aliases

Configured in tsconfig.json:18-21:

{
  "@/*": ["./client/src/*"],
  "@shared/*": ["./shared/*"]
}

Additional alias in vite.config.ts:25:

"@assets": path.resolve(import.meta.dirname, "attached_assets")

Usage:

import { Button } from "@/components/ui/button";
import { users } from "@shared/schema";
import logo from "@assets/logo.png";

API Endpoints

Authentication

  • GET /api/session - Get current demo user (no auth required)

Financial Data

  • GET /api/financial-summary - Get cached financial summary
  • GET /api/transactions - List transactions with optional filters

Integrations

  • GET /api/integrations - List configured integrations
  • GET /api/integrations/status - Check which integrations are properly configured
  • POST /api/integrations - Add new integration
  • PATCH /api/integrations/:id - Update integration credentials
  • GET /api/integrations/events - List webhook events with optional source filter
  • POST /api/admin/events/replay - Replay webhook events to ChittyOS services

Wave Accounting

  • GET /api/integrations/wave/authorize - Start OAuth flow (returns authorization URL)
  • GET /api/integrations/wave/callback - OAuth callback handler (redirects to /connections)
  • POST /api/integrations/wave/refresh - Refresh expired access token

Stripe

  • POST /api/integrations/stripe/connect - Create/fetch Stripe customer
  • POST /api/integrations/stripe/checkout - Create ad-hoc payment session
  • POST /api/integrations/stripe/webhook - Stripe webhook endpoint (signature verified)

Mercury Bank

  • GET /api/mercury/accounts - List available Mercury accounts via ChittyConnect
  • POST /api/mercury/select-accounts - Select which accounts to sync
  • POST /api/integrations/mercury/webhook - Mercury webhook endpoint (authenticated)

Recurring Charges

  • GET /api/charges/recurring - List recurring charges from integrations
  • GET /api/charges/optimizations - Get optimization recommendations
  • POST /api/charges/manage - Cancel/modify a recurring charge

AI Services

  • POST /api/ai/advice - Get initial AI financial advice
  • POST /api/ai/cost-reduction - Generate cost reduction plan
  • POST /api/ai/message - Conversational AI advice (includes previous context)

GitHub Integration

  • GET /api/github/repositories - List user repositories
  • GET /api/github/repositories/:owner/:repo/commits - Get repository commits
  • GET /api/github/repositories/:owner/:repo/pulls - Get pull requests
  • GET /api/github/repositories/:owner/:repo/issues - Get issues

Forensic Accounting

  • GET /api/forensics/investigations - List investigations
  • GET /api/forensics/investigations/:id - Get investigation
  • POST /api/forensics/investigations - Create investigation
  • PATCH /api/forensics/investigations/:id/status - Update status
  • POST /api/forensics/investigations/:id/evidence - Add evidence
  • GET /api/forensics/investigations/:id/evidence - List evidence
  • POST /api/forensics/evidence/:id/custody - Update chain of custody
  • POST /api/forensics/investigations/:id/analyze - Comprehensive analysis
  • POST /api/forensics/investigations/:id/analyze/duplicates - Duplicate payment detection
  • POST /api/forensics/investigations/:id/analyze/timing - Unusual timing detection
  • POST /api/forensics/investigations/:id/analyze/round-dollars - Round dollar anomalies
  • POST /api/forensics/investigations/:id/analyze/benfords-law - Benford's Law analysis
  • POST /api/forensics/investigations/:id/trace-funds - Trace flow of funds
  • POST /api/forensics/investigations/:id/flow-of-funds - Create flow of funds record
  • GET /api/forensics/investigations/:id/flow-of-funds - Get flow of funds
  • POST /api/forensics/investigations/:id/calculate-damages/direct-loss - Direct loss calculation
  • POST /api/forensics/investigations/:id/calculate-damages/net-worth - Net worth method
  • POST /api/forensics/calculate-interest - Pre-judgment interest calculation
  • POST /api/forensics/investigations/:id/generate-summary - Executive summary
  • POST /api/forensics/investigations/:id/reports - Create forensic report
  • GET /api/forensics/investigations/:id/reports - Get forensic reports

Tasks

  • GET /api/tasks - List financial tasks
  • POST /api/tasks - Create task
  • PATCH /api/tasks/:id - Update task
  • DELETE /api/tasks/:id - Delete task

Property CRUD (Phase 4)

  • POST /api/properties - Create property
  • PATCH /api/properties/:id - Update property
  • POST /api/properties/:id/units - Create unit
  • PATCH /api/properties/:id/units/:unitId - Update unit
  • POST /api/properties/:id/leases - Create lease
  • PATCH /api/properties/:id/leases/:leaseId - Update lease

Property Financial Data (Phase 4)

  • GET /api/properties/:id/financials - NOI, cap rate, cash-on-cash, occupancy
  • GET /api/properties/:id/rent-roll - Unit-level rent roll with payment status
  • GET /api/properties/:id/pnl?start=YYYY-MM-DD&end=YYYY-MM-DD - Property P&L by REI category

Property Valuation (Phase 4)

  • GET /api/properties/:id/valuation - Aggregated multi-source valuation estimates
  • POST /api/properties/:id/valuation/refresh - Fetch fresh estimates from all configured providers
  • GET /api/properties/:id/valuation/history - Historical valuation timeline

Data Import (Phase 4)

  • POST /api/import/turbotenant - Import TurboTenant CSV ledger (requires X-Account-ID header)
  • POST /api/import/wave-sync - Sync Wave transactions via OAuth

Environment Configuration

Required Variables

Database (required):

DATABASE_URL="postgresql://user:pass@host/dbname"

Application:

NODE_ENV="development"                              # or "production"
MODE="standalone"                                   # or "system" (multi-tenant)
PUBLIC_APP_BASE_URL="http://localhost:5000"        # Base URL for OAuth redirects

OAuth Security (required for production):

OAUTH_STATE_SECRET="random-secret-32chars"         # HMAC secret for OAuth state tokens

AI & OpenAI (optional for development, required for AI features):

OPENAI_API_KEY="sk-..."                            # Required for AI financial advice

Wave Accounting (Phase 3 - Real Integration):

WAVE_CLIENT_ID="..."                               # OAuth client ID from Wave Developer Portal
WAVE_CLIENT_SECRET="..."                           # OAuth client secret
WAVE_REDIRECT_URI="http://localhost:5000/api/integrations/wave/callback"  # Optional, defaults to PUBLIC_APP_BASE_URL/api/integrations/wave/callback

Stripe (Phase 3 - Real Integration):

STRIPE_SECRET_KEY="sk_test_..."                    # Stripe secret key (test or live)
STRIPE_PUBLISHABLE_KEY="pk_test_..."               # Stripe publishable key (optional, frontend)
STRIPE_WEBHOOK_SECRET="whsec_..."                  # Webhook signing secret for verification

Mercury Bank (Phase 3 - Real Integration via ChittyConnect):

CHITTYCONNECT_API_BASE="https://connect.chitty.cc"  # ChittyConnect backend URL
CHITTYCONNECT_API_TOKEN="..."                       # Service authentication token
CHITTY_CONNECT_URL="https://connect.chitty.cc"      # Frontend redirect URL (optional)

GitHub (optional):

GITHUB_TOKEN="ghp_..."                             # Required for GitHub integration

Property Valuation (Phase 4 - optional, each enables its provider):

ZILLOW_API_KEY="..."                               # RapidAPI key for Zillow estimates
REDFIN_API_KEY="..."                               # RapidAPI key for Redfin estimates
HOUSECANARY_API_KEY="..."                          # HouseCanary API key
ATTOM_API_KEY="..."                                # ATTOM Data Gateway key

Cook County Assessor (Socrata) is always available — no API key required.

Local Development Setup

  1. Provision Neon database:

  2. Initialize schema:

    npm run db:push
  3. Create demo user (manual step required):

    INSERT INTO users (username, password, display_name, email, role)
    VALUES ('demo', 'hashed_password', 'Demo User', 'demo@example.com', 'user');
  4. Start dev server:

    npm run dev
  5. Access application: http://localhost:5000

Testing

Manual Testing

  1. Start dev server: npm run dev
  2. Navigate to http://localhost:5000
  3. Application auto-logs in as "demo" user
  4. Test dashboard, integrations, AI chat
  5. Check browser console for errors
  6. Monitor server logs in terminal

Testing AI Features

Testing Integrations

  • Mercury Bank: Real integration via ChittyConnect (requires CHITTYCONNECT_API_BASE + token)
  • Wave Accounting: Real integration via OAuth 2.0 (requires WAVE_CLIENT_ID + WAVE_CLIENT_SECRET)
  • Stripe: Real integration (requires STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET)
  • DoorLoop, QuickBooks, Xero, Brex, Gusto: Not yet implemented — return empty data with console.warn
  • Unimplemented integration functions return {} or [], not fabricated data

Common Issues & Solutions

Database Connection Errors

Error: DATABASE_URL must be set

Solutions:

  1. Verify DATABASE_URL environment variable is set
  2. Test connection: psql $DATABASE_URL -c "SELECT 1"
  3. Check Neon dashboard for database status
  4. Ensure WebSocket support (ws package installed)

Port 5000 Already in Use

Error: EADDRINUSE: address already in use :::5000

Solution:

lsof -ti:5000 | xargs kill -9

Note: Port cannot be changed (hardcoded for Replit deployment).

OpenAI API Errors

Error: 401 Unauthorized or 429 Rate Limit

Solutions:

  1. Verify OPENAI_API_KEY is valid
  2. Check API key has credits at https://platform.openai.com/account/billing
  3. Implement rate limiting or caching for AI requests
  4. Handle errors gracefully (see server/lib/openai.ts:58-60)

Demo User Not Found

Error: Demo user not found from /api/session

Solution: Create demo user in database:

INSERT INTO users (username, password, display_name, email, role)
VALUES ('demo', 'any_value', 'Demo User', 'demo@example.com', 'user');

Type Checking Failures

Error: TypeScript errors from npm run check

Common causes:

  1. Schema changes not reflected in types (types are auto-generated from schema)
  2. Missing imports from @shared/schema
  3. Path alias not resolving (check tsconfig.json)

Solution: Verify schema exports match usage, run npm run check to see all errors.

ChittyOS Integration Points

ChittyID Integration (Planned)

  • Replace demo authentication with ChittyID
  • Link financial data to ChittyID for cross-platform identity
  • Each user should have associated ChittyID DID

ChittyConnect Integration (Planned)

  • Expose financial summary as MCP resource
  • Provide AI financial advice as MCP tool
  • Enable cross-service financial queries

ChittyChronicle Integration (Planned)

  • Log all financial transactions to audit trail
  • Track AI advice and outcomes
  • Compliance and forensic analysis

Development Best Practices

Database Changes

  1. Update shared/schema.ts (single source of truth)
  2. Run npm run db:push to apply changes
  3. Test with demo user in development
  4. Types are auto-generated from schema (no manual type updates needed)

API Design

  • Always use storage abstraction layer (never direct Drizzle queries in routes)
  • Validate inputs with Zod schemas from @shared/schema
  • Use consistent error handling pattern
  • Return JSON responses with appropriate status codes

Frontend Development

  • Use shadcn/ui components for consistency (@/components/ui/*)
  • Implement responsive design with Tailwind utilities
  • Use TanStack Query for all API calls (handles caching, loading, errors)
  • Optimize re-renders with proper React patterns (memo, useCallback)

Security Considerations

OAuth Security (Phase 3 implemented):

  • CSRF Protection: OAuth state tokens use HMAC-SHA256 signatures (server/lib/oauth-state-edge.ts)
  • Replay Prevention: State tokens expire after 10 minutes (timestamp validation)
  • Tampering Detection: State includes cryptographic signature verified server-side
  • Production Requirement: Set OAUTH_STATE_SECRET to random 32+ character string

Webhook Security:

  • Stripe: Webhook signatures verified using STRIPE_WEBHOOK_SECRET
  • Mercury: Service authentication via serviceAuth middleware
  • Idempotency: All webhook events deduplicated using KV with 7-day TTL

Integration Validation (server/lib/integration-validation.ts):

  • Validates required environment variables before allowing integration connections
  • Returns 503 Service Unavailable if integration not properly configured
  • Prevents cryptic errors from misconfigured services

General Security:

  • Critical: Replace demo authentication before production (ChittyID integration pending)
  • Never commit API keys (use environment variables)
  • Sanitize financial data in logs (mask account numbers)
  • Validate all user inputs on backend (Zod schemas)
  • Use HTTPS in production (HTTP allowed for local dev only)
  • Credential data stored as JSONB in database (encrypted at rest by Neon)

Known Limitations

  1. No Real Authentication: Service token auth only — no end-user auth (ChittyID integration pending)
  2. DoorLoop Still Mock: DoorLoop integration returns hardcoded data (real API integration pending)
  3. No Migrations: Uses drizzle-kit push (destructive) instead of proper migrations
  4. Forensic Tables Not in System Schema: Forensic tables use integer IDs from shared/schema.ts and may not exist in the production database yet
  5. Frontend Not Updated: React components need to support tenant switching
  6. Legacy Express Code: server/routes.ts, server/storage.ts, server/db.ts are legacy Express code kept for standalone dev reference

Future Enhancements

Phase 1: Complete Multi-Tenant Implementation (COMPLETED)

  • ✅ Database schemas created (system.schema.ts, standalone.schema.ts)
  • ✅ Seeding script for IT CAN BE LLC entities
  • ✅ Mode-aware database connection
  • ✅ Wrangler configuration template with KV/R2 provisioned
  • ✅ SystemStorage with tenant-aware Drizzle queries (server/storage/system.ts)
  • ✅ Service token auth middleware (server/middleware/auth.ts)
  • ✅ Tenant-scoped middleware (server/middleware/tenant.ts)
  • ⏳ Update frontend with tenant switcher

Phase 1.5: Hono Route Migration (COMPLETED)

  • ✅ All 17 route modules migrated from Express to Hono
  • ✅ Edge-compatible: Web Crypto API, Neon HTTP driver, no Node.js dependencies
  • ✅ Per-prefix middleware registration (avoids blocking public routes)
  • ✅ Deployed to Cloudflare Workers at finance.chitty.cc
  • ✅ 30/30 tests passing

Phase 2: ChittyConnect Integration (Partially Completed)

  • ✅ Mercury Bank via ChittyConnect backend (multi-account support)
  • ⏳ Register with ChittyRegistry
  • ⏳ Integrate with ChittyConnect MCP
  • ⏳ Log to ChittyChronicle
  • ⏳ Use ChittyAuth tokens

Phase 3: Real Third-Party Integrations (COMPLETED ✅)

  • Wave Accounting - OAuth 2.0 flow + GraphQL API (server/lib/wave-api.ts)
  • Stripe - Payment processing, checkout, webhooks (server/lib/stripe.ts)
  • Mercury Bank - Multi-account via ChittyConnect (static egress IP)
  • OAuth Security - CSRF-protected state tokens (server/lib/oauth-state.ts)
  • Integration Monitoring - Config validation endpoint (/api/integrations/status)
  • Webhook Infrastructure - Idempotent event processing (webhook_events table)
  • DoorLoop - Real property management API (currently mock)

Phase 4: Property Financial API (Partially Completed)

  • property_valuations table in system schema
  • ✅ Property/unit/lease CRUD in SystemStorage
  • ✅ Financial aggregation methods (NOI, cap rate, cash-on-cash, occupancy, rent roll, P&L)
  • ✅ Property mutation + financial endpoints (10 new routes in properties.ts)
  • ✅ Multi-source valuation providers (Zillow, Redfin, HouseCanary, ATTOM, County)
  • ✅ Confidence-weighted valuation aggregation
  • ✅ Valuation routes (current, refresh, history)
  • ✅ TurboTenant CSV import with deduplication
  • ✅ Wave sync import endpoint
  • ✅ Valuation Console (client/src/pages/ValuationConsole.tsx)
  • ✅ Deployed to Cloudflare Workers (35 tests passing)
  • ⏳ Generalize ValuationConsole to any property (currently hardcoded)
  • ⏳ Integrate ValuationConsole with dashboard
  • ⏳ Lease expiration notifications
  • ⏳ Frontend property management UI

Phase 5: ChittyOS Ecosystem Integration

  • ⏳ Replace demo auth with ChittyID
  • ⏳ Expose financial data as MCP resources
  • ⏳ Log to ChittyChronicle (audit trail)
  • ⏳ Issue ChittyCert certificates for secure connections

Phase 6: Advanced Features

  • Consolidated reporting across all entities
  • Inter-company allocation automation
  • Tax optimization and reporting
  • Advanced AI forecasting (beyond GPT-4o)
  • Mobile app (React Native)
  • Export/import (CSV, QFX, OFX)
  • Multi-currency support

Related Documentation

@claude
Copy link

claude bot commented Mar 12, 2026

FULL CODE REVIEW - Please see the detailed review below (posted in sections due to formatting constraints).

OVERVIEW: This is a large PR (3,979 additions) covering four new frontend pages, a property management component suite, three new backend route modules, new schema tables, and new service integrations. The work is substantive and follows many established patterns, but there are several issues worth addressing before merge.

@claude
Copy link

claude bot commented Mar 12, 2026

Critical Issues: 1. Hardcoded OAuth fallback secret - The Google OAuth handler falls back to a hardcoded string when OAUTH_STATE_SECRET is unset. Apply the 503 pattern from Wave OAuth. 2. Standalone schema gap - commsLog and workflows missing from standalone.schema.ts. Security Issues: 3. Missing Zod validation on /api/comms/send and /api/comms/template. 4. No role check on workflow approve/complete mutations. 5. No rate limiting on comms endpoints. 6. Tenant isolation gap - propertyId not validated against tenantId. 7. Template arity not validated in comms handler. Code Quality: 8. No zValidator on POST/PATCH endpoints. 9. JSONB unsafe casts. 10. Client-side pagination. 11. CSV memory usage. 12. Hardcoded Chicago timezone. Architecture: 13. Express auth shims are no-ops. 14. No idempotency on POST endpoints. 15. Google refresh token plaintext. Positives: Clean TanStack Query hooks, edge-compatible API clients, correct OAuth callback mounting, tenantId scoping in updateTransaction, 503 on missing env vars, 135 tests passing. Priority: Must fix - items 1 and 2. Should fix - items 3-10. Nice to have - items 11-15.

@claude
Copy link

claude bot commented Mar 12, 2026

Code Review: feat: core bookkeeping UI + property management

This is a large PR (3,979 additions) covering four new frontend pages, a property management component suite, three new backend route modules, new schema tables, and new service integrations. The work is substantive and follows many established patterns, but there are several issues worth addressing before merge.


Critical Issues

1. Hardcoded OAuth fallback secret (server/routes/google.ts)

The Google OAuth handler uses a fallback that silently degrades CSRF protection to zero if the env var is not set. The Wave OAuth in this codebase returns 503 when secrets are missing — apply the same here: check for the secret and return 503 if it is missing rather than falling back to a hardcoded string.

2. New tables missing from standalone schema (database/standalone.schema.ts)

commsLog and workflows are defined only in database/system.schema.ts. Standalone mode will throw at runtime when any of the new routes are hit. Either add equivalent tables to standalone.schema.ts, or add explicit mode guards in each route returning 501 Not Implemented for standalone mode.


Security Issues

3. Missing input validation on comms routes

POST /api/comms/send and POST /api/comms/template accept to, message, recipientName with no Zod validation. The codebase uses zValidator in other routes — this should be consistent. At minimum: email format validation, E.164 phone format, and message length cap.

4. No authorization on workflow mutations

PATCH /api/workflows/:id/approve and PATCH /api/workflows/:id/complete do not verify the requesting user has permission to approve. Any authenticated user in a tenant can approve any workflow. Add a role check (accountant or admin required for approvals).

5. No rate limiting on SMS/email endpoints

POST /api/comms/send can be called in a tight loop, racking up Twilio/SendGrid costs. Add per-tenant rate limiting or a daily cap enforced via a count query on the commsLog table.

6. Tenant isolation gap in comms/workflows

When fetching or mutating by propertyId, there is no check that the property belongs to the current tenantId. A user in Tenant A could supply Tenant B's propertyId. Validate property ownership before operating.

7. Template injection in comms

The comms template handler calls the template function with Object.values(params) without validating that the arg count matches the template arity. This silently produces malformed messages. Add a per-template parameter schema.


Code Quality

8. No Zod schemas on new POST/PATCH endpoints

POST /api/transactions, POST /api/accounts, POST /api/comms/send, POST /api/workflows all use manual if (!body.field) guards. Existing routes use zValidator middleware — apply it here for consistency.

9. Unsafe type casts on JSONB fields

Several routes cast credentials and metadata JSONB columns directly to Record without runtime validation. Use a Zod schema or type guard before accessing nested properties.

10. In-memory filtering and pagination

Transactions.tsx and Accounts.tsx load all records and filter/paginate client-side. This degrades at realistic dataset sizes. GET /api/transactions should accept page, limit, category, accountId as query params and paginate server-side.

11. CSV export memory usage

The CSV escaping logic is correct, but the entire dataset is materialized in memory. For large exports, stream the response or enforce a row cap with a user-visible warning.

12. Google Calendar hardcoded timezone

The Google Calendar embed uses ctz=America/Chicago. This will confuse non-Chicago tenants. Derive the timezone from the property address or a user preference.


Architecture / Conventions

13. Legacy Express auth shims in server/routes.ts

All auth middleware in the legacy Express routes is stubbed as a no-op. This is pre-existing, but worth flagging since this PR adds meaningful new functionality that is still Express-backed in standalone mode, meaning no auth in that path.

14. Missing idempotency on POST endpoints

POST /api/transactions, POST /api/accounts, and POST /api/workflows will create duplicates on network retry. A client-supplied idempotency key (or at minimum documentation) would prevent silent duplication.

15. Google refresh token stored in plaintext

The OAuth refresh token is stored in integrations.credentials JSONB. Neon encrypts at rest, which is acceptable, but consider encrypting the value with OAUTH_STATE_SECRET before storing so it is protected if rows are exported or logged.


Positive Notes

  • New hooks (use-accounts.ts, use-transactions.ts, etc.) are cleanly structured with proper TanStack Query patterns.
  • Edge-compatible Twilio, SendGrid, and Google API clients using native fetch — correct for Cloudflare Workers.
  • Google OAuth callback correctly mounted before the protected middleware prefix, matching the Wave pattern.
  • updateTransaction() correctly scopes mutations by tenantId — other new storage methods should follow this.
  • The /status endpoints returning 503 when env vars are missing is the right pattern.
  • 135 tests passing is a good baseline. Coverage for the new comms and workflows routes would be a valuable follow-up.

Summary

Area Priority
Hardcoded OAuth secret fallback Must fix before merge
Standalone schema gap Must fix before merge
Workflow authorization Should fix
Comms rate limiting + validation Should fix
Tenant isolation in comms/workflows Should fix
In-memory pagination Should fix
Zod validation on new routes Should fix
Google token encryption Nice to have
Idempotency keys Nice to have
Timezone configurability Nice to have

@claude
Copy link

claude bot commented Mar 12, 2026

line1
x### not a header

chitcommit and others added 2 commits March 12, 2026 06:01
Recurring charges are now detected by analyzing the tenant's own
transaction history — grouping expenses by payee, identifying repeat
charges with similar amounts (10% tolerance), and classifying frequency
(monthly/quarterly/annual/irregular).

Replaces the stub fetchChargesFromIntegration() that always returned []
with detectRecurringCharges() that scans real transaction data.

Optimization recommendations now use detected patterns: consolidation
for duplicate-category charges, negotiation for high-cost monthly
charges, downgrade for software subscriptions, and cancellation review
for irregular low-occurrence charges.

Frontend updated: frequency badge column, occurrence count per merchant,
updated empty state message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ysis

The legacy chargeAutomation.ts had 6 fetch*Charges() functions that all
returned empty arrays with console.warn("not yet implemented"). Now uses
the same transaction pattern analysis as the Hono charges route —
detecting recurring expenses by payee grouping, amount consistency, and
interval classification.

manageRecurringCharge() now flags transactions via metadata instead of
returning a hardcoded failure.

Zero stubs remain in the codebase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 12, 2026 11:03
@claude
Copy link

claude bot commented Mar 12, 2026

PR Review: feat/core-bookkeeping-ui

This is a substantial PR adding four major pages (Transactions, Accounts, Reports, Integrations) plus Google Workspace, Twilio/SendGrid comms, and workflow management. The frontend work is clean and well-structured. Here are my findings organized by priority.


🔴 Critical / Security

1. Authentication shims bypass all auth in legacy Express routes (server/routes.ts)

The PR adds no-op shims for chittyConnectAuth, serviceAuth, resolveTenant, and isAuthenticated:

const chittyConnectAuth = (_req: any, _res: any, next: any) => next();
const serviceAuth = (_req: any, _res: any, next: any) => next();
const resolveTenant = (_req: any, _res: any, next: any) => next();
const isAuthenticated = (_req: any, _res: any, next: any) => next();

While labeled "legacy Express compat shims for dev server startup," there's no environment guard preventing these from being active in production. Any Express route protected by these middleware is now completely unauthenticated. If the legacy Express server is ever deployed, this is an open door. At minimum, add if (process.env.NODE_ENV === 'production') throw new Error('...') or a clear startup assertion.

2. POST /api/comms/send has no rate limiting

The endpoint sends real SMS/email via Twilio/SendGrid on every request with no rate limit, no per-tenant cap, and no recipient validation. A single authenticated user can trigger unlimited outbound messages, burning through provider budgets. Add rate limiting middleware (e.g., Hono's built-in limiter or a KV-backed counter).

3. Phone number and email not validated before sending (comms.ts)

The to field is passed directly to Twilio/SendGrid without format validation. Twilio will reject badly formed numbers with an error, but this should be validated server-side before incurring API round-trips. Consider E.164 validation for SMS and RFC 5321 for email.

4. Potential HTML injection in email templates (server/lib/sendgrid.ts)

Template functions like lease_reminder, rent_receipt, etc. interpolate user-supplied values (tenant names, property names) directly into HTML strings without escaping:

html: `<p>Hi ${tenantName},</p><p>Your lease at <strong>${propertyName}</strong>...`

If tenantName or propertyName contains <script> tags or malicious HTML, it will be sent in email. Add a simple HTML-escape helper for template variables.

5. Drive query string injection (server/lib/google-api.ts)

const q = `'${folderId}' in parents and trashed = false`;

folderId is interpolated without sanitization. A malformed/malicious folderId could break the query syntax or cause unintended Drive API behavior. Sanitize or validate folderId before using it in query strings.

6. getWorkflow in approve/complete endpoints lacks tenant scoping (server/routes/workflows.ts)

const workflow = await storage.getWorkflow(id);
// then update without checking tenantId

storage.getWorkflow(id) fetches by ID only. A user from Tenant A could approve/complete a workflow belonging to Tenant B by guessing/knowing its UUID. The updateWorkflow storage method also only filters by id, not tenantId. Add tenantId to both getWorkflow and updateWorkflow lookups for cross-tenant isolation.


🟡 Code Quality

7. Duplicate recurring charge detection logic

detectRecurringCharges in server/routes/charges.ts and detectRecurringFromTransactions in server/lib/chargeAutomation.ts are nearly identical (~80 lines each). The commit message acknowledges this ("same transaction pattern analysis as the Hono charges route"), but this is fragile — fixes to one won't propagate to the other. Extract to a shared utility in server/lib/.

8. Pervasive any casts in chargeAutomation.ts

transactions = await (storage as any).getTransactions(String(userId));
transactions = (storage as any).getTransactionsByUser...
await (storage as any).updateTransaction(chargeId, ...)

The storage abstraction layer should be typed. The IStorage interface in server/storage.ts needs getTransactions, updateTransaction, etc. added to avoid these casts, which suppress type errors that could indicate real bugs.

9. Dead code in CommsPanel.tsx (line ~447)

message: channel === 'email' ? message : message,

Both branches are identical. This is harmless but suggests the email body was meant to be HTML-wrapped or formatted differently for email vs. SMS.

10. Median calculation mutates input array (server/routes/charges.ts)

const median = amounts.sort((a, b) => a - b)[Math.floor(amounts.length / 2)];

amounts is sorted in-place here, which modifies the array used immediately after for consistent.filter(...). It happens to work because sorting doesn't affect the filter result, but it's a subtle bug waiting to happen if the code changes. Use [...amounts].sort(...) as done in the chargeAutomation.ts version.

11. getSessionUser() returns arbitrary first user (server/storage/system.ts)

async getSessionUser() {
  const [user] = await this.db.select().from(schema.users).limit(1);
  return user;
}

This is documented as a legacy compat shim, but returning the first user in the table is non-deterministic — the result depends on insertion order, database row storage, and query plan. This will silently return the wrong user if user row ordering ever changes. Consider at least ordering by createdAt ASC to make it deterministic, or better, add a clear throw if used outside standalone dev mode.

12. New routes missing Zod input validation

The existing codebase uses Zod for input validation (e.g., shared/schema.ts insertion schemas). The new routes in accounts.ts, comms.ts, workflows.ts use manual if (!body.name) checks, which are less robust and miss type coercion. Align with the project's Zod-based validation pattern.


🟠 Performance

13. Full transaction table scan on every /api/charges/recurring request

const transactions = await storage.getTransactions(tenantId);
const charges = detectRecurringCharges(transactions);

getTransactions fetches all transactions for the tenant on every hit. For a tenant with years of history this could be thousands of rows. The pattern detection is O(n) over all transactions with no caching. Consider: (a) adding a date window (e.g., last 18 months), or (b) caching results with a short TTL in KV since transaction history doesn't change frequently.


✅ Positives

  • The charge detection refactor is a genuine improvement — replacing stubs with real pattern analysis from transaction history is the right approach.
  • Frontend hooks (use-accounts.ts, use-transactions.ts, etc.) follow the existing TanStack Query pattern consistently.
  • Google OAuth uses generateOAuthState/validateOAuthState for CSRF protection — good.
  • updateWorkflow correctly merges metadata rather than overwriting — thoughtful detail.
  • The four new pages (Transactions, Accounts, Reports, Integrations) are feature-complete and well-structured with appropriate role-based nav visibility.
  • CSV import fix (FormData → raw text) is correct.

Test Plan Gaps

The PR test plan omits the new backend routes. Before merging:

  • Test POST /api/comms/send with invalid phone/email format
  • Test cross-tenant workflow access (Tenant A tries to approve Tenant B's workflow)
  • Test GET /api/charges/recurring with a large transaction set for performance
  • Verify the legacy Express dev server doesn't expose the shim'd middleware paths at a non-dev URL

🤖 Generated with Claude Code

Copy link
Contributor

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/src/hooks/use-integrations.ts`:
- Around line 74-93: The useTurboTenantImport mutation currently builds a raw
fetch URL that becomes ?tenantId=null and bypasses the withTenant/apiRequest
pattern; change useTurboTenantImport to construct the request using the same
tenant-aware helper as useWaveSync (or call withTenant(tenantId,
'/api/import/turbotenant') to only append tenantId when non-null) and send the
CSV body via fetch while preserving custom headers (X-Account-ID and
Content-Type); ensure you explicitly omit the tenantId query parameter when
tenantId is null/undefined and keep the onSuccess invalidation logic targeting
['/api/transactions', tenantId] so the hook remains consistent with
apiRequest-driven flows.

In `@server/routes/charges.ts`:
- Around line 147-166: The consolidation block is creating duplicate
recommendations per charge in a category; update the logic in the for loop that
iterates over charges (the block referencing categoryGroups, sameCategory, and
recommendations.push) so you only emit one "consolidate" recommendation per
category (e.g., only when the current charge is the first in sameCategory —
compare charge.id to sameCategory[0].id or track a handledCategories Set) and
skip creating recommendations for the other charges in that category; keep the
totalInCategory and potentialSavings calculation but only add it once and still
continue to avoid double-recommending.
- Around line 237-268: Add Zod input validation for the /api/charges/manage
endpoint: create a Zod schema (e.g., ManageChargeSchema) to parse and validate {
chargeId: string, action: enum('cancel','downgrade','consolidate','negotiate') }
and replace the raw c.req.json() + manual checks in
chargeRoutes.post('/api/charges/manage') with schema.parseAsync(c.req.json()).
For the metadata merge, do not overwrite existing metadata in
storage.updateTransaction; instead fetch the existing transaction first—either
add and call storage.getTransaction(chargeId, tenantId) (preferred) or, if you
cannot add that API, locate the transaction via getTransactionsByAccount and
filter by id—then shallow-merge existing.metadata with { chargeAction: action,
flaggedAt: new Date().toISOString() } and pass the merged metadata to
storage.updateTransaction; update any storage interface types you change to
include getTransaction if you add it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b53067f7-458b-4cb0-b863-84d9cd7915ef

📥 Commits

Reviewing files that changed from the base of the PR and between 4b6cb98 and 393acc6.

📒 Files selected for processing (3)
  • client/src/hooks/use-integrations.ts
  • client/src/pages/Integrations.tsx
  • server/routes/charges.ts

@claude
Copy link

claude bot commented Mar 12, 2026

Code Review PR 46 - Core Bookkeeping UI and Property Management

@claude
Copy link

claude bot commented Mar 12, 2026

CRITICAL - Must Fix

  1. Auth middleware completely bypassed in server/routes.ts

All four auth/tenant middleware functions (chittyConnectAuth, resolveTenant, serviceAuth, isAuthenticated) are replaced with no-ops that call next() unconditionally. This makes every route in routes.ts unauthenticated in the Express standalone path. The comment says Hono middleware cannot be used as Express middleware, but the original imports were Express-compatible middleware from server/middleware/auth.ts. These should either continue to work as-is or the standalone route file needs proper Express-native guards reinstated.

  1. IDOR in workflow update endpoints

SystemStorage.updateWorkflow and getWorkflow never filter by tenantId. Compare with the correct pattern already used in updateTransaction: .where(and(eq(transactions.id, id), eq(transactions.tenantId, tenantId))). The workflow PATCH routes must pass tenantId down and storage must add a tenant condition to both the lookup and update queries.

@claude
Copy link

claude bot commented Mar 12, 2026

HIGH - Should Fix

  1. Google access tokens used without expiry check

In server/routes/google.ts, every API call uses creds.access_token directly. The expires_at field is stored during OAuth but never checked. Once the access token expires (~1 hour), all Google API calls silently fail with 401s. Add a helper that checks expires_at, calls GoogleAPIClient.refreshToken() if expired, persists the new token, and returns a fresh access token before proceeding.

  1. Division by zero in charge detection (server/routes/charges.ts)

The expression Math.abs(a - median) / median produces NaN/Infinity when median equals 0. If a payee group has a median charge of zero, consistent will be empty and valid data is silently dropped. Add if (median === 0) continue before the consistent check.

  1. getSessionUser returns arbitrary first user

The method queries users.limit(1) which returns whichever user sorts first in the DB. Add a NODE_ENV !== production guard and a comment that this must never be used in multi-tenant production paths.

@claude
Copy link

claude bot commented Mar 12, 2026

MEDIUM - Nice to Fix

  1. formatTransaction uses any

function formatTransaction(t: any) should use typeof schema.transactions.$inferSelect to catch type mismatches at compile time, consistent with the project TypeScript-first conventions.

  1. No input validation in POST /api/workflows

title is notNull() in the schema but not validated in the route handler — the request hits a DB constraint error and returns 500 instead of a clean 400. type also accepts arbitrary strings with no allowlist. Add explicit 400 guards and a VALID_TYPES allowlist matching the pattern in POST /api/accounts.

  1. Google OAuth scopes are broader than necessary

googleapis.com/auth/calendar grants full read/write access; calendar.events is sufficient for event creation. googleapis.com/auth/spreadsheets grants full access to all user sheets; spreadsheets.readonly is sufficient if Sheets is only embedded. Narrower scopes reduce blast radius if tokens leak.

  1. Comms template params are positional and unvalidated

Object.values(params) order depends on key insertion order, so callers must send keys in exactly the right sequence. Missing or extra keys silently produce wrong messages. Consider documenting required key order per template name, or accept a typed params object keyed by template name.

@claude
Copy link

claude bot commented Mar 12, 2026

Positive Notes

  • detectRecurringCharges replacing stub integrations with real transaction analysis is a solid improvement. The frequency classification (monthly/quarterly/annual) and 10% tolerance window are reasonable heuristics.
  • New schema tables (comms_log, workflows) are well-indexed and follow the existing UUID + defaultNow() convention.
  • updateTransaction correctly scopes with both id and tenantId. Workflows should match this pattern.
  • Edge-compatible Twilio and SendGrid clients (pure fetch, no Node.js deps) are the right approach for Cloudflare Workers.
  • OAuth callback registration before protected middleware in createApp() is handled correctly.
  • Role-based nav visibility in Sidebar.tsx (bookkeeper+, accountant+) is clean and consistent with existing patterns.

Pre-merge checklist:

  • Restore real auth middleware in server/routes.ts
  • Add tenantId guard to getWorkflow and updateWorkflow
  • Add token refresh logic before Google API calls
  • Guard against median === 0 in recurring charge detection

Copy link
Contributor

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

Copilot reviewed 37 out of 37 changed files in this pull request and generated 9 comments.


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

Copy link
Contributor

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/lib/chargeAutomation.ts`:
- Around line 116-123: The code currently swallows any error from
storage.getTransactions() and treats it as "no transactions"; instead, only use
the fallback when getTransactions is not available — not when it fails
unexpectedly. Change the logic so you first check if (storage as
any).getTransactions is a function and call it; if it's not a function, call
getTransactionsByUser if present; but if getTransactions exists and throws,
rethrow or let the error propagate (do not convert to []). Refer to storage,
getTransactions, and getTransactionsByUser to implement this behavior.
- Around line 57-101: The code computes a filtered numeric array named
`consistent` but continues to use the full `group` for cadence and occurrence
logic; change this so all cadence calculations use the filtered transaction
objects instead. Create a `consistentGroup` by filtering `group` to the
transactions whose absolute amounts match the `consistent` criterion (or
recompute `median` from `consistent` then filter by that), then compute `dates`,
`intervals`, `avgInterval`, determine `frequency`, set `latest` (most recent in
`consistentGroup`), `occurrences` (use `consistentGroup.length`), and
`nextChargeDate` from `consistentGroup` instead of `group` before pushing into
`recurring`.
- Around line 141-156: The current loop over charges emits a consolidation
recommendation for every charge in the same category, causing duplicates; fix
this by emitting one recommendation per category instead—either iterate
categoryGroups.entries() (or use a Set emittedCategories) and when
sameCategory.length > 1 push a single recommendation (use a representative
charge from sameCategory, e.g. sameCategory[0] for
merchantName/chargeId/currentAmount) with
potentialSavings/reasoning/alternativeOptions built from the whole sameCategory,
then skip per-charge pushing; update references to categoryGroups, charges,
recommendations, sameCategory and the 'consolidate' suggestedAction accordingly.
- Around line 205-211: The code calls storage.updateTransaction with three args
unconditionally and returns success even when persistence isn't available;
change the branch to detect the updateTransaction signature and call it
correctly: check (storage as any).updateTransaction.length === 3 and call
updateTransaction(chargeId, String(_userId), { metadata: ... }) for the system
signature, otherwise call updateTransaction(chargeId, { metadata: ... }) for
standalone; if updateTransaction is not present, return success: false with a
clear message. Ensure you reference the existing symbols updateTransaction,
storage, chargeId and _userId when making the fix.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2fe09600-c205-4e10-b129-d96b045990fd

📥 Commits

Reviewing files that changed from the base of the PR and between 393acc6 and 9289839.

📒 Files selected for processing (1)
  • server/lib/chargeAutomation.ts

@chitcommit chitcommit merged commit 0ade4b7 into main Mar 12, 2026
20 of 21 checks passed
@chitcommit chitcommit deleted the feat/core-bookkeeping-ui branch March 12, 2026 12:45
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