- Modal enlarged to min(1400px, 96vw) × 92vh
- Canvas fills full width; field controls moved to a strip below
- Inch rulers on top and left: major ticks at every inch (labeled),
half-inch, quarter-inch, and eighth-inch subdivisions
- Layout editor button hidden on mobile/portrait screens (canvas
requires landscape space to be usable)
- SVG canvas showing all layout fields scaled to check dimensions
- Click or dropdown to select a field; drag to reposition
- Sidebar shows X/Y coordinates in decimal inches with fraction
equivalents (¼", ½", ¹⁄₁₆", etc.)
- End X/Y inputs appear for Line and Graph fields
- Nudge buttons move selected field by ¹⁄₁₆" per click
- Auto-saves on drag end; debounced save on input/nudge changes
- Visible toggle hides fields from PDF without deleting them
- Admin-only Reset to Default wipes and re-seeds the layout
- Accessible to editor+ role via ⊞ button in account header
- Fix account settings modal overflow: add max-height to .modal, make
.modal-body flex/scrollable, widen #acct-settings-modal to 620px
- Add role column to user_accounts (editor|viewer) with migration;
existing assignments promoted to editor
- New isEditorForAccount() in auth middleware for per-account write checks
- Replace global requireEditor with per-account checks in checks.js,
deposits.js, pdf.js, deposit-pdf.js, qbo-import.js
- GET /api/accounts now returns user_role per account
- users.js returns {account_id, role} per assignment; POST/PUT accept
accounts as [{id, role}]
- Frontend: state.accountRole tracks effective role for active account;
applyRoleUI and renderRow use it; user management shows role dropdown
per account assignment
Three-tier user model: admin (all accounts, all actions), editor
(assigned accounts, read/write), viewer (assigned accounts, read-only).
Backend:
- express-session with custom SQLite session store (no extra packages)
- bcryptjs for password hashing
- src/middleware/auth.js: requireAuth, requireAdmin, requireEditor,
canAccessAccount helpers
- src/routes/auth.js: login, logout, /me, setup-needed, change-password
- src/routes/users.js: full CRUD + account assignments (admin only)
- All API routes protected; /api/accounts filtered by user access;
write routes gated by requireEditor; admin-only routes locked down
Frontend:
- Login overlay (full-page) with first-run admin-setup flow
- Role-based UI: admin-only elements hidden for non-admins; edit/delete
and PDF buttons hidden for viewers; account switcher shows only
accessible accounts for non-admins
- Users modal (admin only): user list with role badges, create/edit/delete
users, set account access via checkboxes
- Change-password section available to all logged-in users
- apiFetch redirects to login on 401
Red "Delete Account" button at top of Account Settings. Clicking opens
a second modal with an irreversibility warning and the account name,
requiring a second explicit confirmation before deletion. Deletes the
account row plus all associated checks and deposits. Redirects to the
setup wizard if no accounts remain.
Two-tab modal: "Checks to Print" parses QBO Transaction List / Check
Detail CSV exports; "Deposits" parses QBO Deposit Detail CSV exports
and groups by date. Both tabs show a preview before confirming import.
- Halve all check line thicknesses (line_thick * 0.5)
- Add second_signature column to account table with runtime migration
- Account settings toggle: enables a second signature line drawn 0.25"
above the primary signature line, matching its width and thickness
Pencil button next to "Next check: X" in the header opens a modal
with a warning about sequence gaps/duplicates. Saves via new
PUT /api/account/:id/check-no endpoint (sets current_check_no = next - 1).
- Add select-all checkbox to checks table header; checks/unchecks all
visible (filtered) rows; supports indeterminate state
- Add summary bar below checks toolbar showing count and total amount
of filtered checks
- Deposit slip: output to full 8.5x11 letter page for trimming
- Deposit slip: remove beige left strip fill (white background)
- Deposit slip: remove vertical separator between depositor/bank info
- Deposit slip: stack bank info below account info instead of side-by-side
- Deposit slip: lower date underline position
- Deposit slip left strip: flip text orientation to read tilt-left
(rotate 90 instead of -90; reposition all strip element anchors)
- Deposit slip left strip: center MICR and labels in strip width
- Deposit slip total: include decimal point in rotated digit amount
- New Deposits tab with ledger: date, checks total, cash, deposit total, item count, status
- Slide-in deposit panel: date, currency, coin, cash back, dynamic check entry rows, live totals
- Save deposit, then generate Deposit Slip or Deposit Report PDF
- Deposit slip: 3.375" x 8.5" portrait with Style A background drawn server-side,
digit-column amounts, GnuMICR routing/account line rotated 90 deg, rotated
deposit total and check count in left margin
- Deposit report: plain Courier ledger with depositor/bank info, check grid, totals
- deposits and deposit_items tables in schema; ON DELETE CASCADE for items
- Routes: GET/POST/PUT/DELETE /api/deposits, POST /api/deposit-pdf
- Generating a slip marks deposit as printed; date range and status filters
- README updated to describe deposit slip feature
- Add gear button in header to open account settings modal
- Modal covers Organization, Bank, Account, Logo upload, and Printer Offset fields
- PUT /api/account/:id backend endpoint with full field validation
- Logo file reader with inline preview; only updates logo if a new file is chosen
- CSS for btn-header-icon, settings-section-label, logo-preview, form-row-4
- Add GnuMICR.otf (tracked alongside existing GnuMICR.ttf)
- Schema: account_id FK on checks and layout_fields; UNIQUE per-account on check_no and field_name
- DB: runtime migration recreates both tables to add account_id (assigns existing rows to account 1)
- Routes: GET /api/accounts lists all; GET /api/account/:id replaces hardcoded id=1; POST /api/account/setup always creates a new account and returns accountId
- checks.js: all queries scoped by account_id; POST requires account_id in body
- pdf.js: resolves account from check's account_id instead of id=1; layout fields fetched per-account
- import-mdb.js: always INSERTs a new account (never deletes existing); all records tagged with new accountId
- Frontend: account switcher in header; activeAccountId persisted to localStorage; all API calls pass account_id; switching accounts reloads checks; wizard and import auto-switch to newly created account