Skip to content

Latest commit

 

History

History
300 lines (224 loc) · 11.4 KB

File metadata and controls

300 lines (224 loc) · 11.4 KB

AGENTS.md — crew-log

Instructions for Cursor agents (and any other AI assistant) working in this repo.


What this app is

crew-log is a small Node.js app for Texas Rock Solid that tracks crew labor hours — foreman, job/address, # of guys, hours worked, trades — and totals man-hours for payroll.

It's the production version of a static HTML mock Andrew built. The mock had no backend, no persistence, no auth; this repo gives it all three plus a real deployment shape.

The app is deployed and operated by Operator (a separate AI employee app that lives at /opt/agent on the same VPS and serves dashboard.texasrocksolid.com). Operator handles git pull, pm2 restart, nginx, certs, and backups. We push code; Operator runs it.

Production URL: https://crew.texasrocksolid.com

Current version

v0.2 — first production-ready release. Job sites and foremen are first-class entities (their own tables with FK from entries), not free-text strings. The entry form picks from a list instead of typing, with a "did you mean...?" near-match warning to prevent typo-induced duplicate totals. There's a Manage Lists modal for rename / merge / archive.

v0.1 (free-text job/foreman) was never deployed.


Read first, in this order

Before touching any code:

  1. README.md — overview, API contract, schema, deployment expectations
  2. package.json — dependencies and scripts
  3. src/server.js — Express app, all routes (single file by design)
  4. src/db.js — SQLite schema (better-sqlite3, sync)
  5. src/auth.js — shared-PIN gate, in-memory rate limiter, signed cookies
  6. src/validation.js — server-side input validation, name normalization, near-match suggestions
  7. public/index.html + public/app.js + public/styles.css — vanilla front-end. The picker (foreman/job dropdown with add-new + suggestions) and the Manage Lists modal both live in app.js.

Skipping these will land code in the wrong place or violate a convention.


Coding rules

  • Plain Node.js. No TypeScript anywhere.
  • Vanilla JS / CSS on the front-end. No framework, no build step, no bundler.
  • better-sqlite3, synchronous, no ORM. Prepared statements only — never string-concatenate SQL.
  • One responsibility per file. Don't add abstractions until something is used in two places.
  • Secrets in .env only. Never hardcode PINs, secrets, or URLs. Bootstrap vars: PORT, CREW_PIN, COOKIE_SECRET, NODE_ENV.
  • Validate on the server. Front-end validation is UX; server validation is the contract. Anything user-supplied gets re-checked in src/validation.js before it touches the DB.
  • Errors return as JSON, never throw past the route handler. The agent loop / browser should always get a structured response.

Design language (TRS)

  • Primary orange: #E87722
  • Dark: #333
  • Pill-shaped buttons (border-radius 40px)
  • Card-based summary blocks with orange top border
  • Mobile (≤ 700px): table rows render as stacked cards with data-label pseudo-elements. Desktop: real table.

Don't introduce other colors / shapes without asking.


Running the app

npm install
cp .env.example .env       # then fill in CREW_PIN and COOKIE_SECRET
npm run dev                # node --watch, auto-restart on file changes
# or
npm start                  # production-like (no watch)

Open http://localhost:3001, enter the PIN, work.

