commit 641fbdadeb6aa888bf4bedb601e936707955f2f9 Author: Weei Han Date: Thu Jul 30 14:35:52 2026 +0800 Add spec pack diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..626d439 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,54 @@ +# CLAUDE.md — Claude Code Implementation Guide +## SOP Governance Tool (Stage 1, single-tenant) + +You are pair-programming with a **coding beginner** who is the product owner and domain expert (3PL logistics). Optimize for his learning and for maintainability, not for cleverness. + +## Project context +- Full specs live in `/docs` — READ THE RELEVANT SPEC before implementing anything: + `01-PRD` (scope/roles) · `02-FRD` (requirements + acceptance criteria) · `03-architecture` (stack & decisions AD-1…AD-6) · `04-database-schema` (DDL + RLS) · `05-api-spec` (routes & payloads) · `06-ui-ux` (screens & tokens) · `07-development-plan` (milestones M0–M7, incl. M6.5 incident log). +- Stack is FIXED: Next.js App Router + TypeScript + Tailwind + shadcn/ui + Supabase + Vercel + Anthropic API. Do not introduce other databases, ORMs (use supabase-js), state libraries, or i18n frameworks. +- Single org today, but `org_id` + RLS on every table is non-negotiable (AD-1). + +## How to work with me (the human) +1. **Explain as you go.** After writing code, give a 3–5 line plain-English summary of what it does and why. If I ask "explain", teach me line-by-line. +2. **Small steps.** Implement ONE checklist item at a time, then tell me exactly how to test it in the browser before moving on. +3. **Ask before:** adding any dependency, changing the schema, deviating from a spec, or deleting more than ~30 lines. +4. **Never** put secrets in client code, disable RLS "temporarily", or mock away auth to make something work. +5. When something fails, show me the error, your diagnosis in plain English, then the fix. + +## Conventions +- TypeScript strict; server components by default, `"use client"` only when needed. +- All workflow mutations go through `/app/api/*` route handlers using the service-role client; state transitions ONLY via `lib/workflow.ts`; every mutation writes `audit_log` in the same transaction (AD-3). +- Reads use the user-scoped client under RLS. +- Errors: return `{ error: { code, message } }` with correct HTTP status (see 05-api-spec §5). +- Commits: after each working step, imperative plain English ("Add SOP publish route with version snapshot"). +- Files: kebab-case; components PascalCase; one component per file. + +## Session plan (follow in order; ~2 hours each) +Before each session: read the matching milestone in `docs/07-development-plan.md` and the FRD items it cites. After each: run the ✅ checklist with me, commit, push (Vercel auto-deploys). + +- **S1 (M0):** Scaffold app, Tailwind, shadcn init; Supabase clients in `lib/supabase/`; env wiring; deploy hello world. +- **S2 (M0):** Apply `db/schema.sql` + `db/policies.sql` + `db/seed.sql`; verify tables + RLS deny-by-default with an anon query test. +- **S3–4 (M1):** Login page + session; profiles + JWT claims hook; role-redirect middleware; route guards. +- **S5 (M1):** Users & Departments admin pages (invite, role change, deactivate). +- **S6–7 (M2):** SOP list (filters/search) + create dialog; SOP detail shell with status badge. +- **S8–9 (M2):** Structured editor: sections nav, step cards (add/reorder/delete), photo upload to Storage, autosave. +- **S10 (M3):** `lib/workflow.ts` (canTransition, nextVersion) with unit tests; submit route + validation. +- **S11 (M3):** decide (approve/reject) + approvals queue page; publish route with version snapshot transaction; versions tab. +- **S12 (M3):** `lib/audit.ts`; wire audit into all mutations; audit page + per-SOP history tab. +- **S13 (M4):** Assign-departments dialog + route; staff `/my-sops` list (mobile shell, needs-ack vs acked). +- **S14 (M4):** Staff SOP viewer (mobile, language switcher) + acknowledge flow (typed-name sheet, immutability, re-ack on new version) + share link. +- **S15 (M5):** Dashboard cards + per-SOP acknowledgement table + department table + overdue badges. +- **S16 (M5):** Ack report endpoint + CSV export; audit CSV. +- **S17 (M6):** `lib/ai/` (client, prompts, JSON parser with one retry); `/api/ai/draft` + editor modal; ai_log + daily rate limit. +- **S18 (M6):** `/api/ai/translate` + translations tab + reviewed flag + staff-viewer fallback rule (safety → English until reviewed). +- **S19 (M6.5):** Incidents table + policies; POST /api/incidents; staff report flow (viewer "Report a problem" + My SOPs button, severity buttons, photo upload). +- **S20 (M6.5):** Admin incident list with filters + detail; review/close route with resolution note + "requires SOP revision" jump; SOP detail Incidents tab; dashboard open-incidents card. +- **S21 (M7):** Security checklist from `docs/03-architecture.md §6` — verify each item with me and show the evidence. +- **S22 (M7):** Real data seed for the company; fix onboarding frictions; final walkthrough of `docs/02-FRD.md` acceptance criteria. + +## Definition of done (any task) +Works in the browser · matches its FRD acceptance criteria · no TypeScript errors · explained to me in plain English · committed · deployed preview checked. + +## Scratch — next session goal +(Human writes 1–2 lines here before each session.) diff --git a/README.md b/README.md new file mode 100644 index 0000000..56223ac --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# SOP Governance Tool — Spec Pack (Stage 1) + +Complete specification suite for the single-tenant SOP governance tool, written to be dropped into the project repository and used by **Claude Code**. + +## Reading order (human) +| # | File | What it answers | +|---|------|-----------------| +| 1 | `docs/01-PRD.md` | What we're building, for whom, and what we're deliberately NOT building | +| 2 | `docs/02-FRD.md` | Every functional requirement with testable acceptance criteria (FR-x.x) | +| 3 | `docs/03-architecture.md` | Stack, directory layout, and the 6 architectural decisions (AD-1…AD-6) | +| 4 | `docs/04-database-schema.md` | Full Postgres DDL, JSON shapes, RLS policy matrix, seed plan | +| 5 | `docs/05-api-spec.md` | Every mutation & AI route with request/response examples and error codes | +| 6 | `docs/06-ui-ux.md` | Screen-by-screen specs, design tokens, mobile staff experience | +| 7 | `docs/07-development-plan.md` | M0–M7 milestones, weekly rhythm, scope-cut order | +| 8 | `CLAUDE.md` | Session-by-session implementation plan + working rules for Claude Code | + +## How to use with Claude Code +1. Create your repo, copy this whole folder in (`CLAUDE.md` at repo root, `docs/` alongside). +2. Open Claude Code in the repo. It reads `CLAUDE.md` automatically. +3. Say: **"Read CLAUDE.md and docs/07-development-plan.md, then start Session S1."** +4. One session at a time. Run each milestone's ✅ checklist before moving on. + +## Scope guardrails +- **Stage 1 only:** one company, fixed roles, 1-level approval, minimal incident log (report + review, no escalation workflows), no billing, no WhatsApp API (share links manually). +- **Stage-2 ready by design:** `org_id` on every table + RLS from day one — multi-tenant SaaS later is an evolution, not a rewrite. +- If a feature isn't in `02-FRD.md`, it is out of scope. Add it to a "later" list, don't build it. + +Parent documents: BRD v1.1 · Solo-Founder Roadmap (Stage 0 → 1 → 2). diff --git a/docs/01-PRD.md b/docs/01-PRD.md new file mode 100644 index 0000000..88412e6 --- /dev/null +++ b/docs/01-PRD.md @@ -0,0 +1,74 @@ +# Product Requirements Document (PRD) +## SOP Governance Tool — Stage 1 (Single-Tenant) + +**Version:** 1.0 · **Scope:** Stage 1 of the Solo-Founder Roadmap · **Parent docs:** BRD v1.1, Solo-Founder Roadmap + +--- + +## 1. Purpose + +A web application for ONE company (the founder's own 3PL warehouse) that replaces the manual Stage-0 concierge workflow: create governed SOPs with AI assistance, approve them, distribute them to staff in their language, and collect signed acknowledgements with an audit trail. + +This is a learning-and-validation build. It must be simple enough for a beginner developer (working with Claude Code) to build in 3–4 months at 10–15 hrs/week, while laying foundations (org_id everywhere, RLS) that make the Stage-2 SaaS an evolution rather than a rewrite. + +## 2. Problem Statement + +Today (Stage 0), the founder delivers SOP governance manually: AI chat for drafting, Word templates, WhatsApp for distribution, screenshots + a Google Sheet for acknowledgement records. It works and earns money, but it doesn't scale past ~3 clients and consumes founder hours on clerical work. + +## 3. Goals & Non-Goals + +### Goals +1. Replace the manual workflow for the founder's own warehouse (tenant #1). +2. SOP lifecycle: Draft → Submitted → Approved → Published, with version history. +3. Staff view SOPs on their phones in their language (EN / BM / 中文) and acknowledge with one tap. +4. Dashboard showing acknowledgement % per SOP and per department. +5. AI assistance: draft an SOP from a description; translate an approved SOP. +6. Every governance action recorded in an audit log. +7. Staff can report operational incidents (problem + severity + photo, optionally linked to an SOP); approver/admin review and close them, feeding SOP revisions. + +### Non-Goals (explicitly out — do not build) +- Multi-tenancy UI, billing, subscriptions, self-serve signup +- WhatsApp Business API integration (Stage 1.5 — staff access via a link for now) +- Checklists, evidence capture, training/quizzes, RACI, flow diagrams +- Incident escalation chains, SLA timers, incident alerts (the incident log is capture-and-review only) +- Custom roles, SSO, configurable approval chains (fixed 1-level approval only) +- Native mobile app (mobile-first web only) +- DOCX/PDF import (paste text instead) + +## 4. Users & Roles (fixed set) + +| Role | Who | Can do | +|---|---|---| +| **Admin** | The founder | Everything: manage users/departments, edit/approve/publish any SOP, view dashboard & audit log | +| **Approver** | Ops manager / dept head | Approve or reject submitted SOPs in their scope; view dashboard | +| **Editor** | Supervisors | Create/edit drafts, submit for approval, use AI draft/translate | +| **Staff** | Frontline workers | View published SOPs assigned to them (own language), acknowledge | + +One user has exactly one role (keep it simple). Admin can change a user's role. + +## 5. Core User Stories + +1. As an **Editor**, I describe a process in plain language and get an AI-drafted SOP I can edit, so drafting takes minutes not days. +2. As an **Editor**, I submit a draft; the **Approver** gets it in their queue and approves or rejects with a comment. +3. As an **Admin**, publishing an approved SOP creates version 1.0 (or increments), assigns it to selected departments, and freezes that version's content. +4. As a **Staff** member, I open a link on my phone, see the SOP in my preferred language with large readable steps, and tap "I have read and agree" — recording my name, the version, language, and timestamp. +5. As an **Admin**, I see per-SOP and per-department acknowledgement % and can export the acknowledgement list. +6. As an **Approver**, I can view any prior version of an SOP and who approved it. +7. As a **Staff** member, when something goes wrong on the floor, I report it from my phone in under a minute — with a photo — and the **Admin** sees which SOPs keep generating problems and revises them. + +## 6. Success Metrics (Definition of Done for Stage 1) + +- Founder's warehouse runs on it: ≥ 20 published SOPs, ≥ 80% staff acknowledgement. +- An SOP goes from idea → published in under 1 hour including approval. +- Zero manual screenshot/Sheet work remains for tenant #1. +- A non-technical Approver and a Staff member can use it with no training beyond one WhatsApp message. +- Audit log answers: who created, edited, approved, published, acknowledged — with timestamps. + +## 7. Constraints & Principles + +- **Stack is fixed:** Next.js (App Router) + Supabase + Vercel + Anthropic API. No substitutions. +- **Beginner-maintainable:** prefer boring, well-documented patterns over clever ones. +- **org_id on every table** from day one, with RLS. Single-tenant today, SaaS-ready tomorrow. +- **AI is server-side only.** No API keys in the browser. AI never auto-publishes. +- **Mobile-first for Staff screens;** desktop-first for Admin/Editor screens. +- **English UI** for admin screens; SOP *content* is multilingual (EN/BM/ZH). diff --git a/docs/02-FRD.md b/docs/02-FRD.md new file mode 100644 index 0000000..09fb8e9 --- /dev/null +++ b/docs/02-FRD.md @@ -0,0 +1,97 @@ +# Functional Requirements Document (FRD) +## SOP Governance Tool — Stage 1 + +Each requirement has an ID, description, and acceptance criteria (AC). Build modules in order M1→M7 (see 07-development-plan.md). + +--- + +## M1 — Authentication & Users + +**FR-1.1 Login/logout** — Email + password via Supabase Auth. +AC: Wrong password shows a friendly error; session persists across refresh; logout clears session. + +**FR-1.2 User management (Admin)** — Admin creates users (name, email, role, department, preferred_language), deactivates users, changes roles. +AC: Deactivated users cannot log in; a Staff user cannot open any admin page (redirected). + +**FR-1.3 Departments (Admin)** — Create/rename departments; assign users to one department. +AC: Deleting a department with users is blocked with a clear message. + +**FR-1.4 Role guard** — Every page and API route enforces the role matrix in the PRD §4. +AC: A Staff JWT calling an admin API receives 403; verified by test. + +## M2 — SOP Repository & Editor + +**FR-2.1 SOP record** — Fields: code (e.g. WH-PICK-001), title, department, category, owner (user), status, review_months (3/6/12/24). +AC: Code is unique; list view filters by department, status, category; search by title/code. + +**FR-2.2 Structured editor** — Sections: Purpose, Scope, Roles & Responsibilities, Procedure (ordered steps, each step = text + optional photo), Exceptions, Safety Notes, Required Records. Purpose + Procedure mandatory. +AC: Cannot submit for approval with a mandatory section empty; steps can be added, reordered, deleted; photos upload to Supabase Storage and preview inline. + +**FR-2.3 Autosave drafts** — Editor autosaves every 10s or on blur. +AC: Refresh mid-edit loses at most 10 seconds of typing. + +## M3 — Governance: Workflow, Versions, Audit + +**FR-3.1 Status flow** — draft → submitted → approved → published; rejection returns submitted → draft with a required comment. Published SOPs edited again create a NEW draft version; the published version stays live until the new one is published. +AC: Illegal transitions (e.g. draft → published) are impossible via UI and API. + +**FR-3.2 Approval (1 level)** — Approver/Admin sees a queue of submitted SOPs; approves or rejects with comment. +AC: Approval records approver id, timestamp, decision, comment; Editor is notified in-app. + +**FR-3.3 Versioning** — First publish = 1.0. Each subsequent publish increments: minor edit → 1.1, "major revision" checkbox → 2.0. Published version content is immutable (stored snapshot). +AC: Version history list shows every version with date, publisher, change note; opening an old version renders its frozen content. + +**FR-3.4 Audit log** — Append-only records for: sop.created, sop.edited, sop.submitted, sop.approved, sop.rejected, sop.published, sop.assigned, ack.recorded, user.created, user.role_changed. +AC: Log entries store actor, action, entity, timestamp, and detail JSON; Admin can filter by SOP and export CSV. No update/delete path exists. + +**FR-3.5 Review reminders** — Dashboard flags SOPs past `published_at + review_months`. +AC: An overdue SOP shows a red "Review overdue" badge on dashboard and list. + +## M4 — Distribution & Acknowledgement + +**FR-4.1 Assignment** — On publish (or later), Admin assigns the SOP to one or more departments; all active Staff in those departments become assignees. New staff added to a department inherit its assignments. +AC: Assignee list is visible per SOP; unassigning a department removes pending (not completed) acknowledgement expectations. + +**FR-4.2 Staff "My SOPs" page (mobile-first)** — Staff log in and see assigned published SOPs: "Needs acknowledgement" on top, then "Acknowledged". Content renders in the staff member's preferred_language if a translation exists, else English, with a language switcher. +AC: Loads fast on a phone; steps render with large text and photos; language switch is one tap. + +**FR-4.3 Acknowledgement** — Button: "I have read and agree / Saya faham & setuju / 我已阅读并同意". Tap records user, SOP, version, language viewed, timestamp, and a typed-name confirmation field (acts as the signature). +AC: One acknowledgement per user per published version; a NEW published version resets the requirement (re-ack needed); record is immutable. + +**FR-4.4 Share link** — Each published SOP has a link Staff can open after login (paste into a WhatsApp group manually for now). +AC: Link deep-links to that SOP in the viewer for an authenticated assignee; unauthenticated users hit login first, then land on the SOP. + +## M5 — Dashboard & Reports + +**FR-5.1 Dashboard (Admin/Approver)** — Cards: total published SOPs, drafts, pending approvals, overdue reviews. Table: per-SOP acknowledgement % with progress bar; filter by department. Second table: per-department overall %. +AC: Numbers reconcile with raw data (verified by a seeded test dataset). + +**FR-5.2 Acknowledgement export** — Per SOP: CSV of assignees with acknowledged version/date/language or "outstanding". +AC: Opens correctly in Excel; includes SOP code, title, version in the header rows. + +## M6 — AI Assistance + +**FR-6.1 AI Draft** — Editor enters a plain-language description (and optionally pastes messy notes); server calls Anthropic API and returns a structured draft filling the FR-2.2 sections; Editor reviews/edits before saving. +AC: AI output populates the editor fields (never publishes anything); a failed AI call shows a retry-able error without losing the input; prompt + response stored in ai_log for cost tracking. + +**FR-6.2 AI Translate** — On an approved/published SOP, generate BM and/or ZH translations of all sections. Translations are stored per version and flagged `machine=true` until a human marks "reviewed". +AC: Publishing a new version marks old translations stale and prompts regeneration; safety-notes sections require the human "reviewed" flag before the translation is shown to Staff (English shown until then). + +**FR-6.3 AI guardrails** — All AI routes: server-side only, role Editor+, rate-limited (e.g. 20 calls/user/day), inputs capped (~8k chars). +AC: Exceeding the limit returns a clear message; the Anthropic key never appears in client bundles (verified by build inspection). + +## M7 — Operational Incident Log (minimal) + +**FR-7.1 Report an incident (staff+, any role)** — From the staff SOP viewer ("Report a problem" link) or a standalone "Report incident" button on My SOPs. Fields: description (required), severity (low/medium/high), optional photo, optional linked SOP (pre-filled when opened from a viewer; records the SOP version too). +AC: Submitting takes < 60 seconds on a phone; photo uploads to Storage; reporter, timestamp, and department are recorded automatically; confirmation screen shown. + +**FR-7.2 Incident list (approver/admin)** — Table of incidents: date, reporter, department, linked SOP, severity, status (open / reviewed / closed). Filter by status, severity, department, SOP. Detail view shows full description + photo. +AC: New incidents default to "open"; the dashboard shows an "Open incidents" count card (red if any high severity open). + +**FR-7.3 Review & close (approver/admin)** — Status transitions open → reviewed → closed, with an optional resolution note. Closing prompts: "Does this incident require an SOP revision?" — if yes, a one-click jump to that SOP's edit page. +AC: Status changes and notes are audit-logged (`incident.reviewed`, `incident.closed`); a closed incident is read-only. + +**FR-7.4 Incidents per SOP** — SOP detail page gains an "Incidents" tab listing incidents linked to that SOP across versions. +AC: An SOP with ≥3 incidents in 90 days shows a "Review suggested" hint on the dashboard. + +**Deliberately excluded (do not build):** escalation chains, assignment/ownership of incidents, SLA timers, email/WhatsApp alerts, root-cause taxonomies, client-facing incident reports. The module is a capture-and-review loop feeding SOP revisions — nothing more in Stage 1. diff --git a/docs/03-architecture.md b/docs/03-architecture.md new file mode 100644 index 0000000..87e18bf --- /dev/null +++ b/docs/03-architecture.md @@ -0,0 +1,84 @@ +# System Architecture +## SOP Governance Tool — Stage 1 + +## 1. Stack (fixed — do not substitute) + +| Layer | Choice | Why | +|---|---|---| +| Frontend + backend | **Next.js 14+ (App Router, TypeScript)** | One codebase for pages and API routes; industry standard | +| Database / Auth / Storage | **Supabase** (Postgres + Auth + Storage + RLS) | Hands a beginner the hard parts; RLS becomes Stage-2 tenant isolation | +| Hosting | **Vercel** | Push-to-deploy; free tier sufficient | +| AI | **Anthropic API** (claude-sonnet for drafting/translation) | Called ONLY from server routes | +| Styling | **Tailwind CSS + shadcn/ui** | Fast, consistent, well-documented | + +## 2. High-Level Diagram + +``` +[Browser: Admin/Editor desktop UI · Staff mobile UI] + │ (HTTPS) + ▼ +[Vercel: Next.js App] + ├─ Server Components / Pages ──────────► reads via Supabase client (RLS enforced) + ├─ Route Handlers /api/* ─────────────► writes + workflow logic (service role where needed) + │ └─ /api/ai/* ───────────────────► Anthropic API (server-side key) + ▼ +[Supabase] + ├─ Postgres (all tables, RLS policies) + ├─ Auth (email/password, JWT with role + org_id claims) + └─ Storage (bucket: sop-photos) +``` + +## 3. Key Architectural Decisions + +**AD-1: org_id everywhere, single org today.** +Every table carries `org_id`. Stage 1 seeds exactly one org row and all data references it. RLS policies already filter by org_id. Stage 2 = add org signup + billing; data layer barely changes. Do NOT skip this "because we only have one company." + +**AD-2: Roles live in a `profiles` table, mirrored into the JWT.** +`profiles(id → auth.users.id, org_id, role, department_id, preferred_language, full_name, active)`. A Postgres function copies role/org_id into JWT claims (custom access token hook) so RLS can check them cheaply. + +**AD-3: Reads through RLS; privileged writes through route handlers.** +Simple reads (my SOPs, dashboard queries) use the anon/user Supabase client and rely on RLS. Workflow mutations (submit/approve/publish/assign/acknowledge) go through `/api/*` route handlers that (a) re-verify the session and role, (b) run the transition logic in one transaction via the service-role client, (c) write the audit_log row in the same transaction. This keeps state-machine rules in ONE place. + +**AD-4: Published content is a frozen snapshot.** +`sop_versions.content` stores the full JSON of all sections at publish time. The editor works on the draft row; publish copies it into an immutable version row. Acknowledgements and translations reference the version, never the live draft. + +**AD-5: AI calls are stateless, logged, and cheap-by-default.** +One route per capability (`/api/ai/draft`, `/api/ai/translate`). Each call: validate role + rate limit → build prompt from templates in `lib/ai/prompts.ts` → call Anthropic → validate/parse JSON output (retry once on parse failure) → log tokens to `ai_log`. Use a smaller/cheaper model for translation, a stronger one for drafting. + +**AD-6: No client-side secrets, no localStorage for auth.** +Supabase handles session cookies. `ANTHROPIC_API_KEY` and `SUPABASE_SERVICE_ROLE_KEY` exist only in Vercel env vars and are only imported in server files. + +## 4. Directory Layout + +``` +/app + /(auth)/login + /(admin)/dashboard /sops /sops/[id]/edit /approvals /users /audit + /(staff)/my-sops /my-sops/[id] + /api/sops/... /api/ai/... /api/ack ... +/components (ui/ = shadcn, sop/ = editor pieces, staff/ = mobile viewer) +/lib + supabase/ (browser client, server client, service client) + ai/ (anthropic client, prompts, parsers) + workflow.ts (status machine: canTransition(), nextVersion()) + audit.ts (writeAudit()) +/db + schema.sql policies.sql seed.sql +/docs (this spec pack) +CLAUDE.md +``` + +## 5. Environments + +- **Local:** `supabase start` (local Postgres) or a free "dev" Supabase project; `.env.local` for keys. +- **Production:** Supabase project in **Singapore region** (PDPA/data-residency story), Vercel production. +- One branch (`main`) is fine for a solo dev; Vercel preview deploys per push give you a test URL. + +## 6. Security Checklist (must all be true before real staff data goes in) + +- [ ] RLS enabled on every table; anonymous access returns zero rows +- [ ] Staff role cannot read drafts, other users' profiles beyond names, or the audit log +- [ ] All /api routes verify session server-side (no trust of client-sent role) +- [ ] Storage bucket: authenticated read, Editor+ write, no public listing +- [ ] Service-role key and Anthropic key absent from client bundle (`next build` + search) +- [ ] Supabase daily backups enabled (dashboard toggle) and a restore has been tested once diff --git a/docs/04-database-schema.md b/docs/04-database-schema.md new file mode 100644 index 0000000..9045f7d --- /dev/null +++ b/docs/04-database-schema.md @@ -0,0 +1,231 @@ +# Database Schema +## SOP Governance Tool — Stage 1 (Supabase Postgres) + +Run order: `schema.sql` → `policies.sql` → `seed.sql`. All tables have `org_id` (AD-1). Timestamps are `timestamptz default now()`. + +## 1. Entity Overview + +``` +orgs ─┬─ departments ─┬─ profiles (← auth.users) + │ └─ sop_assignments + ├─ sops ─┬─ sop_versions ─┬─ acknowledgements + │ │ └─ sop_translations + │ └─ approvals + ├─ incidents (→ sops, sop_versions optional) + ├─ audit_log + └─ ai_log +``` + +## 2. Tables (DDL) + +```sql +create type user_role as enum ('admin','approver','editor','staff'); +create type sop_status as enum ('draft','submitted','approved','published','archived'); +create type lang_code as enum ('en','ms','zh'); + +create table orgs ( + id uuid primary key default gen_random_uuid(), + name text not null, + created_at timestamptz default now() +); + +create table departments ( + id uuid primary key default gen_random_uuid(), + org_id uuid not null references orgs(id), + name text not null, + unique (org_id, name) +); + +create table profiles ( + id uuid primary key references auth.users(id) on delete cascade, + org_id uuid not null references orgs(id), + full_name text not null, + role user_role not null default 'staff', + department_id uuid references departments(id), + preferred_language lang_code not null default 'en', + active boolean not null default true, + created_at timestamptz default now() +); + +create table sops ( + id uuid primary key default gen_random_uuid(), + org_id uuid not null references orgs(id), + code text not null, -- e.g. WH-PICK-001 + title text not null, + department_id uuid references departments(id), + category text, + owner_id uuid references profiles(id), + status sop_status not null default 'draft', + review_months int not null default 12 check (review_months in (3,6,12,24)), + draft_content jsonb not null default '{}'::jsonb, -- working copy (sections, steps) + current_version_id uuid, -- FK added after sop_versions exists + published_at timestamptz, + created_by uuid references profiles(id), + created_at timestamptz default now(), + updated_at timestamptz default now(), + unique (org_id, code) +); + +create table sop_versions ( + id uuid primary key default gen_random_uuid(), + org_id uuid not null references orgs(id), + sop_id uuid not null references sops(id) on delete cascade, + version_label text not null, -- '1.0', '1.1', '2.0' + content jsonb not null, -- FROZEN snapshot of sections at publish + change_note text, + is_major boolean not null default false, + published_by uuid references profiles(id), + published_at timestamptz default now(), + unique (sop_id, version_label) +); + +alter table sops + add constraint fk_current_version + foreign key (current_version_id) references sop_versions(id); + +create table approvals ( + id uuid primary key default gen_random_uuid(), + org_id uuid not null references orgs(id), + sop_id uuid not null references sops(id) on delete cascade, + decision text not null check (decision in ('approved','rejected')), + comment text, + decided_by uuid not null references profiles(id), + decided_at timestamptz default now() +); + +create table sop_assignments ( + id uuid primary key default gen_random_uuid(), + org_id uuid not null references orgs(id), + sop_id uuid not null references sops(id) on delete cascade, + department_id uuid not null references departments(id), + assigned_by uuid references profiles(id), + assigned_at timestamptz default now(), + unique (sop_id, department_id) +); + +create table acknowledgements ( + id uuid primary key default gen_random_uuid(), + org_id uuid not null references orgs(id), + sop_version_id uuid not null references sop_versions(id), + user_id uuid not null references profiles(id), + language_viewed lang_code not null, + typed_name text not null, -- the "signature" + acknowledged_at timestamptz default now(), + unique (sop_version_id, user_id) -- one ack per user per version +); + +create table sop_translations ( + id uuid primary key default gen_random_uuid(), + org_id uuid not null references orgs(id), + sop_version_id uuid not null references sop_versions(id) on delete cascade, + language lang_code not null, + content jsonb not null, + machine boolean not null default true, + reviewed_by uuid references profiles(id), + reviewed_at timestamptz, + created_at timestamptz default now(), + unique (sop_version_id, language) +); + +create table audit_log ( + id bigint generated always as identity primary key, + org_id uuid not null references orgs(id), + actor_id uuid references profiles(id), + action text not null, -- 'sop.published', 'ack.recorded', ... + entity_type text not null, -- 'sop','user','acknowledgement' + entity_id uuid, + detail jsonb, + created_at timestamptz default now() +); +-- Append-only: grant INSERT/SELECT; never UPDATE/DELETE (enforced by grants + no policy). + +create table ai_log ( + id bigint generated always as identity primary key, + org_id uuid not null references orgs(id), + user_id uuid references profiles(id), + kind text not null check (kind in ('draft','translate')), + input_chars int, output_chars int, + input_tokens int, output_tokens int, + model text, success boolean, error text, + created_at timestamptz default now() +); + +create type incident_severity as enum ('low','medium','high'); +create type incident_status as enum ('open','reviewed','closed'); + +create table incidents ( + id uuid primary key default gen_random_uuid(), + org_id uuid not null references orgs(id), + reporter_id uuid not null references profiles(id), + department_id uuid references departments(id), -- copied from reporter at insert + sop_id uuid references sops(id), -- optional link + sop_version_id uuid references sop_versions(id), -- optional, set when reported from viewer + description text not null, + severity incident_severity not null default 'medium', + photo_path text, -- Storage path, optional + status incident_status not null default 'open', + resolution_note text, + reviewed_by uuid references profiles(id), + closed_at timestamptz, + created_at timestamptz default now() +); + +create index idx_incidents_status on incidents(org_id, status, severity); +create index idx_incidents_sop on incidents(sop_id); + +create index idx_sops_org_status on sops(org_id, status); +create index idx_ack_version on acknowledgements(sop_version_id); +create index idx_audit_entity on audit_log(entity_type, entity_id); +``` + +### draft_content / content JSON shape +```json +{ + "purpose": "…", + "scope": "…", + "roles": "…", + "steps": [ { "order": 1, "text": "…", "photo_path": "sop-photos/…" } ], + "exceptions": "…", + "safety": "…", + "records": "…" +} +``` + +## 3. RLS Policy Sketch (`policies.sql`) + +Enable RLS on every table. Helper: JWT carries `org_id` and `role` claims (custom access token hook reading `profiles`). + +| Table | staff | editor | approver | admin | +|---|---|---|---|---| +| sops (read) | published + assigned to their dept only | all in org | all in org | all | +| sops (write) | — | insert/update drafts | — | all | +| sop_versions | read if assigned | read | read | all | +| acknowledgements | insert own; read own | read | read | read | +| profiles | read own + names in org | read org | read org | all | +| audit_log | — | — | read | read | +| approvals | — | read | insert/read | all | +| translations | read | read/write | read | all | +| incidents | insert own; read own | read org | read/update org | all | + +Representative policy: +```sql +alter table acknowledgements enable row level security; + +create policy ack_insert_own on acknowledgements + for insert with check ( + user_id = auth.uid() + and org_id = (auth.jwt() ->> 'org_id')::uuid + ); + +create policy ack_read on acknowledgements + for select using ( + org_id = (auth.jwt() ->> 'org_id')::uuid + and ( user_id = auth.uid() + or (auth.jwt() ->> 'role') in ('admin','approver','editor') ) + ); +``` + +Workflow mutations (publish, approve, assign) run via the **service-role client inside /api routes** so drafts can be snapshotted and audit rows written in one transaction; RLS remains the safety net for direct reads. + +## 4. Seed (`seed.sql`) +One org (""), 3 departments (Inbound, Outbound, Admin), the founder as admin, 2 test staff, 2 sample SOPs (one draft, one published v1.0 with BM translation), a few acknowledgements — enough for the dashboard to show real numbers on day one. diff --git a/docs/05-api-spec.md b/docs/05-api-spec.md new file mode 100644 index 0000000..cd4e136 --- /dev/null +++ b/docs/05-api-spec.md @@ -0,0 +1,92 @@ +# API Specification +## SOP Governance Tool — Stage 1 (Next.js Route Handlers) + +Conventions: JSON in/out · session cookie auth (Supabase) verified server-side on every route · errors as `{ "error": { "code": "FORBIDDEN", "message": "…" } }` with proper HTTP status · every mutation writes `audit_log` in the same transaction. + +Plain reads (lists, dashboard) may query Supabase directly from server components under RLS; the routes below are the **mutations and AI calls** — the state machine lives here and only here (`lib/workflow.ts`). + +## 1. SOPs + +### POST /api/sops — create draft (editor+) +Req: `{ "code":"WH-PICK-001", "title":"Order Picking", "department_id":"…", "category":"Warehouse", "review_months":12 }` +Res 201: `{ "id":"…", "status":"draft" }` +409 if code exists. + +### PATCH /api/sops/:id — update draft content/meta (editor+; only when status = draft) +Req: `{ "draft_content": { …sections… }, "title?":"…" }` → Res 200 `{ "ok":true, "saved_at":"…" }` +409 `NOT_EDITABLE` if not draft. + +### POST /api/sops/:id/submit — draft → submitted (editor+) +Validates mandatory sections (purpose, ≥1 step). Res 200 `{ "status":"submitted" }` +422 `VALIDATION` listing missing sections. + +### POST /api/sops/:id/decide — approve/reject (approver/admin) +Req: `{ "decision":"approved" | "rejected", "comment":"…" }` (comment required on reject) +approved → status `approved`; rejected → status `draft`. Writes `approvals` row. +Res 200 `{ "status":"approved" }`. + +### POST /api/sops/:id/publish — approved → published (admin) +Req: `{ "change_note":"Initial release", "is_major":false }` +Logic (one transaction): compute next label via `nextVersion(current, is_major)` (none→1.0, 1.0+minor→1.1, +major→2.0) → insert `sop_versions` snapshot of `draft_content` → set `sops.current_version_id`, `status='published'`, `published_at` → mark existing translations of prior version stale (no-op flag; new version simply has none) → audit. +Res 200 `{ "version_label":"1.0", "version_id":"…" }`. + +### POST /api/sops/:id/assign — set departments (admin) +Req: `{ "department_ids": ["…","…"] }` (replaces the set) +Res 200 `{ "assigned_departments": 2 }`. + +## 2. Acknowledgement + +### POST /api/ack (staff, self only) +Req: `{ "sop_version_id":"…", "language_viewed":"ms", "typed_name":"Ahmad bin Ali" }` +Checks: user's department is assigned to the SOP; version is the current one; typed_name non-empty. +Res 201 `{ "acknowledged_at":"…" }` · 409 `ALREADY_ACKNOWLEDGED` · 403 if not an assignee. + +### GET /api/sops/:id/ack-report (approver/admin) +Res 200: +```json +{ "sop": { "code":"WH-PICK-001", "title":"…", "version":"1.2" }, + "summary": { "assignees": 24, "acknowledged": 21, "pct": 87.5 }, + "rows": [ { "name":"Ahmad", "department":"Outbound", + "status":"acknowledged", "version":"1.2", + "language":"ms", "at":"2026-07-01T02:11:00Z" }, + { "name":"Ravi", "department":"Outbound", "status":"outstanding" } ] } +``` +`?format=csv` streams CSV of the same. + +## 3. AI + +### POST /api/ai/draft (editor+, rate-limited 20/day) +Req: `{ "description":"How we pick e-commerce orders…", "notes?":"pasted messy notes" }` +Server: prompt template → Anthropic → parse strict JSON (retry once) → log `ai_log`. +Res 200: `{ "draft": { "purpose":"…", "scope":"…", "roles":"…", "steps":[{"order":1,"text":"…"}], "exceptions":"…", "safety":"…", "records":"…" } }` +Client fills the editor; **user must Save** — the route never writes to `sops`. +429 `RATE_LIMIT` · 502 `AI_FAILED` (input preserved client-side). + +### POST /api/ai/translate (editor+) +Req: `{ "sop_version_id":"…", "languages":["ms","zh"] }` +Res 200: `{ "created":[ {"language":"ms","translation_id":"…"} ] }` (upserts `sop_translations`, `machine=true`). + +### POST /api/translations/:id/review (editor+) — human sign-off +Res 200 `{ "reviewed": true }`. Staff see safety sections in a translation only when reviewed (FRD-6.2). + +## 4. Users & Departments (admin) + +- `POST /api/users` `{ email, full_name, role, department_id, preferred_language }` → creates auth user (invite email) + profile. +- `PATCH /api/users/:id` `{ role?, department_id?, active?, preferred_language? }` +- `POST /api/departments` `{ name }` · `PATCH /api/departments/:id` `{ name }` +- `DELETE /api/departments/:id` → 409 `HAS_USERS` if occupied. + +## 5. Incidents + +### POST /api/incidents (any authenticated role) +Req: `{ "description":"Pallet label mismatch on inbound", "severity":"high", "sop_id?":"…", "sop_version_id?":"…", "photo_path?":"sop-photos/…" }` +Server sets reporter_id + department from the session profile. Res 201 `{ "id":"…", "status":"open" }` · 422 if description empty. + +### PATCH /api/incidents/:id (approver/admin) +Req: `{ "status":"reviewed" | "closed", "resolution_note?":"Racking relabeled; SOP WH-INB-002 updated" }` +Transitions: open→reviewed→closed only; closed is immutable (409 `CLOSED`). Writes audit (`incident.reviewed` / `incident.closed`). Res 200 `{ "status":"closed" }`. + +Reads (incident list, filters, per-SOP tab) go through RLS-scoped queries in server components — staff see only their own reports; approver/admin see all. + +## 6. Status Codes Summary +200/201 success · 400 malformed · 401 no session · 403 wrong role / not assignee · 404 not found in org · 409 state conflict (duplicate code, not editable, already acknowledged) · 422 validation with field list · 429 AI rate limit · 500/502 server or AI failure. diff --git a/docs/06-ui-ux.md b/docs/06-ui-ux.md new file mode 100644 index 0000000..c14b6c3 --- /dev/null +++ b/docs/06-ui-ux.md @@ -0,0 +1,87 @@ +# UI / UX Specification +## SOP Governance Tool — Stage 1 + +## 1. Design Principles + +1. **Two very different audiences:** Admin/Editor screens are information-dense desktop tools; Staff screens are huge-tap-target mobile pages usable by a warehouse worker in gloves, in a second language. Never compromise the Staff side for the Admin side. +2. **Boring and consistent beats clever.** shadcn/ui defaults, one accent color, no animation beyond built-ins. +3. **Status is always visible.** Every SOP shows a colored status badge everywhere it appears. + +## 2. Design Tokens + +- Accent/primary: teal `#028090` · success `#02C39A` · warning `#E0A100` · danger `#C0392B` +- Neutrals: slate scale; background `#F7FAF9`; card white +- Font: system UI stack (no webfont — fast on cheap phones) +- Status badges: draft = slate, submitted = amber, approved = blue, published = teal, archived = grey, review-overdue = red outline +- Staff screens: base font 18px, step text 20px, buttons ≥ 56px tall, single column + +## 3. Navigation Map + +``` +Login + ├─ ADMIN/EDITOR/APPROVER (desktop shell: sidebar + topbar) + │ ├─ Dashboard (admin, approver) + │ ├─ SOPs (list) ─ SOP detail ─ Edit draft + │ ├─ Approvals queue (approver, admin) + │ ├─ Incidents (approver, admin) + │ ├─ Users & Departments (admin) + │ └─ Audit log (admin, approver) + └─ STAFF (mobile shell: just a header + list) + ├─ My SOPs (+ Report incident) + └─ SOP viewer (+ acknowledge, + Report a problem) +``` +After login, route by role: staff → /my-sops, others → /dashboard. + +## 4. Screen Specs + +### 4.1 Login +Centered card: email, password, sign-in. Error inline. Company name/logo above. + +### 4.2 Dashboard (admin/approver) +Top row of 4 stat cards: Published · Drafts · Pending approval · Review overdue (red if >0). +Then "Acknowledgement by SOP" table: code, title, dept, version, progress bar with %, outstanding count, row click → SOP detail. Department filter dropdown. Second small table: department overall %. + +### 4.3 SOP List +Toolbar: search (code/title), filters (status, department, category), "New SOP" (editor+). +Table rows: code · title · dept · status badge · version · owner · updated. Row → detail. + +### 4.4 SOP Detail +Header: code, title, status badge, version, owner, review date (red if overdue). +Action buttons by role/status: Edit draft · Submit · Approve/Reject (opens comment dialog) · Publish (dialog: change note + "major revision" checkbox) · Assign departments (multi-select dialog) · Translate (language checkboxes) · Copy staff link. +Tabs: **Content** (rendered read-only sections) · **Versions** (list; click renders frozen snapshot) · **Acknowledgements** (the FR-4 report + CSV button) · **Translations** (per language: machine/reviewed chip, preview, "Mark reviewed") · **Incidents** (linked incident list, FR-7.4) · **History** (audit entries for this SOP). + +### 4.5 SOP Editor (draft) +Left: section list with completeness ticks. Main: one section at a time. +Procedure section: ordered step cards — textarea + photo upload + drag handle + delete; "Add step". +Top bar: autosave indicator ("Saved 10:32"), AI Draft button, Submit for approval. +**AI Draft modal:** big textarea "Describe the process in your own words", optional "Paste any existing notes", Generate → spinner → "Draft ready — review each section" fills the editor with unsaved changes highlighted. Nothing is saved until the user saves. + +### 4.6 Approvals Queue +List of submitted SOPs: code, title, submitted by/when. Open → read-only content + Approve / Reject (comment required on reject). + +### 4.7 My SOPs (STAFF, mobile) +Header: company name + user name + language switcher (EN | BM | 中文). +Section "Needs your acknowledgement" (amber cards) then "Acknowledged" (teal tick cards). Card: title, code, version, dept. Tap → viewer. + +### 4.8 SOP Viewer (STAFF, mobile) +Title + version + language switcher. Sections in reading order; steps as numbered cards with photo above text. Sticky bottom bar: +- Not yet acknowledged → button "I have read and agree" (label in current language) → confirm sheet: "Type your full name to sign" + input + Confirm → success screen with tick, timestamp, version. +- Acknowledged → grey bar "Acknowledged v1.2 · 01 Jul 2026". +If viewing a translation where safety section is unreviewed → safety section shows English with a small note "English shown pending translation review". + +### 4.9 Incidents (approver/admin) +List with filters (status, severity, department, SOP): date · reporter · dept · SOP code · severity chip (grey/amber/red) · status chip. Row → detail: full description, photo, linked SOP version, timeline. Actions: "Mark reviewed" · "Close" (dialog: optional resolution note + checkbox "This requires an SOP revision" → on save jumps to that SOP's editor). Dashboard gains an "Open incidents" card (red when any open high-severity). + +**Staff report flow (mobile):** "Report a problem" link at the bottom of the SOP viewer (pre-links SOP + version) and a button on My SOPs (no SOP link). One screen: description textarea, severity selector (three big buttons), optional photo, Submit → success tick. Labels tri-lingual via lib/i18n.ts. + +### 4.10 Users & Departments (admin) +Two tabs. Users table: name, email, role (inline select), department, language, active toggle, "Invite user" dialog. Departments: simple list + add/rename. + +### 4.11 Audit Log (admin/approver) +Filter by SOP / action / date. Table: time · actor · action · entity · detail summary. Export CSV. + +## 5. Empty / Error / Loading States +Every list has an empty state with the primary action ("No SOPs yet — create your first one" / "Nothing to approve 🎉"). Mutations show button spinners; failures show a toast with the server message and never lose form input. AI failures keep the description text in the modal with a Retry button. + +## 6. Accessibility & Language Notes +Staff-facing static labels (buttons, headers on staff screens) are tri-lingual via a tiny dictionary file (`lib/i18n.ts`) keyed by preferred_language — do NOT pull in a heavy i18n framework. Admin screens stay English. Contrast AA minimum; all tap targets ≥ 44px; photos get alt text from step text. diff --git a/docs/07-development-plan.md b/docs/07-development-plan.md new file mode 100644 index 0000000..aab0e60 --- /dev/null +++ b/docs/07-development-plan.md @@ -0,0 +1,56 @@ +# Development Plan +## SOP Governance Tool — Stage 1 + +Assumption: solo beginner + Claude Code, 10–15 focused hrs/week ⇒ ~15–17 weeks. Each milestone ends with a **demo you can show on your own phone/laptop** and a short acceptance checklist. Do not start a milestone until the previous checklist passes. + +Golden rules: +1. **One milestone at a time.** Resist building ahead. +2. **Never paste code you can't explain.** Ask Claude Code to explain until you can. +3. **Commit after every working step** with a plain-English message. +4. **Deploy to Vercel from Week 1** — always have a live URL. + +--- + +### M0 · Setup (Week 1) +Scaffold Next.js + TypeScript + Tailwind + shadcn/ui; create Supabase project (Singapore); run `db/schema.sql`; connect; deploy "hello world" to Vercel; put this `/docs` pack + `CLAUDE.md` in the repo. +✅ Live URL loads · schema visible in Supabase table editor · repo pushed. + +### M1 · Auth & Users (Weeks 2–3) — FR-1.x +Login/logout; profiles; role-based redirect; Users & Departments admin pages; JWT claims hook; role guard middleware. +✅ Staff login lands on /my-sops and CANNOT open /users (test it) · admin can invite a user and change a role · deactivated user blocked. + +### M2 · SOP Repository & Editor (Weeks 4–6) — FR-2.x +SOP list with filters/search; create SOP; structured editor with steps + photo upload; autosave. +✅ Create WH-PICK-001, add 5 steps with 2 photos, refresh mid-edit and lose ≤10s · duplicate code rejected politely. + +### M3 · Governance (Weeks 7–8) — FR-3.x +`lib/workflow.ts` state machine; submit / decide / publish routes; version snapshotting; approvals queue; version history tab; audit log writes + audit page. +✅ Full loop: draft → submit → approve → publish v1.0 → edit → publish v1.1 → open v1.0 frozen content · reject requires a comment · every action appears in audit log · illegal transitions blocked at API (curl test). + +### M4 · Distribution & Acknowledgement (Weeks 9–10) — FR-4.x +Assign departments; staff My SOPs + mobile viewer; acknowledge flow with typed-name signature; share link; re-ack on new version. +✅ On YOUR OWN PHONE: staff account sees assigned SOP in BM… acknowledges… it moves to "Acknowledged" · same user cannot ack twice · publishing v1.2 moves it back to "Needs acknowledgement". + +### M5 · Dashboard & Reports (Week 11) — FR-5.x +Stat cards; per-SOP % table; department table; overdue-review flag; CSV export. +✅ Numbers match a hand-count of seeded data · CSV opens in Excel with outstanding names listed. + +### M6 · AI (Weeks 12–13) — FR-6.x +`/api/ai/draft` + editor modal; `/api/ai/translate` + translations tab + reviewed flag; ai_log; rate limit. +✅ Describe a process in 3 sentences → usable structured draft in the editor · translate v1.x to BM + ZH, mark BM reviewed, staff viewer shows BM (safety in English until reviewed) · 21st call today politely refused · Anthropic key absent from client bundle. + +### M6.5 · Incident Log (Week 14) — FR-7.x +Incidents table + RLS; staff report flow (viewer link + My SOPs button, photo, severity); admin incident list + review/close with resolution note; SOP detail Incidents tab; dashboard open-incidents card. +✅ Staff reports a problem with photo from the viewer in <60s · approver closes it with a note and jumps to the SOP editor · closed incident is read-only · all transitions in audit log. + +### M7 · Hardening & Go-Live (Weeks 15–16) +Security checklist from 03-architecture §6 (all boxes); Supabase backups on + one restore test; seed real company data; onboard real staff via WhatsApp-shared link; fix top frictions for one week. +✅ 20+ real SOPs published · ≥80% acknowledgement · founder does ZERO manual screenshot/Sheet work for a full week. + +### Scope-cut order (if behind schedule) +Cut from the end of the week's scope, never extend weeks: 1) ZH translation (keep BM) · 2) CSV exports (screen only) · 3) incident photo + per-SOP tab (keep report + list) · 4) audit log UI (data still recorded) · 5) photo upload in steps (text only). NEVER cut: RLS, the state machine, acknowledgement immutability, the basic incident report+list. + +### Weekly rhythm (fits around warehouse + trading) +- 2 × 2-hr build sessions with Claude Code (follow CLAUDE.md session plan) +- 1 × 1-hr review: read the week's code, run the checklist, commit, deploy +- 10 min: write next session's goal at the top of CLAUDE.md scratch section