# Verification plan

Nothing in this repository has been run against a real Gmail, a real MTA, a real
Docker host or a real Android device. This is the plan for finding out what
breaks, in the order that finds the expensive problems first.

Budget roughly a day for stages 1–3, which is where the risk actually is.

**The single most important test is 2C.** It is the one failure mode that is
silent — everything looks fine, and text quietly gets mangled. Do not skip it.

---

## Stage 1 — Server (30 minutes)

Do this before touching any client. Every client failure is ambiguous until the
server is known good.

| # | Step | Pass |
|---|---|---|
| 1.1 | Load `schema.sql`, `seed.sql`, `upgrade-01`, `upgrade-02` in that order | No errors |
| 1.2 | Open `/admin/login.php`, sign in as `admin@example.com` / `sahi-change-me` | Dashboard loads |
| 1.3 | Change that password immediately | — |
| 1.4 | Open **Health** | Every row green or amber, none red |
| 1.5 | Press **Test the model** | Reports token usage |
| 1.6 | **Access keys** → issue an extension key | Key shown once |
| 1.7 | `php tests/run.php` | 52 passed, 0 failed |

Then check the engine directly, with no browser in the way:

```bash
curl -s -X POST https://YOUR_SERVER/api/check.php \
  -H 'Content-Type: application/json' \
  -H 'X-Sahi-Key: YOUR_KEY' \
  -d '{"text":"pls revert back on the informations i recieved"}'
```

You should get four issues back. If this fails, nothing downstream can work and
everything else is a waste of time.

**Known fragile:** if `mbstring` is missing, checking dies on the first
non-ASCII character. Health catches it; check Health first.

---

## Stage 2 — Extension

### 2A. Test page first

Load `extension/` unpacked at `chrome://extensions`, set the server and key in
options, and grant it access to `test-page.html`.

Type into the textarea: `pls revert back on the informations i recieved`

- Four underlines appear about a second after you stop typing
- Clicking one opens a card with the correction
- Accept replaces exactly that word — nothing either side of it moves
- The badge count drops by one

Repeat in the contenteditable box. Same behaviour.

**If underlines appear in the wrong place**, stop. That is the offset pipeline,
not the site — go to 2C and fix it before touching Gmail.

### 2B. Gmail, then WhatsApp

The order matters: Gmail's editor is more forgiving, so a failure there means
something fundamental, while a failure in WhatsApp alone means Lexical.

In a Gmail compose window, type the same sentence and check the same four
things. Then in WhatsApp Web, in a chat with yourself.

**Watch for the specific failure this design exists to catch:** the correction
appears applied, but the send button stays greyed out, or the edit reverts on
the next keystroke. That means `execCommand('insertText')` did not take and the
editor rejected the write. It is the single most likely thing to break, and it
will look almost right.

**When selectors miss entirely** — no badge, no underlines, nothing at all —
that is a markup change, not a bug. Find the field's selector in devtools and
publish it from **Site selectors** in the admin. Do not edit `adapters.js`; that
is what the hotfix screen is for.

### 2C. The offset torture test — do not skip

Paste each of these and confirm every underline sits exactly on the flagged
word, and that Accept replaces only that word:

```
pls revert back, i recieved the report
नमस्ते pls revert back today
Done 🎉 pls recieve it, i will check
Patient Telmisartan 40mg — pls revert back
```

Emoji and Devanagari are the cases where PHP's byte counting and JavaScript's
UTF-16 counting disagree. The server converts, and `tests/run.php` covers it,
but the browser is where it is proven.

An underline two characters to the left of where it belongs is not cosmetic. It
means Accept will cut the wrong text out of a real message, and nobody will
notice until it has happened a hundred times.

Line four should produce **one** issue, not two. If `Telmisartan` is flagged,
the dictionary is not loading — check `seed.sql` ran.

---

## Stage 3 — Embed widget

Open `server/embed/demo.html`, paste an embed key into the script tag, and add
that origin under **Access keys**.

- Underlines in the textarea, the input, and the contenteditable
- `data-sahi-ignore` on the patient name field means it is never checked
- The drug names in the Advice box are not flagged
- The badge menu rewrites the whole field

Then on one real HIS screen:

| Check | Why it matters |
|---|---|
| Field found at all | Server-generated IDs are mangled; the selector must be `[id$='txtNotes']` |
| Still works after a partial postback | UpdatePanel replaces the DOM; the widget should rediscover the field |
| RadEditor fields underline | Its text is in a same-origin iframe |
| **A correction survives Save** | RadEditor keeps its own copy — if the edit is not synced back it looks applied and is silently lost |

That last row is the one that costs you a client. Make the correction, save the
record, reload it, and confirm the correction is still there.

---

## Stage 4 — Office add-in

Sideload `office/manifest.xml` after replacing the host and generating a GUID.

- **Word:** Check writing lists the issues. Accept selects the phrase in the
  document so you can see what changed, replaces only that range, and Ctrl+Z
  undoes it. Formatting around it survives.
- **Outlook, plain text:** corrections apply to the body.
- **Outlook, formatted:** the add-in should **refuse** and tell you why. If it
  instead replaces the body and eats your signature, that is a bug — report it.

---

## Stage 5 — Android

Expect compilation errors on first build; nothing here has been through a
compiler.

Once it runs:

- Settings → server and key → **Save and connect** reports the organisation
- Select text in WhatsApp → Sahi in the selection toolbar → Replace puts the
  corrected text back in the message box
- Enable the keyboard, type in WhatsApp, tap the **Sahi** key: corrections
  appear as chips; tapping one replaces that word only
- **Open a banking app or any password field.** The Sahi bar must be gone
  entirely. If it is visible, stop and fix it before anyone else installs this.

Run the 2C strings through the phone too. `InputConnection` uses UTF-16 like the
server, so they should apply cleanly — but prove it.

---

## Stage 6 — Unattended

```bash
php bin/cron.php nightly --dry-run
php bin/cron.php weekly --dry-run
```

Then send one for real to yourself and **open it in Outlook**, not just Gmail.
Outlook's rendering is the strict one and the digest has never been through a
real mail path. Check that it is readable with images blocked, since many
hospital mail clients do that by default.

---

## Stage 7 — Docker

```bash
cp deploy/env.sample .env
docker compose -f deploy/docker-compose.yml --env-file .env up -d --build
```

- All three containers healthy
- `/admin/health.php` reports LanguageTool responding
- With `SAHI_ANTHROPIC_KEY` empty and the org's AI switch off, checking still
  works on rules and LanguageTool alone

That last line is the one you will put in a contract. Verify it yourself before
you write it down.

---

## Recording it

Keep a single sheet: stage, pass or fail, and for failures the exact text typed
and what happened. Two lines of that is worth more than a paragraph of
description, because almost every failure here reproduces from the input.

Send failures back with the input string. Most will be a selector or a small
fix.
