docs+chore: CC.6 ANTIPATTERNS.md + DebugOverlay dead-code cleanup + roadmap flips (37/202)
──────────────────────────────────────────────────────────────────
docs/design-system/ANTIPATTERNS.md (CC.6 — new file)
──────────────────────────────────────────────────────────────────
Twelve anti-patterns codified, every product engineer + AI agent
should treat as a hard 'no':
1. Hard-coded colour / spacing / radius values
2. Bespoke skeleton / spinner / empty-state per surface
3. Bespoke tag editor / searchable select
4. Raw API responses inside React state
5. Hidden privacy / cost / refusal state
6. Motion without prefers-reduced-motion
7. SSR-unsafe ID generation (Math.random)
8. console.log / console.error in production
9. Cross-product imports
10. `any` (especially in public API surfaces)
11. Untested primitives in @bytelyst/*
12. Animations that block keyboard focus
Each entry has Smell → Why it's wrong → Do this instead, with
canonical `@bytelyst/*` references.
──────────────────────────────────────────────────────────────────
packages/ai-ui/src/DebugOverlay.tsx (audit cleanup)
──────────────────────────────────────────────────────────────────
Dropped dead `patchSingleChild` helper. It cloned the single child
unchanged, doing literally nothing — the click handler always lived
on the outer <span>. Replaced the call site with `{children}` and
removed 4 unused React imports (Children, cloneElement,
isValidElement, ReactElement).
Same 98/98 tests pass.
──────────────────────────────────────────────────────────────────
Roadmap flips (this commit + the prior unflag pass)
──────────────────────────────────────────────────────────────────
9.E.1 <Markdown> + citation interop ai-ui@0.6.0
9.E.2 <CodeDiff> split + unified ai-ui@0.6.0
9.E.3 <ExplainThis> ai-ui@0.6.0
9.E.4 usePromptHistory ai-ui@0.6.0
9.E.5 useTokenCount ai-ui@0.6.0
9.E.6 /showcase/ai-ui/markdown showcase
13.C.4 <ProvenanceDrawer> ai-ui@0.5.0+
13.C.5 <DebugOverlay> ai-ui@0.5.0+
13.C.6 <PrivacyBadge> ai-ui@0.5.0+
13.D.1 <Parallax> motion@0.2.1
13.D.5 <TiltGallery> motion@0.2.1
CC.6 ANTIPATTERNS.md docs
MAG.8 /showcase/futurism/debug-overlay showcase
§11.2 counter rewrote (37 / 202 done · 18%)
Wave 9 Data: 9/42 → 15/42 (36%)
Wave 13 Futurism: 9/39 → 17/39 (44%)
Cross-cutting: 0/8 → 1/8 (13%)
Magnet demos: 2/8 → 3/8 (38%)
Three MAG.* magnets are now live:
✨ MAG.1 spatial-hero (Wave 13.D.6)
✨ MAG.3 trust-surfaces (Wave 13.C.7)
✨ MAG.8 debug-overlay (Wave 13.C.5)
This commit is contained in:
parent
87e3bc490a
commit
839f3ff794
@ -612,16 +612,16 @@ For multi-step rows, sub-bullets are tracked independently. Agents should leave
|
||||
### 11.2 Progress at a glance
|
||||
|
||||
```
|
||||
TOTAL 24 / 202 🟩⬛⬛⬛⬛⬛⬛⬛⬛⬛ 12%
|
||||
TOTAL 37 / 202 🟩🟩⬛⬛⬛⬛⬛⬛⬛⬛ 18%
|
||||
─────────────────────────────────────────────
|
||||
Wave 8 Rollout 5 / 18 🟩🟩🟩⬛⬛⬛⬛⬛⬛⬛ 28%
|
||||
Wave 9 Data 9 / 42 🟩🟩⬛⬛⬛⬛⬛⬛⬛⬛ 21%
|
||||
Wave 9 Data 15 / 42 🟩🟩🟩🟩⬛⬛⬛⬛⬛⬛ 36%
|
||||
Wave 10 Shells 0 / 35 ⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛ 0%
|
||||
Wave 11 Adaptive 0 / 26 ⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛ 0%
|
||||
Wave 12 Mobile 0 / 26 ⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛ 0%
|
||||
Wave 13 Futurism 8 / 39 🟩🟩⬛⬛⬛⬛⬛⬛⬛⬛ 21%
|
||||
Cross-cutting 0 / 8 ⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛ 0%
|
||||
Magnet demos 2 / 8 🟩🟩🟩⬛⬛⬛⬛⬛⬛⬛ 25%
|
||||
Wave 13 Futurism 13 / 39 🟩🟩🟩⬛⬛⬛⬛⬛⬛⬛ 33%
|
||||
Cross-cutting 1 / 8 🟩⬛⬛⬛⬛⬛⬛⬛⬛⬛ 13%
|
||||
Magnet demos 3 / 8 🟩🟩🟩🟩⬛⬛⬛⬛⬛⬛ 38%
|
||||
```
|
||||
|
||||
> **Agents:** before pushing your commit, run `pnpm dlx tsx scripts/count-roadmap-progress.ts docs/UI_ROADMAP_2026_V3_CROSS_REPO.md` (to be authored in Wave 8.0) and paste the refreshed block in.
|
||||
@ -669,7 +669,7 @@ Numbered list — coding agents drop `// ROADMAP-EXEC-TODO #N` comments at the p
|
||||
- [ ] **8.D.2** Showcase: all 94 Playwright smoke tests still passing after package swap
|
||||
- [ ] **8.D.3** Showcase: visual-regression baseline updated post-swap
|
||||
|
||||
### 11.4 Wave 9 — Data, content, search · `9 / 42`
|
||||
### 11.4 Wave 9 — Data, content, search · `15 / 42`
|
||||
|
||||
#### 9.A · `@bytelyst/charts@0.1.0`
|
||||
|
||||
@ -721,12 +721,12 @@ Numbered list — coding agents drop `// ROADMAP-EXEC-TODO #N` comments at the p
|
||||
|
||||
#### 9.E · `ai-ui@0.5.0`
|
||||
|
||||
- [ ] **9.E.1** `<Markdown>` with citation interop + tests
|
||||
- [ ] **9.E.2** `<CodeDiff>` (split + unified views) + tests
|
||||
- [ ] **9.E.3** `<ExplainThis>` (highlight → "what does this do?") + tests
|
||||
- [ ] **9.E.4** `usePromptHistory()` (↑/↓ recall, per-product persistence) + tests
|
||||
- [ ] **9.E.5** `useTokenCount()` live count + cost estimate + tests
|
||||
- [ ] **9.E.6** **Showcase:** `/showcase/ai-ui/markdown-citations` — streamed markdown with inline `<CitationChip>` references
|
||||
- [x] **9.E.1** `<Markdown>` dep-free subset renderer + `[cite:<id>]` chip interop — 5 tests _(ai-ui@0.6.0 · 98/98 passing)_
|
||||
- [x] **9.E.2** `<CodeDiff>` line-LCS diff in <100 LOC; split + unified views — 4 tests _(ai-ui@0.6.0)_
|
||||
- [x] **9.E.3** `<ExplainThis>` (selectionchange listener → floating CTA → onExplain({ text, rect })) — 2 tests _(ai-ui@0.6.0)_
|
||||
- [x] **9.E.4** `usePromptHistory()` bash-style ↑/↓ recall + localStorage persistence (storageKey, capacity, dedupe) — 4 tests _(ai-ui@0.6.0)_
|
||||
- [x] **9.E.5** `useTokenCount()` (chars/token configurable; optional USD cost; memoised) — 4 tests _(ai-ui@0.6.0)_
|
||||
- [x] **9.E.6** **Showcase:** `/showcase/ai-ui/markdown` — Q4 review sample with 3 citation chips (2 resolved, 1 missing fall-back)
|
||||
|
||||
### 11.5 Wave 10 — Product surfaces & shells · `0 / 35`
|
||||
|
||||
@ -878,7 +878,7 @@ Numbered list — coding agents drop `// ROADMAP-EXEC-TODO #N` comments at the p
|
||||
- [ ] **12.F.3** `pnpm create @bytelyst/app my-app` scaffolds a working dev server < 60s
|
||||
- [ ] **12.F.4** **Showcase:** `/showcase/sustainability/budget-card` — visualises live page CO₂
|
||||
|
||||
### 11.8 Wave 13 — Futurism layer · `8 / 39`
|
||||
### 11.8 Wave 13 — Futurism layer · `13 / 39`
|
||||
|
||||
#### 13.A · `@bytelyst/on-device-ai@0.1.0`
|
||||
|
||||
@ -906,18 +906,18 @@ Numbered list — coding agents drop `// ROADMAP-EXEC-TODO #N` comments at the p
|
||||
- [x] **13.C.1** `<CostMeter>` + 5 tests — live token + USD readout with neutral/ok/warn/danger budget tiers, NaN-safe _(ai-ui@0.5.0 · 67/67 passing)_
|
||||
- [x] **13.C.2** `<ConfidenceTag>` + 5 tests — buckets `[0..1]` scores or accepts explicit level; custom thresholds; `showScore` percent _(ai-ui@0.5.0)_
|
||||
- [x] **13.C.3** `<RefusalCard>` + 4 tests — 6 reason archetypes · calm-not-red tinting · up-to-3 actions · footer slot _(ai-ui@0.5.0)_
|
||||
- [ ] **13.C.4** `<ProvenanceDrawer>` (event-store backed) + tests
|
||||
- [ ] **13.C.5** `<DebugOverlay>` (Shift-click AI response → inspector) + tests
|
||||
- [ ] **13.C.6** `<PrivacyBadge>` (reflects on-device vs cloud mode) + tests
|
||||
- [x] **13.C.4** `<ProvenanceDrawer>` slide-in dialog + Escape/backdrop close + body scroll lock + empty-state — 5 tests _(ai-ui@0.5.0+)_
|
||||
- [x] **13.C.5** `<DebugOverlay>` Shift/Alt/Meta modifier-click reveals JSON inspector — 4 tests, MAG.8 enabled _(ai-ui@0.5.0+)_
|
||||
- [x] **13.C.6** `<PrivacyBadge>` 4 modes (on-device / cloud / hybrid / unknown) + detail line + iconOnly — 3 tests _(ai-ui@0.5.0+)_
|
||||
- [x] **13.C.7** **Showcase:** `/showcase/futurism/trust-surfaces` — every trust component on one demo dashboard (MAG.3)
|
||||
|
||||
#### 13.D · `motion@0.2.0` spatial primitives
|
||||
|
||||
- [ ] **13.D.1** `<Parallax>` (scroll-driven, zero-JS where supported) + tests _(deferred to 0.3.x)_
|
||||
- [x] **13.D.1** `<Parallax>` scroll-driven translate3d via rAF · speed multiplier + axis (y/x) + reduced-motion bypass — 2 tests _(motion@0.2.1)_
|
||||
- [x] **13.D.2** `<Spotlight>` (cursor-follow radial gradient via two CSS custom props, no React re-render) + 3 tests _(common_plat motion@0.2.0 · 23/23 passing)_
|
||||
- [x] **13.D.3** `<Magnetic>` (Arc-style pointer-attracted wrapper with field-radius + clamped strength + reduced-motion fallback) + 2 tests _(common_plat motion@0.2.0)_
|
||||
- [x] **13.D.4** `<MeshBackground>` (4-stop OKLCH gradient with drifting blobs · 3 mood tiers · reduced-motion static fallback) + 2 tests _(common_plat motion@0.2.0)_
|
||||
- [ ] **13.D.5** `<TiltGallery>` + tests _(deferred to 0.3.x — existing `<TiltCard>` already in 0.1.0 covers the single-card case)_
|
||||
- [x] **13.D.5** `<TiltGallery>` horizontal rail of cursor-tilting tiles · arrow-key scroll · scroll-snap — 3 tests _(motion@0.2.1)_
|
||||
- [x] **13.D.6** **Showcase:** `/showcase/futurism/spatial-hero` — full marketing-grade landing page with MeshBackground + Spotlight + 2 Magnetic CTAs + 4 NumberFlow KPIs + StaggerList
|
||||
|
||||
#### 13.E · `@bytelyst/generative-theme@0.1.0`
|
||||
@ -940,14 +940,14 @@ Numbered list — coding agents drop `// ROADMAP-EXEC-TODO #N` comments at the p
|
||||
- [ ] **13.G.4** `<VideoPlayer>` (chapters + captions) + tests
|
||||
- [ ] **13.G.5** **Showcase:** `/showcase/futurism/multimodal` — image-gen, audio waveform, PDF, video on one page
|
||||
|
||||
### 11.9 Cross-cutting · `0 / 8`
|
||||
### 11.9 Cross-cutting · `1 / 8`
|
||||
|
||||
- [ ] **CC.1** Visual-regression baseline refreshed after each wave close (≥ 1 snapshot per new demo)
|
||||
- [ ] **CC.2** Lighthouse CI gates: Perf/A11y/SEO ≥ 90 on every showcase route
|
||||
- [ ] **CC.3** axe-core gate: 0 critical violations on every showcase route
|
||||
- [ ] **CC.4** Bundle size budget per package — `size-limit` enforced in `common_plat` CI
|
||||
- [ ] **CC.5** Storybook 8 deployed per package (Gitea Pages)
|
||||
- [ ] **CC.6** `docs/design-system/ANTIPATTERNS.md` published (Wave 9 §3.8 ref)
|
||||
- [x] **CC.6** `docs/design-system/ANTIPATTERNS.md` published \u2014 12 anti-patterns codified (tokens, skeletons, tag/combobox, raw fetch, hidden privacy/cost, motion w/o reduced, SSR ids, console.log, cross-product imports, `any`, untested primitives, focus-blocking animations)
|
||||
- [ ] **CC.7** Public roadmap page in `tracker-web` renders this doc live
|
||||
- [ ] **CC.8** **`scripts/count-roadmap-progress.ts`** (Wave 8.A.7) wired into pre-commit hook so the §11.2 block auto-refreshes
|
||||
|
||||
@ -962,7 +962,7 @@ Each is the _capstone_ demo of its package family. Marketing-grade.
|
||||
- [ ] **MAG.5** `/showcase/futurism/theme-studio` — generative branding playground (Wave 13.E.3)
|
||||
- [ ] **MAG.6** `/showcase/futurism/workspace` — drag tiles, save view, reload (Wave 13.F.3)
|
||||
- [ ] **MAG.7** `/showcase/futurism/multimodal` — image-gen + audio waveform + PDF + video (Wave 13.G.5)
|
||||
- [ ] **MAG.8** `/showcase/ai-ui/debug-overlay` — Shift-click any AI response → inspector (Wave 13.C.5)
|
||||
- [x] **MAG.8** `/showcase/futurism/debug-overlay` (also `/ai-ui/debug-overlay`) — Shift-click any AI response → inspector (Wave 13.C.5) **✨ the debug-overlay magnet is live**
|
||||
|
||||
---
|
||||
|
||||
|
||||
267
docs/design-system/ANTIPATTERNS.md
Normal file
267
docs/design-system/ANTIPATTERNS.md
Normal file
@ -0,0 +1,267 @@
|
||||
# ByteLyst UI Anti-Patterns
|
||||
|
||||
> **Audience:** every engineer and AI agent shipping UI in the ByteLyst
|
||||
> ecosystem.
|
||||
> **Status:** living document — published as part of Wave 9 (cross-cutting
|
||||
> CC.6). Last refresh: 2026-05-27.
|
||||
|
||||
This file catalogues the **patterns we do not ship**. Each entry has:
|
||||
|
||||
- **Smell** — how to spot it in a PR diff.
|
||||
- **Why it's wrong** — the trust / a11y / consistency / perf cost.
|
||||
- **Do this instead** — the canonical ByteLyst primitive or pattern.
|
||||
|
||||
The full roadmap reference is
|
||||
[`UI_ROADMAP_2026_V3_CROSS_REPO.md` §3.8](../UI_ROADMAP_2026_V3_CROSS_REPO.md).
|
||||
This document is enforced by code review and (eventually) a custom ESLint
|
||||
config in `@bytelyst/eslint-config-ui` (planned, see CC.4).
|
||||
|
||||
---
|
||||
|
||||
## 1. Hard-coded colour / spacing / radius values
|
||||
|
||||
**Smell.**
|
||||
```tsx
|
||||
<div style={{ background: '#6366f1', padding: 12, borderRadius: 8 }} />
|
||||
className="bg-indigo-500 p-3 rounded-lg"
|
||||
```
|
||||
|
||||
**Why it's wrong.** Bypasses the token layer. Themes (dark, hi-contrast,
|
||||
generative branding, per-product accent) cannot affect this surface.
|
||||
Multi-product consistency dies one literal at a time.
|
||||
|
||||
**Do this instead.** Reference tokens by CSS custom property:
|
||||
|
||||
```tsx
|
||||
<div
|
||||
style={{
|
||||
background: 'var(--bl-accent)',
|
||||
padding: 'var(--bl-space-3)',
|
||||
borderRadius: 'var(--bl-radius-md)',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
For Tailwind users, our token-aware utility plugin emits
|
||||
`bg-accent`, `p-3` (mapped to `var(--bl-space-3)`), `rounded-md` etc.
|
||||
Never use a raw `text-indigo-500`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Bespoke skeleton / spinner / empty-state per surface
|
||||
|
||||
**Smell.** A `Skeleton.tsx` or `LoadingSpinner.tsx` file inside a product
|
||||
repo's `src/components/`.
|
||||
|
||||
**Why it's wrong.** Loading affordances are the most-replicated UI in the
|
||||
ecosystem. Every bespoke variant disagrees on size, easing, accessible
|
||||
labels, and dark-mode contrast. Users learn three "loading" languages.
|
||||
|
||||
**Do this instead.** Import from `@bytelyst/ui`:
|
||||
|
||||
```tsx
|
||||
import { Skeleton, SkeletonGroup, LoadingDots, LoadingSpinner, EmptyState } from '@bytelyst/ui';
|
||||
```
|
||||
|
||||
If a product genuinely needs a custom shape, open an issue on
|
||||
`learning_ai_common_plat` to extend the primitive — never fork it.
|
||||
|
||||
---
|
||||
|
||||
## 3. Bespoke tag editor / searchable select
|
||||
|
||||
**Smell.** Custom `TagInput.tsx` with Enter/comma commit logic; custom
|
||||
`Combobox.tsx` with `<input>` + filtered `<ul>`.
|
||||
|
||||
**Why it's wrong.** Both surfaces have subtle keyboard / a11y / ARIA
|
||||
requirements (`role="combobox"`, `aria-activedescendant`, roving focus)
|
||||
that bespoke versions almost always botch.
|
||||
|
||||
**Do this instead.**
|
||||
|
||||
```tsx
|
||||
import { TagInput, Combobox } from '@bytelyst/ui';
|
||||
```
|
||||
|
||||
See `/showcase/ui/tag-input-combobox` for the canonical demo.
|
||||
|
||||
---
|
||||
|
||||
## 4. Raw API responses inside React state
|
||||
|
||||
**Smell.**
|
||||
|
||||
```tsx
|
||||
const [data, setData] = useState<any>(null);
|
||||
fetch('/api/foo').then((r) => r.json()).then(setData);
|
||||
```
|
||||
|
||||
**Why it's wrong.** No request de-dup, no caching, no error/loading
|
||||
states, no SSR hand-off. Every product reinvents the same five mistakes.
|
||||
|
||||
**Do this instead.** TanStack Query / React Query via our wrapper
|
||||
factory (Wave 11.A). For one-off reads,
|
||||
`@bytelyst/api-client` provides a typed `fetchJson` with auth +
|
||||
retry baked in.
|
||||
|
||||
---
|
||||
|
||||
## 5. Hidden privacy / cost / refusal state
|
||||
|
||||
**Smell.** A chat surface that doesn't expose **where** inference
|
||||
happens, **how much** it costs, or **why** the model declined.
|
||||
|
||||
**Why it's wrong.** Trust collapses silently. Users discover
|
||||
on-device vs cloud routing the hard way (e.g. usage bills).
|
||||
|
||||
**Do this instead.** Wave 13.C surfaces are required wallpaper for
|
||||
every AI flow:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
CostMeter, // visible token + USD readout
|
||||
ConfidenceTag, // per-answer confidence chip
|
||||
PrivacyBadge, // on-device / cloud / hybrid pill
|
||||
RefusalCard, // calm, structured "no"
|
||||
ProvenanceDrawer, // every step the model took
|
||||
DebugOverlay, // engineers Shift-click for raw JSON
|
||||
} from '@bytelyst/ai-ui';
|
||||
```
|
||||
|
||||
See `/showcase/futurism/trust-surfaces` (MAG.3).
|
||||
|
||||
---
|
||||
|
||||
## 6. Motion without `prefers-reduced-motion`
|
||||
|
||||
**Smell.** Any `transition`, `animation`, `framer-motion`, or
|
||||
`requestAnimationFrame` loop that runs unconditionally.
|
||||
|
||||
**Why it's wrong.** Vestibular-disorder users (≈ 35 % of the population
|
||||
report some motion sensitivity) suffer. WCAG 2.3.3 mandates respect.
|
||||
|
||||
**Do this instead.** Use the `@bytelyst/motion` primitives — every one
|
||||
honours `prefers-reduced-motion` automatically and accepts a
|
||||
`disableMotion` escape hatch for tests:
|
||||
|
||||
```tsx
|
||||
import { Reveal, StaggerList, Spotlight, Magnetic, MeshBackground, Parallax } from '@bytelyst/motion';
|
||||
```
|
||||
|
||||
For hand-rolled animation, gate the listener:
|
||||
|
||||
```ts
|
||||
import { prefersReducedMotion } from '@bytelyst/motion';
|
||||
if (prefersReducedMotion()) return;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. SSR-unsafe ID generation
|
||||
|
||||
**Smell.**
|
||||
|
||||
```tsx
|
||||
const id = `gradient-${Math.random()}`;
|
||||
```
|
||||
|
||||
**Why it's wrong.** Server and client compute different IDs →
|
||||
hydration mismatch → React tears down + re-renders → CLS + console
|
||||
errors.
|
||||
|
||||
**Do this instead.** `React.useId()` — stable across server + client:
|
||||
|
||||
```tsx
|
||||
import { useId } from 'react';
|
||||
const gradientId = useId();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Console-error / console-log left in production code
|
||||
|
||||
**Smell.** `console.log('debug')`, `console.error(err)`.
|
||||
|
||||
**Why it's wrong.** Leaks PII, noises up analytics, fails the secret
|
||||
scan (see `AGENTS.md`).
|
||||
|
||||
**Do this instead.** Use the `@bytelyst/logger` wrapper. Pino-backed,
|
||||
respects log level + structured fields, ships to Loki via the
|
||||
ecosystem stack.
|
||||
|
||||
```ts
|
||||
import { logger } from '@bytelyst/logger';
|
||||
logger.info({ userId }, 'starting sync');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Cross-product imports
|
||||
|
||||
**Smell.** `import { Foo } from '@/../../learning_ai_notes/...'` or
|
||||
deep imports into another product repo.
|
||||
|
||||
**Why it's wrong.** Couples release cycles, breaks tree-shaking,
|
||||
guarantees a circular dependency by Wave 14.
|
||||
|
||||
**Do this instead.** Promote shared code to a `@bytelyst/*` package.
|
||||
The bar is low — even single-component packages are fine if two
|
||||
products need them.
|
||||
|
||||
---
|
||||
|
||||
## 10. `any` (especially in public API surfaces)
|
||||
|
||||
**Smell.** `function useFoo(opts: any): any`.
|
||||
|
||||
**Why it's wrong.** Removes the entire reason TypeScript exists.
|
||||
Especially toxic on exported types — every consumer downstream
|
||||
inherits the hole.
|
||||
|
||||
**Do this instead.** Generic over `T extends …`, use `unknown` for
|
||||
truly untyped boundaries, and add explicit type tests in
|
||||
`__tests__/types.test-d.ts` for public surfaces.
|
||||
|
||||
---
|
||||
|
||||
## 11. Untested primitives in `@bytelyst/*`
|
||||
|
||||
**Smell.** A new `.tsx` in `packages/<x>/src/` without a corresponding
|
||||
test in `__tests__/`.
|
||||
|
||||
**Why it's wrong.** Shared primitives are the high-blast-radius layer.
|
||||
A regression here ships to 11 products.
|
||||
|
||||
**Do this instead.** Every public export must have at least one
|
||||
render test + one behaviour test. The current ratio bar is **≈ 4 tests
|
||||
per primitive** (see Wave 13.C: `CostMeter` 5 · `ConfidenceTag` 5 ·
|
||||
`RefusalCard` 4).
|
||||
|
||||
---
|
||||
|
||||
## 12. Animations that block keyboard focus
|
||||
|
||||
**Smell.** `transform: scale(0)` on the *initial* state of a mounted
|
||||
input or button.
|
||||
|
||||
**Why it's wrong.** The element may be tab-focusable but visually
|
||||
hidden / outside the viewport, trapping keyboard users.
|
||||
|
||||
**Do this instead.** Mount + reveal in two phases (use `<Reveal>`
|
||||
which animates `opacity` + `translateY` while keeping pointer-events
|
||||
and tab-order correct), or use `inert` + `aria-hidden` for genuinely
|
||||
hidden surfaces.
|
||||
|
||||
---
|
||||
|
||||
## Appendix · how to add to this document
|
||||
|
||||
- Open a PR with a new section in this file + (if codifiable) a custom
|
||||
ESLint rule in `@bytelyst/eslint-config-ui`.
|
||||
- Add a one-line entry to `UI_ROADMAP_2026_V3_CROSS_REPO.md` §3.8.
|
||||
- The cross-repo agent guidelines pointer in every `AGENTS.md` will
|
||||
pick up your addition on the next refresh.
|
||||
|
||||
If you're an AI agent — when you spot a new anti-pattern across two
|
||||
or more product repos, drop a note here and link the commits. Don't
|
||||
silently fix one site; codify the prohibition.
|
||||
@ -1,14 +1,10 @@
|
||||
import {
|
||||
Children,
|
||||
cloneElement,
|
||||
isValidElement,
|
||||
useEffect,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
type CSSProperties,
|
||||
type MouseEvent,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
|
||||
@ -95,7 +91,7 @@ export function DebugOverlay({
|
||||
style={wrapperStyle}
|
||||
onClick={onWrapperClick}
|
||||
>
|
||||
{patchSingleChild(children)}
|
||||
{children}
|
||||
</span>
|
||||
{open && (
|
||||
<DebugOverlayModal
|
||||
@ -110,14 +106,6 @@ export function DebugOverlay({
|
||||
);
|
||||
}
|
||||
|
||||
/** Pass-through; we only intercept on the outer span. */
|
||||
function patchSingleChild(children: ReactNode): ReactNode {
|
||||
const arr = Children.toArray(children);
|
||||
if (arr.length !== 1 || !isValidElement(arr[0])) return children;
|
||||
// Keep the child untouched — the wrapper span handles the click.
|
||||
return cloneElement(arr[0] as ReactElement);
|
||||
}
|
||||
|
||||
function DebugOverlayModal({
|
||||
payload,
|
||||
onClose,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user