@rslsp1/fa-app-tools 7.2.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -132,7 +132,7 @@ __export(index_exports, {
132
132
  module.exports = __toCommonJS(index_exports);
133
133
 
134
134
  // package.json
135
- var version = "7.2.0";
135
+ var version = "8.0.0";
136
136
 
137
137
  // src/hooks/useOnClickOutside.ts
138
138
  var import_react = require("react");
@@ -171,7 +171,7 @@ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
171
171
  }
172
172
 
173
173
  // src/features/szenen/szenenViewStyles.generated.ts
174
- var SZENEN_VIEW_STYLES = '/* ==========================================================================\n SZENEN VIEW\n ========================================================================== */\n\n.szenen-view {\n display: grid;\n gap: var(--space-md);\n width: 100%;\n min-width: 0;\n font-family: var(--font-sans);\n padding: var(--space-md);\n}\n\n@media (max-width: 760px) {\n .szenen-view {\n padding: var(--space-sm);\n gap: var(--space-sm);\n }\n}\n\n.szenen-view__intro {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n padding: clamp(0.6rem, 1.2vw, 0.9rem) clamp(0.75rem, 1.6vw, 1.1rem);\n color: var(--color-text-primary);\n background:\n radial-gradient(circle at 85% 15%, rgba(45, 212, 191, 0.16), transparent 34%),\n linear-gradient(135deg, var(--color-bg-surface), var(--color-bg-elevated));\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-view__intro h2,\n.szenen-view__intro p {\n margin: 0;\n}\n\n.szenen-view__intro h2 {\n margin-bottom: 0.1rem;\n font-size: clamp(1rem, 1.9vw, 1.3rem);\n line-height: 1.2;\n}\n\n.szenen-view__intro > div > p:last-child {\n max-width: 44rem;\n color: var(--color-text-secondary);\n font-size: 0.82rem;\n line-height: 1.35;\n}\n\n.szenen-view__eyebrow {\n margin-bottom: 0.1rem !important;\n color: var(--color-accent-teal, #2dd4bf);\n font-size: 0.62rem;\n font-weight: 800;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\n.szenen-view__step {\n flex: none;\n padding: 0.25rem 0.6rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n.szenen-view__nav {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-view__nav button,\n.szenen-view button {\n min-height: 2.25rem;\n padding: 0.45rem 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-view button:hover:not(:disabled) {\n color: var(--color-text-primary);\n background: var(--color-bg-surface-hover);\n border-color: var(--color-border-hover);\n}\n\n.szenen-view button:focus-visible,\n.szenen-view input:focus-visible,\n.szenen-view select:focus-visible,\n.szenen-view textarea:focus-visible {\n outline: 2px solid var(--color-accent-blue);\n outline-offset: 2px;\n}\n\n.szenen-view button:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.szenen-view__nav button.is-active,\n.szenen-view__nav button[aria-selected="true"] {\n color: var(--color-text-primary);\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-view__area {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-view__capability-note,\n.szenen-area__lead {\n margin: 0;\n padding: var(--space-sm) var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-accent-purple);\n border-radius: var(--radius-sm);\n}\n\n.szenen-view__host-notes {\n color: var(--color-text-secondary);\n}\n\n.szenen-view__host-notes > summary {\n cursor: pointer;\n font-size: 0.8rem;\n}\n\n/* `box-sizing` steht hier und nicht beim Host: Die React-App bringt in\n * `master.css` einen globalen `* { box-sizing: border-box }` mit, Flow **nicht**\n * \u2014 dort ragte `width: 100%` plus Innenabstand um die Randbreite aus dem\n * Elternelement heraus und erzeugte auf ~390 px einen horizontalen Scroll. Die\n * gemeinsame Ansicht darf sich auf keinen Host-Reset verlassen. */\n.szenen-view input:not([type="checkbox"]),\n.szenen-view select,\n.szenen-view textarea {\n box-sizing: border-box;\n width: 100%;\n min-height: 2.25rem;\n padding: 0.5rem 0.65rem;\n color: var(--color-text-primary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font: inherit;\n}\n\n.szenen-view textarea {\n min-height: 7rem;\n resize: vertical;\n font-family: var(--font-mono);\n font-size: 0.8rem;\n}\n\n.szenen-view label {\n display: grid;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-view code,\n.szenen-view pre {\n font-family: var(--font-mono);\n overflow-wrap: anywhere;\n}\n\n.szenen-view h3,\n.szenen-view h4,\n.szenen-view h5 {\n margin-top: 0;\n}\n\n.szenen-view ul,\n.szenen-view ol {\n padding-left: 1.25rem;\n}\n\n.szenen-elements,\n.szenen-detail,\n.szenen-scene,\n.szenen-setup,\n.szenen-shot,\n.szenen-media,\n.szenen-runs {\n display: grid;\n gap: var(--space-md);\n min-width: 0;\n}\n\n/* Arbeitsoberflaeche: Liste links, Bearbeitung rechts.\n *\n * Breit stehen beide Spalten nebeneinander \u2014 die Auswahl bleibt sichtbar,\n * waehrend bearbeitet wird. Schmal rendert die Komponente nur eine der beiden;\n * hier steht deshalb keine Regel, die die andere versteckt. */\n@media (min-width: 761px) {\n .szenen-elements {\n grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);\n align-items: start;\n }\n\n .szenen-elements__master,\n .szenen-elements__detail {\n min-width: 0;\n }\n}\n\n.szenen-elements__master,\n.szenen-elements__detail {\n display: grid;\n gap: var(--space-sm);\n align-content: start;\n min-width: 0;\n}\n\n.szenen-elements__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-elements__heading {\n margin: 0;\n font-size: 1.05rem;\n}\n\n.szenen-elements__scope {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n min-width: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-elements__scope > select {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.szenen-elements__new {\n flex: none;\n font-weight: 700;\n}\n\n.szenen-elements__create {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-accent-blue);\n border-radius: var(--radius-sm);\n}\n\n.szenen-elements__create label {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-elements__create-actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n}\n\n.szenen-elements__options > summary {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n cursor: pointer;\n}\n\n.szenen-elements__back {\n justify-self: start;\n font-weight: 700;\n}\n\n.szenen-elements__empty-detail {\n margin: 0;\n color: var(--color-text-tertiary);\n}\n\n.szenen-elements__filter,\n.szenen-detail__actions,\n.szenen-runs__start,\n.szenen-scene-editor__add,\n.szenen-scene-editor__add-relation {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-elements__filter > label,\n.szenen-runs__start > label,\n.szenen-scene-editor__add > input {\n flex: 1 1 16rem;\n}\n\n.szenen-elements__filter > label:has(input[type="checkbox"]) {\n display: flex;\n flex: 1 1 16rem;\n flex-direction: row;\n align-items: center;\n}\n\n.szenen-elements__filter input[type="checkbox"] {\n width: 1rem;\n height: 1rem;\n accent-color: var(--color-accent-blue);\n}\n\n.szenen-elements__list {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-elements__list > li > button {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs) var(--space-sm);\n width: 100%;\n height: 100%;\n text-align: left;\n}\n\n.szenen-elements__list > li > button > .szenen-elements__title {\n flex: 1 1 100%;\n}\n\n.szenen-elements__list > li > button.is-selected,\n.szenen-elements__list > li > button[aria-current="true"] {\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-elements__type {\n color: var(--color-accent-blue);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-elements__title {\n color: var(--color-text-primary);\n font-size: 0.95rem;\n}\n\n.szenen-elements__subtype,\n.szenen-elements__retired {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n}\n\n.szenen-elements__retired {\n color: var(--color-warning);\n}\n\n.szenen-detail {\n margin-top: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n@media (min-width: 761px) {\n .szenen-detail {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n }\n}\n\n.szenen-detail > header,\n.szenen-detail__heads,\n.szenen-detail__draft,\n.szenen-capability {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* .szenen-runs ist ein eigenst\xE4ndiger sichtbarer Bereich, beh\xE4lt Styling auf Desktop */\n@media (min-width: 761px) {\n .szenen-runs {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n }\n}\n\n@media (max-width: 760px) {\n .szenen-runs {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n }\n}\n\n.szenen-detail__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin-bottom: 0;\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__heads ul,\n.szenen-scene-editor ul,\n.szenen-runs > ol,\n.szenen-capability > ul {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-detail__heads li,\n.szenen-scene-editor li,\n.szenen-runs > ol > li,\n.szenen-capability > ul > li {\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n\n/* Stufe 6.7.2 (D-126) \u2014 Advanced-/Debug-Fl\xE4che f\xFCr die rohe, schema-getriebene\n * Feldliste einer Szene. Gestrichelt wie die anderen \u201EAngebot statt\n * Auskunft"-Bereiche, aber ohne Akzentfarbe: sie ist kein Vorschlag, sondern\n * ein Eskalationsweg. */\n.szenen-detail__technical {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__technical > summary {\n margin-bottom: var(--space-sm);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__technical[open] > summary {\n margin-bottom: var(--space-md);\n}\n\n.szenen-detail__field small,\n.szenen-capability small {\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__preview-errors,\n.szenen-detail__notice--error,\n.szenen-view [role="alert"] {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-detail__notice {\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__notice--success,\n.szenen-view [data-tone="success"] {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n border-color: var(--color-accent-green);\n}\n\n.szenen-scene-editor {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-md);\n margin-bottom: 0;\n}\n\n.szenen-scene-editor > section {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* \u2500\u2500 Stufe 6.7.2 (D-126): Node-/Relationskarten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Nodes und Beziehungen sind `<details>` \u2014 dieselbe Interaktionssprache wie\n * \u201ERohantwort (Diagnose)" und \u201EBilder zu diesem Lauf" in RunsPanel.tsx. Kopf\n * (immer sichtbar) tr\xE4gt Inhalt zuerst, Technik zuletzt; Body (nur bei\n * ge\xF6ffneter Karte) tr\xE4gt die Bearbeitungscontrols. Padding wandert von der\n * `<li>` in Kopf/Body, damit die Karte keinen doppelten Rand bekommt. */\n.szenen-scene-editor li {\n padding: 0;\n overflow: hidden;\n}\n\n.szenen-node-karte__kopf,\n.szenen-relation-karte__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs) var(--space-sm);\n padding: var(--space-sm);\n cursor: pointer;\n list-style: none;\n}\n\n.szenen-node-karte__kopf::-webkit-details-marker,\n.szenen-relation-karte__kopf::-webkit-details-marker {\n display: none;\n}\n\n.szenen-node-karte__titel {\n flex: 1 1 12rem;\n min-width: 0;\n color: var(--color-text-primary);\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n.szenen-node-karte__typ {\n flex: none;\n padding: 0.15rem 0.55rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n white-space: nowrap;\n}\n\n/* node_id klein, monospace, sekund\xE4r \u2014 sichtbar auch bei zugeklappter Karte,\n * aber optisch klar hinter Beschreibung und Typ. */\n.szenen-node-karte__meta {\n flex: 1 1 100%;\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n.szenen-node-karte__meta code {\n font-size: inherit;\n}\n\n.szenen-node-karte__body,\n.szenen-relation-karte__body {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-sm) var(--space-sm);\n border-top: 1px solid var(--color-border);\n}\n\n/* \xA74 \u2014 Prim\xE4raktion und destruktive Aktion optisch getrennt: \u201ENode/Beziehung\n * entfernen" bekommt einen eigenen, warnenden Ton statt gleichgewichtig neben\n * der normalen Bearbeitung zu stehen. */\n.szenen-node-karte__entfernen,\n.szenen-relation-karte__entfernen {\n justify-self: start;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-relation-karte__kopf {\n font-size: 0.9rem;\n}\n\n.szenen-relation-karte__kopf code {\n overflow-wrap: anywhere;\n}\n\n.szenen-relation-karte__pfeil {\n flex: none;\n color: var(--color-text-secondary);\n}\n\n/* Die Composer-\xD6ffner (\u201E+ Node hinzuf\xFCgen", \u201E+ Beziehung hinzuf\xFCgen") sind\n * Angebote, keine Ausk\xFCnfte \u2014 dasselbe gestrichelte Muster wie beim Folgelauf\n * und der Fehlerrettung (\xA73.3/\xA73.5). */\n.szenen-scene-editor__oeffnen {\n width: 100%;\n border-style: dashed;\n}\n\n.szenen-refpicker {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n margin: var(--space-sm) 0;\n padding: var(--space-sm) 0;\n border: none;\n border-radius: 0;\n}\n\n.szenen-refpicker legend {\n padding: 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n}\n\n.szenen-capability > ul > li {\n display: grid;\n grid-template-columns: minmax(10rem, 1fr) auto minmax(14rem, 2fr);\n align-items: start;\n gap: var(--space-sm);\n}\n\n.szenen-capability__state {\n padding: 0.1rem 0.5rem;\n border-radius: var(--radius-full);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-capability__state--yes {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n}\n\n.szenen-capability__state--no {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n}\n\n.szenen-capability__state--unknown {\n color: #fef08a;\n background: rgba(234, 179, 8, 0.2);\n}\n\n.szenen-runs > ol > li {\n display: grid;\n gap: var(--space-sm);\n}\n\n/* B1 \u2014 die geschlossene Runde. Vorher stand die `run_id` als erstes und war\n die einzige Information, die niemand liest; jetzt beantwortet die Kopfzeile\n welche Runde, ob sie durchging, mit welchem Modell und was ungef\xE4hr\n herauskam. */\n.szenen-runs__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n width: 100%;\n padding: var(--space-xs) 0;\n text-align: left;\n color: var(--color-text-secondary);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n\n.szenen-runs__marke {\n font-weight: 700;\n color: var(--color-text-primary);\n}\n\n.szenen-runs__status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n}\n\n.szenen-runs__status[data-status="succeeded"] {\n border-color: var(--color-success);\n}\n\n.szenen-runs__status[data-status="failed"] {\n border-color: var(--color-danger);\n}\n\n.szenen-runs__modell {\n font-size: 0.75rem;\n}\n\n/* Der Auszug ist die Inhaltsvorschau der Runde. Er bekommt eine eigene Zeile,\n damit er nicht mit Marke und Modell um dieselbe Zeile ringt. */\n.szenen-runs__auszug {\n flex: 1 1 100%;\n min-width: 0;\n color: var(--color-text-tertiary);\n font-size: 0.8125rem;\n}\n\n/* B2 \u2014 ein Fehler steht ausserhalb der aufklappbaren Fl\xE4che: Wer eine rote\n Runde sieht, soll nicht erst klicken m\xFCssen, um zu erfahren, woran sie\n gescheitert ist. */\n.szenen-runs__fehler {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n}\n\n.szenen-runs__fehler p {\n margin: 0;\n}\n\n.szenen-runs__inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-runs__prompt > h5,\n.szenen-runs__sekundaer > h5 {\n margin: 0 0 var(--space-xs);\n}\n\n.szenen-runs__prompt > p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.szenen-runs__prompt--negativ > p,\n.szenen-runs__sekundaer > p {\n margin: 0;\n color: var(--color-text-secondary);\n white-space: pre-wrap;\n}\n\n.szenen-runs__mehr {\n justify-self: start;\n margin-top: var(--space-xs);\n padding: 0;\n color: var(--color-accent-teal, var(--color-text-secondary));\n background: transparent;\n border: none;\n font-size: 0.75rem;\n text-decoration: underline;\n cursor: pointer;\n}\n\n.szenen-runs > ol > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-runs > ol > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n.szenen-runs details pre {\n max-height: 20rem;\n overflow: auto;\n padding: var(--space-sm);\n white-space: pre-wrap;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border-radius: var(--radius-sm);\n}\n\n.szenen-media > section {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-media__lookup,\n.szenen-media__attach {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-media__lookup > label,\n.szenen-media__attach > label:first-child {\n flex: 1 1 18rem;\n}\n\n.szenen-media__list {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-media__list > li {\n display: grid;\n grid-template-columns: minmax(12rem, 1fr) auto auto auto;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n@media (max-width: 760px) {\n .szenen-view {\n gap: var(--space-sm);\n }\n\n /* C1 \u2014 auf 390 px bleiben drei Kacheln je Reihe gross genug zum Erkennen\n und klein genug, dass vier Bilder eine Reihe sind und keine Seite. */\n .szenen-bildgalerie {\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n }\n\n /* Der Viewer bekommt den ganzen Bildschirm; die Bl\xE4tterkn\xF6pfe bleiben am\n Rand, wo der Daumen sie erreicht. */\n .szenen-bildviewer {\n padding: var(--space-xs);\n }\n\n .szenen-bildviewer__blaettern {\n width: 2.25rem;\n height: 4rem;\n }\n\n .szenen-view__intro {\n align-items: start;\n flex-direction: column;\n gap: var(--space-xs);\n padding: var(--space-sm);\n }\n\n .szenen-view__intro h2 {\n font-size: 1.05rem;\n }\n\n .szenen-view__intro > div > p:last-child {\n font-size: 0.78rem;\n }\n\n .szenen-view__nav {\n position: sticky;\n top: 0;\n z-index: 2;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-xs);\n padding: var(--space-xs);\n }\n\n .szenen-view__nav button {\n width: 100%;\n min-width: 0;\n min-height: 2.5rem;\n padding: 0.35rem 0.5rem;\n }\n\n\n .szenen-scene-editor {\n grid-template-columns: 1fr;\n }\n\n .szenen-capability > ul > li {\n grid-template-columns: 1fr;\n }\n\n .szenen-derive,\n .szenen-elements,\n .szenen-scene,\n .szenen-setup,\n .szenen-shot,\n .szenen-media,\n .szenen-runs {\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter > label {\n width: 100%;\n min-width: 0;\n flex: none;\n }\n\n .szenen-elements__filter > label:has(input[type="checkbox"]) {\n flex: none;\n grid-column: 1 / -1;\n }\n\n /* Schmal ist die Liste eine Spalte, und die Auswahl fuehrt weiter \u2014 der\n * Eintrag ist deshalb ein Ziel fuer den Finger, keine Kachel. */\n .szenen-elements__list {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .szenen-elements__list > li > button {\n min-height: 2.75rem;\n }\n\n .szenen-elements__back {\n justify-self: stretch;\n min-height: 2.5rem;\n }\n\n .szenen-detail__actions,\n .szenen-runs__start,\n .szenen-scene-editor__add,\n .szenen-scene-editor__add-relation {\n align-items: stretch;\n flex-direction: column;\n }\n\n .szenen-runs__start > *,\n .szenen-scene-editor__add > *,\n .szenen-scene-editor__add-relation > * {\n width: 100%;\n flex: none;\n }\n}\n\n/* \u2500\u2500 Stufe 6.5: Rekonstruktion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich zeigt drei Dinge nebeneinander: den Vorschlag, das nicht\n * Ableitbare und die Widerspr\xFCche. Die Diagnosen sind deshalb bewusst nicht\n * kleiner gesetzt als der Vorschlag \u2014 wer sie \xFCbersieht, \xFCbernimmt Geratenes.\n */\n\n.szenen-derive {\n display: grid;\n gap: var(--space-md);\n}\n\n.szenen-form {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-field {\n display: grid;\n gap: var(--space-xs);\n}\n\n.szenen-field > span {\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-error {\n padding: var(--space-sm) var(--space-md);\n color: var(--color-danger);\n background: var(--color-accent-red-dim);\n border: 1px solid var(--color-accent-red);\n border-radius: var(--radius-sm);\n}\n\n.szenen-empty {\n padding: var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-diagnose {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-warning);\n border-radius: var(--radius-sm);\n}\n\n/* Ein Widerspruch ist der schwerste der drei Befunde: dort steht eine\n * Entscheidung aus, die das System bewusst nicht getroffen hat (D-74). */\n.szenen-diagnose--konflikt {\n border-left-color: var(--color-danger);\n}\n\n.szenen-diagnose > h4 {\n margin: 0 0 var(--space-sm);\n font-size: 0.9375rem;\n}\n\n.szenen-diagnose ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-proposal {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-proposal > h4 {\n margin: 0 0 var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.9375rem;\n}\n\n.szenen-proposal__payload {\n max-height: 18rem;\n margin: 0 0 var(--space-sm);\n padding: var(--space-sm);\n overflow: auto;\n /* Ein erzeugter Prompt ist ein einziger langer Satz \u2014 ohne Umbruch\n verschwindet er nach rechts aus dem Bild. JSON-Payloads bringen ihre\n eigenen Zeilenumbrueche mit und leiden nicht darunter. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-proposal__done {\n margin: var(--space-sm) 0 0;\n color: var(--color-success);\n}\n\n/* Die Belege eines Laufs: Rohantwort und Systemprompt. Zum Lesen und Kopieren,\n nicht zum Bedienen \u2014 deshalb voll markierbar und in Monospace. */\n.szenen-beleg__text {\n width: 100%;\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n line-height: 1.5;\n white-space: pre;\n background: var(--color-bg-app);\n color: var(--color-text);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n resize: vertical;\n}\n\n/* Der Folgelauf (D-87): abgesetzt, damit er nicht wie Teil des Ergebnisses\n aussieht \u2014 er ist eine Frage, keine Auskunft. */\n.szenen-folgelauf {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-folgelauf > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-run__herkunft {\n margin: 0 0 var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-muted, inherit);\n}\n\n/* Widerspricht das Modell, ist das das Wichtigste auf der Seite. */\n.szenen-diagnose--antwort {\n border-left: 3px solid var(--color-accent, var(--color-border));\n}\n\n/* Der Bildw\xE4hler (D-92) \u2014 Browser und Inspector, dem Labs-Tab nachgebildet,\n aber mit dem CSS dieser Ansicht: eine gemeinsame Komponente \xFCber zwei\n Dom\xE4nengrenzen w\xE4re eine Kopplung, die keiner gewollt hat.\n\n Der Review vom 2026-08-22 (R12.1/R12.2) hat den Ma\xDFstab gesetzt: Der Bestand\n ist ein **Browser**, kein Seiteninhalt. Vierzig Werke d\xFCrfen die Szenenseite\n nicht meterlang machen, und zw\xF6lf Varianten eines Werkes keine zw\xF6lf\n Bildschirmseiten. Deshalb steht hier alles unter zwei Regeln \u2014 begrenzte\n Fl\xE4che mit eigenem Scroll, und Kacheln statt Karten. */\n\n/* \u2500\u2500 Ebene 1: der Browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl {\n /* Dieselbe Begr\xFCndung wie beim Inspector: Als Grid-Element w\xFCrde der\n W\xE4hler sonst so breit wie sein breitester Inhalt. */\n min-width: 0;\n}\n\n.szenen-bildwahl__browser {\n /* Die eigene Scrollfl\xE4che ist der ganze Punkt: Der Bestand w\xE4chst nach\n innen, nicht nach unten. Dieselbe Gr\xF6\xDFenordnung wie `max-h-[300px]` im\n Labs-Browser. */\n max-height: 19rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__raster {\n display: grid;\n /* Vier Spalten auf schmalen Displays \u2014 der gemeldete Screenshot zeigte eine\n einzige Spalte gro\xDFer Karten. Breiter wird automatisch dichter. */\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__karte {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-surface);\n text-align: left;\n cursor: pointer;\n}\n\n/* Das fokussierte Werk ist klar markiert \u2014 sonst wei\xDF niemand, wessen Galerie\n der Inspector unten zeigt. */\n.szenen-bildwahl__karte.is-fokus {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n/* \u2500\u2500 Die Kachel selbst \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Der Platz steht, bevor das Bild da ist \u2014 sonst springt das Raster, sobald\n die erste Vorschau eintrifft. */\n.szenen-bildkachel {\n display: block;\n width: 100%;\n aspect-ratio: 1;\n overflow: hidden;\n background: var(--color-bg-app);\n}\n\n.szenen-bildkachel img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.szenen-bildkachel__leer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background: linear-gradient(135deg, var(--color-bg-app), var(--color-bg-surface));\n}\n\n/* R12.3: Eine fehlende Vorschau bleibt kachelgro\xDF. Vorher fiel die Karte auf\n ihren Prompttext zur\xFCck \u2014 und ein Prompt hat im echten Bestand mehrere\n hundert Zeichen. */\n.szenen-bildkachel__zeichen {\n font-size: 0.875rem;\n font-weight: 700;\n color: var(--color-text-secondary);\n opacity: 0.6;\n}\n\n/* Wie viele Varianten das Werk hat \u2014 klein und \xFCberlagernd, nie als\n dominanter Text. */\n.szenen-bildwahl__anzahl {\n position: absolute;\n top: 0.15rem;\n right: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n font-weight: 700;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* Eine Zeile, dann Schluss. Der Prompt eines Werkes ist oft mehrere hundert\n Zeichen lang und w\xE4re als Prim\xE4rdarstellung genau die Textw\xFCste, gegen die\n der Review antritt. */\n.szenen-bildwahl__kacheltitel {\n display: block;\n padding: 0.15rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.3;\n color: var(--color-text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Ebene 2: der Inspector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__inspector {\n /* Ohne `min-width: 0` gewinnt der Inhalt: Der W\xE4hler ist Element eines\n Grids (`.szenen-form`), und dort ist `min-width: auto` die Vorgabe. Der\n Streifen unten ist rund 1000 px breit \u2014 gemessen am 2026-08-22 im echten\n Browser zog er die Auswahl von 374 px auf 1088 px auf und schob die\n ganze Seite waagerecht heraus, statt selbst zu scrollen. */\n min-width: 0;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n /* Sichtbar abgesetzt vom Browser: zwei Ebenen, nicht eine lange Liste. */\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__inspektorkopf {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bildwahl__werktitel {\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--color-text-primary);\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.szenen-bildwahl__zurueck {\n flex: 0 0 auto;\n font-size: 0.6875rem;\n padding: 0.15rem 0.5rem;\n}\n\n/* Der Streifen: waagerecht scrollend, feste kleine Kacheln \u2014 zw\xF6lf Varianten\n bleiben ein \xDCberblick statt zw\xF6lf Bildschirmseiten. */\n.szenen-bildwahl__streifen {\n /* Die Breite kommt vom Elter, nie vom Inhalt \u2014 sonst scrollt hier nichts. */\n max-width: 100%;\n display: flex;\n gap: var(--space-sm);\n margin: 0;\n padding: 0 0 0.25rem;\n list-style: none;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n\n.szenen-bildwahl__streifen > li {\n flex: 0 0 auto;\n width: 5rem;\n}\n\n.szenen-bildwahl__variante {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-app);\n cursor: pointer;\n}\n\n.szenen-bildwahl__variante.is-gewaehlt {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n.szenen-bildwahl__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Das Gew\xE4hlte \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__gewaehlt {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__gewaehlt li {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.5rem;\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n max-width: 100%;\n}\n\n/* Das gew\xE4hlte Bild wird best\xE4tigt, nicht nur benannt: eine kleine Vorschau\n neben dem Titel. */\n.szenen-bildwahl__gewaehlt .szenen-bildkachel {\n width: 2.25rem;\n flex: 0 0 auto;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__gewaehlt .szenen-bildwahl__kacheltitel {\n min-width: 0;\n max-width: 12rem;\n font-size: 0.75rem;\n}\n\n.szenen-beleg__kopf {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details + details {\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details > summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n/* \u2500\u2500 Die Fehlerrettung (Stufe 6.6, A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Sie steht im Fehlerblock und muss trotzdem als Werkzeug lesbar sein, nicht\n * als weitere Fehlermeldung. Deshalb abgesetzt wie der Folgelauf \u2014 und aus\n * demselben Grund: Beides sind Angebote, keine Ausk\xFCnfte.\n *\n * Der wichtigste optische Punkt ist die Trennung von Original und\n * Arbeitskopie. Das Original bekommt den ruhigen, \u201Efesten" Hintergrund der\n * Belege; die Kopie sieht aus wie ein normales Eingabefeld, weil sie eines\n * ist. */\n.szenen-rettung {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-rettung > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-rettung textarea[readonly] {\n background: var(--color-bg-surface);\n color: var(--color-text-secondary);\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung textarea:not([readonly]) {\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0;\n}\n\n/* Ein Befund tr\xE4gt Code, Bezug und Meldung. Der Code steht klein und\n * monospaced davor: Er ist die Kennung, nicht die Aussage. */\n.szenen-diagnose__liste {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-diagnose__liste code {\n font-size: 0.8125rem;\n}\n\n.szenen-diagnose__liste > li[data-severity=\'warning\'] {\n color: var(--color-text-secondary);\n}\n\n/* \u2500\u2500 Die Draft-Kette (Stufe 6.6, C4) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Hauptweg. Er steht nach den Einzel\xFCbernahmen und muss als das erkennbar\n * sein, was er ist: nicht eine weitere Option, sondern der k\xFCrzere Weg zum\n * selben Ziel. Deshalb durchgezogener Rahmen statt gestricheltem \u2014 er ist\n * Angebot **und** Empfehlung. */\n.szenen-kette {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-kette > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-kette ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0 0 var(--space-sm);\n padding-left: var(--space-md);\n}\n\n.szenen-kette code {\n font-size: 0.8125rem;\n}\n\n/* Ein Hinweis, der eine Bedingung erklaert \u2014 nicht rot, weil er kein Fehler\n ist, aber abgesetzt, weil er eine Folge hat. */\n.szenen-detail__hint {\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Stufe 6.7: Bilder am Prompt-Lauf (H0/H1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich tr\xE4gt zwei Dinge, die man nie verwechseln darf: den **Eingang**\n * (woraus der Prompt entstand) und den **Ausgang** (was daraus wurde). Sie\n * bekommen deshalb verschiedene Akzentkanten statt nur verschiedener\n * \xDCberschriften \u2014 wer schnell liest, sieht die Trennung, bevor er sie liest.\n */\n.szenen-bilder {\n display: grid;\n gap: var(--space-md);\n margin-top: var(--space-sm);\n}\n\n.szenen-bilder__eingang,\n.szenen-bilder__ausgang {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__eingang {\n border-left: 3px solid var(--color-accent-purple, var(--color-border));\n}\n\n.szenen-bilder__ausgang {\n border-left: 3px solid var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-bilder h5 {\n margin: 0;\n}\n\n.szenen-bilder__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: 0;\n align-items: baseline;\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-bilder__eingangsliste,\n.szenen-bilder__batchliste,\n.szenen-bilder__auftraege {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bilder__eingangsliste > li,\n.szenen-bilder__batchliste > li,\n.szenen-bilder__auftraege > li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n/* Ein unterbrochener Auftrag ist der Fall, f\xFCr den der Auftragssatz existiert.\n Er wird deshalb markiert \u2014 nicht rot, denn er ist kein Fehler, sondern eine\n offene Entscheidung. */\n.szenen-bilder__auftraege > li[data-status="started"] {\n border-left: 3px solid var(--color-warning);\n}\n\n.szenen-bilder__auftraege > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-bilder__auftraege > li[data-status="partial"] {\n border-left: 3px solid var(--color-accent-blue, var(--color-border));\n}\n\n.szenen-bilder__auftraege > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n/* Das Eingangsbild benutzt seit C3 dieselbe Kachel wie W\xE4hler und Galerie.\n Sie ist von Haus aus so breit wie ihr Platz \u2014 in dieser Zeile bekommt sie\n deshalb eine feste Gr\xF6sse, sonst sch\xF6be sie die Metadaten heraus. */\n.szenen-bilder__eingangsliste .szenen-bildkachel {\n flex: 0 0 auto;\n width: 4rem;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batch {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batchkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-bilder__herkunft {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n/* Review-Befund \u2014 ein transienter Bildfehler ist wiederholbar, kein\n endg\xFCltiges \u201Efehlt". Dezent wie ein Angebot, nicht wie ein Fehlerzustand. */\n.szenen-bilder__retry {\n justify-self: start;\n padding: 0.15rem 0.5rem;\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n/* C4 \u2014 die Provenienz vollst\xE4ndig, aber sekund\xE4r. */\n.szenen-bilder__technik > summary {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-bilder__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n}\n\n/* C5 \u2014 der Verlauf verdichtet, seine Ausreisser nicht. */\n.szenen-bilder__verlauf > summary {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n cursor: pointer;\n}\n\n.szenen-bilder__verlauf[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__auffaellig {\n margin: 0;\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n.szenen-bilder__auftragskennung {\n flex-basis: 100%;\n color: var(--color-text-tertiary);\n font-size: 0.6875rem;\n}\n\n/* \u2500\u2500 C1/C2 \u2014 Galerie und grosse Ansicht \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Das Raster w\xE4chst mit dem Platz: auf 390 px drei bis vier Kacheln je Reihe,\n auf dem Desktop entsprechend mehr. Vorher war jedes Bild eine Zeile aus\n Daumennagel plus vier Metadatenfeldern \u2014 vier Bilder ergaben vier Textzeilen\n und das Protokoll war die Hauptsache. */\n.szenen-bildgalerie {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));\n gap: var(--space-sm);\n min-width: 0;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildgalerie__kachel {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildgalerie__kachel:hover,\n.szenen-bildgalerie__kachel:focus-visible {\n border-color: var(--color-accent-teal, var(--color-text-primary));\n}\n\n.szenen-bildgalerie__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* Die grosse Ansicht liegt \xFCber allem und bringt ihren eigenen Grund mit \u2014\n `position: fixed` bezieht sich im Flow-Canvas auf dessen Rahmen, in React auf\n das Fenster; in beiden F\xE4llen genau das Gew\xFCnschte. */\n.szenen-bildviewer {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: grid;\n grid-template-rows: auto 1fr auto;\n gap: var(--space-sm);\n padding: var(--space-sm);\n /* Deckend, nicht get\xF6nt: Bei 0.92 schimmerte der Laufbereich durch und man\n las Prompttext quer \xFCber dem Bild \u2014 im Browser gemessen. */\n background: #000;\n}\n\n/* Titel, Z\xE4hlung, Schliessen \u2014 drei Spalten, damit ein langer Werktitel die\n Z\xE4hlung nicht in die n\xE4chste Zeile und \xFCber das Bild schiebt. */\n.szenen-bildviewer__kopf {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n gap: var(--space-sm);\n color: var(--color-text-primary);\n}\n\n.szenen-bildviewer__titel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n}\n\n.szenen-bildviewer__zaehler {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n}\n\n.szenen-bildviewer__buehne {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--space-sm);\n min-height: 0;\n min-width: 0;\n}\n\n/* Der Rahmen nimmt den Platz, der zwischen den Bl\xE4tterkn\xF6pfen \xFCbrig ist \u2014\n `min-width: 0`, damit er auch wirklich schrumpft statt sie hinauszuschieben. */\n.szenen-bildviewer__rahmen {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n\n.szenen-bildviewer__bild {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.szenen-bildviewer__leer {\n color: var(--color-text-secondary);\n text-align: center;\n}\n\n/* Die Bl\xE4tterkn\xF6pfe sind daumengross und liegen an den R\xE4ndern \u2014 auf einem\n Telefon ist der Rand erreichbar, die Bildmitte nicht. */\n.szenen-bildviewer__blaettern {\n flex: 0 0 auto;\n width: 2.75rem;\n height: 3.5rem;\n font-size: 1.75rem;\n line-height: 1;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildviewer__details {\n max-height: 8rem;\n overflow-y: auto;\n color: var(--color-text-secondary);\n}\n\n/* Die Aktionen stehen abgesetzt: Sie sind Angebote, keine Ausk\xFCnfte \u2014 dasselbe\n Muster wie beim Folgelauf und bei der Fehlerrettung. */\n/* D \u2014 die Aktionen stehen abgesetzt und standardm\xE4ssig kompakt: Sie sind\n Angebote, keine Ausk\xFCnfte. Vorher hielten beide Formulare dauerhaft offen und\n machten aus jedem Lauf zwei zus\xE4tzliche Bildschirmseiten. */\n.szenen-bilder__aktion {\n min-width: 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__aktion > summary {\n color: var(--color-text-primary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-bilder__aktion[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion h5 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-bilder__aktion ol {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n font-size: 0.8125rem;\n}\n\n/* Regelwerk-UX Schritt 16 \u2014 der ZIP-Sammelimport.\n *\n * \xA78: Der gemeldete Einsatz ist mobil. Deshalb **ein** Seedfeld statt einer\n * gestapelten Eingabe je Bild, und die Zuordnung Bild -> Seed als umbrechende\n * Zeile statt als Tabelle: Eine Tabelle waere auf 390 px abgeschnitten, und\n * abgeschnitten heisst hier, dass der Nutzer die Zuordnung nicht mehr pruefen\n * kann \u2014 genau die Pruefung, wegen der die Vorschau existiert. */\n.szenen-bilder__zip {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bilder__zipkopf {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipvorlagen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n margin: 0;\n}\n\n/* Eine laengere Liste bleibt scrollbar, statt die Sektion und damit den\n * Import-Knopf aus dem Blick zu schieben (\xA78, letzter Punkt). */\n.szenen-bilder__zipliste {\n max-height: 16rem;\n overflow-y: auto;\n}\n\n.szenen-bilder__zipliste > li {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-bilder__zipliste > li > span:first-child {\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipliste > li > span:last-child {\n color: var(--color-text-secondary);\n white-space: nowrap;\n}\n\n/* ==========================================================================\n STUFE 6.10 \u2014 ELEMENTEDITOR UND VERSIONSNAVIGATION\n ========================================================================== */\n\n/* \xA74.2 \u2014 vier Sachverhalte, vier Marken. Die Marken stehen in einer eigenen\n * Spalte, damit ANGEZEIGT + BEVORZUGT nebeneinander lesbar bleiben, statt sich\n * durch den Text zu schieben. */\n.szenen-detail__heads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-version__titel {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n margin-right: auto;\n}\n\n/* V17 \u2014 Kennungen bleiben vollst\xE4ndig sichtbar, aber optisch sekund\xE4r. */\n.szenen-version__id,\n.szenen-ref__id,\n.szenen-detail__field-key {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n overflow-wrap: anywhere;\n}\n\n.szenen-version__marken {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-version__marken > span {\n padding: 0.1rem 0.5rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.7rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n}\n\n.szenen-version--angezeigt {\n border-color: var(--color-accent-teal, #2dd4bf) !important;\n}\n\n/* V9 \u2014 der Basiswechsel ist eine Entscheidung, keine Nebenwirkung. */\n.szenen-detail__confirm {\n display: grid;\n gap: var(--space-sm);\n margin: var(--space-md) 0;\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-accent-amber, #f59e0b);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__confirm p {\n margin: 0;\n}\n\n/* Untergruppen von Objektfeldern (`prompt_render_settings`, `overrides`,\n `blender_render_settings`) \u2014 seit A1/A2 auf- und zuklappbar. Eine optionale\n Gruppe startet zu: Blender und `overrides` f\xFCllten auf einem 390-px-Display\n sonst mehrere Bildschirmseiten, obwohl die meisten Shots sie nie anfassen. */\n.szenen-detail__group {\n min-width: 0;\n margin: var(--space-md) 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group > summary {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__group[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n/* A2 \u2014 dass eine zugeklappte Gruppe schon Werte tr\xE4gt, muss man sehen, ohne\n sie zu \xF6ffnen. Sonst \xFCbersieht man eine gesetzte Blender-Ausgabe genau so\n lange, bis sie im Ergebnis auftaucht. */\n.szenen-detail__group-status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--color-text-tertiary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group[data-konfiguriert="ja"] > summary .szenen-detail__group-status {\n color: var(--color-text-primary);\n border-color: var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-detail__group-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n/* A3 \u2014 die Kernaussage steht am Feld, die Begr\xFCndung dahinter. Nicht als\n Tooltip: Auf einem Telefon gibt es kein Hover, und was dort unerreichbar ist,\n ist gestrichen \u2014 nur ohne dass es jemand entschieden h\xE4tte. */\n.szenen-detail__warum > summary {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-detail__warum > small {\n display: block;\n padding-top: var(--space-xs);\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__fieldrow {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n margin-bottom: var(--space-md);\n}\n\n/* A4 \u2014 der technische Feldname bleibt lesbar, tritt aber hinter das fachliche\n Label zur\xFCck. */\n.szenen-detail__field-label {\n color: var(--color-text-primary);\n}\n\n.szenen-detail__field {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-detail__field > span {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n}\n\n/* Mehrfachauswahl und Listeneditoren. */\n.szenen-feld__mehrfach,\n.szenen-feld__liste,\n.szenen-feld__reflist,\n.szenen-feld__ref,\n.szenen-feld__zielmodell,\n.szenen-feld__kamera {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-feld__mehrfach > label,\n.szenen-feld__null {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-feld__liste-zeile {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__reflist ol {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-feld__reflist li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n padding: var(--space-xs) var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \xA76.1 \u2014 Titel und Typ zuerst, Kennungen daneben. */\n.szenen-ref__anzeige {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-ref__typ {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-ref__warn {\n padding: 0.1rem 0.5rem;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n/* \xA76.5 \u2014 abgeleitete Angaben, ausdr\xFCcklich nur Anzeige. */\n.szenen-detail__derived {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__derived dl {\n display: grid;\n grid-template-columns: minmax(10rem, auto) 1fr;\n gap: var(--space-xs) var(--space-sm);\n margin: 0;\n}\n\n.szenen-detail__derived dt {\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__derived dd {\n margin: 0;\n}\n\n.szenen-feld__abgeleitet {\n margin: 0;\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n}\n';
174
+ var SZENEN_VIEW_STYLES = '/* ==========================================================================\n SZENEN VIEW\n ========================================================================== */\n\n.szenen-view {\n display: grid;\n gap: var(--space-md);\n width: 100%;\n min-width: 0;\n font-family: var(--font-sans);\n padding: var(--space-md);\n}\n\n@media (max-width: 760px) {\n .szenen-view {\n padding: var(--space-sm);\n gap: var(--space-sm);\n }\n}\n\n.szenen-view__intro {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n padding: clamp(0.6rem, 1.2vw, 0.9rem) clamp(0.75rem, 1.6vw, 1.1rem);\n color: var(--color-text-primary);\n background:\n radial-gradient(circle at 85% 15%, rgba(45, 212, 191, 0.16), transparent 34%),\n linear-gradient(135deg, var(--color-bg-surface), var(--color-bg-elevated));\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-view__intro h2,\n.szenen-view__intro p {\n margin: 0;\n}\n\n.szenen-view__intro h2 {\n margin-bottom: 0.1rem;\n font-size: clamp(1rem, 1.9vw, 1.3rem);\n line-height: 1.2;\n}\n\n.szenen-view__intro > div > p:last-child {\n max-width: 44rem;\n color: var(--color-text-secondary);\n font-size: 0.82rem;\n line-height: 1.35;\n}\n\n.szenen-view__eyebrow {\n margin-bottom: 0.1rem !important;\n color: var(--color-accent-teal, #2dd4bf);\n font-size: 0.62rem;\n font-weight: 800;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\n.szenen-view__step {\n flex: none;\n padding: 0.25rem 0.6rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n.szenen-view__nav {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-view__nav button,\n.szenen-view button {\n min-height: 2.25rem;\n padding: 0.45rem 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-view button:hover:not(:disabled) {\n color: var(--color-text-primary);\n background: var(--color-bg-surface-hover);\n border-color: var(--color-border-hover);\n}\n\n.szenen-view button:focus-visible,\n.szenen-view input:focus-visible,\n.szenen-view select:focus-visible,\n.szenen-view textarea:focus-visible {\n outline: 2px solid var(--color-accent-blue);\n outline-offset: 2px;\n}\n\n.szenen-view button:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.szenen-view__nav button.is-active,\n.szenen-view__nav button[aria-selected="true"] {\n color: var(--color-text-primary);\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-view__area {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-view__capability-note,\n.szenen-area__lead {\n margin: 0;\n padding: var(--space-sm) var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-accent-purple);\n border-radius: var(--radius-sm);\n}\n\n.szenen-view__host-notes {\n color: var(--color-text-secondary);\n}\n\n.szenen-view__host-notes > summary {\n cursor: pointer;\n font-size: 0.8rem;\n}\n\n/* `box-sizing` steht hier und nicht beim Host: Die React-App bringt in\n * `master.css` einen globalen `* { box-sizing: border-box }` mit, Flow **nicht**\n * \u2014 dort ragte `width: 100%` plus Innenabstand um die Randbreite aus dem\n * Elternelement heraus und erzeugte auf ~390 px einen horizontalen Scroll. Die\n * gemeinsame Ansicht darf sich auf keinen Host-Reset verlassen. */\n.szenen-view input:not([type="checkbox"]),\n.szenen-view select,\n.szenen-view textarea {\n box-sizing: border-box;\n width: 100%;\n min-height: 2.25rem;\n padding: 0.5rem 0.65rem;\n color: var(--color-text-primary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font: inherit;\n}\n\n.szenen-view textarea {\n min-height: 7rem;\n resize: vertical;\n font-family: var(--font-mono);\n font-size: 0.8rem;\n}\n\n.szenen-view label {\n display: grid;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-view code,\n.szenen-view pre {\n font-family: var(--font-mono);\n overflow-wrap: anywhere;\n}\n\n.szenen-view h3,\n.szenen-view h4,\n.szenen-view h5 {\n margin-top: 0;\n}\n\n.szenen-view ul,\n.szenen-view ol {\n padding-left: 1.25rem;\n}\n\n.szenen-elements,\n.szenen-detail,\n.szenen-scene,\n.szenen-setup,\n.szenen-erweitert,\n.szenen-prompts,\n.szenen-shot,\n.szenen-media,\n.szenen-runs {\n display: grid;\n gap: var(--space-md);\n min-width: 0;\n}\n\n/* Arbeitsoberflaeche: Liste links, Bearbeitung rechts.\n *\n * Breit stehen beide Spalten nebeneinander \u2014 die Auswahl bleibt sichtbar,\n * waehrend bearbeitet wird. Schmal rendert die Komponente nur eine der beiden;\n * hier steht deshalb keine Regel, die die andere versteckt. */\n@media (min-width: 761px) {\n .szenen-elements {\n grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);\n align-items: start;\n }\n\n .szenen-elements__master,\n .szenen-elements__detail {\n min-width: 0;\n }\n}\n\n.szenen-elements__master,\n.szenen-elements__detail {\n display: grid;\n gap: var(--space-sm);\n align-content: start;\n min-width: 0;\n}\n\n.szenen-elements__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-elements__heading {\n margin: 0;\n font-size: 1.05rem;\n}\n\n.szenen-elements__scope {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n min-width: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-elements__scope > select {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.szenen-elements__new {\n flex: none;\n font-weight: 700;\n}\n\n.szenen-elements__create {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-accent-blue);\n border-radius: var(--radius-sm);\n}\n\n.szenen-elements__create label {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-elements__create-actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n}\n\n.szenen-elements__options > summary {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n cursor: pointer;\n}\n\n.szenen-elements__back {\n justify-self: start;\n font-weight: 700;\n}\n\n.szenen-elements__empty-detail {\n margin: 0;\n color: var(--color-text-tertiary);\n}\n\n.szenen-elements__filter,\n.szenen-detail__actions,\n.szenen-runs__start,\n.szenen-scene-editor__add,\n.szenen-scene-editor__add-relation {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-elements__filter > label,\n.szenen-runs__start > label,\n.szenen-scene-editor__add > input {\n flex: 1 1 16rem;\n}\n\n.szenen-elements__filter > label:has(input[type="checkbox"]) {\n display: flex;\n flex: 1 1 16rem;\n flex-direction: row;\n align-items: center;\n}\n\n.szenen-elements__filter input[type="checkbox"] {\n width: 1rem;\n height: 1rem;\n accent-color: var(--color-accent-blue);\n}\n\n.szenen-elements__list {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-elements__list > li > button {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs) var(--space-sm);\n width: 100%;\n height: 100%;\n text-align: left;\n}\n\n.szenen-elements__list > li > button > .szenen-elements__title {\n flex: 1 1 100%;\n}\n\n.szenen-elements__list > li > button.is-selected,\n.szenen-elements__list > li > button[aria-current="true"] {\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-elements__type {\n color: var(--color-accent-blue);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-elements__title {\n color: var(--color-text-primary);\n font-size: 0.95rem;\n}\n\n.szenen-elements__subtype,\n.szenen-elements__retired {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n}\n\n.szenen-elements__retired {\n color: var(--color-warning);\n}\n\n.szenen-detail {\n margin-top: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n@media (min-width: 761px) {\n .szenen-detail {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n }\n}\n\n.szenen-detail > header,\n.szenen-detail__heads,\n.szenen-detail__draft,\n.szenen-capability {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* .szenen-runs ist ein eigenst\xE4ndiger sichtbarer Bereich, beh\xE4lt Styling auf Desktop */\n@media (min-width: 761px) {\n .szenen-runs {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n }\n}\n\n@media (max-width: 760px) {\n .szenen-runs {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n }\n}\n\n.szenen-detail__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin-bottom: 0;\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__heads ul,\n.szenen-scene-editor ul,\n.szenen-runs > ol,\n.szenen-capability > ul {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-detail__heads li,\n.szenen-scene-editor li,\n.szenen-runs > ol > li,\n.szenen-capability > ul > li {\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n\n/* Stufe 6.7.2 (D-126) \u2014 Advanced-/Debug-Fl\xE4che f\xFCr die rohe, schema-getriebene\n * Feldliste einer Szene. Gestrichelt wie die anderen \u201EAngebot statt\n * Auskunft"-Bereiche, aber ohne Akzentfarbe: sie ist kein Vorschlag, sondern\n * ein Eskalationsweg. */\n.szenen-detail__technical {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__technical > summary {\n margin-bottom: var(--space-sm);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__technical[open] > summary {\n margin-bottom: var(--space-md);\n}\n\n.szenen-detail__field small,\n.szenen-capability small {\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__preview-errors,\n.szenen-detail__notice--error,\n.szenen-view [role="alert"] {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-detail__notice {\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__notice--success,\n.szenen-view [data-tone="success"] {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n border-color: var(--color-accent-green);\n}\n\n.szenen-scene-editor {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-md);\n margin-bottom: 0;\n}\n\n.szenen-scene-editor > section {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* \u2500\u2500 Stufe 6.7.2 (D-126): Node-/Relationskarten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Nodes und Beziehungen sind `<details>` \u2014 dieselbe Interaktionssprache wie\n * \u201ERohantwort (Diagnose)" und \u201EBilder zu diesem Lauf" in RunsPanel.tsx. Kopf\n * (immer sichtbar) tr\xE4gt Inhalt zuerst, Technik zuletzt; Body (nur bei\n * ge\xF6ffneter Karte) tr\xE4gt die Bearbeitungscontrols. Padding wandert von der\n * `<li>` in Kopf/Body, damit die Karte keinen doppelten Rand bekommt. */\n.szenen-scene-editor li {\n padding: 0;\n overflow: hidden;\n}\n\n.szenen-node-karte__kopf,\n.szenen-relation-karte__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs) var(--space-sm);\n padding: var(--space-sm);\n cursor: pointer;\n list-style: none;\n}\n\n.szenen-node-karte__kopf::-webkit-details-marker,\n.szenen-relation-karte__kopf::-webkit-details-marker {\n display: none;\n}\n\n.szenen-node-karte__titel {\n flex: 1 1 12rem;\n min-width: 0;\n color: var(--color-text-primary);\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n.szenen-node-karte__typ {\n flex: none;\n padding: 0.15rem 0.55rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n white-space: nowrap;\n}\n\n/* node_id klein, monospace, sekund\xE4r \u2014 sichtbar auch bei zugeklappter Karte,\n * aber optisch klar hinter Beschreibung und Typ. */\n.szenen-node-karte__meta {\n flex: 1 1 100%;\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n.szenen-node-karte__meta code {\n font-size: inherit;\n}\n\n.szenen-node-karte__body,\n.szenen-relation-karte__body {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-sm) var(--space-sm);\n border-top: 1px solid var(--color-border);\n}\n\n/* \xA74 \u2014 Prim\xE4raktion und destruktive Aktion optisch getrennt: \u201ENode/Beziehung\n * entfernen" bekommt einen eigenen, warnenden Ton statt gleichgewichtig neben\n * der normalen Bearbeitung zu stehen. */\n.szenen-node-karte__entfernen,\n.szenen-relation-karte__entfernen {\n justify-self: start;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-relation-karte__kopf {\n font-size: 0.9rem;\n}\n\n.szenen-relation-karte__kopf code {\n overflow-wrap: anywhere;\n}\n\n.szenen-relation-karte__pfeil {\n flex: none;\n color: var(--color-text-secondary);\n}\n\n/* Die Composer-\xD6ffner (\u201E+ Node hinzuf\xFCgen", \u201E+ Beziehung hinzuf\xFCgen") sind\n * Angebote, keine Ausk\xFCnfte \u2014 dasselbe gestrichelte Muster wie beim Folgelauf\n * und der Fehlerrettung (\xA73.3/\xA73.5). */\n.szenen-scene-editor__oeffnen {\n width: 100%;\n border-style: dashed;\n}\n\n.szenen-refpicker {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n margin: var(--space-sm) 0;\n padding: var(--space-sm) 0;\n border: none;\n border-radius: 0;\n}\n\n.szenen-refpicker legend {\n padding: 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n}\n\n.szenen-capability > ul > li {\n display: grid;\n grid-template-columns: minmax(10rem, 1fr) auto minmax(14rem, 2fr);\n align-items: start;\n gap: var(--space-sm);\n}\n\n.szenen-capability__state {\n padding: 0.1rem 0.5rem;\n border-radius: var(--radius-full);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-capability__state--yes {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n}\n\n.szenen-capability__state--no {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n}\n\n.szenen-capability__state--unknown {\n color: #fef08a;\n background: rgba(234, 179, 8, 0.2);\n}\n\n.szenen-runs > ol > li {\n display: grid;\n gap: var(--space-sm);\n}\n\n/* B1 \u2014 die geschlossene Runde. Vorher stand die `run_id` als erstes und war\n die einzige Information, die niemand liest; jetzt beantwortet die Kopfzeile\n welche Runde, ob sie durchging, mit welchem Modell und was ungef\xE4hr\n herauskam. */\n.szenen-runs__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n width: 100%;\n padding: var(--space-xs) 0;\n text-align: left;\n color: var(--color-text-secondary);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n\n.szenen-runs__marke {\n font-weight: 700;\n color: var(--color-text-primary);\n}\n\n.szenen-runs__status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n}\n\n.szenen-runs__status[data-status="succeeded"] {\n border-color: var(--color-success);\n}\n\n.szenen-runs__status[data-status="failed"] {\n border-color: var(--color-danger);\n}\n\n.szenen-runs__modell {\n font-size: 0.75rem;\n}\n\n/* Der Auszug ist die Inhaltsvorschau der Runde. Er bekommt eine eigene Zeile,\n damit er nicht mit Marke und Modell um dieselbe Zeile ringt. */\n.szenen-runs__auszug {\n flex: 1 1 100%;\n min-width: 0;\n color: var(--color-text-tertiary);\n font-size: 0.8125rem;\n}\n\n/* B2 \u2014 ein Fehler steht ausserhalb der aufklappbaren Fl\xE4che: Wer eine rote\n Runde sieht, soll nicht erst klicken m\xFCssen, um zu erfahren, woran sie\n gescheitert ist. */\n.szenen-runs__fehler {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n}\n\n.szenen-runs__fehler p {\n margin: 0;\n}\n\n.szenen-runs__inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-runs__prompt > h5,\n.szenen-runs__sekundaer > h5 {\n margin: 0 0 var(--space-xs);\n}\n\n.szenen-runs__prompt > p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.szenen-runs__prompt--negativ > p,\n.szenen-runs__sekundaer > p {\n margin: 0;\n color: var(--color-text-secondary);\n white-space: pre-wrap;\n}\n\n.szenen-runs__mehr {\n justify-self: start;\n margin-top: var(--space-xs);\n padding: 0;\n color: var(--color-accent-teal, var(--color-text-secondary));\n background: transparent;\n border: none;\n font-size: 0.75rem;\n text-decoration: underline;\n cursor: pointer;\n}\n\n.szenen-runs > ol > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-runs > ol > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n.szenen-runs details pre {\n max-height: 20rem;\n overflow: auto;\n padding: var(--space-sm);\n white-space: pre-wrap;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border-radius: var(--radius-sm);\n}\n\n.szenen-media > section {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-media__lookup,\n.szenen-media__attach {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-media__lookup > label,\n.szenen-media__attach > label:first-child {\n flex: 1 1 18rem;\n}\n\n.szenen-media__list {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-media__list > li {\n display: grid;\n grid-template-columns: minmax(12rem, 1fr) auto auto auto;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n@media (max-width: 760px) {\n .szenen-view {\n gap: var(--space-sm);\n }\n\n /* C1 \u2014 auf 390 px bleiben drei Kacheln je Reihe gross genug zum Erkennen\n und klein genug, dass vier Bilder eine Reihe sind und keine Seite. */\n .szenen-bildgalerie {\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n }\n\n /* Der Viewer bekommt den ganzen Bildschirm; die Bl\xE4tterkn\xF6pfe bleiben am\n Rand, wo der Daumen sie erreicht. */\n .szenen-bildviewer {\n padding: var(--space-xs);\n }\n\n .szenen-bildviewer__blaettern {\n width: 2.25rem;\n height: 4rem;\n }\n\n .szenen-view__intro {\n align-items: start;\n flex-direction: column;\n gap: var(--space-xs);\n padding: var(--space-sm);\n }\n\n .szenen-view__intro h2 {\n font-size: 1.05rem;\n }\n\n .szenen-view__intro > div > p:last-child {\n font-size: 0.78rem;\n }\n\n .szenen-view__nav {\n position: sticky;\n top: 0;\n z-index: 2;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-xs);\n padding: var(--space-xs);\n }\n\n .szenen-view__nav button {\n width: 100%;\n min-width: 0;\n min-height: 2.5rem;\n padding: 0.35rem 0.5rem;\n }\n\n\n .szenen-scene-editor {\n grid-template-columns: 1fr;\n }\n\n .szenen-capability > ul > li {\n grid-template-columns: 1fr;\n }\n\n .szenen-derive,\n .szenen-elements,\n .szenen-scene,\n .szenen-setup,\n .szenen-shot,\n .szenen-media,\n .szenen-runs {\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter > label {\n width: 100%;\n min-width: 0;\n flex: none;\n }\n\n .szenen-elements__filter > label:has(input[type="checkbox"]) {\n flex: none;\n grid-column: 1 / -1;\n }\n\n /* Schmal ist die Liste eine Spalte, und die Auswahl fuehrt weiter \u2014 der\n * Eintrag ist deshalb ein Ziel fuer den Finger, keine Kachel. */\n .szenen-elements__list {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .szenen-elements__list > li > button {\n min-height: 2.75rem;\n }\n\n .szenen-elements__back {\n justify-self: stretch;\n min-height: 2.5rem;\n }\n\n .szenen-detail__actions,\n .szenen-runs__start,\n .szenen-scene-editor__add,\n .szenen-scene-editor__add-relation {\n align-items: stretch;\n flex-direction: column;\n }\n\n .szenen-runs__start > *,\n .szenen-scene-editor__add > *,\n .szenen-scene-editor__add-relation > * {\n width: 100%;\n flex: none;\n }\n}\n\n/* \u2500\u2500 Stufe 6.5: Rekonstruktion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich zeigt drei Dinge nebeneinander: den Vorschlag, das nicht\n * Ableitbare und die Widerspr\xFCche. Die Diagnosen sind deshalb bewusst nicht\n * kleiner gesetzt als der Vorschlag \u2014 wer sie \xFCbersieht, \xFCbernimmt Geratenes.\n */\n\n.szenen-derive {\n display: grid;\n gap: var(--space-md);\n}\n\n.szenen-form {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-field {\n display: grid;\n gap: var(--space-xs);\n}\n\n.szenen-field > span {\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-error {\n padding: var(--space-sm) var(--space-md);\n color: var(--color-danger);\n background: var(--color-accent-red-dim);\n border: 1px solid var(--color-accent-red);\n border-radius: var(--radius-sm);\n}\n\n.szenen-empty {\n padding: var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-diagnose {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-warning);\n border-radius: var(--radius-sm);\n}\n\n/* Ein Widerspruch ist der schwerste der drei Befunde: dort steht eine\n * Entscheidung aus, die das System bewusst nicht getroffen hat (D-74). */\n.szenen-diagnose--konflikt {\n border-left-color: var(--color-danger);\n}\n\n.szenen-diagnose > h4 {\n margin: 0 0 var(--space-sm);\n font-size: 0.9375rem;\n}\n\n.szenen-diagnose ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-proposal {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-proposal > h4 {\n margin: 0 0 var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.9375rem;\n}\n\n.szenen-proposal__payload {\n max-height: 18rem;\n margin: 0 0 var(--space-sm);\n padding: var(--space-sm);\n overflow: auto;\n /* Ein erzeugter Prompt ist ein einziger langer Satz \u2014 ohne Umbruch\n verschwindet er nach rechts aus dem Bild. JSON-Payloads bringen ihre\n eigenen Zeilenumbrueche mit und leiden nicht darunter. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-proposal__done {\n margin: var(--space-sm) 0 0;\n color: var(--color-success);\n}\n\n/* Die Belege eines Laufs: Rohantwort und Systemprompt. Zum Lesen und Kopieren,\n nicht zum Bedienen \u2014 deshalb voll markierbar und in Monospace. */\n.szenen-beleg__text {\n width: 100%;\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n line-height: 1.5;\n white-space: pre;\n background: var(--color-bg-app);\n color: var(--color-text);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n resize: vertical;\n}\n\n/* Der Folgelauf (D-87): abgesetzt, damit er nicht wie Teil des Ergebnisses\n aussieht \u2014 er ist eine Frage, keine Auskunft. */\n.szenen-folgelauf {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-folgelauf > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-run__herkunft {\n margin: 0 0 var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-muted, inherit);\n}\n\n/* Widerspricht das Modell, ist das das Wichtigste auf der Seite. */\n.szenen-diagnose--antwort {\n border-left: 3px solid var(--color-accent, var(--color-border));\n}\n\n/* Der Bildw\xE4hler (D-92) \u2014 Browser und Inspector, dem Labs-Tab nachgebildet,\n aber mit dem CSS dieser Ansicht: eine gemeinsame Komponente \xFCber zwei\n Dom\xE4nengrenzen w\xE4re eine Kopplung, die keiner gewollt hat.\n\n Der Review vom 2026-08-22 (R12.1/R12.2) hat den Ma\xDFstab gesetzt: Der Bestand\n ist ein **Browser**, kein Seiteninhalt. Vierzig Werke d\xFCrfen die Szenenseite\n nicht meterlang machen, und zw\xF6lf Varianten eines Werkes keine zw\xF6lf\n Bildschirmseiten. Deshalb steht hier alles unter zwei Regeln \u2014 begrenzte\n Fl\xE4che mit eigenem Scroll, und Kacheln statt Karten. */\n\n/* \u2500\u2500 Ebene 1: der Browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl {\n /* Dieselbe Begr\xFCndung wie beim Inspector: Als Grid-Element w\xFCrde der\n W\xE4hler sonst so breit wie sein breitester Inhalt. */\n min-width: 0;\n}\n\n.szenen-bildwahl__browser {\n /* Die eigene Scrollfl\xE4che ist der ganze Punkt: Der Bestand w\xE4chst nach\n innen, nicht nach unten. Dieselbe Gr\xF6\xDFenordnung wie `max-h-[300px]` im\n Labs-Browser. */\n max-height: 19rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__raster {\n display: grid;\n /* Vier Spalten auf schmalen Displays \u2014 der gemeldete Screenshot zeigte eine\n einzige Spalte gro\xDFer Karten. Breiter wird automatisch dichter. */\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__karte {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-surface);\n text-align: left;\n cursor: pointer;\n}\n\n/* Das fokussierte Werk ist klar markiert \u2014 sonst wei\xDF niemand, wessen Galerie\n der Inspector unten zeigt. */\n.szenen-bildwahl__karte.is-fokus {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n/* \u2500\u2500 Die Kachel selbst \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Der Platz steht, bevor das Bild da ist \u2014 sonst springt das Raster, sobald\n die erste Vorschau eintrifft. */\n.szenen-bildkachel {\n display: block;\n width: 100%;\n aspect-ratio: 1;\n overflow: hidden;\n background: var(--color-bg-app);\n}\n\n.szenen-bildkachel img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.szenen-bildkachel__leer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background: linear-gradient(135deg, var(--color-bg-app), var(--color-bg-surface));\n}\n\n/* R12.3: Eine fehlende Vorschau bleibt kachelgro\xDF. Vorher fiel die Karte auf\n ihren Prompttext zur\xFCck \u2014 und ein Prompt hat im echten Bestand mehrere\n hundert Zeichen. */\n.szenen-bildkachel__zeichen {\n font-size: 0.875rem;\n font-weight: 700;\n color: var(--color-text-secondary);\n opacity: 0.6;\n}\n\n/* Wie viele Varianten das Werk hat \u2014 klein und \xFCberlagernd, nie als\n dominanter Text. */\n.szenen-bildwahl__anzahl {\n position: absolute;\n top: 0.15rem;\n right: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n font-weight: 700;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* Eine Zeile, dann Schluss. Der Prompt eines Werkes ist oft mehrere hundert\n Zeichen lang und w\xE4re als Prim\xE4rdarstellung genau die Textw\xFCste, gegen die\n der Review antritt. */\n.szenen-bildwahl__kacheltitel {\n display: block;\n padding: 0.15rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.3;\n color: var(--color-text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Ebene 2: der Inspector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__inspector {\n /* Ohne `min-width: 0` gewinnt der Inhalt: Der W\xE4hler ist Element eines\n Grids (`.szenen-form`), und dort ist `min-width: auto` die Vorgabe. Der\n Streifen unten ist rund 1000 px breit \u2014 gemessen am 2026-08-22 im echten\n Browser zog er die Auswahl von 374 px auf 1088 px auf und schob die\n ganze Seite waagerecht heraus, statt selbst zu scrollen. */\n min-width: 0;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n /* Sichtbar abgesetzt vom Browser: zwei Ebenen, nicht eine lange Liste. */\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__inspektorkopf {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bildwahl__werktitel {\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--color-text-primary);\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.szenen-bildwahl__zurueck {\n flex: 0 0 auto;\n font-size: 0.6875rem;\n padding: 0.15rem 0.5rem;\n}\n\n/* Der Streifen: waagerecht scrollend, feste kleine Kacheln \u2014 zw\xF6lf Varianten\n bleiben ein \xDCberblick statt zw\xF6lf Bildschirmseiten. */\n.szenen-bildwahl__streifen {\n /* Die Breite kommt vom Elter, nie vom Inhalt \u2014 sonst scrollt hier nichts. */\n max-width: 100%;\n display: flex;\n gap: var(--space-sm);\n margin: 0;\n padding: 0 0 0.25rem;\n list-style: none;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n\n.szenen-bildwahl__streifen > li {\n flex: 0 0 auto;\n width: 5rem;\n}\n\n.szenen-bildwahl__variante {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-app);\n cursor: pointer;\n}\n\n.szenen-bildwahl__variante.is-gewaehlt {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n.szenen-bildwahl__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Das Gew\xE4hlte \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__gewaehlt {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__gewaehlt li {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.5rem;\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n max-width: 100%;\n}\n\n/* Das gew\xE4hlte Bild wird best\xE4tigt, nicht nur benannt: eine kleine Vorschau\n neben dem Titel. */\n.szenen-bildwahl__gewaehlt .szenen-bildkachel {\n width: 2.25rem;\n flex: 0 0 auto;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__gewaehlt .szenen-bildwahl__kacheltitel {\n min-width: 0;\n max-width: 12rem;\n font-size: 0.75rem;\n}\n\n.szenen-beleg__kopf {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details + details {\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details > summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n/* \u2500\u2500 Die Fehlerrettung (Stufe 6.6, A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Sie steht im Fehlerblock und muss trotzdem als Werkzeug lesbar sein, nicht\n * als weitere Fehlermeldung. Deshalb abgesetzt wie der Folgelauf \u2014 und aus\n * demselben Grund: Beides sind Angebote, keine Ausk\xFCnfte.\n *\n * Der wichtigste optische Punkt ist die Trennung von Original und\n * Arbeitskopie. Das Original bekommt den ruhigen, \u201Efesten" Hintergrund der\n * Belege; die Kopie sieht aus wie ein normales Eingabefeld, weil sie eines\n * ist. */\n.szenen-rettung {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-rettung > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-rettung textarea[readonly] {\n background: var(--color-bg-surface);\n color: var(--color-text-secondary);\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung textarea:not([readonly]) {\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0;\n}\n\n/* Ein Befund tr\xE4gt Code, Bezug und Meldung. Der Code steht klein und\n * monospaced davor: Er ist die Kennung, nicht die Aussage. */\n.szenen-diagnose__liste {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-diagnose__liste code {\n font-size: 0.8125rem;\n}\n\n.szenen-diagnose__liste > li[data-severity=\'warning\'] {\n color: var(--color-text-secondary);\n}\n\n/* \u2500\u2500 Die Draft-Kette (Stufe 6.6, C4) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Hauptweg. Er steht nach den Einzel\xFCbernahmen und muss als das erkennbar\n * sein, was er ist: nicht eine weitere Option, sondern der k\xFCrzere Weg zum\n * selben Ziel. Deshalb durchgezogener Rahmen statt gestricheltem \u2014 er ist\n * Angebot **und** Empfehlung. */\n.szenen-kette {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-kette > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-kette ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0 0 var(--space-sm);\n padding-left: var(--space-md);\n}\n\n.szenen-kette code {\n font-size: 0.8125rem;\n}\n\n/* Ein Hinweis, der eine Bedingung erklaert \u2014 nicht rot, weil er kein Fehler\n ist, aber abgesetzt, weil er eine Folge hat. */\n.szenen-detail__hint {\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Stufe 6.7: Bilder am Prompt-Lauf (H0/H1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich tr\xE4gt zwei Dinge, die man nie verwechseln darf: den **Eingang**\n * (woraus der Prompt entstand) und den **Ausgang** (was daraus wurde). Sie\n * bekommen deshalb verschiedene Akzentkanten statt nur verschiedener\n * \xDCberschriften \u2014 wer schnell liest, sieht die Trennung, bevor er sie liest.\n */\n.szenen-bilder {\n display: grid;\n gap: var(--space-md);\n margin-top: var(--space-sm);\n}\n\n.szenen-bilder__eingang,\n.szenen-bilder__ausgang {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__eingang {\n border-left: 3px solid var(--color-accent-purple, var(--color-border));\n}\n\n.szenen-bilder__ausgang {\n border-left: 3px solid var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-bilder h5 {\n margin: 0;\n}\n\n.szenen-bilder__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: 0;\n align-items: baseline;\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-bilder__eingangsliste,\n.szenen-bilder__batchliste,\n.szenen-bilder__auftraege {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bilder__eingangsliste > li,\n.szenen-bilder__batchliste > li,\n.szenen-bilder__auftraege > li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n/* Ein unterbrochener Auftrag ist der Fall, f\xFCr den der Auftragssatz existiert.\n Er wird deshalb markiert \u2014 nicht rot, denn er ist kein Fehler, sondern eine\n offene Entscheidung. */\n.szenen-bilder__auftraege > li[data-status="started"] {\n border-left: 3px solid var(--color-warning);\n}\n\n.szenen-bilder__auftraege > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-bilder__auftraege > li[data-status="partial"] {\n border-left: 3px solid var(--color-accent-blue, var(--color-border));\n}\n\n.szenen-bilder__auftraege > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n/* Das Eingangsbild benutzt seit C3 dieselbe Kachel wie W\xE4hler und Galerie.\n Sie ist von Haus aus so breit wie ihr Platz \u2014 in dieser Zeile bekommt sie\n deshalb eine feste Gr\xF6sse, sonst sch\xF6be sie die Metadaten heraus. */\n.szenen-bilder__eingangsliste .szenen-bildkachel {\n flex: 0 0 auto;\n width: 4rem;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batch {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batchkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-bilder__herkunft {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n/* Review-Befund \u2014 ein transienter Bildfehler ist wiederholbar, kein\n endg\xFCltiges \u201Efehlt". Dezent wie ein Angebot, nicht wie ein Fehlerzustand. */\n.szenen-bilder__retry {\n justify-self: start;\n padding: 0.15rem 0.5rem;\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n/* C4 \u2014 die Provenienz vollst\xE4ndig, aber sekund\xE4r. */\n.szenen-bilder__technik > summary {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-bilder__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n}\n\n/* C5 \u2014 der Verlauf verdichtet, seine Ausreisser nicht. */\n.szenen-bilder__verlauf > summary {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n cursor: pointer;\n}\n\n.szenen-bilder__verlauf[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__auffaellig {\n margin: 0;\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n.szenen-bilder__auftragskennung {\n flex-basis: 100%;\n color: var(--color-text-tertiary);\n font-size: 0.6875rem;\n}\n\n/* \u2500\u2500 C1/C2 \u2014 Galerie und grosse Ansicht \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Das Raster w\xE4chst mit dem Platz: auf 390 px drei bis vier Kacheln je Reihe,\n auf dem Desktop entsprechend mehr. Vorher war jedes Bild eine Zeile aus\n Daumennagel plus vier Metadatenfeldern \u2014 vier Bilder ergaben vier Textzeilen\n und das Protokoll war die Hauptsache. */\n.szenen-bildgalerie {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));\n gap: var(--space-sm);\n min-width: 0;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildgalerie__kachel {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildgalerie__kachel:hover,\n.szenen-bildgalerie__kachel:focus-visible {\n border-color: var(--color-accent-teal, var(--color-text-primary));\n}\n\n.szenen-bildgalerie__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* Die grosse Ansicht liegt \xFCber allem und bringt ihren eigenen Grund mit \u2014\n `position: fixed` bezieht sich im Flow-Canvas auf dessen Rahmen, in React auf\n das Fenster; in beiden F\xE4llen genau das Gew\xFCnschte. */\n.szenen-bildviewer {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: grid;\n grid-template-rows: auto 1fr auto;\n gap: var(--space-sm);\n padding: var(--space-sm);\n /* Deckend, nicht get\xF6nt: Bei 0.92 schimmerte der Laufbereich durch und man\n las Prompttext quer \xFCber dem Bild \u2014 im Browser gemessen. */\n background: #000;\n}\n\n/* Titel, Z\xE4hlung, Schliessen \u2014 drei Spalten, damit ein langer Werktitel die\n Z\xE4hlung nicht in die n\xE4chste Zeile und \xFCber das Bild schiebt. */\n.szenen-bildviewer__kopf {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n gap: var(--space-sm);\n color: var(--color-text-primary);\n}\n\n.szenen-bildviewer__titel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n}\n\n.szenen-bildviewer__zaehler {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n}\n\n.szenen-bildviewer__buehne {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--space-sm);\n min-height: 0;\n min-width: 0;\n}\n\n/* Der Rahmen nimmt den Platz, der zwischen den Bl\xE4tterkn\xF6pfen \xFCbrig ist \u2014\n `min-width: 0`, damit er auch wirklich schrumpft statt sie hinauszuschieben. */\n.szenen-bildviewer__rahmen {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n\n.szenen-bildviewer__bild {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.szenen-bildviewer__leer {\n color: var(--color-text-secondary);\n text-align: center;\n}\n\n/* Die Bl\xE4tterkn\xF6pfe sind daumengross und liegen an den R\xE4ndern \u2014 auf einem\n Telefon ist der Rand erreichbar, die Bildmitte nicht. */\n.szenen-bildviewer__blaettern {\n flex: 0 0 auto;\n width: 2.75rem;\n height: 3.5rem;\n font-size: 1.75rem;\n line-height: 1;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildviewer__details {\n max-height: 8rem;\n overflow-y: auto;\n color: var(--color-text-secondary);\n}\n\n/* Die Aktionen stehen abgesetzt: Sie sind Angebote, keine Ausk\xFCnfte \u2014 dasselbe\n Muster wie beim Folgelauf und bei der Fehlerrettung. */\n/* D \u2014 die Aktionen stehen abgesetzt und standardm\xE4ssig kompakt: Sie sind\n Angebote, keine Ausk\xFCnfte. Vorher hielten beide Formulare dauerhaft offen und\n machten aus jedem Lauf zwei zus\xE4tzliche Bildschirmseiten. */\n.szenen-bilder__aktion {\n min-width: 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__aktion > summary {\n color: var(--color-text-primary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-bilder__aktion[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion h5 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-bilder__aktion ol {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n font-size: 0.8125rem;\n}\n\n/* Regelwerk-UX Schritt 16 \u2014 der ZIP-Sammelimport.\n *\n * \xA78: Der gemeldete Einsatz ist mobil. Deshalb **ein** Seedfeld statt einer\n * gestapelten Eingabe je Bild, und die Zuordnung Bild -> Seed als umbrechende\n * Zeile statt als Tabelle: Eine Tabelle waere auf 390 px abgeschnitten, und\n * abgeschnitten heisst hier, dass der Nutzer die Zuordnung nicht mehr pruefen\n * kann \u2014 genau die Pruefung, wegen der die Vorschau existiert. */\n.szenen-bilder__zip {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bilder__zipkopf {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipvorlagen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n margin: 0;\n}\n\n/* Eine laengere Liste bleibt scrollbar, statt die Sektion und damit den\n * Import-Knopf aus dem Blick zu schieben (\xA78, letzter Punkt). */\n.szenen-bilder__zipliste {\n max-height: 16rem;\n overflow-y: auto;\n}\n\n.szenen-bilder__zipliste > li {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-bilder__zipliste > li > span:first-child {\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipliste > li > span:last-child {\n color: var(--color-text-secondary);\n white-space: nowrap;\n}\n\n/* ==========================================================================\n STUFE 6.10 \u2014 ELEMENTEDITOR UND VERSIONSNAVIGATION\n ========================================================================== */\n\n/* \xA74.2 \u2014 vier Sachverhalte, vier Marken. Die Marken stehen in einer eigenen\n * Spalte, damit ANGEZEIGT + BEVORZUGT nebeneinander lesbar bleiben, statt sich\n * durch den Text zu schieben. */\n.szenen-detail__heads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-version__titel {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n margin-right: auto;\n}\n\n/* V17 \u2014 Kennungen bleiben vollst\xE4ndig sichtbar, aber optisch sekund\xE4r. */\n.szenen-version__id,\n.szenen-ref__id,\n.szenen-detail__field-key {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n overflow-wrap: anywhere;\n}\n\n.szenen-version__marken {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-version__marken > span {\n padding: 0.1rem 0.5rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.7rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n}\n\n.szenen-version--angezeigt {\n border-color: var(--color-accent-teal, #2dd4bf) !important;\n}\n\n/* V9 \u2014 der Basiswechsel ist eine Entscheidung, keine Nebenwirkung. */\n.szenen-detail__confirm {\n display: grid;\n gap: var(--space-sm);\n margin: var(--space-md) 0;\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-accent-amber, #f59e0b);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__confirm p {\n margin: 0;\n}\n\n/* Untergruppen von Objektfeldern (`prompt_render_settings`, `overrides`,\n `blender_render_settings`) \u2014 seit A1/A2 auf- und zuklappbar. Eine optionale\n Gruppe startet zu: Blender und `overrides` f\xFCllten auf einem 390-px-Display\n sonst mehrere Bildschirmseiten, obwohl die meisten Shots sie nie anfassen. */\n.szenen-detail__group {\n min-width: 0;\n margin: var(--space-md) 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group > summary {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__group[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n/* A2 \u2014 dass eine zugeklappte Gruppe schon Werte tr\xE4gt, muss man sehen, ohne\n sie zu \xF6ffnen. Sonst \xFCbersieht man eine gesetzte Blender-Ausgabe genau so\n lange, bis sie im Ergebnis auftaucht. */\n.szenen-detail__group-status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--color-text-tertiary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group[data-konfiguriert="ja"] > summary .szenen-detail__group-status {\n color: var(--color-text-primary);\n border-color: var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-detail__group-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n/* A3 \u2014 die Kernaussage steht am Feld, die Begr\xFCndung dahinter. Nicht als\n Tooltip: Auf einem Telefon gibt es kein Hover, und was dort unerreichbar ist,\n ist gestrichen \u2014 nur ohne dass es jemand entschieden h\xE4tte. */\n.szenen-detail__warum > summary {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-detail__warum > small {\n display: block;\n padding-top: var(--space-xs);\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__fieldrow {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n margin-bottom: var(--space-md);\n}\n\n/* A4 \u2014 der technische Feldname bleibt lesbar, tritt aber hinter das fachliche\n Label zur\xFCck. */\n.szenen-detail__field-label {\n color: var(--color-text-primary);\n}\n\n.szenen-detail__field {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-detail__field > span {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n}\n\n/* Mehrfachauswahl und Listeneditoren. */\n.szenen-feld__mehrfach,\n.szenen-feld__liste,\n.szenen-feld__reflist,\n.szenen-feld__ref,\n.szenen-feld__zielmodell,\n.szenen-feld__kamera {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-feld__mehrfach > label,\n.szenen-feld__null {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-feld__liste-zeile {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__reflist ol {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-feld__reflist li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n padding: var(--space-xs) var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \xA76.1 \u2014 Titel und Typ zuerst, Kennungen daneben. */\n.szenen-ref__anzeige {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-ref__typ {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-ref__warn {\n padding: 0.1rem 0.5rem;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n/* \xA76.5 \u2014 abgeleitete Angaben, ausdr\xFCcklich nur Anzeige. */\n.szenen-detail__derived {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__derived dl {\n display: grid;\n grid-template-columns: minmax(10rem, auto) 1fr;\n gap: var(--space-xs) var(--space-sm);\n margin: 0;\n}\n\n.szenen-detail__derived dt {\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__derived dd {\n margin: 0;\n}\n\n.szenen-feld__abgeleitet {\n margin: 0;\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n}\n\n/* Bereich `Prompts` \u2014 die herkunftsuebergreifende Sicht (P1-A).\n *\n * Die Eintraege selbst rendert `RundenEintrag` und tragen damit die\n * `szenen-runs__*`-Regeln. Hier stehen nur Rahmen und Kennzeichen; eine zweite\n * Laufdarstellung gibt es nicht, also braucht sie auch kein zweites Aussehen. */\n.szenen-prompts__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-prompts__heading {\n margin: 0;\n font-size: 1.05rem;\n margin-right: auto;\n}\n\n.szenen-prompts__filter {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__eintrag {\n list-style: none;\n margin-bottom: var(--space-md);\n}\n\n.szenen-prompts__lauf {\n padding-left: 0;\n}\n\n.szenen-prompts__herkunft {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-prompts__hinweis {\n font-style: italic;\n}\n\n.szenen-prompts__ausgeblendet {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n';
175
175
 
176
176
  // src/lib/utils.ts
177
177
  var getFormattedTimestamp = () => {
@@ -1656,7 +1656,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
1656
1656
  }
1657
1657
 
1658
1658
  // src/components/AvatarArchitectApp.tsx
1659
- var import_react50 = require("react");
1659
+ var import_react53 = require("react");
1660
1660
 
1661
1661
  // src/components/PromptTab.tsx
1662
1662
  var import_react15 = require("react");
@@ -2495,9 +2495,9 @@ function mergeSnapshotAndPending(snapshot, pending, bereitsImSnapshot = 0) {
2495
2495
  }
2496
2496
 
2497
2497
  // src/lib/hfEventLog.ts
2498
- function treeUrl(namespace, subdir = "") {
2498
+ function treeUrl(namespace, subdir = "", revision = "main") {
2499
2499
  const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
2500
- return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
2500
+ return `${HF_BASE}/api/datasets/${HF_REPO}/tree/${encodeURIComponent(revision)}${parts ? "/" + parts : ""}`;
2501
2501
  }
2502
2502
  var DROSSEL_VERSUCHE = 4;
2503
2503
  var DROSSEL_BASIS_MS = 400;
@@ -2530,8 +2530,8 @@ async function mitGeduld(anfrage) {
2530
2530
  }
2531
2531
  return letzte;
2532
2532
  }
2533
- async function hfListDir(namespace, subdir, token) {
2534
- const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
2533
+ async function hfListDir(namespace, subdir, token, revision = "main") {
2534
+ const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir, revision), {
2535
2535
  headers: { Authorization: `Bearer ${token}` }
2536
2536
  }));
