WIP experiment with 95% vibe coding
tiny-workshop CONTRIBUTING.md
18 kB
Markdown
at main

Contributing to Tiny Workshop #

Thank you for your interest in contributing to Tiny Workshop! This document provides guidelines and information for contributors.

Table of Contents #

Project Overview #

Tiny Workshop is a 3D interactive AI agent application featuring a Victorian study environment. The project combines:

  • Frontend: Fyrox-based 3D application (Rust) that renders the environment, manages the character, and provides the chat interface
  • Backend: Bun/TypeScript adapter that bridges the frontend to the Claude Agent SDK
  • Communication: IPC via stdin/stdout JSON-RPC protocol

Current Features #

Implemented:

  • Chat interface with streaming message support
  • z.AI API key authentication with OS keychain integration (with Linux config file fallback)
  • IPC communication between frontend and backend
  • Cross-platform support (Windows, macOS, Linux)
  • HTTP debug server for automated testing
  • Animation state system (placeholder cube with color-coded states)
  • Room state persistence with atomic writes and multi-instance file locking
  • Session logging with JSONL format and crash-safe append-only semantics
  • Session resume with SDK context preservation across app restarts
  • Room system with surfaces and interactables (sticky notes, image stacks)
  • Text rendering for sticky notes using raqote (renders to PNG textures)
  • Image texture rendering with LRU cache (50 images or 50MB limit)
  • Settings dialog with configurable graphics, audio, and API key management

Planned:

  • 3D Victorian study environment with AI character (placeholder geometry in place)

Architecture #

The project follows a multi-process architecture:

┌─────────────────┐         IPC (JSON-RPC)        ┌─────────────────┐
│  Fyrox Frontend │ ◄──────────────────────────► │ Backend Adapter │
│     (Rust)      │    stdin/stdout messages      │ (Bun/TypeScript)│
└─────────────────┘                               └────────┬────────┘
                                                           │
                                                           ▼
                                                  ┌────────────────┐
                                                  │ Claude Agent   │
                                                  │      SDK       │
                                                  └────────────────┘

Project Structure #

tiny-workshop/
├── docs/design/           # Architecture and design documents
├── tiny-workshop/         # Main Rust workspace
│   ├── editor/           # Fyrox editor application
│   ├── executor/         # Desktop runtime
│   ├── executor-wasm/    # WebAssembly runtime (future)
│   ├── executor-android/ # Android runtime (future)
│   ├── game/             # Core game logic
│   │   └── src/
│   │       ├── lib.rs              # Main Game plugin
│   │       ├── ipc.rs              # IPC protocol types
│   │       ├── backend.rs          # Backend process management
│   │       ├── chat_ui.rs          # Chat interface
│   │       ├── credential_manager.rs # OS keychain integration
│   │       ├── debug_server/       # HTTP debug API (feature-gated)
│   │       └── room/               # Room system module
│   │           ├── mod.rs          # Surface/interactable management
│   │           ├── fonts.rs        # Font loading for text rendering
│   │           ├── text_renderer.rs # Text-to-PNG rendering
│   │           ├── image_cache.rs  # LRU texture cache
│   │           └── image_storage.rs # Image file management
│   └── game-dylib/       # Hot-reloadable game plugin
└── backend/              # TypeScript backend adapter
    └── packages/
        ├── ipc-types/    # IPC protocol definitions
        ├── logger/       # Structured logging
        └── adapter/      # Main adapter (SDK integration)

For detailed architecture information, see the design documentation.

Development Setup #

Prerequisites #

For Rust Frontend #

  • Rust: Install via rustup
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    
  • Platform-specific dependencies:
    • Linux: X11/Wayland development libraries
      # Ubuntu/Debian
      sudo apt install libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libasound2-dev
      
      # Fedora
      sudo dnf install libxcb-devel libxkbcommon-devel alsa-lib-devel
      
    • macOS: Xcode Command Line Tools
      xcode-select --install
      
    • Windows: Visual Studio 2019 or later with C++ tools

For Backend Adapter #

  • Bun: Install via the official installer
    # macOS/Linux
    curl -fsSL https://bun.sh/install | bash
    
    # Windows
    powershell -c "irm bun.sh/install.ps1 | iex"
    
  • Node.js (optional, for compatibility testing): v18 or later

