Files
datatools-dev/src/gui/app.py
Michael da7d86f457 feat(ui): Material icons in sidebar + stats overview on home
Two pieces of the mockup 2 layout that hadn't landed yet:

1. Sidebar nav icons — emoji glyphs (🧹 ✂️ 🔍 …) swapped for
   Streamlit's ``:material/<name>:`` syntax, picking the outline
   Material Symbol that best matches each mockup SVG:

       Home               → :material/home:
       Fix Missing Values → :material/help_outline:
       Find Unusual Vals  → :material/insights:
       Clean Text         → :material/text_format:
       Standardize Fmts   → :material/format_list_bulleted:
       Find Duplicates    → :material/search:
       Quality Check      → :material/check_circle:
       Map Columns        → :material/view_column:
       Combine Files      → :material/account_tree:
       Auto Workflows     → :material/auto_awesome:
       Activate           → :material/key:
       Close              → :material/close:

   Streamlit injects the icon name as a literal ligature inside a
   first-child ``<span>`` of the nav anchor, expected to render
   through the Material Symbols font. theme.py's base rule was
   forcing Geist on every span under ``stSidebarNav``, turning the
   ligatures back into plain text labels — added a structural
   exception that targets ``[data-testid="stSidebarNavLink"] >
   span:first-child`` (and any descendant), restoring the Material
   font family, neutralizing the inherited ``ss01/cv01/cv11``
   feature settings, and sizing to 18px.

   Also stripped the leading emojis from every page title in the
   en/es i18n packs (``home.title``, ``close_page.title``,
   ``activation.title``, ``tools.*.page_title``) — the icons live
   in the sidebar now, the page H1 no longer needs to carry one.

2. Stats overview on home — new ``_render_stats_overview`` in
   _home.py emits a 4-card grid above the per-file findings panels:
   Files analyzed, Total findings, Warnings (severity ``warn`` ∪
   ``error``), Info (severity ``info``). Card layout follows the
   mockup §stats verbatim — Geist 28px / 600 / -0.03em for the
   numeric value (the "Display number" row in spec §4), tiny
   uppercase tracked label, paper-surface card with the standard
   warm border + faint shadow. The Warnings / Info cards tint the
   number with ``--warn`` / ``--info`` when the count is non-zero.

CSS for ``.dt-stats / .dt-stat / .dt-stat-label / .dt-stat-value /
.dt-stat-unit`` added to ``_DESIGN_TOKENS_CSS``; falls to a
2-column grid below 900px viewport, matching the mockup's media
query.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:31:40 +00:00

127 lines
4.5 KiB
Python

"""DataTools — Data Cleaning Mastery Suite.
Launch:
streamlit run src/gui/app.py
This module is the navigation manager for the full GUI: it registers
every tool page with ``st.navigation`` so the sidebar can render
section headers ("Data Review", "Data Cleaners", "Transformations",
"Automations") instead of the flat numbered list Streamlit's
auto-page-discovery would produce. The Home page itself is registered
as a callable defined below so the entry script remains the single
file users invoke.
"""
from __future__ import annotations
import sys
from pathlib import Path
import streamlit as st
# Ensure project root is on sys.path so `src.core` imports work
_project_root = Path(__file__).resolve().parent.parent.parent
if str(_project_root) not in sys.path:
sys.path.insert(0, str(_project_root))
# ---------------------------------------------------------------------------
# Home page (rendered when the user selects the default nav entry)
# ---------------------------------------------------------------------------
#
# The renderer lives in ``src/gui/_home.py`` so the ``back_to_home_link``
# helper on tool pages can ``import _home_page`` and pass it into
# ``st.switch_page`` without re-executing this entry script's
# navigation setup (which would crash because tool pages have a
# different "main script" context and the relative ``pages/…`` paths
# below would no longer resolve).
from src.gui._home import _home_page # noqa: E402
# ---------------------------------------------------------------------------
# Navigation registration
# ---------------------------------------------------------------------------
#
# ``st.navigation`` overrides Streamlit's auto-discovery of the
# ``pages/`` directory, so every page we want in the sidebar must be
# listed here. The dict key becomes the section header rendered above
# the entries; an empty-string key suppresses the header so the Home
# entry sits at the top without a label above it.
from src.gui.tools_registry import TOOLS, section_label, tool_name # noqa: E402
from src.i18n import t as _t # noqa: E402
def _page_for(tool_id: str, *, page_slug: str, icon: str, title: str) -> "st.Page":
return st.Page(
f"pages/{page_slug}.py",
title=title,
icon=icon,
url_path=tool_id,
)
def _build_navigation() -> dict[str, list]:
by_section: dict[str, list] = {
"cleaners": [],
"transformations": [],
"automations": [],
}
# Resolve the tool name through ``tool_name`` (i18n lookup) instead
# of using the registry's English ``tool.name`` field, otherwise the
# sidebar stays in English when the user switches to Spanish.
for tool in TOOLS:
by_section[tool.section].append(
_page_for(
tool.tool_id,
page_slug=tool.page_slug,
icon=tool.icon,
title=tool_name(tool.tool_id),
)
)
home = st.Page(
_home_page,
title=_t("nav.home_page_title") or "Home",
icon=":material/home:",
default=True,
url_path="home",
)
# Activate + Close are both reached from the sticky-footer Help
# popover (Activate / Manage-license link and Close button). They
# are registered with ``st.navigation`` so ``/activate`` and
# ``/close`` remain URL-routable — pages not listed in the dict
# 404 even by direct hit — but their sidebar entries are hidden
# via CSS in ``hide_streamlit_chrome``. We group them under a
# single section header that is also hidden by that same CSS rule
# so no orphan "Account" / "Close" label is left in the sidebar.
activate = st.Page(
"pages/_Activate.py",
title=_t("nav.activate_title") or "Activate",
icon=":material/key:",
url_path="activate",
)
close = st.Page(
"pages/99_Close.py",
title=_t("nav.close_title") or "Close",
icon=":material/close:",
url_path="close",
)
# Activate + Close are placed in the unlabeled section alongside
# Home (key ``""`` — Streamlit renders no header for it). The CSS
# in ``hide_streamlit_chrome`` then hides just their two links by
# ``href``, leaving Home visible and no orphan section header /
# drilldown marker in the sidebar.
return {
"": [home, activate, close],
section_label("cleaners"): by_section["cleaners"],
section_label("transformations"): by_section["transformations"],
section_label("automations"): by_section["automations"],
}
pg = st.navigation(_build_navigation())
pg.run()