Repository source: docs/plans/qdb_numeric_permalinks/qdb_numeric_permalinks_step4_implementation_summary.md
QDB Numeric Permalinks — Step 4: Implementation Summary
Stage 1 - Server-side numeric resolution
Changes:
Added
ThreadRepository::byQdbQuoteNumber(PDO $pdo, int $number): ?string— resolves a quote number to its fullroot_post_id, using the sameCAST(substr(root_post_id, instr(root_post_id, '-qdb-')+5) AS INTEGER)extraction already verified in the quote-numbering feature, as an equality check instead ofMAX.Extended
Application::handle()'s existing last-resort qdb bare-path block: when the directThreadRepository::byId()lookup misses and the path segment is all digits (ctype_digit), falls back tobyQdbQuoteNumber(); redirects to/threads/<resolved-id>on a hit, exactly as the existing exact-ID case already did. No change to behavior when the path isn't all digits or isn't the qdb profile.
Verification:
php -lon both changed files — no syntax errors.Added three tests to
tests/QuoteCardDisplayNumberTest.php:testBareNumericPathRedirectsToTheQuoteWithThatNumber— a seeded quote numbered 42:GET /42returns302with the redirect body linking to/threads/thread-...-qdb-42.testBareNumericPathWithNoMatchingQuoteStill404s—GET /999999still404s.testBareNumericPathOnNonQdbProfileDoesNotRedirect— same/42request on the default (zenmemes) profile still404s, confirming the qdb-only gate is unchanged.
./v3 test QuoteCardDisplayNumberTest— 5 run, 5 passed (existing 2 plus the 3 new).Full suite
./v3 test— 683 run, 676 passed, 7 failed; the 7 failures are the same pre-existing set already tracked before this change (including the same order-dependenttestIncrementalApprovalMatchesFreshRebuildForTransitiveApprovalAndScoreRefreshflake noted in the prior feature's Step 4 summary). No failures introduced by this change.
Notes: the Key Risks "numeric fallback shadowing a real route" item is addressed by construction — the new check lives strictly inside the already-last-resort block, after the existing exact-ID check, not as a new earlier route.
Stage 2 - Short link in quote_card.php
Changes:
templates/partials/quote_card.php: anchorhrefis now/<displayNumber>when a-qdb-<N>suffix was actually matched, otherwise unchanged/threads/<full-id>for any quote predating the numbering convention. Also corrected an adjacent comment that was stale since the quote-numbering feature shipped (it said live-authored quotes never carry a-qdb-suffix; they do now).
Verification:
php -l— no syntax errors.Added four tests to
tests/QuoteCardDisplayNumberTest.php:testQuoteCardLinksToTheShortNumericPermalinkOnLatest— a numbered quote's card on/latestlinkshref="/42", not the full ID.testQuoteCardLinksToTheShortNumericPermalinkOnTopSearchAndRandom— the same holds on/top,/search, and/random(all three reusequote_card.php, confirming the shared-partial change is consistent across every consumer, not just/latest).testQuoteWithoutAQdbNumberKeepsItsFullIdPermalink— the base fixture's un-numberedroot-001thread keeps its old/threads/root-001link.testFollowingTheShortNumericPermalinkReachesTheUnchangedQuotePage—/42302s to/threads/thread-...-qdb-42, and that page's rendered content is byte-identical to requesting it directly.
./v3 test QuoteCardDisplayNumberTest— 9 run, 9 passed.Full suite
./v3 test— same 7 pre-existing failures as Stage 1's baseline, no new ones.Live click-through: started a real local server (
php -Sviapublic/router.php) against a disposable scratch qdb repository, submitted a real quote (minted#1), confirmedGET /latestshowshref="/1"for it andhref="/threads/root-001"unchanged for the un-numbered fixture thread, confirmedGET /1returns302andGET /999999returns404. Server stopped afterward; scratch repo/db disposable (job tmp dir), nothing persisted.
Notes: both remaining Key Risks (shared-partial affecting every consumer; un-numbered quotes needing their old link preserved) are covered directly by this stage's test list, not left as assumptions. Feature complete — Completion Contract satisfied end to end, no follow-up cycle required.