# Markdown Viewer — App Spec & Deployment (VIEWER-APP.md)

> ↩ **Back to canonical entry:** [SPEC.md](./SPEC.md)
> A standalone internal tool, **separate from the meowtrace product**. A single-page
> Markdown viewer for convenient browsing/sharing of `.md` files by URL.
> Linked from SPEC.md's document table; no orphaned docs.

**Status:** Deployed (live)
**Last updated:** 2026-07-19

---

## Purpose

A convenience tool for browsing and sharing Markdown files by URL. Not part of the
meowtrace SaaS product — a separate utility hosted on the staging box.

## Live

- **URL:** https://viewer.staging.meowtrace.com
- **Usage:** `https://viewer.staging.meowtrace.com/?url=<md-file-url>`

## Design / Craft

- Single-page HTML, no build step. Source: `projects/meowtrace/viewer/index.html`.
- Rendering: [`marked@12.0.2`](https://cdn.jsdelivr.net/npm/marked@12.0.2/) (GFM).
- Sanitizing: [`DOMPurify@3.1.6`](https://cdn.jsdelivr.net/npm/dompurify@3.1.6/) (XSS-safe render).
- **URL-state sync:** file loads via `?url=` query param (hash `#url=` supported as
  fallback). Each load pushes to `history`, so the address bar always holds a
  shareable link. Back/forward restores input + view. A **Share** button copies the
  current link to clipboard.
- **Relative-path rewriting:** after render + sanitize, every relative reference is
  resolved against the source file's location:

  | Link type | Behavior |
  |---|---|
  | Relative `.md` doc link (`./DEVOPS.md`) | Re-routed through the viewer (`?url=<resolved-absolute>`) so clicking stays in the viewer |
  | `.md` link with `#fragment` | Fragment preserved onto the re-routed viewer link |
  | Other relative link (`../assets/x`) | Resolved to absolute; opens in new tab (`rel=noopener noreferrer`) |
  | Relative image `src` | Resolved to absolute against the source base |
  | In-page `#anchor` | Left alone — scrolls locally |
  | Absolute / `mailto:` / `data:` | Untouched |

  Effect: loading a doc that links to sibling docs (e.g. SPEC.md → DEVOPS.md) lets you
  click through the whole document graph without ever leaving the rendered view.
- Styling: dark, metal-accented house theme; GFM tables, code blocks, focus states,
  `prefers-reduced-motion` respected.

## Known Limitation (疑)

- Markdown is fetched **client-side**. Source hosts that block cross-origin requests
  (no CORS header) will refuse to load — the status line reports it plainly. Files
  from own domains, GitHub raw, and most static hosts load clean.

---

## Deployment (running state)

| Item | Value |
|---|---|
| Host / box | staging box @ `52.74.243.182` |
| DNS | `viewer.staging.meowtrace.com` → `52.74.243.182` (A record) |
| Web root | `/var/www/viewer.staging.meowtrace.com/index.html` (owner `www-data`) |
| Source of truth | `projects/meowtrace/viewer/index.html` |
| nginx vhost | `/etc/nginx/sites-available/viewer.staging.meowtrace.com` (symlinked into `sites-enabled/`) |
| TLS | Let's Encrypt (issuer `YE1`), HTTP-01 via nginx plugin |
| Cert validity | 2026-07-19 → **2026-10-17** |
| Auto-renew | `certbot.timer` (systemd) — active |
| HTTP → HTTPS | 301 redirect (certbot `--redirect`) |
| certbot account email | `subscriber+letsencrypt@consiliumdigitalis.com` |

### Redeploy (push updated viewer)
```bash
sudo cp projects/meowtrace/viewer/index.html /var/www/viewer.staging.meowtrace.com/index.html
# static file only — no nginx reload needed
```

### Verify live
```bash
curl -sS -o /dev/null -w "HTTPS %{http_code}\n" https://viewer.staging.meowtrace.com/
```

---

## Changelog

- **2026-07-19** — Viewer built, deployed to `viewer.staging.meowtrace.com` with
  Let's Encrypt SSL + HTTP→HTTPS redirect. Specs separated from main project DEVOPS.md
  into this dedicated doc.
- **2026-07-19** — Added relative-path rewriting: relative `.md` links re-route
  through the viewer, other relative links/images resolve to absolute, `#anchors`
  scroll locally. Redeployed.
