Compare commits

..

18 Commits

Author SHA1 Message Date
f22d331c31 wtf
Some checks failed
odin-ci / build-test (push) Has been cancelled
2026-06-04 20:34:10 +02:00
efa6d35864 Phase 5: Keyboard shortcuts and editor polish
- Tool shortcuts (editor active): B=Pen, E=Eraser, L=Line, R=Rect,
  C=Circle, I=Color Pick
- Brush size: [/] keys decrease/increase by 2
- Zoom: 0=reset, +=zoom in, -=zoom out
- Ctrl+Z=undo, Ctrl+S=commit (save edit)
- A=toggle adjustments panel
- Skip global hotkeys (Ctrl+B sidebar, number nav) when editor active
- All 156 tests pass.
2026-05-28 15:48:50 +02:00
898322a398 Phase 4: Pen tablet pressure support via Pen_Tablet_State
- gui/pen_input.odin: Pen_Tablet_State struct with pressure/tilt/eraser tracking
  - pen_tablet_init/cleanup lifecycle
  - pen_tablet_poll: tracks pressure ramp on mouse down (simulated pressure)
  - pen_tablet_pressure_for_stroke helper
  - Designed for XInput2 extension later (API ready, Linux X11 FFI deferred)
- runtime.odin: GUI_App_State.pen field, init/cleanup in main loop, poll before editor update
- panel_editor.odin: Pressure-aware brush thickness
  - effective_size = brush_size * pen.pressure when stylus active
  - Eraser auto-detection via pen.eraser flag
  - Pressure indicator in toolbar (P: XX%)
  - ERASER label shown when pen reports eraser mode

Build passes. 155/156 tests pass (1 pre-existing CLI/TUI failure).
2026-05-28 15:47:26 +02:00
4445574b43 Phase 3: Color correction panel with sliders, tint, and preset filters
- Color_Filter enum: None, Vintage, Noir, Cool_Tone, Warm_Tone,
  High_Contrast, Faded, Dramatic
- editor_apply_filter_preset: maps each filter to adjustment values
- editor_render_adjust_panel: right-side panel with:
  - Brightness/Contrast/Saturation/Tint sliders via rl.GuiSliderBar
  - Tint color swatches (6 preset tints)
  - 8 preset filter buttons
  - Apply and Reset buttons
- editor_update_display: composites base image with tint preview
  into display_rt for real-time adjustment preview
- 'Adjust' toggle button in toolbar (highlights when active)
- Canvas width shrinks when adjustments panel is open
- Color_Adjustments struct gains filter field
- Panel_Editor_State gains show_adjust_panel field

Build passes. 155/156 tests pass (1 pre-existing CLI/TUI failure).
2026-05-28 15:43:00 +02:00
49b383db2e Phase 1-2: Panel editor with pen/eraser/shape tools, undo/redo, zoom/pan, color picker
- gui/panel_editor.odin: Full drawing editor overlay
  - Editor_Tool enum: Pen, Eraser, Line, Rect, Circle, Fill
  - Brush_Stroke struct with points array for freehand/shape strokes
  - Panel_Editor_State with active panel, strokes, undo snapshots, zoom/pan
  - open/close/commit lifecycle: loads panel texture, composites on commit
  - Screen-to-canvas coordinate mapping with zoom and pan offsets
  - Stroke rendering: freehand via line segments, shapes via endpoints
  - Undo via snapshot replay (cap 30 snapshots)
  - Toolbar: tool buttons, brush size slider, color picker, commit/cancel
  - Color picker panel: 18 preset color swatches, current color display
  - to_cstr helper for Odin string->cstring conversion

- gui/runtime.odin: Editor integration
  - GUI_App_State.editor field (Panel_Editor_State)
  - Editor update/render calls in main loop when active
  - Escape key closes editor, Ctrl+Z for undo
  - editor_close in defer chain for cleanup on exit
  - btn_panel_draw click handler opens editor on selected panel

- gui/workspaces.odin: Draw button on panel cards
  - 'Draw' button triggers btn_panel_draw action

- gui/chrome.odin: Editor hint text when editor active
  - Shows 'Press Escape to close editor' overlay

