Add spec pack

This commit is contained in:
Weei Han
2026-07-30 14:35:52 +08:00
commit 641fbdadeb
9 changed files with 803 additions and 0 deletions
+54
View File
@@ -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 M0M7, 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 35 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.
- **S34 (M1):** Login page + session; profiles + JWT claims hook; role-redirect middleware; route guards.
- **S5 (M1):** Users & Departments admin pages (invite, role change, deactivate).
- **S67 (M2):** SOP list (filters/search) + create dialog; SOP detail shell with status badge.
- **S89 (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 12 lines here before each session.)
+28
View File
@@ -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` | M0M7 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).
+74
View File
@@ -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 34 months at 1015 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).
+97
View File
@@ -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.
+84
View File
@@ -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
+231
View File
@@ -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 ("<Your Company>"), 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.
+92
View File
@@ -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.
+87
View File
@@ -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.
+56
View File
@@ -0,0 +1,56 @@
# Development Plan
## SOP Governance Tool — Stage 1
Assumption: solo beginner + Claude Code, 1015 focused hrs/week ⇒ ~1517 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 23) — 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 46) — 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 78) — 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 910) — 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 1213) — 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 1516)
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