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
+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