- wrap writePixels in state.storage.transactionSync so a partial multi-chunk
failure doesn't leave the canvas half-written or out of sync with the WS
broadcast
- size new chunk buffer against chunkSize(chunkId) instead of the persisted
blob's length so writes after a canvas-grow no longer silently drop OOB
bytes in the formerly-last short chunk
- refund the cooldown row when writePixels throws so transient storage
errors stop soft-DOSing the user (and halving image-uploader throughput)
- bound readAllChunks by chunk_id < CHUNK_COUNT and trim oversized blobs so
orphan rows from a future shrink no longer crash GET /api/canvas
- require a positive Content-Length on /api/place (411) and reject above the
pre-parse cap (413); previously a missing or zero header bypassed the cap
- drop String(err) from the 500 response body
- drain the INSERT cursor symmetrically with the UPDATE branch in tryAcquire
- assert CHUNK_BYTES <= 2 MB at module load (DO SQLite per-cell BLOB cap)
- correct the inverted webSocketClose comment and guard the re-close call
- add tests for missing / zero / oversized Content-Length
Plan: plans/260510-0232-fix-do-migration-followups/phase-01-do-storage-atomicity.md
Earlier docs assumed a 1 GB per-DO storage cap. Actual May 2026 free-tier
limits per the CF DO docs:
- 10 GB per Durable Object
- 5 GB per account (the real Free-tier ceiling)
- 2 MB per BLOB row
- 1,000 req/sec soft cap per DO
Practical effect: canvas can grow to ~70,000 × 70,000 on Free (was previously
documented as ~32K × 32K). Existing 64 KB CHUNK_BYTES is well-suited:
clustered writes (single pixels + image patches) minimize read-modify-write
amplification; bigger chunks would only help uniform-random writes which
isn't rplace's workload.
No code change. Just doc accuracy.
Replaces the synchronous reactive pipeline with a debounced worker RPC.
Slider drags and file loads no longer block the main thread.
- image-pipeline.js: pure staged pipeline (transform → resize →
color-correction → quantize). Each upstream stage keeps one cached
slot keyed on the inputs it depends on, so a single-slider change
reruns only the downstream stages.
- image-pipeline-worker.js: hosts the pipeline inside a module Worker.
Accepts set-source (transferable) and run(params). Returns indices
+ preview RGBA as transferable ArrayBuffers.
- image-pipeline-client.js: main-thread RPC with request-id tracking
and stale-response dropping.
- ImageImporter.svelte: two-tier scheduler — throttled quick request
(<=384px) for the preview panel while inputs churn, debounced full
request (180ms) for overlay/buildPixels/opaque-count on settle.
Source buffer cloned and transferred to the worker on load/resume.
Main bundle shrinks ~4KB (pipeline code now in its own worker chunk).
* feat(ui): pick-position, cooldown badge, goto, shortcuts, progressive skip, resume
- Pick-position mode: "Pick on canvas" replaces the broken "Use cursor"
button. Next left-click (no drag) sets origin; Esc or second press cancels.
- Visible cooldown on Submit: disables button + shows "Wait Xs" countdown
after a successful place or 429 response.
- Goto-coordinates: "x,y" input in CanvasControls; Enter centers the
viewport on that canvas point.
- Keyboard shortcuts: Q/E zoom, WASD pan (40px), Esc cancels pick or stroke.
Guarded against firing in focused inputs.
- Progressive skip-already-matching: uploader re-filters remaining queue
every 8 batches via an optional shouldSkip predicate.
- Auto-save + resume: new image-job-storage module (localStorage + PNG
dataURL); uploader emits onCheckpoint after each batch; importer shows
a Resume/Discard banner when an unfinished job is found on mount.
* docs(reports): wplace-autobot logic + wplace.live design research
* refactor(rate-limit): switch to 1 req/sec cooldown, batch size up to 2048
Replace per-pixel credit/token-bucket model with a simple per-user cooldown
(SET NX EX 1). Batch size is now independent of the rate limit and capped
at MAX_BATCH_SIZE = 2048.
- rate-limiter: SET NX EX replaces Lua credit script
- worker: response shape { ok: true } (no credits field)
- client: drop credit state/timer/UserInfo; uploader paces by cooldown
- tests: mock checkRateLimit; integration test exercises SET NX EX
- docs: README, system-architecture, code-standards, deployment-guide
* chore(plans): remove implemented plan directories
rplace-implementation (base build), review-fixes, and
image-importer-enhancements are all shipped. Keep plans/reports/ as
historical code-review and research references.
Two quick quality-of-life options for logo and background-heavy imports:
- Skip-white: near-white pixels (r,g,b >= threshold, default 230) are
marked transparent in the output so they don't consume credits. Useful
when the source has a white background you don't want to paint.
- Paint-transparent: fully-transparent source pixels are treated as opaque
white before quantization. Useful if the canvas background is white and
you want empty regions of a logo to blend in.
Both toggles compose with every existing dither method; paint-transparent
runs first, so combining the two leaves originally-transparent pixels as
skip (since they're synthesized white, then the skip-white pass drops
them).
UI adds the two checkboxes and a threshold slider shown only when
skip-white is on. CLI gains --skip-white, --white-threshold,
--paint-transparent. 4 new unit tests pin the behavior including the
paint+skip interaction.
Brightness, contrast, saturation, and gamma adjustments before palette
quantization. Pure-function applyColorCorrection in src/lib keeps the CLI
and browser importer in sync. Inserted post-resize so slider moves stay
responsive on large sources; precision loss is negligible through a
32-color palette.
UI is a collapsible "Color correction" section with four sliders, live
values, a "Reset" button, and an "on" badge when any value differs from
default. CLI gains --brightness, --contrast, --saturation, --gamma
(all -100..+100 except gamma which is 0.1..3.0).
9 unit tests pin down identity, saturate-to-0-or-255 clamping, greyscale
at saturation=-100, gamma midtone direction, and alpha preservation.
Replace the single Floyd-Steinberg toggle with a method dropdown offering
none, floyd, atkinson, jarvis, burkes, sierra, sierra-lite (error diffusion)
and bayer-2 / bayer-4 / bayer-8 (ordered). Kernels live in
src/lib/dither-kernels.js as plain data; rgbaToPalette dispatches via a
shared runErrorDiffusion runner and a runOrderedDither for Bayer.
The legacy `dither: true` option keeps working (maps to `method: 'floyd'`),
so existing CLI invocations and tests stay green. CLI gains
`--dither-method <name>` which takes precedence over `--dither`.
13 unit tests cover exact-palette hits, transparent pixels, all-method
smoke, kernel weight sums (Atkinson=0.75, others=1.0), and verify Bayer
actually scatters on mid-grey where plain nearest would produce one color.
Pure-function transformRgba in src/lib/image-transform.js supports flip H,
flip V, and rotation in 90° steps (fixed application order: rotate → flipH
→ flipV). Wired into the importer pipeline before resize with buttons for
each op; ±90° rotations auto-swap the resize dims so output aspect tracks
the rotation. CLI gains --flip-h, --flip-v, --rotate flags using the same
shared module.
8 new unit tests cover identity, each flip, 90/180/270 rotation, the
flipH+flipV ≡ 180° identity, and invalid rotation rejection.
CanvasRenderer.setOverlay draws the palette-converted import preview on top
of committed + pending layers at (x, y) with a configurable alpha. Image
importer gains an "Overlay" toggle and opacity slider; the overlay
reactively follows resize/dither/origin changes and clears on panel close,
toggle-off, or image swap. Lets the user align the upload before spending
credits.
Six phases spanning resize, on-canvas overlay preview, transforms, more
dithering algorithms, color correction, and skip-white/paint-transparent
toggles. Based on WPlace-AutoBOT's image-processor.js feature set, scoped
to what fits our 32-color canvas.
Three review reports (backend, frontend, security/scale) and a
follow-up re-review after pull. Plan documents which findings were
addressed in this sweep and which were intentionally deferred.