Skip to main content
Labs are interactive coding exercises that run inside a full development environment with file editing, terminal access, automated tests, and optional Docker services. Each lab provides a sandboxed workspace where learners write real code and receive immediate feedback.

Lab Structure

A lab is defined by a directory containing:
  • lab.yaml - Lab manifest with configuration
  • instructions.md - Markdown instructions shown to the user
  • scaffold/ - Initial workspace files (copied on first run)
  • overlay/ - Files applied on every lab start (overrides user changes)
  • solution/ (optional) - Reference solution for comparison
  • tests/ - Automated test suite

Lab Manifest (lab.yaml)

lab.yaml
title: "Build a REST API"
workspace: fresh
test: "npm test"
open:
  - "src/server.js"
  - "src/routes.js"
setup:
  - "npm install"
services:
  - postgres
  - redis
Configuration fields:
title
string
required
Display name shown in the lab header
workspace
'fresh' | 'continue'
  • fresh: Standalone workspace (default)
  • continue: Shares workspace from previous lab
test
string
required
Command to run automated tests (e.g., npm test, pytest, cargo test)
open
string[]
Files to open in the editor when the lab starts
setup
string[]
Shell commands to run during first-time provisioning (e.g., npm install, pip install -r requirements.txt)
services
(string | object)[]
Docker services to start. Can use preset names (postgres, redis, mongodb) or custom service definitions.

Lifecycle Phases

Labs transition through a declarative lifecycle managed by React Query (src/lab/use-lifecycle.ts):
1

Uninitialized

Lab manifest parsed, workspace path resolved
2

Provisioning

  • Check for container runtime (if services defined)
  • Scaffold workspace from scaffold/ directory (first run only)
  • Apply overlay/ files (every run)
  • Generate docker-compose.yml from service definitions
  • Run docker compose up with health checks
  • Execute setup commands
  • Open initial files
  • Spawn default terminal
3

Ready

Lab is fully operational, user can edit files and run tests
4

Failed

Provisioning encountered an error (displayed to user)
5

Missing Runtime

No Docker/Podman found (blocks labs with services)
6

Tearing Down

Services are being stopped (cleanup phase)

Workspace Modes

Fresh Workspace

Each lab gets an isolated directory. User progress persists across sessions but is independent of other labs. Use case: Standalone exercises, self-contained projects

Continue Workspace

Shares the workspace from the previous lab in the sequence. User builds incrementally on prior work. Use case: Multi-part tutorials where Lab 2 extends Lab 1’s code
The overlay/ directory always applies, even in continue mode. Use it to update boilerplate between labs without touching user code.

File System

The lab environment provides full file tree access with:
  • Monaco editor with language servers (TypeScript, Python, Rust, Go, etc.)
  • File tree with create/rename/delete operations
  • Git integration with diff markers and status badges
  • Split editor for side-by-side editing (including solution comparison)
  • Auto-save with dirty state tracking

File Operations (src/lab/use-file-ops.ts)

const { ops } = lab.files;

// Create file/directory
await ops.createFile("src/utils.js", "export const add = (a, b) => a + b;");
await ops.createDir("src/components");

// Rename/delete
await ops.rename("old.js", "new.js");
await ops.delete("temp.txt");
All operations automatically invalidate React Query cache and trigger file tree refresh.

Terminal Integration

Labs provide full PTY (pseudo-terminal) access via xterm.js with:
  • Multiple terminal tabs
  • Shell persistence across UI sessions
  • Custom spawn options (shell, args, working directory)
  • Terminal rename and reordering

Spawning Terminals

const { terminal } = lab;

// Default shell
await terminal.spawn();

// Custom command
await terminal.spawn({
  shell: "npm",
  args: ["run", "dev"],
  cwd: "/workspace/src"
});

// Container exec
await terminal.spawn({
  shell: "docker",
  args: ["exec", "-it", "postgres_container", "/bin/sh"]
});
Terminals are backed by Tauri commands (src/lab/tauri/terminal.ts) that spawn native OS processes.

Automated Testing

Labs run test commands and parse output to provide structured feedback.

TAP Format Parser

The test runner (src/lab/use-test-runner.ts) parses TAP (Test Anything Protocol) output:
TAP version 14
1..3
ok 1 - should parse JSON
ok 2 - should handle errors
not ok 3 - should validate schema
Parsed into:
type TestAssertion = {
  index: number;
  description: string;
  passed: boolean;
};

