Skip to content

Commit 0e4b909

Browse files
committed
docs: tighten README, demo copy, and ignore local agent dirs
1 parent 9eb9ea0 commit 0e4b909

6 files changed

Lines changed: 107 additions & 151 deletions

File tree

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,13 @@ coverage/
5050
# scratch / MCP tooling dirs
5151
.playwright-mcp/
5252
mcps/
53+
54+
# local agent / IDE tooling (do not ship in the product tree)
55+
.agents/
56+
.claude/
57+
.cursor/
58+
.grok/
59+
skills-lock.json
60+
MEMORY.md
61+
ERRORS.md
62+
.mcp.json

README.md

Lines changed: 58 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,171 +1,117 @@
11
# Unhold
22

3-
**Guided case workspace for bank-account and UPI restrictions in India.**
3+
**Organise a bank-account or UPI freeze case in India. Draft a letter. You send it.**
44

5-
Live: [unhold.live](https://www.unhold.live) · Demo (no account): [unhold.live/demo](https://www.unhold.live/demo)
6-
7-
Unhold turns a freeze into a structured file: what happened, which papers you have, what to ask next, and a letter you can copy or print. **You send everything.** The product never emails a bank, police station, or regulator, and never claims an account will be released.
5+
| | |
6+
|---|---|
7+
| Live | [unhold.live](https://www.unhold.live) |
8+
| Demo (no account) | [unhold.live/demo](https://www.unhold.live/demo) |
9+
| Start a case | [unhold.live/start](https://www.unhold.live/start) |
10+
| Open a saved case | [unhold.live/open-case](https://www.unhold.live/open-case) |
11+
| Health | [unhold.live/healthz](https://www.unhold.live/healthz) |
812

913
[![CI](https://github.com/thribhuvan003/unhold/actions/workflows/ci.yml/badge.svg)](https://github.com/thribhuvan003/unhold/actions/workflows/ci.yml)
1014
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
1115

12-
---
16+
Unhold is a case file for a restricted account: facts, papers, next steps, and a letter you can copy or print. **Nothing is emailed to a bank, police station, or regulator on your behalf.** No unfreeze guarantee.
1317

14-
## For reviewers (≈3 minutes)
18+
---
1519

16-
1. Open the [worked example](https://www.unhold.live/demo) at a phone width.
17-
2. Read the server-only data boundary in [`supabase/migrations/20260713194008_server_only_data_boundary.sql`](supabase/migrations/20260713194008_server_only_data_boundary.sql) and the escalation gates in [`lib/escalations/proof-gates.ts`](lib/escalations/proof-gates.ts).
18-
3. From a clean checkout: `pnpm install --frozen-lockfile && pnpm verify`.
20+
## Try it in three minutes
1921

20-
That path exercises lint, TypeScript strict mode, unit + contract tests, the no-auto-send guard, and a production build.
22+
1. Phone width → open the [demo](https://www.unhold.live/demo).
23+
2. Skim the safety boundary: [`docs/PRODUCT_AND_SAFETY.md`](docs/PRODUCT_AND_SAFETY.md).
24+
3. From a clean clone: `pnpm install --frozen-lockfile && pnpm verify`.
2125

2226
---
2327

24-
## What it does
28+
## What you get
2529

26-
| Area | Behaviour |
30+
| Step | What happens |
2731
| --- | --- |
28-
| Intake | Short questions → structured case facts. Guest flow with a recovery code; no mandatory signup. |
29-
| Evidence | Private storage, SHA-256 confirm, consent-gated extraction, human review when confidence is low. |
30-
| Drafts | Case-aware letter text from recorded facts. Citations limited to a maintained allowlist. |
31-
| Escalation | Later steps stay locked until required earlier proof exists (deterministic gates). |
32-
| Follow-up | Idempotent jobs, monitored deadlines, full case history across devices. |
33-
| Control | Copy / print / open-mail only. Nothing is sent on the user’s behalf. |
32+
| Start | Short questions. Guest session + recovery code. No forced signup. |
33+
| Papers | Private upload, hash check, readable/unreadable flags. You still re-read every file. |
34+
| Letter | Draft from your answers. You fill blanks, then copy / print / open mail yourself. |
35+
| Later steps | Locked until earlier send-proof is on file. |
36+
| Follow-up | Reminders only if you opt in. Full history if you reopen the case. |
3437

35-
Product language and non-negotiables: [`docs/PRODUCT_AND_SAFETY.md`](docs/PRODUCT_AND_SAFETY.md).
38+
If someone is losing money to active fraud right now: **1930**.
3639

3740
---
3841

39-
## Safety boundary
40-
41-
Unhold is a document-organisation tool, not a law firm, bank, or government service. Freeze rules differ by authority, bank, and facts. The UI labels uncertainty, attaches sources and review dates to legal positions, and avoids release guarantees or fixed timelines.
42-
43-
If someone is in immediate danger of fraud loss, the public cyber helpline in India is **1930**.
44-
45-
---
42+
## How it is built
4643

47-
## Architecture
48-
49-
```mermaid
50-
flowchart LR
51-
Browser["Browser: session + UI"] --> API["Next.js API routes"]
52-
API --> DB["Supabase Postgres"]
53-
API --> Store["Private object storage"]
54-
API --> Queue["agent_jobs queue"]
55-
Queue --> Agents["Intake · Verifier · Drafter · Bundle · Escalator"]
56-
Monitor["Cron + on-demand kick"] --> Queue
57-
Agents --> Gates["Zod + redaction + proof gates"]
44+
```text
45+
Browser → Next.js (app + /api/v1) → Supabase (Postgres + private storage)
46+
↘ agent_jobs (classify / OCR / draft)
5847
```
5948

60-
**Data plane**
61-
62-
- Browser Supabase client is used for **auth only**.
63-
- Application tables and RPCs are not granted to `anon` / `authenticated`.
64-
- Authorised server routes use the service role after owner / collaborator / operator checks.
65-
- Evidence and sealed bundles live in private buckets; uploads are size- and type-constrained.
66-
67-
**Agent plane**
68-
69-
- Jobs are idempotent, claimed with a DB lock, retried with bounded backoff, and dead-lettered after max attempts.
70-
- Model output is untrusted until it passes schema validation, citation controls, redaction, and the relevant proof gate.
71-
- Deterministic templates keep the core letter path usable when a provider is down.
72-
73-
**Upload / OCR path (latency)**
74-
75-
- Large phone photos are resized in the browser before PUT.
76-
- Confirm returns after integrity checks; vision OCR runs after the response (`after()` + queue kick).
77-
- Vision inputs are downscaled server-side; HEIC/WebP are normalised to JPEG when needed.
78-
79-
### Model routing
80-
81-
| Workload | Primary | Fallback |
82-
| --- | --- | --- |
83-
| Text (classify / draft) | Groq `openai/gpt-oss-120b` | NVIDIA `meta/llama-3.3-70b-instruct` |
84-
| Vision (document OCR) | Groq `qwen/qwen3.6-27b` | NVIDIA `minimaxai/minimax-m3` |
85-
| Embeddings (retrieval) | NVIDIA `nvidia/nv-embedqa-e5-v5` | Template path (no model) |
86-
87-
### Stack
49+
- Browser talks to Supabase for **auth only**. Case data goes through server routes.
50+
- Jobs are claimed with a DB lock, retried with backoff, and reclaimed if a worker dies.
51+
- Templates cover the letter path when a model provider is down.
8852

8953
| Layer | Choice |
9054
| --- | --- |
91-
| App | Next.js 16 (App Router), React 19, TypeScript strict |
55+
| App | Next.js 16, React 19, TypeScript strict |
9256
| Data | Supabase Postgres + private Storage |
93-
| Jobs | DB queue; GitHub Actions every 5 minutes + request-time kick |
94-
| Edge | Vercel, region `bom1` (Mumbai) |
95-
| i18n | English + Hindi (`next-intl`) |
96-
| Limits | Upstash Redis when configured; in-memory fallback locally |
57+
| Host | Vercel `bom1` (Mumbai) |
58+
| Jobs | DB queue + GitHub Actions every 5 minutes + request kick |
59+
| Languages | English + Hindi |
9760

98-
Hobby-plan cron limits and external drain wiring: [`docs/DEPLOY_VERCEL_HOBBY.md`](docs/DEPLOY_VERCEL_HOBBY.md).
61+
Deploy notes for Hobby cron limits: [`docs/DEPLOY_VERCEL_HOBBY.md`](docs/DEPLOY_VERCEL_HOBBY.md).
9962

10063
---
10164

102-
## Local setup
65+
## Local
10366

104-
**Requires:** Node.js ≥ 22.14, pnpm 10.12.1.
67+
Node ≥ 22.14 · pnpm 10.12.1
10568

10669
```bash
10770
git clone https://github.com/thribhuvan003/unhold.git
10871
cd unhold
10972
pnpm install --frozen-lockfile
110-
cp .env.example .env.local
111-
# fill local/test values only — never commit a filled env file
73+
cp .env.example .env.local # local values only — never commit secrets
11274
pnpm dev
11375
```
11476

115-
Environment catalogue: [`config/VERCEL_ENV_KEYS.md`](config/VERCEL_ENV_KEYS.md).
116-
Apply SQL under `supabase/migrations/` in order against your project before exercising auth or storage.
117-
118-
---
119-
120-
## Verification
77+
Env keys: [`config/VERCEL_ENV_KEYS.md`](config/VERCEL_ENV_KEYS.md).
78+
Apply `supabase/migrations/` in order before testing auth or storage.
12179

12280
```bash
123-
pnpm verify # lint · types · unit · contracts · no-auto-send · build
124-
pnpm test:e2e:smoke # Playwright smoke (desktop + mobile)
125-
pnpm test:e2e # full browser suite
81+
pnpm verify # lint · types · unit · contracts · no-auto-send · build
82+
pnpm test:e2e:smoke # Playwright smoke
12683
```
12784

128-
CI runs the same deterministic checks on every PR and on pushes to `main`, plus secret scanning and smoke tests. Do not treat a green badge as a substitute for reading the safety contract.
129-
130-
Health (JSON, no auth): `GET https://www.unhold.live/healthz` or `/api/v1/health`.
131-
13285
---
13386

134-
## Repository layout
87+
## Layout
13588

13689
```text
137-
app/ App Router UI + versioned /api/v1 routes
138-
components/ Mobile-first case UI
139-
lib/agents/ Intake, verifier, drafter, evidence, monitor, escalator
140-
lib/api/ Authz, errors, request contracts
141-
lib/escalations/ Deterministic proof gates
142-
lib/jobs/ Enqueue, claim, process, on-demand kick
143-
lib/evidence/ Hash, mime, compress, vision prep
144-
supabase/migrations/ Schema, RLS, storage, privileges
145-
tests/unit/ Domain + golden fixtures
146-
tests/contract/ API and migration contracts
147-
tests/e2e/ Journeys + a11y smoke
148-
docs/ Product safety, deploy, research notes
90+
app/ UI + /api/v1
91+
components/ Case UI
92+
lib/ Domain, jobs, evidence, gates
93+
supabase/migrations/ Schema and policies
94+
tests/ unit · contract · e2e
95+
docs/ Product safety, deploy
96+
messages/ en + hi copy
14997
```
15098

15199
---
152100

153-
## Status and limitations
101+
## Limits (honest)
154102

155-
Public beta **0.1.0**. Expect sharp edges; report them via issues or [security advisories](SECURITY.md).
103+
Public beta **0.1.0**.
156104

157-
- The user must verify extracted fields and send every letter themselves.
105+
- You verify extracted fields and send every letter.
158106
- Process guidance can go stale; dated sources are not personal legal advice.
159-
- Model reads can be wrong; low-confidence or inconsistent files are flagged for review.
160-
- Whether an account moves, and when, is decided only by the bank and the ordering authority.
107+
- Document reads can be wrong; low-confidence files are flagged.
108+
- Only the bank and ordering authority decide if an account moves.
161109

162110
---
163111

164-
## Security and contributing
112+
## Security · contributing
165113

166-
- Vulnerabilities: private report only — [`SECURITY.md`](SECURITY.md).
167-
- Changes: [`CONTRIBUTING.md`](CONTRIBUTING.md). Prefer safer, clearer, more reliable behaviour over new claims.
168-
169-
---
114+
- Vulnerabilities: [`SECURITY.md`](SECURITY.md) (private report).
115+
- Patches: [`CONTRIBUTING.md`](CONTRIBUTING.md). Prefer safer and clearer over louder claims.
170116

171-
MIT License · Maintained by [thribhuvan003](https://github.com/thribhuvan003)
117+
MIT · [thribhuvan003](https://github.com/thribhuvan003)

app/[locale]/demo/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default async function DemoPage({ params }: Props) {
5353
const path = getUnfreezePath("cyber_upi_chain", locale);
5454

5555
return (
56-
<div className="mx-auto flex max-w-[430px] flex-col gap-4 lg:max-w-2xl">
56+
<div className="u-case-shell mx-auto flex w-full flex-col gap-4">
5757
{/* Lead with the killer insight in plain words, not a caveat — all three
5858
comprehension reviewers (HRs + a normal user) said the value was buried
5959
under a legal briefing. State it first; let the cards below be the proof. */}

components/evidence/PapersChecklist.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const DEFAULT_CORE_DOCS: PaperDocDef[] = [
5757
{
5858
type: "freeze_sms",
5959
label: "Freeze SMS or notice",
60-
why: "Shows when and how the bank froze it. The starting point for any letter. AI also explains what it means.",
60+
why: "Shows when and how the bank froze it. Starting point for any letter.",
6161
term: "freeze intimation",
6262
article: "a",
6363
kindLabel: "freeze SMS or notice",

messages/en.json

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
},
6060
{
6161
"title": "Add the papers for your case",
62-
"desc": "Clear photos or PDFs of what your checklist asks for. Unhold checks readability; you still review the original."
62+
"desc": "Clear photos or PDFs of what your checklist asks for. You still re-read every file yourself."
6363
},
6464
{
6565
"title": "Get a draft letter + proof pack",
@@ -417,38 +417,38 @@
417417
},
418418
"DemoPage": {
419419
"eyebrow": "Worked example · made-up facts",
420-
"title": "See the input, the decision, and the draft — before starting a case.",
421-
"intro": "This is a sample walkthrough, not your case. It shows what Unhold organises, what it suggests next, and the kind of letter draft you review. It does not decide who can release funds or predict an outcome.",
422-
"sampleNote": "Sample only: Asha Rao · ₹1,800 shown as affected · total restriction after an unexpected UPI credit · NCRP reference available. Nothing is sent.",
423-
"truthTitle": "Read this once so the demo does not mislead",
420+
"title": "What Unhold produces — before you start a real case.",
421+
"intro": "Sample walkthrough only. Not your case. Shows facts → next step → a letter draft you would review. Does not decide who can release funds.",
422+
"sampleNote": "Sample: Asha Rao · ₹1,800 held · total freeze after an unexpected UPI credit · NCRP on file. Nothing is sent.",
423+
"truthTitle": "So the demo is not misleading",
424424
"truthPoints": [
425-
"This letter is a draft template filled with sample facts — not a lawyer-signed document.",
426-
"Your real letter uses your answers (freeze type, role, authority type, amount). Court and tax drafts ask different things than cyber drafts.",
427-
"You always send the letter yourself. Unhold never emails the bank or police."
425+
"The letter is a filled template with sample facts — not a lawyer document.",
426+
"A real draft uses your answers. Court and tax freezes ask different things than cyber freezes.",
427+
"You send every letter yourself. Unhold never emails the bank or police."
428428
],
429-
"proofTitle": "What changed from input to output",
429+
"proofTitle": "From input to output",
430430
"proofSteps": [
431431
{
432432
"label": "INPUT",
433-
"text": "Asha’s stated facts, restriction type, amount, date and available NCRP reference."
433+
"text": "Asha’s facts: freeze type, amount, date, NCRP reference."
434434
},
435435
{
436436
"label": "DECISION",
437-
"text": "Ask for the written scope, ordering authority and official next step; do not assume the branch can release it."
437+
"text": "Ask in writing for scope, ordering authority, and the official next stepdo not assume the branch can release the money."
438438
},
439439
{
440440
"label": "OUTPUT",
441-
"text": "A concise L1 draft, a relevant paper checklist and dated official contact sources for Asha to verify."
441+
"text": "L1 draft, paper checklist, dated official contact sources for her to verify."
442442
}
443443
],
444-
"sourcesTitle": "Official links and dated contact checks",
445-
"sourcesDesc": "Contacts are linked to their published source. Legal outcomes still depend on the actual order, facts and jurisdiction:",
444+
"sourcesTitle": "Official links (dated)",
445+
"sourcesDesc": "Each contact points at its published source. Outcomes still depend on the real order and facts:",
446446
"sourceLabel": "SBI official page",
447-
"letterTitle": "Sample draft letter (you would review and send)",
448-
"letterDesc": "Summary first. Open the full draft to see every line. On a real case you fill any blanks before sending.",
449-
"letterFactNote": "total restriction · ₹1,800 · unexpected UPI credit · NCRP reference provided",
450-
"ctaTitle": "Your case won’t look like Asha’s.",
451-
"ctaBody": "The path and letter adapt to what you enter. After you start, write down the recovery code — that is how you reopen the case on another phone.",
447+
"letterTitle": "Sample draft (review before send)",
448+
"letterDesc": "Summary first. Open the full draft for every line. On a real case you fill blanks before sending.",
449+
"letterFactNote": "total freeze · ₹1,800 · unexpected UPI credit · NCRP provided",
450+
"ctaTitle": "Your case will not match Asha’s.",
451+
"ctaBody": "Path and letter follow what you enter. After you start, save the recovery code — that is how you reopen on another phone.",
452452
"startCta": "Start my own case",
453453
"backHome": "Back to home"
454454
},

0 commit comments

Comments
 (0)