4.3 KiB
4.3 KiB
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+ zustandworkflow.currentStepdrives component switching inApp.tsx. - Single zustand store (
src/store/comicStore.ts) withpersistmiddleware. - Two API services: DeepSeek (via OpenAI SDK for script gen) and fal.ai (via
@fal-ai/clientfor images). @path alias →./src.verbatimModuleSyntax: true— type-only imports needimport type.noUnusedLocals,noUnusedParametersbothtrue.framer-motion,fabric,react-router-dominstalled but unused.- No test files exist yet (vitest configured and ready).
Odin (Raylib + Clay layout engine + osdialog)
- Single binary, entry:
odin/src/app/main.odin→run_cli_from_process_args()→.Gui→gui.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
claycollection 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 0–1, not 0–100.SizingPercent(55)= 5500% (off-screen). UseSizingPercent(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 toSizingGrow({}).backgroundColoron image elements generates a covering RECTANGLE. RemovebackgroundColorfrom image elements to let the IMAGE command be visible.- Texture pointers (
rawptr(tex_ptr)) requirereserve(&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 atvendor/clay/bindings/odin/clay-odin/clay.odin.
Raygui Integration
rl.GuiTextBoxoverlays on top of Clay layout. After Clay renders, get element bounds viaclay.GetElementData(id).GuiTextBoxreturnstrueon click — NOT on text change. Compare buffer length after call to detect edits.rl.EndScissorMode()must be called beforerl.GuiTextBoxand after to clear stale scissor state.
Memory
- Odin:
persistent_pool(256KB) ingui/runtime.odinfor strings that must survive temp allocator resets. - Odin: All
delete()'d string defaults innew_initial_state()must usestrings.clone()to avoidfree(): invalid pointercrashes. - React: Zustand store persisted to localStorage (
comic-creator-storage).partializeexcludes ephemeral state.
API Keys (.env)
DEEPSEEK_API_KEY=sk-...
FAL_API_KEY=...
VITE_DEEPSEEK_API_KEY=sk-...
VITE_FAL_KEY=...