From 750357b8fe8f1606e732d88c95b45257db97bc94 Mon Sep 17 00:00:00 2001 From: tornike14 Date: Sat, 15 Aug 2026 16:19:49 -0400 Subject: [PATCH 01/11] Add Event Delegation, Modules & Imports, Error Boundaries, and useEffect Lifecycle topics Four new topics, two JavaScript and two React, each with a step-by-step visualization and a full theory page. - Event Delegation (beginner): capture/target/bubble phases, stopPropagation, and the one-parent-handler delegation pattern - Modules & Imports (intermediate): module graph construction, live bindings, and circular dependency handling - Error Boundaries (intermediate): error propagation up the tree, getDerivedStateFromError, nested boundaries, and recovery - useEffect Lifecycle (beginner): render/commit/paint/effect timeline, dependency comparison, and cleanup timing Registered across all eight registries: topic registry, routes, visualization components, theory content, theory registry, keyword and description maps, and the selector toolbar set. --- src/app/javascript/event-delegation/page.tsx | 29 + src/app/javascript/modules-imports/page.tsx | 29 + src/app/react/error-boundaries/page.tsx | 29 + src/app/react/use-effect-lifecycle/page.tsx | 29 + .../layout/VisualizationPageShell.tsx | 4 + .../components/ErrorStatePanel.tsx | 65 ++ .../visualizations/error-boundaries/data.ts | 553 ++++++++++++++++++ .../error-boundaries/helpers.ts | 54 ++ .../visualizations/error-boundaries/index.tsx | 199 +++++++ .../visualizations/error-boundaries/types.ts | 37 ++ .../components/DomTreePanel.tsx | 45 ++ .../components/EventPathPanel.tsx | 53 ++ .../visualizations/event-delegation/data.ts | 446 ++++++++++++++ .../event-delegation/helpers.ts | 44 ++ .../visualizations/event-delegation/index.tsx | 194 ++++++ .../visualizations/event-delegation/types.ts | 48 ++ .../components/BindingsTablePanel.tsx | 40 ++ .../components/ModuleGraphPanel.tsx | 87 +++ .../visualizations/modules-imports/data.ts | 424 ++++++++++++++ .../visualizations/modules-imports/helpers.ts | 50 ++ .../visualizations/modules-imports/index.tsx | 197 +++++++ .../visualizations/modules-imports/types.ts | 52 ++ .../components/EffectTimelinePanel.tsx | 74 +++ .../use-effect-lifecycle/data.ts | 548 +++++++++++++++++ .../use-effect-lifecycle/helpers.ts | 52 ++ .../use-effect-lifecycle/index.tsx | 208 +++++++ .../use-effect-lifecycle/types.ts | 39 ++ src/content/theory/index.ts | 8 + .../theory/javascript/event-delegation.ts | 61 ++ .../theory/javascript/modules-imports.ts | 72 +++ src/content/theory/react/error-boundaries.ts | 60 ++ .../theory/react/use-effect-lifecycle.ts | 73 +++ src/lib/metadata.ts | 44 ++ src/lib/topics.ts | 43 ++ 34 files changed, 3990 insertions(+) create mode 100644 src/app/javascript/event-delegation/page.tsx create mode 100644 src/app/javascript/modules-imports/page.tsx create mode 100644 src/app/react/error-boundaries/page.tsx create mode 100644 src/app/react/use-effect-lifecycle/page.tsx create mode 100644 src/components/visualizations/error-boundaries/components/ErrorStatePanel.tsx create mode 100644 src/components/visualizations/error-boundaries/data.ts create mode 100644 src/components/visualizations/error-boundaries/helpers.ts create mode 100644 src/components/visualizations/error-boundaries/index.tsx create mode 100644 src/components/visualizations/error-boundaries/types.ts create mode 100644 src/components/visualizations/event-delegation/components/DomTreePanel.tsx create mode 100644 src/components/visualizations/event-delegation/components/EventPathPanel.tsx create mode 100644 src/components/visualizations/event-delegation/data.ts create mode 100644 src/components/visualizations/event-delegation/helpers.ts create mode 100644 src/components/visualizations/event-delegation/index.tsx create mode 100644 src/components/visualizations/event-delegation/types.ts create mode 100644 src/components/visualizations/modules-imports/components/BindingsTablePanel.tsx create mode 100644 src/components/visualizations/modules-imports/components/ModuleGraphPanel.tsx create mode 100644 src/components/visualizations/modules-imports/data.ts create mode 100644 src/components/visualizations/modules-imports/helpers.ts create mode 100644 src/components/visualizations/modules-imports/index.tsx create mode 100644 src/components/visualizations/modules-imports/types.ts create mode 100644 src/components/visualizations/use-effect-lifecycle/components/EffectTimelinePanel.tsx create mode 100644 src/components/visualizations/use-effect-lifecycle/data.ts create mode 100644 src/components/visualizations/use-effect-lifecycle/helpers.ts create mode 100644 src/components/visualizations/use-effect-lifecycle/index.tsx create mode 100644 src/components/visualizations/use-effect-lifecycle/types.ts create mode 100644 src/content/theory/javascript/event-delegation.ts create mode 100644 src/content/theory/javascript/modules-imports.ts create mode 100644 src/content/theory/react/error-boundaries.ts create mode 100644 src/content/theory/react/use-effect-lifecycle.ts diff --git a/src/app/javascript/event-delegation/page.tsx b/src/app/javascript/event-delegation/page.tsx new file mode 100644 index 0000000..cf36a73 --- /dev/null +++ b/src/app/javascript/event-delegation/page.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import dynamic from "next/dynamic"; +import { ErrorBoundary } from "@/components/ErrorBoundary"; +import { VisualizationPageShell } from "@/components/layout/VisualizationPageShell"; +import { VisualizationLoading } from "@/components/visualizations/VisualizationLoading"; +import { createTopicMetadata } from "@/lib/metadata"; +import { getTopicOrThrow } from "@/lib/topics"; + +const topic = getTopicOrThrow("event-delegation"); + +const EventDelegationVisualization = dynamic( + () => + import("@/components/visualizations/event-delegation").then( + (module) => module.EventDelegation, + ), + { loading: () => }, +); + +export const metadata: Metadata = createTopicMetadata(topic); + +export default function EventDelegationPage() { + return ( + + + + + + ); +} diff --git a/src/app/javascript/modules-imports/page.tsx b/src/app/javascript/modules-imports/page.tsx new file mode 100644 index 0000000..bed5620 --- /dev/null +++ b/src/app/javascript/modules-imports/page.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import dynamic from "next/dynamic"; +import { ErrorBoundary } from "@/components/ErrorBoundary"; +import { VisualizationPageShell } from "@/components/layout/VisualizationPageShell"; +import { VisualizationLoading } from "@/components/visualizations/VisualizationLoading"; +import { createTopicMetadata } from "@/lib/metadata"; +import { getTopicOrThrow } from "@/lib/topics"; + +const topic = getTopicOrThrow("modules-imports"); + +const ModulesImportsVisualization = dynamic( + () => + import("@/components/visualizations/modules-imports").then( + (module) => module.ModulesImports, + ), + { loading: () => }, +); + +export const metadata: Metadata = createTopicMetadata(topic); + +export default function ModulesImportsPage() { + return ( + + + + + + ); +} diff --git a/src/app/react/error-boundaries/page.tsx b/src/app/react/error-boundaries/page.tsx new file mode 100644 index 0000000..4c28f35 --- /dev/null +++ b/src/app/react/error-boundaries/page.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import dynamic from "next/dynamic"; +import { ErrorBoundary } from "@/components/ErrorBoundary"; +import { VisualizationPageShell } from "@/components/layout/VisualizationPageShell"; +import { VisualizationLoading } from "@/components/visualizations/VisualizationLoading"; +import { createTopicMetadata } from "@/lib/metadata"; +import { getTopicOrThrow } from "@/lib/topics"; + +const topic = getTopicOrThrow("error-boundaries"); + +const ErrorBoundariesVisualization = dynamic( + () => + import("@/components/visualizations/error-boundaries").then( + (module) => module.ErrorBoundaries, + ), + { loading: () => }, +); + +export const metadata: Metadata = createTopicMetadata(topic); + +export default function ErrorBoundariesPage() { + return ( + + + + + + ); +} diff --git a/src/app/react/use-effect-lifecycle/page.tsx b/src/app/react/use-effect-lifecycle/page.tsx new file mode 100644 index 0000000..5a7a46a --- /dev/null +++ b/src/app/react/use-effect-lifecycle/page.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import dynamic from "next/dynamic"; +import { ErrorBoundary } from "@/components/ErrorBoundary"; +import { VisualizationPageShell } from "@/components/layout/VisualizationPageShell"; +import { VisualizationLoading } from "@/components/visualizations/VisualizationLoading"; +import { createTopicMetadata } from "@/lib/metadata"; +import { getTopicOrThrow } from "@/lib/topics"; + +const topic = getTopicOrThrow("use-effect-lifecycle"); + +const UseEffectLifecycleVisualization = dynamic( + () => + import("@/components/visualizations/use-effect-lifecycle").then( + (module) => module.UseEffectLifecycle, + ), + { loading: () => }, +); + +export const metadata: Metadata = createTopicMetadata(topic); + +export default function UseEffectLifecyclePage() { + return ( + + + + + + ); +} diff --git a/src/components/layout/VisualizationPageShell.tsx b/src/components/layout/VisualizationPageShell.tsx index 06f483e..0ec5045 100644 --- a/src/components/layout/VisualizationPageShell.tsx +++ b/src/components/layout/VisualizationPageShell.tsx @@ -31,6 +31,10 @@ const SELECTOR_TOOLBAR_TOPIC_IDS = new Set([ "memoization", "suspense", "server-components", + "event-delegation", + "modules-imports", + "error-boundaries", + "use-effect-lifecycle", ]); interface VisualizationPageShellProps { diff --git a/src/components/visualizations/error-boundaries/components/ErrorStatePanel.tsx b/src/components/visualizations/error-boundaries/components/ErrorStatePanel.tsx new file mode 100644 index 0000000..2643e9f --- /dev/null +++ b/src/components/visualizations/error-boundaries/components/ErrorStatePanel.tsx @@ -0,0 +1,65 @@ +import { cn } from "@/lib/utils"; +import type { ErrorBoundaryInfo } from "../types"; +import { phaseLabel, phaseColorClass } from "../helpers"; + +/* ── Error state display ── */ + +export const ErrorStatePanel = ({ + boundaries, +}: { + boundaries: ErrorBoundaryInfo[]; +}) => { + if (boundaries.length === 0) { + return ( +

+ no boundaries +

+ ); + } + + return ( +
+ {boundaries.map((b) => ( +
+
+ + {phaseLabel(b.phase)} + + {b.label} +
+ {(b.errorMessage || b.fallbackLabel) && ( +
+ {b.errorMessage && ( + + error: {b.errorMessage} + + )} + {b.fallbackLabel && ( + + fallback: {b.fallbackLabel} + + )} +
+ )} +
+ ))} +
+ ); +}; diff --git a/src/components/visualizations/error-boundaries/data.ts b/src/components/visualizations/error-boundaries/data.ts new file mode 100644 index 0000000..2f263b6 --- /dev/null +++ b/src/components/visualizations/error-boundaries/data.ts @@ -0,0 +1,553 @@ +import type { ErrorBoundaryExample } from "./types"; + +/* ── Example Data ── */ + +export const EXAMPLES: ErrorBoundaryExample[] = [ + /* ── 1. Basic Error Boundary ── */ + { + id: "basic", + title: "Basic Error Boundary", + description: + "A class component catches render errors in its subtree and displays fallback UI instead of crashing the app.", + kind: "basic", + codeLines: [ + { num: 1, text: "class ErrorBoundary extends React.Component {" }, + { num: 2, text: " state = { hasError: false };" }, + { num: 3, text: "" }, + { num: 4, text: " static getDerivedStateFromError(error) {" }, + { num: 5, text: " return { hasError: true };" }, + { num: 6, text: " }" }, + { num: 7, text: "" }, + { num: 8, text: " componentDidCatch(error, info) {" }, + { num: 9, text: " logToService(error, info);" }, + { num: 10, text: " }" }, + { num: 11, text: "" }, + { num: 12, text: " render() {" }, + { num: 13, text: " if (this.state.hasError) {" }, + { num: 14, text: " return

Something went wrong.

;" }, + { num: 15, text: " }" }, + { num: 16, text: " return this.props.children;" }, + { num: 17, text: " }" }, + { num: 18, text: "}" }, + ], + steps: [ + { + descriptionHtml: + "Initial render: ErrorBoundary initializes with hasError: false. It renders its children normally.", + activeLine: 2, + doneLines: [1], + componentTree: { + id: "boundary", + label: "", + highlight: "active", + children: [ + { + id: "counter", + label: "", + children: [ + { id: "btn", label: '" }, + { num: 18, text: " );" }, + { num: 19, text: " }" }, + { num: 20, text: " return this.props.children;" }, + { num: 21, text: " }" }, + { num: 22, text: "}" }, + ], + steps: [ + { + descriptionHtml: + "Initial render: ResettableBoundary is in normal state, rendering its children.", + activeLine: 20, + doneLines: [1, 2, 12, 13], + componentTree: { + id: "boundary", + label: "", + highlight: "active", + children: [ + { id: "child", label: "" }, + ], + }, + activeNodeId: "boundary", + boundaries: [ + { id: "b1", label: "", phase: "normal" }, + ], + }, + { + descriptionHtml: + "DataPanel throws an error during render (e.g., a network-loaded component with bad data).", + activeLine: null, + doneLines: [1, 2, 12, 13, 20], + componentTree: { + id: "boundary", + label: "", + children: [ + { id: "child", label: "", highlight: "removed" }, + ], + }, + activeNodeId: "child", + boundaries: [ + { id: "b1", label: "", phase: "error-thrown", errorMessage: "Bad data" }, + ], + }, + { + descriptionHtml: + 'getDerivedStateFromError sets hasError: true. The boundary re-renders and shows the fallback: a "Try again" button.', + activeLine: 15, + doneLines: [1, 2, 4, 5, 6, 12, 13, 14], + componentTree: { + id: "boundary", + label: "", + children: [ + { id: "fallback", label: '