Review fixes - #2
Merged
Merged
Conversation
- [workspace.dependencies] for image/serde/serde_json shared with pixfix - [profile.release]: thin LTO, strip, codegen-units=1 for the shipped CLI - shellexpand moved behind the tui feature (its only consumer) - rand 0.8 -> 0.9 (rng()/random() renames; seeded determinism comes later) - f32::total_cmp replaces partial_cmp().unwrap() in score/distance sorts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NormalizeError (thiserror) now covers I/O, image load/encode, grid, palette, Lospec, and config failures with paths attached where relevant, and every lib module returns it instead of anyhow or Result<_, String>. anyhow remains only in main.rs glue. Adds the documented CLI exit-code policy (2 usage, 3 input I/O, 4 processing, 5 output exists, 6 partial batch) with exit_code(); main adopts it in a later commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All pipeline defaults and validation now live in one place: PipelineOptions (all-Option plain data) + overlay() precedence merge + into_config(). ConfigFile and PipelineFlags lower into it; pixfix follows later. Wires the previously parsed-but-ignored grid.phase_x/phase_y config keys, moves palette-file/Lospec I/O ahead of image loading, and validates grid size/phase (fixing the --grid-size 0 panic path). Precedence unit tests cover CLI > file > defaults, including the max_grid_candidate inversion regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… path - PipelineFlags carry no clap defaults; hard defaults live only in PipelineOptions::into_config, so an explicit CLI value now always beats the config file (fixes the inverted max_grid_candidate precedence) - DownscaleMode is a clap ValueEnum; --palette and --bg-color validate at parse time; palette sources form a mutually exclusive group that conflicts with --no-quantize - requires=grid_size on --grid-phase and --no-grid-detect, and run_pipeline applies overrides even when detection is skipped (fixes the silent no-op) - bool pairs (--remove-bg/--no-remove-bg, --flood-fill/--no-flood-fill, --overwrite/--no-overwrite) so config-file booleans can be turned off - --no-config, and an explicit --config path that is missing now errors instead of silently using defaults; loaded config path is logged - config assembly happens before image I/O in every command; build_pipeline_config is gone Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New pipeline::grid::Grid carries f32 pitch and canonical phase per axis; PipelineState's grid_size/grid_phase collapse into grid: Option<Grid>. All block iteration goes through Grid::edges_x/edges_y, which clamp and count partial blocks, so pre-phase margins and trailing remainders are now processed instead of skipped, and the width-minus-phase u32 underflow class is gone. --grid-size accepts fractional values (e.g. 10.667). Detection itself still scans integer candidates; the fractional search lands next. grid_variance_scores becomes grid_scores: Vec<(f32, f32)> and diagnostics gain grid_best_guess; TUI and pixfix updated for the new types. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Detection is rebuilt around two 1-D alpha-aware OKLAB gradient profiles (one O(W*H) pass) scored per (pitch, phase) in O(len/pitch), making an exhaustive fractional pitch search affordable — AI upscales routinely use non-integer pitch (512px / 48 cells = 10.667) that integer-only detection could never find, which is how the old code locked onto 2px upscaler ringing. Scoring is energy concentration (captured fraction minus coverage fraction) under dual window policies, so no comb can win vacuously by coverage; harmonics lose on missed edges and sub-pitches on doubled coverage, with a refined integer-multiple comparison as a safety net. Phase is scanned exhaustively per axis (the 8/12px caps are gone), pitch refines by shrinking-step local search with parabolic sub-stepping, near integer estimates snap when the integer scores comparably (clean upscales stay exact), and near-equal axes constrain to a square grid. Confidence becomes sqrt(quality x separation) with the runner-up drawn from outside the winner's harmonic family; below a 0.35 floor the pipeline declines to snap and reports the best guess instead of inventing a grid — noise and heavily AA'd images now warn instead of producing pitch-2 garbage. Transparent pixels contribute silhouette-edge evidence through alpha but their undefined RGB never does. max_candidate now defaults to scaling with image size. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
oklab_hyab (|dL| + chroma distance) lands for the palette matcher; the triangle-inequality is_between test moves from aa_removal to oklab.rs so background de-fringing can share it; ColorHistogram gains from_pixels, sorted_entries (deterministic count-desc/RGBA-asc order for clustering inputs), and opaque_rgb_counts (alpha-stripped, alpha-0 excluded). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lettes, hyAB matcher kmeans_weighted replaces kmeans_oklab/subsample: clustering runs over the color histogram's unique colors weighted by count (exact, no 10k random subsample), so rare structural colors like 1px outlines can no longer be dropped, and all randomness flows from a ChaCha8 seed — identical input now produces identical bytes on every run and platform, which unlocks golden tests. Empty clusters reseed at the farthest weighted point, the k-means++ init keeps a running min-distance array (O(nk)), and pinned centroids (for upcoming dither pairs) join assignment but never move. Auto-extracted palettes default to weighted medoids — real image colors, never invented averages (Mean stays available); palette snapping goes through PaletteMatcher with hyAB distance (city-block lightness tracks perception better at large deltas: dark blue now snaps to navy, not black) memoized per unique RGB instead of a full conversion and palette scan per pixel. palette extract emits sorted medoid colors. Quantization records unique-colors before/after in diagnostics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neighbors below 50% alpha no longer vote or become snap targets — their RGB is blend residue or garbage, and letting them in painted dark halos around sprites on transparent backgrounds. AA removal now iterates up to --aa-passes passes (default 3, early-stopped) so the 2-3px ramps on 10x AI upscales fully collapse instead of leaving residue. A dither guard (on by default) skips any pixel whose exact color appears at least twice among its opaque neighbors: checkerboard partners always have exact diagonal twins, so ordered dithering survives multi-pass removal while lone blend pixels still collapse. The neighborhood scan also returns pixel indices so the between-test reuses precomputed OKLAB conversions, OFFSETS is shared from image_util::neighbors, and pixels-changed/passes land in diagnostics. Threshold docs now match the code: higher = more aggressive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, block confidence Block votes are no longer keyed on exact RGBA: colors bucket by exact RGB, then agglomerate in OKLAB within about one JND, so the per-pixel chroma noise of AI upscales pools its weight instead of letting five identical fringe pixels outvote a hundred nearly-identical block pixels. The winner paints its weighted medoid — always a real input color. Near-transparent pixels never vote color (no more black fringes from RGBA(0,0,0,0) winning), and each block binarizes to fully opaque or fully transparent by weighted alpha vote; --keep-alpha preserves original per-pixel alpha. All modes route through the same vote; CenterPixel samples the fractional block center with a vote fallback. Per-block winner shares land in diagnostics as BlockShareMask, the CLI reports contested blocks, and background-masked pixels (wired next) are excluded from votes with the mask re-blockified after normalization. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nging Border-color detection clusters border pixels in OKLAB (two-pass, mean-refined, four seed attempts) instead of exact-RGBA counting, so gradient and vignette backgrounds — where every border row is a unique value and the old histogram silently gave up — now detect with proper coverage, and the fill tolerance stretches up to 2x for spread-out clusters. Detection and mask construction moved BEFORE grid normalization, at full resolution where the AA fringe is genuinely one pixel wide: masked pixels are excluded from block votes, the mask is re-blockified through normalization, and clearing happens afterwards — the fringe can no longer be baked into solid halo blocks that flood fill then refuses to remove. A defringe dilation additionally absorbs pixels that interpolate between background and sprite. Quantization now runs after background removal so background colors never eat palette slots. Flood fill is shared from image_util::flood (sprite-sheet autosplit adopts it next); detected color/coverage/pixels-removed land in diagnostics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New pipeline::dither detects ordered dithering (checkerboard and row/ column alternation) between block-color pairs on the logical grid, post-normalization: block colors group within an OKLAB tolerance, each block checks its 4-neighborhood for alternation, and a pair qualifies only past both an absolute floor (8 blocks — stray specks can't reach it) and half the pair's total footprint (two adjacent solid regions don't count). Detected pairs are pinned as fixed k-means centroids so quantization cannot merge the partners and flatten the shading; fixed-palette modes warn when a pair collapses onto one entry. On by default per the dithering-looks-nicer policy, with --flatten-dither disabling detection, pinning, and the AA dither guard in one switch. Also lands --seed and --keep-alpha/--logical-size plumbing through options. Pairs are reported in diagnostics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-size Reduced-resolution modes no longer Nearest-stretch the logical image back to the original dimensions — when the pitch doesn't divide evenly that recreated uneven 4px/5px logical pixels, the exact artifact this tool removes. The default output is now an exact pixel-repeat re-upscale by the rounded pitch (48 cells at 10.667px -> 528px, crisp), --logical-size emits the true logical resolution, and explicit --target-width/height still win as a final Nearest resize. Snap mode is unchanged at original resolution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tiles record their sheet-coordinate origin, and the sheet-detected grid is rebased ((phase - origin) mod pitch) into each tile's local frame instead of being copied verbatim — with margins or spacing that aren't a multiple of the pitch, every tile previously snapped against a shifted phase and produced mixels. Tiles no longer re-detect; they receive an explicit per-tile override, and reassembly pads to the max processed tile size since rebased phases can shift logical block counts by one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All CLI output now flows through a Reporter: --quiet actually silences the report lines (they were raw eprintln! before), and --json emits exactly one machine-readable document on stdout — grid pitch/phase/ confidence, logical size, palette, color counts, AA/background/dither stats, contested-block counts, warnings, duration — with a stable always-present-fields schema and NDJSON progress reserved for stderr. main() maps typed errors to documented exit codes (2 usage/config, 3 input I/O, 4 processing, 5 output exists, 6 partial batch; clap usage errors are already 2), and the new analyze subcommand runs grid and background detection and color counting with zero writes so agents can inspect before committing to a pipeline run. palette list/extract/fetch gained JSON documents too; anyhow is gone from the binary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rmat One derive_output_path replaces the copy-pasted naming logic, and the derived extension always comes from the OUTPUT format (png by default) — JPEG inputs used to run the whole pipeline, fail encoding RGBA8 to JPEG, and leave a zero-byte output behind. Saves now encode to a temp file in the destination directory and rename into place, so no failure mode leaves partial files. `-` works as input (stdin) and output (stdout PNG, rejected with --json which owns stdout), --output-format selects png/webp/bmp, and a single check_writable enforces the no-clobber policy everywhere — including sheet --output-dir sprites and palette extract/fetch -o, which previously overwrote unconditionally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-dirs run_batch no longer constructs a progress bar inside the library: it takes a progress sink (the CLI wires indicatif, hidden under --quiet and --json; JSON mode gets NDJSON per-file events on stderr), so pixfix can reuse the parallel implementation instead of its serial copy. All output paths are planned up front with case-insensitive collision detection — recursive globs that flatten a/x.png and b/x.png onto one output used to race two rayon workers over the same file — and --preserve-dirs recreates the input tree instead. --suffix (empty allowed) replaces the hardcoded naming rule agents had to reverse-engineer, extensions match case-insensitively (.PNG works), every file outcome is itemized in the result and the JSON report, already-existing outputs are counted as skipped rather than failed, and partial failure exits 6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A 10s global timeout keeps a stalled connection from hanging the CLI (or a desktop-app worker); slugs are validated to lowercase/digits/hyphens before they reach either the URL or the cache path — '../../x' could previously escape the cache directory on both read and write; palette fetch --refresh bypasses the cache; and the doc comment now names the real platform cache location instead of the Linux-only path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…od fill The fixed-grid split/assemble path and the auto-split detector are two unrelated systems; they now live in their own submodules. The hand-rolled border BFS in cell extraction is replaced by the shared image_util::flood::flood_fill_from_border, the dead RowEnvelope max_bottom_gap field (written with .min() despite its name, never read) is gone, and the two library-level eprintln calls became tracing events so the TUI and desktop app cannot get stderr-corrupted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The desktop app now lowers its ProcessConfig into the library's PipelineOptions and resolves through the same single assembly path as the CLI — the duplicated builder had already drifted (bad downscale modes silently became Snap; now they error). Previews travel as raw IPC bytes instead of JSON number arrays (about 4x smaller per slider drag), auto-split uses the library's background detection instead of hardcoded white, the hand-rolled palette parser (with its operator-precedence bug) is gone in favor of the library's, the app state uses parking_lot (no poisoning), a generation counter stops stale pipeline runs from overwriting newer results while scrubbing, and pipeline work runs under spawn_blocking so the UI stays responsive. Batch goes through the library's parallel run_batch. Fractional grid pitches display properly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
assert_cmd tests covering the agent-facing contract: distinct exit codes (2 usage/config, 3 input, 5 output-exists, 6 partial batch), the --json document schema for process/analyze/batch, --quiet emitting nothing on success, JPEG inputs deriving .png outputs with no partial files, stdin/ stdout roundtrips, seeded byte-identical determinism, batch collision rejection with --preserve-dirs resolution and case-insensitive extension pickup, fractional-pitch detection end to end, and noise declining to snap while still reporting a best guess. Synthetic images are generated in-test (tests/common), so no fixtures are checked in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test job now gates on cargo fmt and clippy -D warnings (and runs the test suite with the tui feature compiled). A new check-pixfix job runs clippy on the desktop app for every PR — the root test job never built it, so PRs could not catch pixfix breakage. The duplicated manual release path is gone from ci.yml: tag pushes release automatically here, manual re-releases from an existing run stay in release.yml. The built frontend bundle pixfix/ui/app.js is no longer tracked (hooks and CI rebuild it), and the tauri.conf build hooks use pixfix-relative paths so tauri build works from the app directory instead of only the repo root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…claims Documents fractional grid detection and the low-confidence best-guess behavior, the analyze subcommand, the scripting/agents section (--json document contract, NDJSON progress, exit-code table, determinism via --seed), dither preservation and --flatten-dither, alpha binarization and --keep-alpha, output sizing (--logical-size, integer re-upscale default), --output-format/stdin/stdout piping, batch --suffix and --preserve-dirs with collision behavior, and config precedence with --no-config. Fixes stale claims: the tui feature is opt-in (not default), batch does not take --target-width/height, --aa-threshold now lives under anti-aliasing with --aa-passes, and palette caching is described accurately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mechanical rustfmt pass so the new CI fmt gate starts green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…paths Verified by building the app bundle: Tauri v2 executes beforeBuildCommand from the cargo workspace root, not the config directory, so the repo-root-relative hook paths are the correct ones. The CI simplification stands (no override or separate frontend step needed); only the comment changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--chroma-key <HEX> (repeatable) removes the given colors everywhere in the image within --chroma-tolerance (default 0.05, OKLAB) — no border detection, no flood fill, so interior regions a border fill could never reach are keyed too. Keys build their own mask through the same pre-snap flow as background removal (keyed pixels never vote in block snapping, de-fringing absorbs the AA halo around keyed regions) and combine freely with --remove-bg. Config files take background.chroma_keys = ["FF00FF"] and chroma_tolerance; pixfix gets a Chroma Key setting in the background section wired through the shared options path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A hand-built single page in the desktop app's Catppuccin Mocha + monospace styling: the hero animates scattered, blurred pixels settling onto the grid (the tool's whole job, as a visual), sections reveal gently on scroll, and the nav underlines the active section. All motion is eased and plays once; prefers-reduced-motion and no-JS both degrade to fully static. Content covers the pipeline stages, quick-start recipes, a condensed flag reference, the agent contract (JSON document, exit codes), and the desktop app. llms.txt (index + key facts) and llms-full.txt (the complete manual in plain text) ship alongside for language models. A Pages workflow deploys docs/ on pushes to master. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A coverage job runs cargo-llvm-cov over the CLI crate and uploads lcov to Codecov (non-gating; needs the CODECOV_TOKEN repo secret). The README gains the codecov badge and a link to the docs site, documents chroma keying with an example, points agents at llms.txt/llms-full.txt, and adds an analyze example to the quick start. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pixel map's right lobe was shifted a column and a stray outline ran down the middle — the heart on the page about fixing wonky pixels was itself wonky. Now left-right symmetric. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erence The product has been pixfix all along; only the plumbing said otherwise. The root crate and binary are now pixfix (cargo install pixfix, pixfix process ...), the Tauri crate becomes pixfix-desktop in desktop/ so the names can coexist in one workspace, and every reference follows: CLI command name, config file (.pixfix.toml), Lospec cache directory, CI package/artifact/binary names, README, docs site, llms.txt manuals, and the tauri.conf hook paths. Repo links point at lovelaced/pixfix, which GitHub already redirects to. Verified end to end: full test suite, clippy, fmt, and a fresh pixfix.app bundle from the renamed crate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…artistic resolution Detection finds the grid the generator physically painted on (often ~2px at 1024), but the intended pixel-art resolution usually sits at a multiple of it — a 512x512 logical narrowboat is no SNES scene. --coarsen N multiplies the final pitch (auto-detected or --grid-size, fractional included: 2.714 x 2 = 5.428) before snapping. Phase is preserved so coarse blocks stay aligned with the fine grid, it does nothing when detection declines, programmatic per-tile sprite-sheet grids are never re-coarsened, and reported values show the multiplied pitch while grid_best_guess diagnostics keep the raw detection. Documented with exact semantics in the README, docs site, and llms-full.txt; config key [grid] coarsen; unit + CLI tests cover multiplication, phase preservation, override interaction, and rejection of 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… chroma keys The detection confidence floor (default 0.35) is now settable from the CLI, config file ([grid] min_confidence), and desktop app — heavily anti-aliased images that score just under the default can be accepted without hand-forcing a pitch, and 0 accepts anything. The desktop app gains Coarsen and Min Confidence setting rows and its chroma key field now takes a comma-separated list, closing the parity gap with the CLI. Documented in README and llms-full.txt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When one axis's pitch sits at an integer multiple (2-4x) of the other's, the coarser axis has usually locked onto a harmonic of the true pitch — dense dither makes the multiple score within the prefer-larger rule's tie factor on one axis but not the other, which is exactly what happened on a real 16:9 Midjourney test image (x locked 5.41 while y found the true 2.71). The coarser axis is now re-refined at the finer axis's pitch and adopts it when competitive; genuinely anisotropic grids keep their ratio because a sub-pitch scores poorly there (same energy, double coverage). On the field-test image the best guess goes from 5.41x2.71 at 0% confidence to a square 2.712x2.712 at 20%. Unit-tested with a pair-correlated synthetic that reproduces the lock mechanism. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When analyze accepts a grid it also snaps at 1x/2x/4x of the detected pitch and publishes each factor contested-block rate: the detected pitch is the render quantum, and a multiple whose rate stays near the 1x baseline means the content also reads cleanly at that coarser artistic resolution. Field calibration drove the thresholds — a genuinely pixel-perfect image cliffs +60pp when over-coarsened (no suggestion), while the Midjourney narrowboat rises gently (30/38/43%) and gets one. suggested_coarsen picks the largest factor within 15pp of baseline and under 50% absolute; the rates are all in the JSON so humans and agents can apply their own taste between qualifying factors. Documented in README and llms-full.txt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Writes a diagnostic PNG next to the output: the source image dimmed, each block tinted red in proportion to how contested its color vote was. A wrong pitch or phase tints the entire frame; tint confined to noisy regions (stars, dither curtains, AA edges) means the grid is right and the noise is content. On the field-test telescope the overlay lights exactly the aurora, the starfield, and the truss edges while flat sky stays dark — instant visual confirmation the reconciled 2.712 pitch is correct. Documented in README and llms-full.txt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Package metadata (repository, homepage, readme, keywords, categories) and an exclude list keep the published crate slim; the desktop crate is publish = false. A publish-crate job runs cargo publish on version tags after tests pass, and skips quietly when the CARGO_REGISTRY_TOKEN secret is absent. The pixfix name is unclaimed on crates.io. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Synthetic golden tests cannot catch perceptual regressions on the inputs this tool exists for. The corpus pins known detection behavior on two genuine Midjourney renders: the dusk narrowboat must keep detecting its 2px render quantum inside its known confidence band, and the aurora telescope must keep DECLINING (its noise is real) while cross-axis reconciliation holds the best guess square at ~2.71px — the exact regression that existed before the reconciliation fix. Assertions are on detection results within tolerance, not output bytes, since parallel float reduction can differ in the last bits across thread schedules. Adds ~3.4MB of tracked images under tests/corpus/ (exempted from the global image gitignore, excluded from the published crate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The library unconditionally pulled CLI-only machinery (clap, indicatif, glob, tempfile, tracing-subscriber) and rayon, which blocked any wasm32-unknown-unknown build. Split them out: - New `cli` feature (in defaults) gates the cli, batch, and paths modules, the stdin/stdout/atomic-write halves of image_util::io, and the clap::ValueEnum derives (OutputFormat, DownscaleMode). - New `parallel` feature (in defaults) gates rayon; src/parallel.rs provides serial drop-ins for the exact adapter surface used, so call sites are identical either way. - rand loses default features: every RNG is caller-seeded ChaCha8, so os_rng/getrandom (which doesn't build on wasm32) was dead weight. - The pixfix binary now declares required-features = ["cli"]. Default build is unchanged: same deps, same rayon paths, same behavior. Verified: cargo build; cargo test --workspace --features tui (167 tests green); cargo clippy --workspace --all-targets --features tui -D warnings; cargo check -p pixfix --no-default-features (native and --target wasm32-unknown-unknown) both pass; cli-without-parallel combo also checks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New workspace member wasm/ (pixfix-wasm, cdylib) exposes one wasm-bindgen function: normalize(bytes, colors, coarsen) — decode, run the default snap pipeline (seeded, quantize optional, 2048px cap), return the PNG plus grid stats as a plain JS object. The docs page gets a "try it" section between quick start and the reference: a drop zone, colors/coarsen controls, side-by-side original/normalized panels (image-rendering: pixelated, checkerboard backing), and a one-line stats readout. The module lazy-loads on first approach to the drop zone (?autoload=1 forces eager init for smoke tests); if it can't load, the demo body hides behind a note pointing at the CLI. Everything is client-side — no image leaves the page. The built pkg (878 KB wasm + JS glue) is committed under docs/demo/pkg since Pages deploys docs/ statically; the workflow comment records the rebuild command. wasm-opt runs with post-MVP feature flags spelled out (the wasm-pack-cached binary predates bulk-memory-by-default) and -Os. Verified: wasm-pack build; Chrome headless over http logs the init success line and renders the section in-place; file:// (import blocked) degrades to the fallback note with no uncaught rejections; Node smoke test drives normalize() end to end on a corpus sprite (grid accepted at 2px, coarsen 2 doubles the pitch, corrupt input rejected cleanly). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An input GIF with more than one frame now takes an animated pipeline (src/anim.rs, core lib, wasm-clean) instead of being flattened to its first frame. Everything global is decided once and shared, because per-frame decisions would shimmer: the grid is detected on the first frame and applied to all (detection declining means no frame snaps), the background color is resolved once on the first post-AA frame with the mask rebuilt per frame, dither pairs come from the first post-snap frame, and quantization runs on ONE histogram merged across all frames with one matcher snapping every frame — so the union of output colors fits the budget and nothing flickers. Per-frame stages (AA removal, masking, snap, output sizing) mirror run_pipeline's order and mask semantics exactly; quantize grows resolve_palette/warn_collapsed_ dither_pairs so both paths share the mode logic. Frames re-encode with their original delays, looping forever. Output is always an animated GIF: derived names get .gif, --output-format gif is accepted (also for stills, new OutputFormat variant), anything else exits 2. Stdin/stdout piping works; --debug-overlay renders from the first frame. process/sheet and analyze reports gain an always-present frames field (1 for stills); analyze inspects the first frame and reports the count. Static GIFs keep the still path. Verified: unit tests (shared palette/grid, delay+pixel roundtrip, deterministic encode), CLI tests (frame count preserved, byte-identical reruns, webp rejection, frames in --json), and a PIL-generated GIF end to end — durations [80,120,80,200] survive both the file and piped paths bit-exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A visitor landing directly on #demo (or any fragment) jumps past the scroll-reveal choreography; sections must never wait invisible for an IntersectionObserver that already scrolled by. With a fragment present everything reveals instantly. Verified via DOM dump: all reveal marks applied on anchored loads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
-h now shows the 16 flags that change results (grid size, coarsen, palette, background, chroma key); detection-tuning flags move behind --help via hide_short_help. The desktop app mirrors this: phase, confidence floor, detection limits, bg thresholds, and exact output size collapse into an Advanced section with a show/hide toggle that reports how many hidden settings are tuned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018xUQfmEhKfYNEHg639XXGs
The CSP had no connect-src, so the webview blocked fetch() to the ipc: custom protocol and Tauri silently fell back to postMessage transport. On macOS that fallback delivers raw ipc::Response bytes as a JSON number array, which Blob stringifies into text instead of PNG data — every preview showed the broken-image glyph. Two-layer fix: allow connect-src ipc: http://ipc.localhost so the fast binary path works, and normalize raw invoke results (ArrayBuffer or number array) through ipcBytes() so a transport fallback can never break previews again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018xUQfmEhKfYNEHg639XXGs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.