Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@
- Use `try`/`catch` and similar control flow only for failures you expect and can handle; do not add unreachable recovery paths that hide bugs instead of failing fast during development.
- Write a brief comment only when a future agent needs to know why the code is written this way and that reason cannot be inferred from the code; do not narrate behavior, translate code into natural language, or restate responsibilities.
- When the project requires the use of newly added basic components, priority should be given to shadcn-related components.
- `pnpm dead-code` (knip) catches unused files and exports that `tsc` and eslint miss. Run it before committing and verify your change does not introduce new unused exports. When a multi-slice task requires landing a type or function before its consumer exists, add a `/** @public — consumed by <slice or task id> */` JSDoc tag to suppress knip; remove the tag when the consumer lands.

## Long Tasks

- Treat a task as long when it cannot be completed safely in one session without explicit slicing.
- For long tasks, create paired `docs/tasks/<topic>.md` and `.json` files: markdown for scope, decisions, validation, and handoff; JSON for terse execution state only.
- Keep the JSON terse: stable task statuses such as `pending`, `in_progress`, `blocked`, `done`, `rolled_back`; `passes` as the completion gate; baseline/current task; rollback notes only when not obvious.
- The first session must at least slice the work and define validation boundaries; it may also complete the first slice if that slice is low-risk and fully validated.
- JSON schema per task: `id`, `status`, `blockedBy`, `passes`. No derived fields (`nextTaskId`, `current` — compute from DAG).
- `status`: `pending` | `in_progress` | `blocked` | `done` | `rolled_back`.
- `blockedBy`: array of task ids that must be `done` before this task is eligible. Empty array = no dependency.
- `passes`: array of validation gates; all must hold before marking `done`.
- Eligible task: `status=pending` and every id in `blockedBy` has `status=done`.
- Claiming a task: set `status` to `in_progress` and commit as the first action, before starting implementation. This ensures other agents see the claim.
- The first session must at least slice the work, declare dependencies via `blockedBy`, and define validation boundaries; it may also complete the first slice if that slice is low-risk and fully validated.
- If a slice fails validation and is not fixed immediately, mark it `blocked` or `rolled_back`, record the first actionable failure in the markdown note, and stop claiming progress.
- MD carries scope, decisions, per-slice implementation notes, and handoff. Do not duplicate DAG, status, or passes — those live in JSON.
- When every slice reaches `done`, close the task: migrate load-bearing decisions and known follow-ups into `docs/decisions.md`, then delete the `docs/tasks/<topic>.{md,json}` pair. Slice-by-slice handoff is carried by git history, not by long-lived docs.

## Documentation Hygiene
Expand Down Expand Up @@ -81,6 +89,7 @@
- Use the gh tool for GitHub-related operations.
- When commits are requested, keep them atomic: commit each validated independent step rather than bundling unrelated changes.
- When uncommitted work spans multiple validated slices, split one commit per slice; temporarily rewind shared task md/json to each slice's boundary state before staging, so every commit reflects the state at that slice's completion and nothing later.
- Before pushing a PR or after being asked to check a PR, read its review comments (`gh api repos/{owner}/{repo}/pulls/{n}/comments`). For each comment that identifies a real issue: fix it, reply with the fix commit hash and a one-line summary, then resolve the thread via GraphQL `resolveReviewThread`. Do not resolve threads that are open questions or that you have not addressed.

## Commit & Pull Request Guidelines

