Repository source: docs/plans/archive/forte_thread_selection_url_sync_step3_development_plan.md
Forte Thread Selection URL Sync — Step 3: Development Plan
Stage 1
Goal: Resolve the effective selected thread and tag server-side, including the mismatch/invalid-value rules from Step 2.
Dependencies: none (Step 2 approved)
Expected changes: new method, e.g.
resolveForteBoardSelection(array $threads, string $requestedTag, string $requestedSelected): arrayreturning a validated{tag, selectedThreadId}pair — mirrorsresolveForteBoardTag()'s validate-and-fallback shape;selectedThreadIdis''unlessrequestedSelectedmatches a real thread; if it does but that thread doesn't carryrequestedTag, returnedtagis''(drops to All Threads) instead of dropping the selection.Verification approach: unit-level check via a few representative inputs (valid match, valid thread + mismatched tag, nonexistent thread ID, empty/garbage input) confirming the returned pair matches Step 2's stated rules.
Risks or open questions: none identified.
Canonical components/API contracts touched: new resolver method only — no callers wired yet.
Stage 2
Goal: Pre-select the thread's row and enable Reply in the initial HTML.
Dependencies: Stage 1
Expected changes:
renderForteBoard()calls the new resolver and passesselectedThreadIddown;paned_board_thread_list.phprenderspaned-list-row--selected/aria-selected="true"/tabindex="0"for the matching row (mirroring how$selectedTagalready driveshidden); the board's Reply toolbar button renders withoutdisabledwhen a thread is pre-selected.Verification approach:
curl(no JS) a URL with a validselected=, confirm the row's selected classes/attributes and the enabled Reply button appear in the raw response.Risks or open questions: none identified.
Canonical components/API contracts touched:
renderForteBoard()(extended),paned_board_thread_list.php(extended),forte_board.php(Reply button markup extended).
Stage 3
Goal: Pre-show the selected thread's content article.
Dependencies: Stage 1 (Stage 2 not required, independent template)
Expected changes:
paned_board_content_pane.php's per-thread article'shiddenattribute becomes conditional onselectedThreadIdinstead of unconditional.Verification approach:
curlthe same URL, confirm only the target thread's article lackshiddenand the placeholder article gains it instead (or vice versa when nothing is selected).Risks or open questions: none identified.
Canonical components/API contracts touched:
paned_board_content_pane.php(extended).
Stage 4
Goal: Pre-highlight the requested reply within the selected thread.
Dependencies: Stages 1 and 3 (need a resolved thread to validate the reply against)
Expected changes:
created_post_idis validated against the resolved thread's own reply posts (ignored if it belongs to a different thread or doesn't exist) and threaded down topaned_thread_reply_tree.php, which appliespaned-highlight-newto the matching node's initial markup.Verification approach:
curla URL with a validselected=+created_post_id=pair, confirm the target reply node carries the highlight class in the raw response; confirm acreated_post_idbelonging to a different thread is silently ignored.Risks or open questions: none identified.
Canonical components/API contracts touched:
paned_thread_reply_tree.php(extended),renderForteBoard()(passes the validated ID through).
Stage 5
Goal: Push/replace the URL on interactive thread selection, per the three-mode
localStoragepreference.Dependencies: none (client-side only; independent of Stages 1-4)
Expected changes:
urlForState()gains the current thread selection; ahistory-moderead helper (try/catch-wrapped, matching this project's existinglocalStoragepreference pattern) resolves to"click-only"/"always"/"never"; the row-click handler and the keyboard/Prev-Next stepping paths callpushStateIfChangedor a newreplaceStateIfChangedcounterpart according to that mode.Verification approach: headless-browser test per mode — default (
"click-only"): click pushes (Back works), stepping replaces (no history growth);"always": stepping also pushes;"never": clicking also replaces.Risks or open questions: none identified.
Canonical components/API contracts touched:
paned_board_reader.js(extended:urlForState, click/stepping handlers, newlocalStorageread).
Stage 6
Goal: Restore thread selection on Back/Forward (no server round-trip).
Dependencies: Stage 5
Expected changes: the existing
popstatelistener (currently tag/sort only) also readsselected=/created_post_id=from the now-current URL and calls the existingselectThread()/highlight logic, matching what the initial-load path already does.Verification approach: headless-browser test — click two different threads (pushing history each time under
"always"or after a click under default mode), press Back, confirm the previous thread is restored without a network request.Risks or open questions: none identified.
Canonical components/API contracts touched:
paned_board_reader.js(popstatehandler extended).
Stage 7
Goal: Full regression check.
Dependencies: Stages 1-6
Expected changes: none (verification only)
Verification approach: confirm the flash is gone for existing permalink/reply-redirect URLs (not just new ones) via
curl; re-verify tag/sort URL syncing is unaffected; re-verifyforte_reactionsandforte_post_permalinkflows end-to-end; confirm all threelocalStoragemodes behave correctly together with the mismatch/invalid-value fallback rules; confirm board load timing at current volume stays reasonable with the added server-side resolution work (no new queries expected — resolution works over already-fetched$threads/reply data).Risks or open questions: none identified.
Canonical components/API contracts touched: none new — integration check across Stages 1-6.