diff --git a/src/gui/pages/2_Text_Cleaner.py b/src/gui/pages/2_Text_Cleaner.py index 61ecf02..8b9db5f 100644 --- a/src/gui/pages/2_Text_Cleaner.py +++ b/src/gui/pages/2_Text_Cleaner.py @@ -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", - ) + 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()