No description
  • Rust 50.9%
  • HTML 24.5%
  • CSS 15.7%
  • JavaScript 8.5%
  • Dockerfile 0.4%
Find a file
Benn1x 14ad1146b8
Some checks failed
build / build (push) Failing after 10s
Docker Build and Push / build-and-push (push) Successful in 53s
fix: restore the blue room-interior fill in PNG export
Brings back the blue floor wash inside rooms on PNG export (both plain and
with-dimensions), matching the canvas. Flips room_fill back to true for the
export RenderOpts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 01:43:46 +02:00
.forgejo/workflows Point deploy CI at code.podesta.ai registry (PodestaAI conventions) 2026-06-08 04:04:15 +02:00
assets feat: add projection screen + projector/beamer assets (gallery) 2026-06-08 21:04:57 +02:00
design-ref Add UI design handoff bundle + distilled design brief 2026-06-08 01:17:51 +02:00
docs feat: add projection screen + projector/beamer assets (gallery) 2026-06-08 21:04:57 +02:00
scripts feat: add projection screen + projector/beamer assets (gallery) 2026-06-08 21:04:57 +02:00
src fix: restore the blue room-interior fill in PNG export 2026-06-09 01:43:46 +02:00
.dockerignore Curved windows, Shift multi-select, subtract-to-hole, item legend/colors 2026-06-08 03:47:54 +02:00
.gitignore Build Atelier v1 (autonomous workflow output) 2026-06-08 02:43:36 +02:00
Cargo.lock Add ellipse rooms, boolean Add/Subtract, window wall-snap; fix Export + Hand 2026-06-08 03:15:03 +02:00
Cargo.toml Make furniture assets placeable: manifest-driven palette + sprite rendering 2026-06-08 15:17:35 +02:00
DECISIONS.md Curved windows, Shift multi-select, subtract-to-hole, item legend/colors 2026-06-08 03:47:54 +02:00
Dockerfile Curved windows, Shift multi-select, subtract-to-hole, item legend/colors 2026-06-08 03:47:54 +02:00
index.html Theme/density: device default only on first open, saved choice thereafter 2026-06-08 14:42:39 +02:00
nginx.conf Curved windows, Shift multi-select, subtract-to-hole, item legend/colors 2026-06-08 03:47:54 +02:00
README.md Build Atelier v1 (autonomous workflow output) 2026-06-08 02:43:36 +02:00
Trunk.toml Build Atelier v1 (autonomous workflow output) 2026-06-08 02:43:36 +02:00

Atelier — 2D Studio Floor Planner

A lightweight 2D floor-plan tool for laying out and furnishing an artist's studio. Draw rooms, drop in studio essentials (easel, drafting table, desk, stool, shelves, flat-file, sink, window, door), measure them in metric or imperial, and export the result. Written in Rust, compiled to WebAssembly, and rendered with Yew + a raw <canvas>. Works on desktop and tablets, with full Apple Pencil / stylus support.

Atelier

Features

  • Rooms — draw as rectangles or closed polygons on a snapping grid; select, move, resize, rotate, and delete; edit walls by dragging corner vertices.
  • Studio item palette — place the nine studio kinds (easel, drafting table, desk, stool, shelves, flat-file, sink, window, door) from the Assets tab; move, rotate, resize, and delete them.
  • Live dimensions with a metric ⇄ imperial toggle (geometry is stored in meters; default grid 0.5 m).
  • Pan & zoom — two-finger pan/zoom on touch, mouse wheel on desktop, plus a floating zoom pill (in / out / fit).
  • Stylus — Pointer Events with pressure-aware preview, coalesced-event pen smoothing, palm rejection (touch ignored while a pen is down), and touch-action: none on the canvas.
  • Undo / redo — full command stack (Ctrl/Cmd+Z, Shift+Cmd+Z or Ctrl+Y), Delete/Backspace to remove the selection, Escape to cancel a draw gesture, and V / R / P tool shortcuts.
  • Persistence — autosave to browser localStorage (restored on reload) plus named Save / Load slots.
  • ExportJSON (re-editable), PNG (rasterized from the model), and SVG (vector); Import JSON round-trips a saved design back in.
  • Theme & density — dark (default) / light theme toggle and pointer / touch density, both persisted; the canvas reads its colors from the live theme tokens, so light/dark are honored on every redraw.
  • Layers panel — the left dock's Layers tab lists every room and item; clicking a row selects it on the canvas.

Prerequisites

  • Rust (stable) — https://rustup.rs
  • The wasm target: rustup target add wasm32-unknown-unknown
  • Trunk (0.21+): cargo install trunk --locked

On some platforms Trunk's auto-downloaded wasm-bindgen binary is unavailable (e.g. Windows-on-ARM). If a build fails fetching wasm-bindgen, install the CLI matching the locked crate version: cargo install wasm-bindgen-cli --version 0.2.122.

Run locally (dev)

trunk serve --open      # http://127.0.0.1:8080, hot reload

Production build

trunk build --release --public-url ./

Static files land in ./dist (index.html, the JS shim, the .wasm, and hashed CSS). The relative --public-url ./ makes the bundle deployable under any subpath on any static host (nginx, S3, Netlify, Forgejo/GitHub Pages, …).

Test

cargo test              # pure-logic unit tests (native, no browser)

This compiles and runs the pure core onlysrc/model/*, src/interaction/{command,pointer,mod}, and src/render/{viewport,mod} — which carry no web-sys/wasm-bindgen deps. Everything browser-coupled (view/, render/scene, storage/, editor.rs, app.rs) is gated behind #[cfg(target_arch = "wasm32")] in src/lib.rs, so cargo test stays browser-free and fast. Current suite: 29 tests across geometry, units, item, room, document (serde round-trip + version handling), command/undo, pointer, and viewport.

Headless e2e smoke

The app exposes a thin JS test API on window.__atelier that drives the same editor instance the UI uses, through the same autosave path — no behavior change to the UI. A headless browser (Playwright, the gstack /browse daemon, etc.) can exercise a full create → resize → place → reload → assert-persisted flow against the real app:

// against a running `trunk serve`
__atelier.clear_all();
__atelier.create_room(0, 0, 4, 3);     // x, y, w, h in meters
__atelier.resize_selected(5, 3);       // resize current selection
__atelier.place_item("Easel");         // ItemKind label, case-insensitive
__atelier.room_count();                // -> 1
__atelier.item_count();                // -> 1
// reload the page, then:
__atelier.autosave_present();          // -> true (autosave restored)

No automated e2e runner is committed in this repo (the SPEC scoped v1 to manual browser verification); the hooks are in place so one can be added without touching the UI.

Verify checklist (manual)

  • Rect & polygon room drawing with live dimensions and snap.
  • Select / move / resize / rotate / delete; corner-drag wall editing.
  • Place items from the Assets palette; undo/redo (Ctrl/Cmd+Z); Delete key.
  • Units toggle; grid/snap toggle; zoom in / out / fit.
  • Theme toggle (dark/light) recolors the canvas; density toggle (pointer/touch).
  • Two-finger pan/zoom and pen palm rejection on a tablet.
  • Save slot + reload restores autosave; Export JSON / PNG / SVG download; Import JSON round-trips; New clears the document.

Architecture

Three layers, deliberately separated so the logic is testable without a browser:

src/
  model/        pure geometry, units, item, room, document + serde   [tested]
  interaction/  command/undo, pointer set + gestures, Tool/Selection [tested]
  render/
    viewport.rs world↔screen transform, pan/zoom/fit                 [tested]
    scene.rs    imperative canvas draw (grid/rooms/items/handles)    [wasm]
  editor.rs     mutable Editor state (doc, undo, viewport, gesture)  [wasm]
  view/         Yew chrome: toolbar, palette, properties, statusbar,
                canvas mount, inline-SVG icons                       [wasm]
  storage/      localStorage slots + autosave; JSON/PNG/SVG export   [wasm]
  app.rs        root App component; owns the Rc<RefCell<Editor>>;
                wires chrome ↔ canvas; installs the smoke-test hooks [wasm]
  • Yew renders the reactive chrome (app bar, tool group, docks, inspector, status bar); a single Rc<RefCell<Editor>> is shared by every component and the canvas.
  • A raw <canvas> is drawn imperatively via web-sys for full Pointer Events control (pressure, pointerType, coalesced events, touch-action) — things an immediate-mode GUI would abstract away.
  • The pure core (model/, render/viewport, interaction/command) has no browser deps and is unit-tested natively with cargo test.

Design system

The visual language — tokens, components, the 3-pane layout, and the dark/light palette — comes from the design handoff in design-ref/project/Atelier/. assets/tokens.css and assets/components.css are reused verbatim from that handoff; assets/app.css adds the .app grid and canvas chrome. Lucide-style icons are inlined as SVG (src/view/icons.rs) rather than loaded from a CDN, so the app is offline-robust with no console errors from icon-load races.

See docs/superpowers/DESIGN-BRIEF.md for the distilled visual rules and DECISIONS.md for how SPEC / PLAN / design conflicts were resolved.

CI

.forgejo/workflows/build.yml runs on a Forgejo Runner: it checks out the repo, installs the Rust toolchain + wasm32-unknown-unknown target + Trunk, runs cargo test (pure core), builds the release bundle with trunk build --release --public-url ./, and uploads dist/ as an artifact. The syntax is GitHub-Actions-compatible — move the file to .github/workflows/ to run the same pipeline on GitHub.

Out of scope (v1) / future ideas

Not built in v1: 3D view, multiple floors, true CAD precision, real-time collaboration, and cloud accounts. Windows and doors are placeable/rotatable items today; wall-snapping doors/windows are a natural next step, along with freehand-sketch → room conversion, standalone measurement annotations, and more item kinds.

Build notes / known deviations

  • The crate ships a distinct binary target atelier-app (src/main.rs) while the library crate stays atelier. This avoids a wasm artifact name collision between the cdylib and the bin (both would otherwise be atelier, which Trunk 0.21 can't disambiguate); index.html points the Trunk rust link at data-bin="atelier-app". The atelier::app::App path and cargo test are unaffected. See DECISIONS.md.