From 6703e2c15c2ef1572ee5965bf80ebbc47d7f704c Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 19 May 2026 00:56:11 +0000 Subject: [PATCH] feat(home): in-card "+ Add more files" replaces Streamlit's dropzone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mockup §file-add lands as the canonical import affordance: - Streamlit's ``st.file_uploader`` widget is still mounted (only path that actually receives browser file events), but parked off-screen via a new ``[data-testid="stFileUploader"] { position:absolute; left:-10000px; … pointer-events:none }`` rule. Its hidden ```` stays reachable to JavaScript. - The Files card is now always rendered (header + bordered body). The bottom row of the card is a ``button.dt-file-add`` styled per mockup §file-add: dashed top border bleeding to the card edges, surface-hover background, ``+ Add more files`` text in ``--ink-secondary``, accent-fill on hover. - A small `` +""", + height=1, + ) - if to_remove is not None: - from src.audit import log_event - log_event( - "upload", - f"Removed {to_remove}", - filename=to_remove, - ) - del home_uploads[to_remove] - # Drop any findings/results tied to the removed file. - findings_by_file_drop = st.session_state.get( - "home_findings_by_file", {} - ) - findings_by_file_drop.pop(to_remove, None) - st.session_state["home_uploads"] = home_uploads - st.session_state["home_findings_by_file"] = findings_by_file_drop - # If we just removed the active upload, also clear the - # singular ``home_uploaded_*`` keys so tool pages don't - # pick up stale bytes; the next render will repopulate - # them from whatever file is now first. - if st.session_state.get("home_uploaded_name") == to_remove: - st.session_state.pop("home_uploaded_name", None) - st.session_state.pop("home_uploaded_size", None) - st.session_state.pop("home_uploaded_bytes", None) - st.rerun() + if to_remove is not None: + from src.audit import log_event + log_event( + "upload", + f"Removed {to_remove}", + filename=to_remove, + ) + del home_uploads[to_remove] + # Drop any findings/results tied to the removed file. + findings_by_file_drop = st.session_state.get( + "home_findings_by_file", {} + ) + findings_by_file_drop.pop(to_remove, None) + st.session_state["home_uploads"] = home_uploads + st.session_state["home_findings_by_file"] = findings_by_file_drop + # If we just removed the active upload, also clear the + # singular ``home_uploaded_*`` keys so tool pages don't + # pick up stale bytes; the next render will repopulate + # them from whatever file is now first. + if st.session_state.get("home_uploaded_name") == to_remove: + st.session_state.pop("home_uploaded_name", None) + st.session_state.pop("home_uploaded_size", None) + st.session_state.pop("home_uploaded_bytes", None) + st.rerun() if not home_uploads: - st.info(t("upload.empty_state")) + # Empty state — page ends cleanly after the Files card. The + # in-card "Add more files" button is the only affordance the + # user needs; the old ``upload.empty_state`` info alert was + # redundant and out of step with the mockup. return # Expose the first uploaded file via the singular ``home_uploaded_*`` diff --git a/src/gui/components/_legacy.py b/src/gui/components/_legacy.py index 8c22c1c..1c5ab98 100644 --- a/src/gui/components/_legacy.py +++ b/src/gui/components/_legacy.py @@ -520,6 +520,61 @@ div[data-testid="stContainer"][data-border="true"] { font-feature-settings: "ss02"; } +/* "+ Add more files" — last row of the files card (mockup §file-add). + The button stays in the document; ``onclick`` triggers a programmatic + click on Streamlit's (off-screen) file_uploader input so the OS file + picker opens. Negative margins bleed the button to the card edges so + the dashed top-border and corner radii match the surrounding card + chrome. */ +.dt-file-add { + display: flex !important; + align-items: center; + justify-content: center; + gap: 8px; + width: calc(100% + 2rem); + padding: 12px 16px; + background: var(--surface-hover); + border: none; + border-top: 1px dashed var(--border-strong); + border-radius: 0 0 var(--r-lg) var(--r-lg); + cursor: pointer; + font-family: var(--font-sans) !important; + font-size: 13px !important; + font-weight: 500 !important; + color: var(--ink-secondary) !important; + margin: 14px -1rem -1rem; + line-height: 1; + transition: background 0.12s ease, color 0.12s ease; +} +.dt-file-add:hover { + background: var(--accent-fill); + color: var(--accent) !important; +} +.dt-file-add svg { + width: 14px; height: 14px; + stroke-width: 2; +} + +/* Empty-state placeholder centered in the empty files card. */ +.dt-files-empty { + margin: 8px 0 4px !important; + text-align: center; + color: var(--ink-tertiary) !important; + font-size: 13px; +} + +/* Streamlit's file_uploader is rendered off-screen so the OS file + picker stays wired up to our in-card "Add more files" button — its + input element is still reachable via JS ``.click()``. */ +.dt-fileuploader-offscreen [data-testid="stFileUploader"] { + position: absolute !important; + left: -10000px !important; + width: 1px !important; + height: 1px !important; + overflow: hidden !important; + pointer-events: none !important; +} + /* ---------- Findings — per-file group cards (mockup §findings) ---------- */ .dt-finding-group-head { display: flex;