Without this, any constructor throw in any module stops load_modules() mid-loop
and render_main_gui() (which builds the menu) never runs — resulting in a blank
page with no menu. The failing module is now logged to the browser console
instead of silently aborting startup.
https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
main.py referenced info.vram_gb but the field is info.vram_total_gb.
This crashed the FastAPI lifespan hook on every startup when
AI_PROVIDER=local_gpu, causing a restart loop.
https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
Old README described the original React/Fabric.js UI and listed
"No local GPU inference" as a non-goal. Updated to reflect:
- miniPaint-based editor with SAM brush selection
- GPU quick-start (nvidia-container-toolkit prereqs, docker-compose.gpu.yml)
- Cloud API quick-start
- GPU tier auto-selection table (FLUX/SDXL/SD by VRAM)
- Full feature list (selection actions, print tools, progress bars)
- Correct clone URL and update commands
- Troubleshooting section
https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
Backend:
- local_diffusion.py: add _make_step_cb() that writes step/total_steps/
progress into _states on every diffusers callback_on_step_end; wired into
txt2img, inpaint, img2img with TypeError fallback for older diffusers
- ai_tools.py: GET /api/generate/progress SSE endpoint — streams _states
as JSON array every 200ms so clients get live denoising step counts
Frontend:
- progress_overlay.js: add connectProgressSSE(pipeType, baseUrl) /
disconnectProgressSSE() — opens EventSource, maps step/total_steps
to bar percentage (0→85% during denoising, 85→100 for decode/place)
- text_to_image.js: connect SSE before POST, disconnect on done/error
- selection_actions.js: connect SSE for AI edit / asymmetry operations
Result: for local GPU, progress bar shows "Step 12 / 30" with exact fill;
for remote providers and upscale (no step callbacks), shimmer animates.
https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
- New progress_overlay.js: animated fullscreen overlay with shimmer bar,
fake progress creep, Esc-to-cancel, used by all slow AI operations
- text_to_image.js: allow local_gpu provider (was incorrectly blocked);
show provider/model/VRAM info in dialog; show estimated generation time;
use progress overlay during generation
- upscale.js: replace alertify.message with progress overlay (90s estimate
for AI upscale, 10s for Lanczos)
- frame_fit.js: progress overlay for extend mode (AI outpaint ~45s)
- print_prepare.js: progress overlay for full upscale+frame chain (~2 min)
- selection_actions.js: progress overlay for all AI region edits
https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
- Add 18x24" to FRAME_SIZES in backend and frontend (frame_fit.js)
- Add 200 DPI option to frame_fit dialog (adequate for large-format prints)
- Add 18x24 portrait/landscape at 200 and 300 DPI to Canvas Size presets (size.js)
- New /api/print/prepare endpoint: chains AI upscale to target DPI then frame-fit
in one server-side call (avoids round-tripping a large upscaled image)
- New print_prepare.js module: "Prepare for Print" dialog with per-frame quality
assessment (current effective DPI, needed upscale factor, AI vs Lanczos note)
- Add "Prepare for Print..." to Image menu above "Fit to Frame..."
https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
Backend (3 new endpoints under /api/image/):
- POST /api/image/scale-selection — scale selected object by any % in-place;
LaMa/OpenCV fills the exposed gap so the scene looks natural
- POST /api/image/ai-edit-region — AI redraws the masked region via the
configured inpaint provider (local_gpu / InvokeAI / ComfyUI / OpenAI)
- POST /api/image/paste-into-selection — scales clipboard image to fit the
selection bounding box, masks it to the selection shape, composites result
Frontend (selection_actions.js + tool integration):
- New SelectionActions panel: fixed bottom-center HUD that appears
automatically after every SAM selection (click or paint)
- Panel actions: Scale by % (default 3%), Make less symmetrical (AI),
custom AI Edit prompt, Replace with clipboard, Copy/Cut to layer, Erase
- Both smart_select.js and brush_select.js updated to show the panel,
add updateLayerWithResult(), and hide panel on clearSelection/on_leave
- brush_select: offerFloatSelection() replaced with richer action panel
Real-world workflows now supported in one click after painting over object:
"Make this 3% bigger" → scale-selection (LaMa fills gap)
"Make this less symmetrical" → ai-edit-region with asymmetry prompt
"Replace this with what I copied" → paste-into-selection
https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
Model selection:
- Add sdxl_offload tier (eff_vram ≥ 4.0 GB) for GTX 1060 6GB and Quadro
6GB cards that were falling through to SD 2.1 despite SDXL fitting with
model_cpu_offload. Cards with 5.3 GB effective VRAM now get SDXL quality.
- Update _tier_label(), _caps(), _build_warnings() for new tier.
Frontend GPU display:
- api.js: add getGpuStatus() fetching /api/gpu/status
- capabilities.js: add getGpuStatus() export with own LRU cache;
refreshCapabilities() now also resets GPU status cache
- provider-badge.js: when AI_PROVIDER=local_gpu show green badge with
GPU name, tier, VRAM, CC, feature flags, and capabilities in tooltip.
Strip "NVIDIA GeForce" prefix so "GTX 1060 6GB" fits in badge.
- ai_provider_settings.js: add local_gpu to all provider dropdowns;
show GPU info panel (device, VRAM, CC, features, tier, model table per
operation) in the settings dialog when a GPU is detected.
https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
The fitted image was processed and inserted correctly but the canvas
(config.WIDTH/HEIGHT) was never updated, so the result was clipped to
the original canvas size. Now wraps both new-layer and replace-layer
paths in Prepare_canvas_action + Update_config_action so the canvas
expands (or shrinks) to the target frame size automatically.
https://claude.ai/code/session_017wupXfpjuoSdAqVyak4fJf
Resize dialog:
- Units selector (pixels / inches) at the top; switching updates the
Width/Height placeholders and clears any partially entered values
- New "Crop to fill" checkbox: when both Width and Height are given,
scales the image with cover-fit (fills target without letterboxing)
then center-crops — subject looks the same at 5x7, 8x10, 11x14
- resize_layer/resize_gui now honour params.units instead of only
reading the global default_units setting
Global units persistence:
- Switching units in either Resize or Canvas Size saves the choice to
default_units so both dialogs open with the same unit next time
https://claude.ai/code/session_017wupXfpjuoSdAqVyak4fJf
- Units selector (pixels/inches) directly in the dialog; switching live-converts
the width/height fields so you can type e.g. 5 / 7 without mental math
- Six print-size presets at 300 DPI added to the Resolution dropdown:
5x7, 8x10, and 11x14 in both Portrait and Landscape orientations
- New "Resize & crop image" checkbox: when enabled, image layers are scaled
with cover-fit (fills the target canvas, no letterboxing) and center-cropped
so the subject stays proportionally the same across all three print sizes
https://claude.ai/code/session_017wupXfpjuoSdAqVyak4fJf
ai_edit.js:
- Extend Base_tools_class and add load() + default_events() so mouse
events actually wire up (was the root cause of tool not working)
- Use get_mouse_info() for coordinate mapping instead of manual
clientX/Y math — consistent with all other miniPaint tools
- Fix _mouseToImage() to use mouse.x/y (already in image coords)
and correct display-scale for overlay brush rendering
- mousedown/mousemove/mouseup now guard on config.TOOL.name
ai_edit.svg: new icon (brush + sparkle star) for the left toolbar
layout.css: add .ai_edit:after CSS rule for the icon
main.js:
- Collapse right-panel Colors section by default (respects saved cookie
so user preference persists)
- Mount compact foreground/background color swatches at the bottom of
the left toolbar; click either square to toggle the full color picker
open/closed; syncs live with config.COLOR every 250ms
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
sklearn was not installed in the container, causing ModuleNotFoundError on
import of ai_tools.py and preventing the server from starting.
Replaced with a self-contained numpy k-means++ implementation:
- k-means++ seeding for better initial centers
- 20-iteration Lloyd's algorithm
- Same output: hex colors sorted by cluster frequency
No new dependencies required.
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
pick_color.js: on hover shows a floating tooltip with:
- 40px color swatch of the sampled pixel
- Hex (#RRGGBB), RGB, and HSL values
- Nearest Pantone name and swatch from the database
- ΔE value (CIE76 color distance in LAB space)
- Quality badge: Excellent (<2) / Good (2-5) / Fair (5-10) / Poor (>10)
- Warning note when ΔE > 10 (color cannot be faithfully printed as Pantone)
Click copies hex to clipboard and sets as active color (existing behavior).
color_utils.js: hexToRgb, rgbToHsl, sRGB→XYZ→CIE LAB conversion (D65),
CIE76 deltaE, nearestPantone() (scans all ~350 entries, returns best match).
Pantone database is pre-converted to LAB at module load for fast scanning.
pantone_colors.js: ~350 representative PMS colors across reds, oranges,
yellows, greens, teals, blues, purples, magentas, browns, grays, black/white.
Hex approximations from open-source Pantone datasets (not official Pantone data).
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
Auto-Enhance (Image menu):
POST /api/enhance — gray-world white balance, CLAHE contrast on L channel,
saturation boost ×1.15 in HSV, unsharp mask; all blended by strength slider
Frontend: strength selector (25/50/75/100%), keep-original option
Extract Color Palette (Image menu):
POST /api/extract-colors — k-means on 150×150 thumbnail, returns N dominant
colors sorted by cluster size. Frontend: floating swatch panel, click=copy
hex, shift+click=set as active color, toggle on/off.
History Panel (Edit menu, Ctrl+H):
Pure frontend — reads app.State.action_history and action_history_index,
renders clickable list of past actions (newest first), click any step to
undo/redo to that point. Auto-refreshes every 800ms while open.
Align to Canvas (Layer menu):
Floating toolbar with 7 alignment buttons: center H, center V, center both,
align left/right/top/bottom edges. Uses Update_layer_action for undo support.
Add Text (Generate menu):
6 styled presets (Heading, Subheading, Body, Caption, Quote, Bold Label)
shown as live-rendered previews in the dialog. Click a preset to insert a
text layer with the correct font/size/weight/color pre-applied.
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
- tiff-writer.js: pure-JS multipage TIFF encoder with RGBA and CMYK
color modes; RGB→CMYK conversion; one IFD per layer for multilayer
- pdf-writer.js: pure-JS PDF-1.4 writer; RGB pages use browser JPEG
encoding (DCTDecode); CMYK pages embed raw DeviceCMYK pixel data;
multipage support (one page per visible layer)
- save.js: adds TIFF (CMYK), TIFF (Multilayer), PDF (RGB), PDF (CMYK)
to the export dialog; CMYK types bypass the separated-layers loop and
composite with a white background before color conversion
Backend:
- sam_service.py: auto-downloads SAM ViT-B (~375 MB) on first use with
progress tracking; loads model to CUDA/MPS/CPU; predict_points() takes
multi-point prompts (include/exclude labels) and returns best mask
- POST /api/segment/point: SAM point-prompt endpoint; returns mask PNG
- GET /api/segment/install-status: poll download progress
- POST /api/segment/install: explicit trigger (also auto on first click)
- main.py: pre-download SAM on startup alongside NCNN
Frontend (ai_edit.js):
- Click mode (default): click object → SAM generates mask instantly
Alt+click → subtract (deselect over-selected area)
Multiple clicks accumulate for multi-object or refinement
- Brush + / Brush − modes: paint to add or erase from SAM mask by hand
- If SAM model is still downloading on first click: inline progress bar,
user retries the click when done
- Unified action bar: Erase | Replace (inline prompt) | Upscale | Expand | Clear
- All modes share the same mask canvas; SAM and brush are fully composited
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
Before: 4 separate tools (ai_inpaint, ai_lama_erase, ai_smart_inpaint,
ai_replace_selection) with no clear relationship, buried in toolbar.
After: one "AI Edit" brush tool. Paint mask → floating action bar appears:
Erase — LaMa removes masked content, no prompt needed
Replace — inline prompt reveals in-place ("make her smile" / "replace
with a wolf") → calls /api/inpaint/remote
Upscale — delegates to existing upscale module
Expand — delegates to existing outpaint module
Clear — wipe mask and start over
Brush size controlled by tool attributes (5–200px).
Red overlay gives immediate visual feedback while painting.
Floating panel appears as soon as first brush stroke is made.
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
- Add _vulkan_available(): checks /dev/dri/renderD* on Linux, assumes
true on macOS/Windows; set REALESRGAN_NCNN=force to override
- Add _test_ncnn_binary(): test-runs the binary after install and checks
stderr for "no vulkan" — marks skipped if Vulkan init fails at runtime
- ensure_ncnn_installed() now returns early with state=skipped when no
Vulkan detected, avoiding a wasted ~30MB download on CPU-only servers
- Recommend PyTorch CPU when available on headless (AI quality, slow but
works); Lanczos as final fallback
- Frontend: handle state=skipped immediately (no polling needed), show
brief informational toast; show "Headless server" note in dialog
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
- upscale.py: add InstallStatus dataclass + ensure_ncnn_installed() async
function that downloads and extracts the NCNN binary for the current
platform (Linux/macOS/Windows), tracks progress (0-100%), and busts the
caps cache when done
- main.py: trigger ensure_ncnn_installed() as a background task on app
startup when no AI upscaler is detected
- print_tools.py: /upscale/available triggers install task when no AI
upscaler found; new GET /upscale/install-status endpoint for polling
- upscale.js: if no AI upscaler on open, poll install-status showing a
progress bar notification, then refresh caps and proceed when done
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
Detection priority (probed once, cached):
1. Real-ESRGAN PyTorch + CUDA GPU → fastest, best quality
2. Real-ESRGAN PyTorch + Apple MPS → fast on Apple Silicon
3. Real-ESRGAN NCNN Vulkan binary → fast on any GPU via Vulkan (no CUDA needed)
4. Real-ESRGAN PyTorch CPU → works, slow (warned in UI)
5. Lanczos → always available, instant fallback
Backend:
- services/upscale.py: full capability probe (probe_upscale_capabilities),
implementations for PyTorch (CUDA/MPS/CPU auto-device) and NCNN binary,
upscale_sync() resolves method with fallback chain,
async upscale_image() runs in thread pool
- print_tools.py: /api/print/upscale uses new service; method="auto" by default;
GET /api/print/upscale/available returns full capability map with device info
and recommended_label; POST /api/print/upscale/refresh-caps busts cache
without restart (useful after installing NCNN binary into container)
Frontend:
- upscale.js: fetches capability map on first open; builds method selector showing
only available options; labels recommended method with ★; shows device info
(CUDA/MPS/CPU/NCNN) in dialog; maps display label back to method key on submit;
shows actual method used in success toast and undo history entry
Scripts:
- scripts/download_realesrgan.py: downloads NCNN Vulkan binary for current platform
(Linux/macOS/Windows) to /app/data/models/realesrgan/; makes executable;
run inside container or locally
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
Backend — new /api/print/* router:
- POST /api/print/frame-fit: fit image to 4x6/5x7/8x10/11x14/16x20/20x24/24x36
and square sizes (4x4/8x8/12x12) at configurable DPI.
Three modes:
crop — center-crop to aspect ratio, Lanczos scale to print res (no AI)
extend — scale to fill one dimension, AI-inpaint the gap; mirror-fill fallback
smart — auto: extend if gap < 15% of frame dimension, else crop
Auto-detects orientation from image shape; respects explicit portrait/landscape.
- POST /api/print/upscale: Lanczos scale (always) or Real-ESRGAN (if installed)
- GET /api/print/frame-sizes: frame catalogue with pixel dimensions at 300dpi
- GET /api/print/upscale/available: reports whether Real-ESRGAN is installed
Frontend:
- modules/image/frame_fit.js: dialog with frame size, orientation, mode, DPI,
optional extend prompt; shows current image size; result as new layer option
- modules/image/upscale.js: dialog with scale factor (1.5–4×), method selector
(auto-hides AI option if Real-ESRGAN not available); result as new layer option
- config-menu.js: Fit to Frame... and Upscale... added under Image menu
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
Each operation (inpaint, txt2img, img2img, outpaint) can now use a different
provider. Resolution order: per-op override → global AI_PROVIDER default.
Example: txt2img→openai, inpaint→invokeai, everything else→invokeai default.
Backend:
- config.py: add AI_PROVIDER_INPAINT / TXT2IMG / IMG2IMG / OUTPAINT settings
- remote_provider.py: get_remote_provider(operation) resolves override then default;
_build_provider() extracted as shared factory; _OP_FIELD maps op→setting name
- ai_tools.py: each endpoint passes its operation to _require_remote();
GET /api/config runs per-op health checks concurrently, returns operations map
and overrides; POST /api/config accepts and applies per-op override fields
Frontend:
- ai_provider_settings.js: four new selects (inpaint/txt2img/img2img/outpaint);
persists to localStorage and sends per-op fields to POST /api/config
- provider-badge.js: shows override summary (e.g. "invokeai · txt2img→openai")
and per-op health in tooltip
- .env.example: document per-op override env vars with examples
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
Frontend:
- tools/ai_replace_selection.js: use any selection → remote inpaint with prompt
- modules/generate/text_to_image.js: Text → Image dialog (new layer or replace canvas)
- modules/generate/outpaint.js: Expand Canvas in any direction via remote provider
- modules/tools/ai_provider_settings.js: in-app provider config (OpenAI / InvokeAI /
ComfyUI / Replicate); persists to localStorage, pushes to POST /api/config at runtime
- config.js: register ai_replace_selection tool
- config-menu.js: add Generate menu (Text→Image, Outpaint); AI Provider Settings under Tools
- modules/help/about.js: updated credits (LaMa, rembg, SAM, InvokeAI, ComfyUI, OpenAI)
- api/capabilities.js: add refreshCapabilities() for post-save cache invalidation
Backend:
- routers/ai_tools.py: POST /api/config — apply provider settings at runtime
without restart (session-scoped, non-persistent; .env for permanence)
https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
- Switch U2Net from onnxruntime to OpenCV DNN to avoid
"cannot enable executable stack" error in Docker
- Add alertify dialog styling to fix white text on white
background issue in popups
- Add offerFloatSelection() to AI Paint that prompts user
after selection to move/scale it (Canva-like workflow)
- Auto-switch to Select tool after floating selection
https://claude.ai/code/session_01CLedz6CanT9t46KBvng3vz
- Fix brush_select isProcessing flag not resetting after first use
(reset in on_leave() when switching tools)
- Add onnxruntime dependency for U2net background removal
- Use full U2net model (176MB) instead of lightweight for better quality
https://claude.ai/code/session_01CLedz6CanT9t46KBvng3vz
- Fix brush_select and smart_select mask scaling to match layer dimensions
- Add "Float Selection" feature to Select tool - when switching to Select
with an active AI selection, offers to copy it to a movable layer
- Add aspect ratio lock toggle (🔗 button) in layer details panel
- When locked, changing width auto-updates height and vice versa
- Add U2net model auto-download - will download lightweight u2netp.onnx (~4MB)
automatically if no model found
- Improve error messages for background removal
- Register on_activate for select tool in config
Workflow: Select object with AI tool → Click Select tool → "Float" selection
→ Move/scale the floated layer freely
https://claude.ai/code/session_01CLedz6CanT9t46KBvng3vz
- Fix My Library: Add CSS styling for library browser, items now visible
- Integrate My Library into Shapes tool with tabbed interface
- Improve AI Inpaint: Add transform mode for scaling/sizing selections
- Add helpful guidance explaining inpaint vs transform modes
- Add U2net as alternative background removal (avoids rembg issues)
- Create U2net model definition and download script
- Improve Caddyfile with multiple options and troubleshooting guide
Note: Brush Select (AI Paint) tool was already implemented and working.
https://claude.ai/code/session_01CLedz6CanT9t46KBvng3vz