- Implemented ComfyGenerateRequest type and associated functions for generating images using various architectures and modes. - Added functions for listing generation options and handling image uploads. - Created workflows for different generation modes including SDXL, Z-Image, Z-Image Turbo, and Anima. - Introduced GenerationJobStatus component to display the status of ongoing generation jobs. - Developed MaskControls for managing mask operations and displaying mask analysis. - Created palette items for tool selection, layer management, and generation settings.
27 KiB
Image Studio product, UX, and architecture audit
Date: 2026-07-09
Status: Phase 1 audit complete — code, workflow, architecture, and automated verification. A screenshot-based interaction pass remains pending because the in-app browser automation bridge was unavailable during this audit.
Implementation update, 2026-07-09: the Phase 0 strict TypeScript baseline has been restored. bun run check now runs type-checking, all 130 tests, ESLint, and the production build as one passing validation gate.
Executive summary
Image Studio has a substantially better technical foundation than its current interface suggests. The document model supports artboards, nested groups, image and raster layers, masks, transforms, assets, generation provenance, selection, command history, and non-destructive generation candidates. The command-only state write path and read-only renderer are especially valuable foundations for a serious editor.
The main problem is that the product is still expressed as a collection of prototype controls rather than a coherent workflow. Generation is treated simultaneously as a tool, a settings panel, a canvas preview mode, and a dense bottom action bar. Layers, masks, generation candidates, document actions, transform properties, shortcuts, and debug commands compete for attention without a stable hierarchy. The current visual language amplifies this by rendering almost every surface as a translucent rounded island.
The recommended direction is a complete visual and workspace redesign while retaining the document, command, input, and renderer foundations. The new product should be organized around three durable concepts:
- Document — artboards, editable layers, masks, groups, transforms, and provenance.
- Operation — select, transform, paint, mask, remove background, inpaint, outpaint, and generate.
- Result tray — temporary model outputs that can be compared, refined, accepted, combined, or dismissed without prematurely mutating the document.
Audit method and limits
The audit covered:
- Repository and domain-specific
AGENTS.mdguidance. - The core document model, editor state, commands, input, renderer, application composition, and React view.
- Primary workflows: image import, selection and transform, layer and group management, masks, brush/eraser, chroma key, magic wand, generation, inpaint preparation, candidate comparison, candidate acceptance, and export.
- Current test, lint, type-check, and build results.
- Current Git status and recent architectural history.
- Runtime HTTP health on a local development server.
The audit could not include screenshots or direct browser interactions. Findings about structure, behavior, accessibility markup, responsiveness, and styling are code-confirmed; exact visual proportions, animation feel, pointer hit areas, focus rendering, and browser-specific behavior still need a live visual pass.
Product thesis
The strongest version of Image Studio is not a smaller Photoshop with an AI dialog. It is an editor for turning uncertain model outputs into controlled, editable compositions.
A central loop should be:
Select context → describe an operation → generate alternatives → compare → accept or combine → refine locally
That loop should work for full-image generation, image-to-image, inpainting, outpainting, background replacement, object cleanup, and future model-backed operations. The document remains authoritative; generated results remain provisional until accepted.
What is already strong
1. The state architecture has a clear backbone
Persistent document state lives in core/, transient editor state lives in editor/, changes flow through deterministic commands, input resolves intent, and the renderer consumes state without owning it. This is a sound basis for undo, serialization, collaboration, macros, and future agent-driven operations.
2. Generated outputs are provisional candidates
GenerationState separates candidates from the document. Candidates retain settings, seed, source inputs, masks, placement, crop information, and inpaint preparation metadata. This is exactly the right conceptual direction for comparing uncertain model outputs before committing them.
3. Accepted generated assets retain provenance
Generated assets record their mode, prompt, negative prompt, seed, model configuration, acceptance path, and inpaint context. This is strategically important. The UI does not expose it yet, but the underlying data could support “reuse settings,” “make variations,” auditability, and workflow history.
4. Masks and nested groups are real document concepts
Groups are tree nodes rather than flat UI labels. Masks are backed by raster assets and participate in commands and rendering. This avoids the common prototype trap where layers look hierarchical but behave as a flat list.
5. Automated behavioral coverage is meaningful
The repository has 130 passing tests across commands, history, transforms, selection, nested layer operations, masks, input, rendering decisions, generation candidates, inpaint preparation, and ComfyUI workflow construction.
Priority findings
Severity meanings:
- P0 — blocks treating the project as safely maintainable.
- P1 — breaks or obscures a primary product workflow.
- P2 — significant friction, inconsistency, or scaling risk.
- P3 — polish and cleanup that should follow the structural redesign.
P0 — strict TypeScript validation fails — resolved 2026-07-09
bunx tsc --noEmit fails despite tests, ESLint, and the production build passing. Current failures include unsafe layer narrowing, possibly undefined mask access, generic select-menu handlers, canvas typed-array incompatibilities, a resize hook call, input test typing, and the two currently modified Comfy files.
This creates a false-green validation story: production assets can build while the architecture's type contracts are already drifting. Strict type checking should become a required baseline before the redesign begins.
Resolution: the affected layer narrowing, optional mask access, generic select handling, typed-array inference, resize-hook initialization, input-test dispatch typing, and current Comfy adapter typing were corrected without weakening the domain unions. The new typecheck and aggregate check scripts make the baseline repeatable.
P1 — there is no durable project lifecycle
The app can import images and export an artboard PNG, but there is no project save, project open, autosave, recovery, or recent-document flow. ImageDocument is already serialization-friendly, but imported assets use object URLs and there is no persistence adapter.
For a layer-based editor, this is not an optional feature. Without it, the product cannot fulfill “turn an AI image into an editable project” beyond a single browser session.
Redesign implication: the workspace needs document identity and save state in its persistent chrome. The architecture needs an asset persistence strategy before project import/export is presented as complete.
P1 — generation mode is hidden while infrastructure settings are promoted
The primary user intent—text-to-image, image-to-image, inpaint, or outpaint—is inside an “Advanced” disclosure. Meanwhile backend architecture, model, seed, and sometimes text encoder and VAE are presented as essentials.
This hierarchy is backwards for the target audience. Users should choose an operation first; compatible models and defaults should follow. Model infrastructure belongs in a secondary model/settings layer unless a power user explicitly expands it.
P1 — generation is modeled as four competing UI concepts
Generation appears as:
- A tool in the left rail.
- A duplicate sparkle action in the top-right toolbar.
- A right-side settings sheet.
- A bottom action island containing execution, status, candidate navigation, comparison, regeneration, acceptance, mask editing, and dismissal.
These surfaces do not form a legible sequence. “Generate” is closer to a workflow or operation workspace than a pointer mode. Treating it as a normal canvas tool also makes panel visibility and keyboard behavior artificially coupled to activeTool.
Redesign implication: generation should open an operation workspace with a clear input stage and a result stage. The active canvas interaction inside that workspace can still be select, pan, paint-mask, or transform.
P1 — accepting one candidate destroys the entire candidate session — resolved 2026-07-09
Both “accept as layer” and “replace pixels” call clearCommittedGenerationPreview, which clears all candidates, not only the accepted candidate. This conflicts with the core use case of combining multiple model outputs. A user who generates several alternatives and accepts one loses the remaining comparison set.
Redesign implication: accepting a candidate should mark or remove only that candidate by default. The result tray should support keeping, pinning, multi-selecting, and clearing the session explicitly.
Resolution: both layer acceptance and masked-pixel replacement now remove only the committed candidate, select the nearest remaining candidate, and preserve the active comparison mode while results remain.
P1 — up to twelve candidates are stored but only six are selectable — resolved 2026-07-09
Generation state retains twelve candidates, while CandidatePicker renders only candidates.slice(0, 6). Candidates seven through twelve have no visible selection path. This is a concrete interaction bug, not merely a styling concern.
Resolution: the picker now renders the complete bounded candidate set in a horizontally scrollable group.
P1 — “variant” is only a renamed ordinary layer — resolved 2026-07-09
“Accept variant” creates a standard top-level layer and records variant-layer provenance. There is no document-level variant set, linked source, stack semantics, exclusive visibility, or comparison group. The label promises more structure than the product provides.
Redesign implication: either call this “Add as another layer” or introduce a real variant/result-set concept. A result tray can provide variant semantics without forcing them into the document tree prematurely.
Resolution: the duplicate variant action and variant-layer provenance value were removed. The UI now describes the real operation as “Add as layer”; refinement layers use the same honest layer acceptance semantics.
P1 — generation preconditions are not represented clearly
The Generate button only requires a non-empty prompt. Image-to-image can proceed without a selected source image. Inpaint configuration is available without explaining or enforcing the required source layer and mask. Outpaint and mode-specific settings coexist regardless of current mode.
The workflow should state its required context before execution and offer direct repair actions such as “Select a layer,” “Create mask,” or “Use active artboard.” Disabled buttons alone are insufficient.
P1 — non-inpaint generated placement ignores the active context
Non-inpaint candidates default to document position (0, 0) at native scale. The default artboard spans negative and positive coordinates, so a 1024×1024 result can appear partly outside the active artboard and does not use the viewport center or selected artboard bounds.
Placement should be explicit: fit active artboard, use requested frame, place at viewport center, or preserve source-layer bounds depending on operation.
P1 — asynchronous generation state is owned by a transient React control — resolved 2026-07-10
Busy state, elapsed time, and errors live inside GenerateActionControls. Switching away from Generate unmounts that surface while the request continues. The user loses status and error visibility, and remounting removes the local busy guard even if a request is still running.
Generation jobs are application state, not ephemeral component state. They need stable IDs, lifecycle status, cancellation where supported, error details, and persistence across panel changes.
Resolution: generation and candidate follow-up work now run through command-driven, bounded job state with stable IDs, lifecycle timestamps, and durable errors. The existing Generate controls consume that state, the top toolbar keeps activity visible across tool and panel changes, concurrent submissions are rejected authoritatively, and document undo/redo no longer rewinds job lifecycle state. Cancellation remains a future adapter capability because the current Comfy request path does not expose cancellation.
P2 — the workspace lacks stable information architecture
The current shell is a canvas surrounded by floating islands:
- Top-right file, generate, export, and layers icons.
- Left vertical tool capsule.
- Right mutually exclusive sheet.
- Bottom contextual control capsule.
- Bottom-left shortcut encyclopedia.
There is no persistent document header, inspector, operation status area, result tray, or clear distinction between modes, properties, document structure, and global actions. The layout works as a feature demo but will not scale with more editing operations.
P2 — the visual system is not actually centralized
The global stylesheet includes generic shadcn-style light/dark tokens, while the product shell mostly uses raw Tailwind color literals, opacity values, bespoke radii, and repeated class-building functions. The main background uses decorative gradients and most surfaces rely on backdrop blur without a consistent opaque surface color.
Consequences:
- Contrast depends on whatever lies behind a control.
- Nearly every component invents its own surface and selected state.
- Rounded pills are used for containers, rows, inputs, menus, buttons, badges, and destructive actions, weakening hierarchy.
- A complete redesign cannot be implemented reliably through token replacement alone.
Redesign implication: create semantic tokens and primitives after the new workspace hierarchy is approved, not before. Tokens should represent canvas, chrome, panel, raised surface, field, hover, selection, focus, warning, generation status, and mask-edit context.
P2 — tool semantics mix modes, operations, and effects
The rail places Select, Generate, Brush, Eraser, Chroma Key, Magic Wand, and Pan at the same level. These are not peers:
- Select and Pan are navigation/interaction modes.
- Brush and Eraser are paint modes whose meaning changes during mask editing.
- Magic Wand is a selection/mask operation.
- Chroma Key is an effect-to-mask workflow.
- Generate is an AI operation workspace.
The redesign should classify tools by behavior, then decide whether they belong in the global rail, context bar, properties panel, or operation launcher.
P2 — layers carry too many actions but too little visual identity
The Layers sheet includes artboards, tree nesting, visibility, locking, export, grouping, ordering, deletion, mask creation, mask coverage analysis, reveal/hide mode entry, raster mask operations, and rename behavior. Rows lack image or mask thumbnails and do not expose opacity, blend behavior, provenance, or core transform properties.
The result is a dense management panel that still cannot answer the basic question “which visual element is this?” quickly.
Redesign implication: use a durable document tree with thumbnails and compact row actions, then move selected-object properties and mask controls into a contextual inspector. Mask editing should become a clear editor mode, not an expanded sub-card full of unrelated actions.
P2 — panel state ownership is inconsistent — resolved 2026-07-10
Generate visibility is derived from authoritative activeTool, while Layers visibility is local React state. App.tsx then manually enforces mutual exclusion across buttons, shortcuts, effects, and command-palette callbacks.
This works today but does not scale to more panels, inspectors, result trays, modal operation states, or workspace layouts. Meaningful workspace state should have one model and one transition path.
Resolution: workspace panel state and Generate/Layers mutual exclusion now live in EditorState and transition only through commands. React consumes the resulting snapshot without corrective panel effects or local application state.
P2 — view code owns application workflows and side effects — resolved 2026-07-10
React/view modules directly orchestrate image decoding, object URLs, network requests, generation preparation, candidate acceptance setup, raster processing, download behavior, and Comfy model discovery. Important examples are useImageImport.tsx, GenerateControls.tsx, GenerateActionControls.tsx, runGenerate.ts, and the mask/chroma-key helpers.
These functions are testable only unevenly and blur the intended boundary that React should display state and capture intent. The redesign is an opportunity to introduce explicit application services/jobs without weakening the command-only mutation rule.
Resolution: explicit operations/, platform/, and server/ boundaries now separate application use cases, browser/runtime adapters, and backend integrations. View modules emit intent and retain only UI-local drafts/disclosures; operations are prevented from accessing browser globals by ESLint and continue to write state exclusively through commands.
P2 — imported object URLs have no durable ownership — resolved 2026-07-10
Image import creates object URLs and revokes them only when no artboard exists. Successful imports keep the URL indefinitely and would not survive project serialization or browser restart.
Asset sources need a lifecycle: persisted blob/handle, data migration, load/release hooks, and garbage collection when unreferenced.
Resolution: imported files are decoded into serialization-safe data URLs before command submission, eliminating retained object URLs and making imported asset sources independent of browser-session URL lifetimes. Temporary paint-preview object URLs remain platform-owned and are explicitly released.
P2 — primary document actions are either invisible or duplicated
Undo and redo exist only as shortcuts/commands. Export exists in both the global top bar and every artboard row. Generate exists in both the rail and top bar. Fit/reset/zoom actions are split between transient bottom controls and the command palette. The command palette also exposes debug commands in the normal product surface.
The redesign should establish a predictable location for document actions and reserve the command palette for acceleration rather than compensating for missing UI.
P2 — accessibility is inconsistent
The four icon-only top-bar buttons have no aria-label or visible label. Other icon buttons are labeled more carefully, but abbreviations such as “Contig,” “Sub,” “Tol,” “Hard,” and “Clean” assume specialist knowledge. Tooltips depend mainly on native title attributes. Focus styles and hit targets need live verification.
P2 — large modules have become change hotspots — resolved 2026-07-10
Several files combine multiple responsibilities:
commands/document.ts— 923 lines.renderer/image-textures.ts— 754 lines.view/CommandPalette.tsx— 617 lines.view/LayersSheet.tsx— 505 lines.commands/tool.ts— 378 lines.view/bottom-controls/GenerateControls.tsx— 338 lines.commands/generation.ts— 315 lines.view/bottom-controls/ChromaKeyControls.tsx— 313 lines.
Line count alone is not a defect, but these files are already coordinating distinct concepts. The redesign should split by product responsibility rather than by arbitrary component size.
Resolution: document-tree mutation helpers, WebGL texture programs, command-palette item construction, layer mask controls, server routes, and browser raster adapters now have focused modules. The remaining larger files represent cohesive command or rendering orchestration rather than mixing those extracted responsibilities.
P3 — prototype identity remains in project metadata and chrome
The package is still named bun-react-template, the document defaults to “Untitled” without displaying that identity, the app has no visible product title, and debug palette items ship beside user actions. These details reinforce the prototype feel.
P3 — the shortcut panel dominates the workspace
The shortcut reference defaults open, occupies a 30rem-wide floating panel, and competes with the canvas. Shortcuts should be discoverable through tooltips, menus, a compact help entry, and an on-demand reference—not persistent primary chrome.
Proposed information architecture
This is a structural proposal, not a visual design.
Persistent application frame
Top bar
- Product/document identity.
- Save state and project actions.
- Undo/redo.
- Export/share.
- Background job status.
- Command palette and help.
Left tool rail
- Select/transform.
- Pan/hand as a temporary or secondary navigation mode.
- Paint.
- Mask/select region.
- AI operation launcher.
- Tool groups can expand, but only true interaction modes remain persistently active.
Left or right document tree
- Artboards, groups, layers, and masks.
- Thumbnails and clear hierarchy.
- Compact visibility/lock state.
- Creation, grouping, ordering, and deletion.
- No model configuration or mask-processing controls.
Context inspector
- Selected object properties.
- Transform, opacity, mask relationship, provenance, and operation-specific parameters.
- Clear empty and multi-selection states.
Canvas
- The document and direct manipulation overlays.
- Temporary generation previews and compare affordances.
- Explicit mask-edit and operation states.
Result tray
- All current candidates, not an arbitrary subset.
- Selection, multi-select, pin, compare, dismiss, and accept.
- Candidate metadata on demand.
- Accept as layer, replace region, create masked refinement layer, or keep as reference.
- Remains visible while the user inspects document context.
AI operation workspace
The user should begin with an operation, not a backend:
- Generate new image.
- Transform selected image.
- Replace or repair region.
- Extend canvas.
- Remove/replace background.
- Create variations.
Each operation declares:
- Required document context.
- Prompt and references.
- Output frame and placement.
- Quality/profile preset.
- Optional advanced model controls.
- Expected result behavior.
The product can then select a compatible architecture and expose technical overrides only when requested.
Visual redesign principles
The new visual system should be created from scratch after wireframes validate the information architecture.
- Canvas first, not chrome first. UI surfaces should frame the work rather than float decoratively over it.
- Hierarchy through structure, not universal pills. Use shape, spacing, typography, surface level, and borders intentionally.
- Stable panels for stable concepts. Document tree and inspector should not appear and disappear like temporary tooltips.
- Context is explicit. Mask edit, generation preview, job progress, and destructive replacement must be visibly distinct states.
- Progressive disclosure follows expertise. Operation and intent first; sampler, scheduler, encoder, and VAE later.
- Generated results look provisional. Candidates need a clear visual status distinct from accepted document layers.
- Every icon-only control has a name. Labels, tooltips, focus states, and shortcuts are part of the component contract.
- Density is deliberate. The layer tree can be compact; prompts and comparison views need space; destructive choices need clarity.
Architecture recommendations
Preserve
- Core document and asset model direction.
- Command-only state mutation.
- Editor/document state separation.
- Input resolution layer.
- Read-only renderer architecture.
- Generation candidate and provenance concepts.
- Nested group and mask invariants.
Refine before UI migration
- Restore strict TypeScript cleanliness.
- Model workspace/panel state consistently.
- Introduce generation job state separate from candidates.
- Change candidate commitment to preserve unrelated candidates.
- Define placement rules per operation.
- Define project and asset persistence.
- Separate application services from React presentation.
- Add operation precondition selectors with user-facing reasons.
Avoid during redesign
- Rewriting the renderer solely to support new styling.
- Encoding new visual layout concepts in the core document model.
- Adding more one-off local panel booleans.
- Treating every AI workflow as another permanent tool-rail icon.
- Building a large component library before the workspace wireframe is approved.
Recommended implementation sequence
Phase 0 — restore engineering baseline
- Make
bunx tsc --noEmitpass. - Add it to the standard validation script/CI.
- Preserve and finish the current Comfy changes separately.
- Add focused tests for candidate preservation and generation preconditions.
Phase 1 — product model corrections
- Introduce durable generation job state.
- Preserve candidate sessions after acceptance.
- Make all candidates reachable.
- Rename or model variants honestly.
- Define operation-specific placement and prerequisites.
- Define project persistence and asset lifecycle.
Phase 2 — low-fidelity redesign
- Produce workspace wireframes for empty document, layered composition, mask edit, generation setup, generation running, candidate comparison, and candidate acceptance.
- Validate control ownership before choosing colors or component styling.
- Run the pending live visual/interaction audit against the old UI for comparison.
Phase 3 — visual system
- Choose the approved visual direction.
- Create semantic tokens.
- Build the application frame, buttons, fields, tree rows, inspector sections, menus, tooltips, dialogs, status elements, and result cards.
- Document accessibility and interaction states as part of each primitive.
Phase 4 — shell migration
- Implement the new app frame around the existing canvas.
- Migrate document actions, tool modes, document tree, and inspector.
- Keep old workflow surfaces behind temporary boundaries until replaced.
Phase 5 — AI workflow migration
- Implement the operation launcher and context checks.
- Move generation settings into intent-first flows.
- Implement durable jobs and the result tray.
- Surface provenance and “reuse settings.”
- Retire the old Generate sheet and bottom candidate action island.
Phase 6 — persistence and production hardening
- Save/open/recovery.
- Asset storage and cleanup.
- Keyboard and screen-reader pass.
- Pointer, touch, and pen pass.
- Performance profiling with large images, masks, deep trees, and candidate sets.
Verification snapshot
Run on 2026-07-09:
bun test: pass, 130 tests.bun run lint: pass.bun run build: pass.bunx tsc --noEmit: fail, multiple strict type errors.- Local HTTP server: 200 OK on port 3017 when run outside sandbox isolation.
- Screenshot/browser interaction pass: not run, browser automation bridge unavailable.
Immediate next deliverable
Create low-fidelity wireframes for seven canonical states:
- Empty/new project.
- Imported image with layers visible.
- Selected layer with transform properties.
- Mask editing.
- AI operation setup.
- Generation running with persistent job status.
- Candidate comparison and multi-result acceptance.
The wireframes should intentionally ignore the existing floating-island styling. They should test information architecture and control ownership before visual direction is explored.