fix(text-cleaner): make all three download buttons actually fire
Only "Download cleaned CSV" was working; "Download changes audit" and
"Download config JSON" did nothing on click.
The symptom is the classic Streamlit footgun for multiple
``st.download_button`` widgets in adjacent columns: without an explicit
``key`` argument the auto-derived widget IDs can collide, especially
when one button is conditionally rendered, and only the first button
in source order actually fires on click. Same goes for unstable
``data`` bytes recomputed inside the ``with col:`` block — the widget
identity can drift between renders.
Robustness pattern applied:
- Compute all three byte buffers up front, outside the columns, so the
``data`` parameter is the same object across reruns.
- Pass an explicit unique ``key`` ("textclean_dl_cleaned" /
"textclean_dl_changes" / "textclean_dl_config") to each button.
- Render the changes button unconditionally with ``disabled=True`` and
a help tooltip when ``result.changes.empty`` — instead of hiding it.
Layout stays steady and the empty case is self-explanatory.
- ``use_container_width=True`` so the three buttons size identically
inside their columns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -328,35 +328,55 @@ else:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Downloads
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# All three byte buffers are prepared up front (outside the columns) so
|
||||
# each ``st.download_button`` sees stable ``data`` across reruns and an
|
||||
# explicit ``key`` — without those, Streamlit auto-derived widget IDs
|
||||
# can collide for multiple download_buttons in adjacent columns and
|
||||
# only the first one actually fires on click. The empty-changes case
|
||||
# now renders a disabled button (rather than vanishing) so the layout
|
||||
# stays steady and the user understands why nothing's available.
|
||||
|
||||
st.divider()
|
||||
stem = Path(st.session_state.get("textclean_input_name", "input")).stem
|
||||
|
||||
cleaned_bytes = result.cleaned_df.to_csv(index=False).encode("utf-8-sig")
|
||||
changes_bytes = (
|
||||
result.changes.to_csv(index=False).encode("utf-8-sig")
|
||||
if not result.changes.empty
|
||||
else b""
|
||||
)
|
||||
config_bytes = json.dumps(options.to_dict(), indent=2).encode("utf-8")
|
||||
|
||||
dl_a, dl_b, dl_c = st.columns(3)
|
||||
with dl_a:
|
||||
cleaned_bytes = result.cleaned_df.to_csv(index=False).encode("utf-8-sig")
|
||||
st.download_button(
|
||||
"Download cleaned CSV",
|
||||
data=cleaned_bytes,
|
||||
file_name=f"{stem}_cleaned.csv",
|
||||
mime="text/csv",
|
||||
key="textclean_dl_cleaned",
|
||||
use_container_width=True,
|
||||
)
|
||||
with dl_b:
|
||||
if not result.changes.empty:
|
||||
changes_bytes = result.changes.to_csv(index=False).encode("utf-8-sig")
|
||||
st.download_button(
|
||||
"Download changes audit",
|
||||
data=changes_bytes,
|
||||
file_name=f"{stem}_changes.csv",
|
||||
mime="text/csv",
|
||||
key="textclean_dl_changes",
|
||||
disabled=result.changes.empty,
|
||||
help="No changes to audit." if result.changes.empty else None,
|
||||
use_container_width=True,
|
||||
)
|
||||
with dl_c:
|
||||
config_bytes = json.dumps(options.to_dict(), indent=2).encode("utf-8")
|
||||
st.download_button(
|
||||
"Download config JSON",
|
||||
data=config_bytes,
|
||||
file_name="text_clean_config.json",
|
||||
mime="application/json",
|
||||
key="textclean_dl_config",
|
||||
use_container_width=True,
|
||||
)
|
||||
|
||||
st.divider()
|
||||
|
||||
Reference in New Issue
Block a user