From 94387d57cdd899ccb1da80257ca73bb8fd80083e Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:34:17 +0530 Subject: [PATCH 01/48] docs(compaction): correct live-value example --- prisma/seed/challenges.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/seed/challenges.ts b/prisma/seed/challenges.ts index fd91e95..0c7336f 100644 --- a/prisma/seed/challenges.ts +++ b/prisma/seed/challenges.ts @@ -96,7 +96,7 @@ export const CHALLENGES = [ input: "slots = [4, -1, -1, 7, -1]", output: "[[3, 1]]", explain: - "Three live values means they belong in slots 0, 1 and 2. Slot 0 already holds one, and slots 1 and 2 are empty. Value 7 at slot 3 is the only one out of place, so one move is enough.", + "Two live values belong in slots 0 and 1. Slot 0 already holds one, and slot 1 is empty. Move value 7 from slot 3 to slot 1; one move is enough.", }, { kind: "callout", From 94a6a07ed648abcd9fb3a7d58060e73d6598564f Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:48:34 +0530 Subject: [PATCH 02/48] feat(content): add validated systems diagram primitives --- src/lib/arena/statement.ts | 72 +++++- src/packs/systems/concept-illustrations.tsx | 265 ++++++++++++++++++++ src/packs/systems/index.ts | 6 + tests/unit/lib/arena/statement.test.ts | 20 ++ 4 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 src/packs/systems/concept-illustrations.tsx diff --git a/src/lib/arena/statement.ts b/src/lib/arena/statement.ts index 6cf2d36..8d3cde2 100644 --- a/src/lib/arena/statement.ts +++ b/src/lib/arena/statement.ts @@ -15,15 +15,83 @@ const calloutBlock = z.object({ title: shortText.optional(), md: markdown, }).strict(); +const diagramTone = z.enum(["accent", "signal", "warning", "danger", "neutral"]); +const diagramCell = z.object({ label: shortText, detail: shortText.optional(), tone: diagramTone.optional() }).strict(); +const snapshotProps = z.object({ + title: shortText, + rows: z.array(z.object({ label: shortText, note: shortText.optional(), cells: z.array(diagramCell).min(1) }).strict()).min(2), +}).strict(); +const budgetProps = z.object({ + title: shortText, + capacity: z.number().positive().finite(), + snapshots: z.array(z.object({ + label: shortText, + note: shortText.optional(), + segments: z.array(z.object({ label: shortText, value: z.number().nonnegative().finite(), tone: diagramTone.optional() }).strict()).min(1), + }).strict()).min(2), +}).strict().superRefine((props, ctx) => { + for (const [index, snapshot] of props.snapshots.entries()) { + if (snapshot.segments.reduce((sum, segment) => sum + segment.value, 0) > props.capacity) { + ctx.addIssue({ code: "custom", message: "segments exceed capacity", path: ["snapshots", index] }); + } + } +}); +const frameProps = z.object({ + title: shortText, + note: shortText.optional(), + frames: z.array(z.object({ label: shortText, status: shortText.optional(), note: shortText.optional(), tone: diagramTone.optional(), fields: z.array(diagramCell).min(1) }).strict()).min(2), +}).strict(); +const rangeProps = z.object({ + title: shortText, + min: z.number().finite(), + max: z.number().finite(), + rows: z.array(z.object({ label: shortText, start: z.number().finite(), end: z.number().finite(), note: shortText.optional(), tone: diagramTone.optional() }).strict()).min(2), + markers: z.array(z.object({ at: z.number().finite(), label: shortText, tone: diagramTone.optional() }).strict()).optional(), +}).strict().superRefine((props, ctx) => { + if (props.max <= props.min) ctx.addIssue({ code: "custom", message: "max must exceed min" }); + for (const [index, row] of props.rows.entries()) { + if (row.start < props.min || row.end > props.max || row.end <= row.start) { + ctx.addIssue({ code: "custom", message: "range must fit the axis", path: ["rows", index] }); + } + } + for (const [index, marker] of (props.markers ?? []).entries()) { + if (marker.at < props.min || marker.at > props.max) { + ctx.addIssue({ code: "custom", message: "marker must fit the axis", path: ["markers", index] }); + } + } +}); +const exchangeProps = z.object({ + title: shortText, + left: shortText, + right: shortText, + events: z.array(z.object({ from: z.enum(["left", "right", "local"]), label: shortText, detail: shortText.optional(), dropped: z.boolean().optional(), tone: diagramTone.optional() }).strict()).min(2), +}).strict(); +const conceptFigureProps = { + "systems.snapshot": snapshotProps, + "systems.budget": budgetProps, + "systems.frames": frameProps, + "systems.ranges": rangeProps, + "systems.exchange": exchangeProps, +} as const; + const figureBlock = z.object({ kind: z.literal("figure"), illustration: z.object({ - id: z.enum(["systems.flow", "systems.state", "systems.timeline"]), + id: z.enum([ + "systems.flow", "systems.state", "systems.timeline", + "systems.snapshot", "systems.budget", "systems.frames", + "systems.ranges", "systems.exchange", + ]), props: z.record(z.string(), z.unknown()).optional(), }).strict(), caption: z.string().trim().min(1).max(1_000).optional(), label: z.string().trim().min(1).max(40).optional(), -}).strict(); +}).strict().superRefine((figure, ctx) => { + const schema = conceptFigureProps[figure.illustration.id as keyof typeof conceptFigureProps]; + if (schema && !schema.safeParse(figure.illustration.props).success) { + ctx.addIssue({ code: "custom", message: `invalid props for ${figure.illustration.id}`, path: ["illustration", "props"] }); + } +}); const exampleBlock = z.object({ kind: z.literal("example"), input: z.string().max(8_000), diff --git a/src/packs/systems/concept-illustrations.tsx b/src/packs/systems/concept-illustrations.tsx new file mode 100644 index 0000000..ec534c8 --- /dev/null +++ b/src/packs/systems/concept-illustrations.tsx @@ -0,0 +1,265 @@ +import { ArrowLeft, ArrowRight, Clock3, X } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +type Tone = "accent" | "signal" | "warning" | "danger" | "neutral"; + +const TONE_STYLES: Record = { + accent: "border-accent/45 bg-accent/15 text-ink", + signal: "border-signal/45 bg-signal/15 text-ink", + warning: "border-warning/45 bg-warning/15 text-ink", + danger: "border-danger/45 bg-danger/15 text-ink", + neutral: "border-edge-strong bg-raised/50 text-body", +}; + +const BAR_STYLES: Record = { + accent: "bg-accent/75", + signal: "bg-signal/75", + warning: "bg-warning/75", + danger: "bg-danger/75", + neutral: "bg-edge-strong", +}; + +interface Cell { + label: string; + detail?: string; + tone: Tone; +} + +function record(value: unknown): Record | null { + return value !== null && typeof value === "object" && !Array.isArray(value) + ? value as Record + : null; +} + +function string(value: unknown): string { + return typeof value === "string" ? value.trim() : ""; +} + +function tone(value: unknown): Tone { + return typeof value === "string" && value in TONE_STYLES ? value as Tone : "neutral"; +} + +function cells(value: unknown): Cell[] { + if (!Array.isArray(value)) return []; + return value.flatMap((item) => { + const data = record(item); + if (!data || !string(data.label)) return []; + return [{ label: string(data.label), detail: string(data.detail) || undefined, tone: tone(data.tone) }]; + }); +} + +function fallback() { + return

Diagram data is unavailable.

; +} + +function DiagramHeading({ title, detail }: { title?: string; detail?: string }) { + return ( +
+ {title} + {detail && {detail}} +
+ ); +} + +/** A sequence of concrete states, useful for cache and queue transitions. */ +export function SnapshotDiagram({ title, rows: rawRows }: { title?: string; rows?: unknown }) { + const rows = Array.isArray(rawRows) ? rawRows.flatMap((entry) => { + const data = record(entry); + if (!data || !string(data.label)) return []; + return [{ label: string(data.label), note: string(data.note), cells: cells(data.cells) }]; + }) : []; + if (rows.length < 2 || rows.some((row) => row.cells.length === 0)) return fallback(); + + return ( +
`${row.label}: ${row.cells.map((cell) => `${cell.label}${cell.detail ? ` ${cell.detail}` : ""}`).join(", ")}${row.note ? `. ${row.note}` : ""}`).join(". ")}`} className="space-y-3"> + + {rows.map((row, index) => ( +
+
+ {String(index + 1).padStart(2, "0")} + {row.label} +
+
+
+ {row.cells.map((cell, cellIndex) => ( +
+ {cell.label} + {cell.detail && {cell.detail}} +
+ ))} +
+ {row.note &&

{row.note}

} +
+
+ ))} +
+ ); +} + +/** Proportional capacity snapshots for memory budgets and token buckets. */ +export function BudgetDiagram({ title, capacity, snapshots: rawSnapshots }: { + title?: string; + capacity?: number; + snapshots?: unknown; +}) { + const limit = typeof capacity === "number" && Number.isFinite(capacity) && capacity > 0 ? capacity : 0; + const snapshots = Array.isArray(rawSnapshots) ? rawSnapshots.flatMap((entry) => { + const data = record(entry); + if (!data || !string(data.label)) return []; + const segments = Array.isArray(data.segments) ? data.segments.flatMap((item) => { + const segment = record(item); + if (!segment || !string(segment.label) || typeof segment.value !== "number" || !Number.isFinite(segment.value) || segment.value < 0) return []; + return [{ label: string(segment.label), value: segment.value, tone: tone(segment.tone) }]; + }) : []; + return [{ label: string(data.label), note: string(data.note), segments }]; + }) : []; + if (!limit || snapshots.length < 2 || snapshots.some((item) => item.segments.reduce((sum, segment) => sum + segment.value, 0) > limit)) return fallback(); + + return ( +
`${snapshot.label}: ${snapshot.segments.map((segment) => `${segment.label} ${segment.value}`).join(", ")}`).join(". ")}`} className="space-y-3.5"> + + {snapshots.map((snapshot, index) => { + const used = snapshot.segments.reduce((sum, segment) => sum + segment.value, 0); + return ( +
+
+ {snapshot.label} + {used} / {limit} +
+
+ {snapshot.segments.map((segment, segmentIndex) => ( +
+ ))} +
+

+ {snapshot.segments.map((segment) => `${segment.label} ${segment.value}`).join(" · ")} + {snapshot.note && ` · ${snapshot.note}`} +

+
+ ); + })} +
+ ); +} + +/** A framed byte layout with per-frame status and independently colored fields. */ +export function FrameDiagram({ title, frames: rawFrames, note }: { title?: string; frames?: unknown; note?: string }) { + const frames = Array.isArray(rawFrames) ? rawFrames.flatMap((entry) => { + const data = record(entry); + if (!data || !string(data.label)) return []; + return [{ label: string(data.label), status: string(data.status), note: string(data.note), tone: tone(data.tone), fields: cells(data.fields) }]; + }) : []; + if (frames.length < 2 || frames.some((frame) => frame.fields.length === 0)) return fallback(); + + return ( +
`${frame.label}${frame.status ? ` ${frame.status}` : ""}: ${frame.fields.map((field) => field.label).join(", ")}`).join(". ")}`} className="space-y-3"> + + {frames.map((frame, index) => ( +
+
+ {frame.label} + {frame.status && {frame.status}} +
+
+ {frame.fields.map((field, fieldIndex) => ( +
+ {field.label} + {field.detail && {field.detail}} +
+ ))} +
+ {frame.note &&

{frame.note}

} +
+ ))} +
+ ); +} + +/** A shared numeric axis makes waits, overlaps, and watermark boundaries visible. */ +export function RangeDiagram({ title, min, max, rows: rawRows, markers: rawMarkers }: { + title?: string; + min?: number; + max?: number; + rows?: unknown; + markers?: unknown; +}) { + const start = typeof min === "number" && Number.isFinite(min) ? min : 0; + const end = typeof max === "number" && Number.isFinite(max) ? max : 0; + const rows = Array.isArray(rawRows) ? rawRows.flatMap((entry) => { + const data = record(entry); + if (!data || !string(data.label) || typeof data.start !== "number" || typeof data.end !== "number") return []; + return [{ label: string(data.label), start: data.start, end: data.end, note: string(data.note), tone: tone(data.tone) }]; + }) : []; + const markers = Array.isArray(rawMarkers) ? rawMarkers.flatMap((entry) => { + const data = record(entry); + if (!data || typeof data.at !== "number" || !string(data.label)) return []; + return [{ at: data.at, label: string(data.label), tone: tone(data.tone) }]; + }) : []; + if (end <= start || rows.length < 2 || rows.some((row) => row.start < start || row.end > end || row.end <= row.start)) return fallback(); + const percent = (value: number) => `${(value - start) / (end - start) * 100}%`; + + return ( +
`${row.label} spans ${row.start} to ${row.end}${row.note ? `. ${row.note}` : ""}`).join(". ")}. ${markers.map((marker) => `${marker.label} at ${marker.at}`).join(". ")}`} className="space-y-2.5"> + + {rows.map((row, index) => ( +
+ {row.label} +
+
+
+ {markers.map((marker, markerIndex) => ( +
+ ))} +
+ {row.note &&

{row.note}

} +
+
+ ))} +
+ {start} + {markers.map((marker, index) => {marker.label} {marker.at})} + {end} +
+
+ ); +} + +/** A packet trace where lost messages and local timer events remain visible. */ +export function ExchangeDiagram({ title, left, right, events: rawEvents }: { + title?: string; + left?: string; + right?: string; + events?: unknown; +}) { + const events = Array.isArray(rawEvents) ? rawEvents.flatMap((entry) => { + const data = record(entry); + if (!data || !string(data.label) || !["left", "right", "local"].includes(string(data.from))) return []; + return [{ label: string(data.label), detail: string(data.detail), from: string(data.from), dropped: data.dropped === true, tone: tone(data.tone) }]; + }) : []; + if (events.length < 2) return fallback(); + + return ( +
`${event.from === "left" ? left ?? "Sender" : event.from === "right" ? right ?? "Receiver" : "Local timer"}: ${event.label}${event.dropped ? " lost" : ""}${event.detail ? `. ${event.detail}` : ""}`).join(". ")}`} className="space-y-2"> + +
+ {left ?? "Sender"}{right ?? "Receiver"} +
+ {events.map((event, index) => ( +
+ {event.from === "local" ? ( + <>
{event.label}
+ ) : ( + <> +
{event.from === "left" ? event.label : event.detail}
+
+ {event.dropped ? : event.from === "left" ? : } +
+
{event.from === "right" ? event.label : event.dropped ? "lost" : event.detail}
+ + )} +
+ ))} +
+ ); +} diff --git a/src/packs/systems/index.ts b/src/packs/systems/index.ts index fda0ec6..da2ae82 100644 --- a/src/packs/systems/index.ts +++ b/src/packs/systems/index.ts @@ -1,8 +1,14 @@ /** Generic visual primitives shared by every systems challenge. */ import { registerIllustration } from "../registry"; +import { BudgetDiagram, ExchangeDiagram, FrameDiagram, RangeDiagram, SnapshotDiagram } from "./concept-illustrations"; import { FlowDiagram, StateDiagram, TimelineDiagram } from "./illustrations"; registerIllustration("systems.flow", FlowDiagram); registerIllustration("systems.state", StateDiagram); registerIllustration("systems.timeline", TimelineDiagram); +registerIllustration("systems.snapshot", SnapshotDiagram); +registerIllustration("systems.budget", BudgetDiagram); +registerIllustration("systems.frames", FrameDiagram); +registerIllustration("systems.ranges", RangeDiagram); +registerIllustration("systems.exchange", ExchangeDiagram); diff --git a/tests/unit/lib/arena/statement.test.ts b/tests/unit/lib/arena/statement.test.ts index ba23008..a1ff7af 100644 --- a/tests/unit/lib/arena/statement.test.ts +++ b/tests/unit/lib/arena/statement.test.ts @@ -35,6 +35,26 @@ describe("arena educational statements", () => { }))).toThrow(); }); + it("rejects concept diagrams that would otherwise render blank or misleading data", () => { + const statement = (id: string, props: unknown) => JSON.stringify({ + version: 1, + blocks: [{ kind: "figure", illustration: { id, props } }], + }); + + expect(() => parseArenaStatement(statement("systems.snapshot", { title: "Missing states", rows: [] }))).toThrow(); + expect(() => parseArenaStatement(statement("systems.budget", { + title: "Overfilled", capacity: 4, + snapshots: [ + { label: "first", segments: [{ label: "used", value: 5 }] }, + { label: "second", segments: [{ label: "used", value: 1 }] }, + ], + }))).toThrow(); + expect(() => parseArenaStatement(statement("systems.ranges", { + title: "Outside axis", min: 0, max: 10, + rows: [{ label: "A", start: 0, end: 5 }, { label: "B", start: 8, end: 12 }], + }))).toThrow(); + }); + it("falls back only when statement.json is absent", async () => { const root = await mkdtemp(path.join(tmpdir(), "bytearena-statement-")); await expect(loadArenaStatement(root)).resolves.toBeNull(); From a45bb7353970478df95220550e7229fb3c8b5b0d Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:48:57 +0530 Subject: [PATCH 03/48] docs(ssd): show compaction outcome and correct examples --- prisma/seed/challenges.ts | 18 ++++++++++++++---- src/packs/ssd/illustrations/slot-strip.tsx | 7 ++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/prisma/seed/challenges.ts b/prisma/seed/challenges.ts index 0c7336f..c75a531 100644 --- a/prisma/seed/challenges.ts +++ b/prisma/seed/challenges.ts @@ -86,6 +86,16 @@ export const CHALLENGES = [ caption: "Five live values scattered across twelve slots. The line marks where they need to end up: slots 0 to 4.", }, + { + kind: "figure", + label: "FIG 2", + illustration: { + id: "ssd.slotStrip", + props: { slots: [4, 7, 2, 9, 5, -1, -1, -1, -1, -1, -1, -1], prefix: 5, highlight: [1, 2, 4] }, + }, + caption: + "Move the three values outside the prefix into its three holes. Each value moves once; all free slots end up together at the back.", + }, { kind: "prose", md: "Return the **moves** that get there. A move copies the value in one slot into an empty one, leaving the source empty. Moves are applied in the order you give them.\n\nThe catch is that a move is not free. Every one of them is real work, and the whole challenge is doing as few as possible.", @@ -214,7 +224,7 @@ export const CHALLENGES = [ "stats.blocks = [\n {index:0, valid:7, invalid:1, free:0, eraseCount:3, isOverProvision:false},\n {index:1, valid:2, invalid:6, free:0, eraseCount:4, isOverProvision:false},\n {index:2, valid:8, invalid:0, free:0, eraseCount:2, isOverProvision:false},\n {index:3, valid:1, invalid:7, free:0, eraseCount:5, isOverProvision:false},\n {index:4, valid:4, invalid:3, free:1, eraseCount:3, isOverProvision:false},\n {index:5, valid:0, invalid:0, free:8, eraseCount:6, isOverProvision:true},\n]\npagesPerBlock = 8", output: "3", explain: - "Block 3 has the most dead pages (7), so erasing it frees the most space for the least migration. Only one live page must be copied. Block 1 is also mostly dead but holds two live pages, so it costs twice as much to reclaim for the same gain. Greedy picks block 3 here, and greedy is right on this snapshot.\n\nThe hard part is that the snapshot changes. If block 3's single live page is about to be overwritten by the next write, waiting one more reclaim would have freed it for nothing. You would have paid one migration to save a page that was about to die on its own. Greedy cannot see that coming. A policy that tracks *why* a block is full of dead pages (hot data churning) can sometimes do better by reclaiming a block whose dead pages are dead for good, not just dead for now.", + "Block 3 has the most dead pages (7), so erasing it frees the most space for the least migration. Only one live page must be copied. Block 1 is also mostly dead, but it holds two live pages: it costs more to migrate and frees fewer slots. Greedy picks block 3 here, and greedy is right on this snapshot.\n\nThe hard part is that the snapshot changes. If block 3's single live page is about to be overwritten by the next write, waiting one more reclaim would have freed it for nothing. You would have paid one migration to save a page that was about to die on its own. Greedy cannot see that coming. A policy that tracks *why* a block is full of dead pages (hot data churning) can sometimes do better by reclaiming a block whose dead pages are dead for good, not just dead for now.", }, { kind: "figure", @@ -331,14 +341,14 @@ export const CHALLENGES = [ blocks: [ { kind: "prose", - md: "Everything from the previous rung still applies. One fact is added, and it changes the shape of a good answer completely: **a flash block wears out**. After some tens of thousands of erases it stops holding charge reliably, and a device is dead when enough of its blocks are.", + md: "Everything from the previous rung still applies. One fact is added, and it changes the shape of a good answer completely: **a flash block wears out**. Erase endurance is finite, so repeatedly reclaiming the same block can shorten device life even when write amplification looks good.", }, { kind: "figure", label: "FIG 1", illustration: { id: "ssd.wearHistogram", props: { eraseCounts: UNEVEN_WEAR } }, caption: - "This policy optimises only for write amplification. Block 5 is being erased four times as often as any other, so the device will fail there while every other block is barely used.", + "This policy optimises only for write amplification. Block 5 has 17 erases; the next highest has 5. One block is wearing more than three times as fast as the others.", }, { kind: "callout", @@ -357,7 +367,7 @@ export const CHALLENGES = [ "stats.blocks = [\n {index:0, valid:7, invalid:1, free:0, eraseCount:3, isOverProvision:false},\n {index:1, valid:2, invalid:6, free:0, eraseCount:4, isOverProvision:false},\n {index:2, valid:8, invalid:0, free:0, eraseCount:2, isOverProvision:false},\n {index:3, valid:1, invalid:7, free:0, eraseCount:17, isOverProvision:false},\n {index:4, valid:4, invalid:3, free:1, eraseCount:3, isOverProvision:false},\n {index:5, valid:0, invalid:0, free:8, eraseCount:6, isOverProvision:true},\n]\npagesPerBlock = 8", output: "1", explain: - "Greedy looks at invalid pages alone and picks block 3 (7 dead, 1 live). But block 3 has already been erased 17 times, far more than any other block, because its hot data keeps dying and being rewritten. Reclaiming it again accelerates it towards failure.\n\nBlock 1 is the wear-aware choice: 6 dead pages (almost as good for write amplification), 2 live pages (one extra migration), and an erase count of 4, which is near the device average. Spreading this erase to block 1 keeps the histogram flat. The cost is one extra migration write now; the saving is a block that does not burn out early.\n\nThis is the whole rung in one decision: the cheapest block *right now* is often the one you are already killing.", + "Greedy looks at invalid pages alone and picks block 3 (7 dead, 1 live). But block 3 has already been erased 17 times, far more than any other block, because its hot data keeps dying and being rewritten. Reclaiming it again adds wear to the current worst block.\n\nBlock 1 is a wear-aware choice: 6 dead pages, 2 live pages (one extra migration), and an erase count of 4. Reclaiming it avoids adding to block 3's already high count. The cost is one extra migration write now; whether that trade is worthwhile depends on future writes.\n\nThis is the whole rung in one decision: the cheapest block *right now* is often the one you are already wearing down.", }, { kind: "steps", diff --git a/src/packs/ssd/illustrations/slot-strip.tsx b/src/packs/ssd/illustrations/slot-strip.tsx index 8642be6..ad43fd9 100644 --- a/src/packs/ssd/illustrations/slot-strip.tsx +++ b/src/packs/ssd/illustrations/slot-strip.tsx @@ -90,5 +90,10 @@ function DeadSlotMark() { function describe(slots: readonly number[], prefix?: number): string { const live = slots.filter((v) => v !== EMPTY_SLOT).length; const base = `An array of ${slots.length} slots, ${live} of them holding live data`; - return prefix === undefined ? `${base}.` : `${base}, which should end up in the first ${prefix}.`; + if (prefix === undefined) return `${base}.`; + const packed = slots.slice(0, prefix).every((value) => value !== EMPTY_SLOT) + && slots.slice(prefix).every((value) => value === EMPTY_SLOT); + return packed + ? `${base}, packed into the first ${prefix} slots.` + : `${base}, which should end up in the first ${prefix} slots.`; } From 4c23161de8dfedf5595bb446bf307febf1c7a801 Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:49:01 +0530 Subject: [PATCH 04/48] fix(arena): make challenge reading usable on phones --- src/components/arena/arena.tsx | 11 +++++++++++ src/components/ui/split.tsx | 18 +++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/arena/arena.tsx b/src/components/arena/arena.tsx index c10534f..733e473 100644 --- a/src/components/arena/arena.tsx +++ b/src/components/arena/arena.tsx @@ -81,6 +81,7 @@ export function Arena({ const [language, setLanguage] = useState(languages[0] ?? "GO"); const [drafts, setDrafts] = useState>({}); const [leftTab, setLeftTab] = useState("description"); + const [mobilePane, setMobilePane] = useState<"problem" | "code">("problem"); const [fontIndex, setFontIndex] = useState(DEFAULT_FONT_INDEX); const [focusMode, setFocusMode] = useState(false); const [consoleOpen, setConsoleOpen] = useState(true); @@ -278,6 +279,15 @@ export function Arena({ onSubmit={submit} /> +
+ + +
+ -
+
{first}
@@ -178,6 +189,7 @@ export function Split({ }} className={cn( "group relative shrink-0 touch-none outline-none", + mobileVisiblePane && "max-md:hidden", // The hit area is deliberately wider than the visible line: a 1px // target is a usability bug even when it looks right. 24px is the // WCAG 2.5.8 minimum, and the right bar for a divider — the 44px @@ -199,7 +211,7 @@ export function Split({ />
-
{second}
+
{second}
); } From 275f896550a13c1e2385a0a24f803a628741ce76 Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:49:03 +0530 Subject: [PATCH 05/48] test(content): verify every diagram and mobile pane --- tests/e2e/challenge-visuals.spec.ts | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/e2e/challenge-visuals.spec.ts diff --git a/tests/e2e/challenge-visuals.spec.ts b/tests/e2e/challenge-visuals.spec.ts new file mode 100644 index 0000000..d985575 --- /dev/null +++ b/tests/e2e/challenge-visuals.spec.ts @@ -0,0 +1,43 @@ +import { expect, test } from "@playwright/test"; + +const SSD_SLUGS = ["compaction", "victim-selection", "wear-levelling"]; +const EXTERNAL_SLUGS = [ + "cache-pressure-easy", + "cache-pressure-medium", + "chunk-store-easy", + "deadline-scheduler-easy", + "durable-queue-easy", + "fair-gate-easy", + "journal-recovery-easy", + "lossy-link-easy", + "lsm-compactor-easy", + "stream-windows-easy", +]; + +for (const slug of [...SSD_SLUGS, ...EXTERNAL_SLUGS]) { + test(`educational diagram renders for ${slug}`, async ({ page }) => { + test.skip(EXTERNAL_SLUGS.includes(slug) && !process.env.BYTEARENA_ARENA_ROOTS, + "External challenge checkout is not configured"); + await page.goto(`/challenges/${slug}`); + await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); + await expect(page.locator('[role="img"][aria-label]').first()).toBeVisible(); + await expect(page.getByText("Diagram data is unavailable.")).toHaveCount(0); + }); +} + +test("phone layout gives the problem and editor their own readable views", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/challenges/compaction"); + + const diagram = page.locator('[role="img"][aria-label]').first(); + await expect(diagram).toBeVisible(); + const bounds = await diagram.boundingBox(); + expect(bounds?.width).toBeGreaterThan(300); + + await page.getByRole("button", { name: "Code & results" }).click(); + await expect(diagram).toBeHidden(); + await expect(page.locator(".monaco-editor")).toBeVisible(); + + await page.getByRole("button", { name: "Problem", exact: true }).click(); + await expect(diagram).toBeVisible(); +}); From 392ca7391fa8cee87b069bb57078dea9d61ddd84 Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:49:43 +0530 Subject: [PATCH 06/48] fix(arena): keep mobile focus and pane state coherent --- src/components/arena/arena.tsx | 22 +++++++++++++++++++--- tests/e2e/challenge-visuals.spec.ts | 1 + 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/components/arena/arena.tsx b/src/components/arena/arena.tsx index 733e473..3221109 100644 --- a/src/components/arena/arena.tsx +++ b/src/components/arena/arena.tsx @@ -226,7 +226,10 @@ export function Arena({ setFocusMode((on) => !on)} + onClick={() => { + setFocusMode((on) => !on); + setMobilePane("code"); + }} > {focusMode ? ( @@ -280,10 +283,23 @@ export function Arena({ />
- -
diff --git a/tests/e2e/challenge-visuals.spec.ts b/tests/e2e/challenge-visuals.spec.ts index d985575..c2d34c5 100644 --- a/tests/e2e/challenge-visuals.spec.ts +++ b/tests/e2e/challenge-visuals.spec.ts @@ -37,6 +37,7 @@ test("phone layout gives the problem and editor their own readable views", async await page.getByRole("button", { name: "Code & results" }).click(); await expect(diagram).toBeHidden(); await expect(page.locator(".monaco-editor")).toBeVisible(); + await page.getByRole("button", { name: "Focus mode" }).click(); await page.getByRole("button", { name: "Problem", exact: true }).click(); await expect(diagram).toBeVisible(); From 1eccdb7dd0b8c769cb1a571f5f084bec19f52557 Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:52:34 +0530 Subject: [PATCH 07/48] fix(content): clean diagram descriptions for screen readers --- src/packs/systems/concept-illustrations.tsx | 12 ++++++++++-- tests/e2e/challenge-visuals.spec.ts | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/packs/systems/concept-illustrations.tsx b/src/packs/systems/concept-illustrations.tsx index ec534c8..83410cf 100644 --- a/src/packs/systems/concept-illustrations.tsx +++ b/src/packs/systems/concept-illustrations.tsx @@ -36,6 +36,10 @@ function string(value: unknown): string { return typeof value === "string" ? value.trim() : ""; } +function withoutFinalPunctuation(value: string): string { + return value.replace(/[.!?]+$/, ""); +} + function tone(value: unknown): Tone { return typeof value === "string" && value in TONE_STYLES ? value as Tone : "neutral"; } @@ -71,8 +75,10 @@ export function SnapshotDiagram({ title, rows: rawRows }: { title?: string; rows }) : []; if (rows.length < 2 || rows.some((row) => row.cells.length === 0)) return fallback(); + const description = rows.map((row) => `${row.label}: ${row.cells.map((cell) => `${cell.label}${cell.detail ? ` ${cell.detail}` : ""}`).join(", ")}${row.note ? `. ${withoutFinalPunctuation(row.note)}` : ""}`).join(". "); + return ( -
`${row.label}: ${row.cells.map((cell) => `${cell.label}${cell.detail ? ` ${cell.detail}` : ""}`).join(", ")}${row.note ? `. ${row.note}` : ""}`).join(". ")}`} className="space-y-3"> +
{rows.map((row, index) => (
@@ -199,8 +205,10 @@ export function RangeDiagram({ title, min, max, rows: rawRows, markers: rawMarke if (end <= start || rows.length < 2 || rows.some((row) => row.start < start || row.end > end || row.end <= row.start)) return fallback(); const percent = (value: number) => `${(value - start) / (end - start) * 100}%`; + const description = rows.map((row) => `${row.label} spans ${row.start} to ${row.end}${row.note ? `. ${withoutFinalPunctuation(row.note)}` : ""}`).join(". "); + return ( -
`${row.label} spans ${row.start} to ${row.end}${row.note ? `. ${row.note}` : ""}`).join(". ")}. ${markers.map((marker) => `${marker.label} at ${marker.at}`).join(". ")}`} className="space-y-2.5"> +
`${marker.label} at ${marker.at}`).join(". ")}`} className="space-y-2.5"> {rows.map((row, index) => (
diff --git a/tests/e2e/challenge-visuals.spec.ts b/tests/e2e/challenge-visuals.spec.ts index c2d34c5..8d892db 100644 --- a/tests/e2e/challenge-visuals.spec.ts +++ b/tests/e2e/challenge-visuals.spec.ts @@ -22,6 +22,9 @@ for (const slug of [...SSD_SLUGS, ...EXTERNAL_SLUGS]) { await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); await expect(page.locator('[role="img"][aria-label]').first()).toBeVisible(); await expect(page.getByText("Diagram data is unavailable.")).toHaveCount(0); + if (slug === "cache-pressure-easy") { + expect(await page.locator('[role="img"][aria-label]').first().getAttribute("aria-label")).not.toContain(".."); + } }); } From 94057c8ffac1164b32eac6be06cca319a21e8d0b Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:57:01 +0530 Subject: [PATCH 08/48] fix(arena): accept optional fault profile in run results --- src/lib/arena/result.ts | 1 + tests/unit/lib/arena/result.test.ts | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/lib/arena/result.ts b/src/lib/arena/result.ts index c557cdb..f0f8c25 100644 --- a/src/lib/arena/result.ts +++ b/src/lib/arena/result.ts @@ -38,6 +38,7 @@ export const arenaResultV1Schema = z run: z .object({ workload_id: identifierSchema, + fault_profile: identifierSchema.optional(), duration_ns: nonnegativeCounterSchema, peak_memory_bytes: nonnegativeCounterSchema, }) diff --git a/tests/unit/lib/arena/result.test.ts b/tests/unit/lib/arena/result.test.ts index 5fd0e29..d32baa8 100644 --- a/tests/unit/lib/arena/result.test.ts +++ b/tests/unit/lib/arena/result.test.ts @@ -74,6 +74,15 @@ describe("Arena Result v1", () => { expect(parseArenaResult(result).seed).toBe("18446744073709551615"); }); + it("accepts a named fault profile without relaxing the run envelope", () => { + const parsed = JSON.parse(result) as { run: Record }; + parsed.run.fault_profile = "torn-tail"; + expect(parseArenaResult(JSON.stringify(parsed)).run.fault_profile).toBe("torn-tail"); + + parsed.run.unexpected = "ignored by nobody"; + expect(() => parseArenaResult(JSON.stringify(parsed))).toThrow(); + }); + it("matches arena identity, version, and the complete metric contract", () => { expect(() => assertResultMatchesManifest(parseArenaResult(result), manifest)).not.toThrow(); }); From 5bed88ba19b46f3492f2e06a91bc5817eaceeea5 Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 16:50:58 +0530 Subject: [PATCH 09/48] docs: define light-first learning interface direction --- docs/visual-direction.md | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/visual-direction.md diff --git a/docs/visual-direction.md b/docs/visual-direction.md new file mode 100644 index 0000000..61d4223 --- /dev/null +++ b/docs/visual-direction.md @@ -0,0 +1,56 @@ +# Visual direction: a systems field guide with a working lab + +Rough Stack teaches by making invisible systems visible. The interface should +feel curious, lucid, and alive, while the code editor and measurements remain +trustworthy instruments. This is a light-first product, not a dark developer +dashboard with pastel accents. + +## Principles + +1. **Show the mechanism.** Every challenge family needs a recognizable diagram + that explains a transition, boundary, or tradeoff. Decorative art never + substitutes for the concept diagram or its text alternative. +2. **Reading first, doing next.** A short invitation leads to a concrete model, + then the contract, example, editor, and feedback. Each view should help the + learner know what to do next without burying the task in chrome. +3. **One visual grammar.** Warm paper, dark ink, generous whitespace, strong + headings, rounded but precise geometry, and consistent states connect the + marketing pages to the challenge workspace. +4. **Color has a job.** Blue identifies action and links; green is success; + amber warns; coral marks failure. Topic colors differentiate families but + never carry correctness alone. Diagrams also use labels, position, and shape. +5. **Honesty is part of polish.** Practice-only challenges are not presented as + ranked, and the site describes the execution path it actually provides. + +## Palette and material + +The default floor is warm off-white, panels are white, and soft sage-tinted +surfaces group supporting material. Main text is deep blue-black. The primary +action is saturated cobalt, with mint, marigold, and coral used sparingly for +topic artwork and states. Diagrams should resemble a well-edited technical +notebook: light fills, crisp borders, explicit labels, and enough room to see +state change. Code uses a quiet light instrument panel, not a disconnected +dark rectangle. All meaningful text and controls must meet WCAG AA contrast. + +## Journey and implementation order + +1. Correct execution and scoring language across home, footer, library, + leaderboard, dashboard, and arena. Generated arena packages are practice + until calibrated ranking exists. +2. Replace global design tokens and shared components. Verify focus, active, + disabled, error, and reduced-motion states before decorating pages. +3. Recompose the home page around a concrete concept demonstration and a clear + path into the library. The first viewport should feel like learning systems, + not reading hosting claims. +4. Make the challenge library scannable by topic and depth. Give each family a + distinct small mechanism illustration using code-native SVG/CSS; use the same + visual vocabulary in its lesson. +5. Bring the light system through statement blocks, diagrams, editor, console, + discussion, and submissions, preserving working interactions on phones. +6. Run unit, build, and viewport/interaction checks; inspect real pages and + actual challenge submissions. Fix regressions before considering a phase + complete. + +The public challenge audit remains the source of truth for oracle/property-test +gaps. This visual work must not claim those gaps are closed by a new coat of +paint. From 16eec4ba493366b04be876c727c374d0c8a4fb80 Mon Sep 17 00:00:00 2001 From: Omkar Tripathi <38633467+triggeredcode@users.noreply.github.com> Date: Thu, 15 Oct 2026 16:51:54 +0530 Subject: [PATCH 10/48] fix: present practice challenges and execution honestly --- src/app/(site)/challenges/page.tsx | 11 ++++++----- src/app/(site)/dashboard/page.tsx | 26 +++++++++++++++++++------- src/app/(site)/leaderboard/page.tsx | 4 +++- src/app/(site)/page.tsx | 12 ++++++------ src/components/footer.tsx | 14 ++++---------- 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/app/(site)/challenges/page.tsx b/src/app/(site)/challenges/page.tsx index 4d30b00..bcbb01f 100644 --- a/src/app/(site)/challenges/page.tsx +++ b/src/app/(site)/challenges/page.tsx @@ -13,7 +13,7 @@ import { Eyebrow } from "@/components/ui/label"; export const metadata = { title: "Challenges", description: - "Hands-on systems engineering challenges. Implement real firmware, scored on real metrics.", + "Hands-on systems engineering challenges with runnable feedback and measurable behavior.", }; export const dynamic = "force-dynamic"; @@ -133,10 +133,11 @@ export default async function ChallengesPage() {

How scoring works

- Solutions are graded on a deterministic, seeded workload so every - submission faces identical traffic. Metrics are compared against a - reference solution and a clairvoyant lower bound. Your score is a - weighted ratio. Beat the reference to score above 100. + Each submission receives a verdict and workload measurements. + The original flash-storage challenges have ranked scores. The + newer systems challenges are practice exercises: their public + metrics are useful feedback, but they are not calibrated for a + leaderboard yet.

diff --git a/src/app/(site)/dashboard/page.tsx b/src/app/(site)/dashboard/page.tsx index 1d14caf..e4891f1 100644 --- a/src/app/(site)/dashboard/page.tsx +++ b/src/app/(site)/dashboard/page.tsx @@ -23,6 +23,7 @@ interface SubmissionRow { title: string; difficulty: string; category: string; + pack: string; }; result: { passed: boolean; @@ -39,6 +40,7 @@ export default async function DashboardPage() { slug: string; difficulty: string; category: string; + pack: string; bestScore: number; attempts: number; passed: boolean; @@ -60,6 +62,7 @@ export default async function DashboardPage() { title: true, difficulty: true, category: true, + pack: true, }, }, result: { @@ -88,6 +91,7 @@ export default async function DashboardPage() { challengeTitle: string; difficulty: string; category: string; + pack: string; bestScore: number; attempts: bigint; passed: boolean; @@ -98,6 +102,7 @@ export default async function DashboardPage() { c.title AS "challengeTitle", c.difficulty::text AS "difficulty", c.category, + c.pack, MAX(r.score) AS "bestScore", COUNT(*) AS "attempts", BOOL_OR(r.passed) AS "passed" @@ -106,7 +111,7 @@ export default async function DashboardPage() { JOIN "Challenge" c ON c.id = s."challengeId" WHERE s."sessionId" = ${sessionToken} AND c."isPublished" = true - GROUP BY c.slug, c.title, c.difficulty, c.category + GROUP BY c.slug, c.title, c.difficulty, c.category, c.pack ORDER BY "bestScore" DESC `; bestPerChallenge = bestRows.map((r) => ({ @@ -114,6 +119,7 @@ export default async function DashboardPage() { slug: r.challengeSlug, difficulty: r.difficulty, category: r.category, + pack: r.pack, bestScore: r.bestScore, attempts: Number(r.attempts), passed: r.passed, @@ -134,7 +140,7 @@ export default async function DashboardPage() { {bestPerChallenge.length > 0 && (

- Best scores + Your challenges

{bestPerChallenge.map((c) => ( @@ -152,10 +158,14 @@ export default async function DashboardPage() {
-

- {c.bestScore} -

-

/ 100

+ {c.pack === "arena" ? ( +

Practice

+ ) : ( + <> +

{c.bestScore}

+

/ 100

+ + )}
@@ -221,7 +231,9 @@ export default async function DashboardPage() { /> - {s.result ? s.result.score : "Not scored"} + {s.challenge.pack === "arena" + ? "Practice" + : s.result ? s.result.score : "Not scored"}