learning_ai_common_plat/docs/design-system/ANTIPATTERNS.md
saravanakumardb1 839f3ff794 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)
2026-05-27 17:08:08 -07:00

7.7 KiB

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

<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:

<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:

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.

import { TagInput, Combobox } from '@bytelyst/ui';

See /showcase/ui/tag-input-combobox for the canonical demo.


4. Raw API responses inside React state

Smell.

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:

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:

import { Reveal, StaggerList, Spotlight, Magnetic, MeshBackground, Parallax } from '@bytelyst/motion';

For hand-rolled animation, gate the listener:

import { prefersReducedMotion } from '@bytelyst/motion';
if (prefersReducedMotion()) return;

7. SSR-unsafe ID generation

Smell.

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:

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.

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.