Optional Tools #

  • Nix: For reproducible builds (see tiny-workshop/flake.nix)
    nix develop
    

Install Dependencies #

Rust Dependencies #

Dependencies are managed via Cargo and will be automatically downloaded on first build:

cd tiny-workshop
cargo build

Backend Dependencies #

The backend uses pnpm for package management:

cd backend
pnpm install

Building and Running #

Frontend (Rust) #

Development Build #

cd tiny-workshop
cargo build

Release Build #

cargo build --release

Run the Executor #

# Debug build
cargo run --package executor

# Release build
cargo run --package executor --release

# With debug server enabled (for automated testing)
cargo run --package executor --features debug_server

# With hot reloading enabled
cargo run --package executor --features dylib

Hot Reloading (Development) #

For faster iteration during development:

# Use the hot-reload profile
cargo build --profile dev-hot-reload

The engine is optimized even in debug builds (see Cargo.toml profile configuration), providing good performance while allowing code debugging.

Debug Server (Automated Testing) #

The debug server exposes an HTTP API for automated testing and external tooling to interact with the application without using the UI.

Quick Start:

# Using the dev script (recommended)
./dev.sh --debug-server      # Linux/macOS
dev.bat --debug-server       # Windows

# Or with cargo directly
cargo run --package executor --features debug_server

The server runs on http://localhost:8765 by default (override with TINY_WORKSHOP_DEBUG_PORT).

Example Usage:

# Check if the server is running
curl http://localhost:8765/health

# Get current game state (auth status, backend connection, etc.)
curl http://localhost:8765/state

# Send a message (requires valid authentication)
curl -X POST http://localhost:8765/send \
  -H "Content-Type: application/json" \
  -d '{"content":"Hello!"}'

# Poll for conversation messages
curl "http://localhost:8765/messages?limit=50"

For full API documentation, see Debug Server Design Doc.

Backend Adapter #

cd backend

# Build all packages
pnpm build

# Run in development mode
pnpm dev

# Type check
pnpm typecheck

Running the Full Application #

  1. Start the backend adapter (it will listen on stdin/stdout)
  2. Start the frontend executor (it will spawn the backend as a child process)

The frontend automatically manages the backend lifecycle.

Testing #

Rust Tests #

cd tiny-workshop

# Run all tests
cargo test

# Run all tests including debug server tests
cargo test --features debug_server

# Run tests for a specific package
cargo test --package game

# Run tests with output
cargo test -- --nocapture

Backend Tests #

cd backend
pnpm test

Integration Tests #

Integration tests verify the IPC protocol and multi-process communication by spawning the actual backend process:

# Build the backend first (required for integration tests)
cd backend && pnpm build

# Then run integration tests
cd ../tiny-workshop
cargo test --test ipc_integration

The integration test framework (game/tests/ipc_integration.rs) includes:

  • test_backend_spawn_and_init - Verifies backend spawning and init message handling
  • test_backend_handles_invalid_json - Confirms graceful error handling for malformed JSON
  • test_backend_shutdown - Tests graceful backend shutdown
  • test_protocol_version_matches - Ensures frontend/backend protocol version alignment
  • test_frontend_message_json_lines_format - Validates JSON lines message format
  • test_backend_message_deserialization_coverage - Tests deserialization of all message types

Note: Integration tests automatically skip if the backend executable is not found, so CI can run without failing.

Manual Testing Checklist #

Code Style #

Rust #

  • Follow the Rust API Guidelines
  • Use rustfmt for formatting:
    cargo fmt
    
  • Use clippy for linting:
    cargo clippy -- -D warnings
    
  • Maximum line length: 100 characters
  • Use meaningful variable names and add comments for complex logic
  • Prefer immutability (let over let mut)
  • Use Result for error handling, avoid unwrap() in production code

Fyrox UI Builder Patterns #

When building Fyrox UI widgets, be aware that some builders have their own content systems:

Correct patterns:

// ScrollViewerBuilder - use .with_content() on the builder
ScrollViewerBuilder::new(WidgetBuilder::new())
    .with_content(my_content)
    .build(ctx);

// ButtonBuilder - use .with_content() for custom widgets, .with_text() for text
ButtonBuilder::new(WidgetBuilder::new())
    .with_content(my_custom_widget)  // or .with_text("Label")
    .build(ctx);