Expand Down
25 changes: 14 additions & 11 deletions docs/tasks/media-native-render-pipeline.json
Original file line number Diff line number Diff line change
@@ -1,41 +1,44 @@
{
"baseline": "2026-04-02",
"current": "in_progress",
"nextTaskId": "carrier-and-signal-families",
"tasks": [
{
"id": "semantic-overlay-layer-system",
"status": "pending",
"status": "done",
"blockedBy": [],
"passes": [
"caption, HUD, browser-chrome, or similar overlays have an authored model with canvas preview/export parity"
]
},
{
"id": "carrier-and-signal-families",
"status": "in_progress",
"status": "done",
"blockedBy": [],
"passes": [
"carrier transforms and signal damage are authored as first-class families instead of generic effect placement only"
]
},
{
"id": "analysis-layer-boundary",
"status": "pending",
"status": "done",
"blockedBy": [],
"passes": [
"analysis-driven overlays or effects consume explicit analysis inputs with bounded validation"
]
},
{
"id": "motion-live-render-contract",
"status": "pending",
"id": "preview-export-quality-split",
"status": "done",
"blockedBy": [],
"passes": [
"a time-parameterized render contract exists for short-loop or live-style output and one preset is validated end-to-end"
"interactive preview, quality preview, and export are explicitly modeled without a second authored-state source of truth"
]
},
{
"id": "preview-export-quality-split",
"status": "pending",
"id": "motion-live-render-contract",
"status": "done",
"blockedBy": ["preview-export-quality-split"],
"passes": [
"interactive preview, quality preview, and export are explicitly modeled without a second authored-state source of truth"
"a time-parameterized render contract exists for short-loop or live-style output and one preset is validated end-to-end"
]
}
]
Expand Down
105 changes: 91 additions & 14 deletions docs/tasks/media-native-render-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
- Scene/global ownership must stay above image-node-local render state and is tracked separately in `docs/tasks/scene-global-render-follow-up.md`.
- Keep preview/export differences at the scheduler or quality tier, not in a second authored-state source of truth.

## Open Slices
## Slices

### 1. Semantic Overlay Layer System

Expand Down Expand Up @@ -110,20 +110,34 @@

## Current Focus

- `carrier-and-signal-families` remains the active slice.
- ASCII already executes as a carrier transform.
- The next implementation should add one more authored carrier or signal family without widening back into generic effect placement only.
All slices are complete. Task is ready for closure — migrate load-bearing decisions to `docs/decisions.md` and delete this pair.

## Files

- `src/render/image/analysisLayer.ts` (analysis layer types, validation, edge map compute)
- `src/render/image/motionRender.ts` (motion render contract, frame context, signal-drift preset)
- `src/render/image/qualityTier.ts` (quality tier type and config resolver)
- `src/render/image/renderSingleImage.ts`
- `src/render/image/carrierExecution.ts`
- `src/render/image/asciiEffect.ts` (carrier orchestrator + ASCII impl)
- `src/render/image/halftoneEffect.ts`
- `src/render/image/signalDamageExecution.ts`
- `src/render/image/effectExecution.ts`
- `src/render/image/overlayExecution.ts`
- `src/render/image/snapshotPlan.ts`
- `src/render/image/types.ts`
- `src/lib/renderer/gpuHalftoneCarrier.ts`
- `src/lib/renderer/gpuSignalDamage.ts`
- `src/lib/renderer/shaders/HalftoneCarrier.frag`
- `src/lib/renderer/shaders/ChannelDrift.frag`
- `src/lib/captionOverlay.ts`
- `src/lib/watermarkOverlay.ts`
- `src/features/canvas/CanvasHalftoneEditPanel.tsx`
- `src/features/canvas/CanvasSignalDamageEditPanel.tsx`
- `src/features/canvas/CanvasCaptionEditPanel.tsx`
- `src/features/canvas/CanvasWatermarkEditPanel.tsx`
- `src/features/canvas/boardImageRendering.ts`
- `src/features/canvas/imageRenderStateEditing.ts`
- `src/features/canvas/renderCanvasDocument.ts`
- no matches

## Handoff

Expand All @@ -133,15 +147,78 @@
- Implemented in the first slice:
- canonical stage naming is now `develop -> style -> overlay -> finalize`
- timestamp handling now flows through a shared overlay runtime entry instead of direct per-call special casing
- Implemented in the current ASCII-first carrier sub-slice:
- Implemented in the ASCII-first carrier sub-slice:
- `CanvasImageRenderStateV1` now carries `carrierTransforms`
- ASCII authoring/editing moved out of `effects[]` and into `carrierTransforms`
- preview/export revision identity now includes carrier transforms
- legacy ASCII effect persistence is treated as read-only compatibility input, not a write-path schema
- Still open after the first slice:
- authored `semanticOverlays` model
- board/global overlay ownership rules
- non-timestamp overlay types
- signal-damage families
- carrier families beyond ASCII (`dither`, `halftone`, `palette`, `textmode`)
- motion/live render contract
- Implemented in the carrier-and-signal-families slice:
- `CarrierTransformNode` is now a union of `ascii | halftone`
- Halftone carrier: GPU shader with mono/CMYK/RGB color separation, circle/diamond/line/square dot shapes
- `CanvasImageRenderStateV1` now carries `signalDamage: SignalDamageNode[]` as a first-class authored family
- Channel drift signal damage: GPU shader with per-channel RGB offset
- Carrier orchestrator dispatches by transform type (no longer ASCII-only)
- Signal damage executes as a dedicated pipeline stage between carriers and style effects
- UI panels: `CanvasHalftoneEditPanel`, `CanvasSignalDamageEditPanel` with full preview/commit workflow
- Family classification: halftone and channel drift are both single-frame deterministic
- Implemented in the semantic-overlay-layer-system slice (authored model):
- `CanvasImageRenderStateV1` now carries `semanticOverlays: SemanticOverlayNode[]` as a first-class authored family
- `SemanticOverlayNode` is a typed union (currently `TimestampSemanticOverlayNode`); extensible for caption, HUD, watermark, etc.
- `ImageRenderOutputState.timestamp` demoted to optional legacy field; normalization migrates it to a timestamp semantic overlay
- Overlay resolution (`resolveImageOverlays`) reads from `semanticOverlays` instead of `output.timestamp`
- UI adjustment type: `TimestampAdjustments` in `@/types`, with `upsertTimestampOverlay` / `applyTimestampAdjustmentsToRenderState` state editing helpers
- Preview/export parity preserved: same overlay execution path, same GPU/CPU fallback
- Revision identity automatically includes `semanticOverlays` via `normalizeCanvasImageRenderState`
- Ownership decision: overlays are per-image authored state; runtime content (e.g. timestamp text) stays on `ImageRenderRequest`
- Implemented in the semantic-overlay-layer-system slice (concrete overlay types):
- `SemanticOverlayNode` union extended with `CaptionSemanticOverlayNode` and `WatermarkSemanticOverlayNode`
- Caption overlay: authored text with configurable position (top/center/bottom), alignment (left/center/right), font size, color, background, padding, opacity
- Watermark overlay: repeating tiled text pattern with configurable angle, density, font size, color, opacity
- CPU canvas renderers: `src/lib/captionOverlay.ts`, `src/lib/watermarkOverlay.ts`
- Overlay execution pipeline dispatches caption and watermark through the same GPU-blend path as timestamp
- State editing helpers: `applyCaptionAdjustmentsToRenderState`, `applyWatermarkAdjustmentsToRenderState` with full default/upsert workflow
- UI panels: `CanvasCaptionEditPanel`, `CanvasWatermarkEditPanel` with preview/commit workflow matching halftone/signal-damage pattern
- Normalization guard updated to recognize `caption` and `watermark` types
- Preview/export parity preserved: same overlay execution path, same blend mechanism
- Implemented in the preview-export-quality-split slice:
- `RenderQualityTier` (`"interactive" | "quality" | "export"`) replaces the scattered `ImageRenderIntent` + `ImageRenderQuality` pair
- `ImageRenderRequest` now carries `qualityTier: RenderQualityTier` instead of `intent` + `quality`
- `resolveRenderQualityTierConfig(tier)` maps each tier to `RenderIntent` and `strictErrors`
- Pipeline (`renderSingleImage`) resolves intent and error behavior from tier config
- Carrier transforms (`asciiEffect`, `halftoneEffect`) accept `RenderQualityTier` for quality-dependent execution (e.g. cell size coarsening in interactive)
- `boardImageRendering.ts`: preview priority → quality tier mapping (`interactive` → `"interactive"`, `background` → `"quality"`)
- `renderCanvasDocument.ts`: export uses `qualityTier: "export"` directly
- `canvasPreviewRuntimeController.ts`: no longer passes `intent` to render call — tier derived from priority
- `CanvasImageRenderStateV1` unchanged — quality tiers affect execution, not authored state
- Old types removed: `ImageRenderIntent`, `ImageRenderQuality`, `IMAGE_RENDER_INTENTS`, `IMAGE_RENDER_QUALITIES`
- Preview/export parity preserved: same pipeline, different tier config
- Implemented in the analysis-layer-boundary slice:
- `AnalysisLayerInputs` replaces the ad-hoc `CarrierSnapshots` bag — typed inputs with `stageSnapshots` and `edgeMap` fields
- `AnalysisRequirement` union (`stage-snapshot | edge-map`) derived from carrier transforms via `deriveAnalysisRequirements`
- `resolveAnalysisSourceCanvas` replaces inline snapshot lookup in carrier orchestrator
- `validateAnalysisInputs` checks requirements against available inputs; export tier throws on missing, preview degrades
- `computeEdgeMap` provides CPU Sobel edge detection as a concrete new analysis type
- `snapshotPlan` now carries `analysisRequirements[]` alongside derived `requiresDevelop/StyleAnalysisSnapshot` flags
- Carrier transforms (`applyImageCarrierTransforms`) accept `analysisInputs: AnalysisLayerInputs` instead of raw snapshots
- Pipeline builds `AnalysisLayerInputs` during execution, validates before carrier stage
- Authored state unchanged — analysis requirements are derived from transform declarations, not user-authored
- Preview/export parity preserved: same analysis resolution path, validation strictness varies by tier
- Implemented in the motion-live-render-contract slice:
- `MotionProgram` authored type (union, currently `SignalDriftMotionProgram`) on `CanvasImageRenderStateV1.motionPrograms`
- `MotionFrameContext` defines time parameter per frame: `frameIndex`, `timeMs`, `normalizedTime`, `totalFrames`
- `applyMotionProgramToDocument` modifies the base render document per-frame (source frame ownership stays with base document)
- `renderMotionSequence` iterates frames, applies motion program, renders each via single-image kernel, supports abort and `onFrame` callback
- Signal-drift preset: sinusoidal RGB channel drift with configurable amplitude and intensity, producing a seamless loop
- Frame-to-frame state: signal-drift is purely time-derived (no accumulator); contract supports stateful presets via `MotionFrameContext`
- Export packaging: frame sequence collected as `MotionFrameResult[]` with per-frame canvas; caller handles encoding
- `normalizeCanvasImageRenderState` includes `motionPrograms` with type guard and clone
- Revision identity: each frame gets a unique revision key via `createImageRenderDocument`
- Single-image kernel unchanged — motion layer composes above it
- Still open (follow-up work, not blocking this task):
- board/global overlay ownership rules (per-image vs board-level, composition rules)
- additional overlay types (HUD, browser chrome, sticker)
- additional carrier families (`dither`, `palette`, `textmode`)
- additional signal damage families (`line-displacement`, `row-shift`, `compression-artifacts`, `pixel-sort`)
- additional motion presets (`grain-oscillate`, `exposure-breathe`)
- canvas preview controller integration for live motion playback
- additional analysis types (segmentation, face landmarks, OCR, object detection)
38 changes: 38 additions & 0 deletions docs/tasks/render-kernel-webgpu-rewrite.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"task": "render-kernel-webgpu-rewrite",
"status": "pending",
"slices": {
"s0-foundation": {
"status": "pending",
"passes": "upload source → passthrough → readback matches input"
},
"s1-ascii-compute": {
"status": "pending",
"passes": "structureWeight=0 matches density output; structureWeight=1 selects correct directional glyphs; <16ms at 1080p cellSize=12"
},
"s2-photo-core": {
"status": "pending",
"passes": "pixel comparison vs WebGL2: inputDecode, geometry, master, outputEncode < 2/255"
},
"s3-photo-extended": {
"status": "pending",
"passes": "pixel comparison: hsl, curve, detail < 2/255"
},
"s4-film": {
"status": "pending",
"passes": "film profile rendering matches WebGL2"
},
"s5-masking-post": {
"status": "pending",
"passes": "gradient mask + brush mask + halation render correctly"
},
"s6-integration": {
"status": "pending",
"passes": "full app smoke test, preview + export work, no WebGL2 in render path"
},
"s7-cleanup": {
"status": "pending",
"passes": "clean build, no WebGL/twgl/glsl refs in src/lib/gpu/, decisions.md updated"
}
}
}
Loading
Loading