Repository source: docs/plans/reply_title_directive/reply_title_directive_step3_development_plan.md
Debug mode: enabled (inherited)
Effort study: railspike-effort / v3/reply_title_directive — host ledger; standing enrollment under protocol P2
Extended estimates: enabled (inherited)
Cycle estimate: 0.85
Reply title directive — Step 3: Development Plan
Shared metadata: scope v2 (approved Step 2), baseline e6493e2ff4869f8bf98e5f43b3f6388814742c26, reference R1, rater Claude Code (single exposed rater, model claude-fable-5-1), stage-work estimates dated 2026-11-03, independently scoped and never summed.
Completion Contract
Normal entry: ordinary reply composer (inline or dedicated page), prepared or signed reply, body containing
title: New name, withFORUM_TITLE_DIRECTIVE_ENABLEDon.End-to-end outcome: reply posts unchanged; thread title changes on thread page, board listings, Recent, Forte, text API, RSS and static copies; root card signals line reads
Title set by <user>; every root and reply card lists eligible likers and flaggers by name, identically for anonymous and signed-in viewers.Required recovery: rejected directive (no authority, flag off, empty value) posts the reply as text with no error;
./v3 rebuildreproduces titles and signals from canonical records; prior-schema read models rebuild on normal startup; retracting or deleting the source reply withdraws the retitle on the next derived-state update; static pages invalidate as for a label write.Deployment/external verification: production acceptance is the maintainer replying
title: …on an own thread on zenmemes.com after deploy and seeing the new title plusTitle set by ilyag; local evidence does not establish it. Flags default on/off without a deploy step.Release condition: focused PHP tests pass (parser, flags, authority, write, precedence, rebuild/incremental parity, prior-version rebuild, signals eligibility equals score counts); Feature Flags page shows both flags in order; classic, paned and Forte cards verified in every theme at phone and desktop widths, including no-JavaScript, long actor lists, retracted replies and flag-off; affected shared consumers checked; dependency delta zero; concept entries and spec written.
Key Risks
High risk: signals name reactions the score does not count. Impact: visible contradiction and exposure of withdrawn or unapproved reactions. Early validation: Stage 5 asserts per-target eligible actor count equals the score reducer's counted set on existing fixtures before any template work. Mitigation: eligibility is computed once in the reducers (approved, not inactive, deduped per identity and tag) and stored, never recomputed per request.
High risk: precedence change retitles existing threads. Impact: silent title changes after deploy. Early validation: local repository inspected 2026-11-03: one thread-subject record, system-authored. Stage 4 test covers a system record beside an existing root subject. Mitigation: only records with
Author-Identity-IDoverride a non-empty subject; system records keep fill-empty semantics.Read-model schema change. Impact: deployed instances with older models must rebuild before new queries run. Early validation: Stage 4 prior-version regression test. Mitigation: one
SCHEMA_VERSIONincrement covering both new columns, spec update,./v3 rebuildcheck, perCONTRIBUTING.md.Template fan-out and first paint. Impact: layout shift or viewer-specific HTML on static high-traffic pages. Early validation: Stage 6 renders the shared partial with a long actor list in two themes first. Mitigation: one server-rendered partial; the viewer's own click appends in place only.
Per-request record globbing. Impact: the incremental updater's subject loader globs the whole directory; a page-render path doing the same would not scale. Mitigation: render reads only read-model columns; globbing stays in write/rebuild paths.
Concepts touched
Reaction record — used unchanged; Stage 8 adds the eligibility/signals sentence if behavior shown changes its description.
Post presentation — changed in Stage 8: signals line and sources.
Thread title — new entry
docs/concepts/thread_title.mdin Stage 8: precedence, directive authority, provenance, withdrawal.Site feature flags — used unchanged (two registrations); no entry proposed.
Stage 1 — Subject record provenance and spec
Estimate: 0.08
Goal: thread-subject records can carry author and reply provenance, and the format is documented.
Dependencies: none.
Expected changes:
ThreadSubjectRecordgains nullablesourcePostId,sourcePostSha256;ThreadSubjectRecordParserreadsSource-Post-ID/Source-Post-SHA256(both or neither, thread scope only, same validation as reaction records); newdocs/specs/thread_subject_record_v1.mdmodeled on the reaction record spec, documentingOperation: set, system versus authored records, precedence and the optional detached signature statement. Existing records remain valid unchanged.Verification approach: extend parser tests (valid provenance pair, one header alone rejected, legacy record unchanged); spec reviewed against
CONTRIBUTING.mdhuman-readable format rules.Estimate details: driver: mirroring existing parser conventions; uncertainty: low.
Risks or open questions: none material.
Canonical components/API contracts touched:
ThreadSubjectRecord,ThreadSubjectRecordParser, new spec.
Stage 2 — Feature flags
Estimate: 0.08
Goal: both flags exist, default correctly and appear in order.
Dependencies: none.
Expected changes:
FeatureFlagRegistryconstantsTITLE_DIRECTIVE_ENABLED(FORUM_TITLE_DIRECTIVE_ENABLED, default true, site-mutable) andTITLE_DIRECTIVE_APPROVED_MEMBERS(FORUM_TITLE_DIRECTIVE_APPROVED_MEMBERS, default false, dependent on the first) appended to theEXPERIENCEpresentation list, parent then child; short labels and one-sentence descriptions; feature flag reference doc updated.Verification approach:
FeatureFlagsBehaviorTestcompleteness/order/dependency assertions; Feature Flags page renders both in the Forum experience group; toggling through the page writes the change record.Estimate details: driver: registry conventions and tests; uncertainty: low.
Risks or open questions:
Impact: dependent flag shown enabled while parent off would mislead. Early warning: existing dependency rendering checks. Mitigation: declare the dependency as sibling flags do.
Canonical components/API contracts touched:
FeatureFlagRegistry, Feature Flags page,site_feature_flags_record_v1(values only).
Stage 3 — Directive extraction, authority and write
Estimate: 0.15
Goal: an authorized reply writes an authored subject record in the same commit.
Dependencies: Stages 1, 2.
Expected changes:
LocalWriteService::extractTitleDirective(string $body): ?stringusing the hashtag line rules (skip quoted lines, first match, case-insensitivetitle:at line start, trimmed one-line value, max length asSubjecttoday); newSecurity/TitleDirectiveAuthority::allows(string $root, PDO $pdo, PostRecord $root, string $identityId, FeatureFlagEvaluator $flags): bool(verified root author, orRetractionAuthority::currentOperator, or approved member when the second flag is on);buildThreadSubjectRecordgains author, provenance and reason parameters;createReply,createPreparedPostandcreateSignedPostwrite the subject record beside the label record, include it in the commit, and callsynchronizeThreadSubjectDerivedState; rejected directives leave the reply untouched.Verification approach: new
LocalWriteServiceTitleDirectiveTest: author retitles; operator retitles; approved member only with flag on; guest and unapproved rejected; flag off rejected; quoted> title:ignored; empty value ignored; record round-trips through the parser; reply body byte-identical; signed reply path covered.Estimate details: driver: three write paths and authority matrix; uncertainty: verified-author determination for unsigned legacy roots (fall back to
attribution_verified).Risks or open questions:
Impact: authority judged from stale read model. Early warning: tests with a freshly approved member. Mitigation: read approval state the way
isApprovedIdentitydoes.Impact: directive and hashtags in one reply write two records. Early warning: test. Mitigation: both in one commit, as today for labels.
Canonical components/API contracts touched:
LocalWriteServicereply paths, newTitleDirectiveAuthority, thread-subject record family.
Stage 4 — Read-model precedence and schema
Estimate: 0.15
Goal: the latest authored set wins everywhere the title is read; schema carries title author and signal eligibility.
Dependencies: Stage 1.
Expected changes:
ReadModelMetadata::SCHEMA_VERSION43→44;threadsgains nullablesubject_author_identity_id;viewer_tagsgainseligibleinteger default 0 (populated in Stage 5);ReadModelBuilder::indexThreadSubjectsandIncrementalReadModelUpdater::deriveThreadSubjectStateapply: authored records (withAuthor-Identity-ID, source present and not retracted or removed) latest-wins over any subject; system records fill only an empty subject;posts.subjectfor the root updated alongside;docs/specs/read_model_schema_v1.mdupdated.Verification approach:
ReadModelThreadSubjectsTestextended (authored overrides root subject; later authored wins; system never overrides; withdrawn source ignored; incremental equals rebuild); new prior-version startup rebuild regression test perCONTRIBUTING.md;./v3 rebuildon the local repository leaves the single existing system record's behavior unchanged.Estimate details: driver: two reducers plus schema contract; uncertainty: ordering ties between same-second records (record ID tiebreak as today).
Risks or open questions:
Impact: deployed model lacks new columns. Early warning: regression test. Mitigation: startup rebuild on version mismatch, already the contract.
Canonical components/API contracts touched:
ReadModelMetadata,ReadModelSchema,ReadModelBuilder,IncrementalReadModelUpdater, read-model spec.
Stage 5 — Signal eligibility and actor query
Estimate: 0.12
Goal: a cheap read-model query returns, per target, the eligible likers and flaggers and the title author, with usernames.
Dependencies: Stage 4.
Expected changes: thread-label and post-reaction reducers (rebuild and incremental) set
viewer_tags.eligiblefor exactly the records they count toward score or flag count (approved author, not inactive, first per identity and tag); newReadModel/SignalActors::forPosts(PDO $pdo, array $postIds, string $threadId): arrayreturning per postlikeandflagactor lists (username, profile slug) and the thread's title author, joined throughprofiles, ordered by record time.Verification approach: new test asserting eligible like count equals
post_score_totalcontribution and eligible flag count equalsapproved_flag_counton the existing reaction fixtures, including a retracted-source label and an unapproved liker; incremental write after a Like matches rebuild.Estimate details: driver: aligning with two existing reducers' dedupe keys; uncertainty: verified-attribution mode (
FORUM_VERIFIED_ATTRIBUTION) selects claimed versus verified sets; eligibility follows the effective set.Risks or open questions:
Impact: verified-attribution flag flip leaves eligibility stale. Early warning: existing startup swap logic. Mitigation: recompute eligibility in the same startup path that swaps effective scores.
Canonical components/API contracts touched:
ReadModelBuilder,IncrementalReadModelUpdater, newSignalActors.
Stage 6 — Signals line UI
Estimate: 0.20
Goal: every root and reply card shows the signals line; the viewer's own click updates it in place.
Dependencies: Stage 5.
Expected changes: new shared partial
templates/partials/post_signals.php(Liked by a, b, c +N · Flagged by d · Title set by e, names linking to profiles, hidden when empty); included bythread_root_card.php,post_card.php,paned_thread_reply_tree.phpandpaned_board_content_article.php;ThreadAndPostPageControllerand the paned/Forte controllers passSignalActorsresults;thread_reactions.jsappends the viewer's name after a successful Like or Flag; CSS in the shared stylesheet with checks in theme files; no viewer-specific server markup;qdb_quote_actions.phpuntouched.Verification approach: template tests for the four inclusions and the empty state; browser test (
tests/browser) across all themes at phone and desktop: long actor list wraps without shifting the main action, no-JavaScript renders the same line, own click appends once; UI checklist entry indocs/plans/ui_copy_audit_checklist.md.Estimate details: driver: four card variants and thirteen themes; uncertainty: paned layout width for long lists.
Risks or open questions:
Impact: copy repeats the Like button state. Early warning: review against
AGENTS.mdUI copy rule. Mitigation: the line is the only place names appear; buttons keep their current text.Impact: layout shift after load. Early warning: compose layout stability browser test pattern. Mitigation: server-rendered, fixed on first paint.
Canonical components/API contracts touched: new
post_signals.php, four card templates,thread_reactions.js, thread/paned/Forte controllers.
Stage 7 — Withdrawal, deletion and shared consumers
Estimate: 0.12
Goal: a retracted or deleted source reply withdraws its retitle, and every title surface and static copy updates.
Dependencies: Stages 3, 4.
Expected changes:
LabelOwnership-equivalent check for subject records (SubjectOwnership::inspect, or a generalized helper) used by the reducers from Stage 4;SourceLabelRemovalandRetractionDeletioninclude matching authored subject records in removal evidence sodelete-recordcleans them as it cleans labels; confirmStaticArtifactInvalidator::invalidateBoardThreadcovers thread, listings, Recent, Forte, RSS and offline snapshot paths, adding any missing path; text API and import catalog already read the family.Verification approach: tests: retract source reply → title reverts to previous effective subject on incremental update and on rebuild; delete source → subject record removed with evidence; static artifact paths deleted after retitle; Forte/API/RSS/Recent show the new title in an HTTP smoke test.
Estimate details: driver: fitting into the removal evidence format; uncertainty: evidence format fields for a non-label family.
Risks or open questions:
Impact: orphaned authored subject records after deletion. Early warning: deletion test. Mitigation: reducers also ignore records whose source is absent, so an orphan is inert even if cleanup fails.
Canonical components/API contracts touched:
SourceLabelRemoval,RetractionDeletion, removal evidence,StaticArtifactInvalidator.
Stage 8 — Acceptance, known-user replay and closeout
Estimate: 0.12
Goal: replay the maintainer's flow, finish documentation and record the study closeout.
Dependencies: Stages 1–7.
Expected changes:
docs/concepts/thread_title.mdcreated;docs/concepts/post_presentation.mdupdated; register entry PF-20261103-F01 moved to blocked (deployment pending) with local evidence; ledger closeout; Step 4 summary; dependency delta reported.Verification approach: local replay of the known-user flow through the ordinary composer: author replies
title: …on own thread with flags at defaults, title changes on thread, board, Recent and Forte, root card showsTitle set by <user>; same reply by a second approved identity is ignored until the second flag is turned on through the Feature Flags page, then works; flag off makes the line plain text. Full focused suite run; broader suite failures reproduced on baseline listed separately. Production acceptance explicitly recorded as pending.Estimate details: driver: browser matrix and documentation; uncertainty: pre-existing suite failures unrelated to this work.
Risks or open questions:
Impact: local pass mistaken for production acceptance. Early warning: Step 2 known-user condition. Mitigation: register stays blocked until the maintainer confirms on zenmemes.com.
Canonical components/API contracts touched: concept inventory, register, ledger, Step 4 summary.
Execution and study checkpoint
2026-11-03T08:53:36Z: Step 2 explicitly approved ("approved step 2"), no new scope. Eight stages planned, zero implemented. Scope v2, baseline e6493e2ff4869f8bf98e5f43b3f6388814742c26, R1 (hashes verified at Step 1), P2. Step 3 projected total 0.85 reaffirmed, independently judged; stage-work originals S1 0.08, S2 0.08, S3 0.15, S4 0.15, S5 0.12, S6 0.20, S7 0.12, S8 0.12, not additive. Shared schema change owned by Stage 4; shared UI acceptance owned by Stage 8. Claude Code single exposed rater. No implementation, measured effort or accepted outcome; await Approved Step 3; records uncommitted. Enhancement request "suggested titles from unauthorized repliers" remains pending and out of scope.
— Claude Code · 2026-11-03 UTC · Model: claude-fable-5-1
Step 3 approval
2026-11-03T17:10:05Z: User: "approved step 3". Plan approved unchanged; feature branch feat/reply-title-directive, planning artifacts organized into docs/plans/reply_title_directive/. Implementation authorized for the eight planned stages.
— Claude Code · 2026-11-03 UTC · Model: claude-fable-5-1