2537
2537
  if (res.status === 404) return [];
@@ -2560,17 +2560,46 @@ function toBase64(text) {
2560
2560
  bytes.forEach((b) => binary += String.fromCharCode(b));
2561
2561
  return btoa(binary);
2562
2562
  }
2563
- async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
2563
+ var HfPreconditionFailedError = class extends Error {
2564
+ constructor(message) {
2565
+ super(message);
2566
+ this.name = "HfPreconditionFailed";
2567
+ }
2568
+ };
2569
+ async function hfRepoHead(token, revision = "main") {
2570
+ const res = await mitGeduld(() => holeMitZeitlimit(
2571
+ `${HF_BASE}/api/datasets/${HF_REPO}/revision/${encodeURIComponent(revision)}`,
2572
+ { headers: { Authorization: `Bearer ${token}` } }
2573
+ ));
2574
+ if (!res.ok) {
2575
+ throw new Error(`HF revision failed: ${res.status} ${res.statusText} (${revision})`);
2576
+ }
2577
+ const daten = await res.json();
2578
+ if (typeof daten.sha !== "string" || daten.sha.length === 0) {
2579
+ throw new Error(
2580
+ `HF revision ${revision}: die Antwort traegt kein "sha". Erhaltene Felder: ${Object.keys(daten || {}).join(", ") || "(keine)"}.`
2581
+ );
2582
+ }
2583
+ return daten.sha;
2584
+ }
2585
+ async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`, options) {
2586
+ const kopf = { summary, description: "" };
2587
+ if (options?.parentCommit) kopf.parentCommit = options.parentCommit;
2564
2588
  const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
2565
2589
  method: "POST",
2566
2590
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
2567
2591
  body: [
2568
- JSON.stringify({ key: "header", value: { summary, description: "" } }),
2592
+ JSON.stringify({ key: "header", value: kopf }),
2569
2593
  JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: toBase64(content) } })
2570
2594
  ].join("\n")
2571
2595
  });
2572
2596
  if (!res.ok) {
2573
2597
  const err = await res.text().catch(() => "");
2598
+ if (options?.parentCommit && (res.status === 412 || res.status === 409)) {
2599
+ throw new HfPreconditionFailedError(
2600
+ `HF commit abgelehnt: Der Branch hat sich seit ${options.parentCommit} bewegt (${res.status}). Es wurde nichts geschrieben.`
2601
+ );
2602
+ }
2574
2603
  throw new Error(`HF upload failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
