| name | yhttp-ssr |
|---|---|
| description | Build, extend, diagnose, and review server-side-rendered YHTTP applications that use Mako templates and YHTTP extensions. Use for localized page routes, shared layouts, forms, metadata, asset integration, Alpine.js progressive enhancement, authentication and OAuth2 provider flows, persistence-backed SSR, route or model registration, RTL/LTR behavior, and bddrest page tests in an existing YHTTP project. |
Implement SSR work through the target project's existing YHTTP composition, Mako, localization, assets, and test conventions. Preserve the installed framework and extension stack.
This skill is self-contained and project-neutral. The target repository is the source of truth: do not assume a package manager, directory name, locale set, authentication provider, database, frontend tool, or deployment platform until the repository confirms it.
- Read repository instructions and the canonical setup, architecture, dependency, and command files, including
Makefilewhen present. - Inspect the installed YHTTP version and extension pins. Read the matching current official YHTTP documentation and repositories for extensions involved in the task; prefer verified APIs over memory.
- Trace the composition root, settings merge, readiness callback, model imports, route-registration imports, relevant handler, inherited template, browser entry point, and tests.
- Find the nearest complete feature and follow it end to end.
- Inspect the worktree before editing. Record pre-existing changes exactly; never call the worktree clean unless status output is empty. Preserve unrelated changes and existing behavior.
- Present a short plan covering rendering, routing, localization, browser behavior, data/auth boundaries, and tests.
- Treat the repository
Makefile, when present, as the canonical developer command contract; read its variables, included rules, virtual-environment prefix, and feature-specific targets before running commands or adding new ones. - Reuse existing targets instead of duplicating their commands in documentation or one-off shell scripts. Add a target only when the workflow is a repeatable project concern and keep its naming consistent with neighboring targets.
- Use the repository's existing targets for formatting, linting, tests, running the development server, and localization compilation when those workflows exist. Do not require Make, makelib, or a particular package manager in a project that does not use them.
- When reporting verification, name the Makefile targets run. If a target cannot run because an environment prerequisite is absent, run the narrow underlying check when safe and report the missing prerequisite explicitly.
- Keep static presentation changes in Mako and existing CSS utilities.
- Add an Alpine view module only for focused browser state or interaction.
- Put endpoint calls in the project's service layer; do not place raw fetch workflows in templates.
- Add handlers, guards, persistence, and transactions only when server state changes.
- Keep business rules and multi-step operations out of templates.
- Do not add another web framework, client-side application shell, or build system.
- Install extensions and merge built-in settings before readiness.
- Import models so metadata is registered, then import route modules so decorators execute.
- Treat an unimported handler or model module as inactive.
- Preserve extension ownership of templates, localization, authentication, media, database sessions, and static files.
- Keep application construction, extension installation, settings merging, and readiness hooks in the composition root. Configure development asset serving, production manifest serving, runtime media, public files, and error handling there according to the target project's settings.
- Keep public and administration document shells separate when the application has both. Public handlers should inherit the public shell; administrative handlers should inherit the administrative shell rather than mixing layout responsibilities.
- Register regex paths with
@app.route(...); accept capture groups in the handler signature. - Map each handler function name to its YHTTP verb: a
GETroute must usedef get(...),CREATEmust usedef create(...),UPDATEmust usedef update(...),DELETEmust usedef delete(...), andREFRESHmust usedef refresh(...). Do not replace these names with descriptive names such aslist_page; the verb-aligned function name is part of the YHTTP route contract. - Apply body and query guards before handler logic. Choose strictness explicitly and update tests with accepted inputs.
- Render pages with
@app.template(...)and return context dictionaries. - Use the project's status and JSON decorators for non-page responses; never expose internal exceptions.
- Reuse installed authentication decorators and session APIs. Do not invent role, cookie, CSRF, or token policy.
- For a page with a captured identifier, keep the route, guards, template,
database lookup, not-found response, and returned context together. A typical
shape is
@app.route(...), strict body/query guards,@app.template(...), a session-scoped lookup, and a context dictionary. Confirm the target YHTTP capture syntax and status factories instead of copying a framework version's example blindly.
- Treat the installed
yhttp-authextension as the owner of access tokens, refresh tokens, cookies, CSRF tokens, OAuth state tokens, Redis blacklist state, and authentication decorators. Useapp.auth.session_new(),session_refresh(),session_delete(),token_fromcookie(),oauth2_session_new(), andoauth2_session_verify()instead of setting cookies or signing tokens in application code. - Trace the actual installed extension version before importing exceptions.
For example, yhttp-auth 11.1 exposes
TokenMissmatchErrorwhile newer versions may exposeTokenMismatchError; a compatibility import may be required during a supported-version transition. Dependency declarations may also specify a different yhttp-auth major range; reconcile the declared and installed versions before relying on an exception or helper. - Keep provider-specific code in a small module such as
auth/oauth2.py. It should build the provider authorization URL and exchange the authorization code; route handlers should validate requests, update the member, create the application session, and redirect. - Configure OAuth under
app.settings.auth.oauth2, separating the signed state-token settings from provider settings. A Google configuration needs an authorization URL, token URL, client ID, and client secret. Keep secrets in protected deployment configuration or a secret manager; never copy real values into a skill, test fixture, log, exception, URL, or commit.
- The public SSR page is
GET /signin. Its optionalthenquery value is validated with the project's path guard and strict query/body guards. The template renders a URL-encoded link to/apiv1/tokens?then=<destination>; it does not construct the Google URL or OAuth state in Mako. GET /apiv1/tokensfirst validatesthenand attemptsapp.auth.session_refresh(req). A valid refresh cookie creates a new application session and redirects tothen. Missing, malformed, or expired refresh tokens start Google sign-in instead.- The start helper reads the configured
publicurl, creates a CSRF cookie and signedOAuth2StateTokenthroughapp.auth.oauth2_session_new(req, redirect, payload), and places that token in Google'sstatequery parameter. The state contains the CSRF-token value and the encoded return path; do not put secrets or unnecessary identity data in it. Preserve only validated local relative paths to prevent an open redirect. - The authorization request uses
response_type=code, scopeopenid email profile, the exact callback URI<publicurl>/apiv1/oauth2/callbacks/google,access_type=offline, andprompt=consent. If a refresh cookie can be decoded with expiration checks disabled, itsidis passed aslogin_hint; verify that this is actually a provider email in the application's token model before copying this behavior. If application tokens use a numeric local member ID, do not pass that ID as a provider email hint; use a verified provider email instead. GET /apiv1/oauth2/callbacks/googlestrictly requirescodeandstate. Calloauth2_session_verify()before contacting Google. It checks that the state JWT is valid and unexpired and that its embedded CSRF value exactly matches theyhttp-csrftokencookie. Convert missing, malformed, expired, or mismatched state to an application 401; do not continue to token exchange.- Exchange the code server-side with a POST containing
code,client_id,client_secret, the same exact callback URI, andgrant_type=authorization_code. Treat non-200 responses, network failures, invalid JSON, missingid_token, missing required provider tokens, and malformed JWTs as an unauthorized provider failure. Use an explicit HTTP timeout and avoid logging response bodies or credentials. - The callback extracts the provider email, optional name, locale, and avatar,
then updates an existing
Memberinside the project's explicit database transaction. An application may deliberately update an existing member rather than auto-provisioning one: an OAuth identity that is not already registered then receives 401 and no application session. Preserve that allowlist policy unless account provisioning is explicitly designed and tested. - After the member update, call
member.token_create()andapp.auth.session_new(req, accesstoken)so yhttp-auth sets the access and refresh cookies. Do not persist a provider access token in the application JWT. If the application stores provider access tokens, refresh tokens, provider markers, or avatars on its member record, treat those fields as sensitive and consider encryption, redaction, rotation, and least-privilege access before adopting the pattern. - Redirect using the validated
redirecturlrecovered from the verified state. Never trust a raw callback query parameter for the final destination, and never let a failed callback create cookies or update the member.
- Some applications detect loopback hosts such as
localhost,127.0.0.1,::1, or0.0.0.0and bypass the provider with a seeded development identity. If the target project has this convenience, keep it limited to explicit development environments and registered identities, unreachable in production, and covered by tests. It is not a general authentication pattern. - The authorization request and code exchange must use the same callback URI,
and that URI must exactly match the provider console registration. Set
production
publicurlto the externally reachable HTTPS origin; do not leave the built-in loopback value in effect. Verify proxy/forwarded-host handling rather than deriving OAuth URLs from an untrusted request host. - Check the deployed
auth.domain,secure,httponly,samesite, andpathsettings. A development default may use a loopback cookie domain and non-secure cookies for local HTTP, while a deployment override may not replace every cookie setting. A production host with a loopback cookie domain orsecure: falsecan prevent the session from working or expose it over HTTP. - The transient CSRF cookie must be sent on the top-level cross-site redirect
from the provider.
SameSite=Strictcan suppress that cookie on a Google callback; test the real browser flow and use the narrowest safe policy (oftenLaxfor this transient cookie) if the installed extension and threat model require it. Keep access/refresh cookie policy separate from OAuth callback state policy.
- A permissive implementation may decode the Google
id_tokenwith signature verification disabled and only check thatemailexists. Do not copy this shortcut. Verify the signature against Google's rotating JWKS and validate issuer, audience/client ID, expiration, issued-at, nonce when used, and the required identity claims. Decide explicitly whetheremail_verifiedis required. - The current authorization request does not use PKCE or a nonce, and
yhttp-auth's state verification is integrity/expiry/CSRF checking rather than a one-time-consumption store. Add PKCE, nonce, and replay prevention when the provider and application threat model require them; at minimum, document these as deliberate decisions and test them. - Provider error callbacks (
error,error_description, and related fields) need a user-safe failure path; a guard that requirescodemay otherwise turn a user cancellation into a generic 400. Do not expose provider response bodies or internal exception details. - Refresh-token responses may omit a refresh token on later consent. Preserve an existing stored refresh token when the provider intentionally omits a replacement, or define a tested failure policy; do not blindly overwrite a usable token with null.
- OAuth state is signed but visible to the browser and provider. Keep its payload minimal, use short expiry/leeway values, and test missing CSRF cookie, invalid signature, expiration, mismatch, replay, invalid destination, and provider denial.
- Inherit the correct shared document shell; keep public and administration layouts separate when the project does.
- Use template-provided helpers for translations, locale data, settings, request data, and asset URLs.
- Escape user-controlled text. Render stored HTML without escaping only when the application contract explicitly trusts it.
- Keep templates presentational and semantic. Add accessible names to icon-only controls and descriptive titles to embedded content.
- Reuse existing components and utility classes before adding new CSS.
- Wrap visible translatable strings in the project's translation helper.
- Update and compile catalogs with the repository's canonical commands when strings change.
- Verify every supported locale, localized links, and canonical path behavior.
- Use logical CSS directions and locale direction from template context. Isolate phone numbers, email addresses, and similar LTR tokens where needed.
- Test both LTR and RTL output rather than inferring one from the other.
- Discover the project's locale mapping and route-rewrite exclusions before adding localized routes. API, asset, media, health, and selected root paths are often intentionally unlocalized. Pass the configured translation helper, locale data, request, settings, and asset resolver to templates through the existing Mako integration rather than recreating them.
- Build internal links with the active locale when the project prefixes public
paths. Use
dir="ltr"or an equivalent isolation rule only for values whose intrinsic direction must remain stable, such as phone numbers, email addresses, and technical identifiers.
- Keep the SSR page useful before JavaScript where practical.
- Identify the project's browser source tree and preserve its existing layout.
If it uses the common
www/convention, shared bootstrap and styles may live inwww/master.jsandwww/master.css, page-specific controllers inwww/views/, API clients inwww/services/, and source images inwww/images/; these names are conventions, not requirements. - When Alpine is installed, keep its state in the established view directory
and network calls in resource services. Register shared behavior in the
existing bootstrap; import page controllers through the configured asset
entries and register them with
Alpine.data(...). - Use Alpine as progressive enhancement, not a client-side application shell.
Mako must render the usable HTML and initial values first; Alpine then
hydrates elements marked with
x-dataafter the shared bootstrap callsAlpine.start(). - Pass initial state from Mako as safely serialized data to
x-data(or an equivalent data attribute), and make the controller tolerate missing or empty values. Do not duplicate server truth by fetching initial state again on hydration. - Keep hydration side effects small and explicit: use Alpine lifecycle hooks
or event bindings for browser-only setup, and preserve the server-rendered
fallback while assets are loading. Use
x-cloakfor elements that must stay hidden until Alpine initializes. - Resolve Vite-managed assets through the project's asset helper; do not hard-code development-server or fingerprinted production paths. If another bundler is installed, follow its resolver instead. Check both development URLs and production-manifest resolution when changing the asset boundary.
- Read the repository's frontend manifest before changing browser code. This
may be
package.jsonor another project-selected manifest; it is the source of truth for the runtime, package manager, dependencies, scripts, and lock file. Add a dependency only when the existing browser stack cannot support the feature, and update its lock file with the selected package manager. - Treat the configured asset bundler, such as
vite.config.*, as the asset-boundary contract. Preserve its development-server settings, plugins, manifest output, build directory, CSS/module-preload policy, and explicit or globbed input entries. A new browser module, stylesheet, or source image must be included by the configured inputs before a template references it. - Keep the project's shared browser bootstrap and stylesheet as shared entries.
They may contain utility-CSS source directives, template scan paths, custom
fonts, and global Alpine helpers such as
[x-cloak]; do not move page-specific rules into shared files without a cross-page need. - Use the repository's formatter configuration, such as
.prettierrc, as the formatting contract. Preserve its quote, width, indentation, XML, and class-sorting settings. Run the repository's frontend-format target after frontend edits and inspect formatter changes for unrelated rewrites. - Use the repository's lint configuration, such as
eslint.config.*, as the lint contract. Fix lint/configuration issues in the appropriate source file instead of suppressing them broadly. - Keep browser service modules limited to endpoint clients and shared request/error handling; keep view modules limited to browser state; keep Mako responsible for SSR markup and initial state. Do not put raw fetch flows in templates or duplicate service logic in each view.
- Use the project's named frontend targets rather than ad-hoc package-manager commands when those workflows are required. A production build must produce the manifest and assets expected by the server-side asset resolver, and generated output must not be treated as source.
- Use the installed database session context and the project's explicit transaction pattern.
- Validate before mutation and keep transaction boundaries visible.
- Add and register a model only when persistence is required; include the project's migration workflow.
- Protect page and API handlers with the established decorators and cover unauthenticated and forbidden states where applicable.
- Extend the nearest bddrest or project-native test instead of relying on template snapshots alone.
- Cover localized redirects, successful rendering, strict query/body rejection, missing resources, and auth states relevant to the change.
- Assert meaningful output: route URLs, localized text, direction-sensitive markup, resolved assets, semantic attributes, and persisted effects.
- Test browser behavior at the strongest available layer. If no browser harness exists, cover the server-rendered contract and keep client logic small.
- For OAuth flows, test the authorization URL parameters, exact callback URI, state expiry/signature, CSRF-cookie presence and mismatch, provider failure and malformed-token handling, registered versus unregistered identities, persisted profile/token updates, session cookies, redirect destination, refresh, logout, and the local-development branch if one exists. Use mocked provider HTTP and deterministic time; never use live credentials.
- Add a regression assertion for every bug fix.
- Read and use the repository's canonical targets or commands for the required formatter, localization compiler when applicable, lint, focused tests, and full coverage suite.
- Run database rebuild or migration checks only when required and only against a confirmed disposable database.
- Inspect the final diff and status; remove formatter noise and retain user-owned changes.
- Report the behavior delivered, checks run, and any remaining browser, deployment, or migration uncertainty.
- Before every commit, run the project's complete coverage command and require the reported total coverage to be exactly 100%. A missing, failed, partial, or unavailable coverage run does not satisfy this gate.
- Add behavioral tests for uncovered lines and branches. Never lower a coverage threshold, omit source files, add coverage pragmas, or weaken assertions just to reach 100%.
- Include the coverage command and its 100% result in the commit handoff. Do not create the commit until the full suite and coverage gate pass.