Skip to content

Latest commit

 

History

History
144 lines (108 loc) · 4.71 KB

File metadata and controls

144 lines (108 loc) · 4.71 KB

Session Log: Lint Error Remediation in React/Next.js App

A real Ouro Loop session on a consumer iOS/web product. Names and specifics anonymized.

Context

  • Project type: Next.js 15 + React 19 + TypeScript consumer app
  • Task: Eliminate all ESLint errors to establish clean lint baseline
  • BOUND interaction: None (no DANGER ZONE touched)
  • Complexity: Simple (config + 2 files)

BOUND (from CLAUDE.md)

DANGER ZONES: src/utils/core-engine.ts, src/app/api/chat/route.ts
NEVER DO: Never remove rate limiter, never expose API keys client-side
IRON LAWS:
  - Build must pass: `next build` with zero errors
  - All UI components must work on mobile (min 375px)
  - Core engine calculations must be deterministic

MAP

User expects:    `npm run lint` passes with zero errors
Failure modes:   overly strict config breaks workflow, fixes introduce bugs
Tightest constraint: build must still pass (IRON LAW)
Dependencies:    ESLint + React Compiler + Next.js built-in rules
Reusable:        Next.js default ESLint config already present
Success metric:  0 lint errors (warnings acceptable)

Baseline

$ npx eslint .
3 errors, 3 warnings

Errors:
1. privacy/page.tsx:14  — <a> should be <Link> (next/no-html-link-for-pages)
2. privacy/page.tsx:182 — <a> should be <Link> (next/no-html-link-for-pages)
3. ReadingPage.tsx:78   — setState in useEffect (react-hooks/set-state-in-effect)

BUILD — Fix 1: Replace <a> with <Link>

File:   src/app/privacy/page.tsx
Change: import Link from "next/link", replace 2 <a href="/"> with <Link href="/">
Risk:   None — server component, Link works in both server and client

Straightforward fix. No DANGER ZONE involvement.

BUILD — Fix 2: Eliminate setState in useEffect (Attempt 1)

File:   src/components/ReadingPage.tsx
Change: Extract detection logic to a derived const, keep useEffect only for
        setting the flag when the derived value becomes true

VERIFY → FAIL

$ npx eslint .
ReadingPage.tsx:81  error  setState synchronously within an effect

The React Compiler's set-state-in-effect rule is stricter than expected. Even wrapping setState in a conditional inside useEffect still triggers it.

[REMEDIATED] gate=ROOT_CAUSE action=revert_and_retry
  was: rewrote the effect but still called setState inside it
  did: realized the rule prohibits ANY setState in effect body
  now: eliminating the effect entirely — pure derived state
  bound: not in DANGER ZONE, safe to experiment

BUILD — Fix 2: Eliminate setState in useEffect (Attempt 2)

File:   src/components/ReadingPage.tsx
Change: Replace useState + useEffect pattern with pure derived value:

BEFORE:
  const [readingComplete, setReadingComplete] = useState(false);
  useEffect(() => {
    if (!readingComplete && !isLoading && messages.length > 1) {
      if (messages.some(m => m.role === "assistant")) {
        setReadingComplete(true);  // ← React Compiler rejects this
      }
    }
  }, [isLoading, messages, readingComplete]);

AFTER:
  const readingComplete =
    readingCompleteOverride ||
    (!isLoading && messages.length > 1 &&
     messages.some(m => m.role === "assistant"));
  // No effect. No setState. Pure derivation.

VERIFY → PASS

$ npx eslint .
0 errors, 3 warnings

$ npx next build
✓ Build successful

Results

  phase:        1/1
  verdict:      PASS
  bound_check:  PASS — no DANGER ZONE touched, build passes (IRON LAW)
  lint:         0 errors (was 3), 3 warnings (unchanged)
  remediation:  1 — ROOT_CAUSE gate caught symptom-level fix, forced deeper solution

LOOP — What fed back into BOUND

IRON LAWS (considered adding):
  - `npm run lint` must pass with zero errors before commit

Decision: Not added yet — team should discuss whether warnings
should also block. Flagged for next review.

Methodology Observations

  1. ROOT_CAUSE gate caught a lazy fix — first attempt "fixed" the lint error by restructuring the effect but keeping setState. The gate correctly identified this as symptom-patching. The real fix was eliminating the effect entirely.

  2. The derived state pattern was superior — not just lint-clean, but architecturally better. React's mental model prefers computed values over synchronized state. The methodology pushed toward a genuinely better solution, not just a passing test.

  3. Simple complexity was correct — the task touched 2 files, no DANGER ZONE, clear scope. No phase plan needed. The complexity router in program.md correctly identified this as "execute directly."

  4. IRON LAW verification caught nothing — but running next build after every change gave confidence. The IRON LAW "build must pass" served as a safety net even for a simple task.