2575
2604
  }
2576
2605
  }
@@ -2620,8 +2649,8 @@ async function mitBegrenzterParallelitaet(count, limit, task) {
2620
2649
  await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
2621
2650
  if (fehlerAufgetreten) throw ersterFehler;
2622
2651
  }
2623
- async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
2624
- const files = await hfListDir(namespace, "events", token);
2652
+ async function loadPendingEvents(namespace, token, sinceTs, knownPaths, revision = "main") {
2653
+ const files = await hfListDir(namespace, "events", token, revision);
2625
2654
  const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs && !knownPaths?.has(f.path)).sort((a, b) => a.path.localeCompare(b.path));
2626
2655
  if (!pending.length) return [];
2627
2656
  const raw = new Array(pending.length);
@@ -2666,7 +2695,13 @@ async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
2666
2695
  const suffix = options?.ordered ? `${String(++writeSeq % 1e6).padStart(6, "0")}${uuid}` : uuid;
2667
2696
  const filename = `${tsToFilename(ts)}_${suffix}.json`;
2668
2697
  const repoPath = `${namespace}events/${filename}`;
2669
- await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
2698
+ await hfUploadSmallFile(
2699
+ repoPath,
2700
+ JSON.stringify(event, null, 2),
2701
+ token,
2702
+ `Event: ${type}`,
2703
+ options?.parentCommit ? { parentCommit: options.parentCommit } : void 0
2704
+ );
2670
2705
  return event;
2671
2706
  }
2672
2707
 
@@ -6004,7 +6039,7 @@ function FlowWorkspacePicker({
6004
6039
  }
6005
6040
 
6006
6041
  // src/features/szenen/SzenenView.tsx
6007
- var import_react49 = require("react");
6042
+ var import_react52 = require("react");
6008
6043
 
6009
6044
  // src/lib/szenen/viewPort.ts
6010
6045
  function capabilitiesOf(port) {
@@ -6018,7 +6053,11 @@ function capabilitiesOf(port) {
6018
6053
  imageBatchHistory: typeof port.imageBatchHistory?.listBatchesForPromptRun === "function",
6019
6054
  imageOrderHistory: typeof port.imageBatchHistory?.listOrdersForPromptRun === "function",
6020
6055
  imageBatchWrite: typeof port.imageBatchWrite?.importBatch === "function",
6021
- imageGeneration: typeof port.imageGeneration?.startFromPromptRun === "function"
6056
+ imageGeneration: typeof port.imageGeneration?.startFromPromptRun === "function",
6057
+ imageJobs: typeof port.imageJobs?.claimAndRun === "function",
6058
+ // P1-A: die herkunftsuebergreifende Prompt-Sicht. Ein Host ohne sie
6059
+ // verliert nur diesen Bereich — nicht die drei bestehenden Einstiege.
6060
+ promptOverview: typeof port.runs.listAllRuns === "function"
6022
6061
  };
6023
6062
  }
6024
6063
  async function callOptional(capability, operation) {
@@ -9102,37 +9141,52 @@ function formatSchemaErrors(errors) {
9102
9141
  }
9103
9142
 
9104
9143
  // src/features/szenen/szenenViewModel.ts
9105
- var KEINE_BAUSTEINE = [
9106
- "scene",
9144
+ var BAUSTEIN_TYPES = [
9145
+ "scene_component",
9146
+ "style",
9147
+ "shot_concept",
9148
+ "prompt_fragment",
9149
+ "prompt_policy"
9150
+ ];
9151
+ var ERWEITERT_REIHENFOLGE = [
9107
9152
  "scene_setup",
9108
- "shot"
9153
+ "shot",
9154
+ "camera_preset",
9155
+ "camera_pattern",
9156
+ "target_model",
9157
+ "scene_derivation_policy",
9158
+ "sequence"
9109
9159
  ];
9110
- var BAUSTEIN_TYPES = SZENEN_ELEMENT_TYPES.filter((typ) => !KEINE_BAUSTEINE.includes(typ));
9160
+ var ERWEITERT_TYPES = (() => {
9161
+ const gehoertHierher = (typ) => typ !== "scene" && !BAUSTEIN_TYPES.includes(typ);
9162
+ const bekannt = ERWEITERT_REIHENFOLGE.filter(
9163
+ (typ) => SZENEN_ELEMENT_TYPES.includes(typ) && gehoertHierher(typ)
9164
+ );
9165
+ const rest = SZENEN_ELEMENT_TYPES.filter(
9166
+ (typ) => gehoertHierher(typ) && !bekannt.includes(typ)
9167
+ );
9168
+ return [...bekannt, ...rest];
9169
+ })();
9111
9170
  var ALLE_BEREICHE = [
9112
9171
  {
9113
- id: "derive",
9114
- title: "0 \xB7 Rekonstruktion",
9115
- description: "Aus einem Bild oder Prompt einen Szenenentwurf vorschlagen lassen."
9116
- },
9117
- {
9118
- id: "elements",
9119
- title: "1 \xB7 Bausteine",
9120
- description: "Figuren, Orte, Stile und Regeln f\xFCr sp\xE4tere Bildprompts pflegen."
9172
+ id: "scene",
9173
+ title: "Szenen",
9174
+ description: "Szenen anlegen, aus Bild oder Text ableiten und daraus Prompts starten."
9121
9175
  },
9122
9176
  {
9123
- id: "scene",
9124
- title: "2 \xB7 Szene",
9125
- description: "Aus Bausteinen eine konkrete Szene und ihre Beziehungen bauen."
9177
+ id: "prompts",
9178
+ title: "Prompts",
9179
+ description: "Alle erzeugten Prompts mit ihren Bildern \u2014 unabhaengig davon, ueber welchen Weg sie entstanden sind."
9126
9180
  },
9127
9181
  {
9128
- id: "setup",
9129
- title: "3 \xB7 Bildaufbau",
9130
- description: "Stil, Komposition und Kamera f\xFCr das sp\xE4tere Bild festlegen."
9182
+ id: "elements",
9183
+ title: "Bausteine",
9184
+ description: "Figuren, Orte, Stile, Prompt-Bausteine und Prompt-Regeln pflegen."
9131
9185
  },
9132
9186
  {
9133
- id: "shot",
9134
- title: "4 \xB7 Prompt",
9135
- description: "Bildprompt erzeugen, Varianten vergleichen und als Item speichern."
9187
+ id: "advanced",
9188
+ title: "Erweitert",
9189
+ description: "Optionale Pr\xE4zisierung: Bildaufbau, Shot, Kameras, Zielmodelle, Regeln, Sequenzen."
9136
9190
  },
9137
9191
  {
9138
9192
  id: "media",
@@ -9142,16 +9196,12 @@ var ALLE_BEREICHE = [
9142
9196
  ];
9143
9197
  function areasFor(port) {
9144
9198
  const faehig = capabilitiesOf(port);
9145
- return ALLE_BEREICHE.filter((bereich) => {
9146
- if (bereich.id === "media") return faehig.media;
9147
- if (bereich.id === "derive") return faehig.derivation;
9148
- return true;
9149
- });
9199
+ return ALLE_BEREICHE.filter((bereich) => bereich.id !== "media" || faehig.media);
9150
9200
  }
9151
9201
  function resolveArea(port, gewuenscht) {
9152
9202
  const verfuegbar = areasFor(port);
9153
9203
  if (gewuenscht && verfuegbar.some((b) => b.id === gewuenscht)) return gewuenscht;
9154
- return verfuegbar.find((b) => b.id === "elements")?.id ?? verfuegbar[0].id;
9204
+ return verfuegbar.find((b) => b.id === "scene")?.id ?? verfuegbar[0].id;
9155
9205
  }
9156
9206
  function noticeFor(ergebnis, was) {
9157
9207
  switch (ergebnis.kind) {
@@ -9165,13 +9215,13 @@ function noticeFor(ergebnis, was) {
9165
9215
  };
9166
9216
  }
9167
9217
  case "rejected": {
9168
- const fehler4 = ergebnis.errors ?? [];
9218
+ const fehler5 = ergebnis.errors ?? [];
9169
9219
  const ungeprueft = ergebnis.unchecked ?? [];
9170
9220
  return {
9171
9221
  tone: "rejected",
9172
- text: `${was}: abgelehnt \u2014 ${fehler4.length} Befund${fehler4.length === 1 ? "" : "e"}.`,
9222
+ text: `${was}: abgelehnt \u2014 ${fehler5.length} Befund${fehler5.length === 1 ? "" : "e"}.`,
9173
9223
  details: [
9174
- ...fehler4.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
9224
+ ...fehler5.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
9175
9225
  // Nicht verschweigen: was nicht geprüft wurde, ist nicht in Ordnung,
9176
9226
  // sondern ungeprüft.
9177
9227
  ...ungeprueft.map((u) => `ungepr\xFCft: ${u}`)
@@ -9192,7 +9242,7 @@ function noticeFor(ergebnis, was) {
9192
9242
  }
9193
9243
 
9194
9244
  // src/features/szenen/areas/ElementsArea.tsx
9195
- var import_react44 = require("react");
9245
+ var import_react45 = require("react");
9196
9246
 
9197
9247
  // src/features/szenen/areas/usePortQuery.ts
9198
9248
  var import_react33 = require("react");
@@ -9224,7 +9274,7 @@ function usePortQuery(frage, abhaengigkeiten) {
9224
9274
  }
9225
9275
 
9226
9276
  // src/features/szenen/areas/ElementDetail.tsx
9227
- var import_react42 = require("react");
9277
+ var import_react43 = require("react");
9228
9278
 
9229
9279
  // src/features/szenen/elementLabels.ts
9230
9280
  var LABELS = {
@@ -12671,7 +12721,7 @@ var ERKLAERUNG = {
12671
12721
  };
12672
12722
  var CapabilityPanel = ({ port, elementId, setupPayload }) => {
12673
12723
  const [bericht, setBericht] = (0, import_react37.useState)(null);
12674
- const [fehler4, setFehler] = (0, import_react37.useState)(null);
12724
+ const [fehler5, setFehler] = (0, import_react37.useState)(null);
12675
12725
  (0, import_react37.useEffect)(() => {
12676
12726
  let abgemeldet = false;
12677
12727
  setBericht(null);
@@ -12693,10 +12743,10 @@ var CapabilityPanel = ({ port, elementId, setupPayload }) => {
12693
12743
  abgemeldet = true;
12694
12744
  };
12695
12745
  }, [port, elementId, setupPayload]);
12696
- if (fehler4) {
12746
+ if (fehler5) {
12697
12747
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("p", { "data-testid": "capability-fehler", role: "alert", children: [
12698
12748
  "F\xE4higkeitsbericht nicht erstellbar: ",
12699
- fehler4
12749
+ fehler5
12700
12750
  ] });
12701
12751
  }
12702
12752
  if (!bericht) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { "data-testid": "capability-laden", children: "F\xE4higkeitsbericht wird erstellt \u2026" });
@@ -12932,19 +12982,7 @@ var VersionNavigator = ({
12932
12982
  };
12933
12983
 
12934
12984
  // src/features/szenen/areas/RunsPanel.tsx
12935
- var import_react41 = require("react");
12936
-
12937
- // src/lib/szenen/itemDerivation.ts
12938
- var SZENEN_ITEM_PREFIX = "szn-";
12939
- function itemIdForRun(runId) {
12940
- return `${SZENEN_ITEM_PREFIX}${runId}`;
12941
- }
12942
- function roundOfRun(run, runsOfShot) {
12943
- return runsOfShot.findIndex((kandidat) => kandidat.run_id === run.run_id) + 1;
12944
- }
12945
-
12946
- // src/features/szenen/areas/RunImagesPanel.tsx
12947
- var import_react40 = __toESM(require("react"));
12985
+ var import_react42 = require("react");
12948
12986
 
12949
12987
  // src/lib/szenen/elementEvents.ts
12950
12988
  var SZENEN_EVENT_PREFIX = "szenen.";
@@ -13063,7 +13101,60 @@ var SZENEN_EVENT_TYPES = {
13063
13101
  * Der Elementreducer ignoriert auch diesen Typ (`default:`-Zweig); gelesen
13064
13102
  * wird mit `collectImageBatches` (`imageBatchStore.ts`).
13065
13103
  */
13066
- imageBatchPersisted: "szenen.image_batch_persisted"
13104
+ imageBatchPersisted: "szenen.image_batch_persisted",
13105
+ /**
13106
+ * Ein vorbereiteter Promptvorgang — der Durchstich „Szene → ChatGPT → Lauf".
13107
+ *
13108
+ * Der bestehende Promptpfad ruft das Modell selbst
13109
+ * (`runPromptDerivation()`); ein aufrufendes Chatmodell formuliert dagegen
13110
+ * **zwischen** zwei Werkzeugaufrufen. Damit zerfaellt der eine Vorgang in
13111
+ * zwei, und der zweite braucht genau den Stand, den der erste eingefroren
13112
+ * hat — sonst loeste der Abschluss die inzwischen geaenderte Szene neu auf
13113
+ * und der Lauf behauptete eine Herkunft, die er nicht hat (D-22).
13114
+ *
13115
+ * Deshalb ein eigenes Ereignis und **kein** fluechtiger Prozesszustand: Der
13116
+ * Snapshot ueberlebt Neustart, Hostwechsel und Sitzungsende, weil er im
13117
+ * selben autoritativen Log liegt wie der Lauf, der daraus entsteht.
13118
+ *
13119
+ * Der Elementreducer ignoriert diesen Typ (`default:`-Zweig); gelesen wird
13120
+ * mit `collectPromptOperations` (`scenePromptOperation.ts`).
13121
+ */
13122
+ promptOperationPrepared: "szenen.prompt_operation_prepared",
13123
+ /**
13124
+ * Ein Bildauftrag wartet auf einen ausfuehrenden Flow-Host.
13125
+ *
13126
+ * Die vorhandene Auftragshistorie (`imageGenerationStarted`) beginnt
13127
+ * **unmittelbar vor** dem Provideraufruf und kennt deshalb keinen wartenden
13128
+ * Auftrag. Genau der entsteht hier: Ein Chatmodell beauftragt ueber MCP ein
13129
+ * Bild, der Auftrag wird dauerhaft abgelegt, und der Werkzeugaufruf endet
13130
+ * sofort mit seiner Kennung — er haelt keine Antwort minutenlang offen.
13131
+ *
13132
+ * Traegt **keinen** Prompt: Der steht im referenzierten Lauf, und ihn hier
13133
+ * zu kopieren waere eine zweite Wahrheit ueber denselben eingefrorenen Text
13134
+ * (D-103).
13135
+ */
13136
+ imageJobRequested: "szenen.image_job_requested",
13137
+ /**
13138
+ * Ein Flow-Host beansprucht einen wartenden Bildauftrag.
13139
+ *
13140
+ * Ein append-only Log kennt kein Lock: Zwei Tabs koennen beide anhaengen.
13141
+ * Deshalb ist die Uebernahme **nicht** der Append, sondern das Ergebnis
13142
+ * einer deterministischen Schiedsregel darueber (`winningClaimOf`) — der
13143
+ * erste Anspruch in Logreihenfolge gewinnt, und jeder Host liest nach
13144
+ * seinem Append erneut, bevor er einen bezahlten Provideraufruf startet.
13145
+ * Ein unterlegener Anspruch bleibt sichtbar stehen; er wird nicht geloescht,
13146
+ * weil das Log das nicht kann und weil der Versuch ein Beleg ist.
13147
+ */
13148
+ imageJobClaimed: "szenen.image_job_claimed",
13149
+ /**
13150
+ * Der Bildauftrag ist erledigt — mit dem Ausgang, den der Host gesehen hat.
13151
+ *
13152
+ * Getrennt von `imageGenerationFinished`: Jenes gehoert zum bezahlten
13153
+ * Provideraufruf, dieses zur Zustellung. Ein Auftrag kann fehlschlagen,
13154
+ * **bevor** ein Provider gerufen wurde (abgelehntes Profil), und dann gibt
13155
+ * es kein `imageGenerationFinished`, das den Wartenden erloesen wuerde.
13156
+ */
13157
+ imageJobSettled: "szenen.image_job_settled"
13067
13158
  };
13068
13159
  function sortForReplay(events) {
13069
13160
  return [...events].sort((a, b) => {
@@ -13324,6 +13415,144 @@ function collectPromptRuns(events) {
13324
13415
  function findPromptRun(events, runId) {
13325
13416
  return collectPromptRuns(events).find((run) => run.run_id === runId) ?? null;
13326
13417
  }
13418
+ var istKennung = (wert) => typeof wert === "string" && wert.trim().length > 0;
13419
+ function groupOf(run) {
13420
+ const quelle = sourceOf(run);
13421
+ if (quelle.kind === "published") {
13422
+ return istKennung(quelle.shot_version_id) ? {
13423
+ kind: "published",
13424
+ key: `published:${quelle.shot_version_id}`,
13425
+ shot_version_id: quelle.shot_version_id
13426
+ } : {
13427
+ kind: "unresolved",
13428
+ key: `run:${run.run_id}`,
13429
+ reason: `Der Lauf ${run.run_id} gilt als ver\xF6ffentlichte Herkunft, tr\xE4gt aber keine shot_version_id. Er steht deshalb f\xFCr sich; eine Zuordnung w\xE4re geraten.`
13430
+ };
13431
+ }
13432
+ if (quelle.kind === "draft_chain") {
13433
+ return istKennung(quelle.chain_id) ? { kind: "draft_chain", key: `chain:${quelle.chain_id}`, chain_id: quelle.chain_id } : {
13434
+ kind: "unresolved",
13435
+ key: `run:${run.run_id}`,
13436
+ reason: `Der Lauf ${run.run_id} stammt aus einer Draft-Kette ohne chain_id. Er steht deshalb f\xFCr sich; eine Zuordnung w\xE4re geraten.`
13437
+ };
13438
+ }
13439
+ const elementId = quelle.scene?.element_id;
13440
+ return istKennung(elementId) ? { kind: "scene_direct", key: `scene:${elementId}`, scene_element_id: elementId } : {
13441
+ kind: "unresolved",
13442
+ key: `run:${run.run_id}`,
13443
+ reason: `Der Lauf ${run.run_id} entstand direkt aus einer Szene, nennt aber kein Szenen-Element. Er steht deshalb f\xFCr sich; eine Zuordnung w\xE4re geraten.`
13444
+ };
13445
+ }
13446
+ function groupKeyOf(run) {
13447
+ return groupOf(run).key;
13448
+ }
13449
+ function siblingsOf(run, alle) {
13450
+ const schluessel = groupKeyOf(run);
13451
+ return alle.filter((kandidat) => groupKeyOf(kandidat) === schluessel);
13452
+ }
13453
+ function namingElementOf(run) {
13454
+ const quelle = sourceOf(run);
13455
+ if (quelle.kind === "scene_direct") {
13456
+ return istKennung(quelle.scene?.element_id) ? quelle.scene.element_id : null;
13457
+ }
13458
+ if (quelle.kind === "draft_chain") {
13459
+ const szene = (quelle.nodes ?? []).find((knoten) => knoten?.type === "scene");
13460
+ if (szene && istKennung(szene.element_id)) return szene.element_id;
13461
+ const shot = (quelle.nodes ?? []).find((knoten) => knoten?.type === "shot");
13462
+ if (shot && istKennung(shot.element_id)) return shot.element_id;
13463
+ }
13464
+ return istKennung(run.shot_element_id) ? run.shot_element_id : null;
13465
+ }
13466
+ function contextOfRun(run) {
13467
+ const kette = run.input_payload?.context_chain;
13468
+ if (!Array.isArray(kette) || kette.length === 0) return void 0;
13469
+ const erstes = kette[0];
13470
+ if (erstes === null) return null;
13471
+ return istKennung(erstes) ? erstes : void 0;
13472
+ }
13473
+ function selectPromptRuns(alle, filter = {}) {
13474
+ const status = filter.status ?? "succeeded";
13475
+ const nachKontext = filter.contextId !== void 0;
13476
+ const runs = [];
13477
+ let hiddenByStatus = 0;
13478
+ let hiddenByContext = 0;
13479
+ let withoutContext = 0;
13480
+ for (const run of alle) {
13481
+ if (status !== "all" && run.status !== status) {
13482
+ hiddenByStatus += 1;
13483
+ continue;
13484
+ }
13485
+ if (nachKontext) {
13486
+ const kontext = contextOfRun(run);
13487
+ if (kontext === void 0) {
13488
+ withoutContext += 1;
13489
+ continue;
13490
+ }
13491
+ if (kontext !== filter.contextId) {
13492
+ hiddenByContext += 1;
13493
+ continue;
13494
+ }
13495
+ }
13496
+ runs.push(run);
13497
+ }
13498
+ return { runs, hiddenByStatus, hiddenByContext, withoutContext };
13499
+ }
13500
+
13501
+ // src/lib/szenen/itemDerivation.ts
13502
+ var SZENEN_ITEM_PREFIX = "szn-";
13503
+ function itemIdForRun(runId) {
13504
+ return `${SZENEN_ITEM_PREFIX}${runId}`;
13505
+ }
13506
+ function roundOfRun(run, runsOfShot) {
13507
+ return runsOfShot.findIndex((kandidat) => kandidat.run_id === run.run_id) + 1;
13508
+ }
13509
+
13510
+ // src/features/szenen/areas/useLaufTitel.ts
13511
+ var import_react39 = require("react");
13512
+ function useLaufTitel(port, laeufe) {
13513
+ const [titelJeElement, setTitelJeElement] = (0, import_react39.useState)(
13514
+ () => /* @__PURE__ */ new Map()
13515
+ );
13516
+ const gebraucht = [...new Set(
13517
+ laeufe.map(namingElementOf).filter((id) => id !== null)
13518
+ )].sort();
13519
+ const schluessel = gebraucht.join(",");
13520
+ (0, import_react39.useEffect)(() => {
13521
+ let abgemeldet = false;
13522
+ const offen = gebraucht.filter((id) => !titelJeElement.has(id));
13523
+ if (offen.length === 0) return void 0;
13524
+ void (async () => {
13525
+ const gelesen = /* @__PURE__ */ new Map();
13526
+ for (const id of offen) {
13527
+ try {
13528
+ const ergebnis = await port.elements.getElement(id);
13529
+ gelesen.set(
13530
+ id,
13531
+ ergebnis.kind === "ok" && ergebnis.value ? ergebnis.value.metadata?.title ?? null : null
13532
+ );
13533
+ } catch {
13534
+ gelesen.set(id, null);
13535
+ }
13536
+ }
13537
+ if (abgemeldet) return;
13538
+ setTitelJeElement((bisher) => new Map([...bisher, ...gelesen]));
13539
+ })();
13540
+ return () => {
13541
+ abgemeldet = true;
13542
+ };
13543
+ }, [port, schluessel]);
13544
+ const jeLauf = /* @__PURE__ */ new Map();
13545
+ for (const lauf of laeufe) {
13546
+ const elementId = namingElementOf(lauf);
13547
+ if (!elementId) continue;
13548
+ const titel = titelJeElement.get(elementId);
13549
+ if (titel) jeLauf.set(lauf.run_id, titel);
13550
+ }
13551
+ return jeLauf;
13552
+ }
13553
+
13554
+ // src/features/szenen/areas/RunImagesPanel.tsx
13555
+ var import_react41 = __toESM(require("react"));
13327
13556
 
13328
13557
  // src/lib/szenen/seedListe.ts
13329
13558
  var DEFAULT_SEED_VORLAGE = {
@@ -13416,9 +13645,9 @@ async function leseBilderAusZip(datei) {
13416
13645
  );
13417
13646
  }
13418
13647
  const { default: JSZip3 } = await import("jszip");
13419
- const archiv = await JSZip3.loadAsync(datei).catch((fehler4) => {
13648
+ const archiv = await JSZip3.loadAsync(datei).catch((fehler5) => {
13420
13649
  throw new Error(
13421
- `${name} konnte nicht als ZIP gelesen werden: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
13650
+ `${name} konnte nicht als ZIP gelesen werden: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
13422
13651
  );
13423
13652
  });
13424
13653
  const pfade = [];
@@ -13450,7 +13679,7 @@ async function leseBilderAusZip(datei) {
13450
13679
  }
13451
13680
 
13452
13681
  // src/features/szenen/areas/Bildbausteine.tsx
13453
- var import_react39 = require("react");
13682
+ var import_react40 = require("react");
13454
13683
  var import_jsx_runtime35 = require("react/jsx-runtime");
13455
13684
  var Kachel = ({
13456
13685
  quelle,
@@ -13467,11 +13696,11 @@ var BildViewer = ({
13467
13696
  }) => {
13468
13697
  const anzahl = bilder.length;
13469
13698
  const bild = bilder[position];
13470
- const blaettern = (0, import_react39.useCallback)((schritt) => {
13699
+ const blaettern = (0, import_react40.useCallback)((schritt) => {
13471
13700
  if (anzahl === 0) return;
13472
13701
  onWechseln((position + schritt + anzahl) % anzahl);
13473
13702
  }, [anzahl, position, onWechseln]);
13474
- (0, import_react39.useEffect)(() => {
13703
+ (0, import_react40.useEffect)(() => {
13475
13704
  const taste = (e) => {
13476
13705
  if (e.key === "Escape") onSchliessen();
13477
13706
  else if (e.key === "ArrowLeft") blaettern(-1);
@@ -13554,8 +13783,8 @@ var BildViewer = ({
13554
13783
  );
13555
13784
  };
13556
13785
  var BildGalerie = ({ bilder, testId, satzTitel }) => {
13557
- const [offen, setOffen] = (0, import_react39.useState)(null);
13558
- (0, import_react39.useEffect)(() => {
13786
+ const [offen, setOffen] = (0, import_react40.useState)(null);
13787
+ (0, import_react40.useEffect)(() => {
13559
13788
  setOffen((vorher) => vorher !== null && vorher >= bilder.length ? null : vorher);
13560
13789
  }, [bilder.length]);
13561
13790
  if (bilder.length === 0) return null;
@@ -13611,10 +13840,11 @@ var AUFTRAG_TEXT = {
13611
13840
  };
13612
13841
  var RunImagesPanel = ({ port, lauf }) => {
13613
13842
  const faehig = capabilitiesOf(port);
13614
- const [nachladen, setNachladen] = (0, import_react40.useState)(0);
13843
+ const [nachladen, setNachladen] = (0, import_react41.useState)(0);
13615
13844
  const neuLesen = () => setNachladen((n) => n + 1);
13616
- const [erzeugenOffen, setErzeugenOffen] = (0, import_react40.useState)(null);
13617
- const [zuordnenOffen, setZuordnenOffen] = (0, import_react40.useState)(null);
13845
+ const [erzeugenOffen, setErzeugenOffen] = (0, import_react41.useState)(null);
13846
+ const [zuordnenOffen, setZuordnenOffen] = (0, import_react41.useState)(null);
13847
+ const [chatAuftraegeOffen, setChatAuftraegeOffen] = (0, import_react41.useState)(null);
13618
13848
  const quelle = sourceOf(lauf);
13619
13849
  const [wahlPhase] = usePortQuery(
13620
13850
  () => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
@@ -13629,10 +13859,17 @@ var RunImagesPanel = ({ port, lauf }) => {
13629
13859
  () => port.imageBatchHistory ? port.imageBatchHistory.listBatchesForPromptRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: [] }),
13630
13860
  [port, lauf.run_id, nachladen]
13631
13861
  );
13862
+ const [chatAuftraegePhase] = usePortQuery(
13863
+ () => port.imageJobs ? port.imageJobs.listJobs(lauf.run_id) : Promise.resolve({ kind: "ok", value: [] }),
13864
+ [port, lauf.run_id, nachladen]
13865
+ );
13632
13866
  const auftraege = auftraegePhase.status === "ready" ? auftraegePhase.value : [];
13633
13867
  const batches = batchesPhase.status === "ready" ? batchesPhase.value : [];
13868
+ const chatAuftraege = chatAuftraegePhase.status === "ready" ? chatAuftraegePhase.value : [];
13869
+ const offeneChatAuftraege = chatAuftraege.filter((a) => a.status === "pending").length;
13634
13870
  const auffaellige = auftraege.filter((a) => a.status !== "succeeded");
13635
13871
  const erzeugenAuf = erzeugenOffen ?? batches.length === 0;
13872
+ const chatAuftraegeAuf = chatAuftraegeOffen ?? offeneChatAuftraege > 0;
13636
13873
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "szenen-bilder", "data-testid": `bilder-${lauf.run_id}`, children: [
13637
13874
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { className: "szenen-bilder__eingang", "data-testid": `eingang-${lauf.run_id}`, children: [
13638
13875
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h5", { children: "Eingang \u2014 woraus dieser Prompt entstand" }),
@@ -13753,6 +13990,43 @@ var RunImagesPanel = ({ port, lauf }) => {
13753
13990
  }
13754
13991
  )
13755
13992
  ] }),
13993
+ faehig.imageJobs && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
13994
+ "details",
13995
+ {
13996
+ className: "szenen-bilder__aktion",
13997
+ "data-testid": `auftraege-chat-bereich-${lauf.run_id}`,
13998
+ open: chatAuftraegeAuf,
13999
+ children: [
14000
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
14001
+ "summary",
14002
+ {
14003
+ onClick: (e) => {
14004
+ e.preventDefault();
14005
+ setChatAuftraegeOffen(!chatAuftraegeAuf);
14006
+ },
14007
+ children: [
14008
+ "Bildauftraege aus dem Chat",
14009
+ offeneChatAuftraege > 0 ? ` (${offeneChatAuftraege} wartend)` : ""
14010
+ ]
14011
+ }
14012
+ ),
14013
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
14014
+ ChatAuftraegeAktion,
14015
+ {
14016
+ port,
14017
+ lauf,
14018
+ auftraege: chatAuftraege,
14019
+ fehler: chatAuftraegePhase.status === "error" ? chatAuftraegePhase.message : null,
14020
+ laedt: chatAuftraegePhase.status === "loading",
14021
+ onFertig: () => {
14022
+ setChatAuftraegeOffen(true);
14023
+ neuLesen();
14024
+ }
14025
+ }
14026
+ )
14027
+ ]
14028
+ }
14029
+ ),
13756
14030
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
13757
14031
  "details",
13758
14032
  {
@@ -13884,12 +14158,12 @@ var BestandsBild = ({ port, mediaRef }) => {
13884
14158
  ] });
13885
14159
  };
13886
14160
  function useBildQuellen(port, bilder) {
13887
- const [stand, setStand] = (0, import_react40.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() }));
13888
- const gehalten = (0, import_react40.useRef)(/* @__PURE__ */ new Map());
13889
- const gescheitert = (0, import_react40.useRef)(/* @__PURE__ */ new Map());
13890
- const [versuch, setVersuch] = (0, import_react40.useState)(0);
14161
+ const [stand, setStand] = (0, import_react41.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() }));
14162
+ const gehalten = (0, import_react41.useRef)(/* @__PURE__ */ new Map());
14163
+ const gescheitert = (0, import_react41.useRef)(/* @__PURE__ */ new Map());
14164
+ const [versuch, setVersuch] = (0, import_react41.useState)(0);
13891
14165
  const schluessel = bilder.map((b) => `${b.media_ref}|${b.mime_type ?? ""}`).join(" ");
13892
- (0, import_react40.useEffect)(() => {
14166
+ (0, import_react41.useEffect)(() => {
13893
14167
  const bestand = port.imageLibrary;
13894
14168
  if (!bestand) return void 0;
13895
14169
  let abgebrochen = false;
@@ -13936,7 +14210,7 @@ function useBildQuellen(port, bilder) {
13936
14210
  erneutVersuchen: () => setVersuch((n) => n + 1)
13937
14211
  };
13938
14212
  }
13939
- var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
14213
+ var BildDetails = ({ bild, fehler: fehler5, aufloesbar }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
13940
14214
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: bild.media_ref }),
13941
14215
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { children: [
13942
14216
  "Position ",
@@ -13949,7 +14223,7 @@ var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ (0,
13949
14223
  "\xD7",
13950
14224
  bild.height
13951
14225
  ] }) : null,
13952
- fehler4 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("em", { "data-testid": `batchbild-fehler-${bild.media_ref}`, children: fehler4 }),
14226
+ fehler5 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("em", { "data-testid": `batchbild-fehler-${bild.media_ref}`, children: fehler5 }),
13953
14227
  !aufloesbar && // Capability-Grenze, kein Fehler: Dieser Host kann Batches lesen,
13954
14228
  // aber keine Bilder auflösen (E). Kein erfundener Fallback auf eine
13955
14229
  // Serverroute (A6).
@@ -13965,18 +14239,18 @@ var HERKUNFT_KURZ = {
13965
14239
  external_import: "extern importiert"
13966
14240
  };
13967
14241
  var BatchKachel = ({ port, batch }) => {
13968
- const { quellen, fehler: fehler4, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
14242
+ const { quellen, fehler: fehler5, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
13969
14243
  const aufloesbar = Boolean(port.imageLibrary);
13970
14244
  const bilder = batch.images.map((bild) => ({
13971
14245
  id: bild.media_ref,
13972
14246
  quelle: quellen.get(bild.media_ref) ?? null,
13973
14247
  titel: `Bild ${bild.position + 1}`,
13974
- ...fehler4.get(bild.media_ref) ? { leerText: "Nicht ladbar" } : {},
14248
+ ...fehler5.get(bild.media_ref) ? { leerText: "Nicht ladbar" } : {},
13975
14249
  details: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
13976
14250
  BildDetails,
13977
14251
  {
13978
14252
  bild,
13979
- fehler: fehler4.get(bild.media_ref) ?? null,
14253
+ fehler: fehler5.get(bild.media_ref) ?? null,
13980
14254
  aufloesbar
13981
14255
  }
13982
14256
  )
@@ -14025,7 +14299,7 @@ var BatchKachel = ({ port, batch }) => {
14025
14299
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { children: [
14026
14300
  "Batches",
14027
14301
  " ",
14028
- batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react40.default.Fragment, { children: [
14302
+ batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_react41.default.Fragment, { children: [
14029
14303
  i > 0 ? ", " : "",
14030
14304
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: h.generation_batch_id }),
14031
14305
  ` (${HERKUNFT_KURZ[h.source_kind]})`
@@ -14036,7 +14310,7 @@ var BatchKachel = ({ port, batch }) => {
14036
14310
  BildDetails,
14037
14311
  {
14038
14312
  bild,
14039
- fehler: fehler4.get(bild.media_ref) ?? null,
14313
+ fehler: fehler5.get(bild.media_ref) ?? null,
14040
14314
  aufloesbar
14041
14315
  }
14042
14316
  ) }, bild.media_ref)) })
@@ -14054,11 +14328,11 @@ var ProfilBefunde = ({ wahl, runId }) => {
14054
14328
  ] });
14055
14329
  };
14056
14330
  var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
14057
- const [modellWahl, setModellWahl] = (0, import_react40.useState)(null);
14058
- const [anzahlText, setAnzahlText] = (0, import_react40.useState)("1");
14059
- const [seitenverhaeltnis, setSeitenverhaeltnis] = (0, import_react40.useState)("");
14060
- const [laeuft, setLaeuft] = (0, import_react40.useState)(false);
14061
- const [notiz, setNotiz] = (0, import_react40.useState)(null);
14331
+ const [modellWahl, setModellWahl] = (0, import_react41.useState)(null);
14332
+ const [anzahlText, setAnzahlText] = (0, import_react41.useState)("1");
14333
+ const [seitenverhaeltnis, setSeitenverhaeltnis] = (0, import_react41.useState)("");
14334
+ const [laeuft, setLaeuft] = (0, import_react41.useState)(false);
14335
+ const [notiz, setNotiz] = (0, import_react41.useState)(null);
14062
14336
  if (!port.imageGeneration) {
14063
14337
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { "data-testid": `erzeugen-unsupported-${lauf.run_id}`, children: "Dieser Host erzeugt keine Bilder \u2014 entweder fehlt der Providerzugang oder der gesicherte Speicherweg. Externe Bilder lassen sich trotzdem zuordnen." });
14064
14338
  }
@@ -14235,23 +14509,23 @@ var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
14235
14509
  };
14236
14510
  var DEFAULT_VORLAGE_KEY = "__default__";
14237
14511
  var ZuordnenAktion = ({ port, lauf, onFertig }) => {
14238
- const [dateien, setDateien] = (0, import_react40.useState)([]);
14239
- const [seeds, setSeeds] = (0, import_react40.useState)({});
14240
- const [zip, setZip] = (0, import_react40.useState)(null);
14241
- const [zipLaedt, setZipLaedt] = (0, import_react40.useState)(false);
14242
- const [seedListe, setSeedListe] = (0, import_react40.useState)("");
14243
- const [vorlagen, setVorlagen] = (0, import_react40.useState)([DEFAULT_SEED_VORLAGE]);
14244
- const [vorlageKey, setVorlageKey] = (0, import_react40.useState)(DEFAULT_VORLAGE_KEY);
14245
- const [vorlagenStand, setVorlagenStand] = (0, import_react40.useState)(0);
14246
- const [vorlageBenennen, setVorlageBenennen] = (0, import_react40.useState)(false);
14247
- const [vorlageName, setVorlageName] = (0, import_react40.useState)("");
14248
- const [modell, setModell] = (0, import_react40.useState)("");
14249
- const [itemId, setItemId] = (0, import_react40.useState)("");
14250
- const [laeuft, setLaeuft] = (0, import_react40.useState)(false);
14251
- const [notiz, setNotiz] = (0, import_react40.useState)(null);
14252
- const auswahlMarke = (0, import_react40.useRef)(0);
14512
+ const [dateien, setDateien] = (0, import_react41.useState)([]);
14513
+ const [seeds, setSeeds] = (0, import_react41.useState)({});
14514
+ const [zip, setZip] = (0, import_react41.useState)(null);
14515
+ const [zipLaedt, setZipLaedt] = (0, import_react41.useState)(false);
14516
+ const [seedListe, setSeedListe] = (0, import_react41.useState)("");
14517
+ const [vorlagen, setVorlagen] = (0, import_react41.useState)([DEFAULT_SEED_VORLAGE]);
14518
+ const [vorlageKey, setVorlageKey] = (0, import_react41.useState)(DEFAULT_VORLAGE_KEY);
14519
+ const [vorlagenStand, setVorlagenStand] = (0, import_react41.useState)(0);
14520
+ const [vorlageBenennen, setVorlageBenennen] = (0, import_react41.useState)(false);
14521
+ const [vorlageName, setVorlageName] = (0, import_react41.useState)("");
14522
+ const [modell, setModell] = (0, import_react41.useState)("");
14523
+ const [itemId, setItemId] = (0, import_react41.useState)("");
14524
+ const [laeuft, setLaeuft] = (0, import_react41.useState)(false);
14525
+ const [notiz, setNotiz] = (0, import_react41.useState)(null);
14526
+ const auswahlMarke = (0, import_react41.useRef)(0);
14253
14527
  const zipAktiv = zip !== null;
14254
- (0, import_react40.useEffect)(() => {
14528
+ (0, import_react41.useEffect)(() => {
14255
14529
  if (!zipAktiv) return;
14256
14530
  let abgebrochen = false;
14257
14531
  void (async () => {
@@ -14305,11 +14579,11 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
14305
14579
  setSeedListe(DEFAULT_SEED_VORLAGE.seeds);
14306
14580
  setVorlageKey(DEFAULT_VORLAGE_KEY);
14307
14581
  }
14308
- } catch (fehler4) {
14582
+ } catch (fehler5) {
14309
14583
  if (veraltet()) return;
14310
14584
  setNotiz({
14311
14585
  tone: "error",
14312
- text: fehler4 instanceof Error ? fehler4.message : String(fehler4)
14586
+ text: fehler5 instanceof Error ? fehler5.message : String(fehler5)
14313
14587
  });
14314
14588
  }
14315
14589
  if (veraltet()) return;
@@ -14328,10 +14602,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
14328
14602
  setNotiz(
14329
14603
  ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
14330
14604
  );
14331
- } catch (fehler4) {
14605
+ } catch (fehler5) {
14332
14606
  setNotiz({
14333
14607
  tone: "error",
14334
- text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
14608
+ text: `Externe Bilder importieren: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
14335
14609
  });
14336
14610
  }
14337
14611
  setLaeuft(false);
@@ -14353,10 +14627,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
14353
14627
  ...seed !== void 0 ? { seed } : {}
14354
14628
  });
14355
14629
  }
14356
- } catch (fehler4) {
14630
+ } catch (fehler5) {
14357
14631
  setNotiz({
14358
14632
  tone: "error",
14359
- text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
14633
+ text: `Externe Bilder importieren: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
14360
14634
  });
14361
14635
  return;
14362
14636
  }
@@ -14692,17 +14966,112 @@ function readAsBase64(datei) {
14692
14966
  leser.readAsDataURL(datei);
14693
14967
  });
14694
14968
  }
14969
+ var CHAT_AUFTRAG_TEXT = {
14970
+ pending: "wartet auf \xDCbernahme",
14971
+ // Wie bei `started` (D-119): laufend und abgebrochen sind aus dem Log
14972
+ // nicht unterscheidbar, und es gibt keinen automatischen Retry, der die
14973
+ // Unterscheidung bräuchte.
14974
+ claimed: "\xFCbernommen (l\xE4uft oder unterbrochen)",
14975
+ succeeded: "erfolgreich",
14976
+ partial: "teilweise",
14977
+ failed: "fehlgeschlagen",
14978
+ rejected: "abgelehnt \u2014 es wurde nichts ausgef\xFChrt"
14979
+ };
14980
+ var ChatAuftraegeAktion = ({
14981
+ port,
14982
+ lauf,
14983
+ auftraege,
14984
+ fehler: fehler5,
14985
+ laedt,
14986
+ onFertig
14987
+ }) => {
14988
+ const [laufend, setLaufend] = (0, import_react41.useState)(null);
14989
+ const [notiz, setNotiz] = (0, import_react41.useState)(null);
14990
+ async function uebernehmen(jobId) {
14991
+ if (!port.imageJobs) return;
14992
+ setLaufend(jobId);
14993
+ setNotiz(null);
14994
+ try {
14995
+ const ergebnis = await port.imageJobs.claimAndRun(jobId);
14996
+ if (ergebnis.kind === "ok") {
14997
+ setNotiz({
14998
+ tone: ergebnis.job.status === "succeeded" ? "ok" : "rejected",
14999
+ text: `Auftrag ${jobId}: ${CHAT_AUFTRAG_TEXT[ergebnis.job.status]}.` + (ergebnis.job.diagnostic ? ` ${ergebnis.job.diagnostic}` : "")
15000
+ });
15001
+ } else if (ergebnis.kind === "lost") {
15002
+ setNotiz({ tone: "rejected", text: ergebnis.message });
15003
+ } else {
15004
+ setNotiz({ tone: "error", text: ergebnis.message });
15005
+ }
15006
+ } catch (e) {
15007
+ setNotiz({
15008
+ tone: "error",
15009
+ text: `Die \xDCbernahme von Auftrag ${jobId} ist technisch gescheitert: ` + (e instanceof Error ? e.message : String(e))
15010
+ });
15011
+ } finally {
15012
+ setLaufend(null);
15013
+ onFertig();
15014
+ }
15015
+ }
15016
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "szenen-bilder__aktion-inhalt", "data-testid": `chat-auftraege-${lauf.run_id}`, children: [
15017
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "szenen-area__lead", children: "Auftr\xE4ge, die \xFCber den MCP aus dem Chat kamen. Der Prompt wird nicht kopiert \u2014 ausgef\xFChrt wird der eingefrorene Prompt dieses Laufs." }),
15018
+ fehler5 && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("p", { role: "alert", "data-testid": `chat-auftraege-fehler-${lauf.run_id}`, children: [
15019
+ "Bildauftr\xE4ge aus dem Chat konnten nicht gelesen werden: ",
15020
+ fehler5
15021
+ ] }),
15022
+ !fehler5 && !laedt && auftraege.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { "data-testid": `chat-auftraege-leer-${lauf.run_id}`, children: "F\xFCr diesen Lauf liegt kein Auftrag aus dem Chat vor." }),
15023
+ notiz && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
15024
+ "p",
15025
+ {
15026
+ role: notiz.tone === "error" ? "alert" : void 0,
15027
+ "data-testid": `chat-auftraege-notiz-${lauf.run_id}`,
15028
+ children: notiz.text
15029
+ }
15030
+ ),
15031
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "szenen-bilder__auftraege", children: auftraege.map((auftrag) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { "data-testid": `chat-auftrag-${auftrag.job_id}`, children: [
15032
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { children: CHAT_AUFTRAG_TEXT[auftrag.status] }),
15033
+ " \u2014 ",
15034
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { children: [
15035
+ auftrag.image_count,
15036
+ " ",
15037
+ auftrag.image_count === 1 ? "Bild" : "Bilder",
15038
+ " mit",
15039
+ " ",
15040
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: auftrag.execution_target_model_key }),
15041
+ " / ",
15042
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: auftrag.execution_profile_id }),
15043
+ auftrag.aspect_ratio ? ` im Format ${auftrag.aspect_ratio}` : ""
15044
+ ] }),
15045
+ auftrag.diagnostic && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { children: [
15046
+ " ",
15047
+ auftrag.diagnostic
15048
+ ] }),
15049
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("br", {}),
15050
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { className: "szenen-bilder__auftragskennung", children: auftrag.job_id }),
15051
+ auftrag.status === "pending" && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
15052
+ "button",
15053
+ {
15054
+ type: "button",
15055
+ disabled: laufend !== null,
15056
+ onClick: () => void uebernehmen(auftrag.job_id),
15057
+ "data-testid": `chat-auftrag-uebernehmen-${auftrag.job_id}`,
15058
+ children: laufend === auftrag.job_id ? "\xDCbernahme l\xE4uft\u2026" : "\xDCbernehmen und ausf\xFChren (kostet einen Bildaufruf)"
15059
+ }
15060
+ )
15061
+ ] }, auftrag.job_id)) })
15062
+ ] });
15063
+ };
14695
15064
 
14696
15065
  // src/features/szenen/areas/RunsPanel.tsx
14697
15066
  var import_jsx_runtime37 = require("react/jsx-runtime");
14698
15067
  var RunsPanel = ({ port, source, contextId }) => {
14699
- const [anweisung, setAnweisung] = (0, import_react41.useState)("");
14700
- const [laeuft, setLaeuft] = (0, import_react41.useState)(false);
14701
- const [notiz, setNotiz] = (0, import_react41.useState)(null);
14702
- const [nachladen, setNachladen] = (0, import_react41.useState)(0);
15068
+ const [anweisung, setAnweisung] = (0, import_react42.useState)("");
15069
+ const [laeuft, setLaeuft] = (0, import_react42.useState)(false);
15070
+ const [notiz, setNotiz] = (0, import_react42.useState)(null);
15071
+ const [nachladen, setNachladen] = (0, import_react42.useState)(0);
14703
15072
  const faehig = capabilitiesOf(port);
14704
15073
  const quellSchluessel = source.kind === "published" ? source.shotVersionId : source.kind === "draft_chain" ? source.chainId : source.sceneElementId;
14705
- const aktuelleQuelle = (0, import_react41.useRef)({ kind: source.kind, id: quellSchluessel });
15074
+ const aktuelleQuelle = (0, import_react42.useRef)({ kind: source.kind, id: quellSchluessel });
14706
15075
  aktuelleQuelle.current = { kind: source.kind, id: quellSchluessel };
14707
15076
  const [phase, neuLaden] = usePortQuery(
14708
15077
  () => {
@@ -14730,10 +15099,11 @@ var RunsPanel = ({ port, source, contextId }) => {
14730
15099
  neuLaden();
14731
15100
  };
14732
15101
  const laeufe = phase.status === "ready" ? phase.value : [];
14733
- const [gewaehlteRunde, setGewaehlteRunde] = (0, import_react41.useState)(void 0);
15102
+ const titel = useLaufTitel(port, laeufe);
15103
+ const [gewaehlteRunde, setGewaehlteRunde] = (0, import_react42.useState)(void 0);
14734
15104
  const neueste = laeufe.length > 0 ? laeufe[laeufe.length - 1].run_id : null;
14735
15105
  const offeneRunde = gewaehlteRunde === void 0 ? neueste : gewaehlteRunde;
14736
- (0, import_react41.useEffect)(() => {
15106
+ (0, import_react42.useEffect)(() => {
14737
15107
  setGewaehlteRunde(void 0);
14738
15108
  setAnweisung("");
14739
15109
  setNotiz(null);
@@ -14768,6 +15138,7 @@ var RunsPanel = ({ port, source, contextId }) => {
14768
15138
  port,
14769
15139
  lauf,
14770
15140
  runde: roundOfRun(lauf, laeufe),
15141
+ titel: titel.get(lauf.run_id) ?? null,
14771
15142
  offen: lauf.run_id === offeneRunde,
14772
15143
  onUmschalten: () => setGewaehlteRunde(
14773
15144
  lauf.run_id === offeneRunde ? null : lauf.run_id
@@ -14792,7 +15163,7 @@ var STATUS_TEXT = {
14792
15163
  };
14793
15164
  var LANGER_TEXT = 240;
14794
15165
  var Sekundaertext = ({ titel, text, testId }) => {
14795
- const [ganz, setGanz] = (0, import_react41.useState)(false);
15166
+ const [ganz, setGanz] = (0, import_react42.useState)(false);
14796
15167
  const lang = text.length > LANGER_TEXT;
14797
15168
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("section", { className: "szenen-runs__sekundaer", children: [
14798
15169
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("h5", { children: titel }),
@@ -14811,7 +15182,7 @@ var Sekundaertext = ({ titel, text, testId }) => {
14811
15182
  };
14812
15183
  var LISTE_ANFANG = 3;
14813
15184
  var SekundaerListe = ({ titel, eintraege, testId, className }) => {
14814
- const [alle, setAlle] = (0, import_react41.useState)(false);
15185
+ const [alle, setAlle] = (0, import_react42.useState)(false);
14815
15186
  const lang = eintraege.length > LISTE_ANFANG;
14816
15187
  const gezeigt = lang && !alle ? eintraege.slice(0, LISTE_ANFANG) : eintraege;
14817
15188
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("section", { className: "szenen-runs__sekundaer", children: [
@@ -14829,10 +15200,10 @@ var SekundaerListe = ({ titel, eintraege, testId, className }) => {
14829
15200
  )
14830
15201
  ] });
14831
15202
  };
14832
- var RundenEintrag = ({ port, lauf, runde, offen, onUmschalten, itemMoeglich, onItemGespeichert }) => {
14833
- const [notiz, setNotiz] = (0, import_react41.useState)(null);
14834
- const [laeuft, setLaeuft] = (0, import_react41.useState)(false);
14835
- const [nachladen, setNachladen] = (0, import_react41.useState)(0);
15203
+ var RundenEintrag = ({ port, lauf, runde, titel, offen, onUmschalten, itemMoeglich, onItemGespeichert }) => {
15204
+ const [notiz, setNotiz] = (0, import_react42.useState)(null);
15205
+ const [laeuft, setLaeuft] = (0, import_react42.useState)(false);
15206
+ const [nachladen, setNachladen] = (0, import_react42.useState)(0);
14836
15207
  const [itemPhase] = usePortQuery(
14837
15208
  () => offen && port.items ? port.items.getItemForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
14838
15209
  [port, lauf.run_id, nachladen, offen]
@@ -14860,10 +15231,7 @@ var RundenEintrag = ({ port, lauf, runde, offen, onUmschalten, itemMoeglich, onI
14860
15231
  "aria-expanded": offen,
14861
15232
  "data-testid": `runde-umschalten-${lauf.run_id}`,
14862
15233
  children: [
14863
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "szenen-runs__marke", children: [
14864
- "Runde ",
14865
- runde
14866
- ] }),
15234
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "szenen-runs__marke", "data-testid": `titel-${lauf.run_id}`, children: titel ? `${titel} \xB7 Runde ${runde}` : `Runde ${runde}` }),
14867
15235
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
14868
15236
  "span",
14869
15237
  {
@@ -14987,43 +15355,43 @@ var ElementDetail = ({
14987
15355
  author,
14988
15356
  onChanged
14989
15357
  }) => {
14990
- const [nachladen, setNachladen] = (0, import_react42.useState)(0);
15358
+ const [nachladen, setNachladen] = (0, import_react43.useState)(0);
14991
15359
  const [phase] = usePortQuery(
14992
15360
  () => port.elements.getElement(elementId),
14993
15361
  [port, elementId, nachladen]
14994
15362
  );
14995
- const [entwurf, setEntwurf] = (0, import_react42.useState)({});
14996
- const [notiz, setNotiz] = (0, import_react42.useState)(null);
14997
- const [laeuft, setLaeuft] = (0, import_react42.useState)(false);
14998
- const [wahl, setWahl] = (0, import_react42.useState)(null);
14999
- const [basiswechsel, setBasiswechsel] = (0, import_react42.useState)(null);
15363
+ const [entwurf, setEntwurf] = (0, import_react43.useState)({});
15364
+ const [notiz, setNotiz] = (0, import_react43.useState)(null);
15365
+ const [laeuft, setLaeuft] = (0, import_react43.useState)(false);
15366
+ const [wahl, setWahl] = (0, import_react43.useState)(null);
15367
+ const [basiswechsel, setBasiswechsel] = (0, import_react43.useState)(null);
15000
15368
  const [kettenPhase] = usePortQuery(
15001
15369
  () => port.context.chainFor(contextId),
15002
15370
  [port, contextId]
15003
15371
  );
15004
- const kontextkette = (0, import_react42.useMemo)(() => {
15372
+ const kontextkette = (0, import_react43.useMemo)(() => {
15005
15373
  if (kettenPhase.status === "ready") return { status: "ready", chain: kettenPhase.value };
15006
15374
  if (kettenPhase.status === "error") return { status: "error", message: kettenPhase.message };
15007
15375
  return { status: "loading" };
15008
15376
  }, [kettenPhase]);
15009
15377
  const kette = kontextkette.status === "ready" ? kontextkette.chain : null;
15010
15378
  const schnappschuss = phase.status === "ready" ? phase.value : null;
15011
- const kopfErgebnis = (0, import_react42.useMemo)(
15379
+ const kopfErgebnis = (0, import_react43.useMemo)(
15012
15380
  () => schnappschuss && kette ? effectivePreferredHeadSafe(schnappschuss, kette) : null,
15013
15381
  [schnappschuss, kette]
15014
15382
  );
15015
15383
  const kopfAufloesung = resolutionOf(kopfErgebnis);
15016
15384
  const bevorzugteVersion = selectedHeadVersionId(kopfAufloesung);
15017
- const felder = (0, import_react42.useMemo)(
15385
+ const felder = (0, import_react43.useMemo)(
15018
15386
  () => schnappschuss ? fieldsForType(schnappschuss.element.type) : [],
15019
15387
  [schnappschuss?.element.type]
15020
15388
  );
15021
- (0, import_react42.useEffect)(() => {
15389
+ (0, import_react43.useEffect)(() => {
15022
15390
  setWahl(null);
15023
15391
  setBasiswechsel(null);
15024
15392
  setNotiz(null);
15025
15393
  }, [elementId]);
15026
- const automatisch = (0, import_react42.useMemo)(() => {
15394
+ const automatisch = (0, import_react43.useMemo)(() => {
15027
15395
  if (!schnappschuss) return { kind: "keiner" };
15028
15396
  if (schnappschuss.draft) return { kind: "draft" };
15029
15397
  if (bevorzugteVersion) return { kind: "version", versionId: bevorzugteVersion };
@@ -15033,14 +15401,14 @@ var ElementDetail = ({
15033
15401
  if (schnappschuss.versions.length === 0) return { kind: "draft" };
15034
15402
  return { kind: "keiner" };
15035
15403
  }, [schnappschuss, bevorzugteVersion]);
15036
- const angezeigt = (0, import_react42.useMemo)(() => {
15404
+ const angezeigt = (0, import_react43.useMemo)(() => {
15037
15405
  if (!wahl) return automatisch;
15038
15406
  if (wahl.kind === "version" && !schnappschuss?.versions.some((v) => v.version_id === wahl.versionId)) {
15039
15407
  return automatisch;
15040
15408
  }
15041
15409
  return wahl;
15042
15410
  }, [wahl, automatisch, schnappschuss]);
15043
- const angezeigtePayload = (0, import_react42.useMemo)(() => {
15411
+ const angezeigtePayload = (0, import_react43.useMemo)(() => {
15044
15412
  if (!schnappschuss) return {};
15045
15413
  if (angezeigt.kind === "draft") return schnappschuss.draft?.payload ?? {};
15046
15414
  if (angezeigt.kind === "version") {
@@ -15049,7 +15417,7 @@ var ElementDetail = ({
15049
15417
  return {};
15050
15418
  }, [schnappschuss, angezeigt]);
15051
15419
  const anzeigeSchluessel = angezeigt.kind === "version" ? `v:${angezeigt.versionId}` : angezeigt.kind;
15052
- (0, import_react42.useEffect)(() => {
15420
+ (0, import_react43.useEffect)(() => {
15053
15421
  if (!schnappschuss) return;
15054
15422
  setEntwurf(formFromPayload(felder, angezeigtePayload));
15055
15423
  }, [
@@ -15062,7 +15430,7 @@ var ElementDetail = ({
15062
15430
  const bearbeitbar = angezeigt.kind === "draft";
15063
15431
  const setzeFeld = (pfad, wert) => setEntwurf((vorher) => ({ ...vorher, [pfad]: wert }));
15064
15432
  const payloadAusFormular = () => payloadFromForm(felder, entwurf);
15065
- const strukturPayload = (0, import_react42.useMemo)(() => {
15433
+ const strukturPayload = (0, import_react43.useMemo)(() => {
15066
15434
  const lies = (name) => {
15067
15435
  try {
15068
15436
  const roh = JSON.parse(entwurf[name] || "[]");
@@ -15073,19 +15441,19 @@ var ElementDetail = ({
15073
15441
  };
15074
15442
  return { nodes: lies("nodes"), relations: lies("relations") };
15075
15443
  }, [entwurf.nodes, entwurf.relations]);
15076
- const kettenfeld = (0, import_react42.useMemo)(() => {
15444
+ const kettenfeld = (0, import_react43.useMemo)(() => {
15077
15445
  const kette2 = schnappschuss?.draft?.chain;
15078
15446
  if (!kette2) return null;
15079
15447
  return kette2.links[0]?.field ?? null;
15080
15448
  }, [schnappschuss?.draft?.chain]);
15081
- const laufStand = (0, import_react42.useMemo)(() => {
15449
+ const laufStand = (0, import_react43.useMemo)(() => {
15082
15450
  if (!schnappschuss || schnappschuss.element.type !== "scene") return null;
15083
15451
  if (schnappschuss.draft) return schnappschuss.draft.payload;
15084
15452
  const kopf = usableHeadVersionId(kopfAufloesung);
15085
15453
  if (!kopf) return null;
15086
15454
  return schnappschuss.versions.find((v) => v.version_id === kopf)?.payload ?? null;
15087
15455
  }, [schnappschuss, kopfAufloesung]);
15088
- const szenenBlocker = (0, import_react42.useMemo)(() => {
15456
+ const szenenBlocker = (0, import_react43.useMemo)(() => {
15089
15457
  if (!schnappschuss || schnappschuss.element.type !== "scene") return null;
15090
15458
  if (laufStand === null) {
15091
15459
  return schnappschuss.versions.length > 0 ? "Diese Szene hat keinen Entwurf und in diesem Kontext keinen bevorzugten Kopf. Welcher Stand gilt, entscheidet keine Reihenfolge (D-36) \u2014 bitte oben eine Version als bevorzugt setzen." : "Diese Szene hat noch keinen gespeicherten Stand. Ein Lauf friert ein, was gespeichert ist \u2014 bitte zuerst den Entwurf speichern.";
@@ -15095,7 +15463,7 @@ var ElementDetail = ({
15095
15463
  const grund = flag.reasons[0];
15096
15464
  return grund && "message" in grund ? grund.message : "Kein Lauf moeglich.";
15097
15465
  }, [schnappschuss, laufStand]);
15098
- const vorschau = (0, import_react42.useMemo)(() => {
15466
+ const vorschau = (0, import_react43.useMemo)(() => {
15099
15467
  if (!schnappschuss) return null;
15100
15468
  try {
15101
15469
  const roh = validatePayload(schnappschuss.element.type, payloadAusFormular());
@@ -15481,15 +15849,15 @@ var ElementDetail = ({
15481
15849
  };
15482
15850
 
15483
15851
  // src/features/szenen/areas/useSchmalesDisplay.ts
15484
- var import_react43 = require("react");
15852
+ var import_react44 = require("react");
15485
15853
  var SCHMALE_ANSICHT = "(max-width: 760px)";
15486
15854
  function messen(abfrage) {
15487
15855
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
15488
15856
  return window.matchMedia(abfrage).matches;
15489
15857
  }
15490
15858
  function useSchmalesDisplay(abfrage = SCHMALE_ANSICHT) {
15491
- const [schmal, setSchmal] = (0, import_react43.useState)(() => messen(abfrage));
15492
- (0, import_react43.useEffect)(() => {
15859
+ const [schmal, setSchmal] = (0, import_react44.useState)(() => messen(abfrage));
15860
+ (0, import_react44.useEffect)(() => {
15493
15861
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
15494
15862
  const liste = window.matchMedia(abfrage);
15495
15863
  const aktualisieren = () => setSchmal(liste.matches);
@@ -15516,16 +15884,16 @@ var ElementsArea = ({
15516
15884
  const gemischt = auswahlTypen.length > 1;
15517
15885
  const ueberschrift = listTitle ?? (einzelTyp ? elementTypPlural(einzelTyp) : "Bausteine");
15518
15886
  const einzahl = einzelTyp ? elementTypLabel(einzelTyp) : "Objekt";
15519
- const [typ, setTyp] = (0, import_react44.useState)(einzelTyp ?? "");
15520
- const [zeigeStillgelegte, setZeigeStillgelegte] = (0, import_react44.useState)(false);
15521
- const [gewaehlt, setGewaehlt] = (0, import_react44.useState)(null);
15522
- const [neuerTitel, setNeuerTitel] = (0, import_react44.useState)("");
15523
- const [neuerTyp, setNeuerTyp] = (0, import_react44.useState)(auswahlTypen[0]);
15524
- const [notiz, setNotiz] = (0, import_react44.useState)(null);
15525
- const [anlegend, setAnlegend] = (0, import_react44.useState)(false);
15526
- const [anlageOffen, setAnlageOffen] = (0, import_react44.useState)(false);
15887
+ const [typ, setTyp] = (0, import_react45.useState)(einzelTyp ?? "");
15888
+ const [zeigeStillgelegte, setZeigeStillgelegte] = (0, import_react45.useState)(false);
15889
+ const [gewaehlt, setGewaehlt] = (0, import_react45.useState)(null);
15890
+ const [neuerTitel, setNeuerTitel] = (0, import_react45.useState)("");
15891
+ const [neuerTyp, setNeuerTyp] = (0, import_react45.useState)(auswahlTypen[0]);
15892
+ const [notiz, setNotiz] = (0, import_react45.useState)(null);
15893
+ const [anlegend, setAnlegend] = (0, import_react45.useState)(false);
15894
+ const [anlageOffen, setAnlageOffen] = (0, import_react45.useState)(false);
15527
15895
  const schmal = useSchmalesDisplay();
15528
- const [ansicht, setAnsicht] = (0, import_react44.useState)("liste");
15896
+ const [ansicht, setAnsicht] = (0, import_react45.useState)("liste");
15529
15897
  const zeigeListe = !schmal || ansicht === "liste";
15530
15898
  const zeigeDetail = !schmal || ansicht === "detail";
15531
15899
  const waehle = (elementId) => {
@@ -15737,204 +16105,28 @@ var ElementsArea = ({
15737
16105
  };
15738
16106
 
15739
16107
  // src/features/szenen/areas/SceneArea.tsx
15740
- var import_jsx_runtime40 = require("react/jsx-runtime");
15741
- var SceneArea = ({ port, contextId }) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "szenen-scene", "data-testid": "bereich-szene", children: [
15742
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "szenen-area__lead", children: "Szenen dieses Kontexts. Auswahl \xF6ffnet die Bearbeitung der Nodes und Beziehungen." }),
15743
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ElementsArea, { port, contextId, restrictToTypes: ["scene"] })
15744
- ] });
16108
+ var import_react49 = require("react");
15745
16109
 
15746
- // src/features/szenen/areas/SetupArea.tsx
15747
- var import_jsx_runtime41 = require("react/jsx-runtime");
15748
- var SetupArea = ({ port, contextId }) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "szenen-setup", "data-testid": "bereich-setup", children: [
15749
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "szenen-area__lead", children: "Bildaufbauten, Kameras und Stile dieses Kontexts." }),
15750
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
15751
- ElementsArea,
15752
- {
15753
- port,
15754
- contextId,
15755
- restrictToTypes: ["scene_setup", "camera_preset", "camera_pattern", "style"],
15756
- allTypesLabel: "alle Bildaufbau-Typen",
15757
- listTitle: "Bildaufbau"
15758
- }
15759
- )
15760
- ] });
16110
+ // src/features/szenen/areas/DeriveArea.tsx
16111
+ var import_react48 = require("react");
15761
16112
 
15762
- // src/features/szenen/areas/ShotArea.tsx
15763
- var import_jsx_runtime42 = require("react/jsx-runtime");
15764
- var ShotArea = ({ port, contextId }) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-shot", "data-testid": "bereich-shot", children: [
15765
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "szenen-area__lead", children: "Prompts dieses Kontexts. Auswahl zeigt Runden und startet Prompt-L\xE4ufe." }),
15766
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15767
- ElementsArea,
15768
- {
15769
- port,
15770
- contextId,
15771
- restrictToTypes: ["shot", "shot_concept", "target_model"],
15772
- allTypesLabel: "alle Prompt-Typen",
15773
- listTitle: "Prompts"
15774
- }
15775
- )
15776
- ] });
15777
-
15778
- // src/features/szenen/areas/MediaArea.tsx
15779
- var import_react45 = require("react");
15780
- var import_jsx_runtime43 = require("react/jsx-runtime");
15781
- var leer = async () => ({ kind: "ok", value: [] });
15782
- var MediaArea = ({ port }) => {
15783
- const media = port.media;
15784
- const [versionInput, setVersionInput] = (0, import_react45.useState)("");
15785
- const [versionId, setVersionId] = (0, import_react45.useState)("");
15786
- const [itemInput, setItemInput] = (0, import_react45.useState)("");
15787
- const [itemId, setItemId] = (0, import_react45.useState)("");
15788
- const [assetId, setAssetId] = (0, import_react45.useState)("");
15789
- const [role, setRole] = (0, import_react45.useState)("reference");
15790
- const [position, setPosition] = (0, import_react45.useState)("0");
15791
- const [notiz, setNotiz] = (0, import_react45.useState)(null);
15792
- const [busy, setBusy] = (0, import_react45.useState)(false);
15793
- const [refresh, setRefresh] = (0, import_react45.useState)(0);
15794
- const [versionPhase] = usePortQuery(
15795
- () => versionId ? media.listVersionMedia(versionId) : leer(),
15796
- [media, versionId, refresh]
15797
- );
15798
- const [itemPhase] = usePortQuery(
15799
- () => itemId ? media.listItemMedia(itemId) : Promise.resolve({ kind: "ok", value: [] }),
15800
- [media, itemId, refresh]
15801
- );
15802
- const zuordnen = async () => {
15803
- if (!versionInput.trim() || !assetId.trim()) {
15804
- setNotiz({ tone: "error", text: "Version-ID und Asset-ID sind erforderlich." });
15805
- return;
15806
- }
15807
- const parsedPosition = Number(position);
15808
- if (!Number.isInteger(parsedPosition) || parsedPosition < 0) {
15809
- setNotiz({ tone: "error", text: `Position muss eine nicht-negative Ganzzahl sein, war ${JSON.stringify(position)}.` });
15810
- return;
15811
- }
15812
- setBusy(true);
15813
- const result = await media.attachVersionMedium({
15814
- versionId: versionInput.trim(),
15815
- assetId: assetId.trim(),
15816
- role,
15817
- position: parsedPosition
15818
- });
15819
- setNotiz(noticeFor(result, "Referenzmedium zuordnen"));
15820
- if (result.kind === "ok") {
15821
- setVersionId(versionInput.trim());
15822
- setRefresh((value) => value + 1);
15823
- }
15824
- setBusy(false);
15825
- };
15826
- const entfernen = async (medium) => {
15827
- setBusy(true);
15828
- const result = await media.detachVersionMedium({
15829
- versionId,
15830
- assetId: medium.assetId,
15831
- role: medium.role
15832
- });
15833
- setNotiz(noticeFor(result, "Referenzmedium entfernen"));
15834
- if (result.kind === "ok") setRefresh((value) => value + 1);
15835
- setBusy(false);
15836
- };
15837
- const versionMedien = versionPhase.status === "ready" ? versionPhase.value : [];
15838
- const itemMedien = itemPhase.status === "ready" ? itemPhase.value : [];
15839
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "szenen-media", "data-testid": "bereich-medien", children: [
15840
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "szenen-area__lead", children: "Bereits vorhandene Medien-Assets einer Elementversion zuordnen. Hochgeladen wird hier nichts \u2014 Medien entstehen \xFCber den bestehenden Speicherweg aus Stufe 5." }),
15841
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("section", { children: [
15842
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("h3", { children: "Version" }),
15843
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "szenen-media__lookup", children: [
15844
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("label", { children: [
15845
- "Version-ID",
15846
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("input", { value: versionInput, onChange: (event) => setVersionInput(event.target.value) })
15847
- ] }),
15848
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", onClick: () => setVersionId(versionInput.trim()), children: "Medien laden" })
15849
- ] }),
15850
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "szenen-media__attach", children: [
15851
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("label", { children: [
15852
- "Asset-ID",
15853
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("input", { value: assetId, onChange: (event) => setAssetId(event.target.value) })
15854
- ] }),
15855
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("label", { children: [
15856
- "Rolle",
15857
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("select", { value: role, onChange: (event) => setRole(event.target.value), children: [
15858
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("option", { value: "reference", children: "reference" }),
15859
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("option", { value: "mask", children: "mask" }),
15860
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("option", { value: "palette", children: "palette" }),
15861
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("option", { value: "example", children: "example" })
15862
- ] })
15863
- ] }),
15864
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("label", { children: [
15865
- "Position",
15866
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("input", { type: "number", min: "0", value: position, onChange: (event) => setPosition(event.target.value) })
15867
- ] }),
15868
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", disabled: busy, onClick: zuordnen, children: "zuordnen" })
15869
- ] }),
15870
- versionPhase.status === "loading" && versionId && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { children: "Versionsmedien werden geladen \u2026" }),
15871
- versionPhase.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("p", { role: "alert", children: [
15872
- "Versionsmedien konnten nicht geladen werden: ",
15873
- versionPhase.message
15874
- ] }),
15875
- versionPhase.status === "ready" && versionId && versionMedien.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("p", { children: [
15876
- "Version ",
15877
- versionId,
15878
- " hat keine Medien."
15879
- ] }),
15880
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { className: "szenen-media__list", children: versionMedien.map((medium) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("li", { children: [
15881
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("code", { children: medium.assetId }),
15882
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
15883
- medium.role,
15884
- " \xB7 Position ",
15885
- medium.position
15886
- ] }),
15887
- medium.url && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("a", { href: medium.url, target: "_blank", rel: "noreferrer", children: "\xF6ffnen" }),
15888
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", disabled: busy, onClick: () => entfernen(medium), children: "entfernen" })
15889
- ] }, `${medium.assetId}:${medium.role}`)) })
15890
- ] }),
15891
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("section", { children: [
15892
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("h3", { children: "Item-Medien" }),
15893
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "szenen-media__lookup", children: [
15894
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("label", { children: [
15895
- "Item-ID",
15896
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("input", { value: itemInput, onChange: (event) => setItemInput(event.target.value) })
15897
- ] }),
15898
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", onClick: () => setItemId(itemInput.trim()), children: "Item-Medien laden" })
15899
- ] }),
15900
- itemPhase.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("p", { role: "alert", children: [
15901
- "Item-Medien konnten nicht geladen werden: ",
15902
- itemPhase.message
15903
- ] }),
15904
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { className: "szenen-media__list", children: itemMedien.map((medium) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("li", { children: [
15905
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("code", { children: medium.assetId }),
15906
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
15907
- medium.role,
15908
- " \xB7 Position ",
15909
- medium.position
15910
- ] }),
15911
- medium.url && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("a", { href: medium.url, target: "_blank", rel: "noreferrer", children: "\xF6ffnen" })
15912
- ] }, `${medium.assetId}:${medium.role}:${medium.position}`)) })
15913
- ] }),
15914
- notiz && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { role: notiz.tone === "error" ? "alert" : "status", "data-tone": notiz.tone, children: notiz.text })
15915
- ] });
15916
- };
15917
-
15918
- // src/features/szenen/areas/DeriveArea.tsx
15919
- var import_react48 = require("react");
15920
-
15921
- // src/lib/szenen/bildVorschau.ts
15922
- async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
15923
- const laden = port.loadPreview;
15924
- if (!laden) return;
15925
- const parallel = Math.max(1, optionen.parallel ?? 4);
15926
- const lauf = optionen.lauf;
15927
- const offen = mediaRefs.filter((ref) => !(optionen.hat?.(ref) ?? false));
15928
- let naechster = 0;
15929
- async function arbeiter() {
15930
- while (naechster < offen.length) {
15931
- if (lauf?.abgebrochen) return;
15932
- const ref = offen[naechster];
15933
- naechster += 1;
15934
- const ergebnis = await laden.call(port, ref);
15935
- if (lauf?.abgebrochen) return;
15936
- if (ergebnis.kind === "ok") fertig(ref, ergebnis.value);
15937
- else optionen.gescheitert?.(ref, ergebnis.message);
16113
+ // src/lib/szenen/bildVorschau.ts
16114
+ async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
16115
+ const laden = port.loadPreview;
16116
+ if (!laden) return;
16117
+ const parallel = Math.max(1, optionen.parallel ?? 4);
16118
+ const lauf = optionen.lauf;
16119
+ const offen = mediaRefs.filter((ref) => !(optionen.hat?.(ref) ?? false));
16120
+ let naechster = 0;
16121
+ async function arbeiter() {
16122
+ while (naechster < offen.length) {
16123
+ if (lauf?.abgebrochen) return;
16124
+ const ref = offen[naechster];
16125
+ naechster += 1;
16126
+ const ergebnis = await laden.call(port, ref);
16127
+ if (lauf?.abgebrochen) return;
16128
+ if (ergebnis.kind === "ok") fertig(ref, ergebnis.value);
16129
+ else optionen.gescheitert?.(ref, ergebnis.message);
15938
16130
  }
15939
16131
  }
15940
16132
  await Promise.all(
@@ -16780,52 +16972,52 @@ async function runSceneDerivation(input, port, userPrompt) {
16780
16972
 
16781
16973
  // src/features/szenen/areas/RecoveryPanel.tsx
16782
16974
  var import_react46 = require("react");
16783
- var import_jsx_runtime44 = require("react/jsx-runtime");
16975
+ var import_jsx_runtime40 = require("react/jsx-runtime");
16784
16976
  var Diagnosen = ({ befunde, testId }) => {
16785
16977
  if (befunde.length === 0) return null;
16786
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { className: "szenen-diagnose__liste", "data-testid": testId, children: befunde.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("li", { "data-severity": d.severity, children: [
16787
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("code", { children: d.code }),
16788
- d.proposal_type && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
16978
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("ul", { className: "szenen-diagnose__liste", "data-testid": testId, children: befunde.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("li", { "data-severity": d.severity, children: [
16979
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("code", { children: d.code }),
16980
+ d.proposal_type && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
16789
16981
  " \xB7 ",
16790
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("strong", { children: d.proposal_type })
16982
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("strong", { children: d.proposal_type })
16791
16983
  ] }),
16792
- d.pointer && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
16984
+ d.pointer && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
16793
16985
  " \xB7 ",
16794
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("code", { children: d.pointer })
16986
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("code", { children: d.pointer })
16795
16987
  ] }),
16796
- typeof d.line === "number" && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
16988
+ typeof d.line === "number" && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
16797
16989
  " \xB7 Zeile ",
16798
16990
  d.line,
16799
16991
  ", Spalte ",
16800
16992
  d.column
16801
16993
  ] }),
16802
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { children: d.message })
16994
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { children: d.message })
16803
16995
  ] }, i)) });
16804
16996
  };
16805
- var Ausbeute = ({ bewertung }) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "szenen-diagnose", "data-testid": "rettung-ergebnis", children: [
16806
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("p", { "data-testid": "rettung-status", children: [
16997
+ var Ausbeute = ({ bewertung }) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "szenen-diagnose", "data-testid": "rettung-ergebnis", children: [
16998
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { "data-testid": "rettung-status", children: [
16807
16999
  bewertung.status === "usable" && "Diese Fassung ist vollst\xE4ndig verwertbar.",
16808
17000
  bewertung.status === "partial" && "Diese Fassung ist teilweise verwertbar \u2014 nicht alles kommt durch.",
16809
17001
  bewertung.status === "invalid" && "Aus dieser Fassung ist nichts zu \xFCbernehmen."
16810
17002
  ] }),
16811
- bewertung.proposals.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
16812
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("h5", { children: "Verwertbar" }),
16813
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { "data-testid": "rettung-verwertbar", children: bewertung.proposals.map((p) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("li", { "data-testid": `rettung-verwertbar-${p.type}`, children: [
16814
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("strong", { children: p.type }),
17003
+ bewertung.proposals.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
17004
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("h5", { children: "Verwertbar" }),
17005
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("ul", { "data-testid": "rettung-verwertbar", children: bewertung.proposals.map((p) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("li", { "data-testid": `rettung-verwertbar-${p.type}`, children: [
17006
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("strong", { children: p.type }),
16815
17007
  " \u2014 ",
16816
17008
  p.status === "complete" ? "vollst\xE4ndig" : "teilweise; vor dem Ver\xF6ffentlichen zu erg\xE4nzen",
16817
- p.status === "partial" && p.gaps && p.gaps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { children: p.gaps.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("li", { children: [
16818
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("code", { children: l.pointer }),
17009
+ p.status === "partial" && p.gaps && p.gaps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("ul", { children: p.gaps.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("li", { children: [
17010
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("code", { children: l.pointer }),
16819
17011
  ": ",
16820
17012
  l.reason
16821
17013
  ] }, i)) })
16822
17014
  ] }, p.type)) })
16823
17015
  ] }),
16824
- bewertung.rejected.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
16825
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("h5", { children: "Nicht verwertbar" }),
16826
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { "data-testid": "rettung-abgelehnt", children: bewertung.rejected.map((r) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("li", { "data-testid": `rettung-abgelehnt-${r.type}`, children: [
16827
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("strong", { children: r.type }),
16828
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Diagnosen, { befunde: r.diagnostics, testId: `rettung-grund-${r.type}` })
17016
+ bewertung.rejected.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
17017
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("h5", { children: "Nicht verwertbar" }),
17018
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("ul", { "data-testid": "rettung-abgelehnt", children: bewertung.rejected.map((r) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("li", { "data-testid": `rettung-abgelehnt-${r.type}`, children: [
17019
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("strong", { children: r.type }),
17020
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Diagnosen, { befunde: r.diagnostics, testId: `rettung-grund-${r.type}` })
16829
17021
  ] }, r.type)) })
16830
17022
  ] })
16831
17023
  ] });
@@ -16870,16 +17062,16 @@ var RecoveryPanel = ({ port, lauf }) => {
16870
17062
  setLaeuft(false);
16871
17063
  }
16872
17064
  }
16873
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("section", { className: "szenen-rettung", "data-testid": "rettung", children: [
16874
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("h4", { children: "Antwort retten" }),
16875
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("p", { className: "szenen-area__lead", children: [
17065
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("section", { className: "szenen-rettung", "data-testid": "rettung", children: [
17066
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("h4", { children: "Antwort retten" }),
17067
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "szenen-area__lead", children: [
16876
17068
  "Der Lauf ist bezahlt und seine Antwort liegt vor. Sie l\xE4sst sich hier pr\xFCfen und korrigieren \u2014 ",
16877
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("strong", { children: "ohne" }),
17069
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("strong", { children: "ohne" }),
16878
17070
  " einen weiteren Modellaufruf. Das Original bleibt unver\xE4ndert."
16879
17071
  ] }),
16880
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("label", { className: "szenen-field", children: [
16881
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { children: "Original der Modellantwort (unver\xE4nderlich)" }),
16882
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
17072
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("label", { className: "szenen-field", children: [
17073
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { children: "Original der Modellantwort (unver\xE4nderlich)" }),
17074
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
16883
17075
  "textarea",
16884
17076
  {
16885
17077
  readOnly: true,
@@ -16890,12 +17082,12 @@ var RecoveryPanel = ({ port, lauf }) => {
16890
17082
  }
16891
17083
  )
16892
17084
  ] }),
16893
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("label", { className: "szenen-field", children: [
16894
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { children: [
17085
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("label", { className: "szenen-field", children: [
17086
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { children: [
16895
17087
  "Arbeitskopie",
16896
17088
  bearbeitet ? " (bearbeitet)" : ""
16897
17089
  ] }),
16898
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
17090
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
16899
17091
  "textarea",
16900
17092
  {
16901
17093
  rows: 8,
@@ -16905,8 +17097,8 @@ var RecoveryPanel = ({ port, lauf }) => {
16905
17097
  }
16906
17098
  )
16907
17099
  ] }),
16908
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "szenen-rettung__aktionen", children: [
16909
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
17100
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "szenen-rettung__aktionen", children: [
17101
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
16910
17102
  "button",
16911
17103
  {
16912
17104
  type: "button",
@@ -16916,7 +17108,7 @@ var RecoveryPanel = ({ port, lauf }) => {
16916
17108
  children: "Arbeitskopie pr\xFCfen (kein Modellaufruf)"
16917
17109
  }
16918
17110
  ),
16919
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
17111
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
16920
17112
  "button",
16921
17113
  {
16922
17114
  type: "button",
@@ -16926,7 +17118,7 @@ var RecoveryPanel = ({ port, lauf }) => {
16926
17118
  children: "Arbeitsfassung speichern"
16927
17119
  }
16928
17120
  ),
16929
- arbeitskopie !== null && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
17121
+ arbeitskopie !== null && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
16930
17122
  "button",
16931
17123
  {
16932
17124
  type: "button",
@@ -16941,15 +17133,15 @@ var RecoveryPanel = ({ port, lauf }) => {
16941
17133
  }
16942
17134
  )
16943
17135
  ] }),
16944
- meldung && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "szenen-error", role: "alert", "data-testid": "rettung-meldung", children: meldung }),
16945
- bewertung && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Ausbeute, { bewertung }),
16946
- bewertung && bewertung.diagnostics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "szenen-diagnose", children: [
16947
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("h5", { children: "Befunde an dieser Fassung" }),
16948
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Diagnosen, { befunde: bewertung.diagnostics, testId: "rettung-befunde" })
17136
+ meldung && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "szenen-error", role: "alert", "data-testid": "rettung-meldung", children: meldung }),
17137
+ bewertung && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Ausbeute, { bewertung }),
17138
+ bewertung && bewertung.diagnostics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "szenen-diagnose", children: [
17139
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("h5", { children: "Befunde an dieser Fassung" }),
17140
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Diagnosen, { befunde: bewertung.diagnostics, testId: "rettung-befunde" })
16949
17141
  ] }),
16950
- gesichert && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("p", { "data-testid": "rettung-gesichert", children: [
17142
+ gesichert && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { "data-testid": "rettung-gesichert", children: [
16951
17143
  "Gespeichert als Arbeitsfassung ",
16952
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("code", { children: gesichert.recovery_id }),
17144
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("code", { children: gesichert.recovery_id }),
16953
17145
  gesichert.edited ? " (bearbeitet)" : " (unver\xE4ndert)",
16954
17146
  ". Das Original des Laufs ist davon unber\xFChrt."
16955
17147
  ] })
@@ -16958,7 +17150,7 @@ var RecoveryPanel = ({ port, lauf }) => {
16958
17150
 
16959
17151
  // src/features/szenen/areas/ChainActions.tsx
16960
17152
  var import_react47 = require("react");
16961
- var import_jsx_runtime45 = require("react/jsx-runtime");
17153
+ var import_jsx_runtime41 = require("react/jsx-runtime");
16962
17154
  var ChainActions = ({ port, runId, recoveryId }) => {
16963
17155
  const [graph, setGraph] = (0, import_react47.useState)(null);
16964
17156
  const [meldung, setMeldung] = (0, import_react47.useState)(null);
@@ -16984,14 +17176,14 @@ var ChainActions = ({ port, runId, recoveryId }) => {
16984
17176
  setLaeuft(false);
16985
17177
  }
16986
17178
  }
16987
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("section", { className: "szenen-kette", "data-testid": "kette", children: [
16988
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("h4", { children: "Draft-Kette" }),
16989
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("p", { className: "szenen-area__lead", children: [
17179
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("section", { className: "szenen-kette", "data-testid": "kette", children: [
17180
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("h4", { children: "Draft-Kette" }),
17181
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "szenen-area__lead", children: [
16990
17182
  "Szene, Aufbau und Shot entstehen als zusammenh\xE4ngende Entw\xFCrfe. Daraus l\xE4sst sich sofort ein Prompt erzeugen \u2014 ",
16991
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("strong", { children: "ohne" }),
17183
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("strong", { children: "ohne" }),
16992
17184
  " vorher etwas zu ver\xF6ffentlichen."
16993
17185
  ] }),
16994
- !graph && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
17186
+ !graph && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
16995
17187
  "button",
16996
17188
  {
16997
17189
  type: "button",
@@ -17001,34 +17193,34 @@ var ChainActions = ({ port, runId, recoveryId }) => {
17001
17193
  children: "Als Draft-Kette \xFCbernehmen"
17002
17194
  }
17003
17195
  ),
17004
- graph && /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { "data-testid": "kette-ergebnis", children: [
17005
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("p", { children: [
17196
+ graph && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { "data-testid": "kette-ergebnis", children: [
17197
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { children: [
17006
17198
  "Kette ",
17007
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { children: graph.chainId }),
17199
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("code", { children: graph.chainId }),
17008
17200
  " angelegt. Kein Knoten ist ver\xF6ffentlicht."
17009
17201
  ] }),
17010
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ul", { "data-testid": "kette-knoten", children: graph.created.map((k) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("li", { "data-testid": `kette-knoten-${k.type}`, children: [
17011
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("strong", { children: k.type }),
17202
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { "data-testid": "kette-knoten", children: graph.created.map((k) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("li", { "data-testid": `kette-knoten-${k.type}`, children: [
17203
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("strong", { children: k.type }),
17012
17204
  " \u2014 Element ",
17013
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { children: k.elementId }),
17205
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("code", { children: k.elementId }),
17014
17206
  ", Entwurf ",
17015
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { children: k.draftId }),
17207
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("code", { children: k.draftId }),
17016
17208
  " (Revision ",
17017
17209
  k.revision,
17018
17210
  ")"
17019
17211
  ] }, k.type)) }),
17020
- graph.missing.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "szenen-diagnose", "data-testid": "kette-fehlend", children: [
17021
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("h5", { children: "Unvollst\xE4ndig erzeugt" }),
17022
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ul", { children: graph.missing.map((m) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("li", { children: [
17023
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("strong", { children: m.type }),
17212
+ graph.missing.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "szenen-diagnose", "data-testid": "kette-fehlend", children: [
17213
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("h5", { children: "Unvollst\xE4ndig erzeugt" }),
17214
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { children: graph.missing.map((m) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("li", { children: [
17215
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("strong", { children: m.type }),
17024
17216
  ": ",
17025
17217
  m.reason
17026
17218
  ] }, m.type)) })
17027
17219
  ] })
17028
17220
  ] }),
17029
- shotKnoten && /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
17030
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { children: 'Die Entw\xFCrfe lassen sich jetzt unter \u201E1 \xB7 Bausteine" \xE4ndern. Der Prompt-Lauf friert den Stand ein, den er dabei vorfindet \u2014 dieselbe Aktion steht auch am Shot selbst, unter \u201E4 \xB7 Prompt".' }),
17031
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
17221
+ shotKnoten && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
17222
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { children: 'Die Entw\xFCrfe lassen sich jetzt unter \u201E1 \xB7 Bausteine" \xE4ndern. Der Prompt-Lauf friert den Stand ein, den er dabei vorfindet \u2014 dieselbe Aktion steht auch am Shot selbst, unter \u201E4 \xB7 Prompt".' }),
17223
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
17032
17224
  PromptStartAction,
17033
17225
  {
17034
17226
  port,
@@ -17036,12 +17228,12 @@ var ChainActions = ({ port, runId, recoveryId }) => {
17036
17228
  }
17037
17229
  )
17038
17230
  ] }),
17039
- meldung && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { className: "szenen-error", role: "alert", "data-testid": "kette-meldung", children: meldung })
17231
+ meldung && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "szenen-error", role: "alert", "data-testid": "kette-meldung", children: meldung })
17040
17232
  ] });
17041
17233
  };
17042
17234
 
17043
17235
  // src/features/szenen/areas/DeriveArea.tsx
17044
- var import_jsx_runtime46 = require("react/jsx-runtime");
17236
+ var import_jsx_runtime42 = require("react/jsx-runtime");
17045
17237
  var MODUS_TEXT = {
17046
17238
  prompt: "Nur Prompt",
17047
17239
  image: "Nur Bild",
@@ -17059,15 +17251,15 @@ var BelegFeld = ({ titel, text, testId, offen }) => {
17059
17251
  setKopiert(false);
17060
17252
  }
17061
17253
  }
17062
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("details", { open: offen, "data-testid": `belege-${testId}`, children: [
17063
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("summary", { children: [
17254
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("details", { open: offen, "data-testid": `belege-${testId}`, children: [
17255
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("summary", { children: [
17064
17256
  titel,
17065
17257
  " (",
17066
17258
  text.length,
17067
17259
  " Zeichen)"
17068
17260
  ] }),
17069
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-beleg__kopf", children: [
17070
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17261
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-beleg__kopf", children: [
17262
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17071
17263
  "button",
17072
17264
  {
17073
17265
  type: "button",
@@ -17076,9 +17268,9 @@ var BelegFeld = ({ titel, text, testId, offen }) => {
17076
17268
  children: "Kopieren"
17077
17269
  }
17078
17270
  ),
17079
- kopiert && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { "data-testid": `kopiert-${testId}`, children: "Kopiert." })
17271
+ kopiert && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-testid": `kopiert-${testId}`, children: "Kopiert." })
17080
17272
  ] }),
17081
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17273
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17082
17274
  "textarea",
17083
17275
  {
17084
17276
  ref: feld,
@@ -17092,8 +17284,8 @@ var BelegFeld = ({ titel, text, testId, offen }) => {
17092
17284
  )
17093
17285
  ] });
17094
17286
  };
17095
- var Belege = ({ lauf }) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-diagnose", "data-testid": "belege", children: [
17096
- lauf.raw_output ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17287
+ var Belege = ({ lauf }) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-diagnose", "data-testid": "belege", children: [
17288
+ lauf.raw_output ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17097
17289
  BelegFeld,
17098
17290
  {
17099
17291
  titel: "Rohantwort des Modells",
@@ -17104,9 +17296,9 @@ var Belege = ({ lauf }) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div",
17104
17296
  ) : (
17105
17297
  /* Kein Text ist auch eine Auskunft: Der Provider hat nichts geliefert,
17106
17298
  das Modell hat also nicht „falsch geantwortet". */
17107
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { "data-testid": "roh-antwort-fehlt", children: "Es gibt keine Rohantwort \u2014 der Lauf ist gescheitert, bevor ein Text ankam (etwa Netz, Sperre oder Providerfehler)." })
17299
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { "data-testid": "roh-antwort-fehlt", children: "Es gibt keine Rohantwort \u2014 der Lauf ist gescheitert, bevor ein Text ankam (etwa Netz, Sperre oder Providerfehler)." })
17108
17300
  ),
17109
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17301
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17110
17302
  BelegFeld,
17111
17303
  {
17112
17304
  titel: "Systemprompt dieses Laufs",
@@ -17118,18 +17310,18 @@ var Belege = ({ lauf }) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div",
17118
17310
  ] });
17119
17311
  var Herkunft = ({ lauf }) => {
17120
17312
  if (!lauf.follows) return null;
17121
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { className: "szenen-run__herkunft", "data-testid": "folgt-auf", children: [
17313
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { className: "szenen-run__herkunft", "data-testid": "folgt-auf", children: [
17122
17314
  "Folgt auf Lauf ",
17123
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { children: lauf.follows.run_id }),
17315
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("code", { children: lauf.follows.run_id }),
17124
17316
  ".",
17125
17317
  lauf.status === "succeeded" && " Im Folgelauf gelungen."
17126
17318
  ] });
17127
17319
  };
17128
17320
  var Widerspruch = ({ lauf }) => {
17129
17321
  if (!lauf.reply) return null;
17130
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-diagnose szenen-diagnose--antwort", "data-testid": "modell-antwort", children: [
17131
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h4", { children: "Das Modell antwortet auf die Beanstandung" }),
17132
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { children: lauf.reply })
17322
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-diagnose szenen-diagnose--antwort", "data-testid": "modell-antwort", children: [
17323
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h4", { children: "Das Modell antwortet auf die Beanstandung" }),
17324
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { children: lauf.reply })
17133
17325
  ] });
17134
17326
  };
17135
17327
  function readAsBase642(datei) {
@@ -17236,10 +17428,10 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17236
17428
  if (item.images.length === 1) onWaehlen(deckbild);
17237
17429
  else setFokus(item.itemRef === fokus ? null : item.itemRef);
17238
17430
  }
17239
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-bildwahl", "data-testid": "bildwahl", children: [
17240
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { className: "szenen-field", children: [
17241
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: "Bild aus dem Bestand" }),
17242
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17431
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-bildwahl", "data-testid": "bildwahl", children: [
17432
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "szenen-field", children: [
17433
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Bild aus dem Bestand" }),
17434
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17243
17435
  "input",
17244
17436
  {
17245
17437
  type: "search",
@@ -17253,23 +17445,23 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17253
17445
  }
17254
17446
  )
17255
17447
  ] }),
17256
- items.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { children: "Bilder werden geladen \u2026" }),
17257
- items.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { className: "szenen-error", role: "alert", children: [
17448
+ items.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { children: "Bilder werden geladen \u2026" }),
17449
+ items.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { className: "szenen-error", role: "alert", children: [
17258
17450
  "Der Bildbestand ist nicht lesbar: ",
17259
17451
  items.message
17260
17452
  ] }),
17261
- items.status === "ready" && werke.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "szenen-empty", "data-testid": "bildwahl-leer", children: "Zu dieser Suche gibt es kein Bild. Ohne Treffer bleibt der Upload." }),
17262
- werke.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17453
+ items.status === "ready" && werke.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "szenen-empty", "data-testid": "bildwahl-leer", children: "Zu dieser Suche gibt es kein Bild. Ohne Treffer bleibt der Upload." }),
17454
+ werke.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17263
17455
  "div",
17264
17456
  {
17265
17457
  className: "szenen-bildwahl__browser",
17266
17458
  ref: browser,
17267
17459
  "data-testid": "bildwahl-werke",
17268
- children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { className: "szenen-bildwahl__raster", children: werke.map((item) => {
17460
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "szenen-bildwahl__raster", children: werke.map((item) => {
17269
17461
  const deckbild = item.images[0];
17270
17462
  if (!deckbild) return null;
17271
17463
  const fokussiert = item.itemRef === fokus;
17272
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { "data-media-ref": deckbild.mediaRef, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
17464
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { "data-media-ref": deckbild.mediaRef, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
17273
17465
  "button",
17274
17466
  {
17275
17467
  type: "button",
@@ -17279,7 +17471,7 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17279
17471
  "data-testid": `item-waehlen-${item.itemRef}`,
17280
17472
  title: item.title,
17281
17473
  children: [
17282
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17474
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17283
17475
  Kachel,
17284
17476
  {
17285
17477
  quelle: quelleVon(deckbild),
@@ -17287,7 +17479,7 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17287
17479
  ...gescheitert.has(deckbild.mediaRef) ? { leerText: "Vorschau fehlt" } : {}
17288
17480
  }
17289
17481
  ),
17290
- item.images.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
17482
+ item.images.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
17291
17483
  "span",
17292
17484
  {
17293
17485
  className: "szenen-bildwahl__anzahl",
@@ -17298,22 +17490,22 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17298
17490
  ]
17299
17491
  }
17300
17492
  ),
17301
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "szenen-bildwahl__kacheltitel", children: item.title })
17493
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "szenen-bildwahl__kacheltitel", children: item.title })
17302
17494
  ]
17303
17495
  }
17304
17496
  ) }, item.itemRef);
17305
17497
  }) })
17306
17498
  }
17307
17499
  ),
17308
- werk && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("section", { className: "szenen-bildwahl__inspector", "data-testid": "bildwahl-galerie", children: [
17309
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("header", { className: "szenen-bildwahl__inspektorkopf", children: [
17310
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: "szenen-bildwahl__werktitel", "data-testid": "bildwahl-werktitel", children: [
17500
+ werk && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("section", { className: "szenen-bildwahl__inspector", "data-testid": "bildwahl-galerie", children: [
17501
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("header", { className: "szenen-bildwahl__inspektorkopf", children: [
17502
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "szenen-bildwahl__werktitel", "data-testid": "bildwahl-werktitel", children: [
17311
17503
  werk.title,
17312
17504
  " \xB7 ",
17313
17505
  werk.images.length,
17314
17506
  " Bilder"
17315
17507
  ] }),
17316
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17508
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17317
17509
  "button",
17318
17510
  {
17319
17511
  type: "button",
@@ -17324,7 +17516,7 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17324
17516
  }
17325
17517
  )
17326
17518
  ] }),
17327
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { className: "szenen-bildwahl__streifen", children: werk.images.map((bild, position) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
17519
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "szenen-bildwahl__streifen", children: werk.images.map((bild, position) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
17328
17520
  "button",
17329
17521
  {
17330
17522
  type: "button",
@@ -17334,7 +17526,7 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17334
17526
  "data-testid": `bild-waehlen-${bild.mediaRef}`,
17335
17527
  title: bild.title,
17336
17528
  children: [
17337
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17529
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17338
17530
  Kachel,
17339
17531
  {
17340
17532
  quelle: quelleVon(bild),
@@ -17342,7 +17534,7 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17342
17534
  ...gescheitert.has(bild.mediaRef) ? { leerText: "Vorschau fehlt" } : {}
17343
17535
  }
17344
17536
  ),
17345
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: "szenen-bildwahl__nummer", children: [
17537
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "szenen-bildwahl__nummer", children: [
17346
17538
  "#",
17347
17539
  position + 1
17348
17540
  ] })
@@ -17350,10 +17542,10 @@ var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
17350
17542
  }
17351
17543
  ) }, bild.mediaRef)) })
17352
17544
  ] }),
17353
- gewaehlt.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { className: "szenen-bildwahl__gewaehlt", "data-testid": "bilder-gewaehlt", children: gewaehlt.map((bild) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("li", { children: [
17354
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Kachel, { quelle: quelleVon(bild), alt: bild.title }),
17355
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "szenen-bildwahl__kacheltitel", children: bild.title }),
17356
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17545
+ gewaehlt.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "szenen-bildwahl__gewaehlt", "data-testid": "bilder-gewaehlt", children: gewaehlt.map((bild) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { children: [
17546
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Kachel, { quelle: quelleVon(bild), alt: bild.title }),
17547
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "szenen-bildwahl__kacheltitel", children: bild.title }),
17548
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17357
17549
  "button",
17358
17550
  {
17359
17551
  type: "button",
@@ -17389,6 +17581,15 @@ var DeriveArea = ({ port, contextId }) => {
17389
17581
  () => port.derivation.listPolicies(contextId),
17390
17582
  [port, contextId]
17391
17583
  );
17584
+ const sortiert = (0, import_react48.useMemo)(() => regeln.status === "ready" ? [...regeln.value].sort((a, b) => {
17585
+ const szeneZuerst = Number(b.produces.includes("scene")) - Number(a.produces.includes("scene"));
17586
+ return szeneZuerst !== 0 ? szeneZuerst : a.title.localeCompare(b.title, "de");
17587
+ }) : [], [regeln]);
17588
+ (0, import_react48.useEffect)(() => {
17589
+ if (policyVersionId) return;
17590
+ const fuerSzene = sortiert.find((r) => r.produces.includes("scene"));
17591
+ if (fuerSzene) setPolicyVersionId(fuerSzene.versionId);
17592
+ }, [sortiert, policyVersionId]);
17392
17593
  const gewaehlt = regeln.status === "ready" ? regeln.value.find((r) => r.versionId === policyVersionId) ?? null : null;
17393
17594
  async function starten(folgt) {
17394
17595
  if (!gewaehlt) {
@@ -17463,12 +17664,12 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17463
17664
  const text = beanstandung ?? vorgabeBeanstandung(satz);
17464
17665
  const bilderFehlen = satz.input.images.some((bild) => bild.kind !== "library");
17465
17666
  const bereit = Boolean(text.trim()) && !bilderFehlen && !laeuft;
17466
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-folgelauf", "data-testid": "folgelauf", children: [
17467
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h4", { children: "Korrektur- oder Erg\xE4nzungslauf" }),
17468
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "szenen-area__lead", children: satz.status === "failed" ? "Das Modell bekommt seine Antwort und die Beanstandung noch einmal vorgelegt." : "Das Ergebnis steht. Wenn etwas offen blieb, kann das Modell nachbessern." }),
17469
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { className: "szenen-field", children: [
17470
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: "Beanstandung" }),
17471
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17667
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-folgelauf", "data-testid": "folgelauf", children: [
17668
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h4", { children: "Korrektur- oder Erg\xE4nzungslauf" }),
17669
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "szenen-area__lead", children: satz.status === "failed" ? "Das Modell bekommt seine Antwort und die Beanstandung noch einmal vorgelegt." : "Das Ergebnis steht. Wenn etwas offen blieb, kann das Modell nachbessern." }),
17670
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "szenen-field", children: [
17671
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Beanstandung" }),
17672
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17472
17673
  "textarea",
17473
17674
  {
17474
17675
  rows: 4,
@@ -17478,9 +17679,9 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17478
17679
  }
17479
17680
  )
17480
17681
  ] }),
17481
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { className: "szenen-field", children: [
17482
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: "Zus\xE4tzliche Anweisung (optional)" }),
17483
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17682
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "szenen-field", children: [
17683
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Zus\xE4tzliche Anweisung (optional)" }),
17684
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17484
17685
  "textarea",
17485
17686
  {
17486
17687
  rows: 2,
@@ -17491,8 +17692,8 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17491
17692
  }
17492
17693
  )
17493
17694
  ] }),
17494
- bilderFehlen && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "szenen-error", "data-testid": "folgelauf-ohne-bild", children: "Die Bilder dieses Laufs wurden nicht im Bestand abgelegt \u2014 er ist \xE4lter als diese M\xF6glichkeit. Ein Folgelauf ohne sie k\xF6nnte ein fehlendes Pflichtfeld nur erfinden. Starten Sie einen neuen Lauf mit dem Referenzbild; dessen Bilder bleiben dann erhalten." }),
17495
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17695
+ bilderFehlen && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "szenen-error", "data-testid": "folgelauf-ohne-bild", children: "Die Bilder dieses Laufs wurden nicht im Bestand abgelegt \u2014 er ist \xE4lter als diese M\xF6glichkeit. Ein Folgelauf ohne sie k\xF6nnte ein fehlendes Pflichtfeld nur erfinden. Starten Sie einen neuen Lauf mit dem Referenzbild; dessen Bilder bleiben dann erhalten." }),
17696
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17496
17697
  "button",
17497
17698
  {
17498
17699
  type: "button",
@@ -17532,19 +17733,19 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17532
17733
  setMeldung(ergebnis.message);
17533
17734
  }
17534
17735
  }
17535
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-derive", "data-testid": "bereich-rekonstruktion", children: [
17536
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "szenen-area__lead", children: "Aus einem Referenzbild, einem Prompt oder beidem einen Szenenentwurf vorschlagen lassen. Der Vorschlag wird nie ver\xF6ffentlicht \u2014 er wird ein Entwurf, den Sie pr\xFCfen und \xE4ndern." }),
17537
- regeln.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { children: "Regeln werden geladen \u2026" }),
17538
- regeln.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { className: "szenen-error", role: "alert", children: [
17736
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-derive", "data-testid": "bereich-rekonstruktion", children: [
17737
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "szenen-area__lead", children: "Aus einem Referenzbild, einem Prompt oder beidem einen Szenenentwurf vorschlagen lassen. Der Vorschlag wird nie ver\xF6ffentlicht \u2014 er wird ein Entwurf, den Sie pr\xFCfen und \xE4ndern." }),
17738
+ regeln.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { children: "Regeln werden geladen \u2026" }),
17739
+ regeln.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { className: "szenen-error", role: "alert", children: [
17539
17740
  "Die Regeln konnten nicht gelesen werden: ",
17540
17741
  regeln.message
17541
17742
  ] }),
17542
- regeln.status === "ready" && regeln.value.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { className: "szenen-empty", children: [
17743
+ regeln.status === "ready" && regeln.value.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { className: "szenen-empty", children: [
17543
17744
  "In diesem Kontext ist keine Rekonstruktionsregel (",
17544
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { children: "scene_derivation_policy" }),
17545
- ') ver\xF6ffentlicht. Ohne sie ist kein Lauf reproduzierbar \u2014 legen Sie hier eine unter \u201E1 \xB7 Bausteine" an, oder wechseln Sie in den Kontext, in dem Ihre Regel liegt. Regeln aus anderen Kontexten stehen hier bewusst nicht zur Auswahl.'
17745
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("code", { children: "scene_derivation_policy" }),
17746
+ ') ver\xF6ffentlicht. Ohne sie ist kein Lauf reproduzierbar \u2014 legen Sie eine unter \u201EErweitert" an, oder wechseln Sie in den Kontext, in dem Ihre Regel liegt. Regeln aus anderen Kontexten stehen hier bewusst nicht zur Auswahl.'
17546
17747
  ] }),
17547
- regeln.status === "ready" && regeln.value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
17748
+ regeln.status === "ready" && regeln.value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
17548
17749
  "form",
17549
17750
  {
17550
17751
  className: "szenen-form",
@@ -17553,17 +17754,17 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17553
17754
  void starten();
17554
17755
  },
17555
17756
  children: [
17556
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { className: "szenen-field", children: [
17557
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: "Regel" }),
17558
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
17757
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "szenen-field", children: [
17758
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Regel" }),
17759
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
17559
17760
  "select",
17560
17761
  {
17561
17762
  value: policyVersionId,
17562
17763
  onChange: (e) => setPolicyVersionId(e.target.value),
17563
17764
  "data-testid": "regel-auswahl",
17564
17765
  children: [
17565
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: "", children: "\u2014 bitte w\xE4hlen \u2014" }),
17566
- regeln.value.map((regel) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("option", { value: regel.versionId, children: [
17766
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("option", { value: "", children: "\u2014 bitte w\xE4hlen \u2014" }),
17767
+ sortiert.map((regel) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("option", { value: regel.versionId, children: [
17567
17768
  regel.title,
17568
17769
  " (",
17569
17770
  regel.produces.join(", "),
@@ -17573,21 +17774,21 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17573
17774
  }
17574
17775
  )
17575
17776
  ] }),
17576
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { className: "szenen-field", children: [
17577
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: "Eingang" }),
17578
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17777
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "szenen-field", children: [
17778
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Eingang" }),
17779
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17579
17780
  "select",
17580
17781
  {
17581
17782
  value: modus,
17582
17783
  onChange: (e) => setModus(e.target.value),
17583
17784
  "data-testid": "modus-auswahl",
17584
- children: (gewaehlt ? gewaehlt.inputModes : ["prompt", "image", "image_prompt"]).map((m) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: m, children: MODUS_TEXT[m] }, m))
17785
+ children: (gewaehlt ? gewaehlt.inputModes : ["prompt", "image", "image_prompt"]).map((m) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("option", { value: m, children: MODUS_TEXT[m] }, m))
17585
17786
  }
17586
17787
  )
17587
17788
  ] }),
17588
- modus !== "image" && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { className: "szenen-field", children: [
17589
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: "Referenzprompt" }),
17590
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17789
+ modus !== "image" && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "szenen-field", children: [
17790
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Referenzprompt" }),
17791
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17591
17792
  "textarea",
17592
17793
  {
17593
17794
  value: promptText,
@@ -17597,8 +17798,8 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17597
17798
  }
17598
17799
  )
17599
17800
  ] }),
17600
- modus !== "prompt" && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
17601
- port.imageLibrary && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17801
+ modus !== "prompt" && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
17802
+ port.imageLibrary && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17602
17803
  BildWaehler,
17603
17804
  {
17604
17805
  port,
@@ -17607,9 +17808,9 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17607
17808
  onEntfernen: (ref) => setBestandsbilder((v) => v.filter((b) => b.mediaRef !== ref))
17608
17809
  }
17609
17810
  ),
17610
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { className: "szenen-field", children: [
17611
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: port.imageLibrary ? "Oder hochladen" : "Referenzbild" }),
17612
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17811
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "szenen-field", children: [
17812
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: port.imageLibrary ? "Oder hochladen" : "Referenzbild" }),
17813
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17613
17814
  "input",
17614
17815
  {
17615
17816
  type: "file",
@@ -17619,40 +17820,40 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17619
17820
  "data-testid": "bild-eingabe"
17620
17821
  }
17621
17822
  ),
17622
- port.imageLibrary && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("small", { "data-testid": "upload-hinweis", children: "Hochgeladene Bilder werden im Bestand abgelegt \u2014 nur so ist sp\xE4ter ein Folgelauf damit m\xF6glich." })
17823
+ port.imageLibrary && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("small", { "data-testid": "upload-hinweis", children: "Hochgeladene Bilder werden im Bestand abgelegt \u2014 nur so ist sp\xE4ter ein Folgelauf damit m\xF6glich." })
17623
17824
  ] })
17624
17825
  ] }),
17625
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "submit", disabled: !gewaehlt || laeuft, "data-testid": "lauf-starten", children: laeuft ? "Rekonstruiert \u2026" : "Rekonstruieren" })
17826
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "submit", disabled: !gewaehlt || laeuft, "data-testid": "lauf-starten", children: laeuft ? "Rekonstruiert \u2026" : "Rekonstruieren" })
17626
17827
  ]
17627
17828
  }
17628
17829
  ),
17629
- meldung && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "szenen-error", role: "alert", "data-testid": "derive-meldung", children: meldung }),
17630
- lauf && lauf.status === "failed" && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("section", { className: "szenen-run szenen-run--failed", "data-testid": "lauf-fehler", children: [
17631
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h3", { children: "Der Lauf ist gescheitert" }),
17632
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { children: [
17633
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("strong", { children: lauf.failure?.class }),
17830
+ meldung && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "szenen-error", role: "alert", "data-testid": "derive-meldung", children: meldung }),
17831
+ lauf && lauf.status === "failed" && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("section", { className: "szenen-run szenen-run--failed", "data-testid": "lauf-fehler", children: [
17832
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h3", { children: "Der Lauf ist gescheitert" }),
17833
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { children: [
17834
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("strong", { children: lauf.failure?.class }),
17634
17835
  " \u2014 ",
17635
17836
  lauf.failure?.message
17636
17837
  ] }),
17637
- lauf.failure?.diagnostics && lauf.failure.diagnostics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-diagnose", "data-testid": "lauf-diagnosen", children: [
17638
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h4", { children: "Befunde" }),
17639
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Diagnosen, { befunde: lauf.failure.diagnostics, testId: "lauf-befunde" })
17838
+ lauf.failure?.diagnostics && lauf.failure.diagnostics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-diagnose", "data-testid": "lauf-diagnosen", children: [
17839
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h4", { children: "Befunde" }),
17840
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Diagnosen, { befunde: lauf.failure.diagnostics, testId: "lauf-befunde" })
17640
17841
  ] }),
17641
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Herkunft, { lauf }),
17642
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Widerspruch, { lauf }),
17643
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Belege, { lauf }),
17644
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(RecoveryPanel, { port, lauf }),
17842
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Herkunft, { lauf }),
17843
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Widerspruch, { lauf }),
17844
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Belege, { lauf }),
17845
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RecoveryPanel, { port, lauf }),
17645
17846
  folgeblock(lauf)
17646
17847
  ] }),
17647
- lauf && lauf.status === "succeeded" && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("section", { className: "szenen-run", "data-testid": "lauf-ergebnis", children: [
17648
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h3", { children: "Vorschlag" }),
17649
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Herkunft, { lauf }),
17650
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Widerspruch, { lauf }),
17651
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "szenen-run__feedback", children: lauf.feedback }),
17652
- lauf.conflicts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-diagnose szenen-diagnose--konflikt", "data-testid": "konflikte", children: [
17653
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h4", { children: "Widerspr\xFCche zwischen Bild und Prompt" }),
17654
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { children: lauf.conflicts.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("li", { children: [
17655
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { children: k.field }),
17848
+ lauf && lauf.status === "succeeded" && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("section", { className: "szenen-run", "data-testid": "lauf-ergebnis", children: [
17849
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h3", { children: "Vorschlag" }),
17850
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Herkunft, { lauf }),
17851
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Widerspruch, { lauf }),
17852
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "szenen-run__feedback", children: lauf.feedback }),
17853
+ lauf.conflicts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-diagnose szenen-diagnose--konflikt", "data-testid": "konflikte", children: [
17854
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h4", { children: "Widerspr\xFCche zwischen Bild und Prompt" }),
17855
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { children: lauf.conflicts.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { children: [
17856
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("code", { children: k.field }),
17656
17857
  ": Bild sagt \u201E",
17657
17858
  k.from_image,
17658
17859
  '", Prompt sagt \u201E',
@@ -17660,31 +17861,31 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17660
17861
  '". Nichts davon wurde \xFCbernommen.'
17661
17862
  ] }, i)) })
17662
17863
  ] }),
17663
- lauf.not_derivable.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-diagnose", "data-testid": "nicht-ableitbar", children: [
17664
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h4", { children: "Nicht ableitbar" }),
17665
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { children: lauf.not_derivable.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("li", { children: [
17666
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { children: n.field }),
17864
+ lauf.not_derivable.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-diagnose", "data-testid": "nicht-ableitbar", children: [
17865
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h4", { children: "Nicht ableitbar" }),
17866
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { children: lauf.not_derivable.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { children: [
17867
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("code", { children: n.field }),
17667
17868
  ": ",
17668
17869
  n.reason
17669
17870
  ] }, i)) })
17670
17871
  ] }),
17671
- lauf.assumptions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-diagnose", "data-testid": "annahmen", children: [
17672
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h4", { children: "Erschlossen, nicht belegt" }),
17673
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { children: lauf.assumptions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { children: a }, i)) })
17872
+ lauf.assumptions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "szenen-diagnose", "data-testid": "annahmen", children: [
17873
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h4", { children: "Erschlossen, nicht belegt" }),
17874
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { children: lauf.assumptions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { children: a }, i)) })
17674
17875
  ] }),
17675
17876
  lauf.proposals.map((vorschlag) => {
17676
17877
  const bezug = REFERENCE_FIELD[vorschlag.type];
17677
17878
  const fertig = uebernommen[vorschlag.type];
17678
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
17879
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
17679
17880
  "article",
17680
17881
  {
17681
17882
  className: "szenen-proposal",
17682
17883
  "data-testid": `vorschlag-${vorschlag.type}`,
17683
17884
  children: [
17684
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h4", { children: vorschlag.type }),
17685
- !fertig && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { className: "szenen-field", children: [
17686
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: "Titel" }),
17687
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17885
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h4", { children: vorschlag.type }),
17886
+ !fertig && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "szenen-field", children: [
17887
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Titel" }),
17888
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17688
17889
  "input",
17689
17890
  {
17690
17891
  type: "text",
@@ -17698,9 +17899,9 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17698
17899
  }
17699
17900
  )
17700
17901
  ] }),
17701
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("pre", { className: "szenen-proposal__payload", children: JSON.stringify(vorschlag.payload, null, 2) }),
17702
- bezug && !fertig && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
17703
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17902
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("pre", { className: "szenen-proposal__payload", children: JSON.stringify(vorschlag.payload, null, 2) }),
17903
+ bezug && !fertig && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
17904
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17704
17905
  ReferencePicker,
17705
17906
  {
17706
17907
  port,
@@ -17714,30 +17915,30 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17714
17915
  }))
17715
17916
  }
17716
17917
  ),
17717
- referenzen[vorschlag.type] ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { "data-testid": `referenz-gewaehlt-${vorschlag.type}`, children: [
17918
+ referenzen[vorschlag.type] ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { "data-testid": `referenz-gewaehlt-${vorschlag.type}`, children: [
17718
17919
  "Zeigt auf Version ",
17719
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { children: referenzen[vorschlag.type] }),
17920
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("code", { children: referenzen[vorschlag.type] }),
17720
17921
  "."
17721
17922
  ] }) : (
17722
17923
  /* Der Grund steht **vor** der Handlung. Den Nutzer erst
17723
17924
  auf einen Knopf drücken zu lassen, um ihm dann eine
17724
17925
  Ablehnung zu zeigen, ist keine Auskunft. */
17725
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { "data-testid": `referenz-fehlt-${vorschlag.type}`, children: [
17926
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { "data-testid": `referenz-fehlt-${vorschlag.type}`, children: [
17726
17927
  "Diese ",
17727
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("strong", { children: "Einzel\xFCbernahme" }),
17928
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("strong", { children: "Einzel\xFCbernahme" }),
17728
17929
  " braucht eine ver\xF6ffentlichte ",
17729
17930
  bezug.refersTo,
17730
17931
  "-Version, auf die der Entwurf zeigt (D-03) \u2014 sie ist der Weg in einen bestehenden, l\xE4ngst ver\xF6ffentlichten Zusammenhang. Wollen Sie aus diesem Lauf erst einmal einen Prompt ausprobieren, nehmen Sie unten die ",
17731
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("strong", { children: "Draft-Kette" }),
17932
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("strong", { children: "Draft-Kette" }),
17732
17933
  ": Dort entstehen Szene, Aufbau und Shot zusammenh\xE4ngend, und es muss nichts ver\xF6ffentlicht werden."
17733
17934
  ] })
17734
17935
  )
17735
17936
  ] }),
17736
- fertig ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { className: "szenen-proposal__done", children: [
17937
+ fertig ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { className: "szenen-proposal__done", children: [
17737
17938
  "Als Entwurf \xFCbernommen: ",
17738
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { children: fertig }),
17939
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("code", { children: fertig }),
17739
17940
  ". Er ist noch nicht ver\xF6ffentlicht."
17740
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
17941
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
17741
17942
  "button",
17742
17943
  {
17743
17944
  type: "button",
@@ -17752,13 +17953,337 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
17752
17953
  vorschlag.type
17753
17954
  );
17754
17955
  }),
17755
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(ChainActions, { port, runId: lauf.run_id }),
17756
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Belege, { lauf }),
17956
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChainActions, { port, runId: lauf.run_id }),
17957
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Belege, { lauf }),
17757
17958
  folgeblock(lauf)
17758
17959
  ] })
17759
17960
  ] });
17760
17961
  };
17761
17962
 
17963
+ // src/features/szenen/areas/SceneArea.tsx
17964
+ var import_jsx_runtime43 = require("react/jsx-runtime");
17965
+ var SceneArea = ({ port, contextId }) => {
17966
+ const kannAbleiten = capabilitiesOf(port).derivation;
17967
+ const [ableiten, setAbleiten] = (0, import_react49.useState)(false);
17968
+ const zeigeDerive = kannAbleiten && ableiten;
17969
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "szenen-scene", "data-testid": "bereich-szene", children: [
17970
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "szenen-elements__toolbar", children: [
17971
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "szenen-area__lead", children: "Szenen dieses Kontexts. Auswahl \xF6ffnet die Bearbeitung der Nodes und Beziehungen und startet Prompt-L\xE4ufe." }),
17972
+ kannAbleiten && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
17973
+ "button",
17974
+ {
17975
+ type: "button",
17976
+ className: "szenen-elements__new",
17977
+ "data-testid": "neu-aus-bild-text",
17978
+ "aria-expanded": ableiten,
17979
+ onClick: () => setAbleiten((offen) => !offen),
17980
+ children: ableiten ? "Zur\xFCck zu den Szenen" : "+ Neu aus Bild/Text"
17981
+ }
17982
+ )
17983
+ ] }),
17984
+ !kannAbleiten && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "szenen-view__capability-note", "data-testid": "hinweis-keine-rekonstruktion", children: "Aus Bild oder Text l\xE4sst sich in diesem Host keine Szene ableiten." }),
17985
+ zeigeDerive ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(DeriveArea, { port, contextId }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(ElementsArea, { port, contextId, restrictToTypes: ["scene"] })
17986
+ ] });
17987
+ };
17988
+
17989
+ // src/features/szenen/areas/AdvancedArea.tsx
17990
+ var import_jsx_runtime44 = require("react/jsx-runtime");
17991
+ var AdvancedArea = ({ port, contextId }) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "szenen-erweitert", "data-testid": "bereich-erweitert", children: [
17992
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "szenen-area__lead", children: "Optionale Pr\xE4zisierung: Bildaufbau und Shot, Kameras und Stile der Aufnahme, Zielmodelle, Rekonstruktionsregeln und Sequenzen. F\xFCr den normalen Weg von der Szene zum Bild wird hier nichts gebraucht." }),
17993
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
17994
+ ElementsArea,
17995
+ {
17996
+ port,
17997
+ contextId,
17998
+ restrictToTypes: ERWEITERT_TYPES,
17999
+ allTypesLabel: "alle erweiterten Typen",
18000
+ listTitle: "Erweitert"
18001
+ }
18002
+ )
18003
+ ] });
18004
+
18005
+ // src/features/szenen/areas/PromptsArea.tsx
18006
+ var import_react50 = require("react");
18007
+ var import_jsx_runtime45 = require("react/jsx-runtime");
18008
+ var HERKUNFT_TEXT2 = {
18009
+ published: "aus ver\xF6ffentlichtem Shot",
18010
+ draft_chain: "aus Draft-Kette",
18011
+ scene_direct: "direkt aus Szene",
18012
+ unresolved: "Herkunft unvollst\xE4ndig"
18013
+ };
18014
+ var PromptsArea = ({ port, contextId }) => {
18015
+ const faehig = capabilitiesOf(port);
18016
+ const [nachladen, setNachladen] = (0, import_react50.useState)(0);
18017
+ const [offen, setOffen] = (0, import_react50.useState)(null);
18018
+ const [alleKontexte, setAlleKontexte] = (0, import_react50.useState)(false);
18019
+ const [mitFehllaeufen, setMitFehllaeufen] = (0, import_react50.useState)(false);
18020
+ const [herkunft, setHerkunft] = (0, import_react50.useState)("alle");
18021
+ const [phase] = usePortQuery(
18022
+ () => port.runs.listAllRuns ? port.runs.listAllRuns({ status: "all" }) : Promise.resolve({
18023
+ kind: "ok",
18024
+ value: { runs: [], hiddenByStatus: 0, hiddenByContext: 0, withoutContext: 0 }
18025
+ }),
18026
+ [port, nachladen]
18027
+ );
18028
+ const alle = phase.status === "ready" ? phase.value.runs : [];
18029
+ const sicht = (0, import_react50.useMemo)(() => selectPromptRuns(alle, {
18030
+ status: mitFehllaeufen ? "all" : "succeeded",
18031
+ ...alleKontexte ? {} : { contextId }
18032
+ }), [alle, mitFehllaeufen, alleKontexte, contextId]);
18033
+ const titel = useLaufTitel(port, alle);
18034
+ const gezeigt = (0, import_react50.useMemo)(
18035
+ () => herkunft === "alle" ? sicht.runs : sicht.runs.filter((lauf) => groupOf(lauf).kind === herkunft),
18036
+ [sicht.runs, herkunft]
18037
+ );
18038
+ if (!faehig.promptOverview) {
18039
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "szenen-prompts", "data-testid": "prompts-bereich", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { "data-testid": "hinweis-keine-promptsicht", children: "Dieser Host bietet keine herkunfts\xFCbergreifende Prompt-Liste an. Prompt-L\xE4ufe bleiben im Detail ihrer Szene bzw. ihres Shots erreichbar \u2014 es ist kein Fehler und kein vor\xFCbergehender Zustand." }) });
18040
+ }
18041
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "szenen-prompts", "data-testid": "prompts-bereich", children: [
18042
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "szenen-prompts__toolbar", children: [
18043
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("h3", { className: "szenen-prompts__heading", children: "Prompts" }),
18044
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("label", { className: "szenen-prompts__filter", children: [
18045
+ "Herkunft",
18046
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
18047
+ "select",
18048
+ {
18049
+ "data-testid": "prompts-herkunft",
18050
+ value: herkunft,
18051
+ onChange: (e) => setHerkunft(e.target.value),
18052
+ children: [
18053
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("option", { value: "alle", children: "alle" }),
18054
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("option", { value: "published", children: HERKUNFT_TEXT2.published }),
18055
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("option", { value: "draft_chain", children: HERKUNFT_TEXT2.draft_chain }),
18056
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("option", { value: "scene_direct", children: HERKUNFT_TEXT2.scene_direct }),
18057
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("option", { value: "unresolved", children: HERKUNFT_TEXT2.unresolved })
18058
+ ]
18059
+ }
18060
+ )
18061
+ ] }),
18062
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("label", { className: "szenen-prompts__filter", children: [
18063
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
18064
+ "input",
18065
+ {
18066
+ type: "checkbox",
18067
+ "data-testid": "prompts-alle-kontexte",
18068
+ checked: alleKontexte,
18069
+ onChange: (e) => setAlleKontexte(e.target.checked)
18070
+ }
18071
+ ),
18072
+ "Alle Kontexte"
18073
+ ] }),
18074
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("label", { className: "szenen-prompts__filter", children: [
18075
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
18076
+ "input",
18077
+ {
18078
+ type: "checkbox",
18079
+ "data-testid": "prompts-mit-fehllaeufen",
18080
+ checked: mitFehllaeufen,
18081
+ onChange: (e) => setMitFehllaeufen(e.target.checked)
18082
+ }
18083
+ ),
18084
+ "Fehll\xE4ufe zeigen"
18085
+ ] })
18086
+ ] }),
18087
+ phase.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { "data-testid": "prompts-laden", children: "Prompts werden geladen \u2026" }),
18088
+ phase.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("p", { "data-testid": "prompts-fehler", role: "alert", children: [
18089
+ "Prompts konnten nicht geladen werden: ",
18090
+ phase.message
18091
+ ] }),
18092
+ phase.status === "ready" && /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
18093
+ gezeigt.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { "data-testid": "keine-prompts", children: alle.length === 0 ? "Es gibt noch keinen Prompt-Lauf." : `Kein Prompt passt zu dieser Auswahl \u2014 von ${alle.length} vorhandenen L\xE4ufen bleibt keiner \xFCbrig.` }),
18094
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ol", { "data-testid": "prompts-liste", children: gezeigt.map((lauf) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
18095
+ PromptEintrag,
18096
+ {
18097
+ port,
18098
+ lauf,
18099
+ alle,
18100
+ titel: titel.get(lauf.run_id) ?? null,
18101
+ offen: lauf.run_id === offen,
18102
+ onUmschalten: () => setOffen(
18103
+ lauf.run_id === offen ? null : lauf.run_id
18104
+ ),
18105
+ itemMoeglich: faehig.saveRunAsItem,
18106
+ onItemGespeichert: () => setNachladen((n) => n + 1)
18107
+ },
18108
+ lauf.run_id
18109
+ )) }),
18110
+ (sicht.hiddenByContext > 0 || sicht.withoutContext > 0 || sicht.hiddenByStatus > 0) && /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("p", { className: "szenen-prompts__ausgeblendet", "data-testid": "prompts-ausgeblendet", children: [
18111
+ sicht.hiddenByStatus > 0 && `${sicht.hiddenByStatus} Fehllauf/-l\xE4ufe ausgeblendet. `,
18112
+ sicht.hiddenByContext > 0 && `${sicht.hiddenByContext} Lauf/L\xE4ufe geh\xF6ren zu einem anderen Kontext. `,
18113
+ sicht.withoutContext > 0 && `${sicht.withoutContext} Lauf/L\xE4ufe nennen keinen Kontext und werden keinem zugeordnet \u2014 \u201EAlle Kontexte" zeigt sie. `
18114
+ ] })
18115
+ ] })
18116
+ ] });
18117
+ };
18118
+ var PromptEintrag = ({ port, lauf, alle, titel, offen, onUmschalten, itemMoeglich, onItemGespeichert }) => {
18119
+ const gruppe = groupOf(lauf);
18120
+ const runde = roundOfRun(lauf, siblingsOf(lauf, alle));
18121
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("li", { className: "szenen-prompts__eintrag", "data-testid": `prompt-${lauf.run_id}`, children: [
18122
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("p", { className: "szenen-prompts__herkunft", "data-testid": `herkunft-${lauf.run_id}`, children: [
18123
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { "data-herkunft": gruppe.kind, children: HERKUNFT_TEXT2[gruppe.kind] }),
18124
+ gruppe.kind === "unresolved" && // D-75: Was nicht belegbar ist, wird benannt und nicht
18125
+ // durch eine plausible Kennung ersetzt.
18126
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "szenen-prompts__hinweis", children: [
18127
+ " \u2014 ",
18128
+ gruppe.reason
18129
+ ] })
18130
+ ] }),
18131
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ol", { className: "szenen-prompts__lauf", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
18132
+ RundenEintrag,
18133
+ {
18134
+ port,
18135
+ lauf,
18136
+ runde,
18137
+ titel,
18138
+ offen,
18139
+ onUmschalten,
18140
+ itemMoeglich,
18141
+ onItemGespeichert
18142
+ }
18143
+ ) })
18144
+ ] });
18145
+ };
18146
+
18147
+ // src/features/szenen/areas/MediaArea.tsx
18148
+ var import_react51 = require("react");
18149
+ var import_jsx_runtime46 = require("react/jsx-runtime");
18150
+ var leer = async () => ({ kind: "ok", value: [] });
18151
+ var MediaArea = ({ port }) => {
18152
+ const media = port.media;
18153
+ const [versionInput, setVersionInput] = (0, import_react51.useState)("");
18154
+ const [versionId, setVersionId] = (0, import_react51.useState)("");
18155
+ const [itemInput, setItemInput] = (0, import_react51.useState)("");
18156
+ const [itemId, setItemId] = (0, import_react51.useState)("");
18157
+ const [assetId, setAssetId] = (0, import_react51.useState)("");
18158
+ const [role, setRole] = (0, import_react51.useState)("reference");
18159
+ const [position, setPosition] = (0, import_react51.useState)("0");
18160
+ const [notiz, setNotiz] = (0, import_react51.useState)(null);
18161
+ const [busy, setBusy] = (0, import_react51.useState)(false);
18162
+ const [refresh, setRefresh] = (0, import_react51.useState)(0);
18163
+ const [versionPhase] = usePortQuery(
18164
+ () => versionId ? media.listVersionMedia(versionId) : leer(),
18165
+ [media, versionId, refresh]
18166
+ );
18167
+ const [itemPhase] = usePortQuery(
18168
+ () => itemId ? media.listItemMedia(itemId) : Promise.resolve({ kind: "ok", value: [] }),
18169
+ [media, itemId, refresh]
18170
+ );
18171
+ const zuordnen = async () => {
18172
+ if (!versionInput.trim() || !assetId.trim()) {
18173
+ setNotiz({ tone: "error", text: "Version-ID und Asset-ID sind erforderlich." });
18174
+ return;
18175
+ }
18176
+ const parsedPosition = Number(position);
18177
+ if (!Number.isInteger(parsedPosition) || parsedPosition < 0) {
18178
+ setNotiz({ tone: "error", text: `Position muss eine nicht-negative Ganzzahl sein, war ${JSON.stringify(position)}.` });
18179
+ return;
18180
+ }
18181
+ setBusy(true);
18182
+ const result = await media.attachVersionMedium({
18183
+ versionId: versionInput.trim(),
18184
+ assetId: assetId.trim(),
18185
+ role,
18186
+ position: parsedPosition
18187
+ });
18188
+ setNotiz(noticeFor(result, "Referenzmedium zuordnen"));
18189
+ if (result.kind === "ok") {
18190
+ setVersionId(versionInput.trim());
18191
+ setRefresh((value) => value + 1);
18192
+ }
18193
+ setBusy(false);
18194
+ };
18195
+ const entfernen = async (medium) => {
18196
+ setBusy(true);
18197
+ const result = await media.detachVersionMedium({
18198
+ versionId,
18199
+ assetId: medium.assetId,
18200
+ role: medium.role
18201
+ });
18202
+ setNotiz(noticeFor(result, "Referenzmedium entfernen"));
18203
+ if (result.kind === "ok") setRefresh((value) => value + 1);
18204
+ setBusy(false);
18205
+ };
18206
+ const versionMedien = versionPhase.status === "ready" ? versionPhase.value : [];
18207
+ const itemMedien = itemPhase.status === "ready" ? itemPhase.value : [];
18208
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-media", "data-testid": "bereich-medien", children: [
18209
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "szenen-area__lead", children: "Bereits vorhandene Medien-Assets einer Elementversion zuordnen. Hochgeladen wird hier nichts \u2014 Medien entstehen \xFCber den bestehenden Speicherweg aus Stufe 5." }),
18210
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("section", { children: [
18211
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h3", { children: "Version" }),
18212
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-media__lookup", children: [
18213
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { children: [
18214
+ "Version-ID",
18215
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("input", { value: versionInput, onChange: (event) => setVersionInput(event.target.value) })
18216
+ ] }),
18217
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => setVersionId(versionInput.trim()), children: "Medien laden" })
18218
+ ] }),
18219
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-media__attach", children: [
18220
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { children: [
18221
+ "Asset-ID",
18222
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("input", { value: assetId, onChange: (event) => setAssetId(event.target.value) })
18223
+ ] }),
18224
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { children: [
18225
+ "Rolle",
18226
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("select", { value: role, onChange: (event) => setRole(event.target.value), children: [
18227
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: "reference", children: "reference" }),
18228
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: "mask", children: "mask" }),
18229
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: "palette", children: "palette" }),
18230
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: "example", children: "example" })
18231
+ ] })
18232
+ ] }),
18233
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { children: [
18234
+ "Position",
18235
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("input", { type: "number", min: "0", value: position, onChange: (event) => setPosition(event.target.value) })
18236
+ ] }),
18237
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", disabled: busy, onClick: zuordnen, children: "zuordnen" })
18238
+ ] }),
18239
+ versionPhase.status === "loading" && versionId && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { children: "Versionsmedien werden geladen \u2026" }),
18240
+ versionPhase.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { role: "alert", children: [
18241
+ "Versionsmedien konnten nicht geladen werden: ",
18242
+ versionPhase.message
18243
+ ] }),
18244
+ versionPhase.status === "ready" && versionId && versionMedien.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { children: [
18245
+ "Version ",
18246
+ versionId,
18247
+ " hat keine Medien."
18248
+ ] }),
18249
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { className: "szenen-media__list", children: versionMedien.map((medium) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("li", { children: [
18250
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { children: medium.assetId }),
18251
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { children: [
18252
+ medium.role,
18253
+ " \xB7 Position ",
18254
+ medium.position
18255
+ ] }),
18256
+ medium.url && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("a", { href: medium.url, target: "_blank", rel: "noreferrer", children: "\xF6ffnen" }),
18257
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", disabled: busy, onClick: () => entfernen(medium), children: "entfernen" })
18258
+ ] }, `${medium.assetId}:${medium.role}`)) })
18259
+ ] }),
18260
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("section", { children: [
18261
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("h3", { children: "Item-Medien" }),
18262
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "szenen-media__lookup", children: [
18263
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { children: [
18264
+ "Item-ID",
18265
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("input", { value: itemInput, onChange: (event) => setItemInput(event.target.value) })
18266
+ ] }),
18267
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => setItemId(itemInput.trim()), children: "Item-Medien laden" })
18268
+ ] }),
18269
+ itemPhase.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { role: "alert", children: [
18270
+ "Item-Medien konnten nicht geladen werden: ",
18271
+ itemPhase.message
18272
+ ] }),
18273
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { className: "szenen-media__list", children: itemMedien.map((medium) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("li", { children: [
18274
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("code", { children: medium.assetId }),
18275
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { children: [
18276
+ medium.role,
18277
+ " \xB7 Position ",
18278
+ medium.position
18279
+ ] }),
18280
+ medium.url && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("a", { href: medium.url, target: "_blank", rel: "noreferrer", children: "\xF6ffnen" })
18281
+ ] }, `${medium.assetId}:${medium.role}:${medium.position}`)) })
18282
+ ] }),
18283
+ notiz && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { role: notiz.tone === "error" ? "alert" : "status", "data-tone": notiz.tone, children: notiz.text })
18284
+ ] });
18285
+ };
18286
+
17762
18287
  // src/features/szenen/SzenenView.tsx
17763
18288
  var import_jsx_runtime47 = require("react/jsx-runtime");
17764
18289
  var SzenenView = ({
@@ -17767,9 +18292,9 @@ var SzenenView = ({
17767
18292
  initialArea,
17768
18293
  onAreaChange
17769
18294
  }) => {
17770
- const bereiche = (0, import_react49.useMemo)(() => areasFor(port), [port]);
17771
- const faehig = (0, import_react49.useMemo)(() => capabilitiesOf(port), [port]);
17772
- const [aktiv, setAktiv] = (0, import_react49.useState)(() => resolveArea(port, initialArea));
18295
+ const bereiche = (0, import_react52.useMemo)(() => areasFor(port), [port]);
18296
+ const faehig = (0, import_react52.useMemo)(() => capabilitiesOf(port), [port]);
18297
+ const [aktiv, setAktiv] = (0, import_react52.useState)(() => resolveArea(port, initialArea));
17773
18298
  const wechsle = (id) => {
17774
18299
  setAktiv(id);
17775
18300
  onAreaChange?.(id);
@@ -17780,7 +18305,7 @@ var SzenenView = ({
17780
18305
  /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { children: [
17781
18306
  /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "szenen-view__eyebrow", children: "Vom Motiv zum Bild" }),
17782
18307
  /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("h2", { children: "Bildprompt aus einer Szene" }),
17783
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { children: "Bausteine w\xE4hlen, Szene und Bildaufbau festlegen und daraus einen wiederholbaren Prompt erzeugen." })
18308
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { children: 'Eine Szene anlegen oder aus Bild bzw. Text ableiten und daraus einen Prompt starten. Alle so entstandenen Prompts stehen mit ihren Bildern unter \u201EPrompts". Bausteine und Erweitertes pr\xE4zisieren den Weg, wenn er pr\xE4ziser werden soll.' })
17784
18309
  ] }),
17785
18310
  /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "szenen-view__step", children: aktuellerBereich.title })
17786
18311
  ] }),
@@ -17799,7 +18324,8 @@ var SzenenView = ({
17799
18324
  bereich.id
17800
18325
  )) }),
17801
18326
  /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("section", { className: "szenen-view__area", "aria-label": aktuellerBereich.title, children: [
17802
- aktiv === "derive" && port.derivation && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(DeriveArea, { port, contextId }),
18327
+ aktiv === "scene" && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(SceneArea, { port, contextId }),
18328
+ aktiv === "prompts" && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(PromptsArea, { port, contextId }),
17803
18329
  aktiv === "elements" && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
17804
18330
  ElementsArea,
17805
18331
  {
@@ -17810,9 +18336,7 @@ var SzenenView = ({
17810
18336
  listTitle: "Bausteine"
17811
18337
  }
17812
18338
  ),
17813
- aktiv === "scene" && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(SceneArea, { port, contextId }),
17814
- aktiv === "setup" && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(SetupArea, { port, contextId }),
17815
- aktiv === "shot" && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ShotArea, { port, contextId }),
18339
+ aktiv === "advanced" && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(AdvancedArea, { port, contextId }),
17816
18340
  aktiv === "media" && port.media && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(MediaArea, { port, contextId })
17817
18341
  ] }),
17818
18342
  (!faehig.saveRunAsItem || !faehig.media) && /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("details", { className: "szenen-view__host-notes", children: [
@@ -19253,7 +19777,7 @@ function createHfEventChannel(options) {
19253
19777
  let syncInFlight = null;
19254
19778
  let snapshot = null;
19255
19779
  let snapshotGeladen = ohneSnapshot;
19256
- const refresh = async () => {
19780
+ const refresh = async (revision = "main") => {
19257
19781
  if (!snapshotGeladen) {
19258
19782
  snapshot = await loadLatestSzenenSnapshot(namespace, token);
19259
19783
  snapshotGeladen = true;
@@ -19261,7 +19785,7 @@ function createHfEventChannel(options) {
19261
19785
  knownPaths.add(`${namespace}events/${name}`);
19262
19786
  }
19263
19787
  }
19264
- const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths);
19788
+ const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths, revision);
19265
19789
  if (neue.length) cachedRawEvents = [...cachedRawEvents, ...neue];
19266
19790
  };
19267
19791
  const sync = () => {
@@ -19272,16 +19796,34 @@ function createHfEventChannel(options) {
19272
19796
  }
19273
19797
  return syncInFlight;
19274
19798
  };
19799
+ const sicht = () => {
19800
+ const pending = cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
19801
+ type: event.type,
19802
+ payload: event.payload,
19803
+ clientId: event.clientId,
19804
+ ts: event.ts
19805
+ }));
19806
+ return mergeSnapshotAndPending(snapshot, pending).events;
19807
+ };
19275
19808
  return {
19276
19809
  async readEvents() {
19277
19810
  await sync();
19278
- const pending = cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
19279
- type: event.type,
19280
- payload: event.payload,
19281
- clientId: event.clientId,
19282
- ts: event.ts
19283
- }));
19284
- return mergeSnapshotAndPending(snapshot, pending).events;
19811
+ return sicht();
19812
+ },
19813
+ async readHead() {
19814
+ return hfRepoHead(token);
19815
+ },
19816
+ async readEventsAt(revision) {
19817
+ await refresh(revision);
19818
+ return sicht();
19819
+ },
19820
+ async appendEventAt(type, payload, parentCommit) {
19821
+ if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
19822
+ throw new Error(
19823
+ `Event-Typ "${type}" gehoert nicht zur Domain szenen. Der geteilte Kanal wird ueber das Praefix "${SZENEN_EVENT_PREFIX}" getrennt.`
19824
+ );
19825
+ }
19826
+ await writeHFEvent(namespace, token, type, payload, [], { ordered: true, parentCommit });
19285
19827
  },
19286
19828
  async appendEvent(type, payload) {
19287
19829
  if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
@@ -19465,11 +20007,11 @@ async function resolveShotPayload(input) {
19465
20007
  };
19466
20008
  }
19467
20009
  const shot = shotPayload;
19468
- for (const fehler4 of ohneKettenReferenz(
20010
+ for (const fehler5 of ohneKettenReferenz(
19469
20011
  validatePayload("shot", shotPayload).errors,
19470
20012
  chain ? "scene_setup_ref" : null
19471
20013
  )) {
19472
- diagnostics.push(diagnosticOfSchemaError(fehler4, `Shot ${shotElementId}`));
20014
+ diagnostics.push(diagnosticOfSchemaError(fehler5, `Shot ${shotElementId}`));
19473
20015
  }
19474
20016
  let setup = null;
19475
20017
  let report = null;
@@ -19581,8 +20123,8 @@ async function resolveScenePayload(input) {
19581
20123
  "Kein Scene Setup, kein Shot: Der Lauf entsteht aus der Szene allein. Bildaufbau, Kamera und Zielmodell wurden nicht ergaenzt, sondern sind nicht vorhanden (D-135)."
19582
20124
  ];
19583
20125
  const diagnostics = [];
19584
- for (const fehler4 of validatePayload("scene", scenePayload).errors) {
19585
- diagnostics.push(diagnosticOfSchemaError(fehler4, `Szene ${sceneElementId}`));
20126
+ for (const fehler5 of validatePayload("scene", scenePayload).errors) {
20127
+ diagnostics.push(diagnosticOfSchemaError(fehler5, `Szene ${sceneElementId}`));
19586
20128
  }
19587
20129
  const faehig = sceneOnlyCapabilities(scenePayload);
19588
20130
  return komponiere({
@@ -20472,8 +21014,13 @@ function createEventLogRunPersistence(channel) {
20472
21014
  // Nur veroeffentlichte Laeufe. Ein Draft-Lauf hat eine **leere**
20473
21015
  // shot_version_id (D-98) und wuerde bei der Suche nach '' sonst
20474
21016
  // faelschlich mitkommen — eine stille Vermischung zweier Herkuenfte.
21017
+ //
21018
+ // Seit P1-B faellt die Entscheidung nicht mehr hier, sondern in
21019
+ // `groupKeyOf()`: dieselbe Regel, aus der auch die Geschwister und
21020
+ // damit die Rundennummer entstehen. Vorher stand sie hier **und**
21021
+ // — anders — in `domainService.getPromptRunWithSiblings()`.
20475
21022
  value: collectPromptRuns(events).filter(
20476
- (run) => sourceOf(run).kind === "published" && run.shot_version_id === shotVersionId
21023
+ (run) => groupKeyOf(run) === `published:${shotVersionId}`
20477
21024
  )
20478
21025
  };
20479
21026
  } catch (error) {
@@ -20495,10 +21042,9 @@ function createEventLogRunPersistence(channel) {
20495
21042
  const events = await channel.readEvents();
20496
21043
  return {
20497
21044
  kind: "ok",
20498
- value: collectPromptRuns(events).filter((run) => {
20499
- const quelle = sourceOf(run);
20500
- return quelle.kind === "draft_chain" && quelle.chain_id === chainId;
20501
- })
21045
+ value: collectPromptRuns(events).filter(
21046
+ (run) => groupKeyOf(run) === `chain:${chainId}`
21047
+ )
20502
21048
  };
20503
21049
  } catch (error) {
20504
21050
  return queryError(error, `Laeufe der Kette ${chainId} konnten nicht gelesen werden`);
@@ -20509,10 +21055,9 @@ function createEventLogRunPersistence(channel) {
20509
21055
  const events = await channel.readEvents();
20510
21056
  return {
20511
21057
  kind: "ok",
20512
- value: collectPromptRuns(events).filter((run) => {
20513
- const quelle = sourceOf(run);
20514
- return quelle.kind === "scene_direct" && quelle.scene.element_id === sceneElementId;
20515
- })
21058
+ value: collectPromptRuns(events).filter(
21059
+ (run) => groupKeyOf(run) === `scene:${sceneElementId}`
21060
+ )
20516
21061
  };
20517
21062
  } catch (error) {
20518
21063
  return queryError(
@@ -20521,6 +21066,16 @@ function createEventLogRunPersistence(channel) {
20521
21066
  );
20522
21067
  }
20523
21068
  },
21069
+ // P1-A: dieselbe Faltung, nur ohne Herkunftsfilter. Sie las schon bisher
21070
+ // in jedem der drei Fälle den **vollen** Log — hier fällt lediglich das
21071
+ // Aussieben weg.
21072
+ async listAll() {
21073
+ try {
21074
+ return { kind: "ok", value: collectPromptRuns(await channel.readEvents()) };
21075
+ } catch (error) {
21076
+ return queryError(error, "Die Prompt-Laeufe konnten nicht gelesen werden");
21077
+ }
21078
+ },
20524
21079
  async record(record) {
20525
21080
  try {
20526
21081
  await channel.appendEvent(SZENEN_EVENT_TYPES.promptRunRecorded, record);
@@ -20817,7 +21372,25 @@ function createClientRunPort(options) {
20817
21372
  `Laeufe der Szene ${sceneElementId} konnten nicht gelesen werden`
20818
21373
  );
20819
21374
  }
20820
- }
21375
+ },
21376
+ // ── Die gemeinsame Prompt-Sicht (P1-A) ────────────────────────────────
21377
+ //
21378
+ // Sie wird nur angeboten, wenn die **Persistenz** aufzählen kann. Ein
21379
+ // Host, dessen Speicher das nicht hergibt, bekommt hier keine Operation —
21380
+ // und die Ansicht nennt den Grund, statt eine leere Liste zu zeigen (§8).
21381
+ // Der Filter läuft über `selectPromptRuns()`, dieselbe Funktion, die auch
21382
+ // der Server benutzt; zwei Bezugsrahmen wären zwei Wahrheiten.
21383
+ ...typeof runs.listAll === "function" ? {
21384
+ async listAllRuns(filter) {
21385
+ try {
21386
+ const alle = await runs.listAll();
21387
+ if (alle.kind !== "ok") return alle;
21388
+ return { kind: "ok", value: selectPromptRuns(alle.value, filter) };
21389
+ } catch (error) {
21390
+ return queryError(error, "Die Prompt-Laeufe konnten nicht gelesen werden");
21391
+ }
21392
+ }
21393
+ } : {}
20821
21394
  };
20822
21395
  }
20823
21396
 
@@ -21131,8 +21704,8 @@ function createFlowImageProvider(generateImage) {
21131
21704
  // Kein Seed: weder als Eingabe dokumentiert noch als Ausgabe
21132
21705
  // geliefert — nichts wird erfunden (D-107).
21133
21706
  });
21134
- } catch (fehler4) {
21135
- ersterFehler = fehler4 instanceof Error ? fehler4.message : String(fehler4);
21707
+ } catch (fehler5) {
21708
+ ersterFehler = fehler5 instanceof Error ? fehler5.message : String(fehler5);
21136
21709
  break;
21137
21710
  }
21138
21711
  }
@@ -21658,6 +22231,249 @@ function createImageGenerationPort(options) {
21658
22231
  };
21659
22232
  }
21660
22233
 
22234
+ // src/lib/szenen/imageJobStore.ts
22235
+ var istText6 = (wert) => typeof wert === "string" && wert.trim().length > 0;
22236
+ var SETTLED_STATUS = ["succeeded", "partial", "failed", "rejected"];
22237
+ function collectImageJobs(events) {
22238
+ const nachId = /* @__PURE__ */ new Map();
22239
+ for (const event of events) {
22240
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobRequested) continue;
22241
+ const payload = event.payload;
22242
+ if (!payload || !istText6(payload.job_id) || !istText6(payload.prompt_run_id)) continue;
22243
+ if (nachId.has(payload.job_id)) continue;
22244
+ nachId.set(payload.job_id, {
22245
+ job_id: payload.job_id,
22246
+ prompt_run_id: payload.prompt_run_id,
22247
+ execution_target_model_key: payload.execution_target_model_key ?? "",
22248
+ execution_profile_id: payload.execution_profile_id ?? "",
22249
+ image_count: typeof payload.image_count === "number" ? payload.image_count : 0,
22250
+ ...payload.aspect_ratio ? { aspect_ratio: payload.aspect_ratio } : {},
22251
+ requested_by: payload.requested_by ?? "",
22252
+ store_id: payload.store_id ?? "",
22253
+ created_at: payload.created_at ?? "",
22254
+ status: "pending",
22255
+ claim_attempts: []
22256
+ });
22257
+ }
22258
+ for (const event of events) {
22259
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobClaimed) continue;
22260
+ const payload = event.payload;
22261
+ if (!payload || !istText6(payload.job_id) || !istText6(payload.claim_id)) continue;
22262
+ const auftrag = nachId.get(payload.job_id);
22263
+ if (!auftrag) continue;
22264
+ const anspruch = {
22265
+ job_id: payload.job_id,
22266
+ claim_id: payload.claim_id,
22267
+ client_id: payload.client_id ?? "",
22268
+ claimed_at: payload.claimed_at ?? ""
22269
+ };
22270
+ nachId.set(payload.job_id, {
22271
+ ...auftrag,
22272
+ claim: auftrag.claim ?? anspruch,
22273
+ claim_attempts: [...auftrag.claim_attempts, anspruch],
22274
+ status: auftrag.status === "pending" ? "claimed" : auftrag.status
22275
+ });
22276
+ }
22277
+ for (const event of events) {
22278
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobSettled) continue;
22279
+ const payload = event.payload;
22280
+ if (!payload || !istText6(payload.job_id)) continue;
22281
+ const auftrag = nachId.get(payload.job_id);
22282
+ if (!auftrag) continue;
22283
+ if (!auftrag.claim || auftrag.claim.claim_id !== payload.claim_id) continue;
22284
+ if (auftrag.status !== "claimed") continue;
22285
+ if (!SETTLED_STATUS.includes(payload.status)) continue;
22286
+ nachId.set(payload.job_id, {
22287
+ ...auftrag,
22288
+ status: payload.status,
22289
+ ...payload.generation_batch_id ? { generation_batch_id: payload.generation_batch_id } : {},
22290
+ ...payload.item_id ? { item_id: payload.item_id } : {},
22291
+ ...typeof payload.produced_count === "number" ? { produced_count: payload.produced_count } : {},
22292
+ ...payload.diagnostic ? { diagnostic: payload.diagnostic } : {},
22293
+ settled_at: payload.settled_at ?? ""
22294
+ });
22295
+ }
22296
+ return [...nachId.values()];
22297
+ }
22298
+ function jobsForPromptRun(events, runId) {
22299
+ return collectImageJobs(events).filter((j) => j.prompt_run_id === runId);
22300
+ }
22301
+ function findImageJob(events, jobId) {
22302
+ return collectImageJobs(events).find((j) => j.job_id === jobId) ?? null;
22303
+ }
22304
+ function generationBatchIdOf(jobId) {
22305
+ return `job-${jobId}`;
22306
+ }
22307
+
22308
+ // src/lib/szenen/imageJobPort.ts
22309
+ var ANSPRUCH_VERSUCHE = 4;
22310
+ function fehler4(error, aktion) {
22311
+ return {
22312
+ message: `${aktion}: ${error instanceof Error ? error.message : String(error)}`
22313
+ };
22314
+ }
22315
+ function createImageJobPort(options) {
22316
+ const { channel, imageGeneration, storeId, clientId } = options;
22317
+ const newClaimId = options.newClaimId ?? (() => crypto.randomUUID());
22318
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
22319
+ return {
22320
+ async listJobs(runId) {
22321
+ try {
22322
+ return { kind: "ok", value: jobsForPromptRun(await channel.readEvents(), runId) };
22323
+ } catch (error) {
22324
+ return {
22325
+ kind: "error",
22326
+ ...fehler4(error, `Die Bildauftraege des Laufs ${runId} konnten nicht gelesen werden`),
22327
+ retryable: true
22328
+ };
22329
+ }
22330
+ },
22331
+ async claimAndRun(jobId) {
22332
+ let letzterGrund = "";
22333
+ for (let versuch = 1; versuch <= ANSPRUCH_VERSUCHE; versuch += 1) {
22334
+ let kopf;
22335
+ try {
22336
+ kopf = await channel.readHead();
22337
+ } catch (error) {
22338
+ return {
22339
+ kind: "error",
22340
+ ...fehler4(error, `Der Kopf-Commit des Bestands war nicht lesbar; Bildauftrag ${jobId} wurde nicht uebernommen`),
22341
+ retryable: true
22342
+ };
22343
+ }
22344
+ let auftrag;
22345
+ try {
22346
+ auftrag = findImageJob(await channel.readEventsAt(kopf), jobId);
22347
+ } catch (error) {
22348
+ return {
22349
+ kind: "error",
22350
+ ...fehler4(error, `Der Bildauftrag ${jobId} konnte nicht gelesen werden`),
22351
+ retryable: true
22352
+ };
22353
+ }
22354
+ if (!auftrag) {
22355
+ return {
22356
+ kind: "rejected",
22357
+ message: `Der Bildauftrag ${jobId} steht nicht im autoritativen Log dieses Bestands.`
22358
+ };
22359
+ }
22360
+ if (auftrag.store_id !== storeId) {
22361
+ return {
22362
+ kind: "rejected",
22363
+ message: `Der Bildauftrag ${jobId} gilt fuer den Bestand "${auftrag.store_id}", dieser Host arbeitet auf "${storeId}". Nutzer, Umgebung und Bestand muessen zusammenpassen.`
22364
+ };
22365
+ }
22366
+ if (auftrag.status !== "pending") {
22367
+ return {
22368
+ kind: "lost",
22369
+ job: auftrag,
22370
+ message: `Der Bildauftrag ${jobId} ist bereits "${auftrag.status}"` + (auftrag.claim ? ` (Client ${auftrag.claim.client_id})` : "") + ". Er wird nicht erneut ausgefuehrt \u2014 ein zweiter Lauf waere ein zweites Mal bezahlte Rechenzeit."
22371
+ };
22372
+ }
22373
+ const anspruch = {
22374
+ job_id: jobId,
22375
+ claim_id: newClaimId(),
22376
+ client_id: clientId,
22377
+ claimed_at: now().toISOString()
22378
+ };
22379
+ try {
22380
+ await channel.appendEventAt(
22381
+ SZENEN_EVENT_TYPES.imageJobClaimed,
22382
+ anspruch,
22383
+ kopf
22384
+ );
22385
+ } catch (error) {
22386
+ if (istPreconditionFehler(error)) {
22387
+ letzterGrund = error instanceof Error ? error.message : String(error);
22388
+ continue;
22389
+ }
22390
+ return {
22391
+ kind: "error",
22392
+ ...fehler4(error, `Der Anspruch auf Bildauftrag ${jobId} konnte nicht angehaengt werden`),
22393
+ retryable: true
22394
+ };
22395
+ }
22396
+ const gewonnen = {
22397
+ ...auftrag,
22398
+ status: "claimed",
22399
+ claim: anspruch,
22400
+ claim_attempts: [...auftrag.claim_attempts, anspruch]
22401
+ };
22402
+ return await fuehreAus(gewonnen, anspruch, jobId);
22403
+ }
22404
+ return {
22405
+ kind: "error",
22406
+ message: `Der Bildauftrag ${jobId} liess sich in ${ANSPRUCH_VERSUCHE} Versuchen nicht exklusiv uebernehmen \u2014 der Bestand wird gerade von anderer Seite beschrieben (${letzterGrund}). Es wurde nichts ausgefuehrt und nichts bezahlt; ein neuer Versuch ist gefahrlos.`,
22407
+ retryable: true
22408
+ };
22409
+ }
22410
+ };
22411
+ async function fuehreAus(gewonnen, anspruch, jobId) {
22412
+ const ergebnis = await imageGeneration.startFromPromptRun({
22413
+ runId: gewonnen.prompt_run_id,
22414
+ generationBatchId: generationBatchIdOf(jobId),
22415
+ executionTargetModelKey: gewonnen.execution_target_model_key,
22416
+ executionProfileId: gewonnen.execution_profile_id,
22417
+ imageCount: gewonnen.image_count,
22418
+ ...gewonnen.aspect_ratio ? { aspectRatio: gewonnen.aspect_ratio } : {}
22419
+ });
22420
+ const abschluss = {
22421
+ job_id: jobId,
22422
+ claim_id: anspruch.claim_id,
22423
+ ...abschlussFelder(ergebnis, jobId),
22424
+ settled_at: now().toISOString()
22425
+ };
22426
+ try {
22427
+ await channel.appendEvent(SZENEN_EVENT_TYPES.imageJobSettled, abschluss);
22428
+ } catch {
22429
+ }
22430
+ const gelesen = await channel.readEvents().catch(() => null);
22431
+ return {
22432
+ kind: "ok",
22433
+ job: (gelesen && findImageJob(gelesen, jobId)) ?? {
22434
+ ...gewonnen,
22435
+ status: abschluss.status,
22436
+ ...abschluss.generation_batch_id ? { generation_batch_id: abschluss.generation_batch_id } : {},
22437
+ ...abschluss.diagnostic ? { diagnostic: abschluss.diagnostic } : {},
22438
+ settled_at: abschluss.settled_at
22439
+ }
22440
+ };
22441
+ }
22442
+ }
22443
+ function istPreconditionFehler(error) {
22444
+ return Boolean(error && typeof error === "object" && error.name === "HfPreconditionFailed");
22445
+ }
22446
+ function abschlussFelder(ergebnis, jobId) {
22447
+ const batchId = generationBatchIdOf(jobId);
22448
+ switch (ergebnis.kind) {
22449
+ case "succeeded":
22450
+ return {
22451
+ status: "succeeded",
22452
+ generation_batch_id: batchId,
22453
+ item_id: ergebnis.batch.item_id,
22454
+ produced_count: ergebnis.batch.images.length
22455
+ };
22456
+ case "partial":
22457
+ return {
22458
+ status: "partial",
22459
+ generation_batch_id: batchId,
22460
+ item_id: ergebnis.batch.item_id,
22461
+ produced_count: ergebnis.produced,
22462
+ diagnostic: `Angefordert ${ergebnis.requested}, geliefert ${ergebnis.produced}.`
22463
+ };
22464
+ case "failed":
22465
+ return { status: "failed", produced_count: 0, diagnostic: ergebnis.message };
22466
+ case "unsupported":
22467
+ return {
22468
+ status: "rejected",
22469
+ produced_count: 0,
22470
+ diagnostic: `Zielmodell "${ergebnis.targetModelKey}" ist in dieser Umgebung nicht ausfuehrbar: ${ergebnis.reason}`
22471
+ };
22472
+ default:
22473
+ return { status: "rejected", produced_count: 0, diagnostic: ergebnis.message };
22474
+ }
22475
+ }
22476
+
21661
22477
  // src/lib/szenen/flowViewPort.ts
21662
22478
  var defaultContext = {
21663
22479
  async chainFor(contextId) {
@@ -21756,10 +22572,21 @@ function createFlowSzenenViewPort(options) {
21756
22572
  },
21757
22573
  ...options.now ? { now: options.now } : {}
21758
22574
  }) : void 0;
22575
+ const kanalMitRevision = typeof channel.readHead === "function" && typeof channel.readEventsAt === "function" && typeof channel.appendEventAt === "function";
22576
+ const imageJobs = imageGeneration && hatHfZugangsdaten && kanalMitRevision ? createImageJobPort({
22577
+ channel,
22578
+ imageGeneration,
22579
+ // Dieselbe Form, in der der Server seinen Bestand kennt (`storeId`).
22580
+ storeId: `hf:${options.namespace}`,
22581
+ clientId: options.imageJobClientId ?? crypto.randomUUID(),
22582
+ ...options.newImageJobClaimId ? { newClaimId: options.newImageJobClaimId } : {},
22583
+ ...options.now ? { now: options.now } : {}
22584
+ }) : void 0;
21759
22585
  const port = { elements, context, runs, derivation, imageBatchHistory };
21760
22586
  if (imageLibrary) port.imageLibrary = imageLibrary;
21761
22587
  if (imageBatchWrite) port.imageBatchWrite = imageBatchWrite;
21762
22588
  if (imageGeneration) port.imageGeneration = imageGeneration;
22589
+ if (imageJobs) port.imageJobs = imageJobs;
21763
22590
  return port;
21764
22591
  }
21765
22592
 
@@ -21817,7 +22644,7 @@ function resolveBootstrapTarget(input) {
21817
22644
  // src/components/AvatarArchitectApp.tsx
21818
22645
  var import_jsx_runtime48 = require("react/jsx-runtime");
21819
22646
  function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, requireWorkspaceSelection = false, serverBaseUrl }) {
21820
- (0, import_react50.useEffect)(() => {
22647
+ (0, import_react53.useEffect)(() => {
21821
22648
  const id = "flow-styles";
21822
22649
  if (!document.getElementById(id)) {
21823
22650
  const style = document.createElement("style");
@@ -21826,33 +22653,33 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
21826
22653
  document.head.appendChild(style);
21827
22654
  }
21828
22655
  }, []);
21829
- const [showStart, setShowStart] = (0, import_react50.useState)(true);
21830
- const [layoutChoice, setLayoutChoice] = (0, import_react50.useState)(() => {
22656
+ const [showStart, setShowStart] = (0, import_react53.useState)(true);
22657
+ const [layoutChoice, setLayoutChoice] = (0, import_react53.useState)(() => {
21831
22658
  try {
21832
22659
  return localStorage.getItem("aa-layout") || null;
21833
22660
  } catch {
21834
22661
  return null;
21835
22662
  }
21836
22663
  });
21837
- const [hfToken, setHfToken] = (0, import_react50.useState)(initialHfToken || "");
21838
- const [hfTokenInput, setHfTokenInput] = (0, import_react50.useState)(initialHfToken || "");
21839
- const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react50.useState)(() => {
22664
+ const [hfToken, setHfToken] = (0, import_react53.useState)(initialHfToken || "");
22665
+ const [hfTokenInput, setHfTokenInput] = (0, import_react53.useState)(initialHfToken || "");
22666
+ const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react53.useState)(() => {
21840
22667
  try {
21841
22668
  return normalizeSelectableNamespace(localStorage.getItem("aa-hf-namespace"));
21842
22669
  } catch {
21843
22670
  return DEFAULT_NAMESPACE;
21844
22671
  }
21845
22672
  });
21846
- const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react50.useState)(null);
21847
- const [serverProbeFailed, setServerProbeFailed] = (0, import_react50.useState)(false);
21848
- (0, import_react50.useEffect)(() => {
22673
+ const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react53.useState)(null);
22674
+ const [serverProbeFailed, setServerProbeFailed] = (0, import_react53.useState)(false);
22675
+ (0, import_react53.useEffect)(() => {
21849
22676
  if (hfNamespace !== void 0 || !serverBaseUrl) return;
21850
22677
  fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
21851
22678
  if (typeof d.hfNamespace === "string" && d.hfNamespace) setHfNamespaceFromServer(d.hfNamespace);
21852
22679
  else setServerProbeFailed(true);
21853
22680
  }).catch(() => setServerProbeFailed(true));
21854
22681
  }, [hfNamespace, serverBaseUrl]);
21855
- const [targetConfirmed, setTargetConfirmed] = (0, import_react50.useState)(false);
22682
+ const [targetConfirmed, setTargetConfirmed] = (0, import_react53.useState)(false);
21856
22683
  const isDialogTarget = hfNamespace === void 0 && hfNamespaceFromServer === null && (!serverBaseUrl || serverProbeFailed);
21857
22684
  const isProbingServer = hfNamespace === void 0 && !!serverBaseUrl && hfNamespaceFromServer === null && !serverProbeFailed;
21858
22685
  const bootstrapTarget = resolveBootstrapTarget({
@@ -21863,8 +22690,8 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
21863
22690
  isStartDialogOpen: isDialogTarget && !targetConfirmed
21864
22691
  });
21865
22692
  const effectiveNamespace = bootstrapTarget.namespace ?? "";
21866
- const kontextRef = (0, import_react50.useRef)(null);
21867
- const szenenPort = (0, import_react50.useMemo)(() => hfToken && effectiveNamespace ? createFlowSzenenViewPort({
22693
+ const kontextRef = (0, import_react53.useRef)(null);
22694
+ const szenenPort = (0, import_react53.useMemo)(() => hfToken && effectiveNamespace ? createFlowSzenenViewPort({
21868
22695
  generateText: onGeneratePrompt,
21869
22696
  token: hfToken,
21870
22697
  namespace: effectiveNamespace,
@@ -21886,10 +22713,10 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
21886
22713
  const tagBaseResult = useTagBase(hfToken, effectiveNamespace);
21887
22714
  const szenenKontextId = tagBaseResult.selectedContextId === null ? null : String(tagBaseResult.selectedContextId);
21888
22715
  kontextRef.current = szenenKontextId;
21889
- const [imageUploadStatus, setImageUploadStatus] = (0, import_react50.useState)(/* @__PURE__ */ new Map());
21890
- const [bootstrapLog, setBootstrapLog] = (0, import_react50.useState)([]);
21891
- const [isBootstrapping, setIsBootstrapping] = (0, import_react50.useState)(false);
21892
- const [hfMissingImages, setHfMissingImages] = (0, import_react50.useState)([]);
22716
+ const [imageUploadStatus, setImageUploadStatus] = (0, import_react53.useState)(/* @__PURE__ */ new Map());
22717
+ const [bootstrapLog, setBootstrapLog] = (0, import_react53.useState)([]);
22718
+ const [isBootstrapping, setIsBootstrapping] = (0, import_react53.useState)(false);
22719
+ const [hfMissingImages, setHfMissingImages] = (0, import_react53.useState)([]);
21893
22720
  const syncTopSlot = /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
21894
22721
  localOnlyCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { style: { background: "rgba(234,179,8,0.15)", border: "1px solid rgba(234,179,8,0.3)", padding: "4px 10px", fontSize: 11, color: "#fbbf24", borderRadius: 4, marginBottom: 4 }, children: [
21895
22722
  "\u26A0 ",
@@ -21948,18 +22775,18 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
21948
22775
  setTargetConfirmed(true);
21949
22776
  setShowStart(false);
21950
22777
  };
21951
- const [nodes, setNodes] = (0, import_react50.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
21952
- const [edges, setEdges] = (0, import_react50.useState)([]);
21953
- const [history, setHistory] = (0, import_react50.useState)([]);
21954
- const [galleryItems, setGalleryItems] = (0, import_react50.useState)([]);
21955
- const galleryItemsRef = (0, import_react50.useRef)([]);
21956
- (0, import_react50.useEffect)(() => {
22778
+ const [nodes, setNodes] = (0, import_react53.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
22779
+ const [edges, setEdges] = (0, import_react53.useState)([]);
22780
+ const [history, setHistory] = (0, import_react53.useState)([]);
22781
+ const [galleryItems, setGalleryItems] = (0, import_react53.useState)([]);
22782
+ const galleryItemsRef = (0, import_react53.useRef)([]);
22783
+ (0, import_react53.useEffect)(() => {
21957
22784
  galleryItemsRef.current = galleryItems;
21958
22785
  }, [galleryItems]);
21959
- const hfImageNotFoundRef = (0, import_react50.useRef)(/* @__PURE__ */ new Map());
21960
- const [galleryVisibleCount, setGalleryVisibleCount] = (0, import_react50.useState)(20);
21961
- const [historyVisibleCount, setHistoryVisibleCount] = (0, import_react50.useState)(20);
21962
- const loadThumbnailsForEntries = (0, import_react50.useCallback)(async (entries) => {
22786
+ const hfImageNotFoundRef = (0, import_react53.useRef)(/* @__PURE__ */ new Map());
22787
+ const [galleryVisibleCount, setGalleryVisibleCount] = (0, import_react53.useState)(20);
22788
+ const [historyVisibleCount, setHistoryVisibleCount] = (0, import_react53.useState)(20);
22789
+ const loadThumbnailsForEntries = (0, import_react53.useCallback)(async (entries) => {
21963
22790
  for (const entry of entries) {
21964
22791
  if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
21965
22792
  if (hfImageNotFoundRef.current.has(entry.id)) continue;
@@ -21978,7 +22805,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
21978
22805
  }
21979
22806
  }
21980
22807
  }, [hfToken, effectiveNamespace]);
21981
- (0, import_react50.useEffect)(() => {
22808
+ (0, import_react53.useEffect)(() => {
21982
22809
  if (!hfState) return;
21983
22810
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
21984
22811
  const hfIds = new Set(hfState.metadata.map((m) => m.id));
@@ -22005,36 +22832,36 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
22005
22832
  return [...localOnly, ...merged].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
22006
22833
  });
22007
22834
  }, [hfState]);
22008
- (0, import_react50.useEffect)(() => {
22835
+ (0, import_react53.useEffect)(() => {
22009
22836
  if (!hfState) return;
22010
22837
  const sortedMeta = [...hfState.metadata].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
22011
22838
  const skeletons = sortedMeta.map((m) => ({ id: m.id, prompt: m.prompt, timestamp: m.timestamp, status: "done", nodeId: m.id, tags: m.tags || [] }));
22012
22839
  const repIds = new Set(groupByPrompt(skeletons).slice(0, galleryVisibleCount).map((g) => g.representative.id));
22013
22840
  loadThumbnailsForEntries(sortedMeta.filter((e) => repIds.has(e.id)));
22014
22841
  }, [hfState, galleryVisibleCount, loadThumbnailsForEntries]);
22015
- (0, import_react50.useEffect)(() => {
22842
+ (0, import_react53.useEffect)(() => {
22016
22843
  if (!hfState) return;
22017
22844
  const sortedMeta = [...hfState.metadata].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
22018
22845
  loadThumbnailsForEntries(sortedMeta.slice(0, historyVisibleCount));
22019
22846
  }, [hfState, historyVisibleCount, loadThumbnailsForEntries]);
22020
- const [activePrompt, setActivePrompt] = (0, import_react50.useState)("");
22021
- const [isSynthesizing, setIsSynthesizing] = (0, import_react50.useState)(false);
22022
- const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react50.useState)(0);
22023
- const [currentResult, setCurrentResult] = (0, import_react50.useState)(null);
22024
- const [focusedNodeId, setFocusedNodeId] = (0, import_react50.useState)(null);
22025
- const [leftTab, setLeftTab] = (0, import_react50.useState)("prompt");
22026
- const [promptFeedback, setPromptFeedback] = (0, import_react50.useState)(null);
22027
- const [lastPromptPayload, setLastPromptPayload] = (0, import_react50.useState)(null);
22028
- const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react50.useState)(false);
22029
- const [activeTab, setActiveTab] = (0, import_react50.useState)("history");
22030
- const [mobileTab, setMobileTab] = (0, import_react50.useState)("stage");
22031
- const [middlePanel, setMiddlePanel] = (0, import_react50.useState)("stage");
22032
- const [recentLabItems, setRecentLabItems] = (0, import_react50.useState)([]);
22033
- const [aspectRatio, setAspectRatio] = (0, import_react50.useState)("1:1");
22034
- const [selectedModel, setSelectedModel] = (0, import_react50.useState)("\u{1F34C} Nano Banana Pro");
22035
- const [seed, setSeed] = (0, import_react50.useState)(Math.floor(Math.random() * 1e6));
22036
- const [seedMode, setSeedMode] = (0, import_react50.useState)("random");
22037
- const [imageCount, setImageCount] = (0, import_react50.useState)(() => {
22847
+ const [activePrompt, setActivePrompt] = (0, import_react53.useState)("");
22848
+ const [isSynthesizing, setIsSynthesizing] = (0, import_react53.useState)(false);
22849
+ const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react53.useState)(0);
22850
+ const [currentResult, setCurrentResult] = (0, import_react53.useState)(null);
22851
+ const [focusedNodeId, setFocusedNodeId] = (0, import_react53.useState)(null);
22852
+ const [leftTab, setLeftTab] = (0, import_react53.useState)("prompt");
22853
+ const [promptFeedback, setPromptFeedback] = (0, import_react53.useState)(null);
22854
+ const [lastPromptPayload, setLastPromptPayload] = (0, import_react53.useState)(null);
22855
+ const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react53.useState)(false);
22856
+ const [activeTab, setActiveTab] = (0, import_react53.useState)("history");
22857
+ const [mobileTab, setMobileTab] = (0, import_react53.useState)("stage");
22858
+ const [middlePanel, setMiddlePanel] = (0, import_react53.useState)("stage");
22859
+ const [recentLabItems, setRecentLabItems] = (0, import_react53.useState)([]);
22860
+ const [aspectRatio, setAspectRatio] = (0, import_react53.useState)("1:1");
22861
+ const [selectedModel, setSelectedModel] = (0, import_react53.useState)("\u{1F34C} Nano Banana Pro");
22862
+ const [seed, setSeed] = (0, import_react53.useState)(Math.floor(Math.random() * 1e6));
22863
+ const [seedMode, setSeedMode] = (0, import_react53.useState)("random");
22864
+ const [imageCount, setImageCount] = (0, import_react53.useState)(() => {
22038
22865
  try {
22039
22866
  const v = parseInt(localStorage.getItem("aa-image-count") || "", 10);
22040
22867
  return v >= 1 && v <= 8 ? v : 4;
@@ -22056,41 +22883,41 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
22056
22883
  { label: "4 Bilder", value: "4" },
22057
22884
  { label: "8 Bilder", value: "8" }
22058
22885
  ];
22059
- const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react50.useState)(false);
22060
- const [isRightCollapsed, setIsRightCollapsed] = (0, import_react50.useState)(false);
22061
- const [leftPanelWidth, setLeftPanelWidth] = (0, import_react50.useState)(() => {
22886
+ const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react53.useState)(false);
22887
+ const [isRightCollapsed, setIsRightCollapsed] = (0, import_react53.useState)(false);
22888
+ const [leftPanelWidth, setLeftPanelWidth] = (0, import_react53.useState)(() => {
22062
22889
  try {
22063
22890
  return parseInt(localStorage.getItem("aa-left-width") || "260", 10);
22064
22891
  } catch {
22065
22892
  return 260;
22066
22893
  }
22067
22894
  });
22068
- const [rightPanelWidth, setRightPanelWidth] = (0, import_react50.useState)(() => {
22895
+ const [rightPanelWidth, setRightPanelWidth] = (0, import_react53.useState)(() => {
22069
22896
  try {
22070
22897
  return parseInt(localStorage.getItem("aa-right-width") || "320", 10);
22071
22898
  } catch {
22072
22899
  return 320;
22073
22900
  }
22074
22901
  });
22075
- const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react50.useState)(false);
22076
- const [workspaceTags, setWorkspaceTags] = (0, import_react50.useState)(null);
22077
- const [highContrast, setHighContrast] = (0, import_react50.useState)(() => {
22902
+ const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react53.useState)(false);
22903
+ const [workspaceTags, setWorkspaceTags] = (0, import_react53.useState)(null);
22904
+ const [highContrast, setHighContrast] = (0, import_react53.useState)(() => {
22078
22905
  try {
22079
22906
  return localStorage.getItem("aa-contrast") === "high";
22080
22907
  } catch {
22081
22908
  return false;
22082
22909
  }
22083
22910
  });
22084
- const [activeReferenceId, setActiveReferenceId] = (0, import_react50.useState)(null);
22085
- const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react50.useState)(null);
22086
- const [isScanningImage, setIsScanningImage] = (0, import_react50.useState)(false);
22087
- const [touchStartX, setTouchStartX] = (0, import_react50.useState)(null);
22088
- const [isFullscreen, setIsFullscreen] = (0, import_react50.useState)(false);
22089
- const [zoomScale, setZoomScale] = (0, import_react50.useState)(1);
22090
- const [zoomOffset, setZoomOffset] = (0, import_react50.useState)({ x: 0, y: 0 });
22091
- const lastPinchDist = (0, import_react50.useRef)(null);
22092
- const lastTapTime = (0, import_react50.useRef)(0);
22093
- const dragStart = (0, import_react50.useRef)(null);
22911
+ const [activeReferenceId, setActiveReferenceId] = (0, import_react53.useState)(null);
22912
+ const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react53.useState)(null);
22913
+ const [isScanningImage, setIsScanningImage] = (0, import_react53.useState)(false);
22914
+ const [touchStartX, setTouchStartX] = (0, import_react53.useState)(null);
22915
+ const [isFullscreen, setIsFullscreen] = (0, import_react53.useState)(false);
22916
+ const [zoomScale, setZoomScale] = (0, import_react53.useState)(1);
22917
+ const [zoomOffset, setZoomOffset] = (0, import_react53.useState)({ x: 0, y: 0 });
22918
+ const lastPinchDist = (0, import_react53.useRef)(null);
22919
+ const lastTapTime = (0, import_react53.useRef)(0);
22920
+ const dragStart = (0, import_react53.useRef)(null);
22094
22921
  const openFullscreen = () => {
22095
22922
  setIsFullscreen(true);
22096
22923
  setZoomScale(1);
@@ -22153,7 +22980,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
22153
22980
  setActiveReferenceId(null);
22154
22981
  setActiveReferenceThumbnail(null);
22155
22982
  };
22156
- const labServices = (0, import_react50.useMemo)(() => {
22983
+ const labServices = (0, import_react53.useMemo)(() => {
22157
22984
  const available = groupGenerationsToLabItems([...galleryItems, ...history]);
22158
22985
  return {
22159
22986
  availableItems: available,
@@ -22233,14 +23060,14 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
22233
23060
  setIsScanningImage(false);
22234
23061
  }
22235
23062
  };
22236
- const currentIndex = (0, import_react50.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
22237
- const goToPrev = (0, import_react50.useCallback)(() => {
23063
+ const currentIndex = (0, import_react53.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
23064
+ const goToPrev = (0, import_react53.useCallback)(() => {
22238
23065
  if (currentIndex > 0) setCurrentResult(history[currentIndex - 1]);
22239
23066
  }, [currentIndex, history]);
22240
- const goToNext = (0, import_react50.useCallback)(() => {
23067
+ const goToNext = (0, import_react53.useCallback)(() => {
22241
23068
  if (currentIndex < history.length - 1) setCurrentResult(history[currentIndex + 1]);
22242
23069
  }, [currentIndex, history]);
22243
- const handleGallerySelect = (0, import_react50.useCallback)((g) => {
23070
+ const handleGallerySelect = (0, import_react53.useCallback)((g) => {
22244
23071
  setCurrentResult(g);
22245
23072
  setMobileTab("stage");
22246
23073
  if (g.hasThumb && g.filename && hfToken && !g.fullBase64) {
@@ -22253,7 +23080,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
22253
23080
  });
22254
23081
  }
22255
23082
  }, [hfToken, effectiveNamespace]);
22256
- const handleTitleSet = (0, import_react50.useCallback)((id) => {
23083
+ const handleTitleSet = (0, import_react53.useCallback)((id) => {
22257
23084
  const ts = Date.now();
22258
23085
  setGalleryItems((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
22259
23086
  setHistory((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
@@ -22262,13 +23089,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
22262
23089
  });
22263
23090
  }
22264
23091
  }, [hfToken, effectiveNamespace, hfWriteEvent]);
22265
- const currentGroup = (0, import_react50.useMemo)(() => {
23092
+ const currentGroup = (0, import_react53.useMemo)(() => {
22266
23093
  if (!currentResult?.prompt) return [];
22267
23094
  const groups = groupByPrompt(galleryItems.filter((g) => g.status === "done" && !!g.base64));
22268
23095
  const group = groups.find((gr) => gr.prompt === currentResult.prompt);
22269
23096
  return group ? group.items : [];
22270
23097
  }, [galleryItems, currentResult?.prompt]);
22271
- (0, import_react50.useEffect)(() => {
23098
+ (0, import_react53.useEffect)(() => {
22272
23099
  if (!currentResult?.prompt || !hfToken || !effectiveNamespace || !hfState) return;
22273
23100
  const siblings = galleryItemsRef.current.filter(
22274
23101
  (g) => g.prompt === currentResult.prompt && !g.base64 && !hfImageNotFoundRef.current.has(g.id)
@@ -22307,7 +23134,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
22307
23134
  }
22308
23135
  ) : null;
22309
23136
  useKeyboardNavigation(history, currentResult, setCurrentResult);
22310
- const getSubtreeFormat = (0, import_react50.useCallback)((nodeId, depth = 0) => {
23137
+ const getSubtreeFormat = (0, import_react53.useCallback)((nodeId, depth = 0) => {
22311
23138
  const node = nodes.find((n) => n.id === nodeId);
22312
23139
  if (!node) return "";
22313
23140
  const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
@@ -22315,7 +23142,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
22315
23142
  return `${indent}- ${node.data.label || "(unbenannt)"}
22316
23143
  ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
22317
23144
  }, [nodes, edges]);
22318
- const activePath = (0, import_react50.useMemo)(() => {
23145
+ const activePath = (0, import_react53.useMemo)(() => {
22319
23146
  if (!focusedNodeId) return /* @__PURE__ */ new Set();
22320
23147
  const path = /* @__PURE__ */ new Set([focusedNodeId]);
22321
23148
  let currId = focusedNodeId;
@@ -23467,7 +24294,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
23467
24294
  }
23468
24295
 
23469
24296
  // src/components/FaApp.tsx
23470
- var import_react51 = require("react");
24297
+ var import_react54 = require("react");
23471
24298
  var import_jsx_runtime49 = require("react/jsx-runtime");
23472
24299
  function FaApp({
23473
24300
  onGenerateImage,
@@ -23483,15 +24310,15 @@ function FaApp({
23483
24310
  serverBaseUrl,
23484
24311
  buildInfo
23485
24312
  }) {
23486
- const [hfNamespace, setHfNamespace] = (0, import_react51.useState)(void 0);
23487
- (0, import_react51.useEffect)(() => {
24313
+ const [hfNamespace, setHfNamespace] = (0, import_react54.useState)(void 0);
24314
+ (0, import_react54.useEffect)(() => {
23488
24315
  if (!serverBaseUrl) return;
23489
24316
  fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
23490
24317
  if (typeof d.hfNamespace === "string") setHfNamespace(d.hfNamespace);
23491
24318
  }).catch(() => {
23492
24319
  });
23493
24320
  }, [serverBaseUrl]);
23494
- const wrappedPrompt = (0, import_react51.useMemo)(
24321
+ const wrappedPrompt = (0, import_react54.useMemo)(
23495
24322
  () => alsFlowTextZugang(onGeneratePrompt),
23496
24323
  [onGeneratePrompt]
23497
24324
  );
@@ -23745,15 +24572,15 @@ function createInMemoryWorkspacePersistence(now = () => Date.now()) {
23745
24572
  }
23746
24573
 
23747
24574
  // node_modules/zustand/esm/react.mjs
23748
- var import_react52 = __toESM(require("react"), 1);
24575
+ var import_react55 = __toESM(require("react"), 1);
23749
24576
  var identity = (arg) => arg;
23750
24577
  function useStore(api, selector = identity) {
23751
- const slice = import_react52.default.useSyncExternalStore(
24578
+ const slice = import_react55.default.useSyncExternalStore(
23752
24579
  api.subscribe,
23753
- import_react52.default.useCallback(() => selector(api.getState()), [api, selector]),
23754
- import_react52.default.useCallback(() => selector(api.getInitialState()), [api, selector])
24580
+ import_react55.default.useCallback(() => selector(api.getState()), [api, selector]),
24581
+ import_react55.default.useCallback(() => selector(api.getInitialState()), [api, selector])
23755
24582
  );
23756
- import_react52.default.useDebugValue(slice);
24583
+ import_react55.default.useDebugValue(slice);
23757
24584
  return slice;
23758
24585
  }
23759
24586