# PLANROOM — publishing to Cloudflare

Two pages, no build step, no backend:

| File | Purpose |
|---|---|
| `index.html` | Everything — the landing page and the application in one file |
| `manifest.webmanifest`, `icon*.png`, `icon.svg`, `apple-touch-icon.png` | Installable app metadata and icons |
| `sw.js` | Service worker — caches the page so it opens offline |
| `_headers` | Security headers and the content security policy |

Everything runs in the browser. No database, no accounts, no telemetry.

## Deploy — drag and drop, no command line

1. Cloudflare dashboard → **Workers & Pages** → **Create** → **Pages** → **Upload assets**
2. Name the project, then drop this **folder** in — or drop the zip, which drag and drop accepts
3. **Deploy site**

You get `https://<project>.pages.dev`. The landing page shows first; the app opens at
`#app` in the same page, so there is one file to upload and one URL to share. Add a custom
domain under the project's **Custom domains** tab.

Deep links work: send someone `https://<project>.pages.dev/#app` and they land in the tool.

This folder is deliberately static only. Cloudflare's drag-and-drop upload does not compile a
`functions/` folder — Pages Functions need Wrangler or a Git integration — so nothing here
depends on one. Every feature works from a plain upload except the Jira connection, which is
covered below.

## Deploy — Wrangler, if you prefer the command line

```bash
npx wrangler pages deploy . --project-name planroom
```

Same result, and re-runs are one command.

## Jira — one small Worker

Jira Cloud sends no CORS headers for token auth, so no browser app can call it directly.
GitLab, GitHub and MCP servers need nothing extra; Jira needs a proxy. The file
`jira-proxy.worker.js` (kept outside this folder so it is never published as a static asset)
is a complete Worker you can paste into the dashboard:

1. **Workers & Pages → Create → Start from Hello World → Deploy**
2. **Edit code**, paste the file over the sample, **Deploy**
3. **Settings → Variables and Secrets**:
   - `JIRA_BASE` = `https://your-team.atlassian.net` (required)
   - `ALLOWED_ORIGIN` = `https://<project>.pages.dev` (optional, restricts who may call it)
4. In PLANROOM → **Connect** → Jira connection → **Proxy URL** =
   `https://<your-worker>.<your-subdomain>.workers.dev`

The Worker holds no credentials. Your browser sends its own Jira email and API token on each
call, and only `/rest/api/...` paths are forwarded to the one configured site.

An MCP server for Jira works instead, if you already run one.

## Models — optional

Every document generates without a model. Under **Connect → Models**:

- **Frontier, with your key**: Anthropic, OpenAI, Gemini, Groq, Mistral, OpenRouter
- **Local, no key**: Ollama, LM Studio, LiteLLM, any OpenAI-compatible endpoint. The server
  must allow this origin — set `OLLAMA_ORIGINS=https://<project>.pages.dev`, or turn on CORS
  in LM Studio's server settings.
- **On device**: WebLLM over WebGPU. Downloads once from a CDN, then runs with no network.

Add several and they form a fallback chain, tried in order.

Each model has a **temperature** slider, defaulting to **0.20** — low, because the job is
filling placeholders in a structured document rather than writing prose. Lower keeps the
answer tied to what your sheet already says; higher lets the model rewrite and invent. The
workbench has its own slider for a single draft, which overrides the saved value.

This matters most for the on-device WebGPU option: a small model at 0.7 paraphrases your
inputs away, while at 0.1–0.2 it fills the gaps and leaves your facts intact. At 0.00 the
same sheet produces the same answer every run.

## Security notes worth reading once

- Tokens and keys live in this browser's storage. Set a passphrase under **Connect** to
  encrypt them with AES-GCM; over https that works everywhere.
- A key in a browser app is exposed to anyone with the unlocked device. Scope keys narrowly.
  Local and on-device models need no key at all.
- `_headers` sets a content security policy that allows any https connection, so self-hosted
  GitLab or a private model endpoint keeps working. A stricter named-host version is in the
  file, commented out.

## Themes

The header carries a theme control that cycles **auto → light → dark**. Auto follows the
operating system and switches live when the system does. The choice is remembered on the
device, and applied before the first paint so there is no flash of the wrong theme.

Both palettes are checked against WCAG contrast ratios in the test suite: body and secondary
text clear 4.5:1, form and button borders clear 3:1. Diagrams repaint for dark mode on
screen, while the copies embedded in Word and PDF stay dark-on-white for printing.

## Updating

Re-upload the folder, or re-run the Wrangler command. Bump `CACHE` in `sw.js` when you want
existing visitors to pick up a new build immediately rather than on their next reload.
