From 59b9f6d5f7da623198bfc793d0f00e096b7bdb54 Mon Sep 17 00:00:00 2001 From: forthfate Date: Sat, 26 Sep 2026 10:20:57 +0900 Subject: [PATCH 1/5] fix: open persona timeline evidence modal --- .../src/components/ui/evidence-viewer.tsx | 18 +++++++++++- frontend/src/features/improvements/page.tsx | 28 +++++++++++++------ frontend/src/theme-overrides.css | 2 +- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/ui/evidence-viewer.tsx b/frontend/src/components/ui/evidence-viewer.tsx index a1e367b5..f08634c3 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 d2195e3f..c69b074f 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 && 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); } From 9b878ebc11bd2764773da029cfbf5cfda4605aeb Mon Sep 17 00:00:00 2001 From: forthfate Date: Sat, 26 Sep 2026 12:30:35 +0900 Subject: [PATCH 2/5] fix: align issue comment input typography --- frontend/src/features/issues/discussion.css | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/features/issues/discussion.css b/frontend/src/features/issues/discussion.css index 78089553..ccebeb3b 100644 --- a/frontend/src/features/issues/discussion.css +++ b/frontend/src/features/issues/discussion.css @@ -6,5 +6,6 @@ .issue-message time,.issue-message small { color:var(--muted); font:10px 'DM Mono',monospace; } .proposal-detail .issue-message p { margin:0; font-size:11px; line-height:1.55; white-space:pre-wrap; } .issue-discussion__composer { display:grid; gap:8px; padding:12px; border:1px solid var(--line); border-radius:9px; background:var(--bg); } +.issue-discussion__composer input,.issue-discussion__composer textarea,.issue-discussion__composer select { font:11px/1.5 Manrope,sans-serif; } .issue-discussion__composer textarea { min-height:96px; resize:vertical; } .issue-discussion__composer .modal-actions { justify-content:flex-end; } From 918615d7d097441228d7ce55795fd72ef25e82ee Mon Sep 17 00:00:00 2001 From: forthfate Date: Sat, 26 Sep 2026 12:36:10 +0900 Subject: [PATCH 3/5] fix: keep issue management proposals distinct --- backend/app/store.py | 31 ++++++++++++++++++++++++++---- backend/tests/test_api.py | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/backend/app/store.py b/backend/app/store.py index fff46a8a..e2e74fbb 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,7 +2557,7 @@ 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 @@ -2733,9 +2733,32 @@ 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 or not all(part.isdigit() for part in suffix): + continue + legacy_id = f"{prefix}{suffix[1]}" + 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 d89e96a7..7368c97c 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -2485,6 +2485,46 @@ 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": []}, + }, + ], + ) + ) + + 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", + } + 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", From 2f02d0ec7d3e763a21c3463a8cff2ff405544217 Mon Sep 17 00:00:00 2001 From: forthfate Date: Sat, 26 Sep 2026 12:49:45 +0900 Subject: [PATCH 4/5] fix: namespace reported issue proposal ids --- backend/app/store.py | 12 +++++++++--- backend/tests/test_api.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/backend/app/store.py b/backend/app/store.py index e2e74fbb..bb25688b 100644 --- a/backend/app/store.py +++ b/backend/app/store.py @@ -2562,7 +2562,10 @@ def proposal_lifecycles( ] 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) ] ) @@ -2746,9 +2749,12 @@ def issue_management_items(self) -> list[dict[str, Any]]: 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 or not all(part.isdigit() for part in suffix): + 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_id = f"{prefix}{suffix[1]}" legacy_ids[proposal_id] = legacy_id legacy_counts[legacy_id] = legacy_counts.get(legacy_id, 0) + 1 values = [] diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 7368c97c..3426f087 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -2509,6 +2509,22 @@ def test_issue_management_separates_multiple_supervisor_records_in_one_iteration "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"}], + }, + }, ], ) ) @@ -2517,6 +2533,8 @@ def test_issue_management_separates_multiple_supervisor_records_in_one_iteration 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") From 775588dec571381c9dade4e4380814712c74d8d3 Mon Sep 17 00:00:00 2001 From: forthfate Date: Sat, 26 Sep 2026 12:54:35 +0900 Subject: [PATCH 5/5] fix: refine issue management table columns --- frontend/src/features/issues/page.tsx | 12 +++--------- frontend/src/locales/languages/en.json | 2 +- frontend/src/locales/languages/ja.json | 2 +- frontend/src/locales/languages/ko.json | 2 +- frontend/src/theme-overrides.css | 2 +- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/frontend/src/features/issues/page.tsx b/frontend/src/features/issues/page.tsx index 492bc937..54f7cd7c 100644 --- a/frontend/src/features/issues/page.tsx +++ b/frontend/src/features/issues/page.tsx @@ -29,7 +29,7 @@ type Copy = { resolved: string; deferred: string; comment: string; - assigner: string; + commentAuthor: string; assignerPlaceholder: string; commentPlaceholder: string; verificationRun: string; @@ -334,12 +334,6 @@ export function IssueManagementSection({ render: (x) => (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 693cc646..cf6cdba6 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 e0f4c48a..54c6945c 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 2a457974..98b60073 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 aa3ebdde..677ae3d3 100644 --- a/frontend/src/theme-overrides.css +++ b/frontend/src/theme-overrides.css @@ -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; }