Drop company_id column from all 22 domain tables via migration.
Remove companyId from JWT payload, auth plugins, all service method
signatures (~215 occurrences), all route handlers (~105 occurrences),
test runner, test suites, and frontend auth store/types.
The company table stays as store settings (name, timezone). Tenant
isolation in a SaaS deployment would be at the database level (one
DB per customer) not the application level.
All 107 API tests pass. Zero TSC errors across all packages.
Dev seed script creates 8 accounts, 8 members, 16 repair templates,
6 repair tickets in various statuses, and a school batch with 5
instruments. Run with bun run db:seed-dev.
Batch detail page now has a status progress bar matching the ticket
detail pattern. Add Repair from batch pre-fills the account and
contact name. New repair form reads accountId and contactName from
search params when linked from a batch.
Batch create form now uses searchable account picker (same as ticket
intake) instead of a dropdown. Batch detail shows + Add Repair button
that creates a ticket pre-linked to the batch. Repair count and total
estimate auto-calculated from linked tickets. Batch PDF generates a
summary with all repairs, statuses, estimates, and grand total. Added
Mark Delivered button for completed batches. New repair form shows
batch badge when linked from a batch.
PDF button now opens a modal where staff can select which line items,
customer-visible notes, and photos to include before generating. Defaults
to all customer notes and completed photos. Replaces the old one-click
generation.
Added 4 help/wiki pages for the Repairs module: Repairs Overview, Repair
Templates, Repair Batches, and Notes & Photos. Covers ticket workflow,
template usage, batch management, note visibility, photo phases, and
signed approval process. Updated Getting Started nav to include Repairs.
Critical: Add company scoping to line item update/delete and note
delete via ownership verification through ticket join. Add companyId
validation to signed URL file serving. High: Paginate notes list
endpoint with search and sort support. Fix blob URL memory leaks in
AuthImage components with proper cleanup on unmount. Improve photo
upload error handling — count failures and show specific error count
instead of silently clearing form.
Comprehensive test coverage for repairs: full status lifecycle (new →
picked_up), in_transit branch, pending_parts round-trip, delivered
alternate ending, reopen cancelled, validation errors, search by
instrument, filter by status and isBatch, notes CRUD with visibility
and status capture, service templates CRUD with soft-delete, signed
URL generation and access. Migration to set column default to new.
107 total API tests passing.
New tickets start as 'new' (just created, not yet examined). In Transit
is now a branch status off New for school pickups and shipped instruments.
Intake means the instrument has been physically received and documented.
Status progress bar, labels, filters, and default status all updated.
Removed debug logging from file upload endpoint.
API client no longer sets Content-Type: application/json on requests
without a body (fixes DELETE 400 errors). Added repair_note to the
allowed entityTypes whitelist for file uploads so photos can be
attached to repair notes.
All image thumbnails in ticket photos and note attachments now use
AuthImage component that fetches via Bearer token. Fixed 401 errors
on Photos & Docs tab. Added error logging for note photo uploads to
surface upload failures.
Note photo thumbnails were failing because img src pointed at an
authenticated endpoint without auth headers. Added AuthImage component
that fetches images via Bearer token and renders as blob URLs. Photos
now display inline in note entries. Clicking still opens via signed URL.
Notes can now have N photos attached. Attach Photo button in the note
form allows selecting multiple images before posting. Photos are uploaded
after the note is created, linked via entityType repair_note. Photos
display inline in each note entry as clickable thumbnails that open via
signed URL.
New /files/signed-url/:id endpoint generates a 15-minute JWT-signed URL
for any file. New /files/s/* endpoint serves files using the token from
the query string without requiring auth headers. This allows files to
open in new browser tabs without authentication issues. Photos and
documents in repair tickets now use signed URLs when clicked.
Photos open in new tab on click. PDFs display as a file icon with
filename instead of broken image. Both images and PDFs are clickable
to view full size in a new browser tab.
Generates a customer-facing PDF with ticket details, line items, and
customer-visible notes using jsPDF. PDF is both downloaded to the user
and automatically uploaded to the ticket's documents section. Works as
a quote (pending_approval) or receipt (picked_up). Button in ticket
detail header.
Ticket detail page now uses tabs (Details, Line Items, Notes, Photos &
Docs) instead of a long scrolling page. Photos section gains a Documents
category for uploading signed approvals, quotes, and receipts (accepts
PDFs). PDF generation button added to header (stub for now).
New repair_note table for timestamped journal entries on tickets. Each
note captures author, content, visibility (internal or customer-facing),
and the ticket status at time of writing. Notes display as a running
feed on the ticket detail page with newest first. Internal notes have
a lock icon, customer-visible notes highlighted in blue. Supports add
and delete with appropriate permission gating.
Repairs list now has a filter panel with status (defaults to active only),
condition, batch/individual toggle, and date range filters for intake and
promised dates. Added Batch column to the repairs table. Backend list
endpoint accepts filter query params for status, condition, dates, and
batch membership. Template management page (admin only) with CRUD for
common repair services (rehair, string change, etc.) with instrument
type, size, and default pricing. Sidebar updated with Repair Templates
link gated on repairs.admin permission.
Status progress bar component with visual step indicator, in_transit
status for instruments being transported to shop. Ticket detail page
reworked with inline edit form, reopen for cancelled tickets, photos
grouped by repair phase (intake/in_progress/completed). Intake form
now supports building estimates with template picker and manual line
items that carry over to the ticket. Service template API client and
types added for template search in line item dialogs.
Full-stack implementation of instrument repair tracking: DB schema with
repair_ticket, repair_line_item, repair_batch, and repair_service_template
tables. Backend services and routes with pagination/search/sort. 20 API
tests covering CRUD, status workflow, line items, and batch operations.
Admin frontend with ticket list, detail with status progression, line item
management, batch list/detail with approval workflow, and new ticket form
with searchable account picker and intake photo uploads.
12 RBAC API tests: permission denial for no-role users, viewer read-only,
sales associate can create but not delete, technician scoped access,
instructor inventory denied, admin full access, permission inheritance
(admin implies edit+view), system role undeletable, custom role lifecycle.
Wiki articles for Users & Roles and Profile settings.
Reset password link expires in 1 hour instead of 24.
Backend:
- POST /v1/auth/change-password (current user)
- POST /v1/auth/reset-password/:userId (admin generates 24h signed link)
- POST /v1/auth/reset-password (token-based reset, no auth required)
- GET/PATCH /v1/auth/me (profile read/update)
- Auto-seed system permissions on server startup
Frontend:
- Profile page with name edit, password change, theme/color settings
- Sidebar user link goes to profile page (replaces dropdown)
- Users page: "Reset Password Link" in kebab (copies to clipboard)
- Sign out button below profile link
Backend:
- GET /v1/users (list company users)
- GET/POST/PATCH/DELETE /v1/roles (role CRUD with permissions)
- GET/POST/DELETE /v1/users/:userId/roles (role assignment)
- GET /v1/me/permissions (current user's effective permissions)
Frontend:
- Roles list page with kebab menu (edit permissions, delete custom)
- Role detail page with grouped permission checkboxes and inheritance note
- New role page with auto-generated slug
- Users list page showing assigned roles per user
- Manage Roles dialog for adding/removing roles per user
- Sidebar: Admin section with Users, Roles, Help links
- permission, role, role_permission, user_role_assignment tables
- 42 system permissions across 13 domains
- 6 default roles: Admin, Manager, Sales Associate, Technician, Instructor, Viewer
- Permission inheritance: admin implies edit implies view
- requirePermission() Fastify decorator on ALL routes
- System permissions and roles seeded per company
- Test helpers and API test runner seed RBAC data
- All 42 API tests pass with permissions enforced
Application-level audit logging with 30-day default retention. Captures
insert/update/delete on all domain tables with field-level diffs, user
attribution, and request ID correlation. Sensitive fields masked.
Configurable retention, BullMQ cleanup job, admin UI for querying.
Structured logging with request ID correlation throughout:
- Auth: register, login success, login failure (warn level)
- Accounts: soft-delete
- Members: move between accounts
- Tax exemptions: approve (info), revoke (warn with reason)
- Files: upload, delete (already had logging)
All logs include userId, entityId, and contextual data for debugging.
4xx errors logged as warn, 5xx as error.
- Password minimum increased from 8 to 12 characters
- CORS configurable via CORS_ORIGINS env var (comma-separated whitelist)
- Pagination empty string q param handled via preprocess
- Request timeout set to 30 seconds
- Log file output via LOG_FILE env var (stdout + file in production)
- Pino-pretty in development, JSON to stdout + file in production
Granular permission system: domain.action pattern (accounts.view, pos.edit,
repairs.admin). Custom roles composed of permissions. Default system roles
(Admin, Manager, Sales Associate, Technician, Instructor, Viewer). Migration
plan from current role enum. API and frontend guard patterns.
- StorageProvider interface with LocalProvider (S3 placeholder)
- File table with entity_type/entity_id references, content type, path
- POST /v1/files (multipart upload), GET /v1/files (list by entity),
GET /v1/files/:id (metadata), GET /v1/files/serve/* (content),
DELETE /v1/files/:id
- member_identifier drops base64 columns, uses file_id FKs
- File validation: type whitelist, size limits, per-entity max
- Fastify storage plugin injects provider into app
- 6 API tests for upload, list, get, delete, validation
- Test runner kills stale port before starting backend
Move members.tsx to members/index.tsx so $memberId.tsx resolves as a
sibling route. Fixes member detail page not loading when navigating
from members list.
- Member detail page at /members/:id with edit form and identity documents
- Expandable identity documents on account members tab
- Kebab menu on both members list and account members tab (Edit, View IDs, View Account, Delete)
- Identifier form with image upload (base64), ID type select, dates
- Wiki article for identity documents
Markdown-based help pages rendered in the admin UI. Sidebar category
navigation with search. Articles: Getting Started, Accounts Overview,
Members Overview, Payment Methods, Tax Exemptions. Written for
non-technical store staff.
12 new tests: search by email/phone, pagination params, sort order,
billing mode, scoped member list, isMinor from DOB, DOB override,
isMinor recalculation on update, 404 for missing member. Total: 36.
Custom test framework that starts the backend, creates a test DB, runs
migrations, and hits real HTTP endpoints. Supports --suite and --tag
filtering. 24 tests covering account CRUD, member inheritance, state
normalization, move, search, and auto-generated numbers. Run with
bun run api-test.
- Address field on member table (jsonb, same format as account)
- Members inherit email, phone, address from account when not provided
- State normalization: "Texas" → "TX", "california" → "CA" via shared util
- Member form drops zodResolver to fix optional field validation flashing
- Account name auto-format: "First Last - Account"
- US state lookup with full name + code support
Add opt() preprocessor that coerces empty strings to undefined before Zod
validation. Applied to every optional string field in account, member,
identifier, supplier, product, inventory unit, tax exemption, payment
method, and lookup schemas. Fixes forms rejecting blank optional fields.
- Fix account create form blocking on empty name when includeFirstMember
- Add noValidate to forms to prevent browser native validation on optional fields
- Show member number column on account members tab
- Replace DOB with phone in members table for better at-a-glance info
- Doc 02: Add member_identifier table, member_number, primary_member_id,
account_number auto-generation, isMinor override, tax_exemption as
separate table, member move, updated business rules
- Doc 03: Document lookup table pattern replacing pgEnums for status and
condition, add system/custom value distinction
- Doc 22: Mark all Phase 2 items as complete, add new tables to additions
section, update audit findings, note admin frontend exists
- GET /v1/members with search across all members (includes account name)
- POST /members/:id/move with optional accountId (creates new account if omitted)
- primary_member_id on account table, auto-set when first member added
- isMinor flag on member create (manual override when no DOB provided)
- Account search now includes member names
- New account form includes primary contact fields, auto-generates name
- Members page in sidebar with global search
Theme system with 5 color presets (Slate, Emerald, Violet, Amber, Rose)
and light/dark/system mode. User menu in sidebar with theme picker and
sign out. Login page uses standalone dark branded styling with autofill
override. Auth persists in sessionStorage across refreshes.