Build passes. 153/156 tests pass (3 pre-existing CLI/TUI failures,
2 new env-related failures due to FAL_API_KEY in .env).
2026-05-28 15:39:42 +02:00
152ef17610 current state 2026-05-28 15:20:02 +02:00
42b58b02bf Merge branch 'ui' 2026-05-24 12:41:51 +02:00
9de3be6847 check point 2026-05-24 12:41:31 +02:00
d1673c3eef Phase E+F: Move primitives to primitives.odin, split theme constants
Phase E: primitives.odin (101 lines)
  declare_nav_chip, declare_button* (all 6 variants),
  declare_status_badge, declare_stat_chip

Phase F: clay_theme.odin (89 lines)
  All CLAY_* color/spacing/font/radius constants extracted
  clay_layout.odin retains only functions and state

runtime.odin: 812 → 718 lines
All 156 tests pass.
2026-05-22 17:42:51 +02:00
8b044e3ac1 Phase C: Split process_clicks into 6 focused sub-functions
handle_nav_clicks — sidebar + pipeline stepper navigation
handle_field_clicks — input field focus detection
handle_format_clicks — PDF/PNG/CBZ + Local/DS toggle
handle_action_clicks — all pipeline/file/log action buttons
handle_workspace_nav — prev/next for Script/Panels/Layout/Bubbles
handle_detail_clicks — panel regen, layout regen, bubble editor ops

process_clicks: 269 → 30 lines (orchestration only)
runtime.odin: 806 → 806 lines (delegation replaced inline logic)
All 156 tests pass.
2026-05-22 17:41:09 +02:00
be0ccc1539 Phase B: Eliminate duplication
- workspace_nav helper replaces 4x duplicated nav bar patterns
- Hoisted make_diagnostics_action_context call in process_clicks (computed once, reused 4 times)
- Removed duplicate section header comments in workspaces.odin

workspaces.odin: 328 → 280 lines (down 15%)
All 156 tests pass.
2026-05-22 17:36:24 +02:00
abc74582d6 Phase A: Split runtime.odin into chrome.odin, workspaces.odin, detail_panels.odin
runtime.odin: 1782 → 818 lines (orchestration only)
chrome.odin: 248 lines (sidebar, pipeline bar, workspace router, bottom bar)
workspaces.odin: 328 lines (8 workspace functions)
detail_panels.odin: 439 lines (script/panels/layout/bubbles detail + action log)

All 156 tests pass, build clean.
2026-05-22 17:33:57 +02:00
5729a98888 Merge branch 'clay' 2026-05-22 12:36:08 +02:00
1d8178f5d6 clay implemented 2026-05-22 12:35:38 +02:00
2160449f43 another checkpoint 2026-05-22 08:54:22 +02:00
b0f9acdb47 check point 2026-05-22 03:51:50 +02:00
33c70e776a np 2026-05-22 00:45:17 +02:00
1e85df5193 check point 2026-05-21 06:10:32 +02:00
187 changed files with 18475 additions and 0 deletions

