diff --git a/src/gui/_home.py b/src/gui/_home.py index 5f2a279..d851bc3 100644 --- a/src/gui/_home.py +++ b/src/gui/_home.py @@ -250,7 +250,11 @@ def _home_page() -> None: st.markdown(f"### 📄 {name}") st.success(t("findings.none")) else: - render_findings_panel(findings, header=f"📄 {name}") + render_findings_panel( + findings, + header=f"📄 {name}", + key_namespace=name, + ) st.divider() st.caption(t("chrome.footer")) diff --git a/src/gui/components/_legacy.py b/src/gui/components/_legacy.py index a516306..7faa282 100644 --- a/src/gui/components/_legacy.py +++ b/src/gui/components/_legacy.py @@ -1374,7 +1374,12 @@ def _tool_page_slug(tool_id: str) -> str: return _TOOL_PAGE_PATHS.get(tool_id, "") -def render_findings_panel(findings, *, header: str | None = None) -> None: +def render_findings_panel( + findings, + *, + header: str | None = None, + key_namespace: str = "", +) -> None: """Render a list of :class:`Finding` objects grouped by tool. Each tool gets a header with the count, an open-tool button, and a list @@ -1427,9 +1432,20 @@ def render_findings_panel(findings, *, header: str | None = None) -> None: # rendering blended into the page, making the per-tool # jump non-obvious. The button triggers ``st.switch_page`` # so navigation is still a soft switch (no full reload). + # + # ``key_namespace`` is hashed into the widget key so the + # home page (which calls this once PER uploaded file) + # doesn't collide on the shared tool_id — two files both + # having Clean Text findings would otherwise produce two + # buttons with the same key and Streamlit refuses. + import hashlib as _hashlib + ns = _hashlib.sha1( + (key_namespace or "").encode("utf-8"), + usedforsecurity=False, + ).hexdigest()[:8] if st.button( _t("findings.open_tool", tool=name), - key=f"_findings_open_{tool_id}", + key=f"_findings_open_{tool_id}_{ns}", type="primary", use_container_width=False, ):