Skip to content

Project

Bartłomiej Mucha edited this page Sep 6, 2026 · 1 revision

Foundation 2 — Building a project

A project is one comic chapter: an ordered list of input pages, the slices produced from them, and the render baselines that let the next run tell what changed. It is an entity — it holds state and answers questions about itself; the operations that act on it from outside live on Infrastructure::ProjectEditor.

The invariant at the end is filed here rather than with the matching algorithm because it constrains the project, not the matcher.

Workspace and project model


PageItem

uid          : uid       // local id (e.g. "file-<hex>"), minted if missing/duplicate on load
filePath     : string    // absolute path on disk
order        : int       // 0-indexed strip position — the render builds the virtual strip in this
                         // order (via ProjectItem::inputsInOrder()), not in the stored-vector order.
                         // Reordering rewrites only this field (Infrastructure::ProjectEditor); the
                         // input vector is never physically moved.
status       : enum      // PENDING | PROCESSED | SKIPPED | ERROR
errorMessage : string    // empty unless status == ERROR or SKIPPED

Note: thumbnailPath is not persisted. The GUI derives it on demand via ThumbnailCache::thumbnailPath(page.filePath) — the path is deterministic and does not need to be stored in the workspace JSON.

ProjectItem

A project is an ordered set of source images that are processed together as a single virtual strip, sharing one output profile and a curated list of canvas profiles.

uid               : uid         // stable local identifier
name              : string      // user-facing label, e.g. "Chapter 01"
inputDirectory    : string      // folder inputs were last scanned from (not authoritative — each
                                //   input carries its own path); CLI project-match key + GUI dialog default
outputDirectory   : string      // where output slices are written
outputProfileId   : uid         // references OutputProfile.id in the parent Workspace
                                // exactly one output profile per project
canvasProfileIds  : uid[]       // ordered list of CanvasProfile.id values
                                // order = priority (first match wins)
                                // invariant: no two entries may refer to profiles
                                // whose canvasSize is identical (conflict guard)
pages             : PageItem[]  // ordered input images
processedFiles    : ProcessedFileRecord[]
colourCorrection  : ColourCorrection  // optional per-page grade (page domain); disabled by default
stripOverlays     : StripOverlay[]    // optional text/bubble overlays (strip domain); a lib-owned inventory
processingSignature : string          // fingerprint of the two steps at last render (staleness axis, §7)
stripDirty        : bool

Overlays are a lib-owned inventory, parallel to input files. The consumer creates the RGBA bitmap; the library inventories it: ProjectItem::addOverlay(path, x, y, blend) mints the ovl-… uid, hashes the bitmap and dedups identical content by sha256, removeOverlay(uid) drops one. The field is private, read through getStripOverlays() (const + mutable, like getInputImages()). Files are referenced by path and never copied — the same self-containment level as input files. Colour correction carries no binary artifact (pure numbers), so it is fully contained in the workspace JSON.

Why a list, not a single id?
An artist may work with multiple canvas heights within one chapter (e.g. a normal page at 10 240 px and a splash at 20 480 px, both 1 600 px wide). The list lets the project accept both, while the order encodes which profile takes precedence when two profiles match the same width but could theoretically both match. The conflict guard (§7.5) prevents two profiles with identical dimensions from being registered, so priority only matters for width-only ambiguity during the search (see §7.5).

outputProfileId is a direct, stable reference. If the referenced profile is deleted from the workspace the project is considered misconfigured and the CLI / GUI must report an error before processing begins.

ProcessedFileRecord

inputFilePath : string    // absolute path
sha256        : string    // hex digest at time of last processing
lastProcessed : datetime  // ISO 8601
contributesTo : string[]  // output filenames that contain pixels from this input

Conflict guard (ProjectItem invariant)

Within a single project, canvasProfileIds must never contain two profiles whose canvasSize (width × height) is identical. If it did, the algorithm above would always select the first one and the second would be unreachable — a silently wasted entry.

The conflict is detected at the moment a profile is added to a project, not deferred to serialisation. The library exposes a dedicated mutation method that checks for a collision and returns a structured error identifying the conflicting profile — so the CLI can suggest a removal command and the GUI can highlight the offending entry.


See also: Home · Workspace · Project · Render · Preview · Canvas-Profiles · Processing-Steps · Components · CLI · Platform · Roadmap · Diagrams

Clone this wiki locally