Repository source: docs/plans/chouse_club_hosting_plan_v1.md
Hosting chouse.club on this framework — plan v1
Update (2026-08-10): confirmed direction is to run chouse.club and zenmemes.com as two separate communities (separate content, separate database, separate moderation posture) from one shared codebase — see "Multi-site architecture" below. This replaces the single-site framing of the rest of this doc's Slice 1/5; read those with that in mind.
What chouse.club is today
Fetched 2026-08-10. "The C(lub) House" — a Red Bull–associated community board for builders/entrepreneurs. Single page:
header with a vertical "CHOUSE" wordmark as the main graphic element
a form: "What are you building?" (project description, what help is needed, optional contact info)
a reverse-chronological "Recent submissions" feed of all public posts
contact info is collected but kept private, unlike the rest of the post
minimal, text-forward layout, no elaborate graphics
Content quality is a real problem: genuine submissions ("building a next gen family office network," "sneakernet of Alexandria," etc.) are mixed in with heavy crypto-scam, fake-lottery, and SEO spam — there's no visible moderation or identity gate today.
Why this framework fits
The site is structurally a single-board forum: one composer at the top,
one flat feed below. That's almost exactly templates/pages/board.php as
it exists right now — inline "Start a thread…" composer, thread list,
nothing else required. The bigger win isn't layout, it's everything
chouse.club is currently missing:
OpenPGP identity + approval/vouching (
docs/plans/php_user_approval_vouching_slices_v1.md) gives a real lever against the spam flood — anonymous submission stays possible, but approved/vouched identities can be surfaced above it.Git-backed canonical records make every submission an auditable, diffable commit instead of a row in an opaque DB — useful for a community board where people want to point at "here's what I posted."
Dedalus post analysis (
src/ForumRewrite/Analysis/DedalusPostAnalyzer.php) can be pointed at submissions for automatic spam/quality flagging instead of building that from scratch.Tags give "what are you building" a lightweight taxonomy (
fintech,hardware,ai, …) the current site has no way to express.Replies become possible for free — people can respond to a submission offering the help that's being asked for, which the current flat submit-only feed can't do at all.
Multi-site architecture: zenmemes.com + chouse.club
Decision: separate content per site (separate git repository, separate read-model database, separate moderation), one shared application codebase. Sharing the database/repo was considered and rejected — it would merge chouse's open, spam-exposed submission stream with zenmemes' content and moderation, defeating the reason chouse needs its own approval/vouch gating in the first place.
The good news: this app is already multi-tenant-capable for content.
public/index.php:11-13 resolves FORUM_REPOSITORY_ROOT,
FORUM_DATABASE_PATH, and FORUM_PUBLIC_ARTIFACT_ROOT from environment
variables on every request, and docs/examples/apache_vhost.conf already
sets these per-vhost via SetEnv. Two vhosts pointing at two deployed
copies of the same code, each with its own three env vars, get fully
separate content and databases today with zero application changes.
One important correction to "same repo": it should mean same
source, not one literal public/ directory answering both hostnames.
Static HTML artifacts are written as siblings inside public/, keyed by
thread/post ID (public/threads/root-001.html); if zenmemes.com and
chouse.club shared one public/ tree, their artifacts would collide on
matching IDs. So: one repo, one release/build process, but two
deployed instances (two public/ directories, two vhosts) — same
pattern as deploying the same Docker image twice with different config,
not literally the same running process serving two Host headers.
What isn't parameterized yet and needs to be: branding. Today
SiteConfig::SITE_NAME is a hardcoded PHP constant, and there's no
per-site default theme or composer copy. Extend the existing env-var
precedence pattern (env override → site content flag → code default,
already documented in production_deploy.md) one level further with a
small site-profile registry, following the same shape as
ThemeRegistry.php:
// src/ForumRewrite/SiteProfileRegistry.php (new)
final class SiteProfileRegistry
{
public static function all(): array
{
return [
'zenmemes' => ['name' => 'zenmemes', 'defaultTheme' => 'auto', /* existing copy */],
'chouse' => ['name' => 'chouse', 'defaultTheme' => 'chouse', 'composer' => [...]],
];
}
public static function active(): array
{
$id = getenv('FORUM_SITE_ID') ?: 'zenmemes';
return self::all()[$id] ?? self::all()['zenmemes'];
}
}
SiteConfig::SITE_NAME becomes SiteProfileRegistry::active()['name'];
FORUM_SITE_ID gets set per-vhost next to the three existing SetEnv
lines. This is a small, self-contained change and reuses an idiom
(registry-of-profiles) already established by ThemeRegistry, rather
than inventing a new configuration mechanism.
Key design decisions to make before building
These are real mismatches between chouse.club's current model and this framework's, not implementation details — flag them to the site owner before writing code.
Private contact field. Canonical posts are public git-committed plaintext; there's no concept of "submitted but not shown." Options: (a) store contact info outside the canonical repo entirely — e.g. a private SQLite table or
state/private/file, written by the same compose request but never rendered or committed torecords/; or (b) make contact info opt-in-public and drop the privacy guarantee. (a) preserves current behavior and is the recommended path — it's a small, isolated addition (one extra write next to the canonical post write), not a framework change.Structured submission fields vs. free-text body. The current form has three fields (what you're building / what help you need / contact). MVP: fold the first two into the post body as light structure (e.g. two labeled paragraphs), keep parity with
thread_compose_form.phpas-is. Later: add realhelp_neededas a second body field if the product wants it queryable/filterable — bigger effort, not needed for parity.Anti-spam posture. Recommend keeping anonymous submission (matches current openness) but enabling the approval/vouch flow and Dedalus analysis from day one rather than bolting it on after the spam shows up again, since that's the whole reason to migrate off the current setup.
HTTPS. The production runbook's "don't force HTTPS, don't set HSTS" default is written for the general forum-identity case (public-HTTP OpenPGP fallback). A public site collecting contact info should run HTTPS (Let's Encrypt) as the primary path; keep the runbook's guidance about not force-redirecting or setting HSTS so a cert hiccup can't lock out the domain.
~~Migrating existing content.~~ Resolved: no migration — chouse's instance launches with an empty repository/database. See "Resolved" below.
Plan
Slice 1 — Site identity (site-profile registry)
Add
SiteProfileRegistry(see architecture section above) withzenmemesandchouseprofiles; resolve the active one fromFORUM_SITE_IDat request start, defaultzenmemesfor back-compat if the env var is absent (existing single-site deploys keep working unchanged).Replace
SiteConfig::SITE_NAMEand the handful of places that hardcode it (FrontController.php:265,278,281,templates/pages/about.php) with reads from the active profile. Internal-only storage keys (zenmemes-themelocalStorage key, etc.) can stay as-is — renaming them is cosmetic risk for zero user-facing value, and they're per-site anyway since each deployed instance has its own origin/localStorage.Write a chouse-specific
about.phpvariant (or a per-profile about body) alongside the existing zenmemes one.Single-board mode for chouse: hide/collapse the tags and multi-board chrome on
board.phpwhen the active profile ischouse, since it's one flat feed, not a tagged multi-board forum (tags can still exist under the hood for later taxonomy use). zenmemes' board is unaffected.
Slice 2 — Composer parity
Repoint the home route at the board composer, relabel it: prompt "What are you building?", body placeholder split into "what you're building" / "what help you need," submit label "Submit."
Add the private-contact-field write path (decision #1 above): new optional form field, written to a non-canonical private store next to (not inside) the canonical post commit.
Feed heading → "Recent submissions."
Slice 3 — New "chouse" theme
Follow docs/runbooks/theme_development_guide.md exactly:
ThemeRegistry.php: add['name' => 'chouse', 'label' => 'Chouse', 'mode' => 'light'].public/assets/site.css: variable block, swatch, menu-row garnish, scoped section — same three-block pattern as every existing theme.Design direction: minimal, text-forward, one strong accent color (not a literal Red Bull palette/logo copy — build an original mark inspired by the brief, since reusing another brand's trademark colors/logo verbatim is a legal risk for the site owner, not just a style choice). The vertical "CHOUSE" wordmark is a good CSS-only signature element —
writing-mode: vertical-rlor a rotated fixed-position label, no image asset, matching the repo's no-asset theme convention.Update
tests/LocalAppSmokeTest.phpallow-list per the guide.Work in slices with a screenshot-driven verification pass (headed Chrome, ~380px viewport, theme popover leak check) as the guide prescribes — this is its own sub-plan doc when it starts.
Slice 4 — Moderation wiring
Enable/seed the approval-vouching flow so a first trusted identity exists at launch.
Wire Dedalus post analysis for submission quality/spam signal (reuses existing
reply-agentstyle infra — no new provider integration).Decide surfacing: hide flagged-spam submissions from the default feed view rather than deleting (keeps the git history honest).
Slice 5 — Deploy (two instances, one codebase)
Follow docs/runbooks/production_deploy.md / apache_vhost.conf, doubled:
One source checkout/release process, deployed to two independent instance directories:
/srv/zenmemes/app+/srv/zenmemes/repository+/srv/zenmemes/state/srv/chouse/app+/srv/chouse/repository+/srv/chouse/state
Each
app/gets its ownpublic/so static artifacts never collide (see architecture section — this is the one place "same repo" does not mean "same directory").Two vhosts:
ServerName zenmemes.com/ServerName chouse.club, each setting its ownFORUM_REPOSITORY_ROOT,FORUM_DATABASE_PATH,FORUM_PUBLIC_ARTIFACT_ROOT, plus the newFORUM_SITE_ID(zenmemes/chouse) from slice 1.Point chouse.club's DNS at the host; issue a Let's Encrypt cert (see decision #4 below) for chouse.club specifically — leave zenmemes.com's existing TLS posture alone unless asked to change it. Do not add forced HTTP→HTTPS redirect or HSTS on either vhost.
Initial read-model rebuild + static artifact build for chouse's instance only (zenmemes' is presumably already running); cron for
run_agent_reply_requests.phpper instance if Dedalus fulfillment is async, pointed at that instance's own paths.Shared code changes (bug fixes, new themes, framework features) get released to both instances together; site-specific config (
FORUM_SITE_IDand friends) stays per-vhost so one release doesn't need site-specific branches.Walk the runbook's pre-launch checklist and manual-verification list for the chouse instance before pointing DNS; re-run it against the zenmemes instance too once slice 1's
SiteProfileRegistrychange lands there, since that's a shared-code change touching a currently-running site.
Open questions for the site owner
Keep contact info private (recommended, needs slice 2's private-store work) or accept it becoming public going forward?
Is Dedalus (or another LLM provider) already available/budgeted for spam analysis, or should slice 4 ship without it initially and rely on approval-gating alone?
Resolved
Content migration: not needed. chouse's instance launches with an empty repository/database — no import script, no hand-picking genuine submissions out of the current spam-heavy feed. Removes what was Slice 6 entirely.