comic/AGENTS.md
2026-05-28 15:20:02 +02:00

4.3 KiB
Raw Blame History

comic-odin / comicsroll

Dual-language repo: React/TypeScript frontend (src/) + Odin native desktop app (odin/).

Build & Test — Odin (odin/)

# Build debug binary
./odin/build.sh                          # builds C deps + odin src/app

# Or directly:
odin build odin/src/app -target:linux_amd64 -collection:clay=$(pwd)/odin/vendor/clay/bindings/odin/clay-odin

# Run all tests
odin test odin/tests -target:linux_amd64 -collection:clay=$(pwd)/odin/vendor/clay/bindings/odin/clay-odin

# Run single test
odin test odin/tests -target:linux_amd64 -collection:clay=$(pwd)/odin/vendor/clay/bindings/odin/clay-odin -define:ODIN_TEST_NAMES=tests.<test_proc_name>

# Run binary
./bin/comic_odin gui                     # Raylib GUI
./bin/comic_odin tui                     # Terminal TUI

Build & Test — React Frontend (src/)

npm run dev          # vite dev server
npm run build        # tsc -b && vite build
npm run lint         # eslint . (strictTypeChecked, no any)
npm run typecheck    # tsc --noEmit
npm run test         # vitest run (jsdom, globals)
npm run test:ui      # vitest --ui

Architecture

React (Vite 8, React 19, TypeScript 6, Tailwind 3, Zustand 5)

  • No URL routing — manual useState + zustand workflow.currentStep drives component switching in App.tsx.
  • Single zustand store (src/store/comicStore.ts) with persist middleware.
  • Two API services: DeepSeek (via OpenAI SDK for script gen) and fal.ai (via @fal-ai/client for images).
  • @ path alias./src.
  • verbatimModuleSyntax: true — type-only imports need import type.
  • noUnusedLocals, noUnusedParameters both true.
  • framer-motion, fabric, react-router-dom installed but unused.
  • No test files exist yet (vitest configured and ready).

Odin (Raylib + Clay layout engine + osdialog)

  • Single binary, entry: odin/src/app/main.odinrun_cli_from_process_args().Guigui.run_gui_app()
  • 8 source packages: app (entry), core (domain), shared (config/errors), adapters (DeepSeek/fal/export/storage), ui (controller/screens/jobs), gui (Raylib+Clay UI), osdialog (file dialogs).
  • Clay imported as import clay "clay:." via collection -collection:clay=vendor/clay/bindings/odin/clay-odin.
  • ols.json defines the clay collection path.

Workflow state machine

Story_Input → Generating_Script → Script_Review → Character_Setup →
Generating_Panels → Layout → Speech_Bubbles → Complete

Defined in core/workflow.odin (can_transition()). In the React app, zustand's workflow.currentStep mirrors this.

Clay Layout Gotchas (Critical)

  • SizingPercent() expects 01, not 0100. SizingPercent(55) = 5500% (off-screen). Use SizingPercent(0.55).
  • SizingFit({}) cards + SizingGrow({}) children = invisible. A Fit-height card computes its height from children's initial CloseElement sizes. Grow children have initial height 0, so the card stays tiny. If a card contains a Grow-height child (scroll list, wireframe), override the card height to SizingGrow({}).
  • backgroundColor on image elements generates a covering RECTANGLE. Remove backgroundColor from image elements to let the IMAGE command be visible.
  • Texture pointers (rawptr(tex_ptr)) require reserve(&app.panel_textures, N) after map creation to avoid pointer invalidation on reallocation.
  • Clay v0.14 is vendored at vendor/clay/. The Odin binding is at vendor/clay/bindings/odin/clay-odin/clay.odin.

Raygui Integration

  • rl.GuiTextBox overlays on top of Clay layout. After Clay renders, get element bounds via clay.GetElementData(id).
  • GuiTextBox returns true on click — NOT on text change. Compare buffer length after call to detect edits.
  • rl.EndScissorMode() must be called before rl.GuiTextBox and after to clear stale scissor state.

Memory

  • Odin: persistent_pool (256KB) in gui/runtime.odin for strings that must survive temp allocator resets.
  • Odin: All delete()'d string defaults in new_initial_state() must use strings.clone() to avoid free(): invalid pointer crashes.
  • React: Zustand store persisted to localStorage (comic-creator-storage). partialize excludes ephemeral state.

API Keys (.env)

DEEPSEEK_API_KEY=sk-...
FAL_API_KEY=...
VITE_DEEPSEEK_API_KEY=sk-...
VITE_FAL_KEY=...