diff --git a/CHANGELOG.md b/CHANGELOG.md index 85d7ef8..a00ddca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi ## [Unreleased] ### Added +- **Live Image Result Preservation.** Durable image artifact persistence now leaves the active in-memory pair + available to the current browser poll while storing only bounded download metadata for restart-safe history. - **Resource Diagnostics.** Performance now exposes read-only server RSS, configured/temp disk usage, persisted-job count, DOM/audio counts, and browser heap when available; host Docker metrics remain explicitly outside the panel's scope. - **Throttled Desktop Smoke Coverage.** Chromium CDP now exercises app navigation at 4× CPU throttling and diff --git a/VERSION b/VERSION index 7a429d6..8407e26 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.24.6 +1.24.7 diff --git a/routes/conversation.py b/routes/conversation.py index ba379be..c2fe712 100644 --- a/routes/conversation.py +++ b/routes/conversation.py @@ -205,7 +205,9 @@ def _persist_consistent_image_artifacts(job_id: str, result: dict) -> dict: try: payload = base64.b64decode(encoded, validate=True) artifacts.append({"kind": key, **background_job_store_artifact(job_id, filename, payload, media_type)}) - persisted.pop(key, None) + # Keep the data URI in the short-lived in-memory job so the active + # browser can finish its current poll. background_job_update() + # applies its own field-level sanitizer before SQLite storage. except Exception as exc: persisted.pop(key, None) persisted.setdefault("artifact_error", "One or more generated images could not be persisted") diff --git a/tests/test_conversation.py b/tests/test_conversation.py index 29402b9..98977b3 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -4,6 +4,7 @@ from __future__ import annotations import os import json import tempfile +import shutil import unittest os.environ.setdefault("CONFIG_DIR", tempfile.mkdtemp(prefix="ttvc-conversation-test-config-")) @@ -23,10 +24,23 @@ from routes.conversation import ( match_characters_voices, _request_timeout_seconds, _response_error_text, + _persist_consistent_image_artifacts, ) class AttributionParserTests(unittest.TestCase): + def test_consistent_image_persistence_keeps_live_payload_and_adds_artifact_refs(self): + from core.database import _BACKGROUND_ARTIFACT_DIR + job_id = "live-image-artifact-test" + data_uri = "data:image/png;base64," + "AA==" + try: + result = _persist_consistent_image_artifacts(job_id, {"profile": data_uri, "reference": data_uri}) + self.assertEqual(result["profile"], data_uri) + self.assertEqual(result["reference"], data_uri) + self.assertEqual([item["kind"] for item in result["artifacts"]], ["profile", "reference"]) + finally: + shutil.rmtree(_BACKGROUND_ARTIFACT_DIR / job_id, ignore_errors=True) + def test_active_character_form_overlays_visual_identity_without_changing_record(self): sheet = { "physical": "base form",