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:
@@ -250,7 +250,11 @@ def _home_page() -> None:
|
|||||||
st.markdown(f"### 📄 {name}")
|
st.markdown(f"### 📄 {name}")
|
||||||
st.success(t("findings.none"))
|
st.success(t("findings.none"))
|
||||||
else:
|
else:
|
||||||
render_findings_panel(findings, header=f"📄 {name}")
|
render_findings_panel(
|
||||||
|
findings,
|
||||||
|
header=f"📄 {name}",
|
||||||
|
key_namespace=name,
|
||||||
|
)
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption(t("chrome.footer"))
|
st.caption(t("chrome.footer"))
|
||||||
|
|||||||
@@ -1374,7 +1374,12 @@ def _tool_page_slug(tool_id: str) -> str:
|
|||||||
return _TOOL_PAGE_PATHS.get(tool_id, "")
|
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.
|
"""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
|
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
|
# rendering blended into the page, making the per-tool
|
||||||
# jump non-obvious. The button triggers ``st.switch_page``
|
# jump non-obvious. The button triggers ``st.switch_page``
|
||||||
# so navigation is still a soft switch (no full reload).
|
# 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(
|
if st.button(
|
||||||
_t("findings.open_tool", tool=name),
|
_t("findings.open_tool", tool=name),
|
||||||
key=f"_findings_open_{tool_id}",
|
key=f"_findings_open_{tool_id}_{ns}",
|
||||||
type="primary",
|
type="primary",
|
||||||
use_container_width=False,
|
use_container_width=False,
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user