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:
saravanakumardb1 2026-05-27 17:08:08 -07:00
parent 87e3bc490a
commit 839f3ff794
3 changed files with 289 additions and 34 deletions

View File

@ -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**
---

View 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.

View File

@ -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,