1
Rich-Content-and-Media
kpcto edited this page 2026-08-27 17:23:15 +00:00
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.