Files
ad/CONTENT_MASTER.md

24 KiB

Content master handover: Molodetz

Audience: the coworker agent that takes over content for production Molodetz. This is not an engineering or DPP-template brief.

Fact Value
Production host https://molodetz.nl
Staging host https://staging.app.molodetz.nl (proxies 127.0.0.1:19847)
Test host https://molodetz-test.app.molodetz.nl (proxies 127.0.0.1:19849)
Local/dev http://127.0.0.1:8088 (make dev)
Git remote https://retoor.molodetz.nl/retoor/ad.git (private Gitea)
Local clone /workspace/retoor/molodetz-app
Package molodetz
Language on site English only
Owner / sole public author today retoor
Old brand Absurd Development (git repo name ad; public product name Molodetz)

DEVELOPMENT_FLOW.md in this repo is the multi-agent engineering lesson log that may feed the DPP template. It is not the content bible. Content work stays here and on the live site. Do not fold Molodetz-specific voice, memes, or intro text into /workspace/dpp/dptemplate.md.

Production admin password and SECRET_KEY live with the operator / secrets store (compose PROD_* env). Never put production passwords, API keys, or .env values in this file, in posts, in captions, or in git.

0. Repository and code changes

Private Gitea repository:

https://retoor.molodetz.nl/retoor/ad.git
  • Clone, commit, and push code (gallery wiring, captions in gallery.py, generator scripts if promoted into the repo) against that remote on branch main unless ordered otherwise.
  • Auth for git: Gitea user credentials from the operator secrets store (same class of secret as ~/.config/gitea/retoor.molodetz.nl.env on the build box). Use a one-shot credential helper or GIT_ASKPASS; never embed the password in the remote URL, in .git/config, in this file, or in chat logs.
  • Content-only work that only needs admin UI/API does not require a push. Changing published gallery files without updating GALLERY in git will drift; prefer commit+push for media registry changes.
  • Markdown files (including this one) need explicit user confirmation before commit.

0.1 Machine-readable docs for agents

