Cybersecurity-Projects/PROJECTS/beginner/canary-token-generator/frontend
CarterPerez-dev 5562f82f34 feat(canary): landing — specimen requisition page + Turnstile widget
Landing route (/) is the token-creation page, framed as a "specimen
requisition" intake form per the vector-minimalism ethos. Mind: cataloging
a thing that resists cataloging — a trap baited for an unseen visitor.

Page composition:
- Top archive Strip (FIELD STATION / VOL / ISSUE pill)
- Hero block: large blocky "CANARIES" headline, Latin subtitle, purpose line
- Halftone separator
- Five sections (01 species / 02 annotate / 03 route / 04 species config / 05 verify)
- Bottom Strip with routing/license metadata

Form sub-sections (each a small component to keep complexity under biome's
maxAllowedComplexity=25 cap on FormView):
- SpeciesSection — 7-card grid via TypePicker (radio fieldset, sr-only inputs)
- AnnotateSection — memo textarea + conditional filename (when file-kind)
- RouteSection — Telegram | Webhook radio fieldset + conditional credentials
- ConfigSection — slowredirect destination_url or envfile include_keys
- VerifySection — Turnstile widget if VITE_TURNSTILE_SITE_KEY present
- SubmitFooter — release button + privacy note

Validation: client uses createTokenRequestSchema.safeParse, surfaces field
errors inline (mono caps in alarm color); server-side ApiError.fields are
mapped back into the same FieldErrors shape via buildSubmissionState (Phase 14
single-source envelope contract preserved).

Result view: SpecimenCard with full token dossier (DataRows), kind-discriminated
artifact display (url → CopyField; file/text → download Blob via object-URL;
connection_string → CopyField with note), Manage + Trigger CopyFields, link to
the dossier route /m/:manage_id (route wired in next chunk).

Turnstile: explicit-render widget, lazy script-load, registers token via
setTurnstileTokenProvider on success; cleans up on unmount; no key = no widget
(matches backend dev-bypass at internal/turnstile/verifier.go).

Gates green: typecheck + biome + stylelint + build (406ms, landing chunk
123KB / 38KB gzip).
2026-05-17 14:49:37 -04:00
..
public/asset feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
src feat(canary): landing — specimen requisition page + Turnstile widget 2026-05-17 14:49:37 -04:00
.dockerignore feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
.gitignore feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
.stylelintignore fix(canary-phase1): clear all post-phase-1 audit observations + header normalization 2026-05-10 06:15:26 -04:00
biome.json feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
index.html feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
package.json feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
pnpm-lock.yaml feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
stylelint.config.js fix(canary-phase1): clear all post-phase-1 audit observations + header normalization 2026-05-10 06:15:26 -04:00
tsconfig.app.json feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
tsconfig.json feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
tsconfig.node.json feat(canary): import operator's React + Vite frontend template 2026-05-10 05:28:09 -04:00
vite.config.ts fix(canary-phase1): clear all post-phase-1 audit observations + header normalization 2026-05-10 06:15:26 -04:00