You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every issue in Milestone #2 that produces training labels — in-app annotation (#420), the "Report bad crop" correction flow (#421), and the A/B disagreement re-labeling queue (#418) — needs the same core surface: a component that takes a source image and an optional candidate mask and lets a potter or admin propose an alternative mask assisted by browser-side ML tools.
Without this component, none of those issues can accumulate human-provided ground-truth labels. It is the single prerequisite for any supervised labeling flow in the milestone.
This issue extracts the editor requirement from #420 into a standalone, reusable component so it can be built, tested, and iterated independently of the annotation-persistence logic.
Proposed Solution
New component: web/src/components/MaskEditor.tsx
Props
typeMaskEditorProps={// Source image to annotateimageUrl: string;imageWidth: number;imageHeight: number;// Optional pre-filled candidate mask: RGBA PNG with RGB zeroed, alpha = foreground confidence// (matches the CropRun.mask_asset format from #421)candidateMask?: string|null;// Cloudinary URL or data URI// Called when the user commits a mask; receives the edited mask as a canvas ImageData or blobonCommit: (mask: Blob)=>void;onCancel: ()=>void;// Optional: disable tools not yet implementeddisabledTools?: ToolName[];};
Required tools (all must be present; pixel-by-pixel brush is NOT the primary path)
Tool
Description
Implementation path
Pre-fill
Load candidateMask onto the canvas as the starting state
Canvas alpha composite
Brush + eraser
Adjustable-radius soft/hard edge
Canvas 2D
Polygon edit
Add/move/delete vertices on the mask contour
Canvas 2D + hit testing
Flood fill
Click inside a region to add/subtract from mask, with tolerance slider
Pull a polygon vertex toward the nearest strong image edge within a radius
opencv.js or server-side assist
Browser-ML topology decision
GrabCut and contour snap are OpenCV-shaped operations. Two viable paths — the implementer must prototype both for GrabCut on a representative pottery image (busy background, ~4MP source) and pick based on measured latency and bundle impact:
WASM in-browser (opencv.js or trimmed wasm build): instant feedback, no round-trip. Costs ~8 MB initial payload; memory ceilings on large images.
Server-side assist API (POST /api/annotations/assists/{flood-fill,grabcut,contour-snap}): leverages Python OpenCV already in the build graph; one implementation. Costs one round-trip per click.
Document the chosen topology in docs/agents/glaze-domain.md so downstream issues (#420, #418) don't relitigate it.
State shape
The editor has multiple coupled fields (mask draft, undo stack, active tool, tool params, dirty flag) advanced by multiple event sources. Per react-conventions/SKILL.md, use useReducer keyed on domain events (hydrate, tool_applied, prefill_received, commit_requested, cancelled) rather than a pile of useState calls.
Undo/redo: at minimum 20 steps; keyed on canvas snapshots (not deltas) for simplicity.
Layout
Full-screen MUI Dialog sized to viewport. Extract child components as needed to keep JSX nesting ≤ 4: MaskEditorToolbar, MaskEditorCanvas, MaskEditorFooter. MUI theme tokens only — no inline palette values.
Mask format in/out
Input (candidateMask): RGBA PNG URL where alpha = foreground confidence. Alpha > 128 = foreground on load.
Output (onCommit): same RGBA PNG format — RGB zeroed, alpha = foreground/background (binary or soft edge at implementer's discretion). This matches CropRun.mask_asset and CropAnnotation.mask formats throughout the milestone.
Acceptance Criteria
MaskEditor component renders source image with candidate mask overlaid (alpha-composited).
All six tools wired in the toolbar: pre-fill, brush + eraser, polygon edit, flood fill, GrabCut, contour snap.
Browser-ML topology (wasm vs. server-side) prototyped for GrabCut on a representative image; chosen path implemented and documented in docs/agents/glaze-domain.md.
useReducer state shape with named domain events; no bare useState for mask or tool state.
Undo/redo working across all tools (minimum 20 steps).
onCommit emits an RGBA PNG Blob (RGB zeroed, alpha = mask); onCancel closes without side effects.
Component is self-contained: accepts image URL + optional candidate mask URL; has no direct knowledge of CropRun, CropAnnotation, or any other API model. Callers wire those.
Frontend tests cover: pre-fill loads candidate mask, each tool mutates the mask state, undo reverts, commit emits correct blob, cancel fires without mutation.
Storybook stories for: empty state (no candidate), pre-filled candidate, each tool active.
Out of Scope
Persisting the mask to any API endpoint — callers own that (see #420, #421).
The padding preview slider — that belongs in #420 where crop_padding_fraction is defined.
The "(re-)run backend" dropdown that lets annotators swap the candidate from a different CropRun — that also belongs in #420.
Any server-side model changes.
Dependencies
No upstream code dependencies within this milestone — can be built in parallel with #421.
If the server-side assist topology is chosen, new endpoints (POST /api/annotations/assists/) must be added — those can be stubbed initially and filled in before the acceptance criteria are met.
Vision: Pottery Computer Vision Pipeline
Problem / Motivation
Every issue in Milestone #2 that produces training labels — in-app annotation (#420), the "Report bad crop" correction flow (#421), and the A/B disagreement re-labeling queue (#418) — needs the same core surface: a component that takes a source image and an optional candidate mask and lets a potter or admin propose an alternative mask assisted by browser-side ML tools.
Without this component, none of those issues can accumulate human-provided ground-truth labels. It is the single prerequisite for any supervised labeling flow in the milestone.
This issue extracts the editor requirement from #420 into a standalone, reusable component so it can be built, tested, and iterated independently of the annotation-persistence logic.
Proposed Solution
New component:
web/src/components/MaskEditor.tsxProps
Required tools (all must be present; pixel-by-pixel brush is NOT the primary path)
candidateMaskonto the canvas as the starting stateBrowser-ML topology decision
GrabCut and contour snap are OpenCV-shaped operations. Two viable paths — the implementer must prototype both for GrabCut on a representative pottery image (busy background, ~4MP source) and pick based on measured latency and bundle impact:
opencv.jsor trimmed wasm build): instant feedback, no round-trip. Costs ~8 MB initial payload; memory ceilings on large images.POST /api/annotations/assists/{flood-fill,grabcut,contour-snap}): leverages Python OpenCV already in the build graph; one implementation. Costs one round-trip per click.Document the chosen topology in
docs/agents/glaze-domain.mdso downstream issues (#420, #418) don't relitigate it.State shape
The editor has multiple coupled fields (mask draft, undo stack, active tool, tool params, dirty flag) advanced by multiple event sources. Per
react-conventions/SKILL.md, useuseReducerkeyed on domain events (hydrate,tool_applied,prefill_received,commit_requested,cancelled) rather than a pile ofuseStatecalls.Undo/redo: at minimum 20 steps; keyed on canvas snapshots (not deltas) for simplicity.
Layout
Full-screen MUI Dialog sized to viewport. Extract child components as needed to keep JSX nesting ≤ 4:
MaskEditorToolbar,MaskEditorCanvas,MaskEditorFooter. MUI theme tokens only — no inline palette values.Mask format in/out
candidateMask): RGBA PNG URL where alpha = foreground confidence. Alpha > 128 = foreground on load.onCommit): same RGBA PNG format — RGB zeroed, alpha = foreground/background (binary or soft edge at implementer's discretion). This matchesCropRun.mask_assetandCropAnnotation.maskformats throughout the milestone.Acceptance Criteria
MaskEditorcomponent renders source image with candidate mask overlaid (alpha-composited).docs/agents/glaze-domain.md.useReducerstate shape with named domain events; no bareuseStatefor mask or tool state.onCommitemits an RGBA PNG Blob (RGB zeroed, alpha = mask);onCancelcloses without side effects.CropRun,CropAnnotation, or any other API model. Callers wire those.Out of Scope
crop_padding_fractionis defined.CropRun— that also belongs in #420.Dependencies
POST /api/annotations/assists/) must be added — those can be stubbed initially and filled in before the acceptance criteria are met.Blocks
<MaskEditor>as its core surfaceMilestone Cross-References
Part of milestone #2 — Custom Pottery Crop Model.