Generate a fresh COOKIE_SECRET:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Environment

  • Local dev DB: data/crew.db (gitignored, created on first run)
  • Local dev PIN: whatever you put in your .env (CREW_PIN=1234 is fine for dev)
  • Port: 3001 (different from Operator's 3000 so both can run on the same machine)

Testing changes

Backend changes (src/*.js)

Run the API smoke tests with curl against a running server. Example flow:

JAR=$(mktemp)
PIN=$(grep '^CREW_PIN=' .env | cut -d= -f2)

# 1. Auth
curl -s -c "$JAR" -X POST http://localhost:3001/api/login \
 -H 'Content-Type: application/json' -d "{\"pin\":\"$PIN\"}"

# 2. Create a foreman + a job (these come back with their ids)
curl -s -b "$JAR" -X POST http://localhost:3001/api/foremen \
 -H 'Content-Type: application/json' -d '{"name":"Test Foreman"}'
curl -s -b "$JAR" -X POST http://localhost:3001/api/jobs \
 -H 'Content-Type: application/json' -d '{"name":"Test Job"}'

# 3. Log an entry against those ids (NOT free text)
curl -s -b "$JAR" -X POST http://localhost:3001/api/entries \
 -H 'Content-Type: application/json' \
 -d '{"date":"2026-04-30","foreman_id":1,"job_id":1,"guys":2,"hours":8,"trades":["Lath"]}'

# 4. Confirm the totals roll up
curl -s -b "$JAR" http://localhost:3001/api/summary

Useful negative-path checks when working on the picker or near-match logic:

# Case dupe -> 409 with `existing`
curl -s -b "$JAR" -X POST http://localhost:3001/api/foremen \
 -H 'Content-Type: application/json' -d '{"name":"test foreman"}'

# Typo near-match -> 409 with `suggestions` (Levenshtein <= 2)
curl -s -b "$JAR" -X POST http://localhost:3001/api/foremen \
 -H 'Content-Type: application/json' -d '{"name":"Test Foremen"}'

# Force-bypass when the user really did mean a new one
curl -s -b "$JAR" -X POST 'http://localhost:3001/api/foremen?force=1' \
 -H 'Content-Type: application/json' -d '{"name":"Test Foremen"}'

If a change breaks an existing endpoint, fix it before commit.

Front-end changes (public/*)

Hard-refresh (Cmd+Shift+R) to bust the browser cache. Test on:

  • Desktop width (table layout)
  • ≤ 700px width (mobile cards layout)
  • Print preview (the table should render as a table even if you came from mobile view)

Deployment — leave it to Operator

Do not add deploy scripts, ecosystem files, or nginx configs to this repo. Operator owns the deployment path:

  1. We push to main on GitHub.
  2. We tell Operator: "go to /opt/crew-log, git pull, pm2 restart crew-log".
  3. Operator confirms it's live.

If something about the deploy needs to change (new env var, new port, new dependency that needs build tools), call it out in the commit message and in the PR description — Operator reads commit messages when deciding how to deploy.


Schema migrations (post-v0.2)

v0.2 ships the production schema. Once it's in prod, do not change a column type, drop a column, or rename a table without writing a migration. There is no migration framework yet because there's no installed base — the moment the stakeholder is using it for real, that changes.

When you need to alter the schema, the convention going forward is:

  1. Pick a sequential number based on the highest existing migration in src/migrations/ (create the directory if it doesn't exist yet).
  2. Write src/migrations/NNN_short_name.sql containing the change, wrapped so it's idempotent (CREATE TABLE IF NOT EXISTS, ALTER TABLE guarded by a PRAGMA table_info check, etc.).
  3. Add a CREATE TABLE IF NOT EXISTS schema_migrations (version TEXT PRIMARY KEY, applied_at TEXT) in src/db.js if it isn't there yet, then INSERT OR IGNORE and run any unapplied .sql files at boot, in order, inside a single transaction.
  4. Call out the migration in the commit message so Operator backs up data/crew.db before pm2 restart.

Pure additive changes (a new table, a new nullable column) are low-risk — write the migration, ship it, move on.

Destructive changes (drop column, change type, rename) are high-risk — follow the SQLite "create new table, copy data, drop old, rename" pattern inside a transaction. Test the migration locally on a copy of the prod DB before pushing.

Never delete data/crew.db to "fix" a schema problem in prod. That was fine pre-launch; it's not now.


Programmatic clients (Operator)

Operator (the AI Employee app) lives on the same VPS and may want to call crew-log on behalf of TRS staff (e.g. log hours from a Telegram message, pull a weekly summary, archive a finished job). The contract for this:

  • Operator authenticates the same way a browser does: POST /api/login with the shared PIN, then keeps the signed session cookie for subsequent calls. The cookie is HttpOnly + Secure in prod; an axios instance with withCredentials: true and a tough-cookie jar handles it cleanly.
  • Operator's tools should live in Operator's repo (src/tools/crew_log_*.js), not in this repo. crew-log just exposes the REST API; it doesn't know about any specific consumer.
  • The PIN belongs in Operator's .env (or its settings DB) under a key like CREW_PIN. Don't add a second copy here.

If Operator ends up making a lot of calls and the cookie state becomes awkward, the right fix is to add an opt-in Authorization: Bearer <token> path in src/auth.js that bypasses the cookie. That's a small, well-scoped v0.3 task — don't pre-build it.


Do not

  • Add TypeScript or type annotations
  • Add a build step / bundler / framework
  • Add deployment infrastructure (PM2 configs, Dockerfiles, nginx, certbot scripts) — that's Operator's job
  • Hardcode the PIN, the URL, or any secret
  • Use exec() for anything user-controllable (no shell access from this app)
  • Add a second auth method without removing the first — keep auth simple
  • Refactor working code "to be cleaner" unless asked
  • Add comments that narrate what code does (only explain why when non-obvious)

Commit style

  • One logical change per commit
  • Subject line in imperative mood: fix: ..., feat: ..., refactor: ...
  • If a change affects the API contract or env vars, update README.md in the same commit
  • Never amend a pushed commit. Never force-push to main.

Open items / known limitations of v0.2

Pick from these when asked, in priority order set by Farhad:

  • Bearer-token auth for programmatic clients (Operator). Currently every caller has to PIN-login and ride a cookie. A CREW_API_KEY env var that unlocks an Authorization: Bearer shortcut would be a small src/auth.js change. Defer until Operator actually needs it.
  • Per-foreman PINs / accounts. The schema is ready (real foremen table with ids); auth still needs splitting from one shared PIN to a per-foreman map.
  • Date-range filtering UI (the API already supports ?from=&to= — front-end just needs the controls).
  • Inline field-level validation messages (replace the ugly browser-native tooltips like the step mismatch error).
  • Edit history / audit trail (who created/changed each row, when).
  • "Today" filter button (default view to today in America/Chicago).
  • Sortable columns on desktop.
  • Better empty-state UX (illustration, not just text).

Key file reference

File Contents
README.md Overview, API, schema, deployment expectations
AGENTS.md This file — instructions for AI assistants
BUGS.md Open bugs (create when first one is found)
src/server.js Express app, all routes (entries + jobs + foremen)
src/db.js SQLite schema and connection (jobs, foremen, entries)
src/auth.js PIN auth, rate limiter, cookie session
src/validation.js Input validation, name normalization, near-match suggestions
public/index.html Markup (entry modal + manage-lists modal + pin gate)
public/app.js Client state, picker component, manage-lists logic, fetch calls
public/styles.css All styles, including picker, manage modal, mobile, print
.env Local secrets (gitignored)
data/crew.db Local SQLite database (gitignored)