From 9a7d861903b0b74c17fd2381885f74291f656291 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 19 May 2026 02:21:41 +0000 Subject: [PATCH] fix(ui): bottom padding + close-screen button removed + sidebar collapse + quiet loguru MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four issues batched together since they all touch the GUI shell: - ``stMainBlockContainer``'s ``padding-bottom`` bumped from 0.75rem → 4rem (~one button-height of free space above the fixed Help/Close footer). The last line of content on a page that fills the viewport was previously sitting flush against the footer's top border. - Farewell overlay's "Close this window" button removed per UX request. The auto-dismiss path is now the only flow: try programmatic close (works in Chrome/Edge ``--app`` windows); failing that, surface the hint and redirect the parent window to ``about:blank`` after a short timeout. Previously the user had to click the button to get the same fallback. The ``quit.close_window_button`` i18n key is retained as a no-op for now in case the button comes back; nothing references it. - Sidebar collapse → expand was broken: clicking « collapsed the sidebar but the » expand-back affordance was invisible. Two causes pulled apart: 1. ``.dt-brand { flex: 1 }`` was eating the entire ``stSidebarHeader`` width, squeezing Streamlit's ``stSidebarCollapseButton`` off the right edge. Changed to ``margin: 0 auto 0 0`` so the brand keeps its natural width and the chevron has room to live next to it. 2. The "hide Streamlit chrome" toolbar block was listing ``stToolbar`` and ``stToolbarActions`` for ``display: none`` — but the post-collapse re-open button (``stExpandSidebarButton``) lives inside ``stToolbar``, so hiding the container killed the button too. Dropped both container testids from the hide list and kept the per-icon rules for ``stMainMenu`` / ``stAppDeployButton`` / ``stStatusWidget`` / ``stDecoration``. - Loguru's stderr sink quieted in GUI mode. ``src/gui/app.py`` now runs ``logger.remove()`` + ``logger.add(sys.stderr, level="ERROR", …)`` at the top so internal ``logger.debug`` / ``logger.warning`` breadcrumbs (e.g. ``standardize_dataframe: 7/31 cells were unparseable``) no longer print to the terminal when the user runs ``python -m src.gui``. CLI entry points already do the same configuration per-script. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/gui/app.py | 14 ++++++ src/gui/components/_legacy.py | 92 ++++++++++++++++++++--------------- 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index 875c13e..053da55 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -25,6 +25,20 @@ if str(_project_root) not in sys.path: sys.path.insert(0, str(_project_root)) +# Quiet the loguru stderr sink. The CLIs (``src/cli*.py``) reconfigure +# it per-script to ``WARNING``; the GUI's ``python -m src.gui`` +# entrypoint never did, so internal ``logger.debug`` / +# ``logger.warning`` breadcrumbs bled into the terminal where users +# launched the app from. Drop the default ``DEBUG``-level stderr sink +# and add a clean ``ERROR``-only one so only genuinely actionable +# problems surface — diagnostic warnings still land in the audit log +# via its own sink. Runs once, before any module-level ``logger`` use +# in the GUI tree. +from loguru import logger as _logger # noqa: E402 +_logger.remove() +_logger.add(sys.stderr, level="ERROR", format="{level: <8} | {message}") + + # --------------------------------------------------------------------------- # Home page (rendered when the user selects the default nav entry) # --------------------------------------------------------------------------- diff --git a/src/gui/components/_legacy.py b/src/gui/components/_legacy.py index 2e05e93..1e20581 100644 --- a/src/gui/components/_legacy.py +++ b/src/gui/components/_legacy.py @@ -43,17 +43,17 @@ header[data-testid="stHeader"] { background: transparent !important; height: 0 !important; } -/* Hide every Streamlit-shipped icon button in the header band: - hamburger menu, deploy button, status / running indicator, - toolbar action stacks. ``toolbarMode = "viewer"`` already suppresses - most of these but newer Streamlit releases keep emitting them with - tiny inline styles, so we belt-and-suspenders the visibility from - CSS too. */ +/* Hide the noisy Streamlit-shipped icon buttons in the header band + (hamburger menu, deploy button, status / running indicator). We + deliberately do NOT hide ``stToolbar`` or ``stToolbarActions`` as + containers — those wrap ``stExpandSidebarButton`` which is the + ONLY path back to an expanded sidebar after the user collapses it. + ``toolbarMode = "viewer"`` already suppresses most of these icons; + the CSS belt-and-suspenders the visibility for newer Streamlit + releases that keep emitting them with inline styles. */ #MainMenu, [data-testid="stMainMenu"], [data-testid="stAppDeployButton"], -[data-testid="stToolbar"], -[data-testid="stToolbarActions"], [data-testid="stStatusWidget"], [data-testid="stDecoration"] { display: none !important; @@ -104,7 +104,11 @@ footer { .stMainBlockContainer, [data-testid="stMainBlockContainer"] { padding-top: 0.5rem !important; - padding-bottom: 0.75rem !important; + /* +4rem buys a comfortable read of the last line of content on a + page that fills the viewport: the fixed Help/Close footer is + ~36px (≈2.25rem) tall and we want at least a button-height of + free space between the last widget and the footer's top edge. */ + padding-bottom: 4rem !important; } /* Scale content to fit app window */ .stApp { @@ -192,14 +196,32 @@ body, .stApp { text. Injected into ``[data-testid="stSidebarHeader"]`` by the JS below; ``stLogoSpacer`` is hidden so the brand block takes its place flush against the left edge of the sidebar header. */ +/* The brand sits next to Streamlit's sidebar collapse button inside + ``stSidebarHeader``. ``flex: 1`` would steal all the horizontal + space and squash the collapse chevron out of view — once collapsed + the user would have no way to reopen the sidebar. Keep the brand + at its natural width and let the header's flex layout leave room + for the chevron on the right. */ .dt-brand { display: flex !important; align-items: center; gap: 10px; padding: 0 0 0 4px; - margin: 0; - height: 100%; - flex: 1; + margin: 0 auto 0 0; +} +/* Belt-and-suspenders: keep the in-sidebar collapse button + the + out-of-sidebar collapsed control reachable. The latter is what + appears on the page edge once the sidebar slides shut. */ +[data-testid="stSidebarCollapseButton"], +[data-testid="stSidebarCollapseButton"] button { + display: inline-flex !important; + visibility: visible !important; + opacity: 1 !important; +} +[data-testid="stSidebarCollapsedControl"] { + display: flex !important; + visibility: visible !important; + z-index: 9999 !important; } /* "Letter D (sans)" wordmark per Business/DataTools/app_icons.html §03: 28px ink-filled rounded square, cream "D" in Geist 700 with @@ -1274,12 +1296,7 @@ _FAREWELL_SCRIPT_TEMPLATE = """ 'border-radius:12px;background:#161922;max-width:480px;">' + '

