Adds a Smarty-rendered HTML page to the Phlix web portal. Every page requires two artifacts that must stay in sync: a .tpl file under public/templates/{section}/ and a renderXxx(Request $request): Response method in src/Server/WebPortal/PageRenderer.php.
-
Pick the page name and section. Method becomes render<Name> (PascalCase). Template path becomes public/templates/<section>/<view>.tpl. Match existing convention: renderHome → home/index.tpl, renderLibrary → library/index.tpl, renderLogin → auth/login.tpl. Use <section>/index.tpl for the section's landing page and <section>/<detail>.tpl for sub-views (mirrors library/index.tpl + library/detail.tpl). Verify the chosen path is not already taken with ls public/templates/<section>/ before proceeding.
-
Create the template file at public/templates/<section>/<view>.tpl. Use this exact skeleton (copied from public/templates/home/index.tpl):
{extends file="layouts/main.tpl"}
{block name="title"}<Page Name> - Phlix{/block}
{block name="main"}
<div class="<section>-page">
<!-- page content -->
</div>
{/block}
Escape user-supplied strings with Smarty's |escape modifier and use |default:'...' for optional fields (e.g. {$user.display_name|default:'Guest'}). For media card lists, reuse {include file="partials/media_card.tpl" item=$item} rather than re-rendering card markup. Verify the template renders by running php -l public/templates/<section>/<view>.tpl won't work (Smarty is not PHP) — instead grep for unbalanced blocks: grep -c '{block' public/templates/<section>/<view>.tpl must equal grep -c '{/block}' ....
-
Add the render method to src/Server/WebPortal/PageRenderer.php. Append it after the last existing render* method, keeping the file alphabetical-by-domain ordering (home → library → login). Use this exact shape (modeled on renderLibrary at lines 148-168):
public function render<Name>(Request $request<, array $params optional>): Response
{
$template = new \Smarty();
$template->setTemplateDir($this->templateDir);
$template->assign('current_page', '<section>');
$html = $template->fetch('<section>/<view>.tpl');
return (new Response())->html($html);
}
Match the docblock style used elsewhere in the file — include @param, @return, @template_variables (list every assigned key), and @example Template: <section>/<view>.tpl. If the route has URL params, add the array<string, string> $params second arg exactly like renderLibrary. Verify with php -l src/Server/WebPortal/PageRenderer.php — must print No syntax errors detected.
-
Register the route in src/Server/WebPortal/WebPortalRouter.php. Web portal HTML routes are wired into the same Router instance used by JSON endpoints. Find the section where existing PageRenderer methods are registered and add the route alongside them. The handler closure receives Request $request (and a $params array if the route has placeholders) and must call $this->pageRenderer->render<Name>($request<, $params>). Verify the route was wired by searching: grep -n 'render<Name>' src/Server/WebPortal/WebPortalRouter.php must return exactly one line.
-
Pass data through assigned services only. PageRenderer's constructor injects LibraryManager, ItemRepository, and PlaybackController. If the new page needs another service, add it as a constructor parameter and a private typed property (follow the pattern of private LibraryManager $libraryManager; at line 33-40), then update the call site that constructs PageRenderer (search: grep -rn 'new PageRenderer' src/ public/). Do not introduce service locators, globals, or require_once inside the render method.
-
Sidebar highlighting. If the new page should appear in the sidebar, edit public/templates/layouts/main.tpl to add a nav <a> whose {if $current_page == '<section>'}active{/if} matches the value assigned in step 3. If the page is a sub-view (detail page), set current_page to the parent section so the parent nav stays highlighted (e.g. library/detail.tpl should still assign 'library').
-
Smoke-test the rendered output. With the dev server running, curl -s -o /tmp/page.html -w '%{http_code}\n' http://localhost:<port>/<route> must return 200, and grep -c 'class="sidebar"' /tmp/page.html must return 1 (proves the layout extended correctly). For 404 paths, curl -s -o /dev/null -w '%{http_code}\n' http://localhost:<port>/<bad-route> must return 404.
-
Add a unit test at tests/Unit/Server/WebPortal/PageRendererTest.php (create the directory if absent — mirrors tests/Unit/Auth/, tests/Unit/Session/, etc.). Test should construct PageRenderer with stubbed dependencies, call render<Name>(), and assert the returned Response has status 200 (or 404 for the missing-resource path) and that the HTML body contains a section-specific marker (e.g. the <div class="<section>-page"> from step 2). Run vendor/bin/phpunit tests/Unit/Server/WebPortal/PageRendererTest.php — must report OK.