fix(ui): bottom padding + close-screen button removed + sidebar collapse + quiet loguru

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 02:21:41 +00:00
parent 1016a4d2c4
commit 9a7d861903
2 changed files with 66 additions and 40 deletions

View File

@@ -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)
# ---------------------------------------------------------------------------

View File

@@ -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;">' +
'<h1 style="margin:0 0 8px 0;font-weight:600;letter-spacing:-0.01em;">' +
'__TITLE__</h1>' +
'<p style="opacity:0.7;margin:0 0 20px 0;">__SUBTITLE__</p>' +
'<button id="datatools-close-btn" style="' +
'background:#6ee7b7;color:#052e1a;font-weight:600;' +
'padding:10px 20px;border-radius:8px;border:none;' +
'font-size:15px;cursor:pointer;font-family:inherit;">' +
'__CLOSE_BTN__</button>' +
'<p style="opacity:0.7;margin:0;">__SUBTITLE__</p>' +
'<p id="datatools-close-hint" style="' +
'display:none;font-size:13px;opacity:0.6;margin:14px 0 0 0;">' +
'__CLOSE_HINT__</p>' +
@@ -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);
}
})();
</script>