Workflow
Distribution = der zentrale Schreibort. Alles Writing landet hier (Scripts, Essays, Notes, FAQ), wird hier verfeinert, wird per Git → CF auto-deployed, wird via Apple-Cal sichtbar.
Three places
| Place | URL | Purpose |
|---|---|---|
| Editor SPA | https://a-friend-distribution.pages.dev/ | Capture · edit · stage · reorder · ship. Auth-gated. |
| Read pages | https://a-friend-distribution.pages.dev/<Section>/<slug> | Quartz-rendered pages — was Daniel-Cal verlinkt. |
| Apple Calendar | webcal://a-friend-distribution.pages.dev/calendar.ics (master) + 6 per-format feeds | Eine VEVENT pro Scripts-Entry. Refresh alle 15min. |
Four verbs
1. Capture
Via SPA: + New → fill date, hook/title, body. Save → GitHub commit via /api/entry.
Via Claude-Skill (lokal): Skill schreibt direkt nach Distribution/<Section>/<slug>.md. Skill-Liste:
| Skill | Default-Section | Trigger |
|---|---|---|
afriend-distribution | Scripts | ”draft a script”, “neue Folge”, podcast-clip + topic |
afriend-script-pool | Scripts | ”skript pool”, Readwise-URL, 11-gate voice-safety |
compose-deseltrus | Essays (oder beliebig — sag’s der Skill) | Gedankenstrom für long-form / Substack-essay |
afriend-content-extraction | Notes/Doctrine | virality-canon, content-extraction-doctrine |
Lokale Skill-Writes laufen über Daniel’s Git: git add → commit → push → CF rebuild → live.
2. Edit / reorder
Click row → side-panel → contenteditable hook+body, meta-chips für date/time/format/stage/channels. Save → commit.
Drag-drop innerhalb gleicher Stage = Date-swap zwischen zwei Entries (canonical sort = by date).
Filter-rows nur in Scripts: Stage + Format. Group-toggle by stage / by format (persistiert).
3. Stage
Click stage-chip → popover → next stage. Backward needs confirm. Stages:
idea → scripted → ready-to-capture → captured → cut → published
(Essays haben state: draft → edit → scheduled → published — kommt im SPA-meta-row falls fm.state gesetzt.)
4. Ship
Auto (jetzt aktiv): jeder commit → CF Pages rebuilds → ~2min → live. ICS frisch → Apple Cal pulled binnen 15min. Du fierst nichts.
Fallback bei Bedarf: npm --prefix Distribution run deploy (manual via wrangler).
Check: npx wrangler pages project list → Git-Provider = GitHub ✓.
Sections
| Section | Folder | Frontmatter shape | Use |
|---|---|---|---|
| Scripts | Distribution/Backlog/ | date, time, format(A1-C1), stage, hook, channels, slug | Short-form video scripts → social channels |
| Essays | Distribution/Essays/ | date, title, state, channel(substack/blog/…), slug, tags | Long-form essays für Substack etc. |
| Notes | Distribution/Notes/ | date, title, slug, tags | Internal thinking, free-form |
| FAQ | Distribution/FAQ/ | per FAQ-template | Indiegogo FAQ |
Slug-shape:
- Scripts:
YYYY-MM-DD-<format-code>-<slug>(z.B.2026-05-28-b1-episode-2-fragmentation) - Essays/Notes/FAQ:
YYYY-MM-DD-<slug>(flat)
Format canon (Scripts only)
Source: Strategy/a-friend-content-system-v1.md. Always full name, never abbrev allein.
| Code | Full name | Cadence |
|---|---|---|
| A1 | B-Roll Organic + UGC Scenarios | 2-3 / week |
| A2 | Best Friend’s Content Creator POV | 1-2 / week |
| A3 | Carousels | 1-2 / week |
| B1 | 1M Challenge | 1 / week (Tue) |
| B2 | Reaction / Educational | 1 / week (Thu) |
| C1 | Collab-Posting (Substack) | as it comes |
Apple-Cal subscribe-URLs
Master (alles):
webcal://a-friend-distribution.pages.dev/calendar.ics
Per-format (eigene Farbe pro Cal):
webcal://a-friend-distribution.pages.dev/calendar-a1.ics
webcal://a-friend-distribution.pages.dev/calendar-a2.ics
webcal://a-friend-distribution.pages.dev/calendar-a3.ics
webcal://a-friend-distribution.pages.dev/calendar-b1.ics
webcal://a-friend-distribution.pages.dev/calendar-b2.ics
webcal://a-friend-distribution.pages.dev/calendar-c1.ics
Apple Cal → File → New Calendar Subscription → URL einfügen → name. Jeder Feed = ein Calendar = eine Farbe. Day-view zeigt automatisch die Format-Farbstreifen.
Auth
| Role | Key | Capability |
|---|---|---|
| Founder (edit) | EDIT_SECRET env, local backup /tmp/afd-edit-secret.txt | Volle CRUD über alle Sections |
| Daniel (read) | READ_SECRET env, local backup /tmp/afd-daniel-secret.txt | List + open only |
Paste-in localStorage, sent as Bearer-Header. ⏻ clears. localStorage persistiert across browser-quit per spec — keine clientseitige TTL.
Stale-secret detection + renew flow
Functions discriminieren jetzt zwischen no-secret (kein Token gesendet) und stale-secret (Token präsentiert aber matched nicht mehr env.EDIT_SECRET / env.READ_SECRET). Antwort-shape:
{
"ok": false,
"error": "stale-secret",
"renew_at": "https://dash.cloudflare.com/?to=/:account/pages/view/a-friend-distribution/settings/environment-variables",
"hint": "Token does not match current EDIT_SECRET. Renew via the URL and paste the new value."
}SPA-side: bei stale-secret bleibt der gespeicherte Token IM localStorage (kein wipe), Login-screen zeigt inline klickbare “Renew at Cloudflare” CTA mit dem renew_at URL. Bei no-secret wird localStorage geleert + neutraler “Paste your access token” prompt.
Wann passiert “stale-secret”?
- Du hast den CF env-var EDIT_SECRET rotiert (e.g., via
npx wrangler pages secret put EDIT_SECRET) - Du hast in einem anderen Browser den Token gewechselt und syncthing hat alte Backups
- Browser hat localStorage von älterer Session
Renewal-flow:
- CF Dashboard → Pages → a-friend-distribution → Settings → Environment Variables
- Edit EDIT_SECRET / READ_SECRET → set new value → Save
- Update local backup:
echo -n "<new-secret>" > /tmp/afd-edit-secret.txt - SPA: paste new value in login-input → save → localStorage updated
Local backup files at /tmp/afd-*.txt sind survival nach Mac-restart NICHT garantiert (/tmp wipes). Für dauerhaft: kopiere zu ~/.config/afd/secrets/ oder 1Password.
Safety / Remote setup
| Layer | Status | Failover |
|---|---|---|
| Local repo | ~/Projects/afriend-business/ | Syncthing → iMac mirror |
| GitHub | deseltrus/afriend-business (private) | Source of truth. Re-clone if local lost. |
| CF Pages | a-friend-distribution.pages.dev | Built from GitHub on push. Wegwerf-build artifacts. |
| Secrets | CF env (wrangler pages secret list) | Local backup unter /tmp/afd-*.txt |
| Calendar | Generated from Backlog/. Repo stale → ICS stale | Run npm run deploy to force rebuild |
Failure recovery: if CF goes wrong, repo + secrets give complete rebuild path. Manual wrangler pages deploy works any time.
Repo hygiene
Total repo size: ~21GB working, ~4GB git pack. Mass = source-assets/, _assets/, content-extraction/, design-system PNGs.
Going forward:
- Generated/derivative PNGs (campaign exports, proposal renders) → gitignore wenn regenerierbar
- Source-PNGs / hand-crafted assets → keep in git
- Large media (video stills, raw footage) → consider git-LFS or external R2 bucket
node_modules/,_quartz/public/already gitignored
No urgent action — repo functional. Optimize when push-times become painful.
What lives where
Distribution/
├─ _admin-src/index.html ← Editor SPA (dashboard at /)
├─ _functions-src/api/
│ ├─ entries.js ← GET list per section
│ ├─ entry.js ← GET/PUT/POST/DELETE one entry (sections: scripts|essays|notes|faq)
│ └─ advance.js ← POST stage advance (scripts only)
├─ _scripts/
│ ├─ prebuild.mjs ← sync-doctrine → sync-scripts → emit-ics → build-faq
│ ├─ emit-ics.mjs ← Backlog/*.md → calendar.ics + 6 per-format feeds
│ ├─ build-faq.mjs ← FAQ/*.md → Quartz-list page
│ ├─ sync-doctrine.mjs ← mirror ~/.claude/skills/… into Doctrine/
│ ├─ sync-scripts.mjs ← mirror Onari-substrate scripts/ into Scripts/
│ └─ build.mjs ← quartz build + admin SPA overlay + functions copy
├─ Backlog/<slug>.md ← Scripts entries (source of truth)
├─ Essays/<slug>.md ← Long-form (Substack etc.)
├─ Notes/<slug>.md ← Internal thinking
├─ FAQ/<slug>.md ← Indiegogo FAQ
├─ Strategy/ ← Constitution, pillars, values (Quartz-rendered)
├─ Doctrine/ ← Auto-mirror, read-only
├─ Scripts/ ← Auto-mirror, read-only
├─ Inbox/, Daily-Compile/ ← Local Obsidian only (Quartz-ignored)
└─ calendar*.ics ← 7 feeds emitted on each build
Decision rules
- One output, one hook/title, one body. Multi-version only on explicit ask.
- English forever für composed content. Founder-quotes verbatim in
<details>Original</details>. - No emoji in entries.
- No fabrication. Audience / channels / tags / pillar-links nur wenn user es gibt.
- Stage moves forward unless explicit override.
- No skill auto-chain. Skills triggern nur auf user-explicit-invoke.
- Skill writes target the right section. Scripts → Backlog/, Essays → Essays/, etc.