diff --git a/.env.example b/.env.example index 12c41e5..d4562e8 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,10 @@ # Copy to .env.local and fill in. Never commit .env.local. +# --- App label ------------------------------------------------------------ +# Visitor-facing prototype label. Organization, package imports, and URLs stay +# Roughstack; changing this value and rebuilding updates navigation + metadata. +NEXT_PUBLIC_PLATFORM_NAME="Rivets" + # --- Database ------------------------------------------------------------- # Host-side value. Inside docker compose the web service overrides the host to # `postgres`, since that is the service name on the compose network. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01072bf..47fecf7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to Rough Stack +# Contributing to Roughstack -Thank you for helping build Rough Stack. Contributions should keep the platform +Thank you for helping build Roughstack. Contributions should keep the platform reproducible, reviewable, secure around untrusted code, and independent from private official-evaluation material. diff --git a/LICENSE b/LICENSE index 402544d..d1f204d 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2026 Rough Stack contributors + Copyright 2026 Roughstack contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE index e1f1743..1ab962c 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ -Rough Stack -Copyright 2026 Rough Stack contributors +Roughstack +Copyright 2026 Roughstack contributors This product is licensed under the Apache License, Version 2.0. diff --git a/README.md b/README.md index 2e647d1..a591f32 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,21 @@ -# Rough Stack +# Roughstack web platform -An interactive coding platform and secure evaluation engine for **systems engineering** -challenges. Not another algorithm quiz site: you implement real storage and hardware -internals, and your solution is graded on the metrics engineers actually argue about. +The visitor-facing app currently prototypes the name **Rivets**. The +organization and repository names remain Roughstack. Set +`NEXT_PUBLIC_PLATFORM_NAME` before building to try a different label; the +navigation, footer, and metadata read it from one place in `src/lib/brand.ts`. +The symbol is separate from that label. Its shared source is +`src/components/brand-mark.tsx`, including the generated browser icon. -The first challenge asks you to write the data-movement policy for a simulated NAND -flash device. Your code is scored on write amplification, erase count, and wear evenness -against a synthetic workload of thousands of operations, then attacked with injected -hardware faults to see whether the logic actually holds up. +An interactive coding platform for **systems engineering** challenges. You implement +storage and systems internals, run them against deterministic workloads, and inspect +the resulting behavior. Generated challenges currently provide practice feedback; +their zero-valued scores are not a ranking system. + +The original SSD challenges ask you to write policies for a simulated NAND flash +device. Those challenges measure write amplification, erase count, and wear evenness +against synthetic workloads and injected faults. The newer Go practice challenges +cover caching, queues, scheduling, logs, and related systems concepts. The current open-source runtime executes submissions through a local Docker backend. A synthetic stub is available for UI development. Durable judging, @@ -182,5 +190,5 @@ docs/ ## License -Rough Stack is licensed under the [Apache License 2.0](LICENSE). See +Roughstack is licensed under the [Apache License 2.0](LICENSE). See [NOTICE](NOTICE) for attribution information. diff --git a/SECURITY.md b/SECURITY.md index 5ea3f8d..305c96c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ # Security policy -Rough Stack executes participant-controlled code. Reports involving sandbox +Roughstack executes participant-controlled code. Reports involving sandbox escape, path traversal, secret exposure, authentication bypass, result forgery, queue corruption, denial of service, or private-evaluation leakage must be handled privately. diff --git a/SUPPORT.md b/SUPPORT.md index a6befde..51ee349 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -6,7 +6,7 @@ - Use `SECURITY.md` for vulnerabilities; never publish exploit details in an issue. -Rough Stack is community-maintained software. Support is best effort and does not +Roughstack is community-maintained software. Support is best effort and does not include service-level guarantees. Include operating system, runtime versions, the exact revision, relevant logs with secrets removed, and minimal reproduction steps when asking for help. diff --git a/docs/adr/0001-arena-repository-boundaries.md b/docs/adr/0001-arena-repository-boundaries.md index 00f47ae..ca591f1 100644 --- a/docs/adr/0001-arena-repository-boundaries.md +++ b/docs/adr/0001-arena-repository-boundaries.md @@ -5,7 +5,7 @@ ## Context -Rough Stack needs to support multiple independently evolving systems arenas without +Roughstack needs to support multiple independently evolving systems arenas without turning the web application and judge into a collection of arena-specific branches. Official evaluation also needs hidden workloads and reference material that must never enter a public build. @@ -16,7 +16,7 @@ evaluation material to leak into a public image or artifact. ## Decision -Rough Stack uses three repository boundaries: +Roughstack uses three repository boundaries: 1. `bytearena` is the public platform repository. It owns contracts, discovery, validation, scoring primitives, the web/control plane, judge worker, execution @@ -30,8 +30,8 @@ Rough Stack uses three repository boundaries: The dependency direction is one-way: ```text -bytearena-arenas ──uses──> versioned Rough Stack contracts and SDK -bytearena-official ─uses──> versioned Rough Stack contracts and public arena identity +bytearena-arenas ──uses──> versioned Roughstack contracts and SDK +bytearena-official ─uses──> versioned Roughstack contracts and public arena identity bytearena ─loads──> immutable arena bundles through public contracts ``` diff --git a/docs/adr/0002-repository-responsibilities.md b/docs/adr/0002-repository-responsibilities.md index 84d3a27..7795645 100644 --- a/docs/adr/0002-repository-responsibilities.md +++ b/docs/adr/0002-repository-responsibilities.md @@ -15,7 +15,7 @@ deployments and gives public repositories names that do not describe their conte ## Decision -Rough Stack uses four repositories with explicit responsibilities: +Roughstack uses four repositories with explicit responsibilities: 1. `roughstack/web-platform` contains the website, editor, accounts, discussions, submissions, database schema, control plane, and deployment configuration. diff --git a/docs/dependency-policy.md b/docs/dependency-policy.md index 88b21e0..8efe546 100644 --- a/docs/dependency-policy.md +++ b/docs/dependency-policy.md @@ -1,6 +1,6 @@ # Dependency policy -Rough Stack treats dependency maintenance as part of its security posture. +Roughstack treats dependency maintenance as part of its security posture. ## Requirements diff --git a/docs/design-exhibition.md b/docs/design-exhibition.md new file mode 100644 index 0000000..667840f --- /dev/null +++ b/docs/design-exhibition.md @@ -0,0 +1,54 @@ +# Rivets: computing exhibition + +Active visual direction, 16 October 2026. This supersedes the palette and page compositions in `design-system.md`; its accessibility and interaction requirements still apply. + +## Intent + +Computer science presented with the visual confidence of an exhibition identity. The audience is curious, technical, and comfortable with complexity. Avoid school-workbook styling: no graph paper, decorative schematic corners, dotted backdrops, or ubiquitous pastel cards. Actual axes, memory slots, and data relationships remain where they explain a concept. + +## Tokens + +| Role | Value | +| --- | --- | +| Exhibition dark | `#282334` | +| Reading paper | `#f2f0ed` | +| Signal orange | `#ff784f` | +| Electric lilac | `#b9a1ff` | +| Mint | `#a9e8cb` | +| Dark text | `#252231` | + +Bricolage Grotesque carries the large, compact headlines. Inter carries explanations. JetBrains Mono is reserved for scores, counts, addresses and code. White text sits on aubergine; aubergine text sits on the bright colors. Do not use white body text on orange or lilac. + +## Composition + +```text +HOME dark navigation / monumental headline + moving storage cutaway + orange action / journal recovery story on an acid-lime field + paper reading surface / large subject illustrations + +DASHBOARD lilac workbench header / oversize completion counter + dark next-experiment panel / colored progress segments + illustrated challenge collection / light submission history + +RANKINGS dark score-board header / orange focal type + three leading results as large typographic panels + light table / clear numeric hierarchy +``` + +Color blocks, scale contrasts, asymmetry and custom geometry provide character. Borders and rounded cards should not be the primary source of hierarchy. Keep text left-aligned and preserve open space around the single focal element in each section. + +## Mark and illustration + +The identity is a modular R: a vertical spine, an open bowl, and a detached diagonal leg. It suggests construction and decomposition, remains recognizable at favicon size, and is independent of the configurable platform name. Navbar and favicon share the same SVG source. + +Challenge art uses saturated flat grounds without wallpaper. Difficulty variants differ in shape as well as color. Within the two-column storage collection, card grounds vary across each row and down adjacent rows. The landing storage machine is a conceptual illustration, not a scored run. + +## Motion and review + +### Landing: motion without a manual + +The palette and modular R at `design-exhibition-baseline-2026-10-16` are the approved checkpoint. The hero is a conceptual storage cutaway: violet layers (#7954ef), hot-pink writes (#ff4ba0), cyan routes (#53d9ff), and lime cells (#b7ff45) against plum (#282334). It runs when visible without controls or explanatory overlays. The green completion tick was removed after visual review. Pause animation offscreen and render static art when reduced motion is requested. + +The second vignette asks how a file survives an interrupted write. A journal, power interruption, and recovered file sit on a violet illustration field within an acid-lime section. Brief copy leads to the crash-recovery challenge. It is an illustration, not an executable simulation. + +Keep the Bricolage headline, readable body typography, left-aligned copy, open composition, and modular logo. Subject cards can respond to hover or focus with subtle motion. Review the homepage, library, dashboard, leaderboard, and both cache difficulty cards on desktop and phone sizes. Verify keyboard focus, contrast, reduced motion, and responsive layout. A passing build does not establish aesthetic quality. diff --git a/docs/design-system.md b/docs/design-system.md new file mode 100644 index 0000000..a06b3f1 --- /dev/null +++ b/docs/design-system.md @@ -0,0 +1,176 @@ +--- +title: Rivets visual design system +status: active implementation specification +updated: 2026-10-16 +scope: public site, challenge discovery, progress, rankings +supersedes: visual-direction.md +--- + +# Rivets design system + +> The active art direction is now [Computing exhibition](design-exhibition.md). The earlier palette and compositions below are retained as design history; accessibility and truthful interaction requirements still apply. + +Read this before changing the public UI. The product teaches computer science +through implementation. Its audience is curious enough to open a computer and +ask how it works, but does not necessarily know the vocabulary yet. The first +impression should invite that curiosity; the workspace should sustain it. + +## Direction: a science magazine you can operate + +A large, original illustration and confident typography establish the first +impression. A short interaction reveals an idea inside the illustration. +Explanations, controls, and results become more detailed as a learner proceeds. +The landing page is an invitation; it does not need to show the entire lesson. + +The previous dark half-adder panel is not the visual direction. Its density, +tiny labels, nested rectangles, and truth table made the hero feel like a +diagnostic console. Correct behavior alone is not visual acceptance. + +Keep the challenge library's domain grouping. Give difficulty variants visibly +different art while retaining the family's visual identity. Dashboard and +leaderboard should use the same type and color language, with real data taking +the place of decorative statistics. + +## Reference observations and decisions + +| Reference | Observed principle | Rivets application | +| --- | --- | --- | +| [MIT Technology Review, Pentagram](https://www.pentagram.com/work/mit-technology-review) | Flexible grid; a few large visual ideas; distinct display, reading, and caption roles; bright color balanced by open space. | One dominant hero image; large unbroken headline; quiet descriptions; captions attached to the visual they explain. | +| [Thinking Machines Lab](https://thinkingmachines.ai/) | Narrow readable text measure and unusually generous space around its identity. | Limit supporting copy to about 55–65 characters per line. Leave real space around the hero rather than filling it with status badges. | +| [Muriel Cooper, Pentagram](https://www.pentagram.com/work/muriel) | Typography and motion can express the behavior of information. | Animate a change in memory position; keep the values identifiable during the movement. | +| [Quanta art direction, Olena Shmahalo](https://www.olenashmahalo.com/project/quanta) | Scientific concepts provide subjects for editorial illustration. | Draw a specific mechanism with a clear silhouette. Do not use random circuitry as a substitute for an idea. | + +These are principles, not assets to reproduce. The following tokens and recipes +are original project decisions. Taste still requires a rendered review. + +## Tokens + +```yaml +palette: + paper: '#f8fafc' # cool light canvas, dominant surface + ink: '#172730' # reading and outlines + cobalt: '#2447e7' # primary action, one dominant illustration mass + coral: '#f27657' # contrasting illustration mass, not small text + pollen: '#f4ce57' # supporting illustration mass + mist: '#e4eafb' # quiet structure and secondary surfaces +type: + display: Bricolage Grotesque + body: Inter + technical: JetBrains Mono + hero: 'clamp(3.2rem, 6.8vw, 6.4rem); 0.98 line-height; -0.055em tracking' + section: 'clamp(2rem, 3.8vw, 3.6rem); 1.05 line-height' + body_size: '16–18px; 1.55–1.65 line-height' + caption: '12–14px; 1.4 line-height' +layout: + max_width: 1152px + gutter: '20px phone, 32px desktop' + section_gap: '64px phone, 96px desktop' + reading_measure: 65ch +motion: + response: 180ms + spatial_transition: 600ms + reduced_motion: immediate state change +``` + +Use at least two thirds light or neutral space in a typical viewport. Saturation +belongs primarily to illustration and the main action. Coral and yellow are +surfaces, not body-text colors. Labels must pass contrast checks on their actual +background. Do not put a gradient wash behind every section. + +Display type carries the invitation. Body type carries explanation. Monospace +is reserved for code, addresses, counts, and state. Keep full headlines one +color and one family. Do not turn every caption into tracked uppercase text. + +## Page composition + +### Landing page + +```text +brand navigation / start + +Large, short headline One oversized sculptural memory illustration +Two lines of explanation Values and spaces are immediately distinguishable +One clear primary action One action; one sentence explains the result + +Featured challenges: three distinct subjects with short descriptions + +One small experiment + explanation of how to learn here +``` + +The hero illustration is an isometric memory ribbon with visible gaps and +colored blocks. Its shape must be interesting before interaction. Compaction +moves the same values into the first available positions, preserving their +order; a reset restores the scattered state. This is a conceptual illustration, +not an execution result. Do not add a fake terminal, fake live statistics, or +another table inside it. + +Remove sections that restate the same promise. A visitor should reach the +challenge choices within roughly two desktop viewports. The entire front page +does not need to list every future topic. Neural-network content remains a +future direction until a real exercise exists. + +### Challenge variants + +Family determines silhouette; difficulty changes palette and an actual detail. +For Cache Pressure, Easy uses green with two equal blocks. Medium uses violet +with unequal blocks and a small clock motif, reflecting variable sizes and +expiry. Difficulty remains a text badge. Color alone must not identify a tier. +Unknown slugs continue to receive usable artwork and a normal challenge link. + +### Dashboard + +Treat the page as a personal workbench: a compact editorial heading, a useful +next action, followed by genuine progress and history. Empty state uses a small +memory composition and a direct link to a first challenge. Never invent streaks, +completion percentages, scores, or sign-in capabilities. + +### Leaderboard + +Use a strong heading and compact comparison motif above a legible table. The +data must remain primary. Distinguish the current session and leading ranks +with restrained color. Explain which challenges are ranked without filling the +header with implementation details. A decorative motif must not imply real +rankings when there are no results. + +## Illustration construction + +- Start with a silhouette that reads at thumbnail size. At most three major + color masses should compete in one illustration. +- Use consistent geometry: one projection, one light direction, one stroke + scale. Align connectors to shapes; do not leave near-misses or arbitrary bends. +- Use depth to separate parts, not as a background effect. Avoid blur clouds, + fake glass, and glossy panels around otherwise flat diagrams. +- Keep labels outside the main shapes when possible. The page caption can + explain a metaphor without placing a paragraph inside the art. +- Preserve object identity through motion. Animate only a learner-triggered + transformation; retain a useful static view with reduced motion. +- Prefer code-native SVG for diagrams that change state. Raster art is suitable + for editorial imagery when it adds a visual quality SVG cannot supply. + +## Review gate + +Review the rendered page at 390px, 768px, and 1440px, plus the 320px overflow +check. Ask these questions separately from automated tests: + +1. Is there one obvious focal point within three seconds? +2. Does the image have a deliberate silhouette, color balance, and negative space? +3. Does the headline read naturally without awkward one-word orphan lines? +4. Can the idea be understood before reading the small caption? +5. Does interaction produce a visible, meaningful change? +6. Can Easy and Medium be distinguished at thumbnail size? +7. Do progress and ranking views belong to the same visual family? + +Reject a design if any of the first five answers is no. Review screenshots, +not just the source. Test contrast, overflow, keyboard access, and reduced motion +as functional gates; passing them is not evidence of aesthetic quality. + +## Contributor continuation + +Implementation order: tokens and reusable illustration, landing composition, +variant art, dashboard and leaderboard, then visual review and focused checks. +Keep changes in atomic commits. Preserve the existing baseline tag +`visual-baseline-before-bold-cs-2026-10-16` for comparison. + +When revising this guide, update the tokens, recipes, and review criteria in +place. Record stable design decisions, not assistant transcripts. Link this +guide from older design notes so a future contributor has one source of truth. diff --git a/docs/product.md b/docs/product.md index c52d1c1..b58fdd5 100644 --- a/docs/product.md +++ b/docs/product.md @@ -1,9 +1,9 @@ -Rough Stack: a systems internals learning platform +Roughstack: a systems internals learning platform The Vision: -The software engineering interview and learning ecosystem is saturated with tools for practicing algorithmic problems or basic frontend components. Few platforms let engineers write and execute code for low-level systems challenges such as balancing an LSM tree, writing a wear-leveling policy for an SSD, or implementing a write-ahead log. Rough Stack is built for that gap. +The software engineering interview and learning ecosystem is saturated with tools for practicing algorithmic problems or basic frontend components. Few platforms let engineers write and execute code for low-level systems challenges such as balancing an LSM tree, writing a wear-leveling policy for an SSD, or implementing a write-ahead log. Roughstack is built for that gap. Product Description: -Rough Stack is an interactive, browser-based coding platform and secure evaluation engine for backend and systems engineering challenges. It turns complex hardware and database internals into focused, programmable exercises. +Roughstack is an interactive, browser-based coding platform and secure evaluation engine for backend and systems engineering challenges. It turns complex hardware and database internals into focused, programmable exercises. Users select a challenge, write a solution in the embedded editor, and run it against a deterministic workload. The production architecture uses isolated, ephemeral execution environments. A solution is injected into a fresh environment, exercised against realistic traffic, scored on behavior such as write amplification or tail latency, and then discarded. @@ -18,6 +18,6 @@ The Adversarial Engine: The backend test runner doesn't just check for standard Visual Performance Metrics: Returns not just a "Pass/Fail," but a detailed breakdown of the user's algorithmic efficiency (e.g., how many unnecessary block erases their FTL algorithm triggered). The Goal: -Build a credible systems portfolio and a practical understanding of internals. Rough Stack demonstrates secure execution, distributed orchestration, responsive product engineering, and the tradeoffs inside databases, storage engines, and operating systems. +Build a credible systems portfolio and a practical understanding of internals. Roughstack demonstrates secure execution, distributed orchestration, responsive product engineering, and the tradeoffs inside databases, storage engines, and operating systems. Are these descriptions hitting the exact target you have in your head for the weekend project roadmap? diff --git a/docs/production-readiness.md b/docs/production-readiness.md index 35edbb9..2e69de5 100644 --- a/docs/production-readiness.md +++ b/docs/production-readiness.md @@ -1,6 +1,6 @@ # Production readiness roadmap -Rough Stack has a coherent learning experience, a deterministic arena contract, +Roughstack has a coherent learning experience, a deterministic arena contract, and a strong local development path. It is not ready to accept untrusted public traffic yet. The work below is ordered by dependency and launch risk. @@ -182,6 +182,6 @@ This ordering borrows proven patterns without copying another product's surface: 7. Build guided progression and feedback on top of trustworthy run data. 8. Add community solutions, review, discovery, and authoring workflows. -This sequence keeps the most expensive promise first: when Rough Stack says it +This sequence keeps the most expensive promise first: when Roughstack says it ran a learner's code safely and measured it fairly, that claim must be true and reproducible. diff --git a/docs/releases.md b/docs/releases.md index d7f1b35..39b061c 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,6 +1,6 @@ # Releases and compatibility -Rough Stack has not published a stable release yet. Until the first tagged +Roughstack has not published a stable release yet. Until the first tagged release, `main` is the integration branch and public v1 arena/result contracts must remain backward-compatible unless a documented migration is approved. diff --git a/docs/visual-direction.md b/docs/visual-direction.md new file mode 100644 index 0000000..a38f011 --- /dev/null +++ b/docs/visual-direction.md @@ -0,0 +1,190 @@ +# Visual direction: a systems field guide with a working lab + +> Historical exploration. The active specification is [the design system](design-system.md). +> In particular, the dense dark half-adder hero below has been superseded. + +Roughstack 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. + +## Next chapter: live circuitry + +The first light pass made reading comfortable but treated too much of the +subject as a static notebook. The next pass should make a system's **state +change** the visual signature. The page is a workbench: inputs arrive, a +decision passes through a component, and the output can be inspected. Small +grid marks, ports, routed traces, and measured labels are useful when they +clarify this story; they are not wallpaper. + +The identity has two independent parts. The symbol is a compact route through +three connected modules, with a bright active endpoint. It must read at favicon +size without relying on letters. The temporary six-letter app name is +**Rivets**; the organization and repository identity remain **Roughstack**. +All visitor-facing app naming comes from one configuration value so a later +rename does not require rewriting navigation, metadata, or artwork. Do not +rename package imports, URLs, or repository slugs as part of a visual prototype. + +### Palette and behavior + +Keep paper-white reading surfaces and deep ink. Raise the energy with electric +blue for action, vivid tangerine for events, teal for resolved state, and acid +yellow for the active signal. Colors label states consistently but never alone: +every diagram also uses text, position, and form. Saturated color should be +concentrated around a decision or transition, leaving long explanations and +the code editor calm. + +Typography uses two voices. The sans serif carries headlines, explanations, +and navigation. Monospace marks commands, measurements, trace steps, figure +captions, and compact technical labels. It is an instrument, not the default +reading face; a problem statement must remain comfortable at paragraph length. + +The homepage's first visual should answer a learner action. A three-step cache +demonstrator is the first test: choose a trace step and see which entry becomes +newest or leaves the cache. Use real controls, visible output, keyboard focus, +and reduced-motion handling. In the library, each family keeps its specific +mechanism illustration, but stronger topic color and circuit-like registration +marks tie the cards to the homepage. No infinite ornamental motion. + +This takes cues from [Brilliant's interactive learning](https://brilliant.org/faq/) +and [Teenage Engineering's color-coded controls](https://teenage.engineering/products/op-1/original/overview), +while [IBM's data-visualization guidance](https://www.ibm.com/design/language/data-visualization/design/basics/) +reinforces that color should help recognition and decisions. The result should +not resemble any of those products directly. + +### Order and checks + +1. Separate product label, organization identity, and icon. Replace the old + stack-of-lines mark in the header and favicon as one atomic identity change. +2. Refresh semantic colors, focus and hover states, and the challenge family + artwork. Check text contrast before pushing saturation further. +3. Make the hero demonstrate a real transition; carry its visual grammar into + the challenge library and arena entry point. +4. Build and test desktop, phone, reduced motion, navigation, and all challenge + diagrams. Inspect the running preview before treating the pass as complete. + +### Interaction grammar + +The workspace is an instrument, not a decorative dashboard. Controls rise a +little on hover and settle when pressed; they never move without an action. +Submission has a readable sequence: send, queue, execute, then verdict. The +console gives each stage a distinct text label and color without implying that +queued work is already running. A passed run is green, a failed check is red, +and neither verdict relies on color alone. A scaffold that fails build checks +before the workload starts has no measurements; its panel says so rather than +presenting an invented score. + +The first learning demo illustrates the same rule. Selecting a cache operation +changes its entries and advances a short progress rail. The transition is brief +and stops entirely for reduced-motion preferences. We do not use looping +animations on lesson art, editor chrome, or reading text. Monospace is reserved +for commands, pipeline steps, status, and measurements; the explanations keep +their easier-to-read sans-serif voice. + +## Bold direction: computation you can touch + +The previous light-circuit pass is preserved by the annotated Git tag +`visual-baseline-before-bold-cs-2026-10-16`. This chapter is deliberately a +larger step. A grid or chip silhouette says “software,” but it does not let a +curious visitor feel a computer *doing* something. Our visual DNA is a visible +cause and effect: choose an input, watch a transformation, predict an output, +then build the mechanism yourself. + +Brilliant's strength is the small decision that makes an idea click. Our +subject and implementation are different. We start with a real half-adder: +two switches, XOR for sum, AND for carry, and an immediate truth table. A +learner can touch the primitive before they read about caches, storage, +scheduling, and networks. The interface should never turn these into a false +claim that logic-gate or neural-network challenges are already published. + +### Visual system + +- A light overall canvas remains. High-energy cobalt, electric lime, coral, + and ice blue gather around *active computation* and focal actions. Dark ink + makes the interactive circuit feel like an instrument, not a site-wide dark + mode. Quiet reading surfaces remain quiet. +- Lines are signals, not wallpaper. In the circuit, route color indicates 0 or + 1 and output nodes change immediately. A short pulse follows input changes; + nothing loops without a learner action. Reduced motion removes the pulse. +- Type has two jobs: generous sans-serif invites exploration and carries + explanations; compact monospace labels inputs, gates, truth values, code, + and measurements. Avoid forcing long copy into a terminal aesthetic. +- The reading site now gives editorial headlines their own geometric display + face, Space Grotesk. Inter remains the quiet reading voice and JetBrains Mono + the instrument voice. This three-role system creates contrast without turning + an entire lesson into code or a magazine cover. +- IBM's THINK history suggests the power of a single confident idea; Tamiko + Thiel's account of the Connection Machine suggests an even stronger test: + form should reveal what the machine is doing, not merely expose generic + circuit ornament. Our original circuit, domain map, and typographic rhythm + follow that principle, not either source's visual assets or layout. See + https://www.ibm.com/history/think and + https://www.tamikothiel.com/theory/cm_txts/index.html. +- The path from bit to system is an editorial map, not an invented course + catalogue. The public library distinguishes runnable challenges from future + frontiers. Cards answer “what do I control?” and “what will I observe?” in + short human-edited copy, while the full statement remains authoritative. + +### Work sequence and acceptance + +1. Build and test the half-adder with real binary behavior, keyboard controls, + text equivalents, and no mandatory motion. +2. Recompose the home page around that instrument and a clear route into the + challenge library. The first viewport should say “computer science from + the inside” in both copy and interaction. +3. Make the library scannable by domain and editorial question, preserving + every existing challenge URL, language, difficulty, and practice/ranked + status. Do not imply unpublished topics can be run. +4. Verify phone, tablet, desktop, keyboard, reduced-motion, contrast, and real + challenge navigation. Keep changes in independent commits so the tagged + state and each iteration remain easy to compare. diff --git a/package-lock.json b/package-lock.json index 61611b8..f1d9195 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1309,9 +1309,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", - "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", "cpu": [ "arm64" ], @@ -1327,13 +1327,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.3" + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", - "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ "x64" ], @@ -1349,20 +1349,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.3" + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", - "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "dependencies": { - "@img/sharp-wasm32": "0.35.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -1372,9 +1372,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", - "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", "cpu": [ "arm64" ], @@ -1388,9 +1388,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", - "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], @@ -1404,9 +1404,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", - "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ "arm" ], @@ -1420,9 +1420,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", - "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ "arm64" ], @@ -1436,9 +1436,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", - "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ "ppc64" ], @@ -1452,9 +1452,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", - "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ "riscv64" ], @@ -1468,9 +1468,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", - "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ "s390x" ], @@ -1484,9 +1484,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", - "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ "x64" ], @@ -1500,9 +1500,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", - "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ "arm64" ], @@ -1516,9 +1516,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", - "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ "x64" ], @@ -1532,9 +1532,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", - "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ "arm" ], @@ -1550,13 +1550,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.3" + "@img/sharp-libvips-linux-arm": "1.3.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", - "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ "arm64" ], @@ -1572,13 +1572,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.3" + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", - "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ "ppc64" ], @@ -1594,13 +1594,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.3" + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", - "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], @@ -1616,13 +1616,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.3" + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", - "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], @@ -1638,13 +1638,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.3" + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", - "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], @@ -1660,13 +1660,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.3" + "@img/sharp-libvips-linux-x64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", - "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ "arm64" ], @@ -1682,13 +1682,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", - "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], @@ -1704,13 +1704,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.3" + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", - "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -1724,16 +1724,16 @@ } }, "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", - "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ "wasm32" ], "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -1743,9 +1743,9 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", - "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], @@ -1762,9 +1762,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", - "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], @@ -1781,9 +1781,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", - "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], @@ -3081,6 +3081,72 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", @@ -10939,9 +11005,9 @@ } }, "node_modules/sharp": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", - "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -10956,31 +11022,31 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.4", - "@img/sharp-darwin-x64": "0.35.4", - "@img/sharp-freebsd-wasm32": "0.35.4", - "@img/sharp-libvips-darwin-arm64": "1.3.3", - "@img/sharp-libvips-darwin-x64": "1.3.3", - "@img/sharp-libvips-linux-arm": "1.3.3", - "@img/sharp-libvips-linux-arm64": "1.3.3", - "@img/sharp-libvips-linux-ppc64": "1.3.3", - "@img/sharp-libvips-linux-riscv64": "1.3.3", - "@img/sharp-libvips-linux-s390x": "1.3.3", - "@img/sharp-libvips-linux-x64": "1.3.3", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", - "@img/sharp-libvips-linuxmusl-x64": "1.3.3", - "@img/sharp-linux-arm": "0.35.4", - "@img/sharp-linux-arm64": "0.35.4", - "@img/sharp-linux-ppc64": "0.35.4", - "@img/sharp-linux-riscv64": "0.35.4", - "@img/sharp-linux-s390x": "0.35.4", - "@img/sharp-linux-x64": "0.35.4", - "@img/sharp-linuxmusl-arm64": "0.35.4", - "@img/sharp-linuxmusl-x64": "0.35.4", - "@img/sharp-webcontainers-wasm32": "0.35.4", - "@img/sharp-win32-arm64": "0.35.4", - "@img/sharp-win32-ia32": "0.35.4", - "@img/sharp-win32-x64": "0.35.4" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" }, "peerDependenciesMeta": { "@types/node": { @@ -11121,9 +11187,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 1b9bc1a..214e3a9 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,8 @@ "fast-uri": "3.1.8", "js-yaml": "4.3.2", "mysql2": "3.24.4", - "nanoid": "3.3.19" + "nanoid": "3.3.19", + "sharp": "0.35.5", + "source-map-js": "1.2.2" } } diff --git a/prisma/seed/challenges.ts b/prisma/seed/challenges.ts index fd91e95..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.", @@ -96,7 +106,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", @@ -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/scripts/arena-validate.mts b/scripts/arena-validate.mts index 1af050e..1c3c8d7 100644 --- a/scripts/arena-validate.mts +++ b/scripts/arena-validate.mts @@ -1,4 +1,5 @@ import path from "node:path"; +import { readFile } from "node:fs/promises"; import { configuredArenaRoots, discoverArenaManifests } from "../src/lib/arena/discovery"; import { loadArenaManifest } from "../src/lib/arena/manifest"; @@ -22,6 +23,21 @@ if (manifests.length === 0) { for (const loaded of manifests) { const { id, version } = loaded.manifest.metadata; + if ( + loaded.manifest.metadata.visibility === "public" && + loaded.manifest.metadata.mode === "practice" && + (!loaded.manifest.submission.starter || + loaded.manifest.submission.starter === loaded.manifest.submission.entrypoint) + ) { + throw new Error(`Public practice arena ${id} requires a separate learner starter`); + } + if (loaded.manifest.submission.language === "go") { + const harness = await readFile(path.join(loaded.arenaRoot, "harness/harness.go"), "utf8"); + const declaredVersion = harness.match(/\bArenaVersion\s*=\s*"([^"]+)"/)?.[1]; + if (declaredVersion !== version) { + throw new Error(`Go harness ${id} reports ${declaredVersion ?? "no version"}, expected ${version}`); + } + } const statement = await loadArenaStatement(loaded.arenaRoot); console.log(`valid ${id}@${version} sha256:${loaded.digest}`); if (statement) console.log(` statement: ${statement.length} blocks`); diff --git a/src/app/(site)/challenges/page.tsx b/src/app/(site)/challenges/page.tsx index 4d30b00..8dce231 100644 --- a/src/app/(site)/challenges/page.tsx +++ b/src/app/(site)/challenges/page.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import { ArrowRight, Cpu, Clock, MemoryStick, FileCode } from "lucide-react"; +import { ArrowRight, Cpu, Clock, FileCode } from "lucide-react"; import { loadArenaChallengeCards, @@ -8,12 +8,12 @@ import { import { prisma } from "@/lib/db"; import { Badge, DifficultyBadge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; -import { Eyebrow } from "@/components/ui/label"; +import { ChallengeArtwork } from "@/components/challenge-artwork"; 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"; @@ -28,6 +28,7 @@ async function getChallenges() { summary: true, difficulty: true, category: true, + pack: true, languages: true, timeLimitSec: true, memoryLimitMb: true, @@ -47,29 +48,113 @@ function titleCase(value: string) { return value.charAt(0) + value.slice(1).toLowerCase(); } +const PATHWAYS = [ + { + id: "memory", + number: "01", + title: "Memory has a cost.", + question: "What deserves to stay close when space runs out?", + tone: "bg-[#a9e8cb]", + slugs: ["cache-pressure-easy", "cache-pressure-medium"], + }, + { + id: "storage", + number: "02", + title: "Data has to survive.", + question: "What happens when writes stop, blocks wear out, or data moves?", + tone: "bg-[#ff916e]", + slugs: ["compaction", "victim-selection", "wear-levelling", "journal-recovery-easy", "chunk-store-easy", "lsm-compactor-easy"], + }, + { + id: "coordination", + number: "03", + title: "Work needs a policy.", + question: "Who goes next, and what happens when everyone arrives at once?", + tone: "bg-[#97c6ef]", + slugs: ["deadline-scheduler-easy", "durable-queue-easy", "fair-gate-easy"], + }, + { + id: "data-in-motion", + number: "04", + title: "Signals move through noise.", + question: "Can you keep meaning intact across packets and event streams?", + tone: "bg-[#b9a1ff]", + slugs: ["lossy-link-easy", "stream-windows-easy"], + }, +] as const; + +const CARD_QUESTIONS: Record = { + "cache-pressure-easy": "Which item leaves when the cache fills?", + "cache-pressure-medium": "What changes when entries have different sizes and lifetimes?", + compaction: "How little can you move to close the gaps?", + "victim-selection": "Which flash block is worth reclaiming?", + "wear-levelling": "Can you reclaim space without wearing one block out?", + "journal-recovery-easy": "Which writes are still real after a crash?", + "chunk-store-easy": "Can a tiny chunk answer a large random read?", + "lsm-compactor-easy": "When should sorted runs become one?", + "deadline-scheduler-easy": "Which job should a single worker take next?", + "durable-queue-easy": "What happens when producers outrun workers?", + "fair-gate-easy": "Which request gets through the gate?", + "lossy-link-easy": "Can a byte stream survive a bad connection?", + "stream-windows-easy": "When is an event-time window truly final?", +}; + export default async function ChallengesPage() { const [challenges, arenas] = await Promise.all([ getChallenges(), getPublicArenas(), ]); const bySlug = new Map( - [...challenges, ...arenas].map((challenge) => [challenge.slug, challenge]), + [ + ...challenges.map((challenge) => ({ ...challenge, practice: challenge.pack === "arena" })), + ...arenas.map((challenge) => ({ ...challenge, practice: true })), + ].map((challenge) => [challenge.slug, challenge]), ); const allChallenges = [...bySlug.values()]; + const practiceCount = allChallenges.filter((challenge) => challenge.practice).length; + const rankedCount = allChallenges.length - practiceCount; + const firstExperiment = allChallenges.find((challenge) => challenge.slug === "cache-pressure-easy") ?? allChallenges[0]; + const pathways = PATHWAYS.map((pathway) => ({ + ...pathway, + challenges: pathway.slugs.flatMap((slug) => { + const challenge = bySlug.get(slug); + return challenge ? [challenge] : []; + }), + })).filter((pathway) => pathway.challenges.length > 0); + const mappedSlugs = new Set(PATHWAYS.flatMap((pathway) => pathway.slugs)); + const additionalChallenges = allChallenges.filter((challenge) => !mappedSlugs.has(challenge.slug)); return ( -
-
- Challenges -

