Skip to content

feat(web): Monaco /playground hitting sandbox via Next.js proxy [STORY-006]#16

Merged
khoks merged 1 commit intomainfrom
story/006-monaco-playground
Apr 26, 2026
Merged

feat(web): Monaco /playground hitting sandbox via Next.js proxy [STORY-006]#16
khoks merged 1 commit intomainfrom
story/006-monaco-playground

Conversation

@khoks
Copy link
Copy Markdown
Owner

@khoks khoks commented Apr 26, 2026

Summary

  • First user-facing feature: Monaco-based /playground route in apps/web with Python/TypeScript selector, Run button, and a result panel surfacing stdout / stderr / exit_code / duration_ms / killed_by / runtime_version.
  • Wiring: browser → Next.js Route Handler POST /api/sandbox/run (re-validates body with SandboxRunRequestSchema, proxies upstream) → Fastify POST /sandbox/runPistonSandboxProvider.
  • New LEARNPRO_API_URL env (defaults to http://localhost:4000) for the proxy.
  • Adds SandboxRunRequestInput (z.input) export to @learnpro/sandbox so callers don't have to pre-fill defaults that zod will fill in at the boundary.
  • Re-scoped on pickup; ACs trimmed and follow-ups filed.

STORY-006 acceptance criteria

  • Monaco editor renders on /playground and accepts code input.
  • Language selector toggles between python and typescript; Monaco language mode follows.
  • Run button POSTs to /api/sandbox/run; result panel renders all fields.
  • Failed runs (non-zero exit, killed_by set, transport error) are visually distinct.
  • Editor is keyboard-navigable (Monaco focus + Esc).
  • Unit tests cover the Next.js Route Handler proxy + the browser-side runSandbox helper.
  • Monaco loads in <500ms warm cache. Manual; perf budget tracking lands with STORY-025.
  • Live stdout/stderr streaming via WebSocket. Out of scope — filed as STORY-059 (Piston's HTTP API is request/response; streaming needs a different sandbox primitive).
  • Submit button + hidden tests. Out of scope — deferred to STORY-016 (depends on a problem entity).
  • Editor language follows the problem language. Out of scope — rewires when STORY-016 lands.

Test plan

  • apps/web tests: 17 new (4 runSandbox helper + 6 Route Handler proxy + 7 status formatter) — all pass.
  • packages/sandbox tests: 24 unit/registry pass + 3 integration skipped (gated on PISTON_URL).
  • apps/api tests: 7 pass.
  • pnpm typecheck green (12/12).
  • pnpm lint green (8/8).
  • pnpm format:check clean.
  • Manual smoke: pnpm devhttp://localhost:3000/playground → Run a Python print and a TS console.log; verify stdout, exit_code=0, duration_ms surface in the result panel. Pending — requires running Piston via infra/docker/docker-compose.dev.yaml.

Notes

  • New external deps in apps/web: @monaco-editor/react@^4.6.0, monaco-editor@^0.52.2, plus workspace dep on @learnpro/sandbox and zod@^3.24.1 (used at the proxy boundary). Materialized via pnpm install in scope of this Story.
  • The Route Handler re-validates with the same SandboxRunRequestSchema so the Fastify boundary validation isn't the only safety net.

…proxy [STORY-006]

Adds the first user-facing surface of the sandbox: a `/playground` route in
`apps/web` with a Monaco editor (Python / TypeScript), a Run button, and a
result panel that surfaces stdout, stderr, exit_code, duration_ms, killed_by,
and runtime_version.

Wiring: browser → Next.js Route Handler `POST /api/sandbox/run` (re-validates
the body against `SandboxRunRequestSchema` and proxies upstream) → Fastify
`POST /sandbox/run` → `PistonSandboxProvider`. Configurable via the new
`LEARNPRO_API_URL` env (defaults to http://localhost:4000).

Adds `SandboxRunRequestInput` (z.input) to `@learnpro/sandbox` so callers can
pass the pre-defaults shape without TypeScript complaining about missing
`time_limit_ms` / `memory_limit_mb` / `output_limit_bytes`.

Re-scoped on pickup:
- WebSocket streaming AC -> filed as STORY-059 (Piston's HTTP API is
  request/response; streaming requires a different sandbox primitive).
- Submit button + hidden tests AC -> deferred to STORY-016 (seed problem
  bank), which will own the problem entity that Submit needs.
- Editor-language-follows-problem-language AC -> rewires when STORY-016
  lands; until then the playground keeps a user-controlled selector.

Tests: 17 new web tests (4 helper + 6 route handler + 7 status). Full sweep
green: typecheck 12/12, lint 8/8, tests 48/48, format clean.
@khoks khoks merged commit 2032f0d into main Apr 26, 2026
1 check passed
@khoks khoks deleted the story/006-monaco-playground branch April 26, 2026 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant