| name | frontend-form-validation |
| description | Use when changing solve inputs, limits, validation, scanner review controls, or form submission behavior. |
Generated from ai/registry.json. Do not edit manually.
Canonical skill: ../../../ai/skills/frontend-form-validation.md.
Referenced context:
../../../ai/rules/frontend-rules.md
../../../ai/rules/frontend-form-rules.md
../../../ai/rules/frontend-quality-rules.md
../../../ai/architecture/api-boundary.md
This file is compiled from canonical AI knowledge files. Edit canonical files under ai, then run npm run ai:sync.
Compiled AI Skill: frontend-form-validation
Canonical Skill: ai/skills/frontend-form-validation.md
Frontend Form Validation
Use for solve controls, inputs, local limits, scanner review controls, and form submission behavior.
Read First
ai/rules/frontend-rules.md
ai/rules/frontend-form-rules.md
ai/rules/frontend-quality-rules.md
ai/architecture/api-boundary.md
Workflow
- Separate lightweight UX validation from API/engine semantics.
- Choose local state or existing RHF/Zod usage based on the actual form, not a mandatory setup.
- Keep notation forms raw-state-free and scan corrections inside typed scan sessions.
- Verify accessible errors, blocked invalid requests, API outcomes, and relevant E2E behavior.
Referenced Context
Reference: ai/rules/frontend-rules.md
Frontend Rules
Boundaries
- Keep solving, notation semantics, puzzle validity, and replay verification in Rust. React renders and coordinates typed product workflows.
- Keep HTTP mechanics and normalization in
apps/web/src/api; UI consumes domain hooks/adapters.
- Typed scan-session contracts MAY contain reviewed stickers, confidence, and manual overrides. Notation solve UI MUST NOT expose facelet, Kociemba, or raw cube-state inputs.
- Keep API load, form, page workflow, solve result, scanner review, and visualization playback state separately owned unless a focused page owner coordinates them.
- Use the active
@rubiks-cube-solver/rubiks-cube package as a visualization adapter, never as canonical solver state.
Web Runtime And Locales
- Preserve static rendering for indexable routes,
hydrateRoot for generated markup, and SPA navigation after hydration.
- Keep
en-US canonical without a prefix. Publish/index any of the nine supported locales only when its visible and SEO content is fully translated with placeholder parity.
- Treat route slugs as stable identifiers. They need not be English and MUST NOT vary by locale; slug changes require redirects and canonical planning.
- Keep route/page code in its bounded context and shared code behind a demonstrated cross-context consumer.
Existing Patterns
- Prefer local state first, React Query for server state, and existing scoped Zustand stores only for genuinely shared client state.
- Use existing Radix-backed primitives for complex interaction semantics. Use the shared
cn helper in shadcn-style primitives and established classnames as cls in feature code when Tailwind conflict resolution is unnecessary.
- React Hook Form and Zod MAY be used when nearby code or form/schema complexity warrants them; they are not mandatory setup.
- New dependencies require the concrete checks in
frontend-quality-rules.md.
React Compiler
apps/web uses React 19 with React Compiler enabled through the Vite React compiler preset. Write ordinary components and hooks and let the compiler provide memoization.
- Do not add
useMemo, useCallback, React.memo, or other manual render memoization. Do not make referential identity a correctness requirement for effects, subscriptions, or child props.
- Do not use
forwardRef. React 19 components accept ref as a prop; type DOM-forwarding components with ComponentPropsWithRef and pass that prop to the owning element. A deliberate non-DOM imperative handle MAY use that prop with useImperativeHandle.
- Use
useEffectEvent when a callback registered by an effect must read the latest props or state without re-subscribing. Keep effect dependencies focused on the values that define the subscription lifecycle.
- Keep derived values and event callbacks as ordinary render-time code. Preserve no legacy memoization solely because it existed before the compiler.
- Do not read from or write to mutable refs during render, except for one-time initialization that React explicitly permits. Put imperative ref synchronization in effects or event handlers.
- Compiler skips unsafe functions rather than changing behavior. Fix Rules of React violations instead of adding blanket opt-outs; use
"use no memo" only as a short-lived, documented containment for a verified compiler issue.
Verification
- Run web build, lint, and targeted tests for changed behavior. Web lint runs Biome plus the official React Hooks/Compiler diagnostics and rejects manual memoization imports.
- Treat
npm run build as the compiler integration check because it exercises both the client bundle and the SSG build.
- Run SSG/SEO and E2E checks when routing, locales, metadata, hydration, scanner, timer, or solve flows change.
Reference: ai/rules/frontend-form-rules.md
Frontend Form Rules
- Keep notation solve input as move notation and trim it before submission. Rust/API owns notation semantics, cube validity, and safety enforcement.
- Use local controlled state for simple controls. React Hook Form and Zod MAY be used for a form whose coordination or schema complexity benefits from them; do not treat them as required setup.
- Keep field labels visible and associated, errors near their owner,
aria-invalid on invalid fields, and API caps discoverable.
- Prevent requests for invalid local limits, but do not duplicate solver validation in React.
- Keep the default scramble empty; examples belong in placeholders or help content.
- Raw facelet/Kociemba input modes remain prohibited for notation forms. Reviewed stickers and manual corrections are allowed only inside typed scan-session workflows.
Reference: ai/rules/frontend-quality-rules.md
Frontend Quality Rules
Focused requirements for accessibility, performance, security, resilience, and dependencies in apps/web.
Accessibility
- Interactive controls MUST have an accessible name, keyboard operation, visible focus, and correct native element or shared primitive semantics.
- Dialogs and sheets MUST preserve focus trapping, Escape handling, focus return, and labelled title/description behavior through existing primitives.
- When the timer page becomes interactive or a timer-page action completes, focus MUST return to the document body so its global keyboard controls remain available. This includes route navigation, event selection, and settings-dialog closure; controls remain focusable while users actively operate them.
- Dynamic errors and completion states SHOULD use an appropriate live/status region without repeatedly announcing scanner frame updates.
- Motion MUST respect reduced-motion preferences; color MUST NOT be the only signal for scan, validation, timer, or solver status.
Performance
- Indexable routes MUST retain SSG output and hydration; do not replace server-rendered content with client-only placeholders.
- Heavy visualization, algorithm, and page code SHOULD remain route- or feature-split. Avoid importing broad package barrels when a supported subpath exists.
- Supported Solver and Notation puzzle stages MUST automatically request their renderer. Solver visualizations MUST load no later than three seconds after mount; Notation visualizations MUST load automatically after their stage approaches the viewport. Clicking a preparing state MAY accelerate loading or retry an error, but MUST NEVER be required to see a visualization. Performance work MUST NOT defer any supported visualization behind an explicit click; tests MUST prove automatic loading for Solver and every supported Notation puzzle.
- Camera analysis MUST cancel stale work and avoid overlapping unbounded requests. Versioned assets remain immutable; mutable HTML and metadata MUST NOT receive immutable caching.
- Performance changes MUST use a concrete signal such as bundle output, request count, render behavior, or measured interaction, not speculative memoization.
Security And Resilience
- Camera permission MUST follow a clear disclosure of purpose and processing. Once permission succeeds, scanner analysis starts automatically by default; users retain pause/exit and manual-review controls.
- Images and reviewed scan data MUST stay within typed scan contracts and configured request limits. Do not persist or transmit camera data beyond the disclosed solve workflow.
- Browser code MUST not weaken CSP, permissions policy, origin restrictions, request size limits, runtime response validation, or typed error handling.
- Async work MUST handle cancellation and stale responses. User-visible flows MUST expose recoverable API/camera failures and avoid silently fabricating successful data.
- Analytics, RUM, and error-tracking services are out of scope unless a separate product/privacy decision explicitly introduces them.
Dependencies
- A new runtime dependency MUST solve a named current gap that existing React, browser APIs, shared primitives, or installed packages cannot reasonably solve.
- The change MUST identify bundle/runtime impact, maintenance owner, licensing/security fit, SSR/hydration compatibility when relevant, and focused tests.
- Do not add a package solely for a small helper, styling convention, or abstraction without a current reused consumer.
Reference: ai/architecture/api-boundary.md
API Boundary Architecture
crates/api is a native Axum HTTP and static-serving boundary around the Rust solver engine.
Current Routes And Layout
crates/api/src/routes.rs owns route composition, HTTP layers, static file serving, and handlers. Focused modules own configuration, request/response types, puzzle dispatch, scan analysis, solve preparation, and state.
- Health routes are
/health, /livez, and /readyz.
- Solver routes include
/puzzles, /puzzles/{puzzle_slug}, /puzzles/{puzzle_slug}/strategies, /puzzles/{puzzle_slug}/solve, legacy /strategies, /solve-notation, and /solve-scan.
- Scan routes include
/scan/analyze-face, /scan/solve-session, and /puzzles/{puzzle_slug}/scan/solve-session.
- When serving web output,
/api/wca-data is a server-side permanent redirect (HTTP 308) to /api/wca-data/v1/docs; unknown /api/* paths return 404 instead of static HTML.
- When serving web output, legacy
/algoritmos prefixes redirect permanently to the corresponding /algorithms path while preserving locale prefixes, suffixes, and query strings.
Contract And Safety
- Request and response structs, stable status strings, limits, and runtime response validation form the frontend contract.
- The API validates request size and cost before expensive work, uses bounded solver concurrency, and verifies solutions by replay.
- Generated pruning-table availability, corrupt artifacts, overload, worker failure, invalid notation/state, and exhausted limits remain explicit typed outcomes.
- Typed scan-session requests MAY include reviewed stickers and manual overrides. Browser notation endpoints MUST remain move-notation based and MUST NOT accept raw facelet/Kociemba input modes.
Frontend Boundary
apps/web/src/api owns request construction, runtime validation, response normalization, and React Query hooks. Shared transport code lives under apps/web/src/api/client.
- Components consume typed API-domain hooks or adapters rather than raw HTTP responses, query keys, or duplicated status parsing.
- WCA Data requests target the independent
/api/wca-data/v1 service contract; Axum does not implement those data endpoints.
Test Shape
- Rust API behavior and router contracts are tested in
crates/api/src/tests.rs and focused crate test modules.
- Web request and hook tests live beside their API domain in
__tests__ directories, including apps/web/src/api/__tests__, apps/web/src/api/client/__tests__, and domain-level __tests__.
- Contract changes require success, error, limit, and frontend normalization coverage for affected behavior.