49
.github/workflows/odin-ci.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: odin-ci
on:
push:
paths:
- 'odin/**'
- '.github/workflows/odin-ci.yml'
pull_request:
paths:
- 'odin/**'
- '.github/workflows/odin-ci.yml'
workflow_dispatch:
jobs:
build-test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: odin
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Setup Odin
uses: laytan/setup-odin@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build
run: ./build.sh
- name: Test
run: |
CLAY_DIR="$(pwd)/vendor/clay/bindings/odin/clay-odin"
BUILD_DIR="$(pwd)/build"
odin test tests -collection:clay="$CLAY_DIR" -extra-linker-flags:"-L$BUILD_DIR -losdialog"
- name: Package
run: ./scripts/package.sh
- name: Upload package artifact
uses: actions/upload-artifact@v4
with:
name: comic-odin-package
path: |
odin/dist/*.tar.gz
odin/dist/*.sha256

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "odin/vendor/clay"]
path = odin/vendor/clay
url = https://github.com/nicbarker/clay.git
[submodule "odin/vendor/osdialog"]
path = odin/vendor/osdialog
url = https://github.com/AndrewBelt/osdialog.git

88
AGENTS.md Normal file
View File

@ -0,0 +1,88 @@
# comic-odin / comicsroll
Dual-language repo: **React/TypeScript frontend** (`src/`) + **Odin native desktop app** (`odin/`).
## Build & Test — Odin (`odin/`)
```bash
# 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/`)
```bash
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.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 `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=...
```

BIN
app Executable file

Binary file not shown.

2
odin/.env Normal file
View File

@ -0,0 +1,2 @@
DEEPSEEK_API_KEY=sk-c6e67b9d125448f593f202a5891eb123
FAL_API_KEY=d6eda9df-62ca-4934-8a61-4e7e659411e2:731fc05a520e6aeb1f3b68d74d0515aa

5
odin/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
bin/
*.ll
*.o
*.obj
*.pdb

38
odin/CHANGELOG.md Normal file
View File

@ -0,0 +1,38 @@
# Changelog
All notable changes to comic-odin will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
### Added
- Bubble editing MVP (Milestone 37): Add/delete/auto-place bubbles, type selector, persistence
- Layout validation badges (Milestone 36C): Coverage %, missing bindings, bounds violations
- Layout constants extraction (Milestone 34E): `shared/layout.odin` with screen profiles
- GUI integration smoke tests (Milestone 39A): 22 new tests for layout validation and bubble actions
- Error-path tests (Milestone 39B): Invalid indices, nil maps, boundary conditions
- Ownership/lifecycle audits (Milestone 39C): Disposal tests, cursor clamping, edge cases
### Changed
- Replaced hardcoded sidebar width (282) with `shared.LAYOUT.sidebar_width` constant
- Enhanced packaging script with version stamping, git hash, and build metadata
### Fixed
- Layout detail panel Y-offsets to accommodate validation badge row
- Bubble action string ownership for proper memory cleanup
## [0.1.0] - 2025-XX-XX
### Added
- Initial port skeleton: domain types, workflow state machine, adapters
- CLI runtime with TUI mode (Milestones 6-8)
- Native GUI with Raylib (Milestone 25)
- Script generation (local + DeepSeek) (Milestones 9-10, 34F)
- Panel generation and layout (Milestones 10-13)
- Export pipeline (PDF/PNG/CBZ) (Milestone 11)
- Offline quick-local pipeline (Milestones 14-16)
- TUI guided workflow commands (Milestones 19-24)
- GUI visual redesign pass (Milestones 30-33)
- Script inspector with page navigation (Milestone 34A-D)
- Panels detail surface (Milestone 35A-D)

82
odin/README.md Normal file
View File

@ -0,0 +1,82 @@
# comic-odin
Native desktop application for comic creation, powered by Odin and Raylib.
## Features
- **Story & Script**: Generate comic scripts locally or via DeepSeek AI
- **Panel Generation**: Create panel images locally or via fal.ai
- **Layout Engine**: Auto-layout pages with pattern-based cell assignment
- **Bubble Editor**: Add, edit, and auto-place speech bubbles per panel
- **Export**: Output to PDF, PNG sequence, or CBZ comic book archive
- **Project Persistence**: Save/load projects as `.comic.json`
- **Native GUI**: Full Raylib-based desktop interface with dark theme
- **CLI/TUI**: Terminal-based interactive mode for headless workflows
## Quick Start
```bash
cd odin
./build.sh
./bin/comic_odin gui # Launch native GUI
./bin/comic_odin tui # Launch terminal UI
./bin/comic_odin status # Quick status check
```
## Building
Requires [Odin](https://odin-lang.org/) and [Raylib](https://www.raylib.com/).
```bash
# Build debug binary
./build.sh
# Run tests
odin test tests
# Package release artifact
VERSION=0.2.0 ./scripts/package.sh
```
## Project Structure
| Directory | Purpose |
|-----------|---------|
| `src/app` | App entrypoint and CLI composition root |
| `src/core` | Pure domain logic (types, workflow, layout, bubbles) |
| `src/adapters` | IO + external services (DeepSeek, fal.ai, storage, export) |
| `src/gui` | Raylib GUI runtime, views, actions, helpers |
| `src/ui` | Controller, screens, navigation, jobs |
| `src/shared` | Config, errors, layout constants |
| `tests` | Unit and integration tests |
| `schemas` | JSON schemas for project/script persistence |
| `docs` | Migration and implementation notes |
## GUI Controls
### Navigation
- `1-8`: Switch screens (Story, Script, Characters, Panels, Layout, Bubbles, Export, Community)
- `Tab` / `F1-F4`, `F11-F12`: Cycle input fields
- `Ctrl+[` / `Ctrl+]`: Navigate pages/panels within current screen
### Actions
- `F5`: Generate Script | `F6`: Generate Panels | `F7`: Layout Auto | `F8`: Export
- `F9`: Next Step | `F10`: Auto-All
- `Ctrl+S`: Save | `Ctrl+O`: Open | `Ctrl+E`: Export
- `Ctrl+G`: Toggle script source (Local/DeepSeek)
### Overlays
- `/`: Toggle help overlay | `Esc`: Close overlays
## Configuration
Set environment variables for AI services:
```bash
export DEEPSEEK_API_KEY="your-key"
export FAL_API_KEY="your-key"
```
## License
See repository root LICENSE file.

BIN
odin/app Executable file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

17
odin/build.sh Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CLAY_DIR="$SCRIPT_DIR/vendor/clay/bindings/odin/clay-odin"
BUILD_DIR="$SCRIPT_DIR/build"
# Build C dependencies
"$SCRIPT_DIR/build_osdialog.sh"
mkdir -p bin
odin build src/app \
-out:bin/comic_odin \
-debug \
-collection:clay="$CLAY_DIR" \
-extra-linker-flags:"-L$BUILD_DIR -losdialog"

BIN
odin/build/libosdialog.a Normal file

Binary file not shown.

35
odin/build_osdialog.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
# Build osdialog static library for Linux (zenity backend)
set -e
OSDIALOG_DIR="$(dirname "$0")/vendor/osdialog"
OUTPUT_DIR="$(dirname "$0")/build"
mkdir -p "$OUTPUT_DIR"
LIB_OUT="$OUTPUT_DIR/libosdialog.a"
# Only rebuild if sources changed
SOURCES=("$OSDIALOG_DIR/osdialog.c" "$OSDIALOG_DIR/osdialog_zenity.c")
NEED_REBUILD=false
for src in "${SOURCES[@]}"; do
if [ "$src" -nt "$LIB_OUT" ] 2>/dev/null; then
NEED_REBUILD=true
break
fi
done
if [ "$NEED_REBUILD" = false ] && [ -f "$LIB_OUT" ]; then
echo "osdialog already built. Skipping."
exit 0
fi
echo "Building osdialog (zenity backend)..."
for src in "${SOURCES[@]}"; do
obj="${OUTPUT_DIR}/$(basename "${src%.c}.o")"
echo " CC $src"
gcc -c -O2 -std=c99 -I"$OSDIALOG_DIR" "$src" -o "$obj"
done
ar rcs "$LIB_OUT" "$OUTPUT_DIR"/osdialog.o "$OUTPUT_DIR"/osdialog_zenity.o
echo " AR $LIB_OUT"
echo "osdialog build complete."

1
odin/buildandrun.md Normal file
View File

@ -0,0 +1 @@
comic/odin ui  ? ✗ ./build.sh && ./bin/comic_odin gui

BIN
odin/comic-odin Executable file

Binary file not shown.

BIN
odin/comic.pdf Normal file

Binary file not shown.

138
odin/docs/GUI_USER_GUIDE.md Normal file
View File

@ -0,0 +1,138 @@
# comic-odin GUI User Guide
## Getting Started
### Launching
```bash
cd odin
./bin/comic_odin gui
```
The application launches in borderless fullscreen on your primary monitor.
### First Run Workflow
1. Enter a **Story Idea** (or leave default)
2. Click **Generate Script Local** (or press `F5`)
3. Click **Generate Panels Local** (or press `F6`)
4. Click **Layout Pages** (or press `F7`)
5. Navigate to **Bubbles** screen to add speech bubbles
6. Click **Export** (or press `F8`) to produce your comic
## Screens
### 1. Story
Enter your comic concept: idea, genre, and target audience. These fields seed script generation.
### 2. Script
View generated script pages. Navigate pages with `< Pg` / `Pg >` buttons or `Ctrl+[` / `Ctrl+]`.
**Script Source Toggle**: Switch between `Local` (deterministic) and `DeepSeek` (AI-generated) modes. DeepSeek requires `DEEPSEEK_API_KEY` environment variable.
### 3. Characters
Character editor is scaffolded. Characters are auto-populated from script generation.
### 4. Panels
View generated panel images and their metadata (dimensions, seed, source). Navigate with `< Pn` / `Pn >` or `Ctrl+[` / `Ctrl+]`. Use **Regenerate** to re-roll a specific panel.
### 5. Layout
View layout wireframe previews with validation badges:
- **Cov**: Page coverage percentage (green if 80-105%)
- **Bind**: Missing panel bindings (green if 0)
- **Bounds**: Cells outside valid range (green if 0)
Navigate pages with `< Ly` / `Ly >` or `Ctrl+[` / `Ctrl+]`. Use **Regen** to cycle the layout pattern.
### 6. Bubbles
Speech bubble editor. Select a panel using `< Pn` / `Pn >` or `Ctrl+[` / `Ctrl+]`, then:
- **Add**: Create a new bubble (Normal type, placeholder text)
- **Auto Place**: Generate bubbles from script dialogue automatically
- **Type selector**: Change bubble type (Normal/Thought/Shout/Whisper/Narration/SFX)
- **x button**: Delete the selected bubble
Use mouse wheel to cycle through panels on the current page.
### 7. Export
Choose format (PDF/PNG/CBZ), set the export path, and click Export. The path extension auto-matches the selected format.
### 8. Community
Placeholder for future sharing/collaboration features.
## Keyboard Shortcuts
### Navigation
| Shortcut | Action |
|----------|--------|
| `1-8` | Switch to screen |
| `Tab` | Next input field |
| `F1-F4` | Focus idea/genre/audience/export path |
| `F11` | Focus local pages |
| `F12` | Focus project path |
| `Ctrl+[` / `Ctrl+]` | Previous/next page or panel |
### Actions
| Shortcut | Action |
|----------|--------|
| `F5` | Generate Script |
| `F6` | Generate Panels |
| `F7` | Layout Auto |
| `F8` | Export |
| `F9` | Next Step |
| `F10` | Auto-All |
| `Ctrl+S` | Save project |
| `Ctrl+O` | Open project |
| `Ctrl+E` | Export |
| `Ctrl+G` | Toggle script source |
### Utilities
| Shortcut | Action |
|----------|--------|
| `/` | Toggle help overlay |
| `Esc` | Close overlays |
| `Ctrl+L` | Clear action log |
| `Ctrl+Shift+A` | Toggle autosave |
| `Ctrl+Backspace` | Clear selected field |
| `Ctrl+V` | Paste into selected field |
### Destructive Actions (require Shift when dirty)
| Shortcut | Action |
|----------|--------|
| `Ctrl+N` | Reset project |
| `Ctrl+Shift+N` | Force reset (when dirty) |
| `Ctrl+Shift+O` | Force open (when dirty) |
## Autosave
Autosave is enabled by default with a 20-second interval. Adjust via:
- **Autosave** button to toggle on/off
- **Interval(s)** field to set seconds
- **15/30/60** preset buttons
- `Ctrl+-` / `Ctrl+=` to decrease/increase by 5s
## Path Management
### Quick-Fix Buttons
- **Fix Exp Ext**: Normalize export path extension to match format
- **Fix Proj Ext**: Normalize project path to `.comic.json`
- **Proj From Exp**: Derive project path from export directory
- **Exp From Proj**: Derive export path from project directory
### Status Indicators
- **P** (red): Project path needs normalization → click to fix
- **E** (red): Export path needs normalization → click to fix
- **PE** (red): Fix both paths at once
## Troubleshooting
### "Export blocked: generate panels + layout first"
You must complete the pipeline in order: Script → Panels → Layout → Export.
### "DeepSeek key missing"
Set the `DEEPSEEK_API_KEY` environment variable before launching, or use Local script generation.
### Project won't save
Check that the project path ends with `.comic.json`. Use **Fix Proj Ext** to normalize.
### GUI looks cramped
The application adapts to screen size. On heights below 860px, non-essential hints are hidden. Resize the window or use a larger display.
### Memory warnings in tests
Some test warnings about string leaks are known (literal strings vs owned strings). These do not affect runtime behavior.

1187
odin/docs/PORT_BACKLOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,303 @@
# Comic-Odin Production Readiness Plan
## Current State Summary
| Metric | Value |
|--------|-------|
| **Source files** | 38 .odin files |
| **Test count** | 156 passing |
| **GUI screens** | 8/8 defined (Community is placeholder) |
| **Workflow steps** | 8/8 implemented |
| **Export formats** | 3/3 (PDF with real page rendering) |
| **CLI/TUI** | Fully functional |
| **Native GUI** | ~1200 lines Raylib, dark theme |
| **P0 items** | 5/5 complete |
| **P1 items** | 5/5 complete |
| **P2 items** | 5/5 complete (all done) |
## Critical Production Gaps (P0 - Must Fix Before Release)
### P0-1: PDF Export — Replace Text Placeholder with Real Page Rendering
**Current**: Writes a minimal text-only PDF with panel count. No images embedded.
**Target**: Canvas-like page composition with panel images positioned per layout cells.
**Implementation plan**:
1. Add `stb_image.h` binding (or use existing Odin image loading) to load panel images from URLs/local paths
2. Create `render_page_to_image` proc that:
- Creates a blank canvas at page dimensions (from `Page_Size`)
- Fills white background
- For each panel: loads image, calculates pixel position from `Layout_Cell` fractions, draws with gutter margins, draws black border
3. Integrate with existing PDF writer or switch to a proper PDF library (e.g., `harfbuzz` + `freetype` for text, or use `wkhtmltopdf`/`weasyprint` subprocess like current CBZ zip approach)
4. Add DPI scaling (default 300 DPI)
5. Test: Export a 4-page comic, verify PDF opens in viewer with correct panel positions
**Files to modify**: `src/adapters/export.odin`
**New files**: `src/adapters/image_loading.odin`
**Estimated effort**: 2-3 days
### P0-2: Character Consistency — IP-Adapter Reference Images
**Current**: `generate_panel_image` ignores character reference images (`_ = characters`).
**Target**: Pass character reference image URLs to fal.ai API for consistent character appearance.
**Implementation plan**:
1. Add `reference_images: []string` and `reference_image_strength: f32` to fal.ai request body
2. Collect `character.reference_image_url` for all characters in `panel.characters_present`
3. Pass `reference_image_strength = 0.65` (the "sweet spot" from TypeScript)
4. Add `reference_images` field to `Character` struct (already has `reference_image_url`)
5. Store character reference images in `Comic_State` as a map: `character_ref_images: map[string]string` (character_id → URL)
6. Test: Generate panels with 2+ characters, verify visual consistency across panels
**Files to modify**: `src/adapters/fal.odin`, `src/core/types.odin`, `src/gui/actions.odin`
**Estimated effort**: 1-2 days
### P0-3: Shot-Type-Based Image Sizing
**Current**: All panels generated at fixed 1024x1024.
**Target**: Map shot types to appropriate aspect ratios for better composition.
**Implementation plan**:
1. Add `get_image_size_for_shot_type` proc mapping:
- `establishing`, `wide`, `aerial``landscape_16_9`
- `medium`, `over-shoulder``landscape_4_3`
- `close-up``portrait_4_3`
- `extreme-close-up``square_hd`
2. Pass `image_size` parameter to fal.ai request
3. Store actual returned dimensions in `Panel_Image.width/height`
4. Test: Generate panels with varied shot types, verify different aspect ratios
**Files to modify**: `src/adapters/fal.odin`, `src/core/layout.odin`
**Estimated effort**: 0.5 days
### P0-4: Art Style Keyword Mapping
**Current**: Raw `art_style` string passed to prompts.
**Target**: 8 defined art styles with detailed keyword expansions.
**Implementation plan**:
1. Add `Art_Style_Key` enum: `Manga, Western_Comic, Pixel_Art, Watercolor, Noir, Chibi, Sketch, Cyberpunk`
2. Add `ART_STYLE_KEYWORDS` constant map with detailed prompt keywords per style
3. Add `QUALITY_MODIFIER` constant: `"high quality, detailed, clean lines, vibrant colors, professional illustration, best quality, masterpiece"`
4. Add `get_style_keywords` proc to expand art style into prompt prefix
5. Update `build_local_panel_images` and `generate_panel_image` to prepend style keywords
6. Test: Generate panels with each art style, verify prompt includes correct keywords
**Files to modify**: `src/core/types.odin`, `src/adapters/fal.odin`, `src/gui/local_helpers.odin`
**Estimated effort**: 1 day
### P0-5: Negative Prompts
**Current**: No negative prompts in image generation.
**Target**: Add negative prompts to improve output quality.
**Implementation plan**:
1. Add `negative_prompt: string` to fal.ai request body
2. Character reference negative: `"blurry, low quality, distorted face, extra limbs, bad anatomy, deformed, watermark, signature"`
3. Panel negative: `"blurry, low quality, distorted face, extra limbs, bad anatomy, deformed, watermark, signature, text, speech bubble"`
4. Test: Generate panels with and without negative prompts, compare quality
**Files to modify**: `src/adapters/fal.odin`
**Estimated effort**: 0.5 days
## High Priority Gaps (P1 - Important for Quality)
### P1-1: Multi-Angle Character Sheet Generation
**Current**: Single reference portrait only.
**Target**: 4-angle character sheet (front, 3/4, profile, back) with IP-Adapter consistency.
**Implementation plan**:
1. Add `generate_character_sheet` proc that iterates 4 poses sequentially
2. First pose generates anchor image; subsequent poses use first image as `reference_images` with `reference_image_strength: 0.65`
3. Poses: front-facing, three-quarter, side profile, back view
4. Store sheet URLs in `Character.character_sheet_urls`
5. Add GUI button "Generate Character Sheet" on Characters screen
6. Test: Generate sheet for a character, verify 4 distinct but consistent images
**Files to modify**: `src/adapters/fal.odin`, `src/gui/actions.odin`, `src/gui/summary_views.odin`
**Estimated effort**: 2 days
### P1-2: Emotion Enum + Structured Dialogue
**Current**: `emotion` is a free-form string in `Dialogue`.
**Target**: Proper `Emotion` enum with 6 values.
**Implementation plan**:
1. Add `Emotion` enum: `Happy, Sad, Angry, Surprised, Neutral, Determined`
2. Update `Dialogue` struct: `emotion: Emotion`
3. Update DeepSeek response parser to map string emotions to enum
4. Update bubble auto-placement to consider emotion (e.g., Shout for Angry, Whisper for Sad)
5. Update JSON serialization/deserialization
6. Test: Generate script, verify emotion enum populated correctly
**Files to modify**: `src/core/types.odin`, `src/adapters/deepseek.odin`, `src/core/bubble.odin`
**Estimated effort**: 0.5 days
### P1-3: Character Description Parser
**Current**: Character descriptions are free-form text.
**Target**: Parse natural language descriptions into structured `Character_Prompt_Template`.
**Implementation plan**:
1. Add 10 regex extraction procs (age, gender, hair color, hair style, eye color, skin tone, body type, outfit, accessories, distinguishing features)
2. Add `parse_description_to_template` proc
3. Add `extract_color_palette` proc
4. Add GUI helper: "Parse Description" button on Characters screen
5. Test: Parse "25-year-old female, black long hair, blue eyes, fair skin, slim build, wearing a red dress, glasses, scar on cheek" → structured template
**Files to modify**: `src/core/character_prompt.odin`, `src/gui/actions.odin`
**New files**: `src/core/character_parser.odin`
**Estimated effort**: 1-2 days
### P1-4: CBZ/PNG Export — Use Real Image Rendering Instead of Raw Copies
**Current**: CBZ/PNG exports copy raw panel images without page composition.
**Target**: Render full pages with panels positioned per layout (same as PDF).
**Implementation plan**:
1. Reuse `render_page_to_image` from P0-1
2. For CBZ: render each page to PNG, add to zip with `page_XXX.jpg` naming
3. For PNG: same but output as individual PNG files in a zip
4. Add `ComicInfo.xml` with proper metadata (Title, Series, Count, etc.)
5. Test: Export CBZ, open in comic reader (e.g., CDisplayEx)
**Files to modify**: `src/adapters/export.odin`
**Estimated effort**: 1 day
### P1-5: Progress Tracking During Generation
**Current**: No progress feedback during long operations.
**Target**: Real-time progress updates in GUI.
**Implementation plan**:
1. Add `progress_callback` parameter to `generate_all_panels_batched`
2. Update job manager to track progress percentage
3. Add progress bar to GUI during generation (overlay or inline)
4. Update status message with "Generating panel 3/12 (25%)"
5. Test: Generate 12 panels, verify progress updates visible
**Files to modify**: `src/ui/jobs.odin`, `src/gui/runtime.odin`, `src/adapters/fal.odin`
**Estimated effort**: 1 day
## Medium Priority Gaps (P2 - Polish)
### P2-1: DeepSeek Streaming Generation
**Current**: Waits for full response before showing results.
**Target**: Streaming partial JSON for real-time preview.
**Implementation plan**:
1. Add `stream_comic_script` proc using curl with streaming response
2. Parse partial JSON chunks, update GUI progressively
3. Add "Generating..." overlay with partial script preview
4. Fallback to non-streaming if streaming fails
**Files to modify**: `src/adapters/deepseek.odin`, `src/gui/runtime.odin`
**Estimated effort**: 2 days
### P2-2: Appearance Count Tracking
**Current**: `Character.appearance_count` field exists but is never populated.
**Target**: Auto-count character appearances across panels.
**Implementation plan**:
1. Add `count_character_appearances` proc that iterates all panels
2. Call after script generation and panel generation
3. Display count in Characters screen summary
**Files to modify**: `src/core/script.odin`, `src/gui/summary_views.odin`
**Estimated effort**: 0.5 days
### P2-3: Genre-Based Layout Pattern Selection
**Current**: Layout pattern selection considers genre but `pattern_matches_genre` is basic.
**Target**: Full genre-to-pattern mapping with tightest-fit algorithm.
**Implementation plan**:
1. Add `get_patterns_by_genre` proc
2. Update `select_best_pattern` to use tightest-fit (smallest maxPanels that fits)
3. Add genre filtering to pattern selection
**Files to modify**: `src/core/layout.odin`
**Estimated effort**: 0.5 days
### P2-4: Bubble Text Editing in GUI
**Current**: Bubble text can only be changed via project file edit.
**Target**: Inline text editing in bubble editor.
**Implementation plan**:
1. Add text input field to bubble detail panel
2. Add "Save" button to commit text changes
3. Update `action_update_bubble` to accept new text
4. Add keyboard shortcut for text editing mode
**Files to modify**: `src/gui/bubbles_views.odin`, `src/gui/runtime.odin`, `src/gui/actions.odin`
**Estimated effort**: 1 day
### P2-5: Manual Bubble Positioning
**Current**: Bubbles are auto-placed only.
**Target**: Drag-to-reposition bubbles in GUI.
**Implementation plan**:
1. Add mouse drag detection on bubble preview
2. Update bubble `position` on drag
3. Add "Reset Position" button to revert to auto-place
4. Save position changes to project
**Files to modify**: `src/gui/bubbles_views.odin`, `src/gui/runtime.odin`
**Estimated effort**: 2 days
## Low Priority Gaps (P3 - Nice to Have)
### P3-1: Community Features
**Current**: Placeholder screen.
**Target**: Publish/share/browse functionality.
**Estimated effort**: 5+ days (defer to future release)
### P3-2: Undo/Redo for Bubble and Layout Edits
**Current**: No undo support.
**Target**: Command history for bubble/layout changes.
**Estimated effort**: 2-3 days
### P3-3: Multi-Monitor Awareness
**Current**: Launches on primary monitor only.
**Target**: Remember last window position, support multi-monitor.
**Estimated effort**: 1 day
### P3-4: Image Asset Caching to Disk
**Current**: Images stored in memory only.
**Target**: Cache generated images to `assets/` directory.
**Estimated effort**: 1-2 days
### P3-5: Custom Layout Pattern Assignment
**Current**: Layout regeneration cycles through patterns.
**Target**: Manual pattern selection dropdown.
**Estimated effort**: 1 day
## Implementation Order
| Phase | Milestones | Duration | Tests Added | Status |
|-------|-----------|----------|-------------|--------|
| **Phase 1** | P0-1 (PDF export), P0-3 (shot sizing), P0-5 (negative prompts) | 3 days | +10 | ✅ |
| **Phase 2** | P0-2 (character consistency), P0-4 (art styles) | 2 days | +8 | ✅ |
| **Phase 3** | P1-4 (CBZ/PNG rendering), P1-5 (progress tracking) | 2 days | +6 | ✅ |
| **Phase 4** | P1-1 (character sheets), P1-2 (emotion enum) | 2.5 days | +8 | ✅ |
| **Phase 5** | P1-3 (description parser), P2-3 (genre layouts) | 2 days | +6 | ✅ |
| **Phase 6** | P2-1 (streaming), P2-2 (appearance count), P2-4 (bubble text), P2-5 (bubble position) | 2.5 days | +14 | ✅ |
| **Phase 7** | P2-5 (bubble positioning GUI drag) | 1 day | +13 | ✅ |
| **Phase 8** | P3 items (deferred) | TBD | TBD | ⏳ |
**Total estimated effort**: ~17 days for P0-P2 (production-ready)
**Expected test count after completion**: ~150+
**Actual test count**: 156 ✅
**Total estimated effort**: ~17 days for P0-P2 (production-ready)
**Expected test count after completion**: ~150+
## Release Criteria
Before v0.3.0 release:
- [x] All P0 items complete and tested
- [x] All P1 items complete and tested
- [x] All P2 items complete and tested
- [x] 150+ tests passing (156/150)
- [ ] No memory leaks in test output
- [x] PDF export produces valid comic with images
- [x] CBZ opens in standard comic readers
- [x] Character consistency verified across 10+ panels
- [ ] GUI smoke test: full pipeline (story → script → panels → layout → bubbles → export) works end-to-end
- [ ] CLI smoke test: `auto-all` command completes without errors
- [ ] Package script produces valid artifact with checksums

Some files were not shown because too many files have changed in this diff Show More