Repository source: docs/plans/qdb_quotes_instance/qdb_quotes_instance_step3_development_plan.md
QDB Quotes Instance — Step 3: Development Plan
Stage 1
Goal: stand up the new site instance's identity/branding plumbing.
Dependencies: none.
Expected changes:
New entry in
SiteProfileRegistry::all()(e.g.qdb) with its own default theme.New entry in
ThemeRegistryfor the QDB-style theme.docs/runbooks/production_deploy.mdgains the newFORUM_SITE_IDvalue and a vhost example pairing it with its ownFORUM_REPOSITORY_ROOT/FORUM_DATABASE_PATH/FORUM_STATIC_HTML_ROOT, mirroring thechouseentry.
Verification approach: run locally with
FORUM_SITE_ID=qdb; confirm site name/default theme resolve to the new profile; existingSiteProfileRegistryTest.phppattern extended for the new entry.Risks or open questions:
none.
Canonical components/API contracts touched:
SiteProfileRegistry,ThemeRegistry(both extended, not forked).
Stage 2
Goal: ship the QDB-accurate visual skin (no voting behavior yet).
Dependencies: Stage 1 (theme entry must exist to attach the stylesheet).
Expected changes:
New
public/assets/theme-qdb.csscarrying the archived palette/fonts (burnt-orange/white chrome, Arial UI text, courier-new quote body) per the theme guide's variable-block/swatch/scoped-section structure.tests/LocalAppSmokeTest.phptheme allow-list updated for the new theme name.
Verification approach: local dev server visual check against the archived classic-QDB snapshot; smoke test passes.
Risks or open questions:
none.
Canonical components/API contracts touched:
ThemeRegistrystylesheet path convention (no new wiring needed, per theme guide).
Stage 3
Goal: extend the existing scored-reaction vocabulary with upvote/downvote, no schema change.
Dependencies: none (independent of Stages 1-2).
Expected changes:
TagScore::scoredTags()gainsupvoteanddownvoteweights (symmetric, e.g. +1/-1), alongside existinglike/flag.Confirm
PostReactionRecordParser's tag-token rules already acceptupvote/downvotetokens as-is (lowercase, no new validation needed).
Verification approach: unit-level check of
TagScore::scoreValueForTag()for the two new tags; confirmReadModelBuilder/IncrementalReadModelUpdaterpick up the new weights automatically since both already iterateTagScore::scoredTags()generically.Risks or open questions:
Score aggregation currently only counts reactions from identities present in
approvalState(existing gating in the score-reduction loop). Need to verify whether anonymous QDB-style voting will move the displayed score under this gate, or whether that gate needs a documented exception for this instance before Stage 6 sign-off.
Canonical components/API contracts touched:
TagScore(extended weight table only).
Stage 4
Goal: render the index as a flat QDB-shaped quote list instead of the generic thread-card board.
Dependencies: Stages 1-3 (profile, theme, and tag vocabulary must exist first).
Expected changes:
New
templates/partials/quote_card.php, modeled onthread_root_card.php's existingdata-thread-reactions-root/data-action="apply-thread-tag"block (same score node, same AJAX wiring) but trimmed to#ID+ score + full quote body — no subject, preview truncation, or labels — with upvote/downvote/flag buttons in place of like/flag.templates/pages/board.phpbranches on the active site profile to renderquote_card.phpinstead ofthread_card.phpfor this instance only; other profiles' board rendering is untouched.
Verification approach: manual browser check of the index against the archived reference shape; confirm zenmemes/chouse board pages render unchanged.
Risks or open questions:
none.
Canonical components/API contracts touched:
templates/pages/board.php(new conditional branch),templates/partials/quote_card.php(new, reuses existing reaction JS/API contract rather than inventing one).
Stage 5
Goal: make the shared reaction feedback copy tag-accurate so upvote/downvote don't say "Liked."
Dependencies: Stage 4 (new buttons must exist to exercise this).
Expected changes:
bindThreadReactions()inpublic/assets/thread_reactions.jsstops hardcoding"Liked."/"Already liked."and instead derives the feedback copy from the button's existingdata-applied-labelattribute (already passed in but currently unused for this string).Regenerate the fingerprinted copies of
thread_reactions.jsper the existing asset-fingerprinting process.
Verification approach: manual check that Like still reads "Liked."/"Already liked." (unchanged wording, now parameterized) and that Upvote/Downvote read their own applied labels.
Risks or open questions:
none.
Canonical components/API contracts touched:
thread_reactions.js(bindThreadReactions) — shared by all instances; change is additive/generalizing, not behavior-changing for existing tags.
Stage 6
Goal: end-to-end verification pass across the whole feature.
Dependencies: Stages 1-5.
Expected changes: none (verification-only stage).
Verification approach:
Confirm the quotes instance's index visually matches the blended classic-QDB/qdb.us reference.
Confirm clicking a quote's
#IDreaches the existing/threads/<id>discussion page with working replies, unmodified.Confirm upvote/downvote/flag update the displayed score without a page reload and persist across reload.
Confirm zenmemes/chouse boards show no behavioral or visual regression.
Risks or open questions:
Resolve the Stage 3 approval-gating question definitively here if still open.
Canonical components/API contracts touched: none (verification only).