- Systems engineering, executed. -

-

- Each challenge gives you a real subsystem to implement, such as a - flash translation layer, write-ahead log, or scheduler. Your solution - is measured against production concerns including amplification, - latency, fairness, and durability. -

+
+
+
+
+

The system is yours to build

+

+ Follow your curiosity down the stack. +

+

+ Memory, storage, coordination, networks. Pick a question, build the missing piece, and run your code against a real workload. +

+
+ {practiceCount > 0 && {practiceCount} practice labs} + {rankedCount > 0 && {rankedCount} ranked challenges} +
+
+ {firstExperiment && ( + +
+ Suggested first experiment + 01 / start +
+ + + {firstExperiment.title} + + + )} +
{allChallenges.length === 0 ? ( @@ -79,49 +164,53 @@ export default async function ChallengesPage() {

) : ( -
    - {allChallenges.map((c) => ( +
    + {pathways.map((pathway) => ( +
    +
    +
    +

    {pathway.number} / {pathway.id.replaceAll("-", " ")}

    +

    {pathway.title}

    +
    +

    {pathway.question}

    +
    +
      + {pathway.challenges.map((c) => (
    • - - -
      -
      -
      - - {c.category} -
      -

      {c.title}

      -

      {c.summary}

      -
      - - - - - - -
      + + +
      + +
      +
      +
      + + {c.category} + {c.practice ? "Practice" : "Ranked"} +
      +

      {c.title}

      +

      {CARD_QUESTIONS[c.slug] ?? "What will your implementation change?"}

      +

      {c.summary}

      +
      + + + Open
      -
    • + ))} +
    +
    ))} -
+ {additionalChallenges.length > 0 && ( +
+

More to explore

+
    {additionalChallenges.map((c) =>
  • {c.category}

    {c.title}

    {c.summary}

    Open challenge
  • )}
+
+ )} +
)} @@ -131,12 +220,13 @@ export default async function ChallengesPage() { aria-hidden="true" />
-

How scoring works

+

Practice or ranked?

- 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..a70ad84 100644 --- a/src/app/(site)/dashboard/page.tsx +++ b/src/app/(site)/dashboard/page.tsx @@ -5,6 +5,7 @@ import { Badge } from "@/components/ui/badge"; import { DifficultyBadge } from "@/components/ui/badge"; import { CheckCircle2, XCircle, Clock, ArrowRight } from "lucide-react"; import type { Metadata } from "next"; +import { ChallengeArtwork } from "@/components/challenge-artwork"; export const metadata: Metadata = { title: "Dashboard", @@ -23,6 +24,7 @@ interface SubmissionRow { title: string; difficulty: string; category: string; + pack: string; }; result: { passed: boolean; @@ -39,6 +41,7 @@ export default async function DashboardPage() { slug: string; difficulty: string; category: string; + pack: string; bestScore: number; attempts: number; passed: boolean; @@ -60,6 +63,7 @@ export default async function DashboardPage() { title: true, difficulty: true, category: true, + pack: true, }, }, result: { @@ -88,6 +92,7 @@ export default async function DashboardPage() { challengeTitle: string; difficulty: string; category: string; + pack: string; bestScore: number; attempts: bigint; passed: boolean; @@ -98,6 +103,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 +112,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 +120,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, @@ -121,41 +128,60 @@ export default async function DashboardPage() { } return ( -
-
-

Dashboard

+
+
+
+

Your personal workbench

+

Dashboard

{sessionToken - ? "Your submission history for this session. Sign in to keep it across devices." + ? "Your work, one experiment at a time. History is saved for this browser session." : "No session yet. Solve a challenge to start building your history."}

+
+
+
Challenges passed
{bestPerChallenge.filter((challenge) => challenge.passed).length}/ {bestPerChallenge.length}Of challenges with results
+
+
+
+

Keep following the question.

+

What will you take apart next?

+
+ Explore challenges
{bestPerChallenge.length > 0 && (
-

- Best scores +

+ Your challenges

-
+
{bestPerChallenge.map((c) => ( +
+
-

{c.title}

-
+

{c.title}

+
{c.category}
-

- {c.bestScore} -

-

/ 100

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

Practice

+ ) : ( + <> +

{c.bestScore}

+

/ 100

+ + )}
@@ -167,6 +193,7 @@ export default async function DashboardPage() {
+
))}
@@ -174,23 +201,23 @@ export default async function DashboardPage() { )}
-

+

Recent submissions

{submissions.length === 0 ? ( -
-

- No submissions yet.{" "} - - Browse challenges → - -

+
+
+
+

A small start can go a long way.

+

No submissions yet. Try Cache Pressure: choose what stays in memory, run your code, and learn from the result.

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