// WindowBuilder - use .with_content()
WindowBuilder::new(WidgetBuilder::new())
    .with_content(window_content)
    .build(ctx);

Incorrect patterns (will cause visibility issues):

// DON'T use .with_child() on WidgetBuilder for these builders!
ScrollViewerBuilder::new(
    WidgetBuilder::new().with_child(content)  // WRONG - content behind decorator
).build(ctx);

ButtonBuilder::new(
    WidgetBuilder::new().with_child(text)  // WRONG - text invisible
).build(ctx);

The .with_child() method on WidgetBuilder adds children to the widget's child list, but specialized builders like ScrollViewerBuilder and ButtonBuilder have internal decorators that may obscure these children. Always use the builder's specific content method (.with_content(), .with_text(), etc.) instead.

TypeScript #

  • Use TypeScript strict mode
  • Format with Prettier (if configured)
  • Use ESLint for linting
  • Prefer async/await over raw promises
  • Use descriptive variable and function names
  • Add JSDoc comments for public APIs

Commit Messages #

Follow the Conventional Commits specification:

<type>(<scope>): <subject>

<body>

<footer>

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting, etc.)
  • refactor: Code refactoring
  • test: Adding or updating tests
  • chore: Maintenance tasks

Examples:

feat(room): Add image stack support for tool output

Add ability to stack multiple images in the room as persistent
interactables. Images are cached with LRU eviction.
fix(ipc): Handle backend disconnection gracefully

Prevent panic when backend crashes by catching channel
disconnection errors and triggering restart logic.

Contribution Workflow #

1. Plan Your Changes #

  • Check the design documents in docs/design/ for relevant architecture
  • For new features, discuss the approach with maintainers first
  • Review open issues in docs/design/ISSUES.md to avoid duplicate work

2. Create a Branch #

git checkout -b feature/your-feature-name

3. Make Changes #

  • Write code following the style guidelines
  • Add tests for new functionality
  • Update documentation if needed
  • Ensure all tests pass locally

4. Commit #

git add .
git commit -m "feat(scope): your descriptive message"

5. Submit Your Changes #

  • Ensure all tests pass
  • Update relevant design documents if you've changed architecture
  • Include clear descriptions of changes
  • Add screenshots/videos for UI changes
  • Document test results

6. Code Review #

  • Address reviewer feedback
  • Keep commits focused and atomic
  • Squash commits if requested

7. Integration #

Once approved, maintainers will integrate your changes.

Documentation #

Design Documents #

Before implementing major features, review and update the design documents in docs/design/:

Adding Documentation #

  • Use Markdown for all documentation
  • Keep language clear and concise
  • Include code examples where appropriate
  • Update the table of contents if adding new sections

Inline Documentation #

  • Add doc comments (/// in Rust, /** */ in TypeScript) for public APIs
  • Include usage examples in doc comments
  • Document parameters, return values, and possible errors

Getting Help #

  • Design Questions: Reference the design documents in docs/design/
  • Open Issues: Check docs/design/ISSUES.md for known problems and gaps
  • Architecture: Review the design docs before starting major work

Development Tips #

Performance Profiling #

  • Use cargo flamegraph for CPU profiling
  • Use tracy for frame-by-frame analysis
  • Target: 60fps (<16ms frame time) on integrated graphics

Debugging #

  • Use RUST_LOG=debug for verbose logging:
    RUST_LOG=debug cargo run --package executor
    
  • Use lldb (macOS/Linux) or Visual Studio debugger (Windows) for native debugging
  • Check tiny-workshop.log for runtime logs

Memory Management #

  • Keep total memory usage under 200MB
  • Use LRU caches for images and textures
  • Profile with valgrind (Linux) or Instruments (macOS)

Hot Reloading #

The game logic can be hot-reloaded during development:

  1. Build with hot-reload profile
  2. Make changes to game-dylib/
  3. Rebuild while executor is running
  4. Changes load automatically

Platform-Specific Testing #

  • Test on all target platforms before submitting
  • Use CI/CD for automated cross-platform testing
  • Pay attention to file path separators and line endings

License #

By contributing to Tiny Workshop, you agree that your contributions will be licensed under the same license as the project (see LICENSE file).

Thank You! #

Your contributions make Tiny Workshop better for everyone. We appreciate your time and effort!