diff --git a/CHANGELOG.md b/CHANGELOG.md
index b636656..6802704 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]
### Changed
+- **Visual identity regression coverage.** Portrait and concept-sheet prompt tests now verify shared identity anchors;
+ completed pairs centralize prompt-version, identity-fingerprint, and asset-digest metadata.
- **Frontend namespace migration completed.** The remaining utility, library, cast-compatibility, i18n, and settings/log
surfaces now keep implementation state private while preserving explicit legacy compatibility exports.
- **Conversation/log namespace migration.** Settings log state and About rendering helpers now use a private scope with
diff --git a/TODO.md b/TODO.md
index 549f9e2..f878266 100644
--- a/TODO.md
+++ b/TODO.md
@@ -261,6 +261,26 @@ architecture change: the larger Audiobook/Rehearser/style split.
## P1 — Speaker attribution: measured state and next steps (paused 2026-09-02)
+### Ordered follow-up queue
+
+These follow-ups are ordered by dependency and expected benefit. Estimates assume one developer familiar with this
+codebase; benefit is relative user/reliability impact.
+
+- [x] **P1 / M — High benefit — Identity-anchor regression tests.** Add automated coverage proving avatar and concept-sheet
+ requests share canonical identity anchors and persist the prompt version used for each generated asset.
+- [ ] **P1 / S–M — Medium benefit — Preserve dialogue-only merging.** Keep the dialogue-only merge for recast/verify
+ outputs where adjacent pairs still occur, with regression coverage for the non-dialogue boundaries.
+- [ ] **P1 / L–XL — Very high benefit — Rework segmentation from Run 3.** Inspect the earlier dialogue split/fusion
+ stages and correct the dominant unmatched-segment class using the measured Run 3 artifacts.
+- [ ] **P1 / M — High benefit — Re-run Book 1.** Re-measure the hand-corrected Book 1 reference after segmentation
+ changes; do not treat a short or incomplete run as a valid result.
+- [ ] **P1 / L–XL — Very high benefit — Run Book 2 generalization.** Execute the backed-up Book 2 reference regression
+ to measure whether segmentation and attribution rules generalize beyond Book 1.
+- [ ] **P1 / L — Very high benefit — Unify visual identity.** Ensure portrait and concept-sheet generation use one
+ canonical identity description, stored prompt version, and stale-asset review path.
+- [ ] **P2 / M–L — Medium–high benefit — Enrich character-form UX.** Add richer inline form editing and per-form
+ bulk-generation controls while preserving one character ID and one voice.
+
Three full regression runs of *Der Sturm* against the hand-corrected reference.
Everything below is measured, not estimated. Artefacts live in
`config/regression_snapshots/der_sturm/` (`*_manual.json` are the human
diff --git a/VERSION b/VERSION
index 3da8cae..9e35561 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.24.98
+1.24.99
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 8e0bee1..6c711e7 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -87,6 +87,7 @@ Done incrementally as global-scope ordered files (no ES-module change needed)
- ✅ Conversation settings/log state and About helpers use a private scope with compatibility exports and a namespace API.
- ✅ All remaining application frontend modules are either privately scoped or intentionally stateless bootstrap/cache IIFEs;
legacy globals are explicit compatibility exports and new integrations use `window.TTVC` namespaces.
+- ✅ Visual pair generation centralizes prompt-version, identity-fingerprint, and asset-digest metadata with regression coverage for shared anchors.
- ✅ `static/css/resource-diagnostics.css` — isolated Performance/resource diagnostic panel styles.
- ✅ `static/css/background-jobs.css` — isolated background-job history and retry-panel styles.
- ✅ `static/css/voice-design.css` — isolated Voice Design form, samples, and preview-match styles.
diff --git a/routes/conversation.py b/routes/conversation.py
index 903d495..bcf9a4e 100644
--- a/routes/conversation.py
+++ b/routes/conversation.py
@@ -159,6 +159,17 @@ def _character_identity_fingerprint(sheet: dict) -> str:
return format(value, "x")
+def _consistent_image_result_metadata(sheet: dict, result: dict | None) -> dict:
+ """Attach the identity contract to every completed visual pair."""
+ payload = dict(result or {})
+ payload["prompt_version"] = _CHARACTER_VISUAL_PROMPT_VERSION
+ payload["identity_consistent"] = True
+ payload["identity_fingerprint"] = _character_identity_fingerprint(sheet)
+ payload["profile_asset_digest"] = _data_image_digest(payload.get("profile"))
+ payload["reference_asset_digest"] = _data_image_digest(payload.get("reference"))
+ return payload
+
+
def _character_form_sheet(sheet: dict) -> dict:
"""Overlay the selected appearance form without changing the character ID or voice."""
if not isinstance(sheet, dict):
@@ -3478,12 +3489,7 @@ async def character_generate_consistent_images(request: Request):
generation_key,
progress=lambda elapsed: (_cij_check_cancelled(job_id), _cij_set(job_id)),
)
- result = dict(result or {})
- result["prompt_version"] = _CHARACTER_VISUAL_PROMPT_VERSION
- result["identity_consistent"] = True
- result["identity_fingerprint"] = _character_identity_fingerprint(sheet)
- result["profile_asset_digest"] = _data_image_digest(result.get("profile"))
- result["reference_asset_digest"] = _data_image_digest(result.get("reference"))
+ result = _consistent_image_result_metadata(sheet, result)
result = _persist_consistent_image_artifacts(job_id, result)
_cij_set(job_id, stage="done", detail="Done", done=True, result=result)
except HTTPException as exc:
diff --git a/static/index.html b/static/index.html
index 5b1017a..372f26b 100644
--- a/static/index.html
+++ b/static/index.html
@@ -10,7 +10,7 @@
-
+
@@ -27,12 +27,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -397,7 +397,7 @@ window.toggleNavTree = function(treeId, chevronId) {
-
+