' + '__TITLE__

' + - '

__SUBTITLE__

' + - '' + + '

__SUBTITLE__

' + '

' + '__CLOSE_HINT__

' + @@ -1310,21 +1327,21 @@ _FAREWELL_SCRIPT_TEMPLATE = """ // push to history. try { win.location.replace('about:blank'); } catch (e) {} } - function wireClose(doc, win) { - var btn = doc.getElementById('datatools-close-btn'); - if (!btn) return; - btn.onclick = function () { - var standalone = isStandalone(win); - if (tryClose(win)) return; - // Close failed (or definitely will fail in a regular tab). - // Surface the hint immediately, then redirect to about:blank - // after a short delay so the user has a moment to read why. - var hint = doc.getElementById('datatools-close-hint'); - if (hint) hint.style.display = 'block'; - setTimeout(function () { - if (!win.closed) fallbackToBlank(win); - }, standalone ? 250 : 1500); - }; + function autoDismiss(doc, win) { + // Try the programmatic close first — succeeds in Chrome/Edge + // --app windows. If it fails (regular browser tab), surface the + // hint and auto-redirect to about:blank so the user lands on a + // clean page instead of the frozen farewell overlay. The "Close + // this window" button used to drive the same flow on click; we + // dropped that affordance per UX request, so this auto-timer is + // the only path on regular tabs. + var standalone = isStandalone(win); + if (tryClose(win)) return; + var hint = doc.getElementById('datatools-close-hint'); + if (hint) hint.style.display = 'block'; + setTimeout(function () { + if (!win.closed) fallbackToBlank(win); + }, standalone ? 400 : 2500); } try { var doc = window.top.document; @@ -1332,17 +1349,12 @@ _FAREWELL_SCRIPT_TEMPLATE = """ if (!doc.getElementById('datatools-farewell-overlay')) { doc.body.appendChild(buildOverlay(doc)); } - wireClose(doc, win); - // Auto-close attempt on first paint — succeeds in Chrome --app - // windows, fails silently on regular tabs (and we don't redirect - // automatically here; the manual button drives that path so the - // user is in control). - tryClose(win); + autoDismiss(doc, win); } catch (e) { // Cross-origin access denied (shouldn't happen given Streamlit's // sandbox flags, but fall back gracefully): cover this iframe. document.body.appendChild(buildOverlay(document)); - wireClose(document, window); + autoDismiss(document, window); } })();