fix(findings): namespace per-tool button keys so multi-file render works

Reported: uploading multiple files on the home page and clicking Run
analysis blew up with

    StreamlitDuplicateElementKey: key='_findings_open_02_text_cleaner'

when two uploaded files both had Clean Text findings.

Root cause: ``render_findings_panel`` is invoked once per uploaded
file from ``_home.py``, but the per-tool jump button used a
filename-agnostic key:

    key=f"_findings_open_{tool_id}"

Two files both flagging Clean Text → two buttons with identical keys
→ Streamlit rejects the second one.

Fix:

- Add ``key_namespace: str = ""`` to ``render_findings_panel``. The
  helper hashes it (sha1 truncated to 8 chars) and appends to every
  button key, so different namespaces produce different keys but the
  same namespace stays stable across reruns.
- The home page now passes the filename:
  ``render_findings_panel(findings, header=f"📄 {name}", key_namespace=name)``.
- The single-call site in ``upload_and_analyze_section`` (the legacy
  helper, only used outside the new home-page path) keeps the default
  empty namespace, which is fine because that path renders findings
  for ONE file at a time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 02:17:03 +00:00
parent 1caedbbbc7
commit 36510eee7b
2 changed files with 23 additions and 3 deletions

View File

@@ -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,
):