Tweak flow: real progress bars, stale viewer/top-bar after image gen, audio tweak #31
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
cmoriarty/trog#31
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while tweaking an image in the studio files pane. Four parts: two are honest-feedback work (real progress bars), one is a bug (the tweak lands but the UI lies about it), one extends tweak to audio.
Today the tweak flow is:
TweakBarPOSTs/orch/jobs, then follows/orch/jobs/{id}/stream(SSE) and renders whatevermsgthe orchestrator narrates next to three blinking pixels (<Pulse />) —studio/src/panes/FilesPane.tsx:158-275. The narration is phase-level only (Swapping brain for image stack→Generating sprite→committed …), because that's all the backend emits (server/trog_lib/orchestrator.py:202-258).1. Real progress bar + time estimate for image generation
Replace the blinking lights during a tweak with a determinate bar and a remaining-time estimate.
Backend is the blocker, not the UI.
comfy_item.run_workflow(server/trog_lib/comfy_item.py:234-251) submits/promptand then pollsGET /history/{prompt_id}every 5s until outputs appear — it never sees step progress. ComfyUI's WebSocket (/ws?clientId=…) carriesprogressmessages (value/max= sampler steps) plusexecutingnode transitions; subscribing there is what turns this into a real percentage. Feed that intoassetq(either frequent progress events or a numeric progress column on the job) so/orch/jobs/{id}/streamcan carry it to the studio.The bar must cover the whole job, not just sampling, because the phases have very different costs:
Image seat already warm)progressvalue/maxpixel_post) + git commitFor the estimate, prefer measured history over a hardcoded guess: jobs already record wall time (the completion event ends with
(Ns)), so a rolling median per profile+kind, split by warm vs cold seat, gives a defensible ETA. Show a range or a "~" prefix rather than false precision, and let the ETA correct itself as steps land.2. Bug: the tweak commits, but the viewer and the top bar both lie
Reproduced by the user: the job reported done and said the viewer refreshed, but the displayed image never changed, and the top bar kept showing
image genwith the pulse animating long after.2a — viewer shows the old image. On
done,TweakBarcallsonDone()→setAssetV(v => v + 1)→ the<img>src becomes/orch/repo/{game}/raw/{path}?v=N(FilesPane.tsx:405-406,469-475), which should defeat any cache. Prime suspect is not caching but path mismatch: the studio sendskind: path.includes("background") ? "background" : "image"(FilesPane.tsx:230), the orchestrator maps that to spec kindsprite/background(orchestrator.py:120-123), and delivery hardcodes the destination asassets/{kind}/{name}.{ext}(comfy_item.build_artifacts:324,deliver_git:327-335). So a tweak of any image not already living atassets/sprite/<name>.png— an animated-sprite sheet, a tile, a differently-named folder, or a.jpg— is committed to a different path than the one the viewer is showing. The commit succeeds, the message is truthful, and the open file genuinely didn't change.Fix direction: the tweak should recommit the path it was invoked on (pass the source path through the job spec and have delivery honour it), and the "recommitted — viewer refreshed" note should name the path it wrote. If a tweak cannot write back to the same path, say so instead of claiming a refresh. Worth confirming the commit path in a repro before building the fix — if some other cause is at play, the same repro will show it.
2b — top bar keeps flashing
image gen. Not a display bug in the top bar:/orch/healthreportswarm_seats: manager.active(orchestrator.py:580-590) and a finished job's seat deliberately stays warm forSEAT_TTL(default 180s,orchestrator.py:54-56) waiting for follow-up work.App.tsxmaps every warm seat to an activity label and animates the pulse, so for three minutes after a job ends the bar claims generation is running. Keep-warm is correct behaviour; the readout conflating warm with working is not. Health should distinguish running jobs from idle-but-warm seats, and the top bar should only animate for actual work (a warm idle seat, if shown at all, should read as a staticimage seat warm).3. Audio tweak, with the same progress bar
TweakBaris only rendered on the image branch of the viewer (FilesPane.tsx:469-475); the audio branch is a bare<audio controls>. The backend refuses audio tweaks outright today:So this task is: check whether the audio seat can now do a source-conditioned regeneration (cover / repaint / audio-to-audio); if it can, wire the route, drop the 422, and render
TweakBarforAUDIO_EXTfiles with the same determinate progress bar and ETA. If the seat still can't, say so in this ticket and leave the audio tweak box out entirely rather than shipping a button that 422s — and keep #22 as the blocker.Note that the audio path has no step-progress source at all:
audio_item.produceis a single blocking HTTP call with a 900s timeout (server/trog_lib/audio_item.py:56-80). Audio progress therefore needs either a progress endpoint on the audio seat or a duration-model estimate — decide which before promising a percentage.4. Progress bars must finish at 100%
Design rule for every bar this ticket adds: 100% means done. A bar that reaches 100% and then sits there spinning is worse than no bar — it converts a progress indicator into a liar. Concretely:
Acceptance
Testable GPU-free:
SEAT_MODE=mock/trog test asset --mockdrives the whole path through the mock seat (server/trog_lib/mock_seat.py), which is also where a fake step-progress stream belongs so the bar can be exercised in CI.Done and deployed. Seven live tweaks against
trog-games/cycle-testdrove the verification, and most of what follows was found by those runs rather than by reading code.The stale viewer had a different cause than this ticket guessed
The ticket blamed the kind-derived destination path. That bug was real (and is fixed), but it was not what the user hit. Forgejo's raw API serves a stale blob when the ref is a branch name. Measured on the live instance, minutes after a tweak committed:
raw ?ref=mainraw ?ref=<head sha>raw(no ref)contents ?ref=mainStill stale twelve minutes later, so it is a branch-ref cache, not a propagation window.
forgejo.read_binaryalways passedref="main", so the studio's viewer, the tweak's own source fetch, and the critics were all reading yesterday's bytes. It now resolves the ref to a commit sha and reads that.What shipped
1. Progress bar with a real estimate. The orchestrator keeps a per-job estimator (
server/trog_lib/jobprogress.py), budgets seat boot / generation / delivery from the measured medians of comparable past runs (trog_asset_jobs.timings), and publishes a snapshot once a second that the SSE feed relays to the studio. The CLI spinner shows the same percentage.Worth recording that the obvious implementation does not work: sampler steps make a bad bar. The pixel graphs report per-node counters, not one sampler counting up — a single render walked 7/8 → 8/9 → 13/14 → 1/11 → 26/26. A fraction built on that either walks backwards or, clamped monotone, pins near its cap. A pass-counting rescue attempt recorded "39 passes" for that graph. Wall time against a corpus of identical runs is the honest signal here: five consecutive renders landed within 2% of each other. Steps remain in the readout as narration (
step 7/8tells you the seat is alive); the bar is time. ComfyUI's websocket is still consumed — that is where the steps come from.2. The stale viewer (item 2a), above, plus the destination-path fix: a tweak now carries
dest_path, sanitized server-side, so it writes back over the file it was invoked on instead ofassets/<kind>/<name>.<ext>. The studio re-reads the committed bytes and only claims "viewer refreshed" when they actually changed — otherwise it says the commit landed elsewhere, or that the bytes are identical.3. The top bar that kept flashing (item 2b) had two causes. Keep-warm was reported as work, so
/orch/healthnow separatesworking_seatsfromwarm_seatsand the readout showsidle · image gen warmunanimated. The other cause was worse: a restart orphaned in-flight jobs, leaving the rowrunningforever with its SSE feed never terminating. Job 133 was sitting in exactly that state, orphaned by a redeploy mid-tweak — very likely what was actually seen. Boot now fails those rows, and job 133 duly reportedFAILED: orchestrator restarted while this job was runningon the first deploy of the fix.4. Bars end at 100%. The fraction is linear to 95% of a phase's measured budget and asymptotic past it; an overrun stretches the estimate rather than pinning the bar; and the studio holds below 100% until the new bytes are in hand.
Audio tweak: still not possible, and now says so. Re-checked the seat —
/music,/sfxand/vocalgenerate from text alone; there is no cover, repaint, or audio-to-audio route to condition on a source, andaudio_item.produceis one blocking call with no progress feed. Rather than ship a button that 422s, the audio viewer states the reason and points at #22. The orchestrator's 422 was updated to say the same thing precisely.Live proof (final run, orchestrator image digest confirmed)
Monotone throughout, no pin, 100% only at completion; predicted 188s at second six against an actual 192s. Afterwards: note
✓ 'checker' recommitted — viewer refreshed(the re-read confirmed changed bytes), viewer showing the new image, top baridle · image gen warmwith the pulse animation off.147 unit tests pass, including regression tests for each live-caught failure. Commits:
6cacef5→55fab9e.One thing this did not fix:
/orch/repo/{slug}/rawreads two Forgejo endpoints per request now (resolve sha, then fetch). If that shows up in the files pane's latency, caching the head sha per repo for a second or two is the obvious next step.Two more UI fixes on this ticket, both from the same root observation: asset generation stops the brain to take its GPUs, and two places treated that designed behaviour as breakage.
5. The feed queues instead of erroring while the brain is away
Before: the model picker was removed outright (taking the explanation with it), the input still accepted sends, and the send reached a stopped brain —
API ConnectionError: execution failed, with the typed line lost.Now, while the brain is down:
you · queued, and the brain-bar note counts what is waiting.Nothing is queued server-side; this is the studio holding text it could not deliver. A reload loses the queue, which is why the lines stay visible in the feed rather than disappearing into a background buffer.
6. The system light no longer counts the brain
Green now means aegra and the orchestrator. Gating on the brain painted the light grey for every image job — the system working exactly as designed, reported as the system being down. The brain still rides in the tooltip:
trog up · aegra · orchestrator · brain swapped out for the seats.Verified against a real swap
Ran image tweaks on
cycle-testand watched the studio through the whole cycle rather than simulating it:brain swapped out for the seats; notice shown; pickerdisabled=trueholdingqwen3.6-27b; button readsqueue; placeholderbrain's away — your line queues until it's back…you · queued, input cleared, no.feed-error— the exact case that used to throw ConnectionError[gemma-4-31b, qwen3-coder-next, qwen3.6-27b, qwen3.6-35b-a3b]tsc --noEmitclean, 147 tests pass, deployed and confirmed on the live bundle (index-y01O9E_a.js). Commitbda3000.Follow-up on the queueing work: after a tweak finished, the brain stayed away for two to three minutes with nothing said about it, the model picker couldn't be touched, and a queued line sat there until the brain "randomly" started swapping.
Nothing random. Keep-warm holds the seat for
ORCH_SEAT_TTL(180s) after a job so a follow-up asset job skips the boot, and the brain only comes back when the worker's idle sweep releases the last seat. The swap at the end was the queued line going out and loading its model. Correct behaviour — and completely invisible, which is the actual defect: the studio said "waits until it's back" and left it at that for three minutes.Shipped
/orch/healthnow reportsseats(per-seatworking+idle_s),seat_ttl, andbrain_back_in.POST /orch/seats/releasehands the GPUs back immediately, with a bring it back now button in that notice. It refuses (409) while a generation is actually running — that is the one case where the seats aren't the caller's to take.Verified on the live stack
Ran a tweak, then watched the window that used to be silent:
In the studio at that moment: notice reading "...another 178s..." with the button,
brain-noteshowingaway · back in ~178s, picker enabled atgemma-4-31b. Changed it toqwen3.6-27bwhile the brain was away — the change stuck. Pressed bring it back now: seats emptied, the brain was serving models again ~40 seconds later instead of the remaining ~three minutes, the notice cleared, and the send button returned. The system light stayed green throughout.147 tests pass (new one covers
seat_state's idle reporting). Commit1a910f6, deployed and confirmed on both new images.Worth knowing: releasing the seat costs the next asset job its warm start (~20s of boot). The button is there for "I want the brain now", not as a default.
Closing — all four items shipped and verified on the live stack, plus two follow-ups reported during testing.
What was asked, and where it landed
runningforever with their SSE feed never terminating. Health separatesworking_seatsfromwarm_seats; boot fails orphans.Plus, from testing this in the studio:
POST /orch/seats/release).What the live runs taught that reading the code did not
raw ?ref=mainis stale,raw ?ref=<sha>is not, and it does not self-correct — still stale twelve minutes after the commit. Every asset read now resolves to a sha first.Not done, deliberately
/music,/sfxand/vocalgenerate from text alone, and generation is one blocking call with no progress feed. The studio says so where the box would be rather than shipping a button that 422s. Stays blocked on #22./orch/repo/{slug}/rawnow costs two Forgejo calls (resolve sha, then fetch). If the files pane feels slower, a short-lived head-sha cache per repo is the fix.State
147 unit tests pass, with regression coverage for each live-caught failure; ruff and
tsc --noEmitclean; studio smoke suite green against the deployed :2027. Eleven commits,c05f1a1→8da8c49, all deployed and confirmed running by image digest. Queue is healthy: 37 done, 2 failed (the orphan sweep's own catch), nothing stuck.