Test States

Idle

No test run initiated

Running

Test command executing, streaming output

Passed

All assertions passed

Failed

One or more assertions failed

Error

Test command crashed or exited non-zero without TAP

Custom Test Commands

Labs support any test runner that produces output:
  • JavaScript: npm test, jest, vitest
  • Python: pytest, unittest
  • Rust: cargo test
  • Go: go test ./...
For best UX, configure runners to output TAP format.

Docker Services

Labs can define containerized services that start automatically.

Preset Services (src/lab/presets.ts)

services:
  - postgres    # PostgreSQL 16 on port 5432
  - redis       # Redis 7 on port 6379
  - mongodb     # MongoDB 7 on port 27017
  - mysql       # MySQL 8 on port 3306
Presets include health checks and default environment variables.

Custom Services

services:
  - name: api
    image: node:20-alpine
    port: 3000
    hostPort: 3000
    env:
      NODE_ENV: development
      DATABASE_URL: postgres://localhost:5432/mydb
    healthcheck: "curl -f http://localhost:3000/health || exit 1"

Container Lifecycle

Service containers are managed by docker-compose.yml generated at runtime:
  1. Detect runtime (Docker or Podman)
  2. Generate compose file (src/lab/compose.ts)
  3. Run compose up with streaming event logs
  4. Poll health checks until all services healthy
  5. Update service status badges in UI
Container orchestration is handled by src/lab/use-containers.ts.

Container Actions

The Services panel provides:
  • Start/Stop/Restart individual containers
  • View Logs with streaming output
  • Exec into container (spawns terminal with /bin/sh)
  • Container status (starting, healthy, failed)

Language Server Protocol (LSP)

Labs integrate language servers for IntelliSense, go-to-definition, and diagnostics.

Supported Languages (src/lab/lsp/server-registry.ts)

  • TypeScript/JavaScript: typescript-language-server
  • Python: pyright
  • Rust: rust-analyzer
  • Go: gopls
LSP servers run as child processes and communicate over JSON-RPC. Monaco adapters (src/lab/lsp/monaco-adapters.ts) bridge LSP protocol to Monaco editor APIs.

Document Sync

The LSP client (src/lab/lsp/document-sync.ts) maintains:
  • textDocument/didOpen on file open
  • textDocument/didChange on editor changes (debounced)
  • textDocument/didSave on save
  • textDocument/didClose on file close
Diagnostics are synced to Monaco markers for inline error squiggles.

Vim Mode

Labs support Vim keybindings via Monaco’s Vim mode adapter (src/lab/vim-status-store.ts). Toggle with Ctrl+Shift+V or through settings.

Solution Comparison

When a solution/ directory exists, users can open solution files side-by-side with their code:
const { solution } = lab;

if (solution.available) {
  solution.openSolution("src/server.js");
}
Solution files open in the right pane and are marked with a badge.

Hotkeys

Labs provide extensive keyboard shortcuts (src/lab/hotkeys/registry.ts):
ShortcutAction
Ctrl+SSave active file
Ctrl+POpen command palette
Ctrl+Shift+POpen file picker
Ctrl+BToggle file tree
Ctrl+JToggle terminal
Ctrl+Shift+TRun tests
Ctrl+GGo to line
Ctrl+Shift+VToggle Vim mode

Persistence

Lab workspaces persist across sessions using a SQLite database (src-tauri/src/workspace.rs):
  • First-run detection (scaffold only applied once)
  • Workspace path resolution
  • User file edits preserved
  • Terminal state persists
The overlay/ directory is the only mechanism to override user changes.

Example Lab Structure

my-api-lab/
├── lab.yaml
├── instructions.md
├── scaffold/
│   ├── package.json
│   ├── src/
│   │   ├── server.js
│   │   └── routes.js
│   └── tests/
│       └── api.test.js
├── solution/
│   └── src/
│       ├── server.js
│       └── routes.js
└── overlay/
    └── tests/
        └── api.test.js  # Updated tests, always applied
Use workspace: continue for multi-lab sequences. Lab 2 can reference Lab 1’s code without re-scaffolding.

Build docs developers (and LLMs) love