| name | page-route |
| description | Build or update a route page under src/pages using a consistent 6-file contract per route basename (`<route>_page`). Use when creating a new page route, refactoring an existing page route, or adding stories/tests/docs for a page. |
Page Route
Overview
Build each route page as six colocated files: .html, .ts, .stories.ts, .spec.ts, .test.ts, and .context.md.
Use a <template> in HTML, keep logic in TypeScript, and colocate both unit and integration tests in the page route directory.
Workflow
- Create or update page files under
src/pages/<route>/ with basename <route>_page.
- Ensure these files exist together:
src/pages/<route>/<route>_page.html
src/pages/<route>/<route>_page.ts
src/pages/<route>/<route>_page.stories.ts
src/pages/<route>/<route>_page.spec.ts
src/pages/<route>/<route>_page.test.ts
src/pages/<route>/<route>_page.context.md
- Put all markup and Tailwind classes in the HTML
<template>.
- Keep state, event wiring, and composition in the TS factory.
- Use data attributes in HTML for TS hooks.
- Ensure all page-level interactive controls prevent mobile double-tap zoom by applying
touch-manipulation (or equivalent touch-action: manipulation) to tap targets (button, a, [role="button"], and clickable wrappers).
- Export a page factory that returns a root element (for example,
LandingPage()).
- Add Storybook stories in
.stories.ts with Default and Dark variants.
- Add unit tests in
.test.ts for logic implemented in <route>_page.ts
(state, branching, DOM updates, and local handlers) using Vitest/jsdom and
mocks for external modules.
- Add Playwright integration tests in
.spec.ts for browser route behavior
(navigation, route rendering, and user-visible flows).
- Keep Playwright page specs colocated in
src/pages/<route>/; reserve tests/ for broader e2e flows.
- Document the page in
.context.md using the required documentation structure.
- Before completing the task, run
python3 .agents/skills/page-route/scripts/verify_pages.py and treat any missing .stories.ts as a blocking failure.
Page Route Contract
- Naming:
- Basename must be
<route>_page.
- All six files share the same basename.
- Location:
- All six files are colocated in
src/pages/<route>/.
- HTML:
- Wrap page markup in one
<template> root.
- Keep structure and Tailwind-only styling in HTML.
- TypeScript:
- Import HTML with
?raw, clone template content, and return one root element.
- Compose page-level components and route actions here.
- Unit test (
.test.ts):
- Validate logic in
<route>_page.ts: template cloning, local state,
branch behavior, and DOM updates.
- Prefer mocked dependencies; avoid turning
.test.ts into full route/e2e
coverage.
- Integration test (
.spec.ts):
- Validate route availability and key user flows with Playwright in a real
browser context.
- Storybook (
.stories.ts):
- Must import
@storybook/html.
- Must include both
Default and Dark stories.
- Docs (
.context.md):
- Must explain page contents, data flows, unit tests, and integration tests.
Documentation File
Write src/pages/<route>/<route>_page.context.md in English with these sections:
# <RouteName> Page
## Overview
What this page is for and where it sits in navigation.
## Page Contents
- Main sections and interactive regions.
## Data Flow
1. How data enters the page.
2. Which modules/services are called.
3. What updates the UI.
## Unit Tests
- Bullet for each test in `<route>_page.test.ts`.
- Emphasize WHAT logic in `<route>_page.ts` is validated and HOW it is tested
(inputs/mocks/actions/assertions).
## Integration Tests
- Bullet for each scenario in `<route>_page.spec.ts`.
- Emphasize WHAT browser flow is validated and HOW Playwright asserts it.
Minimal File Pattern
Use this baseline unless the repo already has a stronger route pattern.
<route>_page.html
<template>
<main data-page="<route>" class="...">
<h1 data-role="title" class="..."></h1>
<section data-slot="content" class="..."></section>
</main>
</template>
<route>_page.ts
import templateHtml from './<route>_page.html?raw';
export function RoutePage(): HTMLElement {
const template = document.createElement('template');
template.innerHTML = templateHtml;
const templateElement = template.content.firstElementChild;
if (!(templateElement instanceof HTMLTemplateElement)) {
throw new Error('Route page template element not found');
}
const root = templateElement.content.firstElementChild?.cloneNode(true);
if (!(root instanceof HTMLElement)) {
throw new Error('Route page template root not found');
}
return root;
}
<route>_page.stories.ts
import type { Meta, StoryObj } from '@storybook/html';
import { RoutePage } from './<route>_page';
const meta: Meta = {
title: 'Pages/RoutePage',
parameters: {
layout: 'fullscreen',
},
};
export default meta;
type Story = StoryObj;
export const Default: Story = {
render: () => RoutePage(),
globals: {
theme: 'light',
},
};
export const Dark: Story = {
render: () => RoutePage(),
globals: {
theme: 'dark',
},
parameters: {
backgrounds: {
default: 'dark',
},
},
};
<route>_page.test.ts
import { describe, expect, it } from 'vitest';
import { RoutePage } from './<route>_page';
describe('route page', () => {
it('renders a root element', () => {
const page = RoutePage();
expect(page).toBeInstanceOf(HTMLElement);
});
});
<route>_page.spec.ts
import { expect, test } from '@playwright/test';
test('route page renders', async ({ page }) => {
await page.goto('/<route>');
await expect(page.getByRole('main')).toBeVisible();
});
Verification Script
Run the repository check script to ensure every discovered page route has all
required files:
python3 .agents/skills/page-route/scripts/verify_pages.py
Implementation Notes
- Keep route files together in one folder to simplify ownership and refactors.
- Keep page docs technical and concise.
- Prefer explicit DOM querying scoped to the page root.
- Do not move page
.spec.ts files into tests/; keep them in the page route.
- For large repeated rows/cards, prefer
content-visibility: auto with a
reasonable contain-intrinsic-size placeholder to defer offscreen rendering
while keeping scroll geometry stable (see search page card rendering pattern).