` from `localStorage`; the bundled list is server-rendered and not replaced by JS.
+
+- [ ] **Step 1: Add a new test for the bundled list**
+
+Add the following test method to `tests/Integration/GetHomePageTest.php`, immediately before the closing brace of the class. The existing `testItReturnsTheHomePageWithSecurityHeaders` test remains unchanged.
+
+```php
+ public function testItRendersTheBundledScenariosListFromTheManifest(): void
+ {
+ $tmpDir = sys_get_temp_dir() . '/bf-home-bundled-' . bin2hex(random_bytes(4));
+ mkdir($tmpDir, 0700, true);
+ file_put_contents(
+ $tmpDir . '/manifest.json',
+ json_encode([
+ 'skirmish' => ['name' => 'Skirmish', 'summary' => 'Open ground.'],
+ 'hold-the-line' => ['name' => 'Hold the Line', 'summary' => 'Defend.'],
+ ], JSON_THROW_ON_ERROR),
+ );
+ foreach (['skirmish.json', 'hold-the-line.json'] as $file) {
+ file_put_contents($tmpDir . '/' . $file, '{}');
+ }
+
+ $handler = new GetHomePage($tmpDir);
+ $request = new Request([], [], [], [], [], '', 'GET', '/', 'text/html', '');
+ $response = $handler->handle($request, []);
+
+ self::assertSame(200, $response->status);
+ self::assertStringContainsString('id="bf-bundled"', $response->body);
+ self::assertStringContainsString('data-bf-bundle="skirmish"', $response->body);
+ self::assertStringContainsString('data-bf-bundle="hold-the-line"', $response->body);
+ self::assertStringContainsString('Open ground.', $response->body);
+ self::assertStringContainsString('Defend.', $response->body);
+
+ unlink($tmpDir . '/manifest.json');
+ foreach (['skirmish.json', 'hold-the-line.json'] as $file) {
+ unlink($tmpDir . '/' . $file);
+ }
+ rmdir($tmpDir);
+ }
+```
+
+Add a new test `testItReturnsTheHomePageWithSecurityHeaders` next to the existing one — that one is no longer the only test. Keep the old test intact (it still passes — the existing assertions are still true on the new view). Wrap the existing test in a separate method, and keep its body as is. The file's structure should now contain two test methods.
+
+- [ ] **Step 2: Run the test to verify it fails**
+
+Run: `vendor/bin/phpunit tests/Integration/GetHomePageTest.php`
+Expected: FAIL on the new test because `GetHomePage` does not yet accept a scenarios dir and the home view does not render the bundled list.
+
+- [ ] **Step 3: Update `GetHomePage` to accept the scenarios dir and pass it to the view**
+
+Replace `src/Http/Handlers/GetHomePage.php` with:
+
+```php
+ $params */
+ public function handle(Request $request, array $params): Response
+ {
+ $csrf = $request->cookies['__csrf'] ?? '';
+ $bundled = $this->loadBundledScenarios();
+
+ ob_start();
+ require_once __DIR__ . '/../../Views/layout.php';
+ require __DIR__ . '/../../Views/home.php';
+ $body = (string) ob_get_clean();
+
+ return Response::html(200, $body);
+ }
+
+ /** @return list
*/
+ private function loadBundledScenarios(): array
+ {
+ if ($this->scenariosDir === '' || !is_file($this->scenariosDir . '/manifest.json')) {
+ return [];
+ }
+ $decoded = json_decode((string) file_get_contents($this->scenariosDir . '/manifest.json'), true);
+ if (!is_array($decoded)) {
+ return [];
+ }
+ $entries = [];
+ foreach ($decoded as $id => $entry) {
+ if (!is_string($id) || !preg_match('/^[a-z0-9-]+$/', $id) || !is_array($entry)) {
+ continue;
+ }
+ if (!isset($entry['name'], $entry['summary'])) {
+ continue;
+ }
+ if (!is_file($this->scenariosDir . '/' . $id . '.json')) {
+ continue;
+ }
+ $entries[] = [
+ 'id' => $id,
+ 'name' => (string) $entry['name'],
+ 'summary' => (string) $entry['summary'],
+ ];
+ }
+ usort($entries, static fn (array $a, array $b): int => strcmp($a['id'], $b['id']));
+ return $entries;
+ }
+}
+```
+
+Note: the `$bundled` local variable inside `handle` is captured by the `ob_start` / `require` block (the view runs in `handle`'s scope), so the view sees the local without any explicit pass. No further wiring is required.
+
+- [ ] **Step 4: Update the home view to render the bundled list**
+
+Replace `src/Views/home.php` with:
+
+```php
+ $bundled Provided by GetHomePage. */
+?> Escape::html($v);
+ $a = static fn (string $v): string => Escape::attr($v);
+ ?>
+ BattleForge
+ New scenario
+ Play bundled
+
+
+ -
+
+
+
+
+ Recent scenarios
+
+
+ )` with:
+ - The shared `layout.php` chrome.
+ - A header bar: `