diff --git a/backend/app/store.py b/backend/app/store.py index 2468f5c..c74e7dc 100644 --- a/backend/app/store.py +++ b/backend/app/store.py @@ -2499,7 +2499,7 @@ def proposal_lifecycles( } ] seen_agent_improvements: set[str] = set() - for record in records: + for record_index, record in enumerate(records): if not isinstance(record, dict): continue iteration = record.get("iteration") @@ -2557,12 +2557,15 @@ def proposal_lifecycles( if record.get("stage") != "agent_proposal_assessment": issue_proposals = ( [ - (f"{run.id}:{record.get('iteration', 0)}:{index}", proposal) + (f"{run.id}:{record.get('iteration', 0)}:{record_index}:{index}", proposal) for index, proposal in enumerate(improvements) ] if improvements else [ - (f"{run.id}:{record.get('iteration', 0)}:issue:{index}", proposal) + ( + f"{run.id}:{record.get('iteration', 0)}:{record_index}:issue:{index}", + proposal, + ) for index, proposal in enumerate(reported_issues) ] ) @@ -2733,9 +2736,35 @@ def _save_issue_management_records(self, values: dict[str, dict[str, Any]]) -> N def issue_management_items(self) -> list[dict[str, Any]]: records = self._issue_management_records() + proposals = self.proposal_lifecycles() + + # Before supervisor-record position became part of the ID, records from + # separate assessments in one iteration could collide. Preserve a + # legacy management record only where it maps to exactly one proposal. + # A collided legacy record has no safe owner, so each new row starts + # independently instead of sharing comments, status, or deletion state. + legacy_ids: dict[str, str] = {} + legacy_counts: dict[str, int] = {} + for proposal in proposals: + proposal_id = str(proposal["proposal_id"]) + prefix = f"{proposal.get('run_id')}:{proposal.get('iteration')}:" + suffix = proposal_id.removeprefix(prefix).split(":") + if len(suffix) == 2 and all(part.isdigit() for part in suffix): + legacy_id = f"{prefix}{suffix[1]}" + elif len(suffix) == 3 and suffix[0].isdigit() and suffix[1] == "issue" and suffix[2].isdigit(): + legacy_id = f"{prefix}issue:{suffix[2]}" + else: + continue + legacy_ids[proposal_id] = legacy_id + legacy_counts[legacy_id] = legacy_counts.get(legacy_id, 0) + 1 values = [] - for proposal in self.proposal_lifecycles(): - record = records.get(proposal["proposal_id"], {}) + for proposal in proposals: + proposal_id = str(proposal["proposal_id"]) + record = records.get(proposal_id) + legacy_id = legacy_ids.get(proposal_id) + if record is None and legacy_id and legacy_counts[legacy_id] == 1: + record = records.get(legacy_id) + record = record or {} # Issues are derived from immutable run evidence. A deletion is a # management-level tombstone, not deletion of the underlying run. if record.get("status") == "deleted": diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 55a4835..3055c00 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -2612,6 +2612,64 @@ def test_issue_management_excludes_agent_assessment_feedback_from_issue_rational assert managed_issue["comments"][0]["assigner"] == "AI supervisor" +def test_issue_management_separates_multiple_supervisor_records_in_one_iteration(tmp_path, monkeypatch): + monkeypatch.setattr(store_module, "RUNS", tmp_path / "runs") + monkeypatch.setattr(store_module, "ISSUE_MANAGEMENT", tmp_path / "issue-management.yaml") + store = store_module.ConsoleStore() + timestamp = store_module.now() + store._save( + Run( + id="run-duplicate-assessments", + workflow_id="workflow", + workflow_name="Workflow", + status="succeeded", + created_at=timestamp, + updated_at=timestamp, + supervisor_results=[ + { + "iteration": 1, + "stage": "issue_assessment", + "response": {"improvements": [{"title": "First issue"}], "reported_issues": []}, + }, + { + "iteration": 1, + "stage": "issue_assessment", + "response": {"improvements": [{"title": "Second issue"}], "reported_issues": []}, + }, + { + "iteration": 2, + "stage": "issue_assessment", + "response": { + "improvements": [], + "reported_issues": [{"title": "First reported issue"}], + }, + }, + { + "iteration": 2, + "stage": "issue_assessment", + "response": { + "improvements": [], + "reported_issues": [{"title": "Second reported issue"}], + }, + }, + ], + ) + ) + + items = store.issue_management_items() + assert {item["proposal_id"] for item in items} == { + "run-duplicate-assessments:1:0:0", + "run-duplicate-assessments:1:1:0", + "run-duplicate-assessments:2:2:issue:0", + "run-duplicate-assessments:2:3:issue:0", + } + first = next(item for item in items if item["title"] == "First issue") + store.update_issue_management_item(first["proposal_id"], comment="Only the first issue") + by_title = {item["title"]: item for item in store.issue_management_items()} + assert by_title["First issue"]["comments"][0]["body"] == "Only the first issue" + assert by_title["Second issue"]["comments"] == [] + + def test_hello_accepts_unsaved_profile_settings(): response = TestClient(app).post( "/api/settings/hello", diff --git a/frontend/src/components/ui/evidence-viewer.tsx b/frontend/src/components/ui/evidence-viewer.tsx index a1e367b..f08634c 100644 --- a/frontend/src/components/ui/evidence-viewer.tsx +++ b/frontend/src/components/ui/evidence-viewer.tsx @@ -14,6 +14,7 @@ export function visualEvidenceArtifacts(files: SavedDataFile[]): EvidenceArtifac return files.reduce((artifacts, file, index) => { const path = file.relative_path ?? file.path; if (!path) return artifacts; + if (artifacts.some((artifact) => artifact.id === path)) return artifacts; const name = file.label || file.filename || `#${index + 1}`; const contentType = file.content_type?.toLowerCase(); const extension = path.toLowerCase().split(".").at(-1); @@ -53,11 +54,26 @@ function HtmlEvidenceViewer({ url }: { url: string }) { return ; } +export function VisualEvidenceModal({ open, onClose, title, runId, iteration, artifacts }: { open: boolean; onClose: () => void; title: string; runId: string; iteration: number; artifacts: EvidenceArtifact[] }) { + const imageArtifacts = artifacts.filter((artifact) => artifact.screenshot); + const htmlArtifacts = artifacts.filter((artifact) => artifact.html); + const tabs = [ + ...imageArtifacts.map((artifact, index) => ({ id: `image-${index}`, label: artifact.screenshot!.split("/").at(-1) ?? `#${index + 1}`, artifact })), + ...(htmlArtifacts.length ? [{ id: "html", label: "HTML" }] : []), + ]; + const [activeTab, setActiveTab] = useState(tabs[0]?.id ?? ""); + const selectedImage = imageArtifacts[Number(activeTab.replace("image-", ""))]; + return + {tabs.length > 1 &&
{tabs.map((tab) => )}
} + {activeTab === "html" ? htmlArtifacts.map((artifact, index) =>
{artifact.html!.split("/").at(-1) ?? artifact.name ?? `#${index + 1}`}
) : selectedImage?.screenshot &&
{selectedImage.screenshot.split("/").at(-1) ?? selectedImage.name}
{selectedImage.name
} +
; +} + export function EvidenceViewer({ runId, iteration, artifacts, className, imageLabel, htmlLabel, imageTitle, htmlTitle }: { runId: string; iteration: number; artifacts: EvidenceArtifact[]; className?: string; imageLabel: string; htmlLabel: string; imageTitle: string; htmlTitle: string }) { const [evidenceModal, setEvidenceModal] = useState<{ kind: "image" | "html"; artifacts: EvidenceArtifact[] } | null>(null); const imageArtifacts = artifacts.filter((artifact) => artifact.screenshot); const htmlArtifacts = artifacts.filter((artifact) => artifact.html); const modalArtifacts = evidenceModal?.artifacts.filter((artifact) => evidenceModal.kind === "image" ? Boolean(artifact.screenshot) : Boolean(artifact.html)) ?? []; if (!imageArtifacts.length && !htmlArtifacts.length) return null; - return <>{imageArtifacts.length > 0 && }{htmlArtifacts.length > 0 && }{evidenceModal && createPortal( setEvidenceModal(null)} className="modal--evidence">{modalArtifacts.map((artifact, index) =>
{artifact.name ?? artifact.id ?? `#${index + 1}`}
{evidenceModal.kind === "image" ? {artifact.name : }
)}
, document.body)}; + return <>{imageArtifacts.length > 0 && }{htmlArtifacts.length > 0 && }{evidenceModal && createPortal( setEvidenceModal(null)} title={evidenceModal.kind === "image" ? imageTitle : htmlTitle} runId={runId} iteration={iteration} artifacts={modalArtifacts} />, document.body)}; } diff --git a/frontend/src/features/improvements/page.tsx b/frontend/src/features/improvements/page.tsx index d2195e3..c69b074 100644 --- a/frontend/src/features/improvements/page.tsx +++ b/frontend/src/features/improvements/page.tsx @@ -46,6 +46,7 @@ import { DataTable, type Column } from "../../components/ui/data-table"; import { CircleStop, RotateCcw } from "lucide-react"; import { EvaluationsPage } from "../evaluations/page"; import { IssueManagementSection } from "../issues/page"; +import { VisualEvidenceModal, visualEvidenceArtifacts } from "../../components/ui/evidence-viewer"; type ImprovementCopy = { improvement: string; @@ -223,17 +224,18 @@ type PersonaJourneyEvent = { }; function PersonaJourneyTimeline({ - buildId, runs, locale, t, hours, onSelect, + buildId, runs, locale, t, hours, }: { buildId: string; runs: Run[]; locale: Locale; t: (typeof copy)["en"]; hours: number; - onSelect: (run: Run) => void; }) { const [rangeStart] = useState(() => hours ? Date.now() - hours * 60 * 60 * 1000 : 0); const drag = useRef<{ pointerId: number; startX: number; startScroll: number; moved: boolean } | null>(null); + const [isDragging, setIsDragging] = useState(false); + const [selectedEvent, setSelectedEvent] = useState(null); const lanes = useMemo(() => { const groups = new Map(); for (const run of runs) { @@ -297,32 +299,41 @@ function PersonaJourneyTimeline({ }, [buildId, rangeStart, runs]); const startDrag = (event: ReactPointerEvent) => { if (event.button !== 0) return; - event.currentTarget.setPointerCapture(event.pointerId); drag.current = { pointerId: event.pointerId, startX: event.clientX, startScroll: event.currentTarget.scrollLeft, moved: false }; }; const moveDrag = (event: ReactPointerEvent) => { const active = drag.current; if (!active || active.pointerId !== event.pointerId) return; const distance = event.clientX - active.startX; - if (Math.abs(distance) > 4) active.moved = true; + if (!active.moved && Math.abs(distance) <= 6) return; + if (!active.moved) { + active.moved = true; + event.currentTarget.setPointerCapture(event.pointerId); + setIsDragging(true); + } event.currentTarget.scrollLeft = active.startScroll - distance; }; const endDrag = (event: ReactPointerEvent) => { - if (drag.current?.pointerId === event.pointerId) event.currentTarget.releasePointerCapture(event.pointerId); + if (drag.current?.pointerId !== event.pointerId) return; + if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId); + if (!drag.current.moved) drag.current = null; + setIsDragging(false); }; + const selectedArtifacts = selectedEvent ? visualEvidenceArtifacts((selectedEvent.run.step_results ?? []).filter((step) => step.loop_index === selectedEvent.iteration).flatMap((step) => step.data_files ?? [])) : []; return

{t.personaJourneysHint}

{lanes.length ?
{lanes.map((lane) =>
{lane.persona}{lane.events.length}
-
{ +
{ if (!drag.current?.moved) return; event.preventDefault(); event.stopPropagation(); drag.current = null; + setIsDragging(false); }}> - {lane.events.map((event) =>
)}
:

{t.noPersonaJourneys}

} + {selectedEvent && setSelectedEvent(null)} title={`${selectedEvent.persona} · ${t.iteration} #${selectedEvent.iteration}`} runId={selectedEvent.run.id} iteration={selectedEvent.iteration} artifacts={selectedArtifacts} />}
; } function Card({ @@ -771,7 +783,7 @@ function ImprovementBuildContent({ onRetryRequest={setRetryingRun} onSelect={setSelectedRun} /> - + {selectedRun && (x.personas ?? []).join(", ") || "—", sortValue: (x) => (x.personas ?? []).join(", "), }, - { - id: "assigner", - header: t.assigner, - render: (x) => x.assigner || "—", - sortValue: (x) => x.assigner, - }, { id: "decision", header: t.aiDecision, @@ -470,7 +464,7 @@ export function IssueManagementSection({ setRun(""); }} className="issue-management-table" - gridTemplateColumns="36px 42px minmax(410px,1fr) 112px 76px 96px 110px 110px 110px" + gridTemplateColumns="36px 42px minmax(410px,1fr) 112px 76px 96px 110px 110px" /> {e.type === "status" ? label(e.status ?? "") : e.body} - {e.assigner ? ` · ${t.assigner} ${e.assigner}` : ""} + {e.assigner ? ` · ${t.commentAuthor} ${e.assigner}` : ""} {e.verification_run_id ? ` · ${t.run} ${e.verification_run_id}` : ""} diff --git a/frontend/src/locales/languages/en.json b/frontend/src/locales/languages/en.json index 693cc64..cf6cdba 100644 --- a/frontend/src/locales/languages/en.json +++ b/frontend/src/locales/languages/en.json @@ -429,7 +429,7 @@ "resolved": "Resolved", "deferred": "Deferred", "comment": "Comment", - "assigner": "Comment author", + "commentAuthor": "Comment author", "assignerPlaceholder": "Comment author name", "commentPlaceholder": "Record what you checked or changed.", "verificationRun": "Verification run", diff --git a/frontend/src/locales/languages/ja.json b/frontend/src/locales/languages/ja.json index e0f4c48..54c6945 100644 --- a/frontend/src/locales/languages/ja.json +++ b/frontend/src/locales/languages/ja.json @@ -423,7 +423,7 @@ "resolved": "解決済み", "deferred": "保留", "comment": "コメント", - "assigner": "コメント作成者", + "commentAuthor": "コメント作成者", "assignerPlaceholder": "コメント作成者の名前", "commentPlaceholder": "確認内容または修正内容を記録してください。", "verificationRun": "検証実行", diff --git a/frontend/src/locales/languages/ko.json b/frontend/src/locales/languages/ko.json index 2a45797..98b6007 100644 --- a/frontend/src/locales/languages/ko.json +++ b/frontend/src/locales/languages/ko.json @@ -428,7 +428,7 @@ "resolved": "해결됨", "deferred": "보류됨", "comment": "코멘트", - "assigner": "작성자", + "commentAuthor": "작성자", "assignerPlaceholder": "코멘트 작성자 이름", "commentPlaceholder": "확인 내용 또는 수정 사항을 남기세요.", "verificationRun": "검증 실행", diff --git a/frontend/src/theme-overrides.css b/frontend/src/theme-overrides.css index 9134126..677ae3d 100644 --- a/frontend/src/theme-overrides.css +++ b/frontend/src/theme-overrides.css @@ -131,7 +131,7 @@ footer { display:flex; align-items:center; justify-content:space-between; margin @media(max-width:720px){.result-behavior-trace { grid-template-columns:1fr; }.result-behavior-trace .result-row__iteration,.result-behavior-trace .result-row__evidence { grid-column:auto; }.result-behavior-trace .result-row__evidence { flex-direction:row; justify-content:flex-start; }} .proposal-tree { display:grid; gap:10px; max-height:560px; margin-top:16px; overflow-y:auto; padding-right:4px; scrollbar-gutter:stable; }.proposal-tree details { min-width:0; border-left:1px solid var(--line); }.proposal-tree summary { display:flex; align-items:center; justify-content:space-between; gap:12px; cursor:pointer; list-style:none; }.proposal-tree summary::-webkit-details-marker { display:none; }.proposal-tree summary::before { content:'▸'; margin-right:8px; color:var(--muted); }.proposal-tree details[open] > summary::before { content:'▾'; }.proposal-tree__build { padding:12px; border:1px solid var(--line)!important; border-radius:8px; background:var(--bg); }.proposal-tree__build > summary > strong { font-size:13px; }.proposal-tree summary small { color:var(--muted); font:10px 'DM Mono',monospace; }.proposal-tree__run { margin:10px 0 0 11px; padding:10px 0 0 13px; }.proposal-tree__run > summary { justify-content:flex-start; }.proposal-tree__run > summary span,.proposal-tree__iteration > summary span { display:grid; gap:3px; }.proposal-tree__run > summary strong { font-size:11px; }.proposal-tree__iteration { margin:9px 0 0 11px; padding:9px 0 0 13px; }.proposal-tree__iteration > summary { padding:8px 10px; border:1px solid var(--line); border-radius:7px; background:var(--surface-raised); }.proposal-tree__count { display:inline!important; margin-left:2px; color:var(--muted); font:400 10px 'DM Mono',monospace; }.proposal-tree__iteration > div { display:grid; gap:6px; margin:7px 0 0 12px; }.proposal-tree__item { display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; padding:10px; border:1px solid var(--line); border-radius:7px; color:var(--text); background:var(--surface); text-align:left; }.proposal-tree__item:hover { border-color:var(--accent); background:var(--surface-raised); }.proposal-tree__item > span { display:grid; gap:3px; min-width:0; }.proposal-tree__item strong { overflow:hidden; font-size:12px; text-overflow:ellipsis; white-space:nowrap; }.proposal-tree__item small { color:var(--muted); font-size:10px; } .proposal-tree__iteration-meta,.proposal-tree__item-meta { display:flex!important; align-items:center; gap:8px; flex:none; }.proposal-tree__iteration > summary .proposal-tree__evidence { display:flex!important; flex-direction:row; gap:5px; align-items:center; }.proposal-tree__score { padding:3px 6px; border:1px solid color-mix(in srgb,var(--accent) 45%,var(--line)); border-radius:999px; color:var(--accent); background:color-mix(in srgb,var(--accent) 10%,var(--bg)); font:600 10px 'DM Mono',monospace; white-space:nowrap; }.proposal-tree__persona-group { display:grid; gap:7px; padding:10px; border:1px solid var(--line); border-radius:7px; background:var(--bg); }.proposal-tree__persona-group > header { display:flex; align-items:baseline; }.proposal-tree__persona-group > header strong { font:600 10px 'DM Mono',monospace; }.proposal-tree__persona-group > div { display:grid; gap:6px; } -.persona-journeys { display:grid; gap:12px; }.persona-journeys > h3,.persona-journeys > .hint { margin:0; }.persona-journeys__lanes { display:grid; gap:12px; }.persona-journeys__lane { display:grid; grid-template-columns:160px minmax(0,1fr); gap:12px; padding:12px 0; border-top:1px solid var(--line); }.persona-journeys__lane > header { display:grid; align-content:start; gap:5px; }.persona-journeys__lane > header strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font:600 11px 'DM Mono',monospace; }.persona-journeys__lane > header small { color:var(--muted); font:10px 'DM Mono',monospace; }.persona-journeys__events { display:flex; gap:10px; overflow-x:auto; padding-bottom:4px; cursor:grab; touch-action:pan-y; }.persona-journeys__events:active { cursor:grabbing; }.persona-journeys__event { display:grid; flex:0 0 min(300px,78vw); gap:6px; padding:10px; border:1px solid var(--line); border-top:2px solid var(--accent); border-radius:7px; color:var(--text); background:var(--bg); text-align:left; cursor:pointer; }.persona-journeys__event:hover { background:var(--surface-raised); border-color:var(--accent); }.persona-journeys__event time { font:600 10px 'DM Mono',monospace; }.persona-journeys__event > small { color:var(--muted); font:9px 'DM Mono',monospace; }.persona-journeys__event dl { display:grid; gap:7px; margin:0; }.persona-journeys__event dl > div { display:grid; gap:2px; }.persona-journeys__event dt { color:var(--muted); font-size:9px; }.persona-journeys__event dd { display:-webkit-box; margin:0; overflow:hidden; font-size:11px; line-height:1.45; -webkit-box-orient:vertical; -webkit-line-clamp:3; } @media(max-width:720px){.persona-journeys__lane { grid-template-columns:1fr; gap:8px; }.persona-journeys__lane > header { grid-template-columns:minmax(0,1fr) auto; align-items:center; }.persona-journeys__event { flex-basis:min(300px,88vw); }} +.persona-journeys { display:grid; gap:12px; }.persona-journeys > h3,.persona-journeys > .hint { margin:0; }.persona-journeys__lanes { display:grid; gap:12px; }.persona-journeys__lane { display:grid; grid-template-columns:160px minmax(0,1fr); gap:12px; padding:12px 0; border-top:1px solid var(--line); }.persona-journeys__lane > header { display:grid; align-content:start; gap:5px; }.persona-journeys__lane > header strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font:600 11px 'DM Mono',monospace; }.persona-journeys__lane > header small { color:var(--muted); font:10px 'DM Mono',monospace; }.persona-journeys__events { display:flex; gap:10px; overflow-x:auto; padding-bottom:4px; cursor:grab; touch-action:pan-y; }.persona-journeys__events.is-dragging { cursor:grabbing; }.persona-journeys__event { display:grid; flex:0 0 min(300px,78vw); gap:6px; padding:10px; border:1px solid var(--line); border-top:2px solid var(--accent); border-radius:7px; color:var(--text); background:var(--bg); text-align:left; cursor:pointer; }.persona-journeys__event:hover { background:var(--surface-raised); border-color:var(--accent); }.persona-journeys__event time { font:600 10px 'DM Mono',monospace; }.persona-journeys__event > small { color:var(--muted); font:9px 'DM Mono',monospace; }.persona-journeys__event dl { display:grid; gap:7px; margin:0; }.persona-journeys__event dl > div { display:grid; gap:2px; }.persona-journeys__event dt { color:var(--muted); font-size:9px; }.persona-journeys__event dd { display:-webkit-box; margin:0; overflow:hidden; font-size:11px; line-height:1.45; -webkit-box-orient:vertical; -webkit-line-clamp:3; } @media(max-width:720px){.persona-journeys__lane { grid-template-columns:1fr; gap:8px; }.persona-journeys__lane > header { grid-template-columns:minmax(0,1fr) auto; align-items:center; }.persona-journeys__event { flex-basis:min(300px,88vw); }} .cycle-interventions__head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; }.cycle-interventions__head .panel-head { margin:0; }.cycle-interventions__head label { display:flex!important; align-items:center; gap:8px; color:var(--muted); font-size:11px; white-space:nowrap; }.cycle-interventions__head select { width:auto; min-width:76px; }.cycle-health { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; margin:16px 0; }.cycle-health article { display:grid; gap:5px; padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.cycle-health small { color:var(--muted); font-size:10px; }.cycle-health strong { font:600 14px 'DM Mono',monospace; }.cycle-ai-action { display:grid; gap:10px; margin:16px 0; }.cycle-ai-action .approve { justify-self:start; }.cycle-ai-action pre { max-height:340px; overflow:auto; margin:0; padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--bg); white-space:pre-wrap; font:11px/1.6 'DM Mono',monospace; } @media(max-width:720px){.cycle-interventions__head { flex-direction:column; }.cycle-interventions__head select { min-width:76px; }.cycle-health { grid-template-columns:1fr; }} .cycle-ai-response { max-height:340px; overflow:auto; padding:14px; border:1px solid var(--line); border-radius:8px; background:var(--bg); font-size:12px; line-height:1.65; }.cycle-ai-response > :first-child { margin-top:0; }.cycle-ai-response > :last-child { margin-bottom:0; }.cycle-ai-response h1,.cycle-ai-response h2,.cycle-ai-response h3 { margin:14px 0 7px; font-size:13px; }.cycle-ai-response p,.cycle-ai-response ul,.cycle-ai-response ol { margin:8px 0; }.cycle-ai-response ul,.cycle-ai-response ol { padding-left:20px; }.cycle-ai-response code { font:10px 'DM Mono',monospace; }.cycle-ai-response pre { overflow:auto; padding:9px; border-radius:6px; background:var(--surface-raised); } .catalog-list { display:grid; gap:6px; max-height:360px; overflow-y:auto; overscroll-behavior:contain; }.catalog-row { width:100%; display:grid; min-height:28px; gap:4px; padding:0; border:0; border-top:1px solid var(--line); color:var(--text); background:transparent; text-align:left; }.catalog-row:first-child { border-top:0; }.catalog-row:hover { color:var(--accent); background:var(--surface-raised); }.catalog-row strong { font-size:12px; }.catalog-row span { color:var(--muted); font-size:11px; }.catalog-empty { margin:0; padding:14px 6px; color:var(--muted); font-size:12px; line-height:1.55; border-top:1px solid var(--line); } @@ -191,4 +191,4 @@ select option[value="docker"] { display:none; } .workflow-graph-zone { box-sizing:border-box; width:100%; height:100%; padding:12px; border:1px dashed color-mix(in srgb,var(--accent) 45%,var(--line)); border-radius:10px; color:var(--muted); background:color-mix(in srgb,var(--accent) 5%,transparent); }.workflow-graph-zone strong,.workflow-graph-zone small { display:block; }.workflow-graph-zone strong { color:var(--text); font:600 11px 'DM Mono',monospace; text-transform:uppercase; }.workflow-graph-zone small { margin-top:3px; font:9px 'DM Mono',monospace; } .decision--acceptable { color:#f1d292; background:#403420; } .issue-management-evidence { display:inline-flex; flex-direction:row; align-items:center; gap:5px; margin-left:auto; }.issue-management-rationale { padding:14px; border:1px solid color-mix(in srgb,var(--accent) 32%,var(--line)); border-radius:8px; background:color-mix(in srgb,var(--accent) 7%,var(--surface-raised)); }.issue-management-rationale h3 { color:var(--text); }.issue-management-rationale p { overflow-wrap:anywhere; } -.issue-management-table-scroll { display:block; width:100%; max-width:100%; overflow-x:auto; overflow-y:hidden; overscroll-behavior-inline:contain; -webkit-overflow-scrolling:touch; }.issue-management-table { display:block; width:max(100%,1102px)!important; min-width:1102px; overflow:visible; }.issue-management-table .tr { min-width:1102px; width:100%; }.issue-management-table .tr > span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; overflow-wrap:normal; word-break:normal; }.issue-management-table .tr > span:nth-last-child(-n+3) { overflow:visible; }.issue-management-table .issue-proposal { display:flex; align-items:center; gap:7px; min-width:0; }.issue-management-table .issue-proposal strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }.issue-management-table .badge { display:inline-flex; flex:none; white-space:nowrap; }.issue-category { display:inline-flex; flex:none; align-items:center; min-height:20px; padding:0 6px; border:1px solid var(--line); border-radius:999px; color:var(--muted); background:var(--surface-raised); font:400 9px 'DM Mono',monospace; font-style:normal; }.issue-category--accuracy_grounding { color:#9cc9ff; }.issue-category--safety_privacy { color:#f1d292; }.issue-category--task_completion { color:#bdecc8; }.issue-category--user_experience { color:#d7b7ff; } +.issue-management-table-scroll { display:block; width:100%; max-width:100%; overflow-x:auto; overflow-y:hidden; overscroll-behavior-inline:contain; -webkit-overflow-scrolling:touch; }.issue-management-table { display:block; width:max(100%,1102px)!important; min-width:1102px; overflow:visible; }.issue-management-table .tr { min-width:1102px; width:100%; }.issue-management-table .tr > span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; overflow-wrap:normal; word-break:normal; }.issue-management-table .tr > span:nth-last-child(-n+2) { overflow:visible; }.issue-management-table .issue-proposal { display:flex; align-items:center; gap:7px; min-width:0; }.issue-management-table .issue-proposal strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }.issue-management-table .badge { display:inline-flex; flex:none; white-space:nowrap; }.issue-category { display:inline-flex; flex:none; align-items:center; min-height:20px; padding:0 6px; border:1px solid var(--line); border-radius:999px; color:var(--muted); background:var(--surface-raised); font:400 9px 'DM Mono',monospace; font-style:normal; }.issue-category--accuracy_grounding { color:#9cc9ff; }.issue-category--safety_privacy { color:#f1d292; }.issue-category--task_completion { color:#bdecc8; }.issue-category--user_experience { color:#d7b7ff; }