diff --git a/AGENTS.md b/AGENTS.md index d16c5b37..86846e02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,7 @@ Inside the V2 code container, Java/Gradle/WPILib, VSCodium, `redhat.java`, and ` apps/control/ Bun control plane: HTTP, WS, sessions, orchestration apps/control/src/app.ts slim factory + top-level fetch dispatcher apps/control/src/app/ response/asset/proxy/status helpers + admin, workspace, websocket route groups +apps/control/src/app/preview*.ts discovery/serving, Markdown rendering, and the signed path token for Preview apps/control/src/containers.ts barrel re-exporting the public container surface apps/control/src/containers/ Docker client, metadata, ports, lifecycle, and the LocalDockerRuntimeProvider class apps/control/src/metrics.ts Prometheus registry, metric handles, route-templating helpers @@ -62,6 +63,20 @@ imports keep `.git` for push. The per-import backup/restore flow was removed (pure discard + git). See [`docs/lessons/overview.md`](./docs/lessons/overview.md) and `docs/decisions/029-lessons-and-modules.md`. +**Project Preview (post-V2):** a third right-pane tab beside AdvantageScope +and PathPlanner reads the project's own Markdown and generated HTML reports +(`build/reports/**` included). Markdown is rendered in-process by +`markdown-it`; HTML reports are served unchanged with their local assets. +Project HTML is framed `sandbox="allow-scripts"` **without** +`allow-same-origin`, which costs the frame its `SameSite=Lax` session cookie — +so `/u/:slug/api/preview/files/` is authorised by a short-lived HMAC path +token instead, and is the one workspace route dispatched ahead of the cookie +ownership check (read-only, GET-only). `plain-java` lessons get a Preview +show/hide button in the selector's slot; `IDELayout` splits `showRightPane` +from `showDriverStation` to make that possible. See +`docs/decisions/041-project-preview.md` and +[`docs/using-coderunner.md`](./docs/using-coderunner.md). + **Containerized control plane (post-V2):** the control plane ships as a Docker image (`containers/control/Dockerfile` → `ghcr.io/mathewdunne/coderunner-control`) and is deployed with docker compose (`docker-compose.yml` base + @@ -119,7 +134,7 @@ arch-independent). See `docs/decisions/035-multi-arch-images-and-workflow-split. ## Key References - `docs/` + `website/` — docs site content and Docusaurus config; published at `https://mathewdunne.github.io/CodeRunner/`; run `bun run docs:dev` to browse locally, `bun run docs:build` to build. -- `docs/decisions/` — all architecture decision logs (011–039 active; 001–010 archived under `docs/decisions/archive/`). +- `docs/decisions/` — all architecture decision logs (011–041 active; 001–010 archived under `docs/decisions/archive/`). - Pinned AdvantageScope submodule: `vendor/AdvantageScope` at tag `v26.0.2`. ## Commands @@ -157,10 +172,10 @@ See `docs/deploying/` and `docs/operating/` for operator documentation. Three test tiers, all runnable without Docker: -- **`bun run test`** — Bun unit/integration tests for the control plane (~350 tests). Covers auth, runs, proxy, containers, the lessons catalog + load pipeline, security, reconciliation, property-based tests, and metrics route-templating cardinality. -- **`bun run test:web`** — Vitest frontend tests (~80 tests). Covers React hooks (`useSession`, `useLessons`, `useSimulationState`, `useContainerStatus`, `useAutoChoosers`, `useGamepad`, `useRunChannel`), DriverStation components, Zustand store, keyboard/gamepad mappings. -- **`bun run e2e`** — Playwright E2E mocked tier (~55 tests). Full login→editor→run→telemetry→DS flows against in-process `ControlApp` with fake codium-server, HALSim, and NT4 backends. No Docker required. -- **`bun run e2e:security`** — Playwright security specs (~8 tests): CSRF, XSS output encoding, response headers. +- **`bun run test`** — Bun unit/integration tests for the control plane (~450 tests). Covers auth, runs, proxy, containers, the lessons catalog + load pipeline, preview discovery/serving/tokens, security, reconciliation, property-based tests, and metrics route-templating cardinality. +- **`bun run test:web`** — Vitest frontend tests (~135 tests). Covers React hooks (`useSession`, `useLessons`, `useSimulationState`, `useContainerStatus`, `useAutoChoosers`, `useGamepad`, `useRunChannel`, `usePreviewDocuments`), DriverStation and PreviewPane components, Zustand store, keyboard/gamepad mappings. +- **`bun run e2e`** — Playwright E2E mocked tier (~75 tests). Full login→editor→run→telemetry→DS flows against in-process `ControlApp` with fake codium-server, HALSim, and NT4 backends, plus the Preview delivery/workflow/console-lesson specs. No Docker required. +- **`bun run e2e:security`** — Playwright security specs (~12 tests): CSRF, XSS output encoding, response headers, Preview isolation. E2E tests use a custom Playwright fixture (`e2e/fixtures/app.ts`) that creates an isolated `ControlApp` per test with its own random port, SQLite DB, and fake upstream servers. Auth is seeded via `loginAs()` which writes user/session rows and HMAC-signs cookies. @@ -169,6 +184,7 @@ Key E2E fixtures: - `e2e/fixtures/fake-halsim.ts` — Fake HALSim bridge (WS, supports stop/restart) - `e2e/fixtures/fake-nt4.ts` — Fake NT4 server for topic announcement - `e2e/fixtures/gamepad-shim.ts` — Playwright addInitScript gamepad override +- `e2e/fixtures/preview-project.ts` — Project tree with a Gradle-shaped HTML report - `e2e/fixtures/runtime.ts` — Runtime seeding helpers The broad Docker smoke tier remains intentionally unimplemented — see diff --git a/apps/control/package.json b/apps/control/package.json index 30610c7c..a240174f 100644 --- a/apps/control/package.json +++ b/apps/control/package.json @@ -14,6 +14,10 @@ "@frc-coderunner/contracts": "workspace:*", "@logtape/logtape": "^2.0.7", "better-auth": "^1.6.10", + "markdown-it": "^15.0.2", "prom-client": "^15.1.3" + }, + "devDependencies": { + "@types/markdown-it": "^14.2.0" } } diff --git a/apps/control/src/__tests__/preview.test.ts b/apps/control/src/__tests__/preview.test.ts new file mode 100644 index 00000000..fce0148c --- /dev/null +++ b/apps/control/src/__tests__/preview.test.ts @@ -0,0 +1,696 @@ +import { describe, expect, test } from "bun:test"; +import { mkdir, symlink, writeFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import type { PreviewDocumentsResponse } from "@frc-coderunner/contracts"; +import type { ControlApp } from "../app"; +import { + mintPreviewToken, + PREVIEW_TOKEN_TTL_SECONDS, + verifyPreviewToken, +} from "../app/preview-token"; +import { + cookieFrom, + createFakeDocker, + login, + withApp, + workspaceProjectPath, +} from "./helpers"; + +const REPORT = "build/reports/tests/test"; + +async function write( + projectPath: string, + relativePath: string, + contents: string, +): Promise { + const target = join(projectPath, relativePath); + await mkdir(dirname(target), { recursive: true }); + await writeFile(target, contents, "utf8"); +} + +async function seedProject(projectPath: string): Promise { + await write(projectPath, "README.md", "# Robot\n\nHello.\n"); + await write(projectPath, "LICENSE.md", "# Licence\n"); + await write(projectPath, "docs/guide.md", "# Guide\n"); + await write(projectPath, "docs/notes/index.html", "

notes

"); + await write(projectPath, `${REPORT}/index.html`, "

report

"); + await write(projectPath, `${REPORT}/css/base.css`, "body{color:red}"); + await write(projectPath, `${REPORT}/js/report.js`, "console.log(1)"); + await write(projectPath, `${REPORT}/secrets.properties`, "token=hunter2"); + await write(projectPath, "src/main/java/Robot.java", "class Robot {}"); + // Excluded trees. + await write(projectPath, ".git/HEAD", "ref: refs/heads/main"); + await write(projectPath, ".git/notes.md", "# secret\n"); + await write(projectPath, ".gradle/cache/out.html", "

x

"); + await write(projectPath, "node_modules/pkg/readme.md", "# dep\n"); + // A hidden directory that is NOT excluded. + await write(projectPath, ".docs/notes.md", "# hidden\n"); +} + +async function documentsFor( + app: ControlApp, + slug: string, + cookie: string, +): Promise { + const resp = await app.fetch( + new Request(`http://localhost/u/${slug}/api/preview/documents`, { + headers: { cookie }, + }), + ); + expect(resp.status).toBe(200); + return (await resp.json()) as PreviewDocumentsResponse; +} + +function fileRequest( + slug: string, + token: string, + encodedPath: string, +): Request { + return new Request( + `http://localhost/u/${slug}/api/preview/files/${token}/${encodedPath}`, + ); +} + +describe("preview tokens", () => { + test("round-trips for the workspace it was minted for", () => { + const { token, expiresIn } = mintPreviewToken("secret", "ws_a".repeat(1)); + expect(expiresIn).toBe(PREVIEW_TOKEN_TTL_SECONDS); + expect(verifyPreviewToken("secret", "ws_a", token)).toBe(true); + }); + + test("does not verify against a different workspace", () => { + // The whole point of signing the workspace id: pasting one student's URL + // into another student's slug must not authorise anything. + const { token } = mintPreviewToken("secret", "ws_a"); + expect(verifyPreviewToken("secret", "ws_b", token)).toBe(false); + }); + + test("does not verify under a different secret", () => { + const { token } = mintPreviewToken("secret", "ws_a"); + expect(verifyPreviewToken("other", "ws_a", token)).toBe(false); + }); + + test("rejects an expired token", () => { + const now = Math.floor(Date.now() / 1000); + const { token } = mintPreviewToken("secret", "ws_a", now); + expect(verifyPreviewToken("secret", "ws_a", token, now + 10)).toBe(true); + expect( + verifyPreviewToken( + "secret", + "ws_a", + token, + now + PREVIEW_TOKEN_TTL_SECONDS + 1, + ), + ).toBe(false); + }); + + test("rejects tampering with the expiry, signature, version or shape", () => { + const { token } = mintPreviewToken("secret", "ws_a"); + const [version, expiresAt, signature] = token.split("."); + // Push the expiry out without re-signing. + expect( + verifyPreviewToken( + "secret", + "ws_a", + `${version}.${Number(expiresAt) + 99999}.${signature}`, + ), + ).toBe(false); + // Flip a signature byte. + const flipped = `${signature?.slice(0, -1)}${signature?.endsWith("A") ? "B" : "A"}`; + expect( + verifyPreviewToken( + "secret", + "ws_a", + `${version}.${expiresAt}.${flipped}`, + ), + ).toBe(false); + // Wrong version, truncated signature, and junk shapes. + expect( + verifyPreviewToken("secret", "ws_a", `p0.${expiresAt}.${signature}`), + ).toBe(false); + expect( + verifyPreviewToken( + "secret", + "ws_a", + `${version}.${expiresAt}.${signature?.slice(0, 5)}`, + ), + ).toBe(false); + expect(verifyPreviewToken("secret", "ws_a", "")).toBe(false); + expect(verifyPreviewToken("secret", "ws_a", "not-a-token")).toBe(false); + expect( + verifyPreviewToken( + "secret", + "ws_a", + `${version}.notanumber.${signature}`, + ), + ).toBe(false); + }); +}); + +describe("GET /u/:slug/api/preview/documents", () => { + test("lists Markdown and HTML, including generated output, excluding machine trees", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + await seedProject(workspaceProjectPath(app, "alice")); + + const body = await documentsFor(app, "alice", cookie); + expect(body.ok).toBe(true); + expect(body.truncated).toBe(false); + + const paths = body.documents.map((d) => d.path); + expect(paths).toContain("README.md"); + expect(paths).toContain("docs/guide.md"); + expect(paths).toContain("docs/notes/index.html"); + // Generated output is in scope even though it is gitignored. + expect(paths).toContain(`${REPORT}/index.html`); + // A dot-directory is not excluded just for being hidden. + expect(paths).toContain(".docs/notes.md"); + + // Excluded directory trees never appear, including Markdown inside them. + expect(paths).not.toContain(".git/notes.md"); + expect(paths.some((p) => p.startsWith(".gradle/"))).toBe(false); + expect(paths.some((p) => p.startsWith("node_modules/"))).toBe(false); + // Non-document files are not listed. + expect(paths).not.toContain("src/main/java/Robot.java"); + expect(paths).not.toContain(`${REPORT}/css/base.css`); + + // Shallow-first, then alphabetical. + expect(paths.slice(0, 3)).toEqual([ + "LICENSE.md", + "README.md", + ".docs/notes.md", + ]); + // Deep generated report pages sort below root-level docs. + expect(paths.indexOf(`${REPORT}/index.html`)).toBeGreaterThan( + paths.indexOf("docs/guide.md"), + ); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("classifies kinds by extension, case-insensitively", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await write(project, "SHOUTING.MD", "# loud\n"); + await write(project, "Page.HTM", "

page

"); + await write(project, "Other.HTML", "

page

"); + + const body = await documentsFor(app, "alice", cookie); + const byPath = new Map(body.documents.map((d) => [d.path, d.kind])); + expect(byPath.get("SHOUTING.MD")).toBe("markdown"); + expect(byPath.get("Page.HTM")).toBe("html"); + expect(byPath.get("Other.HTML")).toBe("html"); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("omits document names that violate the preview path contract", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await write(project, "README.md", "# valid\n"); + await write(project, "Notes: Week 1.md", "# invalid path\n"); + await write(project, "docs/valid.md", "# valid\n"); + + const body = await documentsFor(app, "alice", cookie); + expect(body.documents.map((document) => document.path)).toEqual([ + "README.md", + "docs/valid.md", + ]); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("keeps duplicate filenames distinguishable by full path", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await write(project, "index.html", "

root

"); + await write(project, "a/index.html", "

a

"); + await write(project, "b/deep/index.html", "

b

"); + + const body = await documentsFor(app, "alice", cookie); + const paths = body.documents.map((d) => d.path); + expect(paths).toContain("index.html"); + expect(paths).toContain("a/index.html"); + expect(paths).toContain("b/deep/index.html"); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("skips symlinked files and directories", async () => { + const docker = createFakeDocker(); + await withApp( + async (app, root) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await mkdir(project, { recursive: true }); + + const outsideDir = join(root, "outside"); + await mkdir(outsideDir, { recursive: true }); + await writeFile(join(outsideDir, "leak.md"), "# leak\n", "utf8"); + + await symlink(join(outsideDir, "leak.md"), join(project, "link.md")); + await symlink(outsideDir, join(project, "linkdir")); + + const body = await documentsFor(app, "alice", cookie); + const paths = body.documents.map((d) => d.path); + expect(paths).not.toContain("link.md"); + expect(paths.some((p) => p.startsWith("linkdir"))).toBe(false); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("returns an empty list for an empty project", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const body = await documentsFor(app, "alice", cookie); + expect(body.documents).toEqual([]); + expect(body.truncated).toBe(false); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("requires the session cookie and rejects another student", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + await login(app, "alice"); + const bobCookie = cookieFrom(await login(app, "bob")); + + const anonymous = await app.fetch( + new Request("http://localhost/u/alice/api/preview/documents"), + ); + expect(anonymous.status).toBe(401); + + const wrongUser = await app.fetch( + new Request("http://localhost/u/alice/api/preview/documents", { + headers: { cookie: bobCookie }, + }), + ); + expect(wrongUser.status).toBe(403); + }, + { dockerRunner: docker.runner }, + ); + }); +}); + +describe("GET /u/:slug/api/preview/files//", () => { + test("renders Markdown as an isolated HTML document with heading anchors", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + await seedProject(workspaceProjectPath(app, "alice")); + const { token } = await documentsFor(app, "alice", cookie); + + const resp = await app.fetch(fileRequest("alice", token, "README.md")); + expect(resp.status).toBe(200); + expect(resp.headers.get("content-type")).toContain("text/html"); + + const csp = resp.headers.get("content-security-policy") ?? ""; + // Markdown needs no scripts, so the sandbox does not grant any. + expect(csp).toContain("sandbox;"); + expect(csp).toContain("script-src 'none'"); + expect(csp).not.toContain("allow-same-origin"); + expect(resp.headers.get("x-content-type-options")).toBe("nosniff"); + expect(resp.headers.get("referrer-policy")).toBe("no-referrer"); + expect(resp.headers.get("cache-control")).toContain("no-store"); + + const html = await resp.text(); + expect(html).toContain('

Robot

'); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("serves HTML documents unmodified, under a scripted sandbox", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await write(project, `${REPORT}/index.html`, "

report

"); + const { token } = await documentsFor(app, "alice", cookie); + + const resp = await app.fetch( + fileRequest("alice", token, `${REPORT}/index.html`), + ); + expect(resp.status).toBe(200); + // The report's own markup is preserved, not re-rendered. + expect(await resp.text()).toBe("

report

"); + + const csp = resp.headers.get("content-security-policy") ?? ""; + expect(csp).toContain("sandbox allow-scripts"); + expect(csp).not.toContain("allow-same-origin"); + // Resources are pinned to this token's own namespace. + expect(csp).toContain( + `http://localhost/u/alice/api/preview/files/${token}/`, + ); + expect(csp).toContain("connect-src 'none'"); + expect(csp).toContain("form-action 'none'"); + expect(csp).toContain("navigate-to 'none'"); + expect(csp).toContain("frame-src 'none'"); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("serves allowlisted report assets and refuses everything else", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + await seedProject(workspaceProjectPath(app, "alice")); + const { token } = await documentsFor(app, "alice", cookie); + + const css = await app.fetch( + fileRequest("alice", token, `${REPORT}/css/base.css`), + ); + expect(css.status).toBe(200); + expect(css.headers.get("content-type")).toContain("text/css"); + + const js = await app.fetch( + fileRequest("alice", token, `${REPORT}/js/report.js`), + ); + expect(js.status).toBe(200); + expect(js.headers.get("content-type")).toContain("text/javascript"); + + // The handler must not become a way to read project config or source. + const properties = await app.fetch( + fileRequest("alice", token, `${REPORT}/secrets.properties`), + ); + expect(properties.status).toBe(415); + expect(await properties.text()).not.toContain("hunter2"); + + const java = await app.fetch( + fileRequest("alice", token, "src/main/java/Robot.java"), + ); + expect(java.status).toBe(415); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("isolates directly navigable SVG assets", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await write(project, "README.md", "# x\n"); + await write( + project, + "docs/chart.svg", + '', + ); + const { token } = await documentsFor(app, "alice", cookie); + + const resp = await app.fetch( + fileRequest("alice", token, "docs/chart.svg"), + ); + expect(resp.status).toBe(200); + expect(resp.headers.get("content-type")).toContain("image/svg+xml"); + // An SVG can be navigated to as a document, so it gets document isolation. + const csp = resp.headers.get("content-security-policy") ?? ""; + expect(csp).toContain("sandbox;"); + expect(csp).toContain("script-src 'none'"); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("handles URL-encoded spaces, Unicode, # and % in filenames", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + const names = [ + "my notes.md", + "café ☕.md", + "weird #1.md", + "100% done.md", + ]; + for (const name of names) { + await write(project, `docs/${name}`, `# ${name}\n`); + } + const body = await documentsFor(app, "alice", cookie); + const listed = body.documents.map((d) => d.path); + + for (const name of names) { + expect(listed).toContain(`docs/${name}`); + const encoded = `docs/${name}` + .split("/") + .map(encodeURIComponent) + .join("/"); + const resp = await app.fetch( + fileRequest("alice", body.token, encoded), + ); + expect(resp.status).toBe(200); + expect(await resp.text()).toContain(name.replace(/&/g, "&")); + } + }, + { dockerRunner: docker.runner }, + ); + }); + + test("rejects an encoded separator smuggled inside one segment", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + await seedProject(workspaceProjectPath(app, "alice")); + const { token } = await documentsFor(app, "alice", cookie); + + // "docs%2Fguide.md" decodes to a separator that was never part of the + // URL's structure; it must not be re-interpreted as one. + const resp = await app.fetch( + fileRequest("alice", token, "docs%2Fguide.md"), + ); + expect(resp.status).toBe(400); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("rejects traversal, absolute paths and excluded directories", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + await seedProject(workspaceProjectPath(app, "alice")); + const { token } = await documentsFor(app, "alice", cookie); + + for (const path of [ + "../escape.md", + "..%2Fescape.md", + "%2e%2e/escape.md", + "docs/../../escape.md", + "/etc/passwd", + "%2Fetc%2Fpasswd", + ".git/notes.md", + ".gradle/cache/out.html", + "node_modules/pkg/readme.md", + "docs/", + "", + ]) { + const resp = await app.fetch(fileRequest("alice", token, path)); + expect([400, 403, 404]).toContain(resp.status); + expect(await resp.text()).not.toContain("secret"); + } + }, + { dockerRunner: docker.runner }, + ); + }); + + test("refuses a symlinked file even when it is named directly", async () => { + const docker = createFakeDocker(); + await withApp( + async (app, root) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await write(project, "README.md", "# x\n"); + + const outside = join(root, "outside.md"); + await writeFile(outside, "# leaked\n", "utf8"); + await symlink(outside, join(project, "link.md")); + + const { token } = await documentsFor(app, "alice", cookie); + const resp = await app.fetch(fileRequest("alice", token, "link.md")); + expect(resp.status).toBe(403); + expect(await resp.text()).not.toContain("leaked"); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("refuses a file reached through a directory symlink", async () => { + const docker = createFakeDocker(); + await withApp( + async (app, root) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await write(project, "README.md", "# x\n"); + + const outsideDir = join(root, "outside"); + await mkdir(outsideDir, { recursive: true }); + await writeFile(join(outsideDir, "leak.md"), "# leaked\n", "utf8"); + await symlink(outsideDir, join(project, "linkdir")); + + const { token } = await documentsFor(app, "alice", cookie); + const resp = await app.fetch( + fileRequest("alice", token, "linkdir/leak.md"), + ); + expect([403, 404]).toContain(resp.status); + expect(await resp.text()).not.toContain("leaked"); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("refuses directories and non-regular files", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + await write(project, "README.md", "# x\n"); + // A directory whose name ends in .md still must not be read as one. + await mkdir(join(project, "dir.md"), { recursive: true }); + + const { token } = await documentsFor(app, "alice", cookie); + const resp = await app.fetch(fileRequest("alice", token, "dir.md")); + expect([403, 404]).toContain(resp.status); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("reports a file that disappeared between listing and opening", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + await seedProject(workspaceProjectPath(app, "alice")); + const { token } = await documentsFor(app, "alice", cookie); + + const resp = await app.fetch( + fileRequest("alice", token, "does-not-exist.md"), + ); + expect(resp.status).toBe(404); + // The failure is rendered as a document, because the frame is what the + // student is looking at. + expect(resp.headers.get("content-type")).toContain("text/html"); + expect(await resp.text()).toContain("no longer available"); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("refuses an oversized document", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + const project = workspaceProjectPath(app, "alice"); + // 10 MiB is the document ceiling. + await write(project, "huge.md", "x".repeat(10 * 1024 * 1024 + 1)); + + const { token } = await documentsFor(app, "alice", cookie); + const resp = await app.fetch(fileRequest("alice", token, "huge.md")); + expect(resp.status).toBe(413); + expect(await resp.text()).toContain("too large"); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("rejects a missing, forged, or other-workspace token", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const aliceCookie = cookieFrom(await login(app, "alice")); + const bobCookie = cookieFrom(await login(app, "bob")); + await seedProject(workspaceProjectPath(app, "alice")); + await seedProject(workspaceProjectPath(app, "bob")); + + const alice = await documentsFor(app, "alice", aliceCookie); + const bob = await documentsFor(app, "bob", bobCookie); + + // Bob's token does not open Alice's files. + const crossUser = await app.fetch( + fileRequest("alice", bob.token, "README.md"), + ); + expect(crossUser.status).toBe(403); + + // A forged token is refused. + const forged = await app.fetch( + fileRequest("alice", "p1.99999999999.forged", "README.md"), + ); + expect(forged.status).toBe(403); + + // Alice's own token still works, and a session cookie is not needed. + const ok = await app.fetch( + fileRequest("alice", alice.token, "README.md"), + ); + expect(ok.status).toBe(200); + }, + { dockerRunner: docker.runner }, + ); + }); + + test("rejects mutation methods", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + await seedProject(workspaceProjectPath(app, "alice")); + const { token } = await documentsFor(app, "alice", cookie); + + for (const method of ["POST", "PUT", "PATCH", "DELETE"]) { + const resp = await app.fetch( + new Request( + `http://localhost/u/alice/api/preview/files/${token}/README.md`, + { method }, + ), + ); + expect(resp.status).toBe(405); + } + }, + { dockerRunner: docker.runner }, + ); + }); + + test("returns 404 for an unknown workspace slug without revealing anything", async () => { + const docker = createFakeDocker(); + await withApp( + async (app) => { + const cookie = cookieFrom(await login(app, "alice")); + await seedProject(workspaceProjectPath(app, "alice")); + const { token } = await documentsFor(app, "alice", cookie); + + const resp = await app.fetch(fileRequest("nobody", token, "README.md")); + expect(resp.status).toBe(404); + }, + { dockerRunner: docker.runner }, + ); + }); +}); diff --git a/apps/control/src/app.ts b/apps/control/src/app.ts index 9e478013..daae85a4 100644 --- a/apps/control/src/app.ts +++ b/apps/control/src/app.ts @@ -196,6 +196,10 @@ export async function createApp( const url = new URL(request.url); const start = performance.now(); const route = templateRoute(url.pathname); + // Preview file URLs contain a bearer capability and a private project path. + // Keep both out of logs; the templated route is enough to identify traffic. + const loggedPath = + route === "/u/:slug/api/preview/files/*" ? route : url.pathname; httpRequestsInFlight.inc(); let response: Response; let observedStatus: number; @@ -210,7 +214,7 @@ export async function createApp( ); httpLog.error("unhandled error in request dispatcher", { method: request.method, - path: url.pathname, + path: loggedPath, err: err instanceof Error ? err : new Error(String(err)), }); throw err; @@ -236,7 +240,7 @@ export async function createApp( NOISY_WORKSPACE_PATH.test(url.pathname); const fields = { method: request.method, - path: url.pathname, + path: loggedPath, status: response.status, durationMs, }; diff --git a/apps/control/src/app/assets.ts b/apps/control/src/app/assets.ts index e46b25a3..4a564651 100644 --- a/apps/control/src/app/assets.ts +++ b/apps/control/src/app/assets.ts @@ -1,8 +1,10 @@ +import { existsSync } from "node:fs"; import { cp, mkdir, readdir, readFile, + realpath, rm, stat, writeFile, @@ -64,6 +66,38 @@ export function isInsideDirectory(root: string, target: string): boolean { ); } +/** + * Whether this host exposes `/proc/self/fd`, which is what lets us resolve an + * open descriptor back to the file it actually refers to. Linux (every + * deployment target, and the CI/dev containers) has it; macOS does not, and + * Node exposes no portable equivalent. + */ +const HAS_PROC_SELF_FD = existsSync("/proc/self/fd"); + +/** + * Confirm the file an open descriptor actually refers to lives inside `realRoot` + * (which must itself already be realpath-resolved). + * + * Path-based containment checks are check-then-use: where the caller can write + * into the tree, a directory can be swapped for a symlink in the window between + * the check and the open. Resolving the descriptor we already hold closes that + * race — it names the inode we opened, which cannot be re-pointed underneath us. + * + * Returns true on a host without `/proc` rather than failing closed: there the + * path-based checks are all we have, and refusing every read would be worse + * than the race. Deployments run on Linux, where the check is live. + */ +export async function isOpenFileInsideRoot( + fd: number, + realRoot: string, +): Promise { + if (!HAS_PROC_SELF_FD) { + return true; + } + const real = await realpath(`/proc/self/fd/${fd}`).catch(() => null); + return real !== null && isInsideDirectory(realRoot, real); +} + export function safeRelativeAssetPath(value: string): string | null { if ( value.length === 0 || diff --git a/apps/control/src/app/preview-markdown.ts b/apps/control/src/app/preview-markdown.ts new file mode 100644 index 00000000..a49486c8 --- /dev/null +++ b/apps/control/src/app/preview-markdown.ts @@ -0,0 +1,132 @@ +import MarkdownIt from "markdown-it"; + +/** + * Markdown rendering for Preview. Everything ships with the control plane — + * no CDN renderer, no webfonts, no syntax-highlighting bundle — because the + * whole point is that a student on a locked-down school network can still read + * their project's README. + * + * Raw embedded HTML is disabled (`html: false`). Markdown documents are + * rendered into a frame that is sandboxed *without* `allow-scripts`, so an + * inline ` +`; + +const DEFAULT_GUIDE = `# Guide + +Back to the [README](../README.md). +`; + +const NESTED_INDEX_HTML = ` +Nested index +

Nested index

+`; + +function reportIndexHtml(): string { + return ` + + + Test results + + + + +

Test Summary

+ +

no script

+ + +

closed

+ + +`; +} + +function reportClassHtml(): string { + return ` + + + MyRobotTest + + + +

MyRobotTest

+ + back + + +`; +} + +function reportCss(headingColor: string): string { + return `#report-heading { color: ${headingColor}; }\n`; +} + +function reportJs(marker: string): string { + return `document.addEventListener("DOMContentLoaded", function () { + document.getElementById("script-output").textContent = ${JSON.stringify(marker)}; + var toggle = document.getElementById("toggle"); + if (toggle) { + toggle.addEventListener("click", function () { + document.getElementById("toggle-output").textContent = "open"; + }); + } +}); +`; +} diff --git a/e2e/specs/preview/console-lesson.spec.ts b/e2e/specs/preview/console-lesson.spec.ts new file mode 100644 index 00000000..88b439e7 --- /dev/null +++ b/e2e/specs/preview/console-lesson.spec.ts @@ -0,0 +1,111 @@ +/** + * Preview in a `plain-java` console lesson. + * + * These lessons have no simulation, so they hide the whole right pane and the + * Driver Station. Their instructions and test reports are still worth reading, + * so Preview gets a show/hide button in the topbar's selector slot — and + * revealing it must not drag any simulation chrome back in with it. + */ +import type { Page } from "@playwright/test"; +import type { ControlApp } from "../../../apps/control/src/app"; +import { expect, test } from "../../fixtures/app"; +import { loginAs } from "../../fixtures/auth"; +import { seedPreviewProject } from "../../fixtures/preview-project"; + +function makeConsoleLesson(app: ControlApp, workspaceId: string): void { + app.storage.db + .query( + "UPDATE workspaces SET current_module = ?, current_module_kind = ? WHERE id = ?", + ) + .run("hello-world", "plain-java", workspaceId); +} + +function previewFrame(page: Page) { + return page.frameLocator('[data-testid="preview-frame"]'); +} + +test("a console lesson hides Preview until the student asks for it", async ({ + page, + app, + baseURL, +}) => { + const login = await loginAs(page, app, { name: "console" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + await seedPreviewProject(workspace?.project_path ?? ""); + makeConsoleLesson(app, workspace?.id ?? ""); + + await page.goto(`${baseURL}/u/${login.user.slug}/`); + + // The lesson's own chrome: a Run hint, and no simulation UI at all. + await expect(page.locator('[data-pane="console-hint"]')).toBeVisible(); + await expect(page.getByRole("tab", { name: "AdvantageScope" })).toHaveCount( + 0, + ); + await expect(page.getByRole("tab", { name: "PathPlanner" })).toHaveCount(0); + await expect(page.locator('[data-pane="console"]')).not.toBeVisible(); + + // Preview is offered, but closed. + const toggle = page.getByRole("button", { name: "Preview", exact: true }); + await expect(toggle).toBeVisible(); + await expect(toggle).toHaveAttribute("aria-pressed", "false"); + await expect(page.locator('[data-pane="preview"]')).not.toBeVisible(); + + await toggle.click(); + + // Only the Preview pane appears — no Driver Station, no AS/PP. + await expect(toggle).toHaveAttribute("aria-pressed", "true"); + await expect(previewFrame(page).locator("h1").first()).toHaveText( + "Robot Project", + ); + await expect(page.locator('[data-pane="console"]')).not.toBeVisible(); + await expect(page.getByRole("tab", { name: "AdvantageScope" })).toHaveCount( + 0, + ); + // The Run hint survives alongside it. + await expect(page.locator('[data-pane="console-hint"]')).toBeVisible(); + + // And it hides again. + await toggle.click(); + await expect(toggle).toHaveAttribute("aria-pressed", "false"); + await expect(page.locator('[data-pane="preview"]')).not.toBeVisible(); +}); + +test("a console lesson starts no simulation traffic when Preview opens", async ({ + page, + app, + baseURL, +}) => { + const login = await loginAs(page, app, { name: "quiet" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + await seedPreviewProject(workspace?.project_path ?? ""); + makeConsoleLesson(app, workspace?.id ?? ""); + + const simRequests: string[] = []; + page.on("request", (request) => { + const path = new URL(request.url()).pathname; + if (/\/(sim|ws\/run|ws\/gamepad)/.test(path)) simRequests.push(path); + }); + + await page.goto(`${baseURL}/u/${login.user.slug}/`); + await expect( + page.getByRole("button", { name: "Preview", exact: true }), + ).toBeVisible(); + + // The shell cannot know the lesson kind until /api/session answers, so a + // couple of sim polls fire during the initial load and then stop. That is + // pre-existing console-lesson behaviour; the claim under test is that + // *opening Preview* adds nothing to it. + await page.waitForTimeout(500); + const beforePreview = [...simRequests]; + + await page.getByRole("button", { name: "Preview", exact: true }).click(); + await expect(previewFrame(page).locator("h1").first()).toHaveText( + "Robot Project", + ); + await page.getByRole("button", { name: "Refresh" }).click(); + await page.waitForTimeout(500); + + // Preview reads files; it must not wake the simulator hooks this lesson + // deliberately leaves switched off. + expect(simRequests).toEqual(beforePreview); +}); diff --git a/e2e/specs/preview/delivery.spec.ts b/e2e/specs/preview/delivery.spec.ts new file mode 100644 index 00000000..3e5fd624 --- /dev/null +++ b/e2e/specs/preview/delivery.spec.ts @@ -0,0 +1,297 @@ +/** + * Step 1 of the Preview plan: prove the report delivery model in a real browser + * before any UI is built on top of it. + * + * The thing being proved is that a generated Gradle report renders *correctly* + * (its own stylesheet, script, image and page links all resolve) from inside a + * frame that has been stripped of same-origin privileges — and that the report's + * script, once running, can reach neither the CodeRunner shell nor its + * authenticated APIs. + */ + +import type { PreviewDocumentsResponse } from "@frc-coderunner/contracts"; +import type { Frame, Page } from "@playwright/test"; +import { expect, test } from "../../fixtures/app"; +import { loginAs } from "../../fixtures/auth"; +import { seedPreviewProject } from "../../fixtures/preview-project"; + +async function setUp( + page: Page, + app: { + storage: { + findWorkspaceBySlug: (s: string) => { project_path: string } | null; + }; + } & Parameters[1], +) { + const login = await loginAs(page, app, { name: "previewer" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + if (!workspace) throw new Error("workspace missing after login"); + await seedPreviewProject(workspace.project_path); + return { slug: login.user.slug, projectPath: workspace.project_path }; +} + +async function fetchDocuments( + page: Page, + baseURL: string, + slug: string, +): Promise { + const response = await page.request.get( + `${baseURL}/u/${slug}/api/preview/documents`, + ); + expect(response.status()).toBe(200); + return (await response.json()) as PreviewDocumentsResponse; +} + +function fileUrl( + baseURL: string, + slug: string, + token: string, + path: string, +): string { + const encoded = path.split("/").map(encodeURIComponent).join("/"); + return `${baseURL}/u/${slug}/api/preview/files/${token}/${encoded}`; +} + +/** + * Mounts the document in a frame configured exactly the way the Preview pane + * will: sandboxed for scripts, denied same-origin. + */ +async function mountPreviewFrame(page: Page, src: string): Promise { + await page.evaluate((url) => { + document.querySelector("#preview-probe")?.remove(); + const frame = document.createElement("iframe"); + frame.id = "preview-probe"; + frame.setAttribute("sandbox", "allow-scripts"); + frame.style.cssText = "width:800px;height:600px;border:0"; + frame.src = url; + document.body.appendChild(frame); + }, src); + const handle = await page.waitForSelector("#preview-probe"); + const frame = await handle.contentFrame(); + if (!frame) throw new Error("preview frame did not attach"); + await frame.waitForLoadState("domcontentloaded"); + return frame; +} + +test.describe("preview delivery model", () => { + test("lists project documents and excludes machine trees", async ({ + page, + app, + baseURL, + }) => { + const { slug } = await setUp(page, app); + const body = await fetchDocuments(page, baseURL, slug); + + expect(body.ok).toBe(true); + expect(body.truncated).toBe(false); + expect(body.token).toMatch(/^p1\.\d+\./); + expect(body.tokenExpiresIn).toBeGreaterThan(0); + + const paths = body.documents.map((d) => d.path); + expect(paths).toContain("README.md"); + expect(paths).toContain("docs/guide.md"); + expect(paths).toContain("docs/notes/index.html"); + expect(paths).toContain("build/reports/tests/test/index.html"); + // Generated output is in scope even though it is gitignored. + expect(paths).toContain( + "build/reports/tests/test/classes/MyRobotTest.html", + ); + // Hidden directories are not blanket-excluded. + expect(paths).toContain(".docs/hidden-notes.md"); + + // Excluded trees never appear. + expect(paths.some((p) => p.startsWith(".git/"))).toBe(false); + expect(paths.some((p) => p.startsWith(".gradle/"))).toBe(false); + expect(paths.some((p) => p.startsWith("node_modules/"))).toBe(false); + + // Shallow paths sort above deep ones, so hand-written docs sit above a + // generated report tree rather than being buried under it. + expect(paths.indexOf("README.md")).toBeLessThan( + paths.indexOf("build/reports/tests/test/index.html"), + ); + + // Kinds are classified, case-insensitively by extension. + const readme = body.documents.find((d) => d.path === "README.md"); + expect(readme?.kind).toBe("markdown"); + const report = body.documents.find( + (d) => d.path === "build/reports/tests/test/index.html", + ); + expect(report?.kind).toBe("html"); + }); + + test("renders Markdown with no external requests and working anchors", async ({ + page, + app, + baseURL, + }) => { + const { slug } = await setUp(page, app); + const { token } = await fetchDocuments(page, baseURL, slug); + + // Fail loudly if anything reaches off-origin: the whole point is that a + // student behind a school firewall can still read their README. + const external: string[] = []; + await page.route("**/*", async (route) => { + const url = route.request().url(); + if (!url.startsWith(baseURL) && !url.startsWith("data:")) { + external.push(url); + await route.abort(); + return; + } + await route.continue(); + }); + + await page.goto(`${baseURL}/login`); + const frame = await mountPreviewFrame( + page, + fileUrl(baseURL, slug, token, "README.md"), + ); + + await expect(frame.locator("h1")).toHaveText("Robot Project"); + await expect(frame.locator("table td").first()).toHaveText("1"); + await expect(frame.locator("pre code")).toContainText("public class Robot"); + + // Stable heading anchors, so the hand-written "#wiring" link resolves. + expect(await frame.locator("#getting-started").count()).toBe(1); + expect(await frame.locator("#wiring").count()).toBe(1); + + // The relative image resolves through the token prefix. + const imageWidth = await frame + .locator("img") + .first() + .evaluate((img: HTMLImageElement) => img.naturalWidth); + expect(imageWidth).toBeGreaterThan(0); + + // Raw HTML in Markdown is disabled at the parser, and the frame denies + // scripts anyway, so the embedded +

Rebuilt Summary

+

no script

`, + ); + await writeProjectFile( + project, + `${REPORT}/css/base.css`, + "#report-heading { color: rgb(0, 0, 255); }", + ); + await writeProjectFile( + project, + `${REPORT}/js/report.js`, + `document.addEventListener("DOMContentLoaded", function () { + document.getElementById("script-output").textContent = "rebuilt-script"; + });`, + ); + // ...and add a brand new document. + await writeProjectFile(project, "docs/NEWLY-ADDED.md", "# Newly added\n"); + + await page.getByRole("button", { name: "Refresh" }).click(); + + // One Refresh reloads the document *and* its assets, not just the HTML. + await expect(previewFrame(page).locator("#report-heading")).toHaveText( + "Rebuilt Summary", + ); + await expect(previewFrame(page).locator("#report-heading")).toHaveCSS( + "color", + "rgb(0, 0, 255)", + ); + await expect(previewFrame(page).locator("#script-output")).toHaveText( + "rebuilt-script", + ); + + // The new file is immediately selectable. + await page.getByTestId("preview-picker").click(); + await page.getByPlaceholder("Search documents…").fill("NEWLY-ADDED"); + await expect(page.getByRole("option")).toHaveCount(1); + }); + + test("Refresh returns a scrolled document to the top", async ({ + page, + app, + baseURL, + }) => { + const login = await loginAs(page, app, { name: "scroller" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + const project = workspace?.project_path ?? ""; + const long = Array.from( + { length: 400 }, + (_, i) => `Paragraph ${i} with enough text to make the page scroll.`, + ).join("\n\n"); + await seedPreviewProject(project, { readme: `# Long\n\n${long}\n` }); + + await openWorkspace(page, baseURL, login.user.slug); + await expect(previewFrame(page).locator("h1").first()).toHaveText("Long"); + + await previewFrame(page) + .locator("body") + .evaluate((body: HTMLElement) => { + body.ownerDocument.defaultView?.scrollTo(0, 2000); + }); + const scrolled = await previewFrame(page) + .locator("body") + .evaluate( + (body: HTMLElement) => body.ownerDocument.defaultView?.scrollY ?? 0, + ); + expect(scrolled).toBeGreaterThan(0); + + await page.getByRole("button", { name: "Refresh" }).click(); + + // Explicitly the documented behaviour: Refresh starts at the top. There is + // no scroll-restoration subsystem to test. + await expect + .poll(async () => + previewFrame(page) + .locator("body") + .evaluate( + (body: HTMLElement) => body.ownerDocument.defaultView?.scrollY ?? 0, + ), + ) + .toBe(0); + }); + + test("a deleted file is reported and the picker stays usable", async ({ + page, + app, + baseURL, + }) => { + const login = await loginAs(page, app, { name: "deleter" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + const project = workspace?.project_path ?? ""; + await seedPreviewProject(project); + + await openWorkspace(page, baseURL, login.user.slug); + await expect(previewFrame(page).locator("h1").first()).toHaveText( + "Robot Project", + ); + + await rm(join(project, "README.md")); + await page.getByRole("button", { name: "Refresh" }).click(); + + await expect( + page.getByText("README.md is no longer available."), + ).toBeVisible(); + // The refreshed list still works. + await page.getByTestId("preview-picker").click(); + await page.getByPlaceholder("Search documents…").fill("guide"); + await page.getByRole("option").first().click(); + await expect(previewFrame(page).locator("h1").first()).toHaveText("Guide"); + }); + + test("switching to AdvantageScope and back preserves both instances and the selection", async ({ + page, + app, + baseURL, + }) => { + const login = await loginAs(page, app, { name: "switcher" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + await seedPreviewProject(workspace?.project_path ?? ""); + + await openWorkspace(page, baseURL, login.user.slug); + + await page.getByTestId("preview-picker").click(); + await page.getByPlaceholder("Search documents…").fill("guide"); + await page.getByRole("option").first().click(); + await expect(previewFrame(page).locator("h1").first()).toHaveText("Guide"); + + // The AdvantageScope iframe must survive the round trip, not remount. + const scopeFrameId = await page + .locator('[data-pane="scope"] iframe') + .first() + .evaluate((el: HTMLIFrameElement) => { + const tagged = el as HTMLIFrameElement & { __e2eId?: string }; + tagged.__e2eId ??= Math.random().toString(36); + return tagged.__e2eId; + }); + + await page.getByRole("tab", { name: "AdvantageScope" }).click(); + await page.getByRole("tab", { name: "Preview" }).click(); + + await expect(page.getByTestId("preview-picker")).toHaveText( + /docs\/guide\.md/, + ); + await expect(previewFrame(page).locator("h1").first()).toHaveText("Guide"); + + const afterId = await page + .locator('[data-pane="scope"] iframe') + .first() + .evaluate( + (el: HTMLIFrameElement) => + (el as HTMLIFrameElement & { __e2eId?: string }).__e2eId, + ); + expect(afterId).toBe(scopeFrameId); + }); + + test("replacing the project clears the old content and selection", async ({ + page, + app, + baseURL, + }) => { + const login = await loginAs(page, app, { name: "swapper" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + const project = workspace?.project_path ?? ""; + await seedPreviewProject(project); + + await openWorkspace(page, baseURL, login.user.slug); + await expect(previewFrame(page).locator("h1").first()).toHaveText( + "Robot Project", + ); + + // Stand in for a lesson load / repo import: the project is replaced on + // disk and the shell is told to invalidate. + await rm(project, { recursive: true, force: true }); + await writeProjectFile(project, "OTHER.md", "# Other project\n"); + await page.getByRole("button", { name: "Refresh" }).click(); + + await expect( + page.getByText("README.md is no longer available."), + ).toBeVisible(); + await page.getByTestId("preview-picker").click(); + await page.getByPlaceholder("Search documents…").fill("Other"); + await page.getByRole("option").first().click(); + await expect(previewFrame(page).locator("h1").first()).toHaveText( + "Other project", + ); + }); + + test("truncation is disclosed rather than silently hidden", async ({ + page, + app, + baseURL, + }) => { + const login = await loginAs(page, app, { name: "many" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + const project = workspace?.project_path ?? ""; + await seedPreviewProject(project); + // The document budget is 2,000; go past it. + await Promise.all( + Array.from({ length: 2100 }, (_, i) => + writeProjectFile(project, `bulk/doc-${i}.md`, `# Doc ${i}\n`), + ), + ); + + await openWorkspace(page, baseURL, login.user.slug); + await page.getByTestId("preview-picker").click(); + + await expect( + page.getByText("This project has more documents than Preview lists."), + ).toBeVisible(); + }); +}); diff --git a/e2e/specs/security/preview-isolation.spec.ts b/e2e/specs/security/preview-isolation.spec.ts new file mode 100644 index 00000000..360586d5 --- /dev/null +++ b/e2e/specs/security/preview-isolation.spec.ts @@ -0,0 +1,235 @@ +/** + * Preview isolation. Project HTML is student-authored, executable content that + * the control plane serves from the app's own origin, so the boundary around it + * is the security property that matters most in this feature. + * + * Covered here: the shell is unreachable from a report; a report URL opened + * directly is isolated just the same; and no preview URL reaches another + * student's files. + */ +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import type { PreviewDocumentsResponse } from "@frc-coderunner/contracts"; +import { expect, test } from "../../fixtures/app"; +import { loginAs } from "../../fixtures/auth"; +import { seedPreviewProject } from "../../fixtures/preview-project"; + +/** A report that actively tries to escape, rather than a benign one. */ +const HOSTILE_REPORT = ` + +

idle

+ + +`; + +async function documentsFor( + page: import("@playwright/test").Page, + baseURL: string, + slug: string, +): Promise { + const response = await page.request.get( + `${baseURL}/u/${slug}/api/preview/documents`, + ); + expect(response.status()).toBe(200); + return (await response.json()) as PreviewDocumentsResponse; +} + +function fileUrl( + baseURL: string, + slug: string, + token: string, + path: string, +): string { + return `${baseURL}/u/${slug}/api/preview/files/${token}/${path + .split("/") + .map(encodeURIComponent) + .join("/")}`; +} + +test("a hostile report cannot reach the shell, its storage, or its cookies", async ({ + page, + app, + baseURL, +}) => { + const login = await loginAs(page, app, { name: "hostile" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + const project = workspace?.project_path ?? ""; + await seedPreviewProject(project); + await writeFile(join(project, "hostile.html"), HOSTILE_REPORT, "utf8"); + + // Driven through the real pane, so the frame is configured exactly as a + // student would see it rather than by the test. + await page.goto(`${baseURL}/u/${login.user.slug}/`); + await page.getByRole("tab", { name: "Preview" }).click(); + await page.getByTestId("preview-picker").click(); + await page.getByPlaceholder("Search documents…").fill("hostile"); + await page.getByRole("option").first().click(); + + const frame = page.frameLocator('[data-testid="preview-frame"]'); + await expect(frame.locator("#result")).toHaveText("idle"); + + const results = await frame.locator("#result").evaluate(() => { + const probe = ( + window as unknown as { + __probe: (label: string, fn: () => unknown) => string; + } + ).__probe; + return { + origin: window.origin, + parentDom: probe("parent", () => window.parent.document.body.innerHTML), + topLocation: probe("top", () => window.top?.location.href), + localStorage: probe("storage", () => { + window.localStorage.setItem("pwn", "1"); + return "wrote"; + }), + cookies: probe("cookie", () => document.cookie), + }; + }); + + // An opaque origin is what actually contains the report: it is not + // same-origin with the shell, so none of the shell's state is addressable. + expect(results.origin).toBe("null"); + expect(results.parentDom).toMatch(/^parent:blocked:/); + expect(results.topLocation).toMatch(/^top:blocked:/); + expect(results.localStorage).toMatch(/^storage:blocked:/); + // document.cookie is either blocked outright or empty; never the session. + expect(results.cookies).not.toContain("coderunner_session"); + + // The shell's own session is untouched by all of that. + await expect(page.getByTestId("preview-picker")).toBeVisible(); +}); + +test("a report cannot call authenticated control-plane APIs", async ({ + page, + app, + baseURL, +}) => { + const login = await loginAs(page, app, { name: "fetcher" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + const project = workspace?.project_path ?? ""; + await seedPreviewProject(project); + await writeFile(join(project, "hostile.html"), HOSTILE_REPORT, "utf8"); + + const { token } = await documentsFor(page, baseURL, login.user.slug); + await page.goto(`${baseURL}/login`); + await page.evaluate( + (url) => { + const frame = document.createElement("iframe"); + frame.id = "probe"; + frame.setAttribute("sandbox", "allow-scripts"); + frame.src = url; + document.body.appendChild(frame); + }, + fileUrl(baseURL, login.user.slug, token, "hostile.html"), + ); + + const handle = await page.waitForSelector("#probe"); + const frame = await handle.contentFrame(); + await frame?.waitForLoadState("domcontentloaded"); + + // connect-src 'none' blocks XHR/fetch/WebSocket out of the document. + for (const target of [ + `${baseURL}/u/${login.user.slug}/api/session`, + `${baseURL}/u/${login.user.slug}/api/lessons`, + `${baseURL}/api/auth/providers`, + ]) { + const result = await frame?.evaluate(async (url) => { + try { + const response = await fetch(url, { credentials: "include" }); + return `reachable:${response.status}`; + } catch (error) { + return `blocked:${(error as Error).name}`; + } + }, target); + expect(result).toMatch(/^blocked:/); + } +}); + +test("a preview URL opened directly is sandboxed and uncacheable", async ({ + page, + app, + baseURL, +}) => { + const login = await loginAs(page, app, { name: "direct" }); + const workspace = app.storage.findWorkspaceBySlug(login.user.slug); + await seedPreviewProject(workspace?.project_path ?? ""); + const { token } = await documentsFor(page, baseURL, login.user.slug); + + const response = await page.goto( + fileUrl( + baseURL, + login.user.slug, + token, + "build/reports/tests/test/index.html", + ), + ); + expect(response?.status()).toBe(200); + + const headers = response?.headers() ?? {}; + expect(headers["content-security-policy"]).toContain("sandbox allow-scripts"); + expect(headers["content-security-policy"]).not.toContain("allow-same-origin"); + expect(headers["content-security-policy"]).toContain("navigate-to 'none'"); + expect(headers["x-content-type-options"]).toBe("nosniff"); + // Keeps the capability token out of the Referer of any outbound navigation. + expect(headers["referrer-policy"]).toBe("no-referrer"); + // One student's private files must not sit in a shared cache. + expect(headers["cache-control"]).toContain("no-store"); + expect(headers["cache-control"]).toContain("private"); + + // The header alone, with no iframe involved, still produces an opaque origin. + expect(await page.evaluate(() => window.origin)).toBe("null"); +}); + +test("no preview route reaches another student's project", async ({ + page, + app, + baseURL, +}) => { + const victim = await loginAs(page, app, { name: "victim" }); + const victimWorkspace = app.storage.findWorkspaceBySlug(victim.user.slug); + await writeFile( + join(victimWorkspace?.project_path ?? "", "SECRET.md"), + "# victim secret\n", + "utf8", + ); + const victimDocs = await documentsFor(page, baseURL, victim.user.slug); + + // Now become a different student in the same browser context. + const attacker = await loginAs(page, app, { name: "attacker" }); + const attackerWorkspace = app.storage.findWorkspaceBySlug(attacker.user.slug); + await seedPreviewProject(attackerWorkspace?.project_path ?? ""); + const attackerDocs = await documentsFor(page, baseURL, attacker.user.slug); + + // The victim's document list is refused outright. + const listResponse = await page.request.get( + `${baseURL}/u/${victim.user.slug}/api/preview/documents`, + ); + expect(listResponse.status()).toBe(403); + + // The attacker's own token does not open the victim's files... + const withOwnToken = await page.request.get( + fileUrl(baseURL, victim.user.slug, attackerDocs.token, "SECRET.md"), + ); + expect(withOwnToken.status()).toBe(403); + expect(await withOwnToken.text()).not.toContain("victim secret"); + + // ...and the victim's token does not travel to the attacker's slug either. + const crossSlug = await page.request.get( + fileUrl(baseURL, attacker.user.slug, victimDocs.token, "README.md"), + ); + expect(crossSlug.status()).toBe(403); + + // Traversal out of the attacker's own project is refused. + for (const path of ["../victim/project/SECRET.md", "..%2F..%2Fapp.db"]) { + const attempt = await page.request.get( + `${baseURL}/u/${attacker.user.slug}/api/preview/files/${attackerDocs.token}/${path}`, + ); + expect([400, 403, 404]).toContain(attempt.status()); + expect(await attempt.text()).not.toContain("victim secret"); + } +}); diff --git a/e2e/specs/workspace/pane-layout.spec.ts b/e2e/specs/workspace/pane-layout.spec.ts index 3ee608ce..58c6f3a2 100644 --- a/e2e/specs/workspace/pane-layout.spec.ts +++ b/e2e/specs/workspace/pane-layout.spec.ts @@ -3,7 +3,10 @@ */ import { expect, test } from "../../fixtures/app"; import { loginAs } from "../../fixtures/auth"; -import { seedRuntimeRunning } from "../../fixtures/runtime"; +import { + seedRuntimeRunning, + seedWorkspaceProject, +} from "../../fixtures/runtime"; test("resized pane sizes survive a reload and reset in a new session", async ({ page, @@ -16,6 +19,7 @@ test("resized pane sizes survive a reload and reset in a new session", async ({ const workspace = app.storage.findWorkspaceBySlug( session.user.slug as never, )!; + await seedWorkspaceProject(workspace.project_path); seedRuntimeRunning({ runtime, workspaceId: workspace.id, @@ -53,6 +57,7 @@ test("resized pane sizes survive a reload and reset in a new session", async ({ const freshWorkspace = app.storage.findWorkspaceBySlug( freshSession.user.slug as never, )!; + await seedWorkspaceProject(freshWorkspace.project_path); seedRuntimeRunning({ runtime, workspaceId: freshWorkspace.id, @@ -67,3 +72,147 @@ test("resized pane sizes survive a reload and reset in a new session", async ({ await freshContext.close(); }); + +test("collapse restores sizes and live frames, survives reload, and has a reset escape hatch", async ({ + page, + app, + runtime, + fakeVscode, + fakeHalsim, +}) => { + const { user } = await loginAs(page, app, { name: "Collapse" }); + const workspace = app.storage.findWorkspaceBySlug(user.slug as never)!; + await seedWorkspaceProject(workspace.project_path); + seedRuntimeRunning({ + runtime, + workspaceId: workspace.id, + fakeVscode, + fakeHalsim, + }); + await page.goto(`/u/${user.slug}/`); + const editor = page.locator('[data-pane="editor"] iframe'); + await expect(editor).toBeVisible(); + const editorBody = editor.contentFrame().locator("body"); + await expect(editorBody).toHaveAttribute("data-fake-vscode-ready", "true"); + await editorBody.evaluate((body) => + body.setAttribute("data-layout-sentinel", "alive"), + ); + await page.getByRole("tab", { name: "PathPlanner" }).click(); + const planner = page.locator('iframe[data-pane="pathplanner"]'); + await expect(planner).toBeVisible(); + const plannerBody = planner.contentFrame().locator("body"); + await expect(plannerBody).toHaveAttribute("data-fake-pathplanner-loads", "1"); + const separator = page.getByRole("separator", { + name: "Resize editor and right pane", + }); + const hideEditor = page.getByRole("button", { + name: "Hide editor", + exact: true, + }); + const editorControl = hideEditor.locator(".."); + await page.getByRole("tab", { name: "PathPlanner" }).focus(); + await expect(editorControl).toHaveCSS("opacity", "0"); + await separator.hover(); + await expect(editorControl).toHaveCSS("opacity", "1"); + await separator.focus(); + await separator.press("ArrowLeft"); + await separator.press("ArrowLeft"); + const width = (await editor.boundingBox())!.width; + await page.getByRole("button", { name: "Hide editor", exact: true }).click(); + await expect(editor).not.toBeVisible(); + await expect(planner).toBeVisible(); + // Restore tab overlays the edge: no full-height rail steals pane width. + const restore = page.locator("[data-pane=editor-restore]"); + expect((await restore.boundingBox())!.height).toBeLessThan(50); + expect( + Math.abs( + (await planner.boundingBox())!.width - + (await page.locator("#ide-workbench").boundingBox())!.width, + ), + ).toBeLessThan(3); + await page.getByRole("button", { name: "Show editor", exact: true }).click(); + await expect(editor).toBeVisible(); + await page.mouse.move(0, 0); + await expect(hideEditor).not.toBeFocused(); + await expect(editorControl).toHaveCSS("opacity", "0"); + // Keyboard activation keeps focus on the corresponding control. + await hideEditor.focus(); + await hideEditor.press("Enter"); + const showEditor = page.getByRole("button", { + name: "Show editor", + exact: true, + }); + await expect(showEditor).toBeFocused(); + await showEditor.press("Enter"); + await expect(hideEditor).toBeFocused(); + await expect(editorControl).toHaveCSS("opacity", "1"); + expect(Math.abs((await editor.boundingBox())!.width - width)).toBeLessThan(3); + await expect(editorBody).toHaveAttribute("data-layout-sentinel", "alive"); + await page.getByRole("button", { name: "Hide right pane" }).click(); + await expect(planner).not.toBeVisible(); + // Selecting the already-active tab must reveal the collapsed pane too. + await page.getByRole("tab", { name: "PathPlanner" }).click(); + await expect(planner).toBeVisible(); + await expect(plannerBody).toHaveAttribute("data-fake-pathplanner-loads", "1"); + await page.getByRole("button", { name: "Hide Driver Station" }).click(); + await expect(page.locator("#ide-console")).not.toBeVisible(); + const compact = page.locator('[data-pane="console-restore"]'); + await expect(compact.getByRole("status")).toBeVisible(); + const disable = page.waitForRequest( + (req) => + req.method() === "PATCH" && req.url().includes("/sim/driver-station"), + ); + await compact.getByRole("button", { name: "Disable", exact: true }).click(); + expect((await disable).postDataJSON()).toMatchObject({ enabled: false }); + await page.reload(); + await expect(compact).toBeVisible(); + await page.getByRole("button", { name: "User menu", exact: true }).click(); + await page.getByRole("menuitem", { name: "Layout", exact: true }).click(); + await page.getByRole("menuitem", { name: "Reset layout" }).click(); + await expect(page.locator("#ide-console")).toBeVisible(); + await expect(editor).toBeVisible(); + await expect(planner).toBeVisible(); + expect( + Math.abs( + (await editor.boundingBox())!.width - + (await planner.boundingBox())!.width, + ), + ).toBeLessThan(3); +}); + +test("narrow screens switch upper panes and recover the desktop split", async ({ + page, + app, + runtime, + fakeVscode, + fakeHalsim, +}) => { + const { user } = await loginAs(page, app, { name: "Narrow panes" }); + const workspace = app.storage.findWorkspaceBySlug(user.slug as never)!; + await seedWorkspaceProject(workspace.project_path); + seedRuntimeRunning({ + runtime, + workspaceId: workspace.id, + fakeVscode, + fakeHalsim, + }); + await page.goto(`/u/${user.slug}/`); + const editor = page.locator('[data-pane="editor"] iframe'); + await expect(editor).toBeVisible(); + const width = (await editor.boundingBox())!.width; + await page.setViewportSize({ width: 800, height: 720 }); + await expect(editor).toBeVisible(); + await expect(page.locator("#ide-scope")).not.toBeVisible(); + await page.getByRole("tab", { name: "PathPlanner" }).click(); + await expect(editor).not.toBeVisible(); + await expect(page.locator("#ide-scope")).toBeVisible(); + await page.reload(); + await expect(page.locator("#ide-scope")).toBeVisible(); + await expect(editor).not.toBeVisible(); + await page.getByRole("button", { name: "Show editor" }).click(); + await expect(editor).toBeVisible(); + await expect(page.locator("#ide-scope")).not.toBeVisible(); + await page.setViewportSize({ width: 1280, height: 720 }); + await expect(page.locator("#ide-scope")).toBeVisible(); + expect(Math.abs((await editor.boundingBox())!.width - width)).toBeLessThan(3); +}); diff --git a/graphify-out/.graphify_labels.json b/graphify-out/.graphify_labels.json index 6e9b28b8..7e8e7783 100644 --- a/graphify-out/.graphify_labels.json +++ b/graphify-out/.graphify_labels.json @@ -1 +1 @@ -{"0": "Admin API Contracts", "1": "Allowlist Management E2E", "2": "Driver Station Enable/Disable UI", "3": "Driver Station Switch Project Flow", "4": "Gamepad/Keyboard WPILib Mapping", "5": "Admin Layout & Polling", "6": "Container Runtime Status Helpers", "7": "Shared UI Primitives", "8": "Control Plane App & Metrics", "9": "Docker Runtime Provider Core", "10": "Run Execution Metrics", "11": "NT4 Auto Chooser Protocol", "12": "Control Plane Route Test Suite", "13": "Driver Station Auto/Console Panels", "14": "WebSocket Upstream Proxy", "15": "Web App Shell & Theming", "16": "Auth & Allowlist Test Bodies", "17": "E2E Runtime & Resilience Specs", "18": "WebSocket Handler Factory", "19": "App Storage (SQLite)", "20": "Docker Runtime Types & Ports", "21": "E2E Workspace Fixtures", "22": "Import URL Security Tests", "23": "HALSim/NT4 WebSocket Responses", "24": "Docker Client & Lifecycle", "25": "Container Lifecycle Test Suite", "26": "Admin Backup/Restore Test Suite", "27": "Admin Backup Archive Routes", "28": "Sim Pane Switcher & Topbar", "29": "Better Auth Providers & Storage", "30": "AdvantageScope Patch Application", "31": "Lesson Catalog Authoring & Gradle Cache", "32": "Grafana Alloy & Cloudflare Deploy Config", "33": "Run Lifecycle Test Fixtures", "34": "Sim API & Chooser Announcements", "35": "Deploy Files & WS Origin Guards", "36": "Audit Log & Break-Glass Admin", "37": "Structured Logging", "38": "Control Plane Config Loading", "39": "Import Pipeline Test Fixtures", "40": "Admin App Allowlist Page", "41": "Allowlist Core Module", "42": "Admin Route Dispatch & Security Headers", "43": "Self-Inspection (Container Auto-Detect)", "44": "Public Health/OpenAPI E2E Specs", "45": "Gamepad Input Mapping", "46": "Audit Log & Runtime Config Schema", "47": "PathPlanner Integration Deploy Pipeline", "48": "Gamepad Lease Management", "49": "Container Lease Cleanup CLI", "50": "Keyboard Input Mapping", "51": "ControlApp Test Harness Socket Types", "52": "Architecture & Access Control Docs", "53": "Auto Panel & Gamepad Channel", "54": "HALSim Bridge State Application", "55": "Deploy Files Security Test Suite", "56": "Asset Upload & Manifest", "57": "Workspace Container Bind-Mount & Memory Bounds", "58": "Java Tooling Smoke Test", "59": "Contracts Property-Based Tests", "60": "Admin Allowlist Endpoints", "61": "AdvantageScope Lite & Docusaurus", "62": "CI/Test Command Reference", "63": "Gamepad/Driver Station Zod Schemas", "64": "Mock Workspace Runtime Provider", "65": "Scope Pane & Demo Banner", "66": "HALSim Bridge Message Handling", "67": "Audit/Reconciliation Test Suite", "68": "WebSocket Router Test Suite", "69": "Deploy Files Path Safety", "70": "Run Manager Command Factory", "71": "Java LSP Bridge (Archived)", "72": "VSCodium Editor Container Defaults", "73": "Contracts Schema Unit Tests", "74": "Import Manager (Git Clone Staging)", "75": "Demo Mode Session Seeding", "76": "Backup/Restore & Catalog Import Flows", "77": "Auth Client & Docker Runner Helpers", "78": "Container Capacity & V2 Acceptance Decisions", "79": "Dist Download Utility", "80": "PathPlanner Deploy File Schemas", "81": "Bundled Lesson Catalog Modules", "82": "Fake NT4 Test Server", "83": "Code Status Pill Component", "84": "Database Migrations Runner", "85": "Asset Upload Test Suite", "86": "Capacity Cap Test Suite", "87": "Disk Read Limit Config", "88": "Canonical Image Naming Decisions", "89": "AdvantageScope Lite Hosting (Archived)", "90": "CLI Reference & Control Container", "91": "IDE Layout Resizable Panes", "92": "Simulation State Hook", "93": "Network Mode Config Test", "94": "Rebuild Workspaces CLI Args", "95": "Users CLI Script", "96": "Extension Reconciliation Test", "97": "AdvantageScope Verify Script", "98": "Gamepad Session Safety Disables", "99": "Slug/Login Property Tests", "100": "Container Isolation & HALSim Decisions", "101": "Cloudflare Pages Proxy Function", "102": "Project Swap Fake Socket Test", "103": "Proxy Header Test Suite", "104": "Container Lease Schema (V1/V2)", "105": "Core Schema Tables & Better Auth Migration", "106": "WebSocket Upstream Message Send", "107": "Deployment Hardware & FAQ Docs", "108": "E2E ControlApp Test Fixture Setup", "109": "Driver Station Page Object & Runtime Seeding", "110": "AS Lite NT4 Endpoint Injection Patch", "111": "Editor Pane Reachability", "112": "Gamepad Session Lifecycle Methods", "113": "Admin Workspace Backup/Restore Actions", "114": "Auto Choosers & Container Status Hooks", "115": "Java Tooling Compatibility Decisions", "116": "Demo Mode Disk/Memory Limits", "117": "Editor Migration Decisions", "118": "Playwright Fixture Bootstrap Helpers", "119": "Gamepad Shim E2E Test", "120": "Driver Station Page Object", "121": "Catalog Integrity Test", "122": "Fake Socket Test Double", "123": "Session Hook & Heartbeat", "124": "Auto Choosers Hook Test", "125": "HALSim Test Suite", "126": "Backup Database CLI", "127": "Command Injection Test Suite", "128": "Default-Deny Route Coverage", "129": "Lessons & Testing Decision Docs", "130": "Build Failure.spec", "131": "Admin.po", "132": "Path Planner Pane.test", "133": "Ws Bridge", "134": "Main", "135": "Logging.test", "136": "Middleware", "137": "Metadata", "138": "Image", "139": "023 Metrics And Observability", "140": "User Data", "141": "Ws Proxy.spec", "142": "Halsim.test", "143": "Gamepad.test", "144": "Audit Prune", "145": "Users", "146": "README", "147": "Helpers", "148": "Catalog.test", "149": "Block Devices", "150": "Ws Bridge", "151": "020 Workspace Runtime Provider", "152": "030 Control Plane Hardening Pass", "153": "Auth", "154": "2026 08 30 Pathplanner Integration", "155": "Xss.spec", "156": "Build Ascope Lite", "157": "Index", "158": "Backup", "159": "Rebuild Workspaces", "160": "Img Screenshots", "161": "Use Container Status.test", "162": "Code Container Defaults.test", "163": "Frc Robot", "164": "2026 08 30 Pathplanner Integration", "165": "Restore", "166": "Login.po", "167": "Default Deny.test", "168": "Auth Demo.test", "169": "Robot", "170": "Cleanup Containers", "171": "Config", "172": "Use Gamepad", "173": "016 Project Import Strategy", "174": "035 Multi Arch Images And Workflow Split", "175": "[[path]]", "176": "", "177": "Robot Container", "178": "Clean", "179": "Logging", "180": "Metrics.test", "181": "Store", "182": "004 Backend Wiring", "183": "Dist Download", "184": "Img Screenshots", "185": "Main", "186": "Main", "187": "Typecheck", "188": "Sim Api.test", "189": "Switch Project Dialog", "190": "Dialog", "191": "024 Container Memory Budget", "192": "Scripts", "193": "Global Setup", "194": "Docusaurus.config", "195": "Sidebars", "196": "Vite.config", "197": "Constants", "198": "Icon Rail", "199": "Dropdown Menu", "200": "Scroll Area", "201": "Use Gamepad Channel", "202": "Workspace Page", "203": "README", "204": "Cleanup Containers", "205": "Cloudflare Pages Function.test", "206": "Extension Reconciliation.test", "207": "Users", "208": "Vitest.config", "209": "Setup", "210": "Sim Headless.init", "211": "Build", "212": "Settings", "213": "Helpers", "214": "Helpers", "215": "Admin Routes", "216": "Websocket", "217": "Converters", "218": "Docker Client", "219": "Storage", "220": "Demo Banner", "221": "Theme Provider", "222": "Button", "223": "Card", "224": "Card", "225": "Card", "226": "Card", "227": "Card", "228": "Card", "229": "Card", "230": "Dialog", "231": "Dialog", "232": "Dialog", "233": "Dialog", "234": "Dialog", "235": "Dialog", "236": "Dialog", "237": "Dialog", "238": "Dropdown Menu", "239": "Dropdown Menu", "240": "Dropdown Menu", "241": "Dropdown Menu", "242": "Dropdown Menu", "243": "Dropdown Menu", "244": "Dropdown Menu", "245": "Dropdown Menu", "246": "Dropdown Menu", "247": "Dropdown Menu", "248": "Dropdown Menu", "249": "Dropdown Menu", "250": "Dropdown Menu", "251": "Resizable", "252": "Resizable", "253": "Resizable", "254": "Resizable", "255": "Separator", "256": "Sonner", "257": "Tabs", "258": "Tabs", "259": "Tabs", "260": "Tabs", "261": "Tabs", "262": "Tabs", "263": "Tooltip", "264": "Tooltip", "265": "Tooltip", "266": "Tooltip", "267": "Use Run Channel", "268": "Playwright.config", "269": "Image", "270": "Verify Ascope"} \ No newline at end of file +{"0": "Admin API Contracts", "1": "Admin Route Dispatch & Security Headers", "2": "App Storage (SQLite)", "3": "IDE Layout Resizable Panes", "4": "WebSocket Handler Factory", "5": "Control Plane App & Metrics", "6": "Gamepad/Keyboard WPILib Mapping", "7": "Admin Layout & Polling", "8": "Shared UI Primitives", "9": "Docker Runtime Provider Core", "10": "Run Execution Metrics", "11": "Driver Station Enable/Disable UI", "12": "Docker Runtime Types & Ports", "13": "Control Plane Route Test Suite", "14": "NT4 Auto Chooser Protocol", "15": "E2E Runtime & Resilience Specs", "16": "Container Runtime Status Helpers", "17": "Driver Station Auto/Console Panels", "18": "Auth & Allowlist Test Bodies", "19": "WebSocket Upstream Proxy", "20": "Scope Pane & Demo Banner", "21": "Import URL Security Tests", "22": "Sim Pane Switcher & Topbar", "23": "Code Status Pill Component", "24": "Container Lifecycle Test Suite", "25": "Admin Backup/Restore Test Suite", "26": "Better Auth Providers & Storage", "27": "AdvantageScope Patch Application", "28": "Allowlist Management E2E", "29": "Admin Backup Archive Routes", "30": "Lesson Catalog Authoring & Gradle Cache", "31": "Sim API & Chooser Announcements", "32": "Deploy Files & WS Origin Guards", "33": "Grafana Alloy & Cloudflare Deploy Config", "34": "Control Plane Config Loading", "35": "Public Health/OpenAPI E2E Specs", "36": "Structured Logging", "37": "Admin App Allowlist Page", "38": "Allowlist Core Module", "39": "Self-Inspection (Container Auto-Detect)", "40": "CI/Test Command Reference", "41": "Audit Log & Runtime Config Schema", "42": "E2E Workspace Fixtures", "43": "Gamepad Input Mapping", "44": "PathPlanner Integration Deploy Pipeline", "45": "Gamepad Lease Management", "46": "E2E Workspace Fixtures", "47": "Run Lifecycle Test Fixtures", "48": "Container Lease Cleanup CLI", "49": "Backup Database CLI", "50": "PathPlanner Deploy File Schemas", "51": "Gamepad/Driver Station Zod Schemas", "52": "ControlApp Test Harness Socket Types", "53": "Docker Client & Lifecycle", "54": "Architecture & Access Control Docs", "55": "Auto Panel & Gamepad Channel", "56": "HALSim Bridge State Application", "57": "HALSim/NT4 WebSocket Responses", "58": "Asset Upload & Manifest", "59": "Deploy Files Security Test Suite", "60": "Run Manager Command Factory", "61": "Import Pipeline Test Fixtures", "62": "Path Planner Pane.test", "63": "Container Capacity & V2 Acceptance Decisions", "64": "Contracts Property-Based Tests", "65": "Admin Allowlist Endpoints", "66": "AdvantageScope Lite & Docusaurus", "67": "Workspace Container Bind-Mount & Memory Bounds", "68": "Slug/Login Property Tests", "69": "Scripts", "70": "Mock Workspace Runtime Provider", "71": "WebSocket Router Test Suite", "72": "Deploy Files Path Safety", "73": "Java Tooling & Test Tiers", "74": "VSCodium Editor Container Defaults", "75": "Contracts Schema Unit Tests", "76": "Import Manager (Git Clone Staging)", "77": "Demo Mode Session Seeding", "78": "Backup/Restore & Catalog Import Flows", "79": "Auth Client & Docker Runner Helpers", "80": "HALSim Test Suite", "81": "Bundled Lesson Catalog Modules", "82": "Fake NT4 Test Server", "83": "HALSim Bridge Message Handling", "84": "Database Migrations Runner", "85": "Asset Upload Test Suite", "86": "Capacity Cap Test Suite", "87": "Disk Read Limit Config", "88": "", "89": "AdvantageScope Lite Hosting (Archived)", "90": "Driver Station Page Object & Runtime Seeding", "91": "Rebuild Workspaces", "92": "Canonical Image Naming Decisions", "93": "Audit Log & Break-Glass Admin", "94": "Container Runtime Status Helpers", "95": "Network Mode Config Test", "96": "Simulation State Hook", "97": "Rebuild Workspaces CLI Args", "98": "Users CLI Script", "99": "Extension Reconciliation Test", "100": "Sim Api.test", "101": "AdvantageScope Verify Script", "102": "README", "103": "Container Isolation & HALSim Decisions", "104": "Cloudflare Pages Proxy Function", "105": "Project Swap Fake Socket Test", "106": "Proxy Header Test Suite", "107": "Container Lease Schema (V1/V2)", "108": "Core Schema Tables & Better Auth Migration", "109": "WebSocket Upstream Message Send", "110": "Deployment Hardware & FAQ Docs", "111": "E2E ControlApp Test Fixture Setup", "112": "AS Lite NT4 Endpoint Injection Patch", "113": "Editor Pane Reachability", "114": "Driver Station Switch Project Flow", "115": "Driver Station Switch Project Flow", "116": "Admin Workspace Backup/Restore Actions", "117": "Auto Choosers & Container Status Hooks", "118": "Java Tooling Compatibility Decisions", "119": "Lessons & Testing Decision Docs", "120": "Editor Migration Decisions", "121": "CLI Reference & Control Container", "122": "Gamepad Shim E2E Test", "123": "Catalog Integrity Test", "124": "Driver Station Page Object", "125": "Fake Socket Test Double", "126": "Session Hook & Heartbeat", "127": "Auto Choosers Hook Test", "128": "Command Injection Test Suite", "129": "Default-Deny Route Coverage", "130": "Driver Station Page Object & Runtime Seeding", "131": "Demo Mode Disk/Memory Limits", "132": "Img Screenshots", "133": "Build Failure.spec", "134": "Admin.po", "135": "Java Tooling Smoke Test", "136": "Ws Bridge", "137": "Main", "138": "Logging.test", "139": "Metadata", "140": "Middleware", "141": "Image", "142": "023 Metrics And Observability", "143": "User Data", "144": "Ws Proxy.spec", "145": "024 Container Memory Budget", "146": "Halsim.test", "147": "Gamepad.test", "148": "Audit Prune", "149": "Users", "150": "Helpers", "151": "Card", "152": "Catalog.test", "153": "Block Devices", "154": "Ws Bridge", "155": "Dropdown Menu", "156": "020 Workspace Runtime Provider", "157": "030 Control Plane Hardening Pass", "158": "Auth", "159": "2026 08 30 Pathplanner Integration", "160": "Xss.spec", "161": "Build Ascope Lite", "162": "Index", "163": "Allowlist Management E2E", "164": "Use Container Status.test", "165": "Frc Robot", "166": "Code Container Defaults.test", "167": "Java LSP Bridge (Archived)", "168": "2026 08 30 Pathplanner Integration", "169": "Restore", "170": "Login.po", "171": "Default Deny.test", "172": "Auth Demo.test", "173": "Robot", "174": "Cleanup Containers", "175": "Config", "176": "Use Gamepad", "177": "016 Project Import Strategy", "178": "[[path]]", "179": "Clean", "180": "Robot Container", "181": "Dist Download Utility", "182": "Cloudflare Pages Function.test", "183": "PathPlanner Integration", "184": "Logging", "185": "Metrics.test", "186": "004 Backend Wiring", "187": "Store", "188": "Dist Download", "189": "Img Screenshots", "190": "Audit/Reconciliation Test Suite", "191": "Dialog", "192": "Main", "193": "Main", "194": "Typecheck", "195": "Project Preview & Right Pane", "196": "Global Setup", "197": "Docusaurus.config", "198": "Sidebars", "199": "Web App Shell & Theming", "200": "Constants", "201": "Vite.config", "202": "Audit/Reconciliation Test Suite", "203": "Icon Rail", "204": "Dropdown Menu", "205": "Scroll Area", "206": "Use Gamepad Channel", "207": "Cleanup Containers", "208": "Dist Download Utility", "209": "Gamepad Session Safety Disables", "210": "E2E Fixture Architecture", "211": "Allowlist Management E2E", "212": "Extension Reconciliation.test", "213": "Users", "214": "Vitest.config", "215": "Setup", "216": "Sim Headless.init", "217": "Build", "218": "Settings", "219": "Helpers", "220": "Helpers", "221": "Admin Routes", "222": "Websocket", "223": "Converters", "224": "Docker Client", "225": "Storage", "226": "Demo Banner", "227": "HALSim/NT4 WebSocket Responses", "228": "Button", "229": "Card", "230": "Card", "231": "Dialog", "232": "Dialog", "233": "Dialog", "234": "Dialog", "235": "Allowlist Management E2E", "236": "Dialog", "237": "Dialog", "238": "Dialog", "239": "Dropdown Menu", "240": "Dropdown Menu", "241": "Dropdown Menu", "242": "Dropdown Menu", "243": "Dropdown Menu", "244": "Dropdown Menu", "245": "Dropdown Menu", "246": "Dropdown Menu", "247": "Dropdown Menu", "248": "Dropdown Menu", "249": "Resizable", "250": "Resizable", "251": "Separator", "252": "Sonner", "253": "Tabs", "254": "Tabs", "255": "Tabs", "256": "Tabs", "257": "Tabs", "258": "Tooltip", "259": "Tooltip", "260": "Tooltip", "261": "Tooltip", "262": "Use Run Channel", "263": "Image", "264": "Verify Ascope", "265": null, "266": null, "267": null, "268": null, "269": null, "270": null, "271": null, "272": null, "273": null, "274": "Rebuild Workspaces", "275": null, "276": null, "277": null, "278": null, "279": null, "280": null, "281": null, "282": null, "283": null, "284": "Workspace Container Bind-Mount & Memory Bounds", "285": null, "286": null, "287": null, "288": null, "289": null, "290": null, "291": null, "292": null, "293": null, "294": null, "295": null, "296": null, "297": null, "298": null, "299": null, "300": null, "301": null, "302": "Image Naming & Archive", "303": "Disk Limits & Demo Mode", "304": null, "305": null, "306": null, "307": null, "308": null, "309": null, "310": null, "311": null, "312": null, "313": null, "314": null, "315": null, "316": null, "317": null, "318": null, "319": null, "320": null, "321": null, "322": null, "323": null, "324": null, "325": null, "326": null, "327": null, "328": null, "329": null, "330": null, "331": null, "332": null, "333": null, "334": null, "335": null, "336": null, "337": null, "338": null, "339": null, "340": null, "341": null, "342": null, "343": null, "344": null, "345": null, "346": null, "347": null, "348": null, "349": null, "350": null, "351": null, "352": null, "353": null, "354": null, "355": null, "356": null, "357": null, "358": null, "359": null, "360": null, "361": null, "362": null, "363": null, "364": null, "365": null, "366": null, "367": null, "368": null, "369": "Multi-Arch Image Builds", "370": "SELinux Container Mounts"} \ No newline at end of file diff --git a/graphify-out/GRAPH_REPORT.md b/graphify-out/GRAPH_REPORT.md index f5558a7f..875572eb 100644 --- a/graphify-out/GRAPH_REPORT.md +++ b/graphify-out/GRAPH_REPORT.md @@ -1,120 +1,118 @@ -# Graph Report - CodeRunner (2026-09-07) +# Graph Report - /home/matt/dev/CodeRunner (2026-09-15) ## Corpus Check -- 347 files · ~268,836 words +- 0 files · ~0 words - Verdict: corpus is large enough that graph structure adds value. ## Summary -- 4094 nodes · 5929 edges · 362 communities (276 shown, 86 thin omitted) -- Extraction: 97% EXTRACTED · 3% INFERRED · 0% AMBIGUOUS · INFERRED: 187 edges (avg confidence: 0.8) -- Token cost: 0 input · 0 output - -## Graph Freshness -- Built from commit: `1601a6f1` -- Run `git rev-parse HEAD` and compare to check if the graph is stale. -- Run `graphify update .` after code changes (no API cost). +- 4349 nodes · 6295 edges · 371 communities (282 shown, 89 thin omitted) +- Extraction: 97% EXTRACTED · 3% INFERRED · 0% AMBIGUOUS · INFERRED: 197 edges (avg confidence: 0.8) +- Token cost: 0 input · 67,249 output ## Community Hubs (Navigation) - [[_COMMUNITY_Admin API Contracts|Admin API Contracts]] -- [[_COMMUNITY_Allowlist Management E2E|Allowlist Management E2E]] -- [[_COMMUNITY_Driver Station EnableDisable UI|Driver Station Enable/Disable UI]] -- [[_COMMUNITY_Driver Station Switch Project Flow|Driver Station Switch Project Flow]] +- [[_COMMUNITY_Admin Route Dispatch & Security Headers|Admin Route Dispatch & Security Headers]] +- [[_COMMUNITY_App Storage (SQLite)|App Storage (SQLite)]] +- [[_COMMUNITY_IDE Layout Resizable Panes|IDE Layout Resizable Panes]] +- [[_COMMUNITY_WebSocket Handler Factory|WebSocket Handler Factory]] +- [[_COMMUNITY_Control Plane App & Metrics|Control Plane App & Metrics]] - [[_COMMUNITY_GamepadKeyboard WPILib Mapping|Gamepad/Keyboard WPILib Mapping]] - [[_COMMUNITY_Admin Layout & Polling|Admin Layout & Polling]] -- [[_COMMUNITY_Container Runtime Status Helpers|Container Runtime Status Helpers]] - [[_COMMUNITY_Shared UI Primitives|Shared UI Primitives]] -- [[_COMMUNITY_Control Plane App & Metrics|Control Plane App & Metrics]] - [[_COMMUNITY_Docker Runtime Provider Core|Docker Runtime Provider Core]] - [[_COMMUNITY_Run Execution Metrics|Run Execution Metrics]] -- [[_COMMUNITY_NT4 Auto Chooser Protocol|NT4 Auto Chooser Protocol]] +- [[_COMMUNITY_Driver Station EnableDisable UI|Driver Station Enable/Disable UI]] +- [[_COMMUNITY_Docker Runtime Types & Ports|Docker Runtime Types & Ports]] - [[_COMMUNITY_Control Plane Route Test Suite|Control Plane Route Test Suite]] +- [[_COMMUNITY_NT4 Auto Chooser Protocol|NT4 Auto Chooser Protocol]] +- [[_COMMUNITY_E2E Runtime & Resilience Specs|E2E Runtime & Resilience Specs]] +- [[_COMMUNITY_Container Runtime Status Helpers|Container Runtime Status Helpers]] - [[_COMMUNITY_Driver Station AutoConsole Panels|Driver Station Auto/Console Panels]] -- [[_COMMUNITY_WebSocket Upstream Proxy|WebSocket Upstream Proxy]] -- [[_COMMUNITY_Web App Shell & Theming|Web App Shell & Theming]] - [[_COMMUNITY_Auth & Allowlist Test Bodies|Auth & Allowlist Test Bodies]] -- [[_COMMUNITY_E2E Runtime & Resilience Specs|E2E Runtime & Resilience Specs]] -- [[_COMMUNITY_WebSocket Handler Factory|WebSocket Handler Factory]] -- [[_COMMUNITY_App Storage (SQLite)|App Storage (SQLite)]] -- [[_COMMUNITY_Docker Runtime Types & Ports|Docker Runtime Types & Ports]] -- [[_COMMUNITY_E2E Workspace Fixtures|E2E Workspace Fixtures]] +- [[_COMMUNITY_WebSocket Upstream Proxy|WebSocket Upstream Proxy]] +- [[_COMMUNITY_Scope Pane & Demo Banner|Scope Pane & Demo Banner]] - [[_COMMUNITY_Import URL Security Tests|Import URL Security Tests]] -- [[_COMMUNITY_HALSimNT4 WebSocket Responses|HALSim/NT4 WebSocket Responses]] -- [[_COMMUNITY_Docker Client & Lifecycle|Docker Client & Lifecycle]] +- [[_COMMUNITY_Sim Pane Switcher & Topbar|Sim Pane Switcher & Topbar]] +- [[_COMMUNITY_Code Status Pill Component|Code Status Pill Component]] - [[_COMMUNITY_Container Lifecycle Test Suite|Container Lifecycle Test Suite]] - [[_COMMUNITY_Admin BackupRestore Test Suite|Admin Backup/Restore Test Suite]] -- [[_COMMUNITY_Admin Backup Archive Routes|Admin Backup Archive Routes]] -- [[_COMMUNITY_Sim Pane Switcher & Topbar|Sim Pane Switcher & Topbar]] - [[_COMMUNITY_Better Auth Providers & Storage|Better Auth Providers & Storage]] - [[_COMMUNITY_AdvantageScope Patch Application|AdvantageScope Patch Application]] +- [[_COMMUNITY_Allowlist Management E2E|Allowlist Management E2E]] +- [[_COMMUNITY_Admin Backup Archive Routes|Admin Backup Archive Routes]] - [[_COMMUNITY_Lesson Catalog Authoring & Gradle Cache|Lesson Catalog Authoring & Gradle Cache]] -- [[_COMMUNITY_Grafana Alloy & Cloudflare Deploy Config|Grafana Alloy & Cloudflare Deploy Config]] -- [[_COMMUNITY_Run Lifecycle Test Fixtures|Run Lifecycle Test Fixtures]] - [[_COMMUNITY_Sim API & Chooser Announcements|Sim API & Chooser Announcements]] - [[_COMMUNITY_Deploy Files & WS Origin Guards|Deploy Files & WS Origin Guards]] -- [[_COMMUNITY_Audit Log & Break-Glass Admin|Audit Log & Break-Glass Admin]] -- [[_COMMUNITY_Structured Logging|Structured Logging]] +- [[_COMMUNITY_Grafana Alloy & Cloudflare Deploy Config|Grafana Alloy & Cloudflare Deploy Config]] - [[_COMMUNITY_Control Plane Config Loading|Control Plane Config Loading]] -- [[_COMMUNITY_Import Pipeline Test Fixtures|Import Pipeline Test Fixtures]] +- [[_COMMUNITY_Public HealthOpenAPI E2E Specs|Public Health/OpenAPI E2E Specs]] +- [[_COMMUNITY_Structured Logging|Structured Logging]] - [[_COMMUNITY_Admin App Allowlist Page|Admin App Allowlist Page]] - [[_COMMUNITY_Allowlist Core Module|Allowlist Core Module]] -- [[_COMMUNITY_Admin Route Dispatch & Security Headers|Admin Route Dispatch & Security Headers]] - [[_COMMUNITY_Self-Inspection (Container Auto-Detect)|Self-Inspection (Container Auto-Detect)]] -- [[_COMMUNITY_Public HealthOpenAPI E2E Specs|Public Health/OpenAPI E2E Specs]] -- [[_COMMUNITY_Gamepad Input Mapping|Gamepad Input Mapping]] +- [[_COMMUNITY_CITest Command Reference|CI/Test Command Reference]] - [[_COMMUNITY_Audit Log & Runtime Config Schema|Audit Log & Runtime Config Schema]] +- [[_COMMUNITY_E2E Workspace Fixtures|E2E Workspace Fixtures]] +- [[_COMMUNITY_Gamepad Input Mapping|Gamepad Input Mapping]] - [[_COMMUNITY_PathPlanner Integration Deploy Pipeline|PathPlanner Integration Deploy Pipeline]] - [[_COMMUNITY_Gamepad Lease Management|Gamepad Lease Management]] +- [[_COMMUNITY_E2E Workspace Fixtures|E2E Workspace Fixtures]] +- [[_COMMUNITY_Run Lifecycle Test Fixtures|Run Lifecycle Test Fixtures]] - [[_COMMUNITY_Container Lease Cleanup CLI|Container Lease Cleanup CLI]] -- [[_COMMUNITY_Keyboard Input Mapping|Keyboard Input Mapping]] +- [[_COMMUNITY_Backup Database CLI|Backup Database CLI]] +- [[_COMMUNITY_PathPlanner Deploy File Schemas|PathPlanner Deploy File Schemas]] +- [[_COMMUNITY_GamepadDriver Station Zod Schemas|Gamepad/Driver Station Zod Schemas]] - [[_COMMUNITY_ControlApp Test Harness Socket Types|ControlApp Test Harness Socket Types]] +- [[_COMMUNITY_Docker Client & Lifecycle|Docker Client & Lifecycle]] - [[_COMMUNITY_Architecture & Access Control Docs|Architecture & Access Control Docs]] - [[_COMMUNITY_Auto Panel & Gamepad Channel|Auto Panel & Gamepad Channel]] - [[_COMMUNITY_HALSim Bridge State Application|HALSim Bridge State Application]] -- [[_COMMUNITY_Deploy Files Security Test Suite|Deploy Files Security Test Suite]] +- [[_COMMUNITY_HALSimNT4 WebSocket Responses|HALSim/NT4 WebSocket Responses]] - [[_COMMUNITY_Asset Upload & Manifest|Asset Upload & Manifest]] -- [[_COMMUNITY_Workspace Container Bind-Mount & Memory Bounds|Workspace Container Bind-Mount & Memory Bounds]] -- [[_COMMUNITY_Java Tooling Smoke Test|Java Tooling Smoke Test]] +- [[_COMMUNITY_Deploy Files Security Test Suite|Deploy Files Security Test Suite]] +- [[_COMMUNITY_Run Manager Command Factory|Run Manager Command Factory]] +- [[_COMMUNITY_Import Pipeline Test Fixtures|Import Pipeline Test Fixtures]] +- [[_COMMUNITY_Path Planner Pane.test|Path Planner Pane.test]] +- [[_COMMUNITY_Container Capacity & V2 Acceptance Decisions|Container Capacity & V2 Acceptance Decisions]] - [[_COMMUNITY_Contracts Property-Based Tests|Contracts Property-Based Tests]] - [[_COMMUNITY_Admin Allowlist Endpoints|Admin Allowlist Endpoints]] - [[_COMMUNITY_AdvantageScope Lite & Docusaurus|AdvantageScope Lite & Docusaurus]] -- [[_COMMUNITY_CITest Command Reference|CI/Test Command Reference]] -- [[_COMMUNITY_GamepadDriver Station Zod Schemas|Gamepad/Driver Station Zod Schemas]] +- [[_COMMUNITY_Workspace Container Bind-Mount & Memory Bounds|Workspace Container Bind-Mount & Memory Bounds]] +- [[_COMMUNITY_SlugLogin Property Tests|Slug/Login Property Tests]] +- [[_COMMUNITY_Scripts|Scripts]] - [[_COMMUNITY_Mock Workspace Runtime Provider|Mock Workspace Runtime Provider]] -- [[_COMMUNITY_Scope Pane & Demo Banner|Scope Pane & Demo Banner]] -- [[_COMMUNITY_HALSim Bridge Message Handling|HALSim Bridge Message Handling]] -- [[_COMMUNITY_AuditReconciliation Test Suite|Audit/Reconciliation Test Suite]] - [[_COMMUNITY_WebSocket Router Test Suite|WebSocket Router Test Suite]] - [[_COMMUNITY_Deploy Files Path Safety|Deploy Files Path Safety]] -- [[_COMMUNITY_Run Manager Command Factory|Run Manager Command Factory]] -- [[_COMMUNITY_Java LSP Bridge (Archived)|Java LSP Bridge (Archived)]] +- [[_COMMUNITY_Java Tooling & Test Tiers|Java Tooling & Test Tiers]] - [[_COMMUNITY_VSCodium Editor Container Defaults|VSCodium Editor Container Defaults]] - [[_COMMUNITY_Contracts Schema Unit Tests|Contracts Schema Unit Tests]] - [[_COMMUNITY_Import Manager (Git Clone Staging)|Import Manager (Git Clone Staging)]] - [[_COMMUNITY_Demo Mode Session Seeding|Demo Mode Session Seeding]] - [[_COMMUNITY_BackupRestore & Catalog Import Flows|Backup/Restore & Catalog Import Flows]] - [[_COMMUNITY_Auth Client & Docker Runner Helpers|Auth Client & Docker Runner Helpers]] -- [[_COMMUNITY_Container Capacity & V2 Acceptance Decisions|Container Capacity & V2 Acceptance Decisions]] -- [[_COMMUNITY_Dist Download Utility|Dist Download Utility]] -- [[_COMMUNITY_PathPlanner Deploy File Schemas|PathPlanner Deploy File Schemas]] +- [[_COMMUNITY_HALSim Test Suite|HALSim Test Suite]] - [[_COMMUNITY_Bundled Lesson Catalog Modules|Bundled Lesson Catalog Modules]] - [[_COMMUNITY_Fake NT4 Test Server|Fake NT4 Test Server]] -- [[_COMMUNITY_Code Status Pill Component|Code Status Pill Component]] +- [[_COMMUNITY_HALSim Bridge Message Handling|HALSim Bridge Message Handling]] - [[_COMMUNITY_Database Migrations Runner|Database Migrations Runner]] - [[_COMMUNITY_Asset Upload Test Suite|Asset Upload Test Suite]] - [[_COMMUNITY_Capacity Cap Test Suite|Capacity Cap Test Suite]] - [[_COMMUNITY_Disk Read Limit Config|Disk Read Limit Config]] -- [[_COMMUNITY_Canonical Image Naming Decisions|Canonical Image Naming Decisions]] +- [[_COMMUNITY_Community 88|Community 88]] - [[_COMMUNITY_AdvantageScope Lite Hosting (Archived)|AdvantageScope Lite Hosting (Archived)]] -- [[_COMMUNITY_CLI Reference & Control Container|CLI Reference & Control Container]] -- [[_COMMUNITY_IDE Layout Resizable Panes|IDE Layout Resizable Panes]] -- [[_COMMUNITY_Simulation State Hook|Simulation State Hook]] +- [[_COMMUNITY_Driver Station Page Object & Runtime Seeding|Driver Station Page Object & Runtime Seeding]] +- [[_COMMUNITY_Rebuild Workspaces|Rebuild Workspaces]] +- [[_COMMUNITY_Canonical Image Naming Decisions|Canonical Image Naming Decisions]] +- [[_COMMUNITY_Audit Log & Break-Glass Admin|Audit Log & Break-Glass Admin]] +- [[_COMMUNITY_Container Runtime Status Helpers|Container Runtime Status Helpers]] - [[_COMMUNITY_Network Mode Config Test|Network Mode Config Test]] +- [[_COMMUNITY_Simulation State Hook|Simulation State Hook]] - [[_COMMUNITY_Rebuild Workspaces CLI Args|Rebuild Workspaces CLI Args]] - [[_COMMUNITY_Users CLI Script|Users CLI Script]] - [[_COMMUNITY_Extension Reconciliation Test|Extension Reconciliation Test]] +- [[_COMMUNITY_Sim Api.test|Sim Api.test]] - [[_COMMUNITY_AdvantageScope Verify Script|AdvantageScope Verify Script]] -- [[_COMMUNITY_Gamepad Session Safety Disables|Gamepad Session Safety Disables]] -- [[_COMMUNITY_SlugLogin Property Tests|Slug/Login Property Tests]] +- [[_COMMUNITY_README|README]] - [[_COMMUNITY_Container Isolation & HALSim Decisions|Container Isolation & HALSim Decisions]] - [[_COMMUNITY_Cloudflare Pages Proxy Function|Cloudflare Pages Proxy Function]] - [[_COMMUNITY_Project Swap Fake Socket Test|Project Swap Fake Socket Test]] @@ -124,48 +122,50 @@ - [[_COMMUNITY_WebSocket Upstream Message Send|WebSocket Upstream Message Send]] - [[_COMMUNITY_Deployment Hardware & FAQ Docs|Deployment Hardware & FAQ Docs]] - [[_COMMUNITY_E2E ControlApp Test Fixture Setup|E2E ControlApp Test Fixture Setup]] -- [[_COMMUNITY_Driver Station Page Object & Runtime Seeding|Driver Station Page Object & Runtime Seeding]] - [[_COMMUNITY_AS Lite NT4 Endpoint Injection Patch|AS Lite NT4 Endpoint Injection Patch]] - [[_COMMUNITY_Editor Pane Reachability|Editor Pane Reachability]] -- [[_COMMUNITY_Gamepad Session Lifecycle Methods|Gamepad Session Lifecycle Methods]] +- [[_COMMUNITY_Driver Station Switch Project Flow|Driver Station Switch Project Flow]] +- [[_COMMUNITY_Driver Station Switch Project Flow|Driver Station Switch Project Flow]] - [[_COMMUNITY_Admin Workspace BackupRestore Actions|Admin Workspace Backup/Restore Actions]] - [[_COMMUNITY_Auto Choosers & Container Status Hooks|Auto Choosers & Container Status Hooks]] - [[_COMMUNITY_Java Tooling Compatibility Decisions|Java Tooling Compatibility Decisions]] -- [[_COMMUNITY_Demo Mode DiskMemory Limits|Demo Mode Disk/Memory Limits]] +- [[_COMMUNITY_Lessons & Testing Decision Docs|Lessons & Testing Decision Docs]] - [[_COMMUNITY_Editor Migration Decisions|Editor Migration Decisions]] -- [[_COMMUNITY_Playwright Fixture Bootstrap Helpers|Playwright Fixture Bootstrap Helpers]] +- [[_COMMUNITY_CLI Reference & Control Container|CLI Reference & Control Container]] - [[_COMMUNITY_Gamepad Shim E2E Test|Gamepad Shim E2E Test]] -- [[_COMMUNITY_Driver Station Page Object|Driver Station Page Object]] - [[_COMMUNITY_Catalog Integrity Test|Catalog Integrity Test]] +- [[_COMMUNITY_Driver Station Page Object|Driver Station Page Object]] - [[_COMMUNITY_Fake Socket Test Double|Fake Socket Test Double]] - [[_COMMUNITY_Session Hook & Heartbeat|Session Hook & Heartbeat]] - [[_COMMUNITY_Auto Choosers Hook Test|Auto Choosers Hook Test]] -- [[_COMMUNITY_HALSim Test Suite|HALSim Test Suite]] -- [[_COMMUNITY_Backup Database CLI|Backup Database CLI]] - [[_COMMUNITY_Command Injection Test Suite|Command Injection Test Suite]] - [[_COMMUNITY_Default-Deny Route Coverage|Default-Deny Route Coverage]] -- [[_COMMUNITY_Lessons & Testing Decision Docs|Lessons & Testing Decision Docs]] +- [[_COMMUNITY_Driver Station Page Object & Runtime Seeding|Driver Station Page Object & Runtime Seeding]] +- [[_COMMUNITY_Demo Mode DiskMemory Limits|Demo Mode Disk/Memory Limits]] +- [[_COMMUNITY_Img Screenshots|Img Screenshots]] - [[_COMMUNITY_Build Failure.spec|Build Failure.spec]] - [[_COMMUNITY_Admin.po|Admin.po]] -- [[_COMMUNITY_Path Planner Pane.test|Path Planner Pane.test]] +- [[_COMMUNITY_Java Tooling Smoke Test|Java Tooling Smoke Test]] - [[_COMMUNITY_Ws Bridge|Ws Bridge]] - [[_COMMUNITY_Main|Main]] - [[_COMMUNITY_Logging.test|Logging.test]] -- [[_COMMUNITY_Middleware|Middleware]] - [[_COMMUNITY_Metadata|Metadata]] +- [[_COMMUNITY_Middleware|Middleware]] - [[_COMMUNITY_Image|Image]] - [[_COMMUNITY_023 Metrics And Observability|023 Metrics And Observability]] - [[_COMMUNITY_User Data|User Data]] - [[_COMMUNITY_Ws Proxy.spec|Ws Proxy.spec]] +- [[_COMMUNITY_024 Container Memory Budget|024 Container Memory Budget]] - [[_COMMUNITY_Halsim.test|Halsim.test]] - [[_COMMUNITY_Gamepad.test|Gamepad.test]] - [[_COMMUNITY_Audit Prune|Audit Prune]] - [[_COMMUNITY_Users|Users]] -- [[_COMMUNITY_README|README]] - [[_COMMUNITY_Helpers|Helpers]] +- [[_COMMUNITY_Card|Card]] - [[_COMMUNITY_Catalog.test|Catalog.test]] - [[_COMMUNITY_Block Devices|Block Devices]] - [[_COMMUNITY_Ws Bridge|Ws Bridge]] +- [[_COMMUNITY_Dropdown Menu|Dropdown Menu]] - [[_COMMUNITY_020 Workspace Runtime Provider|020 Workspace Runtime Provider]] - [[_COMMUNITY_030 Control Plane Hardening Pass|030 Control Plane Hardening Pass]] - [[_COMMUNITY_Auth|Auth]] @@ -173,12 +173,11 @@ - [[_COMMUNITY_Xss.spec|Xss.spec]] - [[_COMMUNITY_Build Ascope Lite|Build Ascope Lite]] - [[_COMMUNITY_Index|Index]] -- [[_COMMUNITY_Backup|Backup]] -- [[_COMMUNITY_Rebuild Workspaces|Rebuild Workspaces]] -- [[_COMMUNITY_Img Screenshots|Img Screenshots]] +- [[_COMMUNITY_Allowlist Management E2E|Allowlist Management E2E]] - [[_COMMUNITY_Use Container Status.test|Use Container Status.test]] -- [[_COMMUNITY_Code Container Defaults.test|Code Container Defaults.test]] - [[_COMMUNITY_Frc Robot|Frc Robot]] +- [[_COMMUNITY_Code Container Defaults.test|Code Container Defaults.test]] +- [[_COMMUNITY_Java LSP Bridge (Archived)|Java LSP Bridge (Archived)]] - [[_COMMUNITY_2026 08 30 Pathplanner Integration|2026 08 30 Pathplanner Integration]] - [[_COMMUNITY_Restore|Restore]] - [[_COMMUNITY_Login.po|Login.po]] @@ -189,38 +188,40 @@ - [[_COMMUNITY_Config|Config]] - [[_COMMUNITY_Use Gamepad|Use Gamepad]] - [[_COMMUNITY_016 Project Import Strategy|016 Project Import Strategy]] -- [[_COMMUNITY_035 Multi Arch Images And Workflow Split|035 Multi Arch Images And Workflow Split]] - [[_COMMUNITY_path|[[path]]]] -- [[_COMMUNITY_unnamed|]] -- [[_COMMUNITY_Robot Container|Robot Container]] - [[_COMMUNITY_Clean|Clean]] +- [[_COMMUNITY_Robot Container|Robot Container]] +- [[_COMMUNITY_Dist Download Utility|Dist Download Utility]] +- [[_COMMUNITY_Cloudflare Pages Function.test|Cloudflare Pages Function.test]] +- [[_COMMUNITY_PathPlanner Integration|PathPlanner Integration]] - [[_COMMUNITY_Logging|Logging]] - [[_COMMUNITY_Metrics.test|Metrics.test]] -- [[_COMMUNITY_Store|Store]] - [[_COMMUNITY_004 Backend Wiring|004 Backend Wiring]] +- [[_COMMUNITY_Store|Store]] - [[_COMMUNITY_Dist Download|Dist Download]] - [[_COMMUNITY_Img Screenshots|Img Screenshots]] +- [[_COMMUNITY_AuditReconciliation Test Suite|Audit/Reconciliation Test Suite]] +- [[_COMMUNITY_Dialog|Dialog]] - [[_COMMUNITY_Main|Main]] - [[_COMMUNITY_Main|Main]] - [[_COMMUNITY_Typecheck|Typecheck]] -- [[_COMMUNITY_Sim Api.test|Sim Api.test]] -- [[_COMMUNITY_Switch Project Dialog|Switch Project Dialog]] -- [[_COMMUNITY_Dialog|Dialog]] -- [[_COMMUNITY_024 Container Memory Budget|024 Container Memory Budget]] -- [[_COMMUNITY_Scripts|Scripts]] +- [[_COMMUNITY_Project Preview & Right Pane|Project Preview & Right Pane]] - [[_COMMUNITY_Global Setup|Global Setup]] - [[_COMMUNITY_Docusaurus.config|Docusaurus.config]] - [[_COMMUNITY_Sidebars|Sidebars]] -- [[_COMMUNITY_Vite.config|Vite.config]] +- [[_COMMUNITY_Web App Shell & Theming|Web App Shell & Theming]] - [[_COMMUNITY_Constants|Constants]] +- [[_COMMUNITY_Vite.config|Vite.config]] +- [[_COMMUNITY_AuditReconciliation Test Suite|Audit/Reconciliation Test Suite]] - [[_COMMUNITY_Icon Rail|Icon Rail]] - [[_COMMUNITY_Dropdown Menu|Dropdown Menu]] - [[_COMMUNITY_Scroll Area|Scroll Area]] - [[_COMMUNITY_Use Gamepad Channel|Use Gamepad Channel]] -- [[_COMMUNITY_Workspace Page|Workspace Page]] -- [[_COMMUNITY_README|README]] - [[_COMMUNITY_Cleanup Containers|Cleanup Containers]] -- [[_COMMUNITY_Cloudflare Pages Function.test|Cloudflare Pages Function.test]] +- [[_COMMUNITY_Dist Download Utility|Dist Download Utility]] +- [[_COMMUNITY_Gamepad Session Safety Disables|Gamepad Session Safety Disables]] +- [[_COMMUNITY_E2E Fixture Architecture|E2E Fixture Architecture]] +- [[_COMMUNITY_Allowlist Management E2E|Allowlist Management E2E]] - [[_COMMUNITY_Extension Reconciliation.test|Extension Reconciliation.test]] - [[_COMMUNITY_Users|Users]] - [[_COMMUNITY_Vitest.config|Vitest.config]] @@ -236,20 +237,15 @@ - [[_COMMUNITY_Docker Client|Docker Client]] - [[_COMMUNITY_Storage|Storage]] - [[_COMMUNITY_Demo Banner|Demo Banner]] -- [[_COMMUNITY_Theme Provider|Theme Provider]] +- [[_COMMUNITY_HALSimNT4 WebSocket Responses|HALSim/NT4 WebSocket Responses]] - [[_COMMUNITY_Button|Button]] - [[_COMMUNITY_Card|Card]] - [[_COMMUNITY_Card|Card]] -- [[_COMMUNITY_Card|Card]] -- [[_COMMUNITY_Card|Card]] -- [[_COMMUNITY_Card|Card]] -- [[_COMMUNITY_Card|Card]] -- [[_COMMUNITY_Card|Card]] -- [[_COMMUNITY_Dialog|Dialog]] - [[_COMMUNITY_Dialog|Dialog]] - [[_COMMUNITY_Dialog|Dialog]] - [[_COMMUNITY_Dialog|Dialog]] - [[_COMMUNITY_Dialog|Dialog]] +- [[_COMMUNITY_Allowlist Management E2E|Allowlist Management E2E]] - [[_COMMUNITY_Dialog|Dialog]] - [[_COMMUNITY_Dialog|Dialog]] - [[_COMMUNITY_Dialog|Dialog]] @@ -263,11 +259,6 @@ - [[_COMMUNITY_Dropdown Menu|Dropdown Menu]] - [[_COMMUNITY_Dropdown Menu|Dropdown Menu]] - [[_COMMUNITY_Dropdown Menu|Dropdown Menu]] -- [[_COMMUNITY_Dropdown Menu|Dropdown Menu]] -- [[_COMMUNITY_Dropdown Menu|Dropdown Menu]] -- [[_COMMUNITY_Dropdown Menu|Dropdown Menu]] -- [[_COMMUNITY_Resizable|Resizable]] -- [[_COMMUNITY_Resizable|Resizable]] - [[_COMMUNITY_Resizable|Resizable]] - [[_COMMUNITY_Resizable|Resizable]] - [[_COMMUNITY_Separator|Separator]] @@ -277,46 +268,50 @@ - [[_COMMUNITY_Tabs|Tabs]] - [[_COMMUNITY_Tabs|Tabs]] - [[_COMMUNITY_Tabs|Tabs]] -- [[_COMMUNITY_Tabs|Tabs]] - [[_COMMUNITY_Tooltip|Tooltip]] - [[_COMMUNITY_Tooltip|Tooltip]] - [[_COMMUNITY_Tooltip|Tooltip]] - [[_COMMUNITY_Tooltip|Tooltip]] - [[_COMMUNITY_Use Run Channel|Use Run Channel]] -- [[_COMMUNITY_Playwright.config|Playwright.config]] - [[_COMMUNITY_Image|Image]] - [[_COMMUNITY_Verify Ascope|Verify Ascope]] +- [[_COMMUNITY_Community 265|Community 265]] +- [[_COMMUNITY_Community 266|Community 266]] +- [[_COMMUNITY_Community 267|Community 267]] +- [[_COMMUNITY_Community 268|Community 268]] +- [[_COMMUNITY_Community 269|Community 269]] +- [[_COMMUNITY_Community 270|Community 270]] - [[_COMMUNITY_Community 271|Community 271]] - [[_COMMUNITY_Community 272|Community 272]] - [[_COMMUNITY_Community 273|Community 273]] -- [[_COMMUNITY_Community 274|Community 274]] +- [[_COMMUNITY_Rebuild Workspaces|Rebuild Workspaces]] - [[_COMMUNITY_Community 275|Community 275]] - [[_COMMUNITY_Community 276|Community 276]] - [[_COMMUNITY_Community 277|Community 277]] - [[_COMMUNITY_Community 278|Community 278]] - [[_COMMUNITY_Community 279|Community 279]] - [[_COMMUNITY_Community 280|Community 280]] +- [[_COMMUNITY_Community 281|Community 281]] - [[_COMMUNITY_Community 282|Community 282]] - [[_COMMUNITY_Community 283|Community 283]] -- [[_COMMUNITY_Community 284|Community 284]] +- [[_COMMUNITY_Workspace Container Bind-Mount & Memory Bounds|Workspace Container Bind-Mount & Memory Bounds]] - [[_COMMUNITY_Community 285|Community 285]] -- [[_COMMUNITY_Community 286|Community 286]] - [[_COMMUNITY_Community 287|Community 287]] - [[_COMMUNITY_Community 288|Community 288]] - [[_COMMUNITY_Community 289|Community 289]] - [[_COMMUNITY_Community 290|Community 290]] - [[_COMMUNITY_Community 291|Community 291]] - [[_COMMUNITY_Community 292|Community 292]] +- [[_COMMUNITY_Community 293|Community 293]] - [[_COMMUNITY_Community 294|Community 294]] - [[_COMMUNITY_Community 295|Community 295]] - [[_COMMUNITY_Community 296|Community 296]] -- [[_COMMUNITY_Community 304|Community 304]] -- [[_COMMUNITY_Community 305|Community 305]] -- [[_COMMUNITY_Community 306|Community 306]] -- [[_COMMUNITY_Community 307|Community 307]] -- [[_COMMUNITY_Community 308|Community 308]] -- [[_COMMUNITY_Community 309|Community 309]] -- [[_COMMUNITY_Community 310|Community 310]] +- [[_COMMUNITY_Community 297|Community 297]] +- [[_COMMUNITY_Community 299|Community 299]] +- [[_COMMUNITY_Community 300|Community 300]] +- [[_COMMUNITY_Community 301|Community 301]] +- [[_COMMUNITY_Image Naming & Archive|Image Naming & Archive]] +- [[_COMMUNITY_Disk Limits & Demo Mode|Disk Limits & Demo Mode]] - [[_COMMUNITY_Community 311|Community 311]] - [[_COMMUNITY_Community 312|Community 312]] - [[_COMMUNITY_Community 313|Community 313]] @@ -365,1142 +360,1129 @@ - [[_COMMUNITY_Community 356|Community 356]] - [[_COMMUNITY_Community 357|Community 357]] - [[_COMMUNITY_Community 358|Community 358]] +- [[_COMMUNITY_Community 359|Community 359]] - [[_COMMUNITY_Community 360|Community 360]] +- [[_COMMUNITY_Community 361|Community 361]] +- [[_COMMUNITY_Community 362|Community 362]] +- [[_COMMUNITY_Community 363|Community 363]] +- [[_COMMUNITY_Community 364|Community 364]] +- [[_COMMUNITY_Community 365|Community 365]] +- [[_COMMUNITY_Community 367|Community 367]] +- [[_COMMUNITY_Multi-Arch Image Builds|Multi-Arch Image Builds]] +- [[_COMMUNITY_SELinux Container Mounts|SELinux Container Mounts]] ## God Nodes (most connected - your core abstractions) -1. `cn()` - 75 edges +1. `cn()` - 77 edges 2. `AppStorage` - 44 edges 3. `LocalDockerRuntimeProvider` - 40 edges -4. `LocalDockerRuntimeProvider` - 33 edges -5. `test` - 32 edges -6. `loginAs()` - 30 edges -7. `withApp()` - 29 edges +4. `test` - 36 edges +5. `loginAs()` - 35 edges +6. `LocalDockerRuntimeProvider` - 33 edges +7. `withApp()` - 30 edges 8. `RunManager` - 28 edges 9. `HalSimBridge` - 26 edges -10. `getLogger()` - 23 edges +10. `getLogger()` - 24 edges ## Surprising Connections (you probably didn't know these) - `nt4-multi-workspace.spec.ts (T35.1 NT4 isolation test)` --semantically_similar_to--> `embedded-mode NT4 endpoint injection mechanism` [INFERRED] [semantically similar] e2e/specs/telemetry/nt4-multi-workspace.spec.ts → patches/advantagescope/README.md -- `Grafana ops dashboard screenshot: host VM, workspaces, runs, control-plane panels` --conceptually_related_to--> `rebuildWorkspaces()` [AMBIGUOUS] - website/static/img/screenshots/grafana-ops-dashboard.png → scripts/rebuild-workspaces.ts -- `run-sim.sh two-phase sim runner` --references--> `robot-starter build.gradle` [INFERRED] - containers/code/README.md → catalog/modules/robot-starter/build.gradle -- `WorkspacePage (page object)` --conceptually_related_to--> `SimPaneSwitcher.tsx` [INFERRED] - e2e/page-objects/workspace.po.ts → docs/superpowers/plans/2026-08-30-pathplanner-integration.md -- `applyAdvantageScopePatches()` --references--> `001-lite-nt4-endpoint-injection.patch` [INFERRED] - scripts/apply-ascope-patches.ts → patches/advantagescope/README.md +- `rebuildWorkspaces()` --conceptually_related_to--> `Grafana ops dashboard screenshot: host VM, workspaces, runs, control-plane panels` [AMBIGUOUS] + scripts/rebuild-workspaces.ts → website/static/img/screenshots/grafana-ops-dashboard.png +- `SimPaneSwitcher.tsx` --conceptually_related_to--> `WorkspacePage (page object)` [INFERRED] + docs/superpowers/plans/2026-08-30-pathplanner-integration.md → e2e/page-objects/workspace.po.ts +- `001-lite-nt4-endpoint-injection.patch` --references--> `applyAdvantageScopePatches()` [INFERRED] + patches/advantagescope/README.md → scripts/apply-ascope-patches.ts +- `Demo Mode Quick Start` --conceptually_related_to--> `demo mode test suite` [INFERRED] + README.md → apps/control/src/__tests__/auth-demo.test.ts ## Hyperedges (group relationships) -- **V2 container-lease port model evolution (V1 sim/lsp -> V2 nt4/vscode/halsim)** — 001_v1_core_container_leases_table, 004_v2_code_container_vscode_port_column, 005_drop_v1_columns_nt4_port_column, 006_halsim_port_halsim_port_column, 007_betterauth_tables_container_leases_new_table [EXTRACTED 0.90] -- **Code container orchestration: capacity gating, lease creation, and lifecycle cleanup** — containers_ensurecodecontainer, containers_capacityexceedederror, storage_geteffectivemaxactivecontainers, containers_lifecycle_removecodevolume, containers_metadata_codecontainername [INFERRED 0.85] -- **Gamepad input forwarded through HALSim WebSocket bridge to sim joystick/driver-station state** — gamepad_gamepadsessions, halsim_halsimbridge, halsim_halsimbridgeunavailableerror, 006_halsim_port_halsim_port_column [EXTRACTED 0.90] -- **Per-workspace cross-tenant isolation enforced across routes** — proxy_test, scope_and_nt4_test, routing_test, session_and_admin_test, lessons_routes_test [INFERRED 0.85] -- **Fake Docker runner + MockWorkspaceRuntimeProvider test harness underpins container/runtime tests** — helpers_createfakedocker, helpers_mockworkspaceruntimeprovider, reconciliation_test, network_mode_test, idle_and_admin_test, imports_test [INFERRED 0.85] -- **Import/catalog pipeline hardened against SSRF, path traversal, and command injection** — imports_parsegithuburl, imports_importmanager, ssrf_imports_test, path_traversal_test, command_injection_test, imports_url_property_test [EXTRACTED 1.00] -- **WebSocket upstream proxy bridge (nt4/vscode/halsim)** — proxy_nt4websocketresponse, proxy_vscodewebsocketresponse, proxy_halsimwebsocketresponse, websocket_openproxyupstream, types_proxy_pending_limit [INFERRED 0.85] -- **Workspace route session/ownership gating flow** — workspace_routes_handleworkspaceroute, middleware_requireworkspaceownership, middleware_getsessionfromrequest, middleware_default_deny_pattern [EXTRACTED 1.00] -- **Lesson catalog resolve-and-load flow** — catalog_catalogsource, catalog_remotecatalogsource, catalog_bundledcatalogsource, websocket_ws_message_handler, websocket_stopactiverun [INFERRED 0.85] -- **Container Port/Endpoint Resolution Across Modes** — types_port_constants, converters_upstreamendpoints, metadata_publishedportfor, local_docker_runtime_provider_localdockerruntimeprovider [INFERRED 0.85] -- **Docker Container Adopt/Create/Reconcile Flow** — local_docker_runtime_provider_localdockerruntimeprovider, lifecycle_stopcodecontainer, metadata_v2labelsmatch, docker_client_inspectcontainer, errors_capacityexceedederror [EXTRACTED 1.00] -- **Upstream WebSocket Bridge Session Management for Sim Telemetry/Control** — halsim_halsimbridge, nt4_auto_nt4autochooserbridge, gamepad_gamepadsessions [INFERRED 0.85] -- **Run job lifecycle: RunManager, WorkspaceRuntimeProvider, AppStorage** — runs_runmanager, runtime_workspaceruntimeprovider, storage_appstorage, runs_runjob [EXTRACTED 0.90] -- **Admin pages using useAdminPoll for periodic data fetch and mutation actions** — allowlist_allowlist, auditlog_auditlog, containers_containers, dashboard_dashboard, users_users, workspaces_workspaces, useadminpoll_useadminpoll [INFERRED 0.85] -- **DriverStation composes AutoPanel, ControlsPanel, ConsolePanel via rail tab switching** — driverstation_driverstation, autopanel_autopanel, controlspanel_controlspanel, consolepanel_consolepanel [EXTRACTED 0.90] -- **Driver Station Workbench Panel Composition** — workbenchpanel_workbenchpanel, simcontrolsblock_simcontrolsblock, modecolumn_modecolumn, statustile_statustilerow, enabledisablerow_enabledisablerow [EXTRACTED 1.00] -- **Iframe Pane With Loading Overlay Pattern** — editorpane_editorpane, scopepane_scopepane, pathplannerpane_pathplannerpane [INFERRED 0.80] -- **Sim Pane Tab Switching (Topbar Selector + Layout Panels)** — idelayout_idelayout, topbar_topbar, simpaneswitcher_simpanetabs, simpaneswitcher_simpanetabselector, simpaneswitcher_simpanepanels [INFERRED 0.75] -- **Polling fetch hook pattern (interval + cancelled flag)** — useautochoosers_useautochoosers, usecontainerstatus_usecontainerstatus, useeditorreachability_useeditorreachability, uselessons_uselessons [INFERRED 0.85] -- **base-ui primitive wrapping pattern (data-slot + cn className merge)** — dialog_dialog, dropdownmenu_dropdownmenu, tooltip_tooltip, tabs_tabs, scrollarea_scrollarea [INFERRED 0.85] -- **Gamepad/Keyboard Input Mode to WPILib Channel Pipeline** — workspacepage_workspacepage, gamepad_mapping_gamepadframetowpilib, keyboard_mapping_keyboardcodestowpilib, keyboard_mapping_gamepadstatetovisualizerframe [INFERRED 0.85] -- **Project Swap -> Session Reload -> Editor Remount Cycle** — useprojectswap_useprojectswap, workspacepage_onswapcomplete, usesession_usesession, usesession_reloadkey_pattern [INFERRED 0.85] -- **Console Lesson Sim-hook Gating** — workspacepage_console_lesson_gating, userunchannel_userunchannel, usesimulationstate_usesimulationstate, usescopehandshake_usescopehandshake [INFERRED 0.85] -- **Robot starter lifecycle: Main -> Robot -> RobotContainer -> AdvantageKit Logger** — robot_starter_main, robot_starter_robot, robot_starter_robotcontainer, advantagekit_logger [EXTRACTED 1.00] -- **Sim execution pipeline: start-sim -> run-sim (two-phase gradle build + exec) -> stop-sim, headless-bounded via sim-headless.init.gradle** — start_sim_sh, run_sim_sh, stop_sim_sh, sim_headless_init_gradle [INFERRED 0.85] -- **GCE first-boot provisioning: bootstrap.sh installs Docker/app-user/data-disk, invokes render-env.sh to materialize .env and Alloy config, then docker compose brings up control+Caddy+Alloy** — bootstrap_sh, render_env_sh, caddyfile_gce, alloy_config_template, docker_compose_prod_yml [EXTRACTED 1.00] -- **HALSim-based Driver Station and gamepad control system** — halsim_ws_protocol, halsim_bridge, decision_015_doc, decision_018_doc, gamepad_sessions [EXTRACTED 0.90] -- **Classroom-density memory optimization effort** — decision_024_doc, decision_025_doc, classroom_density_memory_defaults, gradle_build_server, run_sim_sh [EXTRACTED 0.85] -- **Editor base image migration lineage (spike to linuxserver)** — decision_011_doc, decision_012_doc, decision_017_doc, gitpod_openvscode_server, linuxserver_openvscode_server [EXTRACTED 0.85] -- **VSCodium migration multi-decision fix chain** — decision_036_vscodium_web_migration, decision_037_gradle_wrapper_alias_and_extension_pins, decision_038_java_tooling_compatibility_and_smoke [EXTRACTED 1.00] -- **Self-inspection / zero-config-in-container pattern reused across decisions** — self_inspect_ts, code_disk_read_limit, group_add_docker_gid_zero_default [INFERRED 0.85] -- **Demo mode design evolution across decisions** — decision_028_demo_mode, decision_031_containerized_control_plane, decision_034_demo_mode_portability [EXTRACTED 1.00] -- **V1-7 LSP hardening: bridge, orchestrator throttle, browser reconnect** — d008_bridge_ts, d009_lsp_startup_throttle, d009_browser_lsp_reconnect, d008_container_orchestrator [EXTRACTED 1.00] -- **Release and deploy pipeline across GCE and Cloudflare** — gcloud_release_workflow, gcloud_deploy_workflow, cloudflare_deploy_workflow_job, coderunner_workspace_image [EXTRACTED 1.00] -- **Intentional workspace-content discard on lesson switch/reset/import** — lessons_gitless_design, team_import_doc, terms_of_service_doc, privacy_policy_doc [INFERRED 0.85] -- **Fake upstream servers powering the mocked E2E tier** — app_ts_fixture, fake_vscode_ts, fake_halsim_ts, fake_nt4_ts [EXTRACTED 1.00] -- **PathPlanner integration: contracts, handlers, static serving, dormant UI** — deploy_files_contracts, deploy_files_ts, workspace_routes_ts, pathplanner_response_fn, pathplanner_pane_component, sim_pane_switcher_component [EXTRACTED 1.00] -- **VSCodium migration decision chain (011 -> 036 -> 037/034/033)** — decision_017_linuxserver_base, decision_036_vscodium_migration, decision_037_disable_workspace_trust, decision_033_disk_read_limit, decision_034_recursive_chown [INFERRED 0.85] -- **Run lifecycle test coverage (run/build/HALSim failure and recovery paths)** — build_failure_spec, run_lifecycle_spec, transient_unavailability_spec, driver_station_spec, multi_tab_sync_spec [INFERRED 0.80] -- **Editor proxy correctness (HTTP + WS forwarding, headers, asset paths)** — asset_base_path_spec, hop_by_hop_headers_spec, iframe_load_spec, ws_proxy_spec [INFERRED 0.80] -- **Access-control / default-deny enforcement across auth, workspace, and CSRF boundaries** — session_isolation_spec, csrf_spec, roles_spec, requirework_ownership_gate [INFERRED 0.75] -- **AdvantageScope Lite build & patch pipeline** — scripts_build_ascope_lite, scripts_apply_ascope_patches, ascope_patches_readme, ascope_patch_001, ascope_iframe_spec [INFERRED 0.85] -- **Lesson catalog schema validated across contracts, tests, and bundled data** — contracts_lessoncatalogschema, contracts_lessonmoduleschema, scripts_catalog_integrity_test, catalog_modules_json, contracts_property_test [INFERRED 0.85] -- **Security-tier E2E hardening tests (XSS, headers, NT4 isolation)** — xss_spec, response_headers_spec, nt4_multi_workspace_spec, playwright_project_security [INFERRED 0.75] -- **Prebuilt dist artifact download pipeline** — fetch_dist_main, fetch_pathplanner_dist_fetchpathplannerdist, dist_download_downloadandextract, dist_download_withscratch [EXTRACTED 1.00] -- **CodeRunner state backup/restore workflow** — restore_ts_main, restore_ts_restorearchive, restore_ts_restoredb, restore_ts_discoverworkspaces, backup_ts [INFERRED 0.85] -- **V2 workspace container teardown and lease reset** — rebuild_workspaces_rebuildworkspaces, rebuild_workspaces_clearcontainerleases, container_leases_table, cleanup_containers_main [INFERRED 0.75] -- **Three-Pane Workspace UI Screenshots** — screenshot_workspace_shell_three_panes, screenshot_pathplanner_overview, screenshot_using_coderunner_ready [INFERRED 0.80] -- **Project Onboarding and Team Import Flow** — screenshot_sign_in_page, screenshot_switch_project_dialog, screenshot_team_import_progress, screenshot_using_coderunner_start [INFERRED 0.75] - -## Communities (362 total, 86 thin omitted) +- **Right-Pane Tool Selector (AdvantageScope, PathPlanner, Preview)** — using_coderunner_advantagescope_tab, using_coderunner_pathplanner_tab, using_coderunner_preview_tab [EXTRACTED 1.00] +- **In-Process Fake Upstream Servers for Mocked E2E Tier** — testing_fake_vscode, testing_fake_halsim, testing_fake_nt4, testing_controlapp [EXTRACTED 1.00] +- **VSCodium/Java Tooling Decision Chain** — decisions_readme_036_vscodium_web_migration, decisions_readme_037_gradle_wrapper_alias_and_extension_pins, decisions_readme_038_java_tooling_compatibility_and_smoke [EXTRACTED 1.00] + +## Communities (371 total, 89 thin omitted) ### Community 0 - "Admin API Contracts" Cohesion: 0.03 -Nodes (66): AdminActionResponse, adminActionResponseSchema, AdminStatusResponse, adminStatusResponseSchema, AdminWorkspaceStatus, adminWorkspaceStatusSchema, AllianceStation, AuthProvider (+58 more) +Nodes (74): AdminActionResponse, adminActionResponseSchema, AdminStatusResponse, adminStatusResponseSchema, AdminWorkspaceStatus, adminWorkspaceStatusSchema, AllianceStation, AuthProvider (+66 more) -### Community 1 - "Allowlist Management E2E" +### Community 1 - "Admin Route Dispatch & Security Headers" +Cohesion: 0.05 +Nodes (64): handleAdminRoute, Admin workspace backup action, Admin workspace restore action, applySecurityHeaders, authorizeMetrics, constantTimeEqual, dispatch, fetch (request handler) (+56 more) + +### Community 2 - "App Storage (SQLite)" +Cohesion: 0.06 +Nodes (37): AutoPanel(), AutoPanelProps, ControlsPanel(), DriverStation(), DriverStationProps, change, { container, rerender }, props (+29 more) + +### Community 3 - "IDE Layout Resizable Panes" Cohesion: 0.07 -Nodes (40): handleAdminRoute(), addAllowlistEntry(), AllowlistData, EMPTY, getAllowlist(), isEmailAllowed(), loadAllowlist(), normalize() (+32 more) +Nodes (41): AssetManifest, contentTypeFor(), handleUploadAsset(), HAS_PROC_SELF_FD, pathplannerResponse(), readScopeAssetManifest(), safeRelativeAssetPath(), scopeResponse() (+33 more) -### Community 2 - "Driver Station Enable/Disable UI" +### Community 4 - "WebSocket Handler Factory" Cohesion: 0.08 -Nodes (43): AssetManifest, contentTypeFor(), handleUploadAsset(), isInsideDirectory(), pathplannerResponse(), readScopeAssetManifest(), safeRelativeAssetPath(), scopeResponse() (+35 more) +Nodes (32): BINDING_GROUPS, Bumper(), ControlsPanelProps, DpadArm(), FaceButton(), GamepadVisualizer(), SmallButton(), StatusTone (+24 more) -### Community 3 - "Driver Station Switch Project Flow" -Cohesion: 0.05 -Nodes (38): activeWorkspaces, DockerStatsPoller, DockerStatsPollerOptions, log, containerCpuPercent, containerMemoryPercent, containerStartBuckets, containerStartDuration (+30 more) +### Community 5 - "Control Plane App & Metrics" +Cohesion: 0.1 +Nodes (31): AdminApp(), AdminLayout(), Tab, tabs, useAdminPoll(), Allowlist(), AllowlistData, AuditEntry (+23 more) -### Community 4 - "Gamepad/Keyboard WPILib Mapping" +### Community 6 - "Gamepad/Keyboard WPILib Mapping" Cohesion: 0.04 Nodes (43): code:ts (describe("deployFilePathSchema", () => {), code:ts (pathplannerDistDir: string;), code:ts (export async function createPathPlannerDist(root: string): P), code:ts (const pathplannerDistDir = await createPathPlannerDist(root)), code:bash (bun run check:fix), code:ts (test("templates PathPlanner routes with bounded cardinality"), code:ts (if (path.startsWith("/pathplanner/")) return "/pathplanner/*), code:ts (if (suffix.startsWith("/api/deploy-files/"))) (+35 more) -### Community 5 - "Admin Layout & Polling" +### Community 7 - "Admin Layout & Polling" Cohesion: 0.05 Nodes (47): Web Contracts Re-export, dpadToPov (gamepad-mapping), gamepadFrameToWpilib, WPILib XboxController Axis/Button Layout, dpadToPov (keyboard-mapping), gamepadStateToVisualizerFrame, Keyboard-as-Virtual-Gamepad Emulation, KEYBOARD_BINDINGS (+39 more) -### Community 6 - "Container Runtime Status Helpers" -Cohesion: 0.08 -Nodes (29): Avatar(), UserMenuProps, CodeStatus, CodeStatusPill(), TONES, codeStatusFromRun(), ConsoleLine(), ConsolePanel() (+21 more) - -### Community 7 - "Shared UI Primitives" +### Community 8 - "Shared UI Primitives" Cohesion: 0.07 Nodes (24): chooserRoots(), decodeMsgPack(), displayKey(), encodeMsgPack(), isChooser(), normalizeTopicName(), Nt4AutoChooserBridge, Nt4AutoChooserBridgeOptions (+16 more) -### Community 8 - "Control Plane App & Metrics" -Cohesion: 0.11 -Nodes (27): AdminLayout(), Tab, tabs, useAdminPoll(), Allowlist(), AllowlistData, AuditEntry, AuditLog() (+19 more) - ### Community 9 - "Docker Runtime Provider Core" Cohesion: 0.05 Nodes (45): listWorkspaceDiskLimitDevices, parseDockerStatsLine, runtimeFromLease, statusFromLease, upstreamEndpoints, dockerPortBindError, inspectContainer, inspectContainers (+37 more) ### Community 10 - "Run Execution Metrics" -Cohesion: 0.07 -Nodes (27): AutoPanel(), AutoPanelProps, ControlsPanel(), DriverStationProps, MODE_CLASSES, MODE_LABELS, ModeColumn(), ModeColumnProps (+19 more) +Cohesion: 0.06 +Nodes (34): activeWorkspaces, DockerStatsPoller, DockerStatsPollerOptions, log, containerCpuPercent, containerMemoryPercent, containerStartBuckets, containerStartDuration (+26 more) -### Community 11 - "NT4 Auto Chooser Protocol" +### Community 11 - "Driver Station Enable/Disable UI" Cohesion: 0.1 -Nodes (7): runtimeFromLease(), statusFromLease(), dockerPortBindError(), LocalDockerRuntimeProvider, containerRuntimeState(), v2LabelsMatch(), workspaceHomePath() +Nodes (31): addAllowlistEntry(), AllowlistData, EMPTY, getAllowlist(), isEmailAllowed(), loadAllowlist(), normalize(), normalizeEntry() (+23 more) -### Community 12 - "Control Plane Route Test Suite" +### Community 12 - "Docker Runtime Types & Ports" Cohesion: 0.11 -Nodes (32): parseDockerStatsLine(), parsePercent(), defaultDockerRunner(), dockerError(), inspectContainer(), inspectContainerOrThrow(), inspectContainers(), runDocker() (+24 more) +Nodes (29): runtimeFromLease(), statusFromLease(), upstreamEndpoints(), CapacityExceededError, log, configMountType(), containerAttachedToNetwork(), containerHasPublishedPorts() (+21 more) -### Community 13 - "Driver Station Auto/Console Panels" +### Community 13 - "Control Plane Route Test Suite" Cohesion: 0.05 Nodes (37): 10. Become the first admin, 1. Create and configure the GCP project, 2. Create the Terraform state bucket, 3. Configure Terraform variables, 4. Apply Terraform, 5. Populate Secret Manager, 6. Add a DNS A record, 7. Reset the VM to render config (+29 more) -### Community 14 - "WebSocket Upstream Proxy" +### Community 14 - "NT4 Auto Chooser Protocol" +Cohesion: 0.11 +Nodes (3): LocalDockerRuntimeProvider, containerRuntimeState(), release + +### Community 15 - "E2E Runtime & Resilience Specs" Cohesion: 0.07 -Nodes (28): ADMIN_GET_ROUTES, adminCookie, cookie, req, studentCookie, cookieFrom(), createFakeDocker(), ExecOverride (+20 more) +Nodes (21): ResolvedTheme, Theme, THEME_VALUES, ThemeProvider(), ThemeProviderContext, ThemeProviderProps, ThemeProviderState, useTheme() (+13 more) + +### Community 16 - "Container Runtime Status Helpers" +Cohesion: 0.11 +Nodes (33): parseDeployFilePath(), halsimWebSocketResponse(), HOP_BY_HOP_HEADERS, log, nt4AliveResponse(), nt4WebSocketResponse(), probeVscodeReady(), requestedProtocols() (+25 more) -### Community 15 - "Web App Shell & Theming" +### Community 17 - "Driver Station Auto/Console Panels" Cohesion: 0.07 Nodes (35): AutoPanel, preferredChooser, CodeStatusPill, codeStatusFromRun, ConsolePanel, ConsoleViewport, ControlsPanel, GamepadVisualizer (+27 more) -### Community 16 - "Auth & Allowlist Test Bodies" +### Community 18 - "Auth & Allowlist Test Bodies" +Cohesion: 0.08 +Nodes (22): PreviewProjectOptions, RED_PIXEL_PNG, reportClassHtml(), reportCss(), reportIndexHtml(), reportJs(), seedPreviewProject(), write() (+14 more) + +### Community 19 - "WebSocket Upstream Proxy" Cohesion: 0.13 -Nodes (20): body, body, body, body, absolute, refs, headers, received (+12 more) +Nodes (19): body, body, body, body, absolute, refs, headers, received (+11 more) -### Community 17 - "E2E Runtime & Resilience Specs" +### Community 20 - "Scope Pane & Demo Banner" Cohesion: 0.08 -Nodes (18): AdminApp(), ResolvedTheme, Theme, THEME_VALUES, ThemeProvider(), ThemeProviderContext, ThemeProviderProps, ThemeProviderState (+10 more) +Nodes (21): PreviewFrame, PreviewPane(), PreviewPaneProps, PreviewPlaceholderProps, Doc, fetchMock, firstKey, GUIDE (+13 more) -### Community 18 - "WebSocket Handler Factory" -Cohesion: 0.12 -Nodes (26): AdminRouteContext, log, createProjectArchive(), directorySizeBytes(), restoreProjectArchive(), runTar(), apiErrorResponse(), capacityErrorResponse() (+18 more) +### Community 21 - "Import URL Security Tests" +Cohesion: 0.1 +Nodes (6): AppStorage, createStorage(), ensureWorkspaceFiles(), nowIso(), projectPathFor(), randomId() -### Community 19 - "App Storage (SQLite)" +### Community 22 - "Sim Pane Switcher & Topbar" Cohesion: 0.08 -Nodes (30): audit_log table, runtime_config table, audit log test suite, recordAuditEvent (audit.ts, tested), Physical disk vs virtual device filtering for --device-read-bps, listWorkspaceDiskLimitDevices (containers/block-devices), container concurrency cap test suite, max-active-containers cap persisted via runtime_config, adoption/restart gating (+22 more) +Nodes (23): slugFromEmail(), emailArb, now, NUM_RUNS, slug, slugs, baBody, body (+15 more) -### Community 20 - "Docker Runtime Types & Ports" +### Community 23 - "Code Status Pill Component" Cohesion: 0.14 -Nodes (27): halsimWebSocketResponse(), HOP_BY_HOP_HEADERS, log, nt4AliveResponse(), nt4WebSocketResponse(), probeVscodeReady(), requestedProtocols(), stripHopByHopHeaders() (+19 more) +Nodes (24): AdminRouteContext, handleAdminRoute(), log, createProjectArchive(), directorySizeBytes(), restoreProjectArchive(), runTar(), apiErrorResponse() (+16 more) -### Community 21 - "E2E Workspace Fixtures" -Cohesion: 0.09 -Nodes (18): BINDING_GROUPS, Bumper(), ControlsPanelProps, DpadArm(), FaceButton(), GamepadVisualizer(), SmallButton(), StatusTone (+10 more) - -### Community 22 - "Import URL Security Tests" -Cohesion: 0.12 -Nodes (5): AppStorage, ensureWorkspaceFiles(), nowIso(), projectPathFor(), randomId() - -### Community 23 - "HALSim/NT4 WebSocket Responses" +### Community 24 - "Container Lifecycle Test Suite" Cohesion: 0.07 Nodes (27): Adding an entry, Admin API break-glass, Audit log, Between sessions, Checking and adjusting the cap at runtime, code:bash (docker compose exec control coderunner ), code:bash (# Remove all entries before a date), code:bash (# Overall system status: workspaces, container states, activ) (+19 more) -### Community 24 - "Docker Client & Lifecycle" +### Community 25 - "Admin Backup/Restore Test Suite" Cohesion: 0.09 Nodes (9): AppFixtures, seedWorkspaceProject(), WorkspacePage, status, wsp, Deps, dialog, openWorkspace() (+1 more) -### Community 25 - "Container Lifecycle Test Suite" -Cohesion: 0.09 -Nodes (21): first, NUM_RUNS, second, cloneCall, copyCall, importer, mock, samples (+13 more) - -### Community 26 - "Admin Backup/Restore Test Suite" +### Community 26 - "Better Auth Providers & Storage" Cohesion: 0.07 Nodes (26): code:bash (gcloud compute snapshots create coderunner-data-eoy2026 \), code:bash (gcloud compute snapshots delete coderunner-data-eoy2026), code:bash (gcloud compute snapshots describe coderunner-data-eoy2026 \), code:bash (gcloud compute instances delete coderunner --zone=northameri), code:bash (gcloud compute disks delete coderunner-data --zone=northamer), code:bash (# Only if you choose to release the IP), code:bash (terraform state rm google_compute_instance.coderunner google), code:hcl (resource "google_compute_disk" "data" {) (+18 more) -### Community 27 - "Admin Backup Archive Routes" +### Community 27 - "AdvantageScope Patch Application" Cohesion: 0.07 Nodes (26): Build times out, code:bash (# Confirm Docker is running), code:bash (SIM_PORT_RANGE=25810-25999), code:bash (# 1. Prune run logs (safest, often largest single contributo), code:bash (bun run backup), code:bash (docker compose restart control), code:bash (curl -H "Authorization: Bearer $ADMIN_TOKEN" \), code:bash (stat -c '%g' /var/run/docker.sock # e.g. 999) (+18 more) -### Community 28 - "Sim Pane Switcher & Topbar" +### Community 28 - "Allowlist Management E2E" Cohesion: 0.16 Nodes (13): e2e/fixtures/app.ts fixture (ControlApp harness), Commits 066141e / 1a4f5e6 (Vite base path revert), loginAs() / cookieHeader() auth fixture, Decision 015 — workspace routing / DS sync, Commit d111f70 (driver-station payload shape bug), e2e/fixtures/fake-halsim.ts, e2e/fixtures/fake-vscode.ts, Commit 158bab4 (hop-by-hop header stripping) (+5 more) -### Community 29 - "Better Auth Providers & Storage" -Cohesion: 0.09 -Nodes (25): afterFirst, afterSecond, aliceCookie, allowlistPath, baseOptions, boss, coach, cookie (+17 more) +### Community 29 - "Admin Backup Archive Routes" +Cohesion: 0.11 +Nodes (15): DemoBanner(), EditorPane(), EditorPaneProps, ScopePane, Topbar(), EditorReachability, EditorStatus, { result } (+7 more) + +### Community 30 - "Lesson Catalog Authoring & Gradle Cache" +Cohesion: 0.16 +Nodes (5): consumeLines(), lineLooksReady(), randomRunId(), runLogPath(), RunManager -### Community 30 - "AdvantageScope Patch Application" +### Community 31 - "Sim API & Chooser Announcements" Cohesion: 0.08 Nodes (25): addBody, adminCookie, after, aliceWorkspace, backedUpProject, backupsDir, bob, bobWorkspace (+17 more) -### Community 31 - "Lesson Catalog Authoring & Gradle Cache" +### Community 32 - "Deploy Files & WS Origin Guards" Cohesion: 0.08 Nodes (24): aliceWorkspace, bobWorkspace, byName, calls, config, cookie, expectedName, fakeDocker (+16 more) -### Community 32 - "Grafana Alloy & Cloudflare Deploy Config" -Cohesion: 0.11 -Nodes (13): BundledCatalogSource, CatalogManifest, CatalogSource, findModuleOrThrow(), log, parseCatalogRepo(), RemoteCatalogSource, sortByOrder() (+5 more) - -### Community 33 - "Run Lifecycle Test Fixtures" -Cohesion: 0.1 -Nodes (15): sendUpstreamWebSocketMessage(), log, WebSocketHandlerContext, upstreamEndpoints(), DEFAULT_PORT, GamepadLease, GamepadLeaseResolver, GamepadMessageOutcome (+7 more) - -### Community 34 - "Sim API & Chooser Announcements" -Cohesion: 0.11 -Nodes (16): seedRuntimeRunning(), cookie, frames, snap, status, cookie, snapshot, console (+8 more) - -### Community 35 - "Deploy Files & WS Origin Guards" -Cohesion: 0.11 -Nodes (19): SimPanePanels(), SimPanePanelsProps, SimPaneTab, SimPaneTabs(), SimPaneTabSelector(), SimPaneTabsProps, pathplannerTab, [scopePanel, pathplannerPanel] (+11 more) - -### Community 36 - "Audit Log & Break-Glass Admin" -Cohesion: 0.11 -Nodes (15): DemoBanner(), ScopePane, useGamepadChannel(), ScopeStatus, useScopeHandshake(), LoadState, heartbeatCalls, { result } (+7 more) +### Community 33 - "Grafana Alloy & Cloudflare Deploy Config" +Cohesion: 0.15 +Nodes (22): isOpenFileInsideRoot(), ASSET_CONTENT_TYPES, discoverDocuments(), documentCsp(), documentKindFor(), errorDocument(), log, documentShell() (+14 more) -### Community 37 - "Structured Logging" +### Community 34 - "Control Plane Config Loading" Cohesion: 0.14 Nodes (23): applyAdvantageScopePatches(), ascopeRoot, CommandResult, patchDir, patchFiles(), repoRoot, run(), ascopeLiteStatic (+15 more) -### Community 38 - "Control Plane Config Loading" -Cohesion: 0.1 -Nodes (7): cookie, firstBody, rows, runtime2, secondBody, states, MockWorkspaceRuntimeProvider +### Community 35 - "Public Health/OpenAPI E2E Specs" +Cohesion: 0.13 +Nodes (15): LayoutMenu(), Avatar(), UserMenuProps, DropdownMenu(), DropdownMenuCheckboxItem(), DropdownMenuContent(), DropdownMenuItem(), DropdownMenuLabel() (+7 more) -### Community 39 - "Import Pipeline Test Fixtures" -Cohesion: 0.12 -Nodes (16): CATALOG, fetchMock, { result }, useLessons(), UseLessonsReturn, INITIAL_STATE, ProjectSwapKind, ProjectSwapState (+8 more) +### Community 36 - "Structured Logging" +Cohesion: 0.11 +Nodes (15): RunCommandFactory, announceChooser(), body, controlled, cookie, encodeMsgPack(), fakeDocker, FakeWebSocket (+7 more) -### Community 40 - "Admin App Allowlist Page" +### Community 37 - "Admin App Allowlist Page" Cohesion: 0.08 Nodes (22): aliceConnection, aliceMessages, aliceRun, aliceRunId, aliceWorkspace, baseOptions, bobConnection, bobMessages (+14 more) -### Community 41 - "Allowlist Core Module" -Cohesion: 0.11 -Nodes (15): RunCommandFactory, announceChooser(), body, controlled, cookie, encodeMsgPack(), fakeDocker, FakeWebSocket (+7 more) - -### Community 42 - "Admin Route Dispatch & Security Headers" +### Community 38 - "Allowlist Core Module" Cohesion: 0.08 Nodes (23): 1. Choose the new version, 1. Clone and configure, 2. Pull the new images and restart the control plane, 2. Start and verify, 3. Rebuild student workspaces, 3. Sign in and allow students, 4. Verify the update, code:bash (git clone https://github.com/mathewdunne/CodeRunner.git Code) (+15 more) -### Community 43 - "Self-Inspection (Container Auto-Detect)" -Cohesion: 0.11 -Nodes (24): collectFiles, deployFilesSnapshotResponse, parseDeployFilePath, isAllowedWebSocketOrigin, requireWebSocketOrigin, halsimWebSocketResponse, nt4AliveResponse, nt4WebSocketResponse (+16 more) - -### Community 44 - "Public Health/OpenAPI E2E Specs" +### Community 39 - "Self-Inspection (Container Auto-Detect)" Cohesion: 0.1 Nodes (24): ADMIN_TOKEN admin API break-glass, data/allowlist.json, data/app.db (SQLite), Audit log system, bun run audit:prune, Backups doc, Capacity and Sizing doc, CODE_DISK_READ_LIMIT env var (+16 more) -### Community 45 - "Gamepad Input Mapping" +### Community 40 - "CI/Test Command Reference" +Cohesion: 0.12 +Nodes (14): seedRuntimeRunning(), snapshot, cookie, frames, snap, status, cookie, snapshot (+6 more) + +### Community 41 - "Audit Log & Runtime Config Schema" Cohesion: 0.13 Nodes (22): ANSI, colorize(), configureLogging(), createJsonSink(), createSink(), formatAttrs(), formatAttrValue(), formatRecord() (+14 more) -### Community 46 - "Audit Log & Runtime Config Schema" +### Community 42 - "E2E Workspace Fixtures" +Cohesion: 0.09 +Nodes (19): AuditLogEntry, adminCookie, body, fakeDocker, rows, student, studentCookie, workspace (+11 more) + +### Community 43 - "Gamepad Input Mapping" Cohesion: 0.09 Nodes (20): bobCookie, body, clearedCache, cloneCall, cookie, copyCall, ctx, docker (+12 more) -### Community 47 - "PathPlanner Integration Deploy Pipeline" +### Community 44 - "PathPlanner Integration Deploy Pipeline" Cohesion: 0.11 Nodes (23): AdminApp, AdminLayout, Tab (type), Allowlist (admin page), fetchAllowlist, App (default export), FallbackRedirect, RootIndex (+15 more) -### Community 48 - "Gamepad Lease Management" +### Community 45 - "Gamepad Lease Management" Cohesion: 0.11 Nodes (23): BACKEND_ORIGIN Pages secret, Cloudflare Offline Page, Pages Function catch-all [[path]].ts, deploy/cloudflare/wrangler.toml, coderunner-ops.json dashboard, Deployment Overview, CodeRunner Docs Overview, c4-standard-4 GCE VM (+15 more) -### Community 50 - "Keyboard Input Mapping" -Cohesion: 0.15 -Nodes (19): defaultContainerUser(), defaultDataDir, defaultHalsimPortRange, defaultSimPortRange, defaultVscodePortRange, DISABLED_DISK_LIMIT_VALUES, envContainerUser(), loadControlConfig() (+11 more) +### Community 46 - "E2E Workspace Fixtures" +Cohesion: 0.1 +Nodes (19): ADMIN_GET_ROUTES, adminCookie, cookie, req, studentCookie, cookieFrom(), login(), randomToken() (+11 more) -### Community 51 - "ControlApp Test Harness Socket Types" -Cohesion: 0.14 -Nodes (18): DEFAULT_NETWORKS, deriveComposeProject(), deriveContainerUser(), deriveHostDataDir(), deriveNetwork(), inspectFailureMessage(), notDerived(), readComposeProject() (+10 more) +### Community 47 - "Run Lifecycle Test Fixtures" +Cohesion: 0.11 +Nodes (10): BundledCatalogSource, findModuleOrThrow(), parseCatalogRepo(), RemoteCatalogSource, sortByOrder(), config, { fetch: fetchImpl }, { fetch: fetchImpl, calls } (+2 more) -### Community 52 - "Architecture & Access Control Docs" +### Community 48 - "Container Lease Cleanup CLI" Cohesion: 0.09 Nodes (21): `bun run e2e`: Playwright mocked tier, `bun run e2e:security`: security E2E tests, `bun run e2e:workspace-java`: real Java workspace smoke, `bun run test`: control-plane unit and integration tests, `bun run test:web`: frontend unit and component tests, `bun run verify`: full CI gate, code:bash (bunx playwright install chromium), code:bash (bun run test) (+13 more) -### Community 53 - "Auto Panel & Gamepad Channel" +### Community 49 - "Backup Database CLI" +Cohesion: 0.11 +Nodes (22): container_leases table (V1 core schema), run_jobs table (V1 core schema), sessions table (V1 core schema), users table (V1 core schema), workspaces table (V1 core schema), idx_container_leases_lsp_port_unique, idx_container_leases_sim_port_unique, container_leases.lsp_state column (+14 more) + +### Community 50 - "PathPlanner Deploy File Schemas" +Cohesion: 0.15 +Nodes (22): DEPLOY_FILES_WRITE_ROOT constant, deployFilePathSchema, driverStationPatchSchema, gamepadClientMessageSchema, gamepadServerMessageSchema, gamepadStateSchema, importRequestSchema, packages/contracts/src/index.ts (+14 more) + +### Community 51 - "Gamepad/Driver Station Zod Schemas" Cohesion: 0.13 Nodes (12): AppOptions, defaultRunCommandFactory, startFakeHalsim(), startFakeVscode(), AppFixture, FakeHalsimHandle, FakeVscodeHandle, SeededUser (+4 more) -### Community 54 - "HALSim Bridge State Application" +### Community 52 - "ControlApp Test Harness Socket Types" Cohesion: 0.14 Nodes (14): GamepadFrame, GamepadInfo, listConnectedGamepads(), makeLabel(), FakePad, { result }, useGamepad(), UseGamepadResult (+6 more) -### Community 55 - "Deploy Files Security Test Suite" +### Community 53 - "Docker Client & Lifecycle" +Cohesion: 0.11 +Nodes (15): SimPanePanels(), SimPanePanelsProps, SimPaneTab, SimPaneTabs(), SimPaneTabSelector(), SimPaneTabsProps, onPreviewActivated, pathplannerTab (+7 more) + +### Community 54 - "Architecture & Access Control Docs" +Cohesion: 0.16 +Nodes (19): defaultContainerUser(), defaultDataDir, defaultHalsimPortRange, defaultSimPortRange, defaultVscodePortRange, DISABLED_DISK_LIMIT_VALUES, envContainerUser(), loadControlConfig() (+11 more) + +### Community 55 - "Auto Panel & Gamepad Channel" +Cohesion: 0.1 +Nodes (18): aliceCookie, bobCookie, byPath, cookie, docker, encoded, listed, names (+10 more) + +### Community 56 - "HALSim Bridge State Application" +Cohesion: 0.1 +Nodes (20): afterFirst, afterSecond, aliceCookie, allowlistPath, baseOptions, boss, coach, cookie (+12 more) + +### Community 57 - "HALSim/NT4 WebSocket Responses" +Cohesion: 0.11 +Nodes (20): audit_log table, runtime_config table, audit log test suite, recordAuditEvent (audit.ts, tested), container concurrency cap test suite, max-active-containers cap persisted via runtime_config, adoption/restart gating, CapacityExceededError, ensureCodeContainer (containers) (+12 more) + +### Community 58 - "Asset Upload & Manifest" +Cohesion: 0.14 +Nodes (17): DEFAULT_NETWORKS, deriveComposeProject(), deriveContainerUser(), deriveHostDataDir(), deriveNetwork(), inspectFailureMessage(), notDerived(), readComposeProject() (+9 more) + +### Community 59 - "Deploy Files Security Test Suite" Cohesion: 0.1 Nodes (20): 002 — AdvantageScope Lite hosted standalone, 1. `spawnSync(npmCmd, [...], { shell: false })` returns exit `null` on Windows, 2. AS Lite ships a `lite/static/` directory inside the submodule with `index.html` and `popups.css`, 3. Git symlinks under `lite/static/` checked out as 9-byte text files on Windows, 4. AS Lite expects `GET /assets` and `GET /assets//` server routes, 5. AS submodule's `postinstall` is heavy, 6. AS upstream prints `npm audit` warnings about transitive vulns, AdvantageScope as a git submodule pinned to a release tag (+12 more) -### Community 56 - "Asset Upload & Manifest" +### Community 60 - "Run Manager Command Factory" Cohesion: 0.11 Nodes (21): Admin role + break-glass token, Architecture doc, Audit log, Cloudflare Pages Function ([[path]].ts), CODERUNNER_ADMIN_EMAIL bootstrap, Control plane container privileges (non-root, socket access), Decision 031: Containerized Control Plane (referenced), Demo mode (+13 more) -### Community 57 - "Workspace Container Bind-Mount & Memory Bounds" +### Community 61 - "Import Pipeline Test Fixtures" +Cohesion: 0.13 +Nodes (21): Authoring Lesson Modules doc, Bundled lesson catalog (catalog/), catalog/modules/robot-starter license files, github.com/mathewdunne/coderunner-lessons, Google API Services User Data Policy, non-destructive Gradle headless override (strips GUI, enables WS server), hello-world module, LESSONS_CATALOG_REPO env var (+13 more) + +### Community 62 - "Path Planner Pane.test" +Cohesion: 0.11 +Nodes (16): DEFAULT_PORT, GamepadLease, GamepadLeaseResolver, GamepadMessageOutcome, GamepadStatus, log, SessionState, HalSimBridgeUnavailableError (+8 more) + +### Community 63 - "Container Capacity & V2 Acceptance Decisions" +Cohesion: 0.15 +Nodes (16): parseDockerStatsLine(), parsePercent(), cleanupStoppedContainers(), countRunningContainers(), log, managedContainerStats(), removeCodeContainer(), removeCodeVolume() (+8 more) + +### Community 64 - "Contracts Property-Based Tests" Cohesion: 0.15 Nodes (14): clearContainerLeases(), dbPathFromEnv(), DockerCommandResult, DockerRunner, main(), parseContainerNames(), rebuildWorkspaces(), RebuildWorkspacesOptions (+6 more) -### Community 58 - "Java Tooling Smoke Test" +### Community 65 - "Admin Allowlist Endpoints" Cohesion: 0.1 Nodes (19): 1. Add the origin A record, 2. Verify the Caddyfile has the origin vhost, 3. Bootstrap the Cloudflare Pages project, 4. Set `BACKEND_ORIGIN` as a Pages secret, 5. Add the custom domain in Cloudflare, 6. Add GitHub Actions variables, Cloudflare Offline Page, code:block1 (student browser ──443──> Cloudflare Pages (coderunner)) (+11 more) -### Community 59 - "Contracts Property-Based Tests" +### Community 66 - "AdvantageScope Lite & Docusaurus" Cohesion: 0.1 Nodes (19): 001 — Sim container architecture, 1. Missing `WPILibNewCommands.json` vendor dep, 2. Dockerfile `|| true` swallowed gradle build failure, 3. Image size came in at 2.25 GB, not the 1.0–1.4 GB plan estimate, 4. Wrapper-zip warning is cosmetic, Context, Decisions, `eclipse-temurin:17-jdk-jammy` base image (+11 more) -### Community 60 - "Admin Allowlist Endpoints" +### Community 67 - "Workspace Container Bind-Mount & Memory Bounds" +Cohesion: 0.13 +Nodes (20): coderunner-workspace bind-mount contract, coderunner-workspace conservative JVM/Gradle memory bounds, V2 code container (coderunner-workspace) README, Decision 015 (HALSim control protocol), Decision 016 (imported-project sim compat), Decision 016: Imported Project Simulation Compatibility, Decision 025 (two-phase sim runner), Decision 025: Detach Sim JVM from Gradle (+12 more) + +### Community 68 - "Slug/Login Property Tests" +Cohesion: 0.12 +Nodes (20): containers/lsp/bridge/bridge.ts, Bun-native WebSocket-to-stdio Bridge, container_leases.lsp_state column split, Generic ContainerOrchestrator, apps/web/src/java-lsp.ts, Eclipse JDT LS, Decision 008: V1 LSP Container and Bun-native Bridge, apps/control/src/app.ts (+12 more) + +### Community 69 - "Scripts" +Cohesion: 0.13 +Nodes (20): AdminPage (page object), apps/control/src/app.ts, apps/control/src/app/assets.ts, CLI Reference Doc, coderunner dispatching CLI, containers/control/entrypoint.sh, apps/control/src/config.ts (ControlConfig), Configuration Reference (+12 more) + +### Community 70 - "Mock Workspace Runtime Provider" Cohesion: 0.16 Nodes (16): digitalAxis(), dpadToPov(), gamepadStateToVisualizerFrame(), isMappedKeyboardCode(), KEYBOARD_BINDINGS, KeyboardBindingGroup, keyboardCodesToWpilib(), MAPPED_CODES (+8 more) -### Community 61 - "AdvantageScope Lite & Docusaurus" +### Community 71 - "WebSocket Router Test Suite" Cohesion: 0.11 Nodes (18): Authentication, Capturing a session, code:block1 (14:23:01.482 INFO [control.runs] run started workspaceId=a), code:json ({"timestamp":"2026-05-21T14:23:01.482Z","level":"info","cate), code:bash (docker compose logs -f control | tee coderunner-$(date +%Y%m), code:bash (# Manual probe), code:bash (curl http://localhost:4000/healthz), code:json ({"ok":true,"service":"control","version":"v2-3"}) (+10 more) -### Community 62 - "CI/Test Command Reference" +### Community 72 - "Deploy Files Path Safety" Cohesion: 0.11 Nodes (18): Capacity and Sizing, Checking disk usage, Cleaning up, code:bash (# Tighter cap for a memory-constrained host (expect slower c), code:block2 (available_for_containers = total_RAM - 4 GB), code:bash (# Per-container CPU and memory (one-shot)), code:bash (# Overall free space), code:bash (bun run docker:cleanup) (+10 more) -### Community 63 - "Gamepad/Driver Station Zod Schemas" -Cohesion: 0.15 -Nodes (19): Authoring Lesson Modules doc, Bundled lesson catalog (catalog/), catalog/modules/robot-starter license files, github.com/mathewdunne/coderunner-lessons, Google API Services User Data Policy, hello-world module, LESSONS_CATALOG_REPO env var, Lessons-are-gitless design principle (+11 more) +### Community 73 - "Java Tooling & Test Tiers" +Cohesion: 0.12 +Nodes (19): Decision 036: VSCodium Web Migration, Decision 037: Gradle Wrapper Alias and Extension Pins, Decision 038: Java Tooling Compatibility and Smoke, bun run e2e (Playwright Mocked Tier), bun run e2e:security (Security E2E Tests), bun run e2e:workspace-java (Real Java Workspace Smoke), bun run test (Control-Plane Unit/Integration Tests), bun run test:web (Frontend Unit/Component Tests) (+11 more) -### Community 64 - "Mock Workspace Runtime Provider" -Cohesion: 0.18 -Nodes (19): driverStationPatchSchema, gamepadClientMessageSchema, gamepadServerMessageSchema, gamepadStateSchema, importRequestSchema, packages/contracts/src/index.ts, packages/contracts/src/index.test.ts, isWorkspaceSlug() (+11 more) +### Community 74 - "VSCodium Editor Container Defaults" +Cohesion: 0.11 +Nodes (16): bobCookie, body, cookie, docker, evilDir, evilPath, example, newFile (+8 more) -### Community 65 - "Scope Pane & Demo Banner" +### Community 75 - "Contracts Schema Unit Tests" Cohesion: 0.21 Nodes (3): defaultSnapshot(), HalSimBridge, upstreamUrlFor() -### Community 66 - "HALSim Bridge Message Handling" -Cohesion: 0.11 -Nodes (16): bobCookie, body, cookie, docker, evilDir, evilPath, example, newFile (+8 more) - -### Community 67 - "Audit/Reconciliation Test Suite" +### Community 76 - "Import Manager (Git Clone Staging)" Cohesion: 0.11 Nodes (17): Authoring Lesson Modules, code:text (modules.json ← the catalog manifest (required, ), code:json ({), code:json ({), Example curriculum, Module fields, `plain-java`, Publishing (+9 more) -### Community 68 - "WebSocket Router Test Suite" +### Community 77 - "Demo Mode Session Seeding" Cohesion: 0.11 Nodes (17): Auto-import on Tab (additionalTextEdits), code:dockerfile (FROM gitpod/openvscode-server:1.105.1), code:bash (cd /tmp/frc-spike-openvscode), Container boots and serves (:3000), Ctrl-click into library source (jdt:// URI), Decision 011: V2 Editor Spike — openvscode-server with redhat.java and WPILib, Decisions, Docker Hub vs GitHub Releases (+9 more) -### Community 69 - "Deploy Files Path Safety" +### Community 78 - "Backup/Restore & Catalog Import Flows" Cohesion: 0.11 Nodes (17): 003 — Minimal web shell, 1. Headless preview screenshot hangs when AS Lite iframe is loading, 2. Orphaned Vite child after `TaskStop` on the npm wrapper, 3. `WARNING:StorageManager: settings timeout, using defaults` in the console, 4. AS Lite tab-controls panel was clipped (initial layout), AS Lite via iframe to `http://localhost:8080`, not bundled, code:block1 ("editor scope"), Context (+9 more) -### Community 70 - "Run Manager Command Factory" +### Community 79 - "Auth Client & Docker Runner Helpers" Cohesion: 0.11 Nodes (17): 006 - Multi-tenancy spike findings, code:text (alice alive 200 ok), code:bash (npm run spike:multi -- up), code:text (alice: websocket open=true, initialized=true, diagnostics=0), code:text (open=true, initialized=true), code:text (GET /file?user=alice -> 200), code:text (status building at 1 ms), code:text (sim container created in 0.29s) (+9 more) -### Community 71 - "Java LSP Bridge (Archived)" -Cohesion: 0.15 -Nodes (18): BundledCatalogSource (catalog.ts), CatalogSource interface, createCatalogSource (catalog.ts), parseCatalogRepo (catalog.ts), RemoteCatalogSource (catalog.ts), Two-source lesson catalog pattern (bundled + remote), ImportError (imports.ts), sendUpstreamWebSocketMessage (+10 more) +### Community 80 - "HALSim Test Suite" +Cohesion: 0.14 +Nodes (18): createApp (app.ts), Physical disk vs virtual device filtering for --device-read-bps, listWorkspaceDiskLimitDevices (containers/block-devices), BundledCatalogSource (catalog.ts), CatalogSource interface, createCatalogSource (catalog.ts), parseCatalogRepo (catalog.ts), RemoteCatalogSource (catalog.ts) (+10 more) -### Community 72 - "VSCodium Editor Container Defaults" +### Community 81 - "Bundled Lesson Catalog Modules" Cohesion: 0.18 Nodes (15): docker(), DockerResult, migratedSettings, openJavaFile(), repoRoot, startWorkspace(), stopWorkspace(), terminal (+7 more) -### Community 73 - "Contracts Schema Unit Tests" +### Community 82 - "Fake NT4 Test Server" Cohesion: 0.12 Nodes (16): first, NUM_RUNS, parsed, round, slugArb, allianceStationSchema, bridgeConnectionSchema, containerStateSchema (+8 more) -### Community 74 - "Import Manager (Git Clone Staging)" +### Community 83 - "HALSim Bridge Message Handling" Cohesion: 0.12 -Nodes (15): AuditLogEntry, adminCookie, body, fakeDocker, rows, student, studentCookie, workspace (+7 more) +Nodes (15): bashCalls, cloneCall, ctx, importer, mock, row, workspace, CatalogLoadContext (+7 more) -### Community 75 - "Demo Mode Session Seeding" +### Community 84 - "Database Migrations Runner" Cohesion: 0.12 Nodes (16): Alloy config location, code:block1 (https://prometheus-prod-XX-prod-us-central-0.grafana.net/api), code:block2 (https://logs-prod-XXX.grafana.net/loki/api/v1/push), code:bash (echo -n 'https://prometheus-prod-XX-prod-us-central-0.grafan), code:bash (gcloud compute ssh coderunner --zone=us-central1-a --tunnel-), code:bash (cd /opt/coderunner), code:logql (# All logs from one student), code:promql (up{instance="coderunner"}) (+8 more) -### Community 76 - "Backup/Restore & Catalog Import Flows" +### Community 85 - "Asset Upload Test Suite" Cohesion: 0.12 Nodes (16): Browser API behavior, code:text (Browser -> stateless HTTP API -> control-plane HALSim bridge), code:json ({ "type": "", "device": "", "data": {), Context, Decision, Decision 015 — HALSim WebSocket Control Protocol, DriverStation message (`type: "DriverStation"`, `device: ""`), Future hooks (+8 more) -### Community 77 - "Auth Client & Docker Runner Helpers" +### Community 86 - "Capacity Cap Test Suite" Cohesion: 0.12 Nodes (16): Code style and CI gates, code:bash (bun install), code:bash (bun run dev:control), code:bash (bun run dev:control -- --demo), code:bash (bun run dev:web), code:bash (bun run migrate # apply all pending migrations), code:bash (bun run check:fix), code:bash (bun run verify) (+8 more) -### Community 78 - "Container Capacity & V2 Acceptance Decisions" +### Community 87 - "Disk Read Limit Config" Cohesion: 0.12 Nodes (16): Bind mounts, Build, code:bash (bun run docker:build:workspace), code:block2 (frc-sim.managed=true), code:bash (docker run -d \), Environment variables, Example run, First-Run Behavior (+8 more) -### Community 79 - "Dist Download Utility" -Cohesion: 0.14 -Nodes (17): Admin allowlist endpoints, addAllowlistEntry (auth/allowlist), isEmailAllowed (auth/allowlist), loadAllowlist (auth/allowlist), reloadAllowlist, removeAllowlistEntry, saveAllowlist, Reload allowlist before each sign-in check (+9 more) - -### Community 80 - "PathPlanner Deploy File Schemas" -Cohesion: 0.14 -Nodes (17): Baseline response security headers, Batched docker inspect, Capacity admission on container adoption, Container isolation (memory/disk/port caps), Container ports (3000/3300/5810), Decision 015: HALSim WebSocket Control Protocol, Decision 018: Gamepad Input via HALSim WebSocket, Decision 019: Keyboard Input Mode (+9 more) +### Community 88 - "Community 88" +Cohesion: 0.15 +Nodes (17): Container isolation (memory/disk/port caps), Container ports (3000/3300/5810), Decision 015: HALSim WebSocket Control Protocol, Decision 018: Gamepad Input via HALSim WebSocket, Decision 019: Keyboard Input Mode, Decision 020: Workspace Runtime Provider Boundary, control service (docker-compose.yml), coderunner Docker network (+9 more) -### Community 81 - "Bundled Lesson Catalog Modules" +### Community 89 - "AdvantageScope Lite Hosting (Archived)" Cohesion: 0.12 Nodes (16): AdvantageKit, patches/advantagescope/ source-level patches, AdvantageScope (upstream), 001-lite-nt4-endpoint-injection.patch, AS Lite in-iframe timeout banner, Docusaurus, GitHub CLI, Licenses doc (+8 more) -### Community 82 - "Fake NT4 Test Server" +### Community 90 - "Driver Station Page Object & Runtime Seeding" +Cohesion: 0.15 +Nodes (17): bun run e2e:workspace-java (real Java workspace smoke), Code container VS Code defaults test suite, containers/code/Dockerfile, ghcr.io/mathewdunne/coderunner-workspace image, codium-server (VSCodium reh-web), Decision 033: workspace disk read limit, scripts/image.ts, containers/code/root/.../init-frc-setup/run (+9 more) + +### Community 91 - "Rebuild Workspaces" +Cohesion: 0.15 +Nodes (13): test (Playwright base.extend AppFixtures), e2e/fixtures/app.ts (ControlApp test fixture), E2E_TEST=1 gates Better Auth testUtils plugin, e2e/fixtures/fake-halsim.ts, e2e/fixtures/fake-vscode.ts, e2e/global-setup.ts, MockWorkspaceRuntimeProvider, e2e/fixtures/runtime.ts (+5 more) + +### Community 92 - "Canonical Image Naming Decisions" Cohesion: 0.13 Nodes (16): Biome (lint/format/import org), bun run e2e (Playwright mocked tier), bun run e2e:security, bun run test (control-plane unit/integration), bun run test:web (Vitest), bun run verify (CI gate), apps/control/src/config.ts migrations resolution, --demo / CODERUNNER_DEMO_MODE flag (+8 more) -### Community 83 - "Code Status Pill Component" +### Community 93 - "Audit Log & Break-Glass Admin" +Cohesion: 0.13 +Nodes (12): CATALOG, fetchMock, { result }, useLessons(), UseLessonsReturn, INITIAL_STATE, ProjectSwapKind, ProjectSwapState (+4 more) + +### Community 94 - "Container Runtime Status Helpers" +Cohesion: 0.23 +Nodes (15): isInsideDirectory(), collectFiles(), deployFileDeleteResponse(), deployFilesSnapshotResponse(), deployFileWriteResponse(), DeployWorkspace, findDeepestExistingAncestor(), HAS_PROC_SELF_FD (+7 more) + +### Community 95 - "Network Mode Config Test" Cohesion: 0.14 Nodes (13): BridgeEntry, DEFAULT_DRIVER_STATION, DriverStationState, HalSimBridgeOptions, HalSimBridgeSnapshot, HalSimMessage, HalSimWebSocketFactory, JoystickWireState (+5 more) -### Community 84 - "Database Migrations Runner" -Cohesion: 0.21 -Nodes (15): collectFiles(), deployFileDeleteResponse(), deployFilesSnapshotResponse(), deployFileWriteResponse(), DeployWorkspace, findDeepestExistingAncestor(), HAS_PROC_SELF_FD, INVALID_PATH_ERROR (+7 more) - -### Community 85 - "Asset Upload Test Suite" +### Community 97 - "Rebuild Workspaces CLI Args" Cohesion: 0.12 Nodes (15): Backup options, Backups, code:bash (docker compose exec control coderunner backup), code:bash (bun run backup), code:block3 (data/backups/2026-05-16-151038/), code:bash (# Write the backup to a custom location), code:bash (bun run restore -- ), code:bash (# Preview what would be restored without writing anything) (+7 more) -### Community 86 - "Capacity Cap Test Suite" +### Community 98 - "Users CLI Script" Cohesion: 0.12 Nodes (15): Admin and Metrics, Auth and OAuth, code:bash (bun run start -- --demo), Configuration Reference, Demo mode and the `--demo` flag, Docker and Containers, Docker Compose deployment, How environment is loaded (+7 more) -### Community 87 - "Disk Read Limit Config" -Cohesion: 0.17 -Nodes (16): applySecurityHeaders, authorizeMetrics, constantTimeEqual, dispatch, fetch (request handler), handleUploadAsset, userAssetsPath, webShellResponse (+8 more) +### Community 99 - "Extension Reconciliation Test" +Cohesion: 0.12 +Nodes (15): Can CodeRunner run offline or without internet?, Can I build or start simulation from the WPILib extension?, Can I write my own lessons?, Can students accidentally break each other's work?, Can students push code to GitHub?, Do students need accounts? What if I just want to try it?, FAQ, How do students read their README or a test report? (+7 more) + +### Community 100 - "Sim Api.test" +Cohesion: 0.12 +Nodes (16): demo mode test suite, GATED_PATHS list, PUBLIC_PATHS list, Default-deny auth coverage via explicit route manifest (no route table introspection), default-deny route coverage test suite (Plan §A.7.6), DEMO_SLUG (auth/demo), PathPlanner subtree writable, choreo subtree read-only design, safeRelativeAssetPath (asset path validation) (+8 more) -### Community 88 - "Canonical Image Naming Decisions" +### Community 101 - "AdvantageScope Verify Script" Cohesion: 0.13 Nodes (16): makeScriptedRunCommandFactory, consumeLines, defaultRunCommandFactory, dockerRunScript, lineLooksReady, RunManager (app.runs), Startup marks persisted active runs as stopped, run lifecycle and log streaming test suite (+8 more) -### Community 89 - "AdvantageScope Lite Hosting (Archived)" -Cohesion: 0.16 -Nodes (16): containers/lsp/bridge/bridge.ts, Bun-native WebSocket-to-stdio Bridge, container_leases.lsp_state column split, Generic ContainerOrchestrator, apps/web/src/java-lsp.ts, Eclipse JDT LS, Decision 008: V1 LSP Container and Bun-native Bridge, apps/control/src/app.ts (+8 more) +### Community 102 - "README" +Cohesion: 0.13 +Nodes (16): isEmailAllowed (auth/allowlist), createAuth, slugFromEmail, login, buildSocialProviders, getEnabledAuthProviders, routing and shell APIs test suite, RunJob (type) (+8 more) -### Community 90 - "CLI Reference & Control Container" +### Community 103 - "Container Isolation & HALSim Decisions" Cohesion: 0.13 Nodes (14): authProvidersResponseSchema, autoChooserPatchSchema, autoChoosersResponseSchema, deployFilePathSchema, deployFilesSnapshotResponseSchema, gamepadClientMessageSchema, gamepadServerMessageSchema, importResponseSchema (+6 more) -### Community 91 - "IDE Layout Resizable Panes" +### Community 104 - "Cloudflare Pages Proxy Function" Cohesion: 0.16 Nodes (6): FakeSocket, Listener, { rerender }, { result }, { unmount }, useRunChannel() -### Community 93 - "Network Mode Config Test" +### Community 106 - "Proxy Header Test Suite" Cohesion: 0.13 -Nodes (13): AppSocket, waitFor(), CloseCall, fakeDocker, FakeSocket, fakeUpstream, forwarded, hello (+5 more) +Nodes (14): Admin role, Audit log, Authentication, code:json ({), Container isolation, Control plane container privileges, Demo mode, Email allowlist (+6 more) -### Community 94 - "Rebuild Workspaces CLI Args" +### Community 107 - "Container Lease Schema (V1/V2)" Cohesion: 0.13 Nodes (14): 10. Third-party software, 11. Changes to these terms, 12. Governing law, 13. Contact, 1. What CodeRunner is, 2. Who may use it, 3. Acceptable use, 4. Your code (+6 more) -### Community 95 - "Users CLI Script" -Cohesion: 0.13 -Nodes (14): Can CodeRunner run offline or without internet?, Can I build or start simulation from the WPILib extension?, Can I write my own lessons?, Can students accidentally break each other's work?, Can students push code to GitHub?, Do students need accounts? What if I just want to try it?, FAQ, How much does cloud hosting cost? (+6 more) - -### Community 96 - "Extension Reconciliation Test" +### Community 108 - "Core Schema Tables & Better Auth Migration" Cohesion: 0.13 Nodes (14): 031 — Containerized Control Plane, Addendum — non-root control container, Admin bootstrap: `CODERUNNER_ADMIN_EMAIL`, Bind-mount path translation, code:yaml (user: "${CODERUNNER_UID:-1000}:${CODERUNNER_GID:-1000}"), `coderunner` — a dispatching CLI, not raw scripts, Consequences, Context (+6 more) -### Community 97 - "AdvantageScope Verify Script" +### Community 109 - "WebSocket Upstream Message Send" Cohesion: 0.19 Nodes (15): /admin/workspaces/:id/backup route (manual per-workspace backup), S9-S11 command-injection defense tests, MockWorkspaceRuntimeProvider, admin backup/restore workspace project, Bundled/remote catalog lesson load flow (gitless), GitHub team import flow (all-branches, depth-1, keeps .git), ImportManager, ImportRateLimiter (+7 more) -### Community 98 - "Gamepad Session Safety Disables" +### Community 110 - "Deployment Hardware & FAQ Docs" Cohesion: 0.13 Nodes (15): authClient (better-auth), defaultDockerRunner, inspectContainerOrThrow, configureLogging, getLogger, loadProviders, LoginPage Component, OAuthButton Component (+7 more) -### Community 99 - "Slug/Login Property Tests" +### Community 111 - "E2E ControlApp Test Fixture Setup" Cohesion: 0.2 Nodes (15): MAX_ACTIVE_CONTAINERS capacity limit, Container labels + reconciliation, Decision 011: V2 Editor Spike, Decision 012: V2 Code Image, Decision 013: V2 Acceptance Pass, Decision 017: linuxserver Base Migration, First-run init behavior, gitpod/openvscode-server base image (+7 more) -### Community 100 - "Container Isolation & HALSim Decisions" -Cohesion: 0.15 -Nodes (15): bun run e2e:workspace-java (real Java workspace smoke), deploy-cloudflare job in deploy.yml, ghcr.io/mathewdunne/coderunner-workspace image, deploy.yml GitHub Actions workflow, GCE docker compose stack (control/caddy/alloy), PATHPLANNER_DIST_TAG pin, coderunner rebuild-workspaces CLI, release.yml GitHub Actions workflow (+7 more) +### Community 112 - "AS Lite NT4 Endpoint Injection Patch" +Cohesion: 0.14 +Nodes (13): compact, console_, disable, editor, editorBody, editorControl, freshConsole, hideEditor (+5 more) + +### Community 113 - "Editor Pane Reachability" +Cohesion: 0.2 +Nodes (7): IDELayout(), IDELayoutProps, FakeResizeObserver, useSplit(), ResizableHandle(), ResizablePanel(), ResizablePanelGroup() + +### Community 114 - "Driver Station Switch Project Flow" +Cohesion: 0.14 +Nodes (12): waitFor(), CloseCall, fakeDocker, FakeSocket, fakeUpstream, forwarded, hello, importWs (+4 more) -### Community 101 - "Cloudflare Pages Proxy Function" +### Community 115 - "Driver Station Switch Project Flow" +Cohesion: 0.16 +Nodes (9): sendUpstreamWebSocketMessage(), AppSocket, log, WebSocketHandlerContext, CatalogManifest, CatalogSource, log, ImportError (+1 more) + +### Community 116 - "Admin Workspace Backup/Restore Actions" Cohesion: 0.25 Nodes (10): DistDownload, downloadAndExtract(), run(), withScratch(), artifacts, main(), repoRoot, tagArgIndex (+2 more) -### Community 102 - "Project Swap Fake Socket Test" +### Community 117 - "Auto Choosers & Container Status Hooks" Cohesion: 0.14 -Nodes (13): Admin role, Audit log, Authentication, code:json ({), Container isolation, Control plane container privileges, Demo mode, Email allowlist (+5 more) +Nodes (13): 1. Prove the report delivery model, 2. Add contracts and read-only delivery, 3. Add Preview UI, 4. Validate the complete workflow, 5. Document and finish, Acceptance criteria, Existing integration points, Goal and agreed UX (+5 more) -### Community 103 - "Proxy Header Test Suite" +### Community 118 - "Java Tooling Compatibility Decisions" Cohesion: 0.14 Nodes (13): Building the image locally, code:bash (bun run docker:build:workspace), code:block2 (docker build -f containers/code/Dockerfile -t ghcr.io/mathew), code:bash (bun run docker:pull:workspace), code:bash (bun run docker:build:workspace), code:bash (bun run docker:rebuild-workspaces), code:bash (bun run docker:rebuild-workspaces -- --dry-run), Editor acceptance smoke (+5 more) -### Community 104 - "Container Lease Schema (V1/V2)" -Cohesion: 0.19 -Nodes (14): Classroom-density memory defaults, coderunner-workspace bind-mount contract, coderunner-workspace conservative JVM/Gradle memory bounds, V2 code container (coderunner-workspace) README, Decision 024: Container Memory Budget, Decision 025 (two-phase sim runner), Decision 025: Detach Sim JVM from Gradle, Decision 037 (Gradle daemon settings vs java.import.gradle.*) (+6 more) - -### Community 105 - "Core Schema Tables & Better Auth Migration" +### Community 119 - "Lessons & Testing Decision Docs" Cohesion: 0.2 Nodes (13): hello-world bundled module, robot-starter bundled module, catalog/modules.json, docker() helper (Bun.spawn wrapper), openJavaFile(), java-tooling.spec.ts (docker smoke test), startWorkspace(), waitFor() polling helper (+5 more) -### Community 106 - "WebSocket Upstream Message Send" +### Community 120 - "Editor Migration Decisions" Cohesion: 0.17 Nodes (11): FakeNt4Handle, FakeNt4Options, startFakeNt4(), aliceMessages, aliceReady, aliceSocket, allAlice, allBob (+3 more) -### Community 107 - "Deployment Hardware & FAQ Docs" +### Community 121 - "CLI Reference & Control Container" +Cohesion: 0.21 +Nodes (9): CodeStatus, CodeStatusPill(), TONES, codeStatusFromRun(), ConsoleLine(), ConsolePanel(), ConsolePanelProps, parseConsoleLine() (+1 more) + +### Community 122 - "Gamepad Shim E2E Test" Cohesion: 0.28 Nodes (11): config, db, log, AppliedMigrationRow, applyMigrations(), ensureMigrationTable(), listAppliedMigrations(), loadMigrations() (+3 more) -### Community 108 - "E2E ControlApp Test Fixture Setup" +### Community 123 - "Catalog Integrity Test" Cohesion: 0.15 Nodes (11): assetDir, assetsDir, config, cookie, formData, manifest, proc, userAssetDir (+3 more) -### Community 109 - "Driver Station Page Object & Runtime Seeding" +### Community 124 - "Driver Station Page Object" Cohesion: 0.17 Nodes (12): adminCookie, alice, aliceCookie, bob, bobCookie, body, carol, fakeContainerFor() (+4 more) -### Community 110 - "AS Lite NT4 Endpoint Injection Patch" +### Community 125 - "Fake Socket Test Double" Cohesion: 0.15 Nodes (12): 100 MB size cap, 6 imports per hour rate limit, Backup before import, Clone inside the container, not the host, Consequences, Context, Decision, Decision 016 — Project Import Strategy (+4 more) -### Community 111 - "Editor Pane Reachability" +### Community 126 - "Session Hook & Heartbeat" Cohesion: 0.15 Nodes (12): 036 — Editor migration: openvscode-server → VSCodium reh-web, 1. VSCodium `reh-web`, not `code-server`, 2. Stay on the LinuxServer base image, 3. The editor keeps container port 3000, 4. Settings and extension paths do not move, 5. Workspace trust: diagnosed, not fixed, Consequences, Context (+4 more) -### Community 112 - "Gamepad Session Lifecycle Methods" +### Community 127 - "Auto Choosers Hook Test" Cohesion: 0.15 Nodes (12): 009 - LSP reconnect, bridge serialization, and startup throttling, Context, Decision 1: Browser LSP client auto-reconnects with bounded backoff, Decision 2: Bridge serializes JDT LS spawns, Decision 3: Orchestrator-level LSP startup throttle, Decision 4: Cap proxy pending-message buffers, Decision 5: NT4 subprotocol mismatch is fail-fast, not silent, Decision 6: AS Lite in-iframe timeout banner (+4 more) -### Community 113 - "Admin Workspace Backup/Restore Actions" +### Community 128 - "Command Injection Test Suite" +Cohesion: 0.18 +Nodes (13): workspaces.current_module column, Admin allowlist endpoints, addAllowlistEntry (auth/allowlist), loadAllowlist (auth/allowlist), reloadAllowlist, removeAllowlistEntry, saveAllowlist, CODERUNNER_ADMIN_EMAIL bootstrap admin design (+5 more) + +### Community 129 - "Default-Deny Route Coverage" Cohesion: 0.19 Nodes (13): Archive 005: Java LSP MVP integration, Archive 006: Multi-tenancy spike findings, Archive 007: V1 sim container orchestration, Decision 032: Canonical Image Naming, Decision Logs README index, Docker labels as runtime source of truth, SQLite as cache, Dual-mode runtime provider (port mode / network mode), Local WPILib-aware JDT LS image (frc-lsp:mvp) (+5 more) -### Community 114 - "Auto Choosers & Container Status Hooks" +### Community 130 - "Driver Station Page Object & Runtime Seeding" +Cohesion: 0.17 +Nodes (13): deploy-cloudflare job in deploy.yml, Decision 039: PathPlanner integration, Deploy-files API (/u/:slug/api/deploy-files/...), deploy.yml GitHub Actions workflow, GCE docker compose stack (control/caddy/alloy), PATHPLANNER_DIST_TAG pin, coderunner rebuild-workspaces CLI, release.yml GitHub Actions workflow (+5 more) + +### Community 131 - "Demo Mode Disk/Memory Limits" Cohesion: 0.19 Nodes (13): Decision 038: PathPlanner integration, deployFileDeleteResponse(), deployFileWriteResponse(), Deploy-files contracts (zod schemas), deployFilePathSchema path traversal safety design, deployFilesSnapshotResponse(), apps/control/src/app/deploy-files.ts, apps/control/src/metrics.ts (templateRoute) (+5 more) -### Community 115 - "Java Tooling Compatibility Decisions" -Cohesion: 0.23 -Nodes (6): IDELayout(), IDELayoutProps, FakeResizeObserver, ResizableHandle(), ResizablePanel(), ResizablePanelGroup() +### Community 132 - "Img Screenshots" +Cohesion: 0.19 +Nodes (13): ascope-iframe.spec.ts (T34.1 /scope route test), 001-lite-nt4-endpoint-injection.patch, embedded-mode NT4 endpoint injection mechanism, patches/advantagescope/README.md, Decision 013: per-workspace NT4 isolation, nt4-multi-workspace.spec.ts (T35.1 NT4 isolation test), pane-layout.spec.ts (pane sizing persistence test), openWorkspace() test helper (+5 more) -### Community 116 - "Demo Mode Disk/Memory Limits" +### Community 133 - "Build Failure.spec" Cohesion: 0.18 Nodes (8): SimRunAction, patchCall, postCall, { result }, updatedStatus, VALID_STATUS, useSimulationState(), UseSimulationStateReturn -### Community 117 - "Editor Migration Decisions" -Cohesion: 0.17 -Nodes (9): HalSimBridgeUnavailableError, { bridge }, { bridge, socket }, disable, joystick, messages, outcome, sessions (+1 more) +### Community 134 - "Admin.po" +Cohesion: 0.26 +Nodes (11): ascopeRoot, assert(), createCatalogDir(), distDir, exists(), patchDir, repoRoot, runGit() (+3 more) -### Community 118 - "Playwright Fixture Bootstrap Helpers" +### Community 135 - "Java Tooling Smoke Test" Cohesion: 0.17 Nodes (11): config, cookie, dataDir, fakeDocker, lease, name, now, passthrough (+3 more) -### Community 119 - "Gamepad Shim E2E Test" +### Community 136 - "Ws Bridge" Cohesion: 0.17 Nodes (9): args, argsPath, home, projectRoot, robotJar, sleeper, start, stop (+1 more) -### Community 120 - "Driver Station Page Object" +### Community 137 - "Main" Cohesion: 0.32 Nodes (11): Args, dirExists(), discoverWorkspaces(), fileExists(), main(), parseArgs(), restoreArchive(), restoreDb() (+3 more) -### Community 121 - "Catalog Integrity Test" +### Community 138 - "Logging.test" Cohesion: 0.18 Nodes (9): ExtensionRecord, fixture(), old, reconcileScript, record(), repoRoot, staleDirectory, temporaryRoots (+1 more) -### Community 122 - "Fake Socket Test Double" -Cohesion: 0.26 -Nodes (11): ascopeRoot, assert(), createCatalogDir(), distDir, exists(), patchDir, repoRoot, runGit() (+3 more) - -### Community 123 - "Session Hook & Heartbeat" +### Community 139 - "Metadata" Cohesion: 0.17 Nodes (12): code:bash (curl -sS https://raw.githubusercontent.com/VSCodium/vscodium), code:markdown (| [openvscode-server](https://github.com/gitpod-io/openvscod), code:markdown (| [VSCodium](https://github.com/VSCodium/vscodium) / Code – ), code:markdown (- **`linuxserver/vscodium-web` container image** — GNU Gener), code:markdown (Merged per-student container for V2. Combines VSCodium reh-w), code:markdown (| Base image | linuxserver/vscodium-web:1.126.04524-ls35 | G), code:markdown (| 3000 | codium-server (HTTP + WebSocket) — overrides the ba), code:markdown (The container uses s6-overlay for process supervision. The u) (+4 more) -### Community 124 - "Auto Choosers Hook Test" +### Community 140 - "Middleware" Cohesion: 0.17 Nodes (12): code:typescript (test("s6 service script launches codium-server as primary pr), code:typescript (* In-process HTTP+WS server that impersonates the workspace ), code:bash (git add containers/code apps/control/src/__tests__ apps/cont), code:bash (cd /home/matt/dev/CodeRunner), code:bash (rm -rf containers/code/root/etc/s6-overlay/s6-rc.d/svc-openv), code:bash (find containers/code/root -type f | sort), code:block5 (containers/code/root/etc/s6-overlay/s6-rc.d/init-frc-setup/d), code:dockerfile (# V2 merged code container) (+4 more) -### Community 125 - "HALSim Test Suite" +### Community 141 - "Image" Cohesion: 0.17 -Nodes (11): Changes to this policy, Children's privacy, Contact, How long it is kept, How the information is used, Privacy Policy, What information is collected, What is not done with it (+3 more) +Nodes (11): Architecture, code:text (Browser (one student)), code:text (data/), How a Run works, How PathPlanner files flow, How Preview reads documents, How telemetry flows, Persistence and data layout (+3 more) -### Community 126 - "Backup Database CLI" +### Community 142 - "023 Metrics And Observability" Cohesion: 0.17 -Nodes (11): Automated Verification, code:block1 (bun run measure), code:bash (bun run typecheck), Comparison with V1, Decision, Decision 013: V2 Acceptance Pass, Host Capacity (10 students), Manual Verification (+3 more) +Nodes (11): Changes to this policy, Children's privacy, Contact, How long it is kept, How the information is used, Privacy Policy, What information is collected, What is not done with it (+3 more) -### Community 127 - "Command Injection Test Suite" +### Community 143 - "User Data" Cohesion: 0.17 -Nodes (12): createApp (app.ts), containers.ts barrel module, Control plane restart rediscovers labeled containers (reconciliation), seedDemoUser, createAdvantageScopeDist, createCatalogDir, createPathPlannerDist, createWebDist (+4 more) - -### Community 128 - "Default-Deny Route Coverage" -Cohesion: 0.24 -Nodes (12): isInsideDirectory, pathplannerResponse, readScopeAssetManifest, safeRelativeAssetPath, scopeResponse, staticFileResponse, webAssetResponse, deployFileDeleteResponse (+4 more) - -### Community 129 - "Lessons & Testing Decision Docs" -Cohesion: 0.2 -Nodes (12): run_jobs table (V1 core schema), sessions table (V1 core schema), users table (V1 core schema), workspaces table (V1 core schema), Better Auth migration: drop pre-OAuth auth model, run_jobs_new table (Better Auth rebuild), workspaces_new table (Better Auth rebuild), workspaces.current_module column (+4 more) +Nodes (11): Automated Verification, code:block1 (bun run measure), code:bash (bun run typecheck), Comparison with V1, Decision, Decision 013: V2 Acceptance Pass, Host Capacity (10 students), Manual Verification (+3 more) -### Community 130 - "Build Failure.spec" -Cohesion: 0.18 -Nodes (12): slugFromEmail, login, routing and shell APIs test suite, RunJob (type), slugFromEmail property tests (P4-P6), AppStorage, ensureWorkspaceFiles, ensureWorkspaceForUser (slug collision suffixing) (+4 more) +### Community 144 - "Ws Proxy.spec" +Cohesion: 0.21 +Nodes (12): GamepadSessions (gamepad.ts), Gamepad release/close safety-disables joystick and driver station, GamepadSessions test suite, HalSimBridge (halsim.ts), HalSimBridgeUnavailableError (halsim.ts), HalSimBridge test suite, createFakeDocker, dockerInspect (fixture builder) (+4 more) -### Community 131 - "Admin.po" +### Community 145 - "024 Container Memory Budget" Cohesion: 0.18 Nodes (12): Backup/restore flow removal, Bundled vs remote lesson catalog, Decision 016: Project Import Strategy, Decision 021: Testing Suite Implementation, Decision 022: Skip Docker Smoke and Import Tests, Decision 029: Lessons and Modules (referenced), Decision 038: Workspace Java Smoke (referenced), Docker smoke tier (skipped, broad) (+4 more) -### Community 132 - "Path Planner Pane.test" +### Community 146 - "Halsim.test" Cohesion: 0.17 Nodes (12): AdvantageScope git submodule pinned to release tag, Archive 001: Sim container architecture, Archive 002: AdvantageScope Lite hosted standalone, Archive 003: Minimal web shell, AS Lite GET /assets and /assets// routes, AS Lite window.location.hostname NT4 auto-detection, AS Lite embedded via iframe, not bundled/proxied, eclipse-temurin:17-jdk-jammy base image choice (+4 more) -### Community 133 - "Ws Bridge" +### Community 147 - "Gamepad.test" Cohesion: 0.24 Nodes (8): Env, isProxiedPath(), onRequest(), PagesFunctionContext, serviceUnavailable(), TOP_LEVEL_PROXIED, assetRequests, proxiedRequests -### Community 134 - "Main" +### Community 148 - "Audit Prune" Cohesion: 0.25 Nodes (3): FakeSocket, Listener, { result } -### Community 135 - "Logging.test" -Cohesion: 0.24 -Nodes (9): IconRail(), IconRailButton(), IconRailButtonProps, IconRailProps, RailTab, Tooltip(), TooltipContent(), TooltipProvider() (+1 more) +### Community 149 - "Users" +Cohesion: 0.22 +Nodes (9): PaneVisibility, readVisibility(), restored, { result }, { result, rerender }, { result, unmount }, UpperPanes, usePaneVisibility() (+1 more) -### Community 136 - "Middleware" +### Community 150 - "Helpers" Cohesion: 0.2 Nodes (8): missing(), aliceCookie, dockerRunner(), fakeDocker, receivedHeaders, result, source, startedAt -### Community 137 - "Metadata" -Cohesion: 0.22 -Nodes (8): removeCodeContainer(), removeCodeVolume(), stopWorkspaceSim(), codeContainerName(), codeVolumeName(), code container orchestration test suite, container reconciliation test suite, s6 svc-vscodium-web run script (codium-server launcher) +### Community 151 - "Card" +Cohesion: 0.2 +Nodes (7): ListBlockDevicesOptions, listWorkspaceDiskLimitDevices(), log, IdleManagerOptions, log, getLogger(), devices -### Community 138 - "Image" +### Community 152 - "Catalog.test" Cohesion: 0.18 Nodes (10): code:bash (git clone https://github.com/mathewdunne/CodeRunner coderunn), code:bash (cd website && bun install && bun run start), code:bash (bun run docs:dev), code:bash (bun run dev:control # Bun control plane on :4000 with --wa), code:bash (bun run verify # typecheck + Bun tests + Vitest + Pla), CodeRunner, Development, Documentation (+2 more) -### Community 139 - "023 Metrics And Observability" +### Community 153 - "Block Devices" Cohesion: 0.18 Nodes (10): Build, code:bash (yarn), code:bash (yarn start), code:bash (yarn build), code:bash (USE_SSH=true yarn deploy), code:bash (GIT_USER= yarn deploy), Deployment, Installation (+2 more) -### Community 140 - "User Data" +### Community 154 - "Ws Bridge" Cohesion: 0.18 Nodes (10): code:bash (git clone https://github.com/mathewdunne/CodeRunner coderunn), code:bash (CODERUNNER_DOCKER_GID=$(stat -c '%g' /var/run/docker.sock) C), code:bash (CODERUNNER_DEMO_MODE=1 docker compose up), code:powershell ($env:CODERUNNER_DEMO_MODE = "1"; docker compose up), code:bat (set "CODERUNNER_DEMO_MODE=1" && docker compose up), code:bash (docker compose down --remove-orphans), Deploy for a team, Prerequisites (+2 more) -### Community 141 - "Ws Proxy.spec" +### Community 155 - "Dropdown Menu" Cohesion: 0.18 -Nodes (10): Capacity limit, Classroom-density memory defaults, code:block1 (frc-sim.managed=true), Container labels, Container ports, First-run behavior, How student data persists, Idle auto-stop (+2 more) +Nodes (11): code:bash (# Seed Gradle cache on first run.), code:bash (# Fix ownership for /config and /workspace (linuxserver conv), code:bash (# Fix ownership of what this script created as root. The bas), code:bash (git add containers/code/root/etc/s6-overlay/s6-rc.d/init-frc), code:bash (git add ), code:bash (bun run docs:build && grep -rl "vscodium-web-migration" webs), code:bash (git add docs/decisions/036-vscodium-web-migration.md docs/de), File Structure (+3 more) -### Community 142 - "Halsim.test" +### Community 156 - "020 Workspace Runtime Provider" Cohesion: 0.18 -Nodes (10): Architecture, code:text (Browser (one student)), code:text (data/), How a Run works, How PathPlanner files flow, How telemetry flows, Persistence and data layout, The single front door (+2 more) +Nodes (10): Capacity limit, Classroom-density memory defaults, code:block1 (frc-sim.managed=true), Container labels, Container ports, First-run behavior, How student data persists, Idle auto-stop (+2 more) -### Community 143 - "Gamepad.test" +### Community 157 - "030 Control Plane Hardening Pass" Cohesion: 0.18 Nodes (10): Backup and Restore, Build, CLI Reference, Containerized ops: the `coderunner` CLI, Database, Docker Images and Containers, Docs Site, Quality and Tests (+2 more) -### Community 144 - "Audit Prune" +### Community 158 - "Auth" Cohesion: 0.18 Nodes (10): code:block1 (https://github.com//), Constraints and limits, How to do an import, Importing a Team Project, Pushing and pulling after import, Running an imported project, Switching away discards the workspace, What a team import is for (+2 more) -### Community 145 - "Users" +### Community 159 - "2026 08 30 Pathplanner Integration" Cohesion: 0.18 Nodes (10): 017 — Migrate code container to linuxserver/openvscode-server, Bind mount target changes from /home/frc to /config, Context, Decisions, Layered s6-overlay: additive, not replacement, Migration Notes, Runtime PUID/PGID instead of build-time --user, Sim scripts remain independent of s6 (+2 more) -### Community 146 - "README" -Cohesion: 0.2 -Nodes (11): container_leases table (V1 core schema), idx_container_leases_lsp_port_unique, idx_container_leases_sim_port_unique, container_leases.lsp_state column, container_leases.code_state column, V2 merged openvscode-server + sim image design, container_leases.vscode_container column, container_leases.vscode_port column (+3 more) +### Community 160 - "Xss.spec" +Cohesion: 0.22 +Nodes (11): sendUpstreamWebSocketMessage, PROXY_PENDING_LIMIT, SocketData union type, createWebSocketHandlers, openProxyUpstream, resolveGamepadLease, Characterization-test-before-refactor pattern, websocket message router characterization tests (+3 more) -### Community 147 - "Helpers" +### Community 161 - "Build Ascope Lite" Cohesion: 0.2 Nodes (11): CODE_DISK_READ_LIMIT (--device-read-bps cap), CODE_MEMORY_LIMIT raised to 4096m, /config as named volume in demo mode, Decision 028: Demo mode for zero-config local tryout, Decision 033: Workspace Disk Read Limit, Decision 034: Demo mode portability on Docker Desktop, 2026-07-07 disk-thrash production incident, group_add default CODERUNNER_DOCKER_GID=0 (+3 more) -### Community 148 - "Catalog.test" +### Community 162 - "Index" Cohesion: 0.18 Nodes (11): disk.tf (Terraform disk config), Hardware sizing guidance for team, FAQ, Why first build/run is slow, Offline capability of CodeRunner, Boot disk disposable, data disk precious, Seasonal Teardown, Manual data-disk snapshot (+3 more) -### Community 149 - "Block Devices" -Cohesion: 0.18 -Nodes (11): AdvantageScope Lite NT4 client, e2e/fixtures/app.ts (ControlApp test fixture), E2E_TEST=1 gates Better Auth testUtils plugin, e2e/fixtures/fake-halsim.ts, e2e/fixtures/fake-nt4.ts, e2e/global-setup.ts, NT4 wire protocol: MessagePack binary + JSON text, Always rebuild web bundle before E2E (stale dist worse than none) (+3 more) - -### Community 150 - "Ws Bridge" +### Community 163 - "Allowlist Management E2E" Cohesion: 0.18 Nodes (8): ControlApp in-process fixture / createApp(), Decision 018 — gamepad unplug safety, Decision 019 — keyboard focus, Decision 022 — skip Docker smoke / import tests, e2e/fixtures/fake-nt4.ts, Commit 95f450d (auto chooser stale fix), Commit cb9fea6 (gamepad selection persistence + no-lease), e2e/fixtures/gamepad-shim.ts -### Community 152 - "030 Control Plane Hardening Pass" +### Community 165 - "Frc Robot" Cohesion: 0.22 Nodes (8): SimButton(), SimButtonProps, SimControlsBlock(), SimControlsBlockProps, onRestart, onStart, onStop, TONE_CLASSES -### Community 153 - "Auth" +### Community 166 - "Code Container Defaults.test" Cohesion: 0.24 Nodes (8): AllianceToggle(), AllianceToggleProps, Side, SIDE_ACTIVE, SIDE_EDGE, SIDE_STATION, sideOf(), onSelect -### Community 154 - "2026 08 30 Pathplanner Integration" -Cohesion: 0.24 -Nodes (6): EditorPane(), EditorPaneProps, EditorReachability, EditorStatus, { result }, useEditorReachability() +### Community 167 - "Java LSP Bridge (Archived)" +Cohesion: 0.31 +Nodes (3): GamepadSessions, halsimTarget(), resolveLease() + +### Community 168 - "2026 08 30 Pathplanner Integration" +Cohesion: 0.2 +Nodes (9): Choosing a document, If a report looks wrong, Plain-Java lessons, Reading Documents (Preview), Refresh, Refreshing a document, Supported files, Things it deliberately does not do (+1 more) -### Community 155 - "Xss.spec" +### Community 169 - "Restore" Cohesion: 0.2 Nodes (9): 037 — Workspace cache aliases, extension pins, and trust, 1. Share the primed Gradle distribution with WPILib projects, 2. Prevent gallery resolution from replacing pinned VSIXs, 3. Disable workspace trust in the hosted workbench, 4. Keep Gradle daemon limits out of editor build arguments, 5. Close the workspace-image build follow-ups, Browser-owned settings finding, code:text (permwrapper -> wrapper) (+1 more) -### Community 156 - "Build Ascope Lite" +### Community 170 - "Login.po" Cohesion: 0.2 Nodes (9): 1. Why not Better Auth `testUtils`, 2. Auth-callback path tests are deferred, 3. Browser-heavy specs use `test.fixme`, not deletion, 4. HTTP-driven specs preferred over DOM-driven specs where possible, 5. Decisions on smaller details, Decision 021: Testing suite implementation — deviations from TESTING-PLAN.md, Files Touched, Future Work (Deferred) (+1 more) -### Community 157 - "Index" +### Community 171 - "Default Deny.test" Cohesion: 0.2 Nodes (9): Decision 018: Gamepad Input via HALSim WebSocket, Files Touched, Future Work (Deferred), Safety: disable on disconnect, Summary, Why a dedicated WebSocket from browser to control plane, Why a single controller on port 0 for v1, Why HALSim WS, not the FRC DS UDP protocol (+1 more) -### Community 158 - "Backup" +### Community 172 - "Auth Demo.test" Cohesion: 0.2 Nodes (9): Alternatives considered, App-side stays vendor-neutral, code:json ({"timestamp":"2026-05-21T14:23:01.482Z","level":"info","cate), Consequences, Context, Decision, Decision 027: Ship control-plane logs to Grafana Cloud Loki, Label cardinality (+1 more) -### Community 159 - "Rebuild Workspaces" +### Community 173 - "Robot" Cohesion: 0.2 Nodes (9): Base image: `gitpod/openvscode-server:1.105.1`, Consequences, Context, Decision, Decision 012: V2 Code Image — Base Image and Extension Strategy, Direct launch base path handling, Extension cache seeding pattern, Extensions: download at build time (+1 more) -### Community 160 - "Img Screenshots" +### Community 174 - "Cleanup Containers" Cohesion: 0.2 Nodes (9): 030 — Control-Plane Hardening Pass, Consequences, Context, Correctness fixes, Decision, Performance, Security, Status (+1 more) -### Community 161 - "Use Container Status.test" +### Community 175 - "Config" Cohesion: 0.2 Nodes (9): 004 - Backend wiring for save and run, Context, Custom WebSocket sender, no new dependency, Decisions, Host backend plus Docker CLI, Minimal endpoints and run protocol, One-command dev stack without Docker Compose, Replaceable sim process inside long-lived container (+1 more) -### Community 162 - "Code Container Defaults.test" +### Community 176 - "Use Gamepad" Cohesion: 0.2 Nodes (9): 007 - V1 sim container orchestration, code:text (frc-sim.managed=true), Context, Decisions, Docker labels are adopted back into SQLite, Lazy ensure, visible status, Loopback-only published ports, Runtime cache seed (+1 more) -### Community 163 - "Frc Robot" +### Community 177 - "016 Project Import Strategy" Cohesion: 0.2 Nodes (9): 008 - V1 LSP container and Bun-native bridge, Browser LSP client extended for multi-file projects, Bun-native bridge instead of `vscode-ws-jsonrpc`, code:block1 (data/users//project -> /workspace/project), `container_leases` lease state split, Context, Decisions, Generic `ContainerOrchestrator` (+1 more) -### Community 164 - "2026 08 30 Pathplanner Integration" -Cohesion: 0.24 -Nodes (10): container_leases.halsim_port column, container_leases_new table (Better Auth rebuild), HalSimBridge (halsim.ts), HalSimBridge test suite, createFakeDocker, dockerInspect (fixture builder), Nt4AutoChooserBridge, Superseded run must not clobber newer run's status (+2 more) - -### Community 165 - "Restore" +### Community 178 - "[[path]]" Cohesion: 0.2 Nodes (10): readError, useAutoChoosers test suite, useAutoChoosers, useContainerStatus test suite, useContainerStatus, probeEditor, useEditorReachability test suite, useEditorReachability (+2 more) -### Community 166 - "Login.po" -Cohesion: 0.2 -Nodes (10): code-server --abs-proxy-base-path (rejected), Decision 011: V2 editor spike, Decision 017: LinuxServer base migration, Decision 026: Editor Default Theme, Decision 036: Editor migration openvscode-server to VSCodium reh-web, Machine settings.json theme seeding, --user-data-dir ignored by codium-server (post-review correction), VSCodium reh-web chosen over code-server (+2 more) - -### Community 167 - "Default Deny.test" +### Community 179 - "Clean" Cohesion: 0.22 Nodes (10): Decision 037: Workspace cache aliases, extension pins, and trust, Decision 038: Java tooling compatibility, extension reconciliation, and real-image smoke, --disable-workspace-trust server flag, --do-not-include-pack-dependencies flag, Separate tooling (JDT, Java 21) and project (Java 17) JDKs, bun run e2e:workspace-java real-container smoke, Managed extension reconciliation on every container start, Gradle daemon JVM args rejected by Tooling API (+2 more) -### Community 168 - "Auth Demo.test" +### Community 180 - "Robot Container" +Cohesion: 0.2 +Nodes (10): code-server --abs-proxy-base-path (rejected), Decision 011: V2 editor spike, Decision 017: LinuxServer base migration, Decision 026: Editor Default Theme, Decision 036: Editor migration openvscode-server to VSCodium reh-web, Machine settings.json theme seeding, --user-data-dir ignored by codium-server (post-review correction), VSCodium reh-web chosen over code-server (+2 more) + +### Community 181 - "Dist Download Utility" Cohesion: 0.31 Nodes (10): Grafana Alloy config template (config.alloy.tmpl), Alloy log pipeline bounded active-series design, bootstrap.sh first-boot provisioning script, Caddyfile (GCE deployment), Decision 031 (containerized control plane), GCE cloud-init user-data.yaml, deploy/ directory README, docker-compose.prod.yml (Caddy/Alloy overlay) (+2 more) -### Community 169 - "Robot" +### Community 182 - "Cloudflare Pages Function.test" +Cohesion: 0.2 +Nodes (9): DriverStationPage (page object), e2e/fixtures/gamepad-shim.ts, installGamepadShim(), e2e/fixtures/runtime.ts, seedWorkspaceProject(), Switch Project picker (auto-opens on empty workspace), Using CodeRunner, Using CodeRunner: PathPlanner section (+1 more) + +### Community 183 - "PathPlanner Integration" Cohesion: 0.22 -Nodes (6): test (Playwright base.extend AppFixtures), e2e/fixtures/fake-vscode.ts, MockWorkspaceRuntimeProvider, e2e/fixtures/runtime.ts, seedRuntimeRunning(), startFakeVscode() +Nodes (10): Decision 039: PathPlanner Integration, Decision 042: Collapsible Workspace Panes, createAdvantageScopeDist / createPathPlannerDist Throwaway Dists, apps/control/src/__tests__/helpers.ts, AdvantageScope Tab, Choreo Files Read-Only Visibility, User Menu Layout Control, PathPlanner Deploy Path (src/main/deploy/pathplanner) (+2 more) -### Community 170 - "Cleanup Containers" +### Community 184 - "Logging" Cohesion: 0.31 Nodes (5): connectGamepad(), installGamepadShim(), setGamepadAxes(), consoleErrors, leaseErrors -### Community 171 - "Config" +### Community 185 - "Metrics.test" Cohesion: 0.22 Nodes (7): catalog, catalogRoot, ids, manifestPath, repoRoot, subdirPath, lessonCatalogSchema -### Community 172 - "Use Gamepad" +### Community 186 - "004 Backend Wiring" Cohesion: 0.25 Nodes (6): patchCall, { result }, updatedResponse, VALID_RESPONSE, useAutoChoosers(), UseAutoChoosersReturn -### Community 173 - "016 Project Import Strategy" +### Community 187 - "Store" +Cohesion: 0.25 +Nodes (6): LoadState, heartbeatCalls, { result }, { unmount }, VALID_SESSION, useSession() + +### Community 188 - "Dist Download" Cohesion: 0.39 Nodes (8): Args, backupDatabase(), dirExists(), fileExists(), main(), parseArgs(), runTar(), timestamp() -### Community 174 - "035 Multi Arch Images And Workflow Split" +### Community 189 - "Img Screenshots" Cohesion: 0.22 Nodes (8): bridge, enabledDisable, flush, joystick, messages, msg, sockets, zeroButtons -### Community 175 - "[[path]]" -Cohesion: 0.22 -Nodes (8): bashCalls, cloneCall, ctx, importer, mock, row, workspace, GithubImportContext +### Community 190 - "Audit/Reconciliation Test Suite" +Cohesion: 0.25 +Nodes (7): first, NUM_RUNS, second, cases, expectRejected(), result, parseGitHubUrl() -### Community 176 - "" -Cohesion: 0.31 -Nodes (7): configMountType(), containerAttachedToNetwork(), containerHasPublishedPorts(), isLoopbackHost(), publishedPortFor(), PublishedPort, WorkspaceRow +### Community 191 - "Dialog" +Cohesion: 0.36 +Nodes (8): defaultDockerRunner(), dockerError(), dockerPortBindError(), inspectContainer(), inspectContainerOrThrow(), inspectContainers(), runDocker(), runDockerCli() -### Community 177 - "Robot Container" +### Community 192 - "Main" Cohesion: 0.22 -Nodes (8): 038 — Java tooling compatibility, extension reconciliation, and real-image smoke, Add a targeted real-container smoke, Consequences, Context, Decision, Keep separate tooling and project JDKs, Pin a compatible Java extension matrix, Reconcile managed extensions on every container start +Nodes (8): Arrange your workspace, Console lessons, Explore more, Get started, PathPlanner, Preview, Robot lessons and imported projects, Using CodeRunner -### Community 178 - "Clean" +### Community 193 - "Main" Cohesion: 0.22 -Nodes (8): Alternatives considered, Auth, Cardinality discipline, Consequences, Context, Decision, Decision 023: Metrics and observability via Prometheus + Grafana Cloud, Status +Nodes (8): 038 — Java tooling compatibility, extension reconciliation, and real-image smoke, Add a targeted real-container smoke, Consequences, Context, Decision, Keep separate tooling and project JDKs, Pin a compatible Java extension matrix, Reconcile managed extensions on every container start -### Community 179 - "Logging" +### Community 194 - "Typecheck" Cohesion: 0.22 -Nodes (9): GATED_PATHS list, PUBLIC_PATHS list, Default-deny auth coverage via explicit route manifest (no route table introspection), default-deny route coverage test suite (Plan §A.7.6), PathPlanner subtree writable, choreo subtree read-only design, safeRelativeAssetPath (asset path validation), Symlink and path-traversal defense for deploy-files write/delete, deploy-files snapshot/write/delete test suite (+1 more) +Nodes (8): Alternatives considered, Auth, Cardinality discipline, Consequences, Context, Decision, Decision 023: Metrics and observability via Prometheus + Grafana Cloud, Status -### Community 180 - "Metrics.test" +### Community 195 - "Project Preview & Right Pane" Cohesion: 0.22 -Nodes (9): Decision 015 (HALSim control protocol), Decision 016 (imported-project sim compat), Decision 016: Imported Project Simulation Compatibility, libstdc++6 PPA upgrade (GLIBCXX_3.4.32), robot-starter build.gradle, robot-starter headless sim configuration (no GUI/DS extensions), robot-starter settings.gradle, sim-headless.init.gradle init script (+1 more) +Nodes (9): Decision 041: Project Preview, Driver Station Auto Tab, Console Lessons, Console Tab, Driver Station Controls Tab, Driver Station, build/reports/** Generated Documents, README.md Auto-Open in Preview (+1 more) -### Community 181 - "Store" -Cohesion: 0.28 -Nodes (9): coderunner dispatching CLI, containers/control/entrypoint.sh, Configuration Reference, Quick Start (Installation), Troubleshooting Guide, CODERUNNER_DOCKER_GID socket permission fix, OAuth login failure troubleshooting, Port range exhausted troubleshooting (+1 more) - -### Community 182 - "004 Backend Wiring" -Cohesion: 0.31 -Nodes (9): Code container VS Code defaults test suite, containers/code/Dockerfile, codium-server (VSCodium reh-web), Decision 033: workspace disk read limit, containers/code/root/.../init-frc-setup/run, robot-starter .vscode/settings.json, svc-vscodium-web/run service script, Why codium-server over code-server: --server-base-path support (+1 more) +### Community 196 - "Global Setup" +Cohesion: 0.25 +Nodes (6): cookie, firstBody, rows, runtime2, secondBody, states -### Community 183 - "Dist Download" -Cohesion: 0.28 -Nodes (9): ascope-iframe.spec.ts (T34.1 /scope route test), 001-lite-nt4-endpoint-injection.patch, embedded-mode NT4 endpoint injection mechanism, patches/advantagescope/README.md, Decision 013: per-workspace NT4 isolation, nt4-multi-workspace.spec.ts (T35.1 NT4 isolation test), pane-layout.spec.ts (pane sizing persistence test), "mocked" Playwright project (+1 more) +### Community 198 - "Sidebars" +Cohesion: 0.29 +Nodes (6): PathPlannerPane, PathPlannerPaneProps, first, frame, { rerender }, second -### Community 185 - "Main" +### Community 199 - "Web App Shell & Theming" Cohesion: 0.29 Nodes (6): BigButton(), BigButtonProps, EnableDisableRow(), EnableDisableRowProps, onSetEnabled, TONE_ACTIVE -### Community 186 - "Main" -Cohesion: 0.29 -Nodes (6): PathPlannerPane, PathPlannerPaneProps, first, frame, { rerender }, second +### Community 200 - "Constants" +Cohesion: 0.25 +Nodes (7): banner, configInput, demoFlag, log, port, server, enableDefaultMetrics() -### Community 187 - "Typecheck" +### Community 201 - "Vite.config" Cohesion: 0.29 Nodes (6): defaultLogFormat(), parseLogFormatEnv(), cyclic, err, line, parsed -### Community 188 - "Sim Api.test" +### Community 202 - "Audit/Reconciliation Test Suite" Cohesion: 0.25 -Nodes (7): banner, configInput, demoFlag, log, port, server, enableDefaultMetrics() +Nodes (6): cloneCall, copyCall, importer, mock, samples, workspace -### Community 189 - "Switch Project Dialog" +### Community 203 - "Icon Rail" Cohesion: 0.25 Nodes (7): CodeRunner, How to use CodeRunner, Lessons and team projects, Next steps, Self-hosted and modest to run, Video walkthrough, What students get -### Community 190 - "Dialog" +### Community 204 - "Dropdown Menu" +Cohesion: 0.25 +Nodes (7): 041 — Project Preview (Markdown + generated HTML reports), Alternatives rejected, Consequences, Context, Decision, Tested compatibility boundary, The blocking constraint we found first + +### Community 205 - "Scroll Area" Cohesion: 0.25 Nodes (7): Broad Docker smoke tier — not implemented, Consequences, Context, Decision, Decision 022: Skip Docker smoke tier and import/backup-restore E2E tests, Import and backup/restore tests — updated after lessons rework, Status -### Community 191 - "024 Container Memory Budget" +### Community 206 - "Use Gamepad Channel" Cohesion: 0.25 Nodes (7): 005 - Java LSP MVP integration, Context, Decisions, Local WPILib-aware JDT LS image, Package and Vite choices, Plain Monaco client with direct LSP requests, Verification -### Community 192 - "Scripts" +### Community 207 - "Cleanup Containers" Cohesion: 0.25 Nodes (6): Canonical registry-qualified image names, [command, kind], dockerfiles, image, Kind, subprocess -### Community 193 - "Global Setup" +### Community 208 - "Dist Download Utility" Cohesion: 0.32 Nodes (8): Decision 023: Metrics and Observability, Decision 023: Metrics and Observability, Decision 027: Ship control-plane logs to Grafana Cloud Loki, alloy service (prod override), Grafana Alloy, Grafana Cloud, Loki log shipping pipeline, Prometheus exposition (/metrics) -### Community 194 - "Docusaurus.config" -Cohesion: 0.25 -Nodes (8): apps/control/src/app/assets.ts, createPathPlannerDist(), DriverStationPage (page object), e2e/fixtures/gamepad-shim.ts, installGamepadShim(), pathplannerResponse(), Using CodeRunner, Using CodeRunner: PathPlanner section - -### Community 195 - "Sidebars" -Cohesion: 0.29 -Nodes (8): AdminPage (page object), apps/control/src/app.ts, CLI Reference, apps/control/src/config.ts (ControlConfig), createApp() (ControlApp factory), containers/control/Dockerfile, scripts/fetch-dist.ts, PathPlanner 503 troubleshooting - -### Community 196 - "Vite.config" +### Community 209 - "Gamepad Session Safety Disables" Cohesion: 0.25 Nodes (8): Decision 017: LinuxServer base migration, Decision 026: editor default theme, Decision 034: recursive chown retained, Decision 036: VSCodium reh-web migration, Decision 037: --disable-workspace-trust flag, openvscode-server (deprecated editor), VSCodium-web Editor Migration Plan, Workspace trust blocks redhat.java Standard Mode -### Community 197 - "Constants" +### Community 210 - "E2E Fixture Architecture" +Cohesion: 0.32 +Nodes (8): ControlApp, fake-halsim.ts, fake-vscode.ts, Fixture Architecture (In-Process ControlApp), loginAs (Seeded Auth Fixture), MockWorkspaceRuntimeProvider, runtime.ts Fixture Helpers, Start Headless Simulation via CodeRunner (not WPILib extension) + +### Community 211 - "Allowlist Management E2E" Cohesion: 0.32 Nodes (4): createApp(), RunManager (run job lifecycle), makeScriptedRunCommandFactory(), MockWorkspaceRuntimeProvider -### Community 198 - "Icon Rail" +### Community 212 - "Extension Reconciliation.test" Cohesion: 0.29 Nodes (6): consoleErrors, echoPromise, helloPromise, ws, ws1, ws2 -### Community 201 - "Use Gamepad Channel" -Cohesion: 0.33 -Nodes (4): ListBlockDevicesOptions, listWorkspaceDiskLimitDevices(), log, devices +### Community 213 - "Users" +Cohesion: 0.29 +Nodes (3): long, tagged, workspace -### Community 202 - "Workspace Page" +### Community 216 - "Sim Headless.init" Cohesion: 0.29 Nodes (6): args, beforeIndex, beforeMs, countResult, db, dryRun -### Community 203 - "README" +### Community 217 - "Build" Cohesion: 0.29 Nodes (4): [command, email], db, UserRow, users -### Community 204 - "Cleanup Containers" -Cohesion: 0.29 -Nodes (6): Console lessons, Explore more, Get started, PathPlanner, Robot lessons and imported projects, Using CodeRunner - -### Community 205 - "Cloudflare Pages Function.test" +### Community 218 - "Settings" Cohesion: 0.29 Nodes (7): code:bash (IMAGE=ghcr.io/mathewdunne/coderunner-workspace:latest), code:bash (curl -s http://127.0.0.1:33999/u/smoke/vscode/ | grep -oE '/), code:bash (curl -s -i --max-time 5 \), code:bash (docker exec cr-smoke ls /config/extensions), code:bash (docker exec cr-smoke stat -c '%U:%G %n' /config/.gradle /con), code:bash (docker exec cr-smoke java -version), Task 3: Build the image and verify the editor serves under the base path -### Community 206 - "Extension Reconciliation.test" +### Community 219 - "Helpers" Cohesion: 0.29 Nodes (6): Bundled third-party software, CodeRunner, Lesson content, Licenses, Modifications, What this means for you -### Community 207 - "Users" +### Community 220 - "Helpers" Cohesion: 0.29 Nodes (6): Bundled catalog (default, zero-config), How Lessons Work, Remote catalog (your own lessons repo), The two bundled demo modules, Two catalog sources, one menu, What happens when a student loads a lesson -### Community 208 - "Vitest.config" +### Community 221 - "Admin Routes" Cohesion: 0.29 Nodes (6): Choose a deployment route, Cloudflare Offline Page (advanced, optional), Deployment Overview, Google Cloud Deployment (advanced), Local Deployment (recommended), What both need -### Community 209 - "Setup" +### Community 222 - "Websocket" Cohesion: 0.29 Nodes (6): 028 — Demo mode for zero-config local tryout, Affected code, Constraints, Context, Decision, Why synthetic over real sessions -### Community 210 - "Sim Headless.init" +### Community 223 - "Converters" Cohesion: 0.29 Nodes (6): 016 — Imported project simulation compatibility, Context, Decisions, Gradle init script for headless simulation override, Upgrade libstdc++6 via Ubuntu toolchain PPA, Verification -### Community 211 - "Build" +### Community 224 - "Docker Client" Cohesion: 0.29 Nodes (6): code:json ({), Consequences, Context, Decision, Decision 026: Editor Default Theme, Status -### Community 212 - "Settings" +### Community 225 - "Storage" Cohesion: 0.29 Nodes (6): 032 — Canonical Image Naming, code:block1 (${CODERUNNER_IMAGE_NS:-ghcr.io/mathewdunne}/coderunner-contr), Consequences, Context, Decision, Status -### Community 213 - "Helpers" +### Community 226 - "Demo Banner" Cohesion: 0.29 Nodes (6): 033 — Workspace Disk Read Limit, Alternatives considered, Consequences, Context, Decision, Status -### Community 214 - "Helpers" +### Community 227 - "HALSim/NT4 WebSocket Responses" Cohesion: 0.29 -Nodes (7): handleAdminRoute, directorySizeBytes, queryAuditLog, recordAuditEvent, apiErrorResponse, adminStatusResponse, auditActor +Nodes (7): Reload allowlist before each sign-in check, refreshAllowlistBeforeCheck, defaultContainerUser, envContainerUser, loadControlConfig, parseHostDataDir, Reject root workspace-container user in network mode -### Community 215 - "Admin Routes" +### Community 228 - "Button" Cohesion: 0.29 -Nodes (7): demo mode test suite, DEMO_SLUG (auth/demo), AdvantageScope (bundled, modified), CodeRunner (project), Demo Mode Quick Start, VSCodium (bundled), WPILib (bundled) +Nodes (7): createAdvantageScopeDist, createCatalogDir, createPathPlannerDist, createWebDist, withApp, idle lifecycle and admin controls test suite, AdvantageScope Lite and NT4 routing test suite -### Community 216 - "Websocket" +### Community 229 - "Card" Cohesion: 0.29 Nodes (7): BridgeEntryBase (type), ReconnectingWsBridge.ensureEntry, HalSimBridge, Nt4AutoChooserBridge, ReconnectingWsBridge.open, ReconnectingWsBridge, entry.socket !== socket stale-guard pattern -### Community 217 - "Converters" -Cohesion: 0.33 -Nodes (7): Decision 020: Workspace Runtime Provider Boundary, control service (docker-compose.yml), coderunner Docker network, workspace-template service, caddy service (prod override), LocalDockerRuntimeProvider, WorkspaceRuntimeProvider interface - -### Community 218 - "Docker Client" +### Community 230 - "Card" Cohesion: 0.29 -Nodes (5): e2e/fixtures/auth.ts (loginAs helper), Better Auth cookie URL-encoding consistency, loginAs(), LoginPage (page object), signToken() (HMAC session signing) - -### Community 219 - "Storage" -Cohesion: 0.33 -Nodes (3): addAllowlistEntry(), allowlist.spec.ts (auth), oauth-callback.spec.ts (OAuth callback flow) +Nodes (7): Baseline response security headers, Batched docker inspect, Capacity admission on container adoption, Decision 030: Control-Plane Hardening Pass, Nt4AutoChooserBridge, ReconnectingWsBridge base class, Stale run-job status write fix -### Community 220 - "Demo Banner" +### Community 231 - "Dialog" Cohesion: 0.48 Nodes (6): UI written and tested but deliberately unwired, PathPlannerPane.tsx, ScopePane.tsx, SimPaneSwitcher.tsx, apps/web/src/routes/WorkspacePage.tsx, WorkspacePage (page object) -### Community 221 - "Theme Provider" +### Community 232 - "Dialog" Cohesion: 0.29 -Nodes (7): sessionResponseSchema, open-workspace.spec.ts (first-login empty project test), "security" Playwright project, response-headers.spec.ts (S19/S20 header tests), xss.spec.ts (S16/S17 XSS tests), S16 malicious display name test, S17 run console text rendering test - -### Community 222 - "Button" -Cohesion: 0.33 -Nodes (7): DEPLOY_FILES_WRITE_ROOT constant, deployFilePathSchema, deny-list rationale for PathPlanner deploy file paths, openWorkspace() test helper, pathplannerLoads() helper, pathplanner-pane.spec.ts (sim pane tool tabs test), WorkspacePage page object +Nodes (5): e2e/fixtures/auth.ts (loginAs helper), Better Auth cookie URL-encoding consistency, loginAs(), LoginPage (page object), signToken() (HMAC session signing) -### Community 223 - "Card" +### Community 233 - "Dialog" Cohesion: 0.29 Nodes (6): applyAdvantageScopePatches(), ensureEmscripten(), main() build orchestration, skip owlet/docs postinstall rationale, runPostinstallForLite(), EMSDK (Emscripten SDK) 4.0.12 -### Community 224 - "Card" +### Community 234 - "Dialog" +Cohesion: 0.29 +Nodes (7): sessionResponseSchema, open-workspace.spec.ts (first-login empty project test), "security" Playwright project, response-headers.spec.ts (S19/S20 header tests), xss.spec.ts (S16/S17 XSS tests), S16 malicious display name test, S17 run console text rendering test + +### Community 235 - "Allowlist Management E2E" +Cohesion: 0.33 +Nodes (3): addAllowlistEntry(), allowlist.spec.ts (auth), oauth-callback.spec.ts (OAuth callback flow) + +### Community 236 - "Dialog" Cohesion: 0.29 Nodes (7): audit_log SQLite table, apps/control/src/auth/allowlist, backupDatabase() (SQLite serialize snapshot), data/users//{project,assets} backup layout, scripts/allowlist.ts (allowlist CLI), scripts/audit-prune.ts, scripts/backup.ts -### Community 225 - "Card" +### Community 237 - "Dialog" Cohesion: 0.38 Nodes (5): container_leases table, clearContainerLeases(), rebuildWorkspaces(), rebuildWorkspaces test suite, Grafana ops dashboard screenshot: host VM, workspaces, runs, control-plane panels -### Community 226 - "Card" +### Community 238 - "Dialog" Cohesion: 0.33 Nodes (7): PathPlanner Pane Overview Screenshot, Sign-In Page Screenshot, Switch Project Dialog Screenshot, Team Import Progress Screenshot, Using CodeRunner - Ready State Screenshot, Using CodeRunner - Start State Screenshot, Workspace Shell Three-Pane Layout Screenshot -### Community 227 - "Card" +### Community 239 - "Dropdown Menu" Cohesion: 0.4 Nodes (4): { result }, { unmount }, VALID_STATUS, useContainerStatus() -### Community 228 - "Card" +### Community 240 - "Dropdown Menu" Cohesion: 0.33 Nodes (5): dockerfile, initScript, repoRoot, robotSettings, settings -### Community 229 - "Card" -Cohesion: 0.33 -Nodes (6): code:bash (git add ), code:bash (bun run docs:build && grep -rl "vscodium-web-migration" webs), code:bash (git add docs/decisions/036-vscodium-web-migration.md docs/de), File Structure, Task 5: Full regression gate, Task 7: Record decision log 036 - -### Community 230 - "Dialog" +### Community 241 - "Dropdown Menu" Cohesion: 0.33 Nodes (5): Background — verified findings, Follow-up disposition (closed 2026-08-26), Global Constraints, Out of scope, VSCodium-web Editor Migration Implementation Plan -### Community 231 - "Dialog" +### Community 242 - "Dropdown Menu" Cohesion: 0.33 Nodes (6): code:bash (docker exec cr-smoke sh -c 'cat /config/data/User/settings.j), code:bash (| (if $mode == "project" then . else ."security.workspace.tr), code:bash (bun run docker:build:workspace), code:bash (git add containers/code/root/etc/s6-overlay/s6-rc.d/init-frc), code:bash (docker rm -f cr-smoke), Task 4: Verify Java reaches Standard Mode; fix workspace trust only if it does not -### Community 232 - "Dialog" +### Community 243 - "Dropdown Menu" Cohesion: 0.33 Nodes (5): 020 - Workspace runtime provider boundary, Consequences, Context, Decision, Status -### Community 233 - "Dialog" +### Community 244 - "Dropdown Menu" Cohesion: 0.33 Nodes (5): 024 — Bound Per-Workspace Container Memory, Consequences, Context, Decision, Status -### Community 234 - "Dialog" +### Community 245 - "Dropdown Menu" Cohesion: 0.33 Nodes (5): 035 — Multi-Arch Images and CI/Release/Deploy Workflow Split, Consequences, Context, Decision, Status -### Community 235 - "Dialog" +### Community 246 - "Dropdown Menu" Cohesion: 0.33 Nodes (5): 034 — Demo mode portability on Docker Desktop, Consequences, Context, Decision, What was deliberately not done -### Community 236 - "Dialog" +### Community 247 - "Dropdown Menu" Cohesion: 0.33 Nodes (5): 025 — Detach the Simulation JVM from Gradle, Consequences, Context, Decision, Status -### Community 237 - "Dialog" +### Community 248 - "Dropdown Menu" Cohesion: 0.53 Nodes (6): AdvantageKit Logger, robot-starter catalog lesson module, robot-starter Constants.java template class, robot-starter Main.java template class, robot-starter Robot.java template class, robot-starter RobotContainer.java template class -### Community 238 - "Dropdown Menu" -Cohesion: 0.4 -Nodes (6): Decision 039: PathPlanner integration, Deploy-files API (/u/:slug/api/deploy-files/...), pathplanner-dist.tar.gz packaging, PathPlanner (upstream), PathPlanner topbar tab (iframe beside AdvantageScope), pathplanner-web fork (mathewdunne/pathplanner-web) - -### Community 239 - "Dropdown Menu" -Cohesion: 0.33 -Nodes (6): Decision 010: Gradle Project Cache Isolation for Sim and LSP, --project-cache-dir $HOME/.gradle-project-sim, stop-sim.sh, V1-10 three-user smoke test, non-destructive Gradle headless override (strips GUI, enables WS server), robot lesson kind - -### Community 242 - "Dropdown Menu" -Cohesion: 0.4 -Nodes (3): denied, GATED_PATHS, PUBLIC_PATHS - -### Community 243 - "Dropdown Menu" -Cohesion: 0.4 -Nodes (4): baBody, body, userRow, workspace - -### Community 246 - "Dropdown Menu" +### Community 252 - "Sonner" Cohesion: 0.5 Nodes (4): dryRun, main(), _repoRoot, run() -### Community 248 - "Dropdown Menu" -Cohesion: 0.4 -Nodes (5): code:bash (# Seed Gradle cache on first run.), code:bash (# Fix ownership for /config and /workspace (linuxserver conv), code:bash (# Fix ownership of what this script created as root. The bas), code:bash (git add containers/code/root/etc/s6-overlay/s6-rc.d/init-frc), Task 2: Scope `init-frc-setup`'s ownership pass to what it actually creates - -### Community 249 - "Dropdown Menu" +### Community 254 - "Tabs" Cohesion: 0.4 Nodes (4): Consequences, Context, Decision, Decision 014 — Better Auth Integration -### Community 250 - "Dropdown Menu" +### Community 255 - "Tabs" Cohesion: 0.4 Nodes (4): 029 — Lessons & Modules, Consequences, Context, Implementation decisions -### Community 251 - "Resizable" +### Community 256 - "Tabs" Cohesion: 0.4 Nodes (4): 040 — SELinux container mounts, Consequences, Context, Decision -### Community 252 - "Resizable" +### Community 257 - "Tabs" Cohesion: 0.4 Nodes (4): Consequences, Context, Decision, Decision 019: Keyboard Input Mode -### Community 253 - "Resizable" +### Community 258 - "Tooltip" Cohesion: 0.4 Nodes (4): 039 — PathPlanner integration, Consequences, Context, Decision -### Community 254 - "Resizable" +### Community 259 - "Tooltip" Cohesion: 0.4 Nodes (4): 010 - Gradle project cache isolation for sim and LSP, Context, Decisions, Implications -### Community 255 - "Separator" -Cohesion: 0.4 -Nodes (5): Admin workspace backup action, Admin workspace restore action, createProjectArchive, restoreProjectArchive, runTar - -### Community 256 - "Sonner" +### Community 260 - "Tooltip" Cohesion: 0.4 Nodes (5): Decision 018 (gamepad unplug safety), listConnectedGamepads, makeLabel, useGamepad test suite, useGamepad -### Community 257 - "Tabs" +### Community 261 - "Tooltip" Cohesion: 0.4 Nodes (5): Better Auth (OAuth sign-in), isProxiedPath function, onRequest handler, serviceUnavailable function, Decision 014: Better Auth Integration -### Community 258 - "Tabs" +### Community 262 - "Use Run Channel" Cohesion: 0.4 Nodes (5): ascope-dist filesystem-only stage reused across arch, containers/control/Dockerfile multi-stage build, Decision 035: Multi-Arch Images and CI/Release/Deploy Workflow Split, Multi-arch build via native runners + digest merge, ci.yml / release.yml / deploy.yml split -### Community 259 - "Tabs" -Cohesion: 0.4 -Nodes (4): e2e/fixtures/runtime.ts, seedWorkspaceProject(), Switch Project picker (auto-opens on empty workspace), Switching projects discards workspace (by design) - -### Community 260 - "Tabs" +### Community 263 - "Image" Cohesion: 0.4 Nodes (5): docs/ content directory, docs/decisions/ decision logs, Docusaurus site config, website/README.md, Docusaurus sidebars config -### Community 262 - "Tabs" +### Community 265 - "Community 265" Cohesion: 0.5 Nodes (3): path, repoRoot, targets -### Community 263 - "Tooltip" +### Community 266 - "Community 266" +Cohesion: 0.5 +Nodes (3): 042 — Collapsible workspace panes, Decision, Validation + +### Community 267 - "Community 267" Cohesion: 0.5 Nodes (3): Active (V2 and post-V2), Archive, Decision Logs -### Community 264 - "Tooltip" +### Community 268 - "Community 268" Cohesion: 0.5 Nodes (3): Robot Starter, Running it, Where the code lives -### Community 265 - "Tooltip" +### Community 269 - "Community 269" Cohesion: 0.5 Nodes (3): Hello, World, Running it, Try stuff -### Community 266 - "Tooltip" +### Community 270 - "Community 270" Cohesion: 0.67 Nodes (4): defaultLogFormat, formatRecordJson, parseLogFormatEnv, logging.ts test suite (formatRecordJson, parseLogFormatEnv, defaultLogFormat) -### Community 267 - "Use Run Channel" +### Community 271 - "Community 271" Cohesion: 0.5 Nodes (4): Route templating for bounded metric cardinality, statusClass, templateRoute, metrics.ts test suite (templateRoute, statusClass) -### Community 268 - "Playwright.config" -Cohesion: 0.67 -Nodes (4): GamepadSessions (gamepad.ts), Gamepad release/close safety-disables joystick and driver station, GamepadSessions test suite, HalSimBridgeUnavailableError (halsim.ts) - -### Community 269 - "Image" +### Community 272 - "Community 272" Cohesion: 0.67 Nodes (4): persist middleware with partialize (inputMode only), UI store test suite, UIState interface, useUIStore Zustand store -### Community 270 - "Verify Ascope" +### Community 273 - "Community 273" Cohesion: 0.5 Nodes (4): Archive 004: Backend wiring for save and run, Custom WebSocket sender (no @fastify/websocket), Host backend plus Docker CLI (fixed argument arrays), Replaceable sim process under tini -### Community 271 - "Community 271" +### Community 274 - "Rebuild Workspaces" +Cohesion: 0.5 +Nodes (4): AdvantageScope Lite NT4 client, e2e/fixtures/fake-nt4.ts, NT4 wire protocol: MessagePack binary + JSON text, startFakeNt4() + +### Community 275 - "Community 275" Cohesion: 0.83 Nodes (4): downloadAndExtract(), withScratch(), fetch-dist main(), fetchPathPlannerDist() -### Community 272 - "Community 272" +### Community 276 - "Community 276" Cohesion: 0.5 Nodes (4): Demo mode landing screenshot: CodeRunner IDE with demo-mode banner, Driver Station workbench screenshot: sim controls, mode, console output, Lesson catalog / Switch project modal screenshot, Lesson README opened screenshot: editor + AdvantageScope + README preview -### Community 277 - "Community 277" +### Community 281 - "Community 281" Cohesion: 0.67 Nodes (3): Auto-chooser NT4 bridge msgpack decoding, auto-chooser NT4 bridge test, encodeMsgPack test helper -### Community 278 - "Community 278" +### Community 282 - "Community 282" Cohesion: 0.67 Nodes (3): Badge, KindTag, SwitchProjectDialog -### Community 279 - "Community 279" +### Community 283 - "Community 283" Cohesion: 0.67 Nodes (3): Button, DialogContent, DialogFooter -### Community 280 - "Community 280" +### Community 284 - "Workspace Container Bind-Mount & Memory Bounds" +Cohesion: 0.67 +Nodes (3): Classroom-density memory defaults, Decision 024: Container Memory Budget, VS Code Gradle Build Server path + +### Community 285 - "Community 285" Cohesion: 0.67 Nodes (3): scripts/apply-ascope-patches.ts, scripts/build-ascope-lite.ts, scripts/clean.ts @@ -1519,11 +1501,13 @@ Nodes (3): scripts/apply-ascope-patches.ts, scripts/build-ascope-lite.ts, script website/static/img/screenshots/grafana-ops-dashboard.png · relation: conceptually_related_to - `restore.ts main()` → `typecheck.ts` [AMBIGUOUS] scripts/typecheck.ts · relation: conceptually_related_to +- `Decision 032: Canonical Image Naming` → `Archived V1 and MVP Decision Logs (001-010)` [AMBIGUOUS] + docs/decisions/README.md · relation: conceptually_related_to ## Knowledge Gaps -- **1883 isolated node(s):** `console`, `cookie`, `consoleErrors`, `leaseErrors`, `dangerous` (+1878 more) +- **2027 isolated node(s):** `workspace`, `frame`, `victimWorkspace`, `attackerWorkspace`, `console` (+2022 more) These have ≤1 connection - possible missing edges or undocumented components. -- **86 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. +- **89 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. ## Suggested Questions _Questions this graph is uniquely positioned to answer:_ diff --git a/graphify-out/graph.html b/graphify-out/graph.html index 7186343d..794d0078 100644 --- a/graphify-out/graph.html +++ b/graphify-out/graph.html @@ -61,12 +61,12 @@

Communities

-
4094 nodes · 5929 edges · 362 communities
+
4349 nodes · 6295 edges · 371 communities