555 lines
24 KiB
Markdown
555 lines
24 KiB
Markdown
<!-- retoor <retoor@molodetz.nl> -->
|
|||
|
|
# 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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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):
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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.
|
||
|
|
|
||
|
|
### Live gallery (published English set)
|
||
|
|
|
||
|
|
**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` |
|