diff --git a/docs/content-pipeline.md b/docs/content-pipeline.md
new file mode 100644
index 0000000..2c192d9
--- /dev/null
+++ b/docs/content-pipeline.md
@@ -0,0 +1,41 @@
+# ๐ Content Pipeline
+
+> [!WARNING]
+> Still under active development โ this covers the current shape, not a finished architecture. Included deliberately rather than only showcasing finished work.
+
+## The intended shape
+
+```mermaid
+flowchart LR
+ Idea["Idea"] --> Draft["Drafting"]
+ Draft --> Review["Review"]
+ Review --> Approved["Approved"]
+ Approved --> Published["Published"]
+
+ Approved -.glue via automation tool.-> Storage["Draft/asset staging"]
+ Approved -.glue via automation tool.-> Blog["Published blog"]
+ Approved -.planned, not yet wired.-> Social["Social platforms"]
+```
+
+A structured content tracker moves ideas through a status pipeline, with a workflow-automation tool gluing the stages together: approved content flows through to a file-storage staging area and out to a public blog. A separate, still-incomplete leg is meant to fan approved posts out to social platforms automatically.
+
+## A deliberate constraint, not a limitation stumbled into
+
+> [!TIP]
+> The social-publishing tool is **single-origin by design** โ its frontend/backend URLs are baked to one specific public hostname at configuration time, so it deliberately has no presence in either internal DNS zone described in [`docs/dns-naming.md`](dns-naming.md); any other hostname hitting it fails CORS. This was a conscious choice, not a workaround: the tool is meant to be reached exactly one way.
+
+## A real surprise in scale
+
+> [!IMPORTANT]
+> The social-publishing tool's real deployment footprint is 8 containers, not the app+database+cache most people would guess โ it bundles its own workflow engine (mandatory for all of its background jobs, including scheduled posting itself) plus that engine's own database and search-based visibility store. A useful reminder that a tool's advertised simplicity and its actual operational footprint can be very different things.
+
+## What's actually built vs. what's still wiring up
+
+| Piece | Status |
+|---|---|
+| Content tracker โ automation โ draft storage โ blog | Most built-out, working end to end |
+| AI-assisted drafting step | Still being wired up |
+| Social-publishing fan-out | Still being wired up |
+
+---
+Related stacks: [`stacks/productivity/postiz/`](../stacks/productivity/postiz/), [`stacks/productivity/nocodb-content/`](../stacks/productivity/nocodb-content/), [`stacks/productivity/ghost/`](../stacks/productivity/ghost/)
diff --git a/docs/cron-scheduler.md b/docs/cron-scheduler.md
new file mode 100644
index 0000000..d404bef
--- /dev/null
+++ b/docs/cron-scheduler.md
@@ -0,0 +1,32 @@
+# โฑ๏ธ Cron Scheduler
+
+> [!NOTE]
+> A fleet-wide inventory of every known scheduled job โ orchestration flows, system timers, automation-managed cron, app-internal schedules โ visualized as a heatmap so a new job's time can be picked without colliding with existing load.
+
+## The heatmap
+
+```mermaid
+flowchart LR
+ Sources["Job sources
flows ยท timers ยท cron ยท app schedules"] --> Table["Structured inventory
name, host, category,
schedule, duration, resource tags"]
+ Table --> Weight["Weighted load calculation"]
+ Weight --> Grid["7ร24 grid
day ร hour"]
+ Grid --> Bands["4 color bands
quieter โ busier"]
+```
+
+Each cell's color reflects a **computed load weight**, not a raw job count: a base weight per job, plus a duration-based weight, plus resource-intensity weight (disk/CPU/network, summed if a job carries multiple tags), plus an extra weight for anything tagged as a backup job. Jobs whose time can't be resolved to a specific day and hour are excluded from the grid and listed separately rather than forced into a misleading cell.
+
+> [!TIP]
+> Clicking a populated cell expands the full job list for that slot โ name, host, category, and whether that job's schedule can actually be moved or is fixed by whatever app owns it.
+
+## Honest about its own limitation
+
+> [!WARNING]
+> **This is a manually-maintained inventory, not an auto-refreshed one.** The schema has a "last synced" column, but nothing currently populates it โ no automated process re-scans the fleet to keep this current. Entries get added or edited by hand as jobs are created or changed. Documented as a real limitation rather than presented as more automated than it actually is.
+
+## A real, unresolved bug this system surfaced
+
+> [!IMPORTANT]
+> One backup job's prune step targets a datastore name that doesn't actually exist โ not a scheduling collision, an orphaned config that's been silently failing. Two fixes are on the table (drop the dead job and let a different retention mechanism be authoritative, or repoint it at the real datastore name and let its richer retention rules take over) โ deliberately not resolved yet, tracked openly rather than silently patched over with a guess.
+
+---
+Related stacks: [`stacks/platform/dashy/`](../stacks/platform/dashy/), [`stacks/platform/nocodb/`](../stacks/platform/nocodb/)
diff --git a/docs/dashy-soc-board.md b/docs/dashy-soc-board.md
new file mode 100644
index 0000000..28a32f4
--- /dev/null
+++ b/docs/dashy-soc-board.md
@@ -0,0 +1,75 @@
+# ๐ฅ๏ธ Dashy SOC Board
+
+> [!NOTE]
+> The homelab's "one pane of glass" โ a single dashboard page backed by one large custom function, aggregating roughly a dozen distinct data sources that used to be scattered across separate pages into one glanceable board.
+
+## Fourteen data sources, one board
+
+```mermaid
+flowchart TD
+ subgraph Sources["Data sources"]
+ direction LR
+ Wazuh["SOC indexer
+ compliance API"]
+ Loki["Log aggregator
24h error counts"]
+ Prom["Metrics
CPU/RAM/disk"]
+ Slack["Alert channels
per app + per host"]
+ Orch["Orchestration APIs
execution health"]
+ Audit["Fleet config-drift
audit feed"]
+ Hyper["Hypervisor API
VM/LXC tree"]
+ Vault["Secrets platform
seal state, lease counts"]
+ SSO["SSO provider
login/failure stats"]
+ DNS["DNS server
query stats, blocklist"]
+ Backup["Backup server
job age/status"]
+ FW["Edge firewall
state table, top rules"]
+ end
+
+ Sources --> Function["One aggregation function"]
+ Function --> Board["SOC Board
client-rendered"]
+```
+
+## Layout
+
+A top row shows a 6-tier infrastructure dependency ladder alongside fleet-wide 24-hour KPIs. Below that, an "Active Issues" strip surfaces the worst findings fleet-wide, followed by a grid of per-host tiles โ worst-severity badge, error sparkline, sorted consistently. Clicking a tile expands a full detail panel: compliance and resource gauges, that host's folded-in specialty widget where relevant, and findings grouped by category.
+
+> [!TIP]
+> **Deliberately scoped**: security/error/warning signal only. Per-app uptime is handled separately by the dashboard's own native status checks on individual tiles โ the only up/down signal inside this board is *infrastructure-tier* reachability, not per-app availability. Keeping the scope narrow is what keeps the board actually scannable.
+
+## Acknowledge mechanism: client-side, no backend
+
+```mermaid
+flowchart LR
+ Finding["Finding
stable content-derived key"] --> Ack["User acknowledges"]
+ Ack --> Storage["Browser localStorage
flat map, per-browser"]
+ Storage --> Recompute["Live re-derive:
badges, issue strip, gauges"]
+ Recompute -.never touches.-> Upstream["Wazuh / Slack / any
upstream source"]
+```
+
+> [!IMPORTANT]
+> Acknowledging a finding is a **display filter only** โ it never writes back to any upstream source, is scoped per-browser rather than fleet-wide, and a full data set for every host is always kept in memory so the recompute has everything it needs regardless of which panel happens to be open. A reload in a different browser shows the finding again โ a deliberate tradeoff favoring simplicity (no backend state to manage) over persistence.
+
+## Real integration gotchas hit building this
+
+> [!WARNING]
+> A few non-obvious lessons from actually building this, in case they save someone else the debugging time:
+> - Embedding via `fetch` + `innerHTML` means injected `