Combine clients and businesses under entities, polish the web time clock,
and show live invoice PDF preview with tighter line-item editing.
Co-authored-by: Cursor <cursoragent@cursor.com>
0015 was already marked applied with the wrong column name, so redeploys
skipped it; 0016 drops send_reminder_at and adds sendReminderAt.
Co-authored-by: Cursor <cursoragent@cursor.com>
Use camelCase sendReminderAt to match existing invoice columns; the
snake_case name caused dashboard queries to fail after deploy.
Co-authored-by: Cursor <cursoragent@cursor.com>
Restrict line item edits to draft invoices, auto-create drafts on clock-out,
and add sendReminderAt scheduling with dashboard due reminders.
Co-authored-by: Cursor <cursoragent@cursor.com>
Privacy Policy and Terms now share a document layout with table of contents,
plain-paragraph copy, and beenvoice.soconnor.dev contact details for App Store review.
Co-authored-by: Cursor <cursoragent@cursor.com>
Returns whether Authentik and signups are enabled so the app can show the right
sign-in options per instance at runtime.
Co-authored-by: Cursor <cursoragent@cursor.com>
Extract privacy and terms content into reusable components, replace auth modals with links to /privacy and /terms, add settings legal section, and remove duplicate legal-modal markup.
Co-authored-by: Cursor <cursoragent@cursor.com>
Enable session-based auth from the Expo companion app via SecureStore cookies and trusted deep-link origins.
Co-authored-by: Cursor <cursoragent@cursor.com>
Removes output: "standalone" from next.config.js, updates build/start/preview
scripts to use next build/next start, and updates Dockerfile to copy .next
output directory and run via bun run start instead of server.js.
https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
The React Compiler (reactCompiler: true) generates code that calls
react/compiler-runtime during SSR, which resolves to null on Linux/bun,
causing "Cannot read properties of null (reading 'useRef')" on every
statically prerendered page. Disabling restores normal SWC transforms.
https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
Dashboard pages require authentication and include client components
with hooks that fail during static prerendering on Linux/Docker.
Setting force-dynamic in the layout prevents build-time prerendering
across the entire authenticated dashboard.
https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
Prevents Turbopack from statically analyzing better-auth's kysely adapter,
which imports symbols removed in kysely 0.29. Also fixes @typescript-eslint
consistent-type-imports warning on the Db type alias.
https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
- Schema: add publicTokenExpiresAt to invoices table
- invoices.generatePublicToken: accepts optional ttlHours param; sets
expiry timestamp when provided
- invoices.getByPublicToken: returns 403 if token is expired
- New route GET /api/i/[token]/pdf: streams the invoice PDF publicly,
returns 410 Gone if expired; excluded from auth middleware
- MCP invoices_generate_public_token: exposes ttlHours, returns both
webUrl (/i/{token}) and pdfUrl (/api/i/{token}/pdf)
https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
MCP expanded from 49 to 58 tools:
- templates_list, templates_list_by_type, templates_create, templates_update,
templates_delete — full invoice template CRUD
- businesses_get_email_config, businesses_update_email_config — configure
per-business Resend API key and sending domain
- profile_get, profile_update — user profile read/write
invoices_list now accepts optional status and clientId filters (e.g. list
all draft invoices, or all invoices for a specific client). Backed by a
new optional input on invoices.getAll in the tRPC router.
https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
The backend now accepts invoiceId on clock-in to link a timer directly
to a specific invoice. Expose this in the MCP so AI clients can clock in
against a particular invoice rather than relying on latest-invoice lookup.
Also update time_clock_out description to reflect the new behavior.
https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
- Remove "Time Clock" from sidebar navigation
- Redirect /dashboard/time-clock to /dashboard/invoices
- Add InvoiceTimerCard to invoice detail page (shown for non-paid invoices)
- Timer started from an invoice is explicitly linked to that invoice at clock-in
- On clock-out, time is added directly to the linked invoice as a line item
- Active timer widget on dashboard now shows which invoice is being tracked
- Backend: clockIn accepts invoiceId; clockOut prefers explicit invoiceId over
searching for the latest draft invoice for the client
https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
Summary cards now include the in-progress session's hours and earnings,
rounded up to the nearest 15-min increment (matching clock-out billing
logic). A secondary "+Xh est." line appears below each stat when a timer
is active, updating every second as the elapsed counter ticks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add time_entries_update, time_entries_delete, time_entries_get_summary tools
- Fix textResult to use `data ?? null` so JSON.stringify always returns a
string — procedures returning undefined (e.g. getRunning with no timer,
businesses.getById not found) were dropping the text field from the MCP
content item, causing client SDK validation failures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
next build with output:standalone does not automatically copy
.next/static or public into .next/standalone/. The standalone
server.js looks for them at __dirname/.next/static and
__dirname/public, so they must be copied there post-build.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Turbopack disallows ssr:false in Server Components. Extracted the three
recharts dynamic imports into charts-client.tsx ("use client"), which
page.tsx now imports from directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dynamically import recharts components with ssr:false to prevent
server-side rendering of DOM-dependent charts, eliminating the
width/height -1 warnings and redacted SSR errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The baseline/bogus-entry logic was a one-time workaround for a db:push
→ migration transition that is long past. It required a per-migration
isMigrationApplied case — missing cases caused today's invoiceId bug.
Now both db:migrate (dev) and Docker startup use the same standard
migrate() call. Drizzle's own tracking table handles idempotency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Baseline logic was falling through to false for all migrations after
0008, causing incorrect behaviour on fresh deploys. Now correctly
detects api_keys table, time_entry table, invoiceId column, and
verification token value column type.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a banner below the page header when a timer is running: shows
description, client, elapsed time (live), a Stop button that auto-links
to the latest invoice, and a Details link to the time clock page.
Query is prefetched server-side so the widget is instant on load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Clock out and manual entry creation now auto-add a line item to the
client's latest draft/sent invoice and return invoice info
- Time clock page shows invoice badge on each entry with a link
- Toast after clock-out/create includes "View Invoice" action when linked
- MCP time_clock_in now accepts optional startedAt for backdating
- MCP time_clock_out description updated to document invoice linking
- Migration 0012: widen beenvoice_verification_token.value to text to
fix varchar(255) overflow during Authentik PKCE OAuth flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New beenvoice_time_entry DB table with migration (startedAt/endedAt, hours, rate, clientId)
- tRPC router with clockIn, clockOut, getRunning, getAll, getSummary, create, update, delete
- Dashboard page at /dashboard/time-clock with live elapsed timer, entry list, and manual entry form
- 5 MCP tools: time_clock_in, time_clock_out, time_get_running, time_entries_list, time_entries_create
- Sidebar navigation entry
Timer state is stored in PostgreSQL (endedAt IS NULL = running), suitable for serverless/Coolify deployment.
drizzle-kit migrate exits 0 even when migrations fail, so the server
would start on a broken schema. The custom migrate.ts uses drizzle-orm's
programmatic migrator and calls process.exit(1) on failure, which
prevents the server from starting and makes failures visible in Coolify.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migration now runs at container start (when DB is available) rather than
during the build stage (when no DB is reachable). The release image now
includes node_modules, drizzle.config.ts, and the drizzle/ folder so
drizzle-kit can apply pending migrations on each deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NEXT_PUBLIC_ vars are baked in at build time, so NEXT_PUBLIC_APP_URL=localhost
was being embedded in the bundle. Removing baseURL lets better-auth use
window.location.origin automatically, which works correctly on any domain.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PostgreSQL does not support IF NOT EXISTS for ADD CONSTRAINT. Use PL/pgSQL
DO blocks to guard constraint creation idempotently instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>