KiroCrewWebsite
September 20, 2026 · View on GitHub
This file is a ROUTER, not a manual. It carries only the rules whose violation
causes damage before a pointer could be read. Everything else is a link you MUST
open before touching that area. The backend router is ../AGENTS.md.
React + TypeScript + Vite SPA for the Kiro Crew dashboard. Built assets go to
dist/ and are staged into ../src/kiro_crew/static/dist/ so the gateway serves
them.
Read before you touch
| If you are touching… | Read first |
|---|---|
| layout of a page, panels, headers | page-layout |
| narrow screens, gutters, page zoom, touch gestures | narrow-viewport |
| themes, colors, CSS vars, stable class hooks | theming-contract |
Tailwind: a new utility for a token, src/tailwind-theme.css, src/index.css's Tailwind header, safe-area utilities | frontend-conventions § Styling |
| shared components, a11y, URL sanitization, data fetching, the stack, adding a dependency | frontend-conventions |
| any user-facing string, date, number, or sort order | i18n-catalog + i18n gates |
src/extensions.ts, edition composition, registries | extension-seams |
| tests (vitest, MSW, Playwright, Electron), or a test that fails only in CI | testing |
| the Electron desktop shell | electron/README.md |
| anything backend, or a whole-system question | ../AGENTS.md |
Everything under website/docs/ is indexed by its README.
Build and test: a gotcha that produces a silent false green
- The
localStoragetest polyfill must stay onStorage.prototype. Assigning it elsewhere makes the mock silently miss.
npm run test runs the website vitest suite AND the Electron node:test suite, and
a pretest jscpd duplication check runs first, so npm test can fail on
copy-paste before a single test executes. Commands and layers:
testing.
This is a public OSS fork: don't reintroduce internal couplings
The public build is plain npm + Vite, src/rum.ts is an inert no-op stub that stays
inert, and a downstream edition re-adds a removed surface additively through the
extension seams, never by editing core. The full list — build and infra, identity and
telemetry, the removed product surfaces, and the Channels / Board divergences an
upstream sync must not restore — is
oss-fork-boundaries, gated by
the internal-content-scan check.
AUTOSDE.yamlin this directory is live and authoritative. The frontend review rules it declares are read by theclaude-review,codex-review,code-review,fork-gpt-review, andfork-opus-reviewworkflows, and ablocking: truerule there outranks a reviewer's own prompt. Read it before changing frontend code; never treat it as historical.
Rules that must not wait for a pointer
- Settings primitives: pass
configKeyon every newSettingsToggle/SettingsFieldthat writes a config path, or the<SettingRef configKey="...">chip silently degrades to a CLI popover even though a toggle exists. Backend drift guards catch bad keys, not missing ones, so this rule is the only gate for the missing case. - Icons:
lucide-reactonly, withclassName="lucide-inline". Never an emoji, never a hand-rolled SVG, neversize={N}. Enforced byAUTOSDE.yaml(use-lucide-icons,no-emoji-as-icons). - Errors shown to the user render through
ErrorNotice, never a hand-written<div className="text-danger">{err}</div>. Turn onaskAgentwherever the hand-off cannot lose anything. Next to an unsaved draft, leave it off and name that draft in a{/* No hand-off: … */}comment. Inside a Radix menu, leave it off and render a siblingErrorNoticeMenuItem; itsdescribedBymust point to the passive notice'sid, so the hand-off is a real menu focus stop rather than a nested button. Enforced byAUTOSDE.yaml(errors-use-error-notice). - Security: every
dangerouslySetInnerHTMLgoes through DOMPurify viamd()/sanitize()/esc()insrc/api/helpers.ts. A bypass is an XSS bug, so there is no acceptable pointer for this one. - Never format a date, number, or sort order without naming a locale. Route
through the
src/i18n/format.tsseam; naming a locale explicitly IS the opt-out. CI-gated, and the failure (a Chinese UI rendering7/30/2026) ships silently. - Never hardcode a user-facing English string. The dashboard ships in 12 languages; add a catalog key. CI-gated.
- Data fetching is React Query, never
useState+useEffect. Follow the existing query-key convention. - Animation is Framer Motion. Do not add new CSS
@keyframes. - A persistent element that changes form or place stays one element. When
a user action or a state flip minimizes, collapses, relocates or replaces
something the user already has on screen, animate that one element between the
two states (
layoutId/layout, a landing spot the eye can follow, text that stays continuous, restore as the reverse). Never a hard swap of two components (flag ? <Chip/> : <Card/>): the user reads it as "that vanished and something else appeared" and no label repairs it.prefers-reduced-motiondrops the motion, not the continuity. Loading/empty/error → content transitions are not this rule. Evidence for such a change is a recording, not a screenshot. Gated by the UX Review lane (lens 13) — a hard swap with no stated reason is a BLOCK. - Styling uses design tokens (
var(--bg),var(--text), …), never a literal color. - Typography: no
text-xs, and no text below 10px. - Accessibility: use
<Clickable>rather than<div onClick>; give every icon-only button anaria-label; use<Btn>/<SendBtn>rather than a raw<button>; announce streaming regions witharia-live; honor the modal focus-trap contract. - Compose from
src/components/ui.tsx. Never hand-roll a panel section header; usePanelSectionHeader. src/extensions.tsis core-owned and must register nothing. Core registrations belong in the seed maps.- Edition composition is fail-closed: it needs
KIROCREW_EDITION_DIRandKIROCREW_ALLOW_EDITION=1. Never set the latter in a release or publish job. A contaminated public wheel cannot be unpublished.