Before calling write APIs, load the live docs so samples stay in sync with the registry (molodetz/docs_api/*, built by molodetz/docs_export.py):

Doc Production URL Notes
Docs home https://molodetz.nl/docs Sidebar + prose
API groups (HTML) https://molodetz.nl/docs/api/content, .../join, .../account, .../admin Admin group requires an admin session
Full download (markdown) https://molodetz.nl/docs/download.md Auto-generated: every endpoint with method, path, auth, fields, curl, request JSON, response sample
Full download (HTML) https://molodetz.nl/docs/download.html Same content, HTML
OpenAPI https://molodetz.nl/swagger Framework OpenAPI UI (/openapi.json)
Auth prose https://molodetz.nl/docs/api How sessions and API keys work

Replace the host with staging or test URLs when working on those environments. Downloads omit the Admin group unless the request is authenticated as admin.

Auth (Account group)

No JWT. No OAuth. Every HTML route also returns JSON when called with Accept: application/json.

  1. Session cookie (64 hex): POST /auth/login with username, password, optional remember. Cookie lasts 7 days (30 with remember). Sample success: {"ok": true, "redirect": "/admin", "data": null}.
  2. X-API-KEY: after login, GET /profile/api-key (member+). Sample shape {"api_key": "..."}. Destructive renew: POST /profile/api-key/regenerate.
  3. Also accepted by the app (see docs prose): Authorization: Bearer and Basic with the same credential resolution as the API key path.

Log out: POST /auth/logout. Accept terms if gated: POST /terms/accept (member).

Public read (Content group)

Use these to verify what the site shows after you publish. All auth=public.

Method Path Purpose Response sample (registry)
GET / Home + featured flyer counts {"posts": [], "flyer_count": 4, "meme_count": 22}
GET /roll Roll feed; query before cursor {"title": "Roll", "topic": "roll", "posts": [], "next_cursor": null}
GET /standard Standard feed {"title": "Standard", "topic": "standard", "posts": []}
GET /posts/{slug} One post {"post": {"title": "...", "topic": "roll"}, "author": {"username": "retoor"}}
GET /flyers Flyer gallery {"kind": "flyer", "title": "Flyers", "items": []}
GET /memes Meme gallery {"kind": "meme", "title": "Memes", "items": []}
GET /people People list {"people": [{"username": "retoor", "post_count": 5}]}
GET /people/{username} Profile + posts {"person": {"username": "retoor"}, "posts": []}
GET /health Liveness {"status": "ok", "version": "..."}

There is no public or admin HTTP API to upload a new meme/flyer file. Galleries are files under molodetz/static/media/ plus rows in molodetz/gallery.py synced on boot. Content master still uses Content GET to confirm captions and order after deploy.

Example:

curl -H 'Accept: application/json' https://molodetz.nl/roll
curl -H 'Accept: application/json' https://molodetz.nl/flyers

Join group (visitor intake; admin triage separately)

Method Path Auth Body / notes
GET /join public Form; sample {"submitted": false}
POST /join public Required name, contact; optional repo_url, message. Sample: {"ok": true, "redirect": "/join?ok=1", "data": {"uid": "0190..."}}

Join rows are never public. Content master does not invent join stories. Admins list and update them under Admin (below).

Admin group (content master write path)

Requires admin role (retoor). Prefer session cookie after UI login, or admin API key via X-API-KEY. Always send Accept: application/json for machine clients.

Posts

Method Path Purpose Body fields (registry)
GET /admin/posts List
GET /admin/posts/new New form
POST /admin/posts Create required title, body; topic example roll (also standard); status example draft (published); optional is_placeholder
GET /admin/posts/{uid}/edit Edit form path uid
POST /admin/posts/{uid} Save path uid + same fields as create
POST /admin/posts/{uid}/publish Toggle publish path uid
POST /admin/posts/{uid}/delete Soft delete path uid

Create sample response: {"ok": true, "redirect": "/admin/posts", "data": {"uid": "..."}}.

Example create (password never stored here; use operator secrets):

curl -X POST https://molodetz.nl/admin/posts \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-API-KEY: $MOLODETZ_API_KEY" \
  -d '{"title":"A title","body":"Text in **markdown**.","topic":"roll","status":"draft"}'

Home intro (settings)

Method Path Purpose
GET /admin/settings Read settings including site_intro
POST /admin/settings Save settings (form/JSON; include site_intro markdown)

Join triage

Method Path Purpose Body
GET /admin/joins List requests
POST /admin/joins/{uid}/status Set status status in open, contacted, accepted, declined
POST /admin/joins/{uid}/check Fetch repo link title via guarded outbound client

Trash (restore mistaken deletes)

Method Path Body
GET /admin/trash
POST /admin/trash/restore required stamp
POST /admin/trash/purge required stamp (destructive)

Other admin endpoints (services, backups, users, audit, stats) are operations, not day-to-day content. See /docs/api/admin or download.md while logged in as admin for the full list. Do not schedule backups or purge trash unless ordered.

Source of truth

Endpoint tables above are copied from molodetz/docs_api/content.py, join.py, account.py, and admin.py as of this handover. If the registry changes, trust live https://molodetz.nl/docs/download.md over this file and update the handover when you next touch it.


1. What the site is built for

Molodetz is a community blog roll: calm elite serif UI, slightly provocative content, markdown posts in SQLite, two galleries (flyers and memes), a people list, and a real join form.

Purpose, as stated in the home intro (site_intro, seeded from molodetz/seed/intro.md) and in SELECTION.md:

  • Hostile, data-backed elite intro: floor = current skill; the site does not flatter mediocrity.
  • Help people reach elite level, especially by learning to use AI as a craft tool, not as a substitute for judgement.
  • Divide honestly: classic hand-coders who refuse AI, vibe "coders" who refuse to learn to code, and the elite twice path (classic craft + AI).
  • Offer a path: take the profession seriously; ship; read every line the machine writes.
  • Side product context in the intro: a development environment for the elite (vim/tmux-class toolkit plus AI skill). Do not invent product claims beyond what retoor publishes.

SELECTION.md one-liner: "Calm elite serif community blog roll for retoor. English. Posts, flyers, memes, people, join intent."


2. Intentions

  • Fight mediocrity. A pass is the floor, not an ambition (seed roll post and retired Dutch assets said the same thing; English site keeps it).
  • Divide and offer a path. Look at the person next to you: replaced or replacing.
  • Promote elite programmers who embraced AI and kept craft: "elite twice" (know the code, use the machine, read every line, ship).
  • Roast classic programmers who refuse AI on principle (vim/tmux purity, "I don't need autocomplete", compilers-made-us-lazy recycling).
  • Roast vibe coders who refuse to learn to code / do not take the profession seriously (prompt-and-pray, tab-tab-accept-all, README with badges and zero tests, course before craft).
  • Facts over feelings. Pride backed by data is allowed; apology spirals are not the brand.

3. Tone

Do Do not
Harsh, rude, elitist when earned Soft HR copy, engagement bait
Facts, never invented lies Fake testimonials, fake members
Based, conservative craft ethos Hype, hustle-culture emptiness
Pro-AI and pro classic elite; especially the combo Pro-AI that excuses ignorance of code
Proud and specific God-complex apology spiral
English Dutch on the public site
Short punchy posts and one-point flyers Em-dashes, emoji decoration, slurs

Banned characters in shipped prose and captions: em dash (U+2014) and en dash (U+2013). Use commas, periods, semicolons, or hyphens.


4. What NOT to do

  • Invent members, join success stories, or co-authors. People today is effectively retoor only until real people join and are approved.
  • Selfie / photo of retoor as gallery art.
  • DevPlace, Nigel, or other named private individuals in memes or posts.
  • JWT, OAuth-as-flex, framework catalogue flex, payment/ad SDK vibes in content (engineering also forbids JWT; content should not romanticize it).
  • Dutch copy on public pages, captions, or new assets. Old Dutch media files may remain on disk under RETIRED_GALLERY but must stay unpublished.
  • Em-dashes in UI copy, posts, captions, alt text, or image text.
  • Commit secrets, .env, production passwords, API keys, or dump them into CONTENT docs.
  • Weaken factual claims in the intro without retoor's order. Do not "tone down" the hostile frame into a friendly corporate blog unless ordered.
  • Fold Molodetz voice into the DPP template or treat DEVELOPMENT_FLOW.md as a place to store meme recipes.

5. Content surfaces

Surface URL What it is Who edits
Home intro / site_intro setting (markdown). Seed: molodetz/seed/intro.md. Hostile elite manifesto. Admin Settings
Roll /roll Short notes, newest first. Topic roll. Seed placeholders exist. Admin Posts
Standard /standard Standards / taste posts. Topic standard. Seed: "Thirty-one choices. You have zero." (DPP-ish stack choices, no JWT, no JS framework). Admin Posts
Flyers /flyers Portrait gallery. Kind flyer. Featured flyer on home = first flyer by gallery position (flyer-elite-twice.jpg). Files + molodetz/gallery.py sync
Memes /memes Square gallery. Kind meme. Classic / vibe / elite sets + a few older English leftovers. Files + gallery sync
People /people, /people/retoor Authors with posts. Real accounts only; do not invent
Join /join "I'm in" form; stored for admins, never public. Visitors submit; admin triage at /admin/joins
Docs /docs Operator/API docs. Not a content marketing channel. Contributing/component docs were removed on purpose. Engineering

Seed posts (English; from molodetz/content.py)

Topic Title Role
standard Thirty-one choices. You have zero. Stack / taste standard
roll A pass is not an ambition. Floor / mediocrity
roll Finished is a feature. Ship
roll The assistant is not a senior. AI without judgement
roll No course. Anti weekend-ebook path

Roll seeds are created as placeholders (is_placeholder); bodies may carry a carried-over note. Prefer replacing placeholders with real retoor stories over deleting the point.

Flyers (1080x1350), order matters (home featured = first):

  1. flyer-elite-twice.jpg - Elite twice.
  2. flyer-taste.jpg - Taste does not autocomplete.
  3. flyer-demo.jpg - A demo is not a product.
  4. flyer-hands.jpg - Your hands are not the craft.

Memes (1080x1080): six classic, six vibe, six elite (meme-classic-*, meme-vibe-*, meme-elite-*), plus older English leftovers meme-compiles.jpg, meme-senior.jpg, meme-trophy.jpg, meme-todo.jpg (legacy 1280x720 era; keep or retire only on order).

Retired on disk, unpublished (RETIRED_GALLERY in gallery.py): old Dutch flyers/memes/posters/takes (flyer-keuzes.jpg, meme-vloer.jpg, poster-*, take-*, story.jpg, ...). Soft-deleted in DB. Do not re-publish.

Captions/alt live in GALLERY tuples and sync into media_items on boot. English only. Captions must not start with SQL keywords that trip sqlglot string lint (SELECT, INSERT, ...).


6. How to publish

Prefer the admin HTML UI for interactive work. Prefer the Admin API (section 0.1) for scripts and agent automation. Both share the same handlers.

Login

  1. Open https://molodetz.nl/auth/login (or local/staging equivalent).
  2. Username: retoor (production admin username from env bootstrap).
  3. Password: production admin password is with the operator / secrets store, never in the repo. Staging/test may use compose defaults documented in README.md; production uses PROD_ADMIN_PASSWORD.
  4. Session cookie, 7 days (30 with remember). No JWT.

Edit home intro

  1. /admin/settings
  2. Field site_intro (markdown textarea).
  3. Save. Home re-renders from settings (landing cache TTL applies).

Create / edit / publish a post

  1. /admin/posts or /admin/posts/new
  2. Title, markdown body, topic roll or standard, status draft or published.
  3. Publish/unpublish from the posts list.
  4. Soft delete goes to trash; restore from /admin/trash if needed.

Join requests

  1. /admin/joins for open requests.
  2. Status and optional repo-link check (outbound stealth client).
  3. Do not invent acceptances in public content.

Galleries (flyers / memes)

  1. Add JPEG under molodetz/static/media/ with an English filename.
  2. Register in GALLERY in molodetz/gallery.py with kind + English caption (alt text).
  3. Put new flyers in the desired order; index 0 is the home featured flyer.
  4. Deploy / restart so sync_gallery() runs (bootstrap). Dedupe uses perceptual hash distance <= 2; near-duplicate layouts are skipped.
  5. Soft-retire old files via RETIRED_GALLERY + retire_media; keep bytes on disk unless ordered otherwise.
  6. Visual-check every new image (open the PNG/JPG; spelling, crop, legibility). Build a contact sheet when shipping a set (out/memes-sheet.jpg practice).

Engineering owns compose, molohttp, and data trees (./data/production is holy). Content master does not refresh staging from production or touch molohttp unless explicitly ordered.


7. Meme / flyer production recipe

Generator scratch (not committed):
/workspace/tmp/molodetz-app-20261005/memes/
(lib.py, parts.py, classic.py, vibe.py, elite.py, flyers.py, gen.py). Prefer regenerating with Pillow so all text is drawn, never baked into a copyrighted meme photo.

Dimensions and output

Kind Size Format
Meme 1080 x 1080 JPEG, quality ~93, subsampling 0 (lib.save)
Flyer 1080 x 1350 same

Fonts (from lib.py FONT_FILES)

Base dirs:

  • Google: /usr/share/fonts/truetype/sand-box/google/
  • Custom: /usr/share/fonts/truetype/sand-box/custom/
  • DejaVu: /usr/share/fonts/truetype/dejavu/
Key File Typical use
anton Anton/Anton-Regular.ttf Meme titles, impact headers
archivo Archivo Black/ArchivoBlack-Regular.ttf Dense meme body
mono / monob / monosb IBM Plex Mono Regular/Bold/SemiBold Terminals, keys
space Space Mono/SpaceMono-Bold.ttf wordmark molodetz
serif / serifi DM Serif Display Regular/Italic Flyer headlines
caslon / caslonb / casloni Libre Caslon Text Flyer body / italics
sans / sanssb / sansb / sansblack Source Sans Pro UI labels, flyer header
sym / symb DejaVu Sans Glyphs (e.g. crown)
marker Permanent Marker Occasional accent

Text fitting: block() / fit() must succeed; never ship cropped letters. Uppercase via upper=True when the layout calls for it.

Wordmark

  • Literal text: molodetz (lowercase).
  • Font: Space Mono Bold, default size 24, pad 30.
  • Default corner: bottom-right (corner="br"); flyers draw bottom-right with ink; some dark memes use a dark plate behind the mark or bottom-left when a layout conflicts.
  • Alpha ~170-230 depending on background contrast.

Flyer palette and layout (flyers.py)

  • Paper cream PAPER = (243, 238, 227)
  • Ink (28, 26, 24), muted (110, 102, 92), accent red (178, 48, 36)
  • Margin M = 96
  • Header: spaced MOLODETZ (Source Sans Bold 26) + No. N of 4 (Caslon italic)
  • Motif, then DM Serif headline, short red rule, Caslon body
  • Footer: ONE POINT PER SHEET + wordmark
  • One point per sheet. Calm but cutting. English.

Current four points: elite twice; taste does not autocomplete; a demo is not a product; your hands are not the craft.

Meme palettes and targets

Roughly one third each:

  1. Classic (refuse AI) - dark terminals, cream starter packs, charts where keystrokes rise and features stay flat, certificates of manual labour, multi-generation refusal. Colors: near-black gradients, cold greys, warning red (255, 95, 86), occasional yellow band.
  2. Vibe (refuse craft) - Win95-style prompt-and-pray, pink/purple galaxy "vibe debugging", starter packs (TAB, sk-live-, localhost), weekend-to-course arcs, Drake-style NAH/YEP priorities.
  3. Elite (embrace AI + craft) - gold GOLD = (255, 196, 60), dark navy/black panels, crown glyph, build logs that review every line, choose-your-fighter with elite column highlighted, curves that keep climbing, achievement unlocked.

Classic formats allowed as layouts only (two-panel, galaxy brain, Drake approve/reject, starter pack, fake terminal, fake error, fake chart, certificate). Draw backgrounds programmatically or with non-copyrighted generation. No stolen meme template photos.

Dev-culture references encouraged: vim/tmux, Stack Overflow, works on my machine, README-driven, 10x, git blame, compiler errors, prompt-and-pray, tab-tab-tab accept-all, punch cards, etc.

Content bans on assets

No slurs. No real named individuals. No em-dashes in image text. No DevPlace/Nigel. No selfie. English filenames and English captions.

Wiring and QA

  1. Generate into scratch out/, visual-read every file, fix spelling.
  2. Copy into molodetz/static/media/.
  3. Update GALLERY captions; retire Dutch leftovers in RETIRED_GALLERY.
  4. Contact sheet of the full new set (filenames under each tile).
  5. Restart / sync; confirm /flyers, /memes, home featured flyer.
  6. Keep pairwise phash distance clearly above 2 so sync does not drop "duplicates".

8. Consistency checklist (every new asset or post)

  • English only; no Dutch UI copy.
  • No em-dash or en-dash characters.
  • Tone matches section 3 (harsh when earned, factual, pro elite+AI).
  • Targets the right enemy or the elite path; does not punch down with slurs or real names.
  • Post: correct topic (roll vs standard); markdown renders; no secret material.
  • Meme 1080x1080 or flyer 1080x1350; wordmark present; text fully visible (no clip).
  • Caption/alt English, honest, not SQL-keyword-leading.
  • GALLERY updated or post published via admin; retired files not re-listed.
  • Visually verified (Read/open the image or preview the post).
  • No commit of .env, passwords, or private join PII.

9. Success criteria for the content master

  1. Home intro stays hostile, data-backed, and accurately retoor's voice unless retoor orders a rewrite.
  2. Roll accumulates real finishing/AI/craft stories; placeholders shrink.
  3. Standard stays taste-and-standards, not tutorial spam.
  4. Galleries keep the three-way thesis (classic roast / vibe roast / elite twice) with consistent visual system and English captions.
  5. Featured flyer remains the thesis sheet (elite twice) unless retoor reorders deliberately.
  6. Join queue is handled as private admin work; public site never fakes community size.
  7. No Dutch, no em-dashes, no secrets, no invented people in anything shipped.
  8. New assets are reproducible from the Pillow recipe (or an equal programmatic successor), not one-off unreproducible binaries without text sources.
  9. Content changes do not require inventing engineering; when gallery code or settings must change, coordinate with engineering and keep tests green on their side.
  10. Coworker can ship a new meme set or post week using only this document, admin login (secrets from operator), and the generator path above.

Quick reference paths

Item Path / URL
Git remote https://retoor.molodetz.nl/retoor/ad.git
App repo (local) /workspace/retoor/molodetz-app
Production https://molodetz.nl
Staging https://staging.app.molodetz.nl
Test https://molodetz-test.app.molodetz.nl
Docs download https://molodetz.nl/docs/download.md
OpenAPI https://molodetz.nl/swagger
API registry (code) molodetz/docs_api/
Download builder molodetz/docs_export.py
Intro seed molodetz/seed/intro.md
Gallery registry molodetz/gallery.py
Media files molodetz/static/media/
Seed posts molodetz/content.py SEED_POSTS
Feature selection SELECTION.md
Compliance vs DPP COMPLIANCE.md (engineering; not tone)
Engineering lessons (not content) DEVELOPMENT_FLOW.md
Meme generator scratch /workspace/tmp/molodetz-app-20261005/memes/
Deploy notes README.md, molohttp_deploy.md