Compare commits
18 Commits
ae1cae967b
...
f22d331c31
| Author | SHA1 | Date | |
|---|---|---|---|
| f22d331c31 | |||
| efa6d35864 | |||
| 898322a398 | |||
| 4445574b43 | |||
| 49b383db2e | |||
| 152ef17610 | |||
| 42b58b02bf | |||
| 9de3be6847 | |||
| d1673c3eef | |||
| 8b044e3ac1 | |||
| be0ccc1539 | |||
| abc74582d6 | |||
| 5729a98888 | |||
| 1d8178f5d6 | |||
| 2160449f43 | |||
| b0f9acdb47 | |||
| 33c70e776a | |||
| 1e85df5193 |
49
.github/workflows/odin-ci.yml
vendored
Normal 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
@ -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
@ -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 0–1, not 0–100.** `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=...
|
||||||
|
```
|
||||||
2
odin/.env
Normal 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
@ -0,0 +1,5 @@
|
|||||||
|
bin/
|
||||||
|
*.ll
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.pdb
|
||||||
38
odin/CHANGELOG.md
Normal 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
@ -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/assets/2gcegsLrN8TD4j03CyxLo.jpg
Normal file
|
After Width: | Height: | Size: 389 KiB |
BIN
odin/assets/2pEjv5xq_W3sLl_eBKXt5.jpg
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
odin/assets/3Jl1TM8UmRdrHnD8orOBY.jpg
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
odin/assets/46l-wNnIP46lkFUR3SEtp.jpg
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
odin/assets/55G16W3E5x8x73czZLob8.jpg
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
odin/assets/6U3oQ9eoLsNeGHCCnbx52.jpg
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
odin/assets/8WnwlSeWALOOZlUUXtB35.jpg
Normal file
|
After Width: | Height: | Size: 417 KiB |
BIN
odin/assets/9A2y5RiOc-4-CDb7FDX-z.jpg
Normal file
|
After Width: | Height: | Size: 626 KiB |
BIN
odin/assets/Bdv2I8wkmplzmUHH3uiwU.jpg
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
odin/assets/Bod0ehqYOHm7IIM_8TrsN.jpg
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
odin/assets/CM_buwj12cDqtePWooe79.jpg
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
odin/assets/DZ5gMnv66X5-QwnG11lYe.jpg
Normal file
|
After Width: | Height: | Size: 357 KiB |
BIN
odin/assets/Dz6fu_IwR0-tvOTWO7ccW.jpg
Normal file
|
After Width: | Height: | Size: 394 KiB |
BIN
odin/assets/EX8QU_eu27eeV3Oalyerw.jpg
Normal file
|
After Width: | Height: | Size: 380 KiB |
BIN
odin/assets/F3y1b1f3EzzPpXYI6DeY1.jpg
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
odin/assets/FJVw0TPgGJSyB9GcHi9RZ.jpg
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
odin/assets/GYgTd1B7TcieGrp5TGaii.jpg
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
odin/assets/HTTktLBTq-PJrdpPFjt9e.jpg
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
odin/assets/HWqLtvvJLcNqpasvlUq_K.jpg
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
odin/assets/IK_zAvkpLPuuwYh4jEtAu.jpg
Normal file
|
After Width: | Height: | Size: 439 KiB |
BIN
odin/assets/J_vkLN0IPDteP6le9gCwa.jpg
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
odin/assets/K09uo89t9OA_Ac5-03T60.jpg
Normal file
|
After Width: | Height: | Size: 350 KiB |
BIN
odin/assets/KbiWO4HRdHmFc7RVk8tz3.jpg
Normal file
|
After Width: | Height: | Size: 596 KiB |
BIN
odin/assets/KoX2HMxt-NbNus2jzUJhk.jpg
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
odin/assets/L8rCWVxqZoeMA-v4INxF9.jpg
Normal file
|
After Width: | Height: | Size: 405 KiB |
BIN
odin/assets/LdVN2fUfluDU8AnEnhVdO.jpg
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
odin/assets/M18H8gzQr78AyVbHqQRvp.jpg
Normal file
|
After Width: | Height: | Size: 304 KiB |
BIN
odin/assets/MIB7_CUoSOtcWkGVDZMNf.jpg
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
odin/assets/OjM7dpUq_b5WV1jnOKTx-.jpg
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
odin/assets/PKUrqw26x0bictBGfKUoo.jpg
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
odin/assets/PMnRpKSmJeINBDsLRuxDw.jpg
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
odin/assets/PgqOdRQcUaAEskxy7SoU3.jpg
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
odin/assets/QRzzHh3Z_lwzambb0_Gdy.jpg
Normal file
|
After Width: | Height: | Size: 269 KiB |
BIN
odin/assets/QgVb2D2EAkF46XgyNG05i.jpg
Normal file
|
After Width: | Height: | Size: 369 KiB |
BIN
odin/assets/RTuYLiKv-X7NCeXpD6fER.jpg
Normal file
|
After Width: | Height: | Size: 425 KiB |
BIN
odin/assets/SFUWZB370vG5UR_7E4QZx.jpg
Normal file
|
After Width: | Height: | Size: 428 KiB |
BIN
odin/assets/T49RL0lKhmhAKsM8JX0rd.jpg
Normal file
|
After Width: | Height: | Size: 331 KiB |
BIN
odin/assets/UUd1kSUH9UPmJWzYmX21U.jpg
Normal file
|
After Width: | Height: | Size: 348 KiB |
BIN
odin/assets/UeOTNnJLlD1l1FqEQ_E8d.jpg
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
odin/assets/VG0prZpMFnkfgYlrL4iZD.jpg
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
odin/assets/W9Mhstcb_7ka6_YI9ZiTT.jpg
Normal file
|
After Width: | Height: | Size: 332 KiB |
BIN
odin/assets/XWr3AayLIwUr-stSPFDZr.jpg
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
odin/assets/YccGhV3Yt1kgNCIfTjDFt.jpg
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
odin/assets/Z3cMhd91v6ThsQl_Vn-ph.jpg
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
odin/assets/be1K0ROeKdOueIMNXdApi.jpg
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
odin/assets/c_SZNcI8g7wJpBniBANi_.jpg
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
odin/assets/d8XYp2Yyb5uSK70aY_i5L.jpg
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
odin/assets/dBgJnQBWa_5KrmRhwPzfy.jpg
Normal file
|
After Width: | Height: | Size: 401 KiB |
BIN
odin/assets/df6YUrSA3Rey-HoC5_VE8.jpg
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
odin/assets/dlK6N_EWhA1pyEvglDhvN.jpg
Normal file
|
After Width: | Height: | Size: 305 KiB |
BIN
odin/assets/fTsq0Gg_P9LY2U0JNB4am.jpg
Normal file
|
After Width: | Height: | Size: 407 KiB |
BIN
odin/assets/flivfVOq4rymbyzGPRjum.jpg
Normal file
|
After Width: | Height: | Size: 413 KiB |
BIN
odin/assets/fonts/AdwaitaMono-Regular.ttf
Normal file
BIN
odin/assets/fonts/AdwaitaSans-Regular.ttf
Normal file
BIN
odin/assets/hQPWF8gu4b6zhG-MibArE.jpg
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
odin/assets/hk_V5i0kctqjcr-eeMzOg.jpg
Normal file
|
After Width: | Height: | Size: 296 KiB |
BIN
odin/assets/hwU3YX9GljfeCVCPq84yI.jpg
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
odin/assets/i2RsodqUYTIMPzONyEYCx.jpg
Normal file
|
After Width: | Height: | Size: 385 KiB |
BIN
odin/assets/izlV0OeemZJIArgzJkvY7.jpg
Normal file
|
After Width: | Height: | Size: 443 KiB |
BIN
odin/assets/jTMceH-aye5tdqVL5ULqe.jpg
Normal file
|
After Width: | Height: | Size: 274 KiB |
BIN
odin/assets/jWjNSjl862ZHNsBII6PK0.jpg
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
odin/assets/kTupPN30lFeuPRVwWELwI.jpg
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
odin/assets/ky3eRf3sxaOBGUNWTSZDx.jpg
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
odin/assets/lLXSWYxVFUhmvOcxfuVoI.jpg
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
odin/assets/n7KVB2LeoAIEmtQMbnOjp.jpg
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
odin/assets/naOK8Gd8PESgsQaHvP2xx.jpg
Normal file
|
After Width: | Height: | Size: 379 KiB |
BIN
odin/assets/p2RXMpWkJE9u2T2nz-oJ6.jpg
Normal file
|
After Width: | Height: | Size: 305 KiB |
BIN
odin/assets/p_aV09rkFmmsTQLQmHX6I.jpg
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
odin/assets/pfwQSWsEGG22ezSjEor1S.jpg
Normal file
|
After Width: | Height: | Size: 481 KiB |
BIN
odin/assets/qWmJ1EcBuGyIkWc2Cry4V.jpg
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
odin/assets/qspLI2_R3yS_PdSI_hD7T.jpg
Normal file
|
After Width: | Height: | Size: 388 KiB |
BIN
odin/assets/shhHdWzvEUXFEFa0WpBUb.jpg
Normal file
|
After Width: | Height: | Size: 313 KiB |
BIN
odin/assets/sydqHbpgUP3AbEB2KRIe1.jpg
Normal file
|
After Width: | Height: | Size: 382 KiB |
BIN
odin/assets/tY5opo8r3AHg7D1PVClJz.jpg
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
odin/assets/uNO4KmIsLeN9asYlpG1aW.jpg
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
odin/assets/vr0d18GJMCxRnnCBFWKU0.jpg
Normal file
|
After Width: | Height: | Size: 293 KiB |
BIN
odin/assets/wN1Dhg_HnvJ0oU5xC86UU.jpg
Normal file
|
After Width: | Height: | Size: 440 KiB |
BIN
odin/assets/wUA9Y90MDARwLJQMKM_iE.jpg
Normal file
|
After Width: | Height: | Size: 309 KiB |
BIN
odin/assets/ww-HB0SrT8-O-nQzmfHZY.jpg
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
odin/assets/x4mR37Mj9XHaED4Wc25IL.jpg
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
odin/assets/y7m7vcnEIFC8hj8KmDMN_.jpg
Normal file
|
After Width: | Height: | Size: 315 KiB |
BIN
odin/assets/yIuc7V6dQYmvn0c6Ej2ms.jpg
Normal file
|
After Width: | Height: | Size: 305 KiB |
17
odin/build.sh
Executable 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
35
odin/build_osdialog.sh
Executable 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
@ -0,0 +1 @@
|
|||||||
|
comic/odin ui ? ✗ ./build.sh && ./bin/comic_odin gui
|
||||||
BIN
odin/comic-odin
Executable file
BIN
odin/comic.pdf
Normal file
138
odin/docs/GUI_USER_GUIDE.md
Normal 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
303
odin/docs/PRODUCTION_PLAN.md
Normal 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
|
||||||