Repository source: docs/plans/php_thread_labels_after_creation_implementation_checklist_v1.md
PHP Thread Labels After Creation Implementation Checklist V1
This document turns the retained proposal in docs/plans/php_thread_labels_after_creation_proposal_v1.md into a concrete implementation checklist for the current PHP codebase.
Goal
Implement append-only thread labels in a way that keeps canonical parsing, read-model behavior, UI rendering, and write orchestration reviewable in small slices.
Scope
Retained V1 scope:
add a new canonical family in
records/thread-labels/support additive label events only
derive current thread labels into the SQLite read model
render current labels on board and thread pages
create labels from hashtags in thread-root and reply compose flows
emit compact activity items for hashtag-driven label additions
Out of scope for V1:
direct
POST /api/update_thread_labelsfiltering UI
remove or set operations
dedicated label-management controls
Slice 1: Canonical Contract
Focus:
define and validate the new canonical record family before touching reads or writes
Checklist:
add a spec document for the canonical record family under
docs/specs/create
src/ForumRewrite/Canonical/ThreadLabelRecord.phpcreate
src/ForumRewrite/Canonical/ThreadLabelRecordParser.phpextend
src/ForumRewrite/Canonical/CanonicalPathResolver.phpwiththreadLabel()extend
src/ForumRewrite/Canonical/CanonicalRecordRepository.phpwithloadThreadLabel()enforce
records/thread-labels/<record-id>.txtenforce filename equality with
Record-IDvalidate required headers:
Record-ID,Created-At,Thread-ID,Operation,Labelsvalidate
Operation: addonlyvalidate label token normalization and rejection rules
validate optional
Author-Identity-IDdecide whether
Reasonparsing lives as a nullable property onThreadLabelRecordadd fixture records under
tests/fixtures/for valid and invalid thread-label filesextend
tests/CanonicalRecordParsersTest.phpwith parser and repository coverageextend
tests/CanonicalRecordParsersTest.phpwithCanonicalPathResolverassertions for the new family
Expected outcome:
thread-label records have a stable parser and path contract
invalid records fail early at canonical-parse time
other layers can depend on a typed
ThreadLabelRecord
Slice 2: Full Rebuild Read Model
Focus:
materialize current thread labels during a full rebuild before wiring incremental writes
Likely files:
src/ForumRewrite/ReadModel/ReadModelBuilder.phpsrc/ForumRewrite/ReadModel/ReadModelMetadata.phpsrc/ForumRewrite/Canonical/CanonicalRecordRepository.php
Checklist:
extend
threadsschema inReadModelBuilderwiththread_labels_json TEXT NOT NULLbump the read-model schema version if required by current metadata conventions
load
records/thread-labels/during rebuildvalidate each label record target against known root threads
skip invalid committed label records and surface them in metadata or debug output
sort label records by
Created-Atascending andRecord-IDascendingreduce to the final per-thread label set
store reduced labels in lexical ascending order in
thread_labels_jsonensure threads with no label records persist
[]keep hidden/internal threads label-compatible in the reducer
add rebuild coverage for multiple label records against one thread
add rebuild coverage for duplicate adds
add rebuild coverage for same-timestamp tie-breaking
add rebuild coverage for missing-thread label records being skipped without killing the rebuild
Expected outcome:
a clean rebuild produces stable label state for every thread
label state is deterministic and reproducible from canonical inputs
Slice 3: Read Queries And Rendering
Focus:
expose the materialized labels in board and thread reads
Likely files:
src/ForumRewrite/Application.phptemplates used by board and thread rendering
tests/LocalAppSmokeTest.php
Checklist:
extend thread fetch queries in
Application.phpto selectthread_labels_jsondecode
thread_labels_jsonin the board-page data pathdecode
thread_labels_jsonin the single-thread data pathrender labels on the board thread list
render labels on the thread page near the title or metadata line
keep empty-label rendering unobtrusive
keep post permalinks unchanged unless thread-context rendering already exists there
add smoke coverage proving labels appear on
/add smoke coverage proving labels appear on
/threads/<id>add smoke coverage proving threads with no labels still render cleanly
Expected outcome:
the read model exposes labels to the main user-facing surfaces
label display is visible without needing write-path work
Slice 4: Write Flow For Hashtags
Focus:
emit canonical thread-label records from normal posting flows
Likely files:
src/ForumRewrite/Write/LocalWriteService.phpsrc/ForumRewrite/Canonical/ThreadLabelRecordParser.phpsrc/ForumRewrite/Canonical/CanonicalPathResolver.phptests/WriteApiSmokeTest.php
Checklist:
add hashtag extraction helpers in
LocalWriteServiceignore quoted lines during hashtag extraction
normalize and de-duplicate labels per post before write
keep extracted labels free-form within the canonical token rules
after
createThread()writes a root post, emit one thread-label record if hashtags are presentafter
createReply()writes a reply, emit one thread-label record targeting the thread if hashtags are presentinclude
Author-Identity-IDon label records when availableomit
Reasonfor normal hashtag-derived writes in V1 unless there is already a strong UI reason to populate itparse each generated label record before writing it
commit the post file plus any label record file together in one git commit
update artifact invalidation so the board and target thread refresh when label records are written
ensure no label record is written when no valid hashtags are present
add smoke coverage for thread creation with hashtags
add smoke coverage for reply creation with hashtags
add smoke coverage for quoted-line hashtags being ignored
add smoke coverage for duplicate hashtags collapsing to one stored label
Expected outcome:
normal posting flows can create labels without mutating posts
canonical history contains explicit label events tied to the posting action
Slice 5: Incremental Derived State And Activity
Focus:
avoid forcing a full rebuild after every labeled write and make activity reflect label additions
Likely files:
src/ForumRewrite/ReadModel/IncrementalReadModelUpdater.phpsrc/ForumRewrite/Write/LocalWriteService.phpsrc/ForumRewrite/Application.phpsrc/ForumRewrite/ReadModel/ReadModelBuilder.phptests/WriteApiSmokeTest.phptests/LocalAppSmokeTest.php
Checklist:
decide whether to add a dedicated incremental updater path for
ThreadLabelRecordor deliberately fall back to full rebuild for V1if adding an incremental path, teach
IncrementalReadModelUpdaterhow to updatethreads.thread_labels_jsonkeep incremental label reduction behavior consistent with full rebuild behavior
add activity rows for label additions using the retained V1 activity policy
decide whether label activity needs a new
kindvalue such asthread_label_addensure activity rendering in
Application.phpcan present label updates cleanlyensure hidden/internal thread labels do not leak via activity if the thread is not otherwise visible
add write smoke coverage for labeled writes becoming visible immediately
add write smoke coverage for incremental refresh failure behavior if this path is incrementalized
Expected outcome:
labeled writes show up immediately in derived state
activity behavior matches the retained V1 product decision
Cross-Cutting Notes
Watch for these repo-specific integration points:
CanonicalRecordRepositorycurrently has one loader per record family; follow that pattern rather than wideningPostRecordParserReadModelBuildercurrently derivesthreadsentirely from posts; thread labels will be the first separate thread-metadata family, so keep that reducer isolated and explicitLocalWriteServicecurrently commits one or more canonical files and then refreshes derived state; label-record writes should fit the same transaction boundaryApplication.phpcurrently reads thread rows directly from SQLite; prefer extending existing queries rather than inventing a second label lookup pathWriteApiSmokeTest.phpandLocalAppSmokeTest.phpalready cover thread/reply and activity behavior, so extend those before inventing brand-new smoke harnesses
Suggested Verification Order
Run parser tests after Slice 1.
Run rebuild-focused tests after Slice 2.
Run local app smoke tests after Slice 3.
Run write smoke tests after Slice 4.
Re-run the full relevant test set after Slice 5.
Minimum V1 Merge Bar
Before merging, confirm all of the following:
canonical thread-label records parse and round-trip through the repository
full rebuild stores deterministic
thread_labels_jsonboard and thread pages render labels correctly
thread creation and reply creation emit label records from hashtags
quoted hashtags do not create labels
duplicate hashtags do not create duplicate stored labels
labeled writes refresh derived state correctly
activity behavior for label additions matches the retained proposal