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>