Repository source: docs/plans/archive/forte_profiles_step3_development_plan.md
Forte Profiles — Step 3: Development Plan
_Amended after Stage 1: /forte/user/{username} turned out to be a materially different, bigger page than assumed (classic's username.php aggregates across every profile sharing that username — approved/unapproved separately, plus thread and post lists — not just an alternate lookup key for the same profile content /forte/profiles/{slug} shows). Per explicit user direction, building it anyway rather than simplifying it away; split into its own stage (new Stage 3) instead of folding into the original Stage 2, since it needs its own data-aggregation reuse and template. Stages renumbered accordingly._
Stage 1
Goal: Add a Forte-target author-link variant without touching classic's rendering.
Dependencies: none (Step 2 approved)
Expected changes:
TemplateRenderer::renderAuthorHtml()gains an optionalbool $forteTarget = falseparam (default preserves current classic behavior byte-for-byte); a new$forteAuthorclosure exposed alongside the existing$authorclosure inrenderFile(), calling it with$forteTarget = trueto build/forte/profiles/{slug}//forte/user/{username}instead of classic's paths.Verification approach:
php -l; confirm classic pages using$authorare byte-for-byte unchanged (no template calls$forteAuthoryet, so purely additive).Risks or open questions: none identified.
Canonical components/API contracts touched:
TemplateRenderer::renderAuthorHtml()(extended, default-safe).Status: done — see Step 4 summary.
Stage 2
Goal: Stand up the full Forte-styled single-profile page.
Dependencies: none (independent of Stage 1; nothing links to it yet)
Expected changes: new route
/forte/profiles/{slug}reusingfetchProfileBySlug(); newprofile.php-equivalent Forte template (paned-styled single window, not the full board chrome) showing username, approval status/by-whom, thread/post counts, public key details — no "Approve user" form; includes a link back to/forte.Verification approach:
curlthe new route directly for an approved and an unapproved profile, confirm the right fields render and no approve-action markup appears; confirm classic's own/profiles/{slug}is unaffected.Risks or open questions: none identified.
Canonical components/API contracts touched:
fetchProfileBySlug()(reused unchanged), new route + template.
Stage 3
Goal: Stand up the Forte-styled username aggregate page.
Dependencies: none (independent of Stage 2; different route, different data)
Expected changes: new route
/forte/user/{username}reusingfetchProfilesByUsernameToken()plus the same aggregation classic'srenderUsername()already does (countVisibleAuthoredRows(),fetchVisibleAuthoredThreads(),fetchVisibleAuthoredPosts(), approved/unapproved profile split); newusername.php-equivalent Forte template listing combined thread/post counts, authored threads and posts, and links to each individual/forte/profiles/{slug}; link back to/forte.Verification approach:
curlthe new route for a username with multiple profiles (approved + unapproved), confirm counts/lists match classic's own/user/{username}output for the same username; confirm classic's route is unaffected.Risks or open questions:
Confirm
fetchVisibleAuthoredThreads()/fetchVisibleAuthoredPosts()(or their equivalents) are reusable as-is rather than needing Forte-specific variants — expected yes, since they return plain data rows, not HTML.
Canonical components/API contracts touched:
fetchProfilesByUsernameToken(),countVisibleAuthoredRows(),fetchVisibleAuthoredThreads(),fetchVisibleAuthoredPosts()(all reused unchanged), new route + template.
Stage 4
Goal: Point Forte's own author names at the new profile pages.
Dependencies: Stages 1-3 (helper and both destinations must exist first, so links are never briefly broken)
Expected changes:
paned_board_content_pane.php(root post) andpaned_thread_reply_tree.php(replies) switch from$author(...)to$forteAuthor(...).Verification approach:
curlthe board, confirm author links now point at/forte/profiles/...//forte/user/...; click through in a headless browser for both an approved (username-linked) and unapproved (slug-linked) author, confirm each lands on a working Forte page; confirm classic's thread/board pages (still using$author) are unaffected.Risks or open questions: none identified.
Canonical components/API contracts touched:
paned_board_content_pane.php,paned_thread_reply_tree.php(extended).
Stage 5
Goal: Add the quick-glance summary dialog on author-name click.
Dependencies: Stage 4 (needs real Forte-target links to intercept and to fall back to)
Expected changes: new small dialog (titlebar + body, mirroring the
forte_compose_threadNew Thread<dialog>pattern) added to the board page; JS click-intercepts a Forte author link, fetches/api/get_profile?profile_slug=...(reused unchanged), renders username/approval/counts plus a "View full profile" link to the samehrefthe anchor already had; without JS, the realhrefstill navigates straight to the full page.Verification approach: headless-browser test — click an author name, confirm the dialog opens with correct fetched data instead of navigating away; confirm the "View full profile" link matches the anchor's original
href; confirm keyboard/middle-click still reaches the full page directly (JS never prevents default for those).Risks or open questions:
/api/get_profilereturns plainKey: Valuetext, not JSON — confirm the existing parsing approach already used elsewhere (thread_reactions.js'sparseResponseValue) is reusable here rather than writing a second parser./api/get_profiletakes onlyprofile_slug, not a username token — for an approved author (linked via/forte/user/{username}), the dialog still needs a profile slug to fetch;author_profile_slugis already present on the same record$forteAuthorrenders from, so the anchor can carry it via adata-profile-slugattribute regardless of which URL scheme thehrefuses.
Canonical components/API contracts touched:
/api/get_profile(reused unchanged),paned_board_reader.js(extended).
Stage 6
Goal: Add the Forte-styled user directory and a way to reach it from the board.
Dependencies: Stage 2 (links to the same single-profile pages)
Expected changes: new route
/forte/users/reusingfetchApprovedUserDirectoryUsers(); new paned-styled list template linking each entry straight to its full profile page (no dialog detour); a small nav entry point from the board (e.g. a link near the folder tree or statusbar) to reach it.Verification approach:
curl/forte/users/, confirm approved users list with correct counts and working links; confirm the board's new nav entry point reaches it; confirm classic's/users/is unaffected.Risks or open questions: none identified.
Canonical components/API contracts touched:
fetchApprovedUserDirectoryUsers()(reused unchanged), new route + template,forte_board.php(small nav addition).
Stage 7
Goal: Full regression check.
Dependencies: Stages 1-6
Expected changes: none (verification only)
Verification approach: confirm classic's
/profiles/{slug},/user/{username}, and/users/are byte-for-byte unaffected; confirm no "Approve user" affordance exists anywhere in the new Forte pages; re-verify the existing board regression suite (reactions, permalinks, New Thread, Reply, thread-selection URL sync) is unaffected; confirm summary-dialog, single-profile-page, and username-aggregate-page data all match classic's own numbers for the same profile/username (no drift).Risks or open questions: none identified.
Canonical components/API contracts touched: none new — integration check across Stages 1-6.