Files
PersonalBlog/docs/rich-content-and-media.md
T
2026-08-27 08:18:13 +00:00

43 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Rich content & media
Images and video are first-class; embeds/charts/diagrams are a founding requirement (v1.1 §29-A restores what v1.0 omitted).
## Media pipeline (§29.1–29.4, v1.1)
- **Storage port** `ObjectStorage` (put/get/stat/delete); initial adapter stores on `/var/lib/eppp/media`; future adapters target S3/R2/MinIO without changing content records (content stores media IDs, never absolute paths).
- **Upload validation (§29.1):** size checked before reading the body; content type from magic bytes (client header/extension advisory only); SHA-256 on ingest (duplicate checksum returns the existing record); generated content-addressed keys, never the original filename.
- **Image processing (§29.2):** every accepted image re-encoded before serving (original bytes never served); metadata stripped, orientation baked in then dropped (GPS/camera serial never reach the public volume); derivative widths 480/800/1200/1600/2400 (never upscaled), AVIF + WebP per width + one universal JPEG fallback; rendered images carry `width`/`height` (zero CLS); lazy-load below the fold; immutable one-year cache headers.
- **Video posture (§29.3):** no transcoding in v1; accept MP4 (H.264 + AAC) only, reject others with a message naming export settings; probe dimensions/duration, generate a poster frame.
## Embeds — three tiers (§29-A.1)
| Tier | Mechanism | Survives provider death | Public JS |
|---|---|---|---|
| 1 Native | chart/diagram blocks rendered server-side to SVG | yes | none |
| 2 Snapshot | embed + locally cached static image | degrades to image | none |
| 3 Live | sandboxed iframe from allowlisted host, click-to-load | no | one island |
The editor steers to tier 1 whenever data belongs to the author ("paste data" beside "paste embed URL").
## Provider allowlist (§29-A.2)
`eppp_embed_providers`: exact hostnames only (no wildcards), admin-editable, every change audited. Seed: Observable, Datawrapper, CodePen, `youtube-nocookie.com`, `player.vimeo.com`, `archive.org`, plus a configurable self-hosted Grafana host. Enforced twice: in the renderer and in a CSP `frame-src` generated from the same table.
## Fetch, cache & privacy (§29-A.3)
On save of content containing an embed: resolve provider by exact host (unknown host = author-visible error); fetch oEmbed through the hardened fetch; sanitise to an iframe with allowlisted `src` + dimensions; download and re-host the thumbnail (never hotlink); refresh on a 30-day cycle. Live iframes are click-to-load; every iframe carries `sandbox`, `referrerpolicy="no-referrer"`, `loading="lazy"`, a required `title`. **No reader request reaches a third party before explicit interaction.**
## Hardened outbound fetch (§29-A.4, core)
One shared core service (oEmbed, thumbnail download, future link checker): resolve DNS first and reject private/loopback/link-local/multicast/metadata ranges; re-check after every redirect (max 3), pinning the connection to the pre-resolved address (closes DNS-rebinding); 10 s timeout, 5 MB cap; worker egress additionally restricted at the network layer. No extension ever writes its own outbound fetch (FIT-015).
## Native charts & diagrams (§29-A.5)
`core.chart` (line, bar, area, scatter; CSV/JSON props) and `core.diagram` (Mermaid), rendered server-side to a single inline `<svg>`: every chart carries `<title>`, `<desc>` and a visually-hidden data table; all colour comes from theme tokens (a literal colour in chart output is a build failure, FIT-016); renderers are pure functions (no headless browser, no client bundle, no network); limits are configuration (2000 points / 12 series default, exceeded at save time).
Ownership: blocks/UX ship as first-party extensions (`org.eppp.embeds`, `org.eppp.charts`, or one `org.eppp.rich-content`); hardened fetch + CSP generation are core (FIT-015–017).
## Metadata & syndication (§34-A, v1.1)
Core (every content type needs it): per-page `<title>`, meta description, canonical URL, Open Graph tags, JSON-LD `BlogPosting` (headline, datePublished, dateModified, author) on articles; `sitemap.xml` regenerated on publish; `robots.txt` excluding `/admin` and `/api`; `ETag`/`Last-Modified` with conditional requests. RSS remains a v0.2 extension. Open Graph images (when added) are generated server-side (SVG rasterised at publish, cached as a media object), no headless browser.