"Given the <product_requirements_document>, the <technical_specification_document>, and the <repository_structure>, identify what questions the user should know about the codebase when planning to work on this PRD.\n\n<product_requirements_document>\n# Grand Central — MVP PRD\n\n**DRI:** \n**Stakeholders:** People Ops, Partners, Engineering, Design, Data\n**Status:** Draft v1\n**Date:**\n\n---\n\n## 1) Problem statement\n\nPartners and Execs cannot quickly answer basic workforce questions because employee data is scattered across spreadsheets, lacks a single source of truth, and has incomplete history of state changes. This creates slow decisions, duplicated work, and error-prone analysis.\n\n**Evidence to collect pre-ship**\n\n* Time spent per week answering roster questions.\n* Number of duplicate or conflicting records in current Sheets.\n* Top 5 recurring questions stakeholders cannot answer reliably today.\n\n---\n\n## 2) Who is the customer\n\n* **Primary:** People Ops admin who maintains records and exports data.\n* **Secondary:** Partners/Managers who need roster and trend views.\n* **Tertiary:** Employees who look up colleague basics.\n\n**Jobs-to-be-done**\n\n* Maintain canonical person records with auditable history.\n* Import legacy data once with minimal cleanup.\n* Run simple trend analyses without a data analyst.\n\n---\n\n## 3) Goals and non-goals\n\n**Goals (MVP)**\n\n1. Create, edit, and view **person** records with append-only **changesets** and effective dates.\n2. **One-time bulk import** from Google Sheets with dedupe by work email.\n3. **Directory** with search and CSV export of the **current snapshot**.\n4. **Analytics v0:** monthly headcount, joiners vs leavers, level distribution over time; simple “level vs prior experience” view.\n5. **Security:** Google SSO, domain allowlist, audit log on writes.\n\n**Non-goals (MVP)**\n\n* Payroll, performance reviews, external HRIS sync.\n* Complex RBAC beyond Admin vs Viewer.\n* Compensation reporting UI. *Schema ready; UI deferred to v2.*\n\n**Why now**\n\n* Operational risk from spreadsheet fragmentation.\n* Quick wins unlock broader analytics later.\n\n---\n\n## 5) Requirements\n\n### 5.1 Functional\n\n* **Auth:** Google SSO; domain allowlist; logout; session security.\n* **Directory:** list with columns *Name, Work email, Level, Status, Start date*; search by name/email; link to person detail; CSV export of current snapshot.\n* **Person detail:** core fields, plus **History** tab showing changesets in reverse chronological order; show who changed what and when.\n* **Create/Edit:** forms capture effective dates; all edits append a changeset; current snapshot recomputed.\n* **Analytics v0:**\n\n * *Headcount trend* by month (active count, joineive dates.\n2. **One-time bulk import** from Google Sheets with dedupe by work email.\n3. **Directory** with search and CSV export of the **current snapshot**.\n4. **Analytics v0:** monthly headcount, joiners vs leavers, level distribution over time; simple “level vs prior experience” view.\n5. **Security:** Google SSO, domain allowlist, audit log on writes.\n\n**Non-goals (MVP)**\n\n* Payroll, performance reviews, external HRIS sync.\n* Complex RBAC beyond Admin vs Viewer.\n* Compensation reporting UI. *Schema ready; UI deferred to v2.*\n\n**Why now**\n\n* Operational risk from spreadsheet fragmentation.\n* Quick wins unlock broader analytics later.\n\n---\n\n## 5) Requirements\n\n### 5.1 Functional\n\n* **Auth:** Google SSO; domain allowlist; logout; session security.\n* **Directory:** list with columns *Name, Work email, Level, Status, Start date*; search by name/email; link to person detail; CSV export of current snapshot.\n* **Person detail:** core fields, plus **History** tab showing changesets in reverse chronological order; show who changed what and when.\n* **Create/Edit:** forms capture effective dates; all edits append a changeset; current snapshot recomputed.\n* **Analytics v0:**\n\n * *Headcount trend* by month (active count, joiners, leavers).\n * *Level mix* over time (banded junior/mid/senior).\n * *Level vs prior experience* scatter.\n* **Import:** one-time CSV importer for legacy Sheets; idempotent; validation report; dedupe by work email; mapping guide.\n\n### 5.2 Data model (MVP)\n\n* **Person**: id, firstName, lastName, workEmail (unique), phone?, role, level, status, startDate, endDate?, priorWorkExperienceYears.\n* **Changeset**: id, personId, field(s) changed, newValue, effectiveDate, author, createdAt.\n* **Status enum**: FULLTIME, CONTRACTOR, EXEC, PARTNER, RESIGNED.\n* **Compensation (v2-ready)**: CompChange(personId, amount, currency, effectiveDate, notes).\n* **Snapshot rule**: latest effective changes per field as of “now”.\n\n### 5.4 UX principles\n\n* Defaults fast data entry over perfect taxonomy.\n* Make history obvious before saving edits.\n* Show what changed, by whom, and when.\n\n---\n\n## 8) Risks and mitigations\n\n* **Import correctness** → schema mapping guide, dry-run, row-level report.\n* **Duplicate records** → unique email constraint; surface potential duplicates; merge flow later.\n* **Bad effective dates** → inline validation; preview of resulting history.\n* **OAuth misconfig** → automated env checks in CI; clear runbooks.\n\n---\n\n## 9) Acceptance tests (MVP)\n\n1. **Create person**: Authenticated user submits required fields → person appears in directory; audit entry created; event `person_created` emitted.\n2. **Edit with history**: Update level with effective date → new changeset stored; History tab shows entry; snapshot updated.\n3. **Import**: Run importer on validated CSV → ≥95% rows ingested; reconciliation report shows any rejects with reasons.\n4. **Export**: Click Export on directory → CSV downloads with one row per current person; header spec matches appendix.\n5. **Analytics**: Open Analytics → monthly headcount, joiners vs leavers, and level mix charts render from production data; “level vs experience” view loads.\n6. **Security**: Unauthenticated user → redirected to login; export requires Admin.\n\n---\n\n## 10) Open questions\n\n* Exact mapping of legacy Sheets to entities and enums.\n* Admin vs Viewer permissions beyond export.\n* Compensation governance and who can view amounts in v2.\n* Do managers need edit rights or view-only in v1?\n\n---\n\n## 11) Appendix\n\n**A. CSV header spec (current snapshot)**\n`firstName,lastName,workEmail,phone,role,level,status,startDate,endDate,priorWorkExperienceYears`\n\n**B. Glossary**\n\n* **Changeset**: append-only record of a field change with an effective date.\n* **Snapshot**: latest effective value per field at a point in time.\n* **Headcount**: number of active employees in a period.\n* **Joiners/Leavers**: counts of start/end effective events in a period.\n\n**C. Decision log**\n\n* Compensation UI deferred to v2; schema included now.\n* Unique workEmail enforced; no merge UI in v1.\n* SQLite acceptable for MVP, to be revisited post-M6.\n\n\n</product_requirements_document>\n\n<technical_specification_document>\n# Modelling\n\nDescribes how to model entities over time.\n\n## Goals\n\nModel an entity with a set of known fields which changes over time such that:\n\n1. The time when a change occurred is tracked separately from when the change\n was recorded\n\n2. The state of an entity (a view) can be queried for any point in time to\n fetch the values of fields in that entity at that point in time\n\n3. A change that occurred in the past should affect all views of the entity\n that are requested after that point in time\n\n4. The end user should not have to declare changes but instead just edits the\n entity as a whole at a given point in time\n\n## CRDTs\n\nRef: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\n\nTypically used for resolving conflicts between information created from two different sources, we are able to use CRDTs for our usecase by treating a change made to an entity's history as a change from a disconnected user that's converging with other changes.\n\n### LWW Element Set\n\nRef: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type#LWW-Element-Set_(Last-Write-Wins-Element-Set)\n\nAn LWW Element Set is a well known CRDT. It consists of an 'add set', 'remove\nset' and a time stamp for each element in it.\n\nThe timestamp will be the time at which the change occurred. The value is a map\nwith all the fields and corresponding values that are updated. In our case, the\nremove set does not exist since nothing is meant to be removed, just edited. We\nmay mimic a remove by updating a field with a null value if necessary.\n\n## Schema\n\nThe schema to store this in SQLite.\n\n```sql\n\ncreate table changes(\n -- This would be the personId in our case\n entityId text,\n\n -- Time when the event occurred\n timestamp datetime,\n\n -- json with fields that have changed and the corresponding values\n -- eg. {'name': 'Name1', 'age': 22}, {'age': 29}\n value text,\n);\n\n```\n\nHere’s a concise tech spec for “Grand Central.” It captures current stack choices and the target architecture, flags risks, and proposes guardrails.\n\n# 1) Overview\n\n* Purpose: Single source of truth for employee data. Visual analysis now. Workflow automation later.\n* Users: Internal staff. Auth via Google.\n* Non-goals: Public API, multi-tenant SaaS, heavy analytics.\n\n# 2) Core Requirements\n\n* CRUD for people and changesets.\n* Authenticated access only.\n* Visual graphs of trends.\n* Low-ops deploy. Small team ownership.\n* SQLite durability with simple backups. Path to Postgres when needed.\n\n# 3) Tech Stack (current)\n\n* Web framework: Remix (React + SSR). TypeScript everywhere.\n* Styling: Tailwind.\n* ORM: Prisma.\n* DB: SQLite (file URL defaults to `file:/data/sqlite.db`).\n* Auth: `remix-auth` + Google OAuth.\n* Charts: D3 components.\n* Testing: Vitest for unit, Cypress for E2E.\n* Packaging: Docker multi-stage build. Separate Datasette image for read/admin.\n* Runtime: Node process started by `start.sh` which runs `prisma migrate deploy` then `remix-serve`.\n* Ops: Procfile targets (`web`, `datasette`). Fly volume mounted at `/data`. Healthcheck at `/healthcheck`.\n Sources: repo Dockerfiles, README, Fly config. \n\n# 4) Architecture\n\n## 4.1 Logical components\n\n* **Remix app**\n\n * Server routes: SSR HTML, actions for mutations, loaders for reads.\n * Session: Cookie session storage.\n * Auth: Google Strategy. On first login auto-creates user.\n * Data access: Prisma client.\n * Graphs: D3 line and multiseries components rendered client-side.\n* **SQLite**\n\n * Deployed at `/data/sqlite.db` with a persistent volume.\n* **Datasette**\n\n * Read and exploratory UI at `/datasette/`.\n * Insert bot capability gated by token for controlled writes.\n* **Reverse proxy**\n\n * Local dev uses Caddy via `docker-compose.local.yml` to route to Node and Datasette.\n\n## 4.2 Runtime topology\n\n* **Fly.io app**\n\n * Service ports: 80/443 -> internal 8080.\n * Volume mount `/data` for DB durability.\n * Concurrency limits tuned to \\~20–25 connections.\n* **Datasette sidecar**\n\n * Runs as a separate process/container when enabled.\n * Base URL set to `/datasette/`.\n\n## 4.3 Request flow\n\n1. Browser → Remix route.\n2. Loader checks session. If needed, Google OAuth redirects.\n3. Loader/Action hits Prisma → SQLite.\n4. Response rendered via SSR + client hydration.\n5. Optional: Datasette used for admin/read operations.\n\n# 5) Configuration and Secrets\n\n* Required env:\n\n * `SESSION_SECRET`\n * `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`\n * `APP_URL` (for OAuth callback)\n * `DATABASE_URL` (defaults to `file:/data/sqlite.db`)\n * `PORT` (default 8080)\n * `INSERT_TOKEN` for Datasette insert bot\n* Secret handling: Store in Fly secrets or CI secrets. Never in repo. Rotate quarterly.\n\n# 6) Build and Deploy\n\n* **Build**: Multi-stage Docker builds:\n\n * `deps` installs dev deps, `production-deps` prunes, `build` runs `prisma generate` and `npm run build`, final image copies `/build`, `/public`, `.prisma`.\n* **Start**: `start.sh` applies migrations then serves.\n* **Datasette image**: Minimal Python Alpine, installs `datasette`, `datasette-insert`, `datasette-auth-tokens`, serves `/data/sqlite.db`.\n* **CI/CD**: GitHub Actions push images to registry and deploy. Branching model: `dev` → staging, `main` → production.\n Note: README mentions DigitalOcean registry and droplet; repo also contains `fly.toml`. Standardize on one target (recommend Fly for volume + healthchecks).\n\n# 7) Data Model and Migrations\n\n* ORM-driven schema via Prisma.\n* Migration policy:\n\n * All schema changes via Prisma migrations committed to VCS.\n * On boot, `prisma migrate deploy` runs. Fail fast if migration cannot be applied.\n* Backups:\n\n * Nightly snapshot of `/data/sqlite.db` to object storage.\n * Pre-migration snapshot in CI for production.\n\n# 8) Observability\n\n* Logs: stdout structured JSON from app. Retain 14–30 days in platform logs.\n* Health: `/healthcheck` HTTP 200.\n* Metrics (proposed):\n\n * Basic RED metrics via runtime counters.\n * Optionally expose `/metrics` for scrape.\n\n# 9) Security\n\n* HTTPS enforced.\n* Cookie `__session`: httpOnly, sameSite=lax, secure in production.\n* OAuth scopes: minimal email identity.\n* RBAC (future): roles for viewer, editor, admin.\n* Datasette:\n\n * Base URL scoped under `/datasette/`.\n * Token-gated inserts via `datasette-auth-tokens`.\n * Default deny policy except for authenticated actor.\n\n# 10) Performance and Scale\n\n* SQLite fits current workload. Single writer, low read concurrency.\n* Concurrency guard at app level to avoid lock thrash.\n* Thresholds to move to Postgres:\n\n * > 10 req/sec sustained writes.\n * Multi-region rollout.\n * Complex reporting joins.\n* Migration path:\n\n * Replace `DATABASE_URL` to Postgres.\n * Run Prisma `migrate deploy` on Postgres.\n * Cutover via maintenance window.\n\n# 11) Testing Strategy\n\n* Unit: Vitest, `happy-dom` environment.\n* E2E: Cypress with `login()` helper and `cleanupUser()` per test file.\n* CI gates: typecheck, lint, unit, E2E against mocked services.\n\n# 12) Risks and Mitigations\n\n* **SQLite file lock contention** → keep mutations small; queue bulk imports; consider Postgres if contention spikes.\n* **Dual deployment targets confusion** → choose Fly or DO. Remove unused manifests.\n* **Secrets leakage** → enforce CI secret scanning; restrict Procfile usage in prod if not needed.\n\n# 13) Open Questions\n\n* Is Datasette exposed in production or only behind VPN? If exposed, add auth proxy.\n* Which registry is canonical: DOCR or Fly’s? Align CI/CD.\n* Backup RTO/RPO targets?\n\n# 14) Acceptance Criteria\n\n* Auth works with Google in staging and prod.\n* App boots with `prisma migrate deploy` and serves on 8080 behind TLS.\n* `/datasette/` loads with token auth.\n* E2E tests pass in CI on each PR to `dev` and `main`.\n\nSources: project Dockerfiles, Procfile, README, Fly config, package.json, Prisma bootstrap scripts. \n\n\n</technical_specification_document>\n\n<repository_structure>\n.dockerignore\n.env.example\n.eslintrc.js\n.github/workflows/deploy.yml\n.gitignore\n.gitpod.Dockerfile\n.gitpod.yml\n.npmrc\n.prettierignore\nDockerfile\nDockerfile.datasette\nProcfile\nREADME.md\napp/charts/line-chart.ts\napp/charts/multiseries-chart.ts\napp/components/graph.tsx\napp/components/person.tsx\napp/db.server.ts\napp/entry.client.tsx\napp/entry.server.tsx\napp/form-action.server.ts\napp/form.tsx\napp/models/graph.server.test.ts\napp/models/graph.server.ts\napp/models/graph.ts\napp/models/person.server.test.ts\napp/models/person.server.ts\napp/models/person.ts\napp/models/user.server.ts\napp/root.tsx\napp/routes/auth/google.tsx\napp/routes/auth/google/callback.tsx\napp/routes/graphs/levels.tsx\napp/routes/graphs/people.tsx\napp/routes/healthcheck.tsx\napp/routes/index.tsx\napp/routes/login.tsx\napp/routes/logout.tsx\napp/routes/people.tsx\napp/routes/people/$id.tsx\napp/routes/people/csv.tsx\napp/routes/people/new.tsx\napp/session.server.ts\napp/utils.test.ts\napp/utils.ts\ncypress.config.ts\ncypress/.eslintrc.js\ncypress/e2e/smoke.cy.ts\ncypress/fixtures/example.json\ncypress/support/commands.ts\ncypress/support/create-user.ts\ncypress/support/delete-user.ts\ncypress/support/e2e.ts\ncypress/tsconfig.json\ndatasette.metadata.json\ndatasette/metadata.json\ndeploy/grand-central/Caddyfile\ndeploy/grand-central/Caddyfile.local\ndeploy/grand-central/docker-compose.caddy.yml\ndeploy/grand-central/docker-compose.local.yml\ndeploy/grand-central/docker-compose.staging.yml\ndeploy/grand-central/docker-compose.yml\ndoc/modelling.md\nfly.toml\nmocks/README.md\nmocks/index.js\npackage-lock.json\npackage.json\nprisma/generated/zod/index.ts\nprisma/migrations/20230120092252_init/migration.sql\nprisma/migrations/20230125174823_add_changeset/migration.sql\nprisma/migrations/20230128042107_update_changeset/migration.sql\nprisma/migrations/20230223201415_drop_person_table/migration.sql\nprisma/migrations/20230407085725_drop_user_password/migration.sql\nprisma/migrations/migration_lock.toml\nprisma/schema.prisma\nprisma/seed.ts\npublic/favicon.ico\npublic/grand-central.jpg\nremix.config.js\nremix.env.d.ts\nscripts/import_changesets.ts\nstart.sh\ntailwind.config.js\ntest/global-setup.ts\ntest/setup-test-env.ts\ntsconfig.json\nvitest.config.ts\n</repository_structure>\n