Address a further round of Codex review findings on the image PR

Backend:
- validate_load_request rejects a non-.gguf single-file name before the GPU
  handoff, so a family-looking repo paired with README.md no longer evicts the
  chat model and only fails in the background load.
- detect_family scopes the edit/kontext/inpaint keyword check to the model id
  or filename basename, not arbitrary parent directories, so a valid
  text-to-image file under a folder named edit is no longer rejected.
- the images gallery listing skips records that fail schema validation, so one
  corrupt or hand-dropped PNG can no longer 500 the whole endpoint.
- _terminate reaps the killed sd-cli child so cancellation and timeout paths do
  not leak zombie process-table entries.
- the images load route gates the chat-eviction handoff on the resolved device
  being non-CPU, so a CPU-only diffusers fallback no longer evicts a resident
  chat model for a load that cannot use the GPU.

Frontend:
- treat Images as a chat-like full-height route (no outer padding or scroll) so
  its picker is not pushed down and the gallery is not clipped.
- allow /images under the chat-only guard so the native CPU/MPS image path is
  reachable on the no-GPU hosts it was built for.
- roll the optimistic quant label back when a same-repo swap fails after the
  load started, so the selector never advertises a quant that is not loaded.
This commit is contained in:
Daniel Han 2026-07-02 05:41:23 +00:00
commit 18f9510d11
8 changed files with 136 additions and 22 deletions

View file

@ -135,6 +135,26 @@ def test_runtime_env_handles_missing_lib_path():
assert env[var] == "/opt/sdcpp/bin"
def test_terminate_reaps_killed_child():
# Cancellation/timeout paths call _terminate then immediately raise, so it must
# reap the killed child itself -- otherwise a burst of image cancellations leaves
# zombies until a later Popen cleanup. After _terminate the returncode is set
# (the child has been waited on), so nothing lingers.
import subprocess
proc = subprocess.Popen(
[sys.executable, "-c", "import time; time.sleep(30)"],
start_new_session = (os.name == "posix"),
)
try:
eng._terminate(proc)
assert proc.returncode is not None
finally:
if proc.poll() is None:
proc.kill()
proc.wait()
# ── generate (fake subprocess) ──────────────────────────────────────────────