PotterDoc is the external product name for this app.
The repository, internal code identifiers, and some contributor documentation still use glaze as the internal project name during the transition.
A pottery workflow tracking application. Log pieces and record state transitions as work moves through throwing, bisque firing, glazing, and finishing.
- Backend API (
api/) - Django, DRF, public libraries, auth flows, and data isolation. - Frontend Client (
web/) - React components, Vite configuration, and frontend conventions. - Common Tests (
tests/) - Structural tests for the workflow state machine. - Tools (
tools/) - Standalone utilities, Modal crop offloading, and Glaze import tool. - Pages (
pages/) - Static published pages. - CI / CD Infrastructure (
docs/) - GitHub Actions workflows, deployment pipelines, and environment variables. - Agent Development Guide (
docs/agents/) - Shell bootstrap, worktree navigation, and agent workflow context.
This guide assumes you already know the tools listed below and are familiar with separation of concerns and abstraction as design principles; if any term is unfamiliar, click the linked docs to catch up quickly.
- Django is the Python web framework that owns the backend (
backend/,api/). Separation of concerns keeps unrelated responsibilities apart so each layer stays simpler to reason about—for example,api/models.pydefines the data schema,api/serializers.pytranslates between ORM objects and JSON payloads, andapi/views.pywires those serializers into/api/...endpoints that enforce workflow rules fromworkflow.yml. That split keeps the REST API (powered by Django REST Framework, DRF) resilient even when one layer needs to change, while returning consistent data/validation to all clients. - React (web/src/) renders the SPA (Single Page Application) and consumes shared types/API helpers from
web/src/util/types.tsandweb/src/util/api.ts. React follows a component-based paradigm where functions or classes receive props (inputs) and return HTML that the browser can render. - Vite (web tooling) bundles the React app. It provides fast dev reloads (hot module replacement) so UI changes appear immediately while you work, runs the local dev server that powers our web workbench, and produces optimized production builds (tree shaking, minification) so the deployed bundle is as small and performant as possible.
- Material UI supplies the component library used everywhere in the UI for forms, dialogs, buttons, and layout.
- Axios is the HTTP client library we use in the web to talk to REST APIs; it keeps things simple by handling the details of sending and receiving JSON so the UI code does not have to repeat that work. Benefits of Axios over raw
fetchinclude centralized configuration of base URLs and headers, automatic JSON parsing/serialization, and built-in hooks for handling errors, cancellations, and retries. In this project that meansWorkflowState.tsxcan rely on helpers likeupdateCurrentState/updatePieceinstead of duplicating URLs or JSON logic, and we have a single place for surfaces errors before they hit the UI. - A client library is a reusable set of functions that wraps low-level protocols (like HTTP) so developers can interact with remote services using clean function calls, in their programming language of choice, instead of handling bytes, headers, or parsing manually.
While the UI is similar at a surface level to other craft journaling applications, the main differences are under the hood:
- Customizable, potentially non-linear workflows. For some pieces you'll carve first, for others you'll slip first. For others, there might be multiple rounds of each.
- Opinionated data model with immutable stage data for your piece's unique journey and your growth-minded journey as a potter. You can't change the past, so keep moving forward. (Administrative bulk data cleaning is still allowed! And when you find a photo from an earlier stage later, the rewind feature lets you navigate back to that historical state to attach it — without altering the piece's actual history.)
- Data normalization around every piece's history for richer and more reliable single piece and multi-piece analysis.
- Systematically answer questions like "How many pieces do I lose in the firing stage by glaze type?" or "How often do I ruin a piece during trimming?"
Before cloning, ensure the following are installed on your system:
| Tool | Required | Install |
|---|---|---|
| OS | Ubuntu 22.04+ or Debian 12+ (WSL2 on Windows works; macOS untested) | — |
| Bazelisk | Yes — aliased as bazel; downloads Bazel 8.5.1 automatically via .bazelversion |
Bazelisk releases or brew install bazelisk |
curl |
Yes — used by gz_setup to bootstrap RTK |
apt install curl |
git |
Yes | apt install git |
Python (3.12) and Node (22) are managed hermetically by Bazel — no manual installs needed once Bazelisk is present.
VS Code users: install Docker Desktop (Windows/macOS) or Docker Engine (Linux) and the Dev Containers extension, then open the repo and choose Reopen in Container — the devcontainer pre-installs all prerequisites automatically.
The devcontainer pre-forwards backend ports 8080–8087 and Vite ports 5173–5180. These ranges match the authorized origins registered in the Google OAuth client, and support up to 8 simultaneous worktree dev stacks. Running more than 8 concurrent gz_start instances inside the container is not supported — use the host environment instead if you need more.
This section is for folks who just want to fire up the whole stack quickly and start poking around the app.
source env.sh
gz_setup # first-time only: creates venv, installs deps, runs migrations
gz_start # starts backend + web via the Bazel-run launcherUse these shortcuts once you've sourced env.sh; they wrap common CLI sequences so you can focus on implementing features instead of hunting for the right flags. The env.sh script sets up Python/Node paths, loads useful aliases (gz_setup, gz_start, etc.), and keeps environment-specific tweaks (like log rotation and virtualenv activation) centralized, so every developer runs commands against the same configuration without manually sourcing multiple files.
Source the file to load all shortcuts into your shell:
source env.shVS Code / Cursor: the repo ships terminal profiles in .vscode/settings.json for Linux and macOS that automatically source env.sh in every new integrated terminal. Linux uses bash; macOS uses zsh with a repo-owned .vscode/.zshrc. The venv is activated and gz_* helpers are available from the moment the terminal opens.
AI coding agents (Claude Code, Codex, Cursor agent): a companion script env-agent.sh provides a silent, lightweight bootstrap (venv activation + .env.local loading) for non-interactive shells. Claude Code picks it up via .claude/settings.json; Codex and other agents inherit it through BASH_ENV when launched from an env.sh-sourced terminal. Prefer repo-local worktrees under .agent-worktrees/... instead of /tmp; the bootstrap detects the active git worktree root automatically and falls back to the main checkout's .env.local and .venv when the worktree does not have its own yet. gz_setup reuses the shared .venv and web/node_modules by default, and gz_setup --isolated creates worktree-local dependency installs when a branch is changing Python or Node packages. Keep repo-local Codex-specific config in .agent-config/codex/ rather than .codex, which may be reserved by the local Codex installation. See docs/agents/dev.md for details.
Glaze uses a high-level orchestration workflow inspired by the Get Shit Done (GSD) philosophy, but adapted for non-developer QoL and safety with non-frontier models:
/dream: High-level vision and milestone orchestration. Use this to describe a broad feature or user story. The agent will use Plan Mode to break the vision into logical sub-tasks, create a GitHub Milestone, and spawn sub-agents to author specific specs./spec: Detail-oriented issue authoring. Each sub-task from the dream is turned into a precise GitHub issue with problem motivation, proposed solution, and acceptance criteria./do: Execution. When you want an agent to implement an issue or start a PR-sized change, use the explicit issue flow:/do #292./deps: Bazel dependency audit. Use this when you want an agent to inspect the rules_oci image target plus test and lint graphs for unexpected dependencies before planning cleanup work./pm: Session-level communication mode. Use this when the main audience is less technical contributors and you want the assistant to explain choices in terms of user value, trade-offs, and constraints instead of file-by-file edits.
- Normative Content in GitHub: Unlike some agent-first workflows that store state in local markdown files, Glaze keeps all normative requirements and status in GitHub Issues, Milestones, and PRs. This minimizes hallucinations from non-frontier models by using GitHub as the source of truth and ensures that the project remains accessible to human non-developer contributors via the standard GitHub UI.
- Flexible Verification: Verification can happen synchronously (as part of the
/docycle where the agent runs tests before pushing) or asynchronously in bulk using the/audit(performance/flakiness),/cover(coverage), and/deps(Bazel dependency graph audit) skills.
When you run /do #292, the agent will create a branch like issue/292-vibe-coding-flow and a repo-local worktree like .agent-worktrees/codex/issue-292-vibe-coding-flow before it analyzes or edits anything.
The agent should immediately print a copy-friendly line:
Worktree: /home/phil/code/glaze/.agent-worktrees/codex/issue-292-vibe-coding-flow
Open a dedicated terminal tab for that worktree and jump into it with:
gz_cd 292From there, use the normal helpers:
gz_setup
gz_startEach agent gets its own worktree under .agent-worktrees/<agent>/..., and each
issue gets its own branch. Keep one terminal per worktree so gz_start,
gz_stop, logs, and port files stay scoped to the right code checkout.
After the PR is merged or abandoned, stop any servers from that worktree's terminal and clean up the local checkout:
gz_stop
git worktree remove .agent-worktrees/codex/issue-292-vibe-coding-flow
git worktree prune
git branch -d issue/292-vibe-coding-flowUse git branch -D only for an abandoned unmerged branch after confirming the
work is no longer needed.
Keep local-only settings in .env.local files; they are gitignored by default:
.env.local(repo-wide defaults)web/.env.local(web-only overrides)
source env.sh automatically loads both (in that order) so you can inject Cloudinary/API config without committing secrets.
Use the checked-in templates:
cp .env.example .env.local
cp web/.env.example web/.env.local| Command | Description |
|---|---|
gz_setup |
Setup helper: reuses shared deps by default in repo-local worktrees, or use gz_setup --isolated for fresh worktree-local .venv and web/node_modules. Also runs DB migrations and installs Node via nvm if needed. |
| Command | Description |
|---|---|
gz_start |
Start backend and web via the Bazel-run launcher. Rotates old logs before starting. |
gz_stop |
Stop both servers. |
gz_status |
Show whether backend and web are running. |
gz_logs [backend|web] |
Tail logs. Omit argument to tail both. |
Logs are written to .dev-logs/ and rotated with a timestamp on each gz_start.
When changing large download endpoints, run the app locally with gz_start,
trigger the same download three times in the browser, and watch the backend RSS:
BACKEND_PID=$(pgrep -f "uvicorn.*$(cat .dev-pids/backend.port)")
watch -n 1 "ps -o pid,rss,vsz,cmd -p ${BACKEND_PID}"RSS may stay at a high-water mark after the first run, but repeated same-size
downloads should plateau rather than ratchet upward. For ASGI production parity,
repeat the check against Docker/staging and watch the Gunicorn/Uvicorn worker
RSS; large StreamingHttpResponse bodies should use async iterators.
| Command | Description |
|---|---|
gz_test |
Run all tests via Bazel (bazel test --test_output=errors //...) — CI-aligned, incremental. |
| Command | Description |
|---|---|
gz_lint |
Run all linters via Bazel (bazel build --config=lint //...) — CI-aligned. |
Prefer Python for standalone dev tooling when the dependency graph allows it. Use the JS tool path under web/scripts/ when the tool is naturally coupled to the web dependency graph or when the needed package exists in npm but not pip. Wire those scripts through web/BUILD.bazel with js_binary and add a vitest_test when you want the tool itself covered by tests. web/scripts/generate-types.mjs and web/scripts/coverage-audit.mjs are the current examples.
Run gz_help to print the full list of shortcuts at any time.
If you prefer to install dependencies and run servers yourself, follow these explicit commands instead of relying on the helper script.
# Backend
bazel run @uv//:uv -- sync
bazel run @uv//:uv -- run python manage.py migrate
uvicorn backend.asgi:application --port 8080 --reload
# Web (separate terminal)
cd web
bazel run @nodejs_linux_amd64//:npm -- install
bazel run @nodejs_linux_amd64//:npm -- run devUse gz_test for the full suite. The package READMEs above describe each area in more detail.
Before committing — auto-fix Python formatting and fixable lint issues:
source env.sh && gz_format
# equivalent to: ruff format . && ruff check --fix .Glaze uses agent workflows for planning, implementation, offline analysis, and documentation. Use the skill that matches the task:
/dream: define a broad feature or product direction and let the agent break it into milestones and sub-issues./spec: turn a specific idea into a focused issue with scope, motivation, and acceptance criteria./do #<issue>: implement a scoped issue on a repo-local worktree and produce the code change./audit: run the performance and flakiness audit workflow for tests./cover: run the coverage audit workflow./deps: inspect Bazel dependency graphs for unexpected or overly broad dependencies./docs: assess and update human-facing READMEs to keep them aligned with the codebase./stories: auto-generate and update Storybook stories for frontend components.
Use /dream and /spec when the work is still being shaped. Use /do when the task is ready to implement. Use the asynchronous audit skills when you want analysis without blocking the main development loop.
Interactive component stories are published to GitHub Pages via Storybook:
https://shaoster.github.io/glaze/storybook/
Run locally with gz_story. See web/README.md for details.
Deployment details, GitHub Actions workflows, and environment variables live in docs/ci-cd.md.
backend/ Django project settings, root URL config
api/ Models, serializers, views, tests
model_factories.py Auto-generates GlobalModel subclasses from workflow.yml
web/
src/
util/
generated-types.ts Auto-generated OpenAPI types (gitignored)
types.ts Domain types/constants derived from generated-types.ts
api.ts HTTP calls; wire-type → domain-type mapping
workflow.ts Workflow helpers loaded from workflow.yml
components/ React components
App.tsx Root component with MUI dark theme
workflow.yml Source of truth for piece states and valid transitions
env.sh Development shell helpers
docker-compose.yml Production stack: web + Postgres
docker-entrypoint.sh Container startup: migrate, collectstatic, exec Gunicorn
deploy.sh SSH deploy helper (called by gz_deploy)
.env.production.example Template for droplet secrets (copy to .env)
render.yaml Render Blueprint for managed PaaS deployment
The workflow state machine and all valid transitions are defined in workflow.yml. Both the backend and web derive state names and transition rules from this file — nothing is hardcoded elsewhere.
workflow.yml also contains two optional sections beyond the state list:
globals— named domain types backed by Django models. Each entry drives both the backend and frontend:api/model_factories.pyauto-generates the Django model class at import time (amakemigrationsrun is all that is needed to add a new global), and the frontend reads the same declaration to render pickers and resolve display fields. Setfactory: falsefor globals whose model is hand-written (currently onlypiece).custom_fields(per-state) — state-specific fields declared using the embedded DSL. Seeapi/README.mdandweb/README.mdfor more details.