From 09ec01e98b6a7467cf9a367b6249a828bd1661e9 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 8 Jun 2026 17:01:57 +0000 Subject: [PATCH] feat(gui): port journey-level nav + local-first pill to the live app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the live Streamlit app in line with the finalized layout-review mockups (structural/low-risk changes; verified by compile + registry sanity, still pending a streamlit-run visual check): - tools_registry: Data Cleaners now in pipeline order (Clean Text -> Standardize -> Fix Missing -> Find Duplicates); new "finance" section (Reconcile, PDF to CSV) and "coming_soon" section (Find Unusual, Quality Check, Combine Files). Adds those to the Section type + SECTION_LABELS. - app.py: Home becomes the "Start here" front door — a standalone, unlabeled top entry (play_circle icon) ahead of the hidden Activate/Logs/Close pages; nav groups reordered cleaners -> transformations -> automations -> finance -> coming soon. - _legacy.py: render_tool_header now shows the "Runs 100% locally" privacy pill (right-aligned, Ready tools only — omitted on Coming Soon stubs); accent emphasis CSS for the Start-here nav link. - i18n: add nav.start_here_title, nav.section_finance, nav.section_coming_soon to en + es packs. - DECISIONS.md: log the PDF/Reconcile in-bundle (Finance group) call. Co-Authored-By: Claude Opus 4.8 (1M context) --- DECISIONS.md | 33 ++++++++ src/gui/app.py | 35 +++++---- src/gui/components/_legacy.py | 45 +++++++++-- src/gui/tools_registry.py | 138 ++++++++++++++++++---------------- src/i18n/packs/en.json | 3 + src/i18n/packs/es.json | 3 + 6 files changed, 172 insertions(+), 85 deletions(-) create mode 100644 DECISIONS.md diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..07a4fdd --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,33 @@ +# Product & architecture decisions + +A running log of decisions that aren't obvious from the code and would +otherwise be re-litigated. Newest first. + +## 2026-06-08 — PDF to CSV and Reconcile stay in the bundle, under a "Finance" group + +**Decision:** `10_pdf_extractor` (PDF to CSV) and `11_reconciler` (Reconcile +Two Files) remain part of the DataTools suite. In the sidebar they are +segregated into their own **Finance** section, distinct from the +file-cleaning tools. + +**Context / why this needed deciding:** +- Both tools sit outside the documented 9-script cleaning architecture + (TECHNICAL.md / USER-GUIDE.md stop at the orchestrator). +- They occupy the "reconciliation / manual data-entry" territory the + product's honest-positioning note explicitly placed outside a + file-cleaning tool's scope. +- A journey-level UX review flagged that every extra tool in the main + sidebar raises the "which tool do I need?" load for a non-technical + buyer, so tools serving a different job should live in a clearly + different place. + +**Resolution:** Keep them in-bundle (they're built, useful, and ship +today) but group them under "Finance" so the cleaning flow stays +uncluttered. Revisit only if a separate finance-focused product emerges. + +**Implications:** +- `tools_registry.py`: Reconcile + PDF to CSV carry a `finance` section. +- Sidebar order: Start here → Data Cleaners → Transformations → + Automations → Finance → Coming soon. +- This is the source-of-truth realization of the `layout-review/` + mockups (see `layout-review/shell.js`). diff --git a/src/gui/app.py b/src/gui/app.py index 9632a31..28c33e3 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -82,6 +82,8 @@ def _build_navigation() -> dict[str, list]: "cleaners": [], "transformations": [], "automations": [], + "finance": [], + "coming_soon": [], } # Resolve the tool name through ``tool_name`` (i18n lookup) instead # of using the registry's English ``tool.name`` field, otherwise the @@ -96,16 +98,16 @@ def _build_navigation() -> dict[str, list]: ) ) - # Home is now surfaced under the new "Analysis" section as - # "File Analysis" — the home page's content (importing files, - # running the analyzer, browsing findings) is itself a data-analysis - # workflow, so grouping it next to Reconcile keeps the sidebar's - # mental model coherent. ``default=True`` still points at this - # page so first-visit lands here regardless of section placement. + # Home is the product's front door: "Start here". It's surfaced as a + # standalone, unlabeled top entry (in the "" section, ahead of the + # hidden Activate/Logs/Close pages) so it reads as the obvious + # starting point above the tool groups rather than one item among + # equals. The companion CSS in ``hide_streamlit_chrome`` gives its + # nav link accent emphasis. ``default=True`` lands first-visit here. home = st.Page( _home_page, - title=_t("nav.file_analysis_title") or "File Analysis", - icon=":material/insert_chart_outlined:", + title=_t("nav.start_here_title") or "Start here", + icon=":material/play_circle:", default=True, url_path="home", ) @@ -136,17 +138,20 @@ def _build_navigation() -> dict[str, list]: url_path="close", ) - # Activate / Logs / Close stay in the unlabeled section (key ``""``) - # so the CSS in ``hide_streamlit_chrome`` keeps hiding them by - # ``href``. Home moved out of that bucket into "Analysis" — the - # unlabeled section now contains ONLY hidden pages, so no orphan - # entry appears above the "Analysis" header in the sidebar. + # Home leads the unlabeled section (key ``""``) so "Start here" sits + # at the very top with no section header above it. Activate / Logs / + # Close follow in the same unlabeled bucket and stay hidden by their + # ``href`` via the CSS in ``hide_streamlit_chrome``. Section order + # below is the journey order: cleaners (pipeline order) → + # transformations → automations → finance → coming soon (last, so + # not-yet-shipped tools never interleave with working ones). return { - "": [activate, logs, close], - section_label("analysis"): [home, *by_section["analysis"]], + "": [home, activate, logs, close], section_label("cleaners"): by_section["cleaners"], section_label("transformations"): by_section["transformations"], section_label("automations"): by_section["automations"], + section_label("finance"): by_section["finance"], + section_label("coming_soon"): by_section["coming_soon"], } diff --git a/src/gui/components/_legacy.py b/src/gui/components/_legacy.py index bb09d3d..09e714c 100644 --- a/src/gui/components/_legacy.py +++ b/src/gui/components/_legacy.py @@ -95,6 +95,18 @@ footer { [data-testid="stSidebarNav"] a[href$="/close/"] { display: none !important; } +/* "Start here" front-door nav item — accent emphasis so the obvious + entry point reads at a glance above the tool groups. Targets the Home + link by href; accent values mirror theme.py (§3 color scale). */ +[data-testid="stSidebarNav"] a[href$="/home"], +[data-testid="stSidebarNav"] a[href$="/home/"] { + background: #fef4ed !important; + font-weight: 600 !important; +} +[data-testid="stSidebarNav"] a[href$="/home"]:hover, +[data-testid="stSidebarNav"] a[href$="/home/"]:hover { + background: #fde4d3 !important; +} /* Reclaim top padding lost from hidden header. Streamlit's default block-container padding-top is ~6rem (room for the header it ships). We hide the header so reclaim that space — the page title should sit @@ -2168,14 +2180,37 @@ def render_tool_header(tool_id: str) -> None: button as a defense-in-depth so the label can never wrap, no matter how the column ends up sized. """ - col_title, col_help = st.columns([8, 2]) + col_title, col_help = st.columns([7, 3]) with col_title: st.title(_t(f"tools.{tool_id}.page_title")) with col_help: - # Spacer pushes the popover button down so it sits closer to - # the title's baseline than to its top — without the spacer the - # button floats above the big title text. - st.write("") + # Local-first reassurance + Help, right-aligned opposite the + # title. The "Runs 100% locally" privacy pill is shown on every + # working tool page (where the user is actively feeding in a + # customer list) and omitted on not-yet-shipped "Coming Soon" + # tools, which process nothing. When the pill is shown it also + # serves as the spacer that nudges the popover down toward the + # title baseline; without it we keep the explicit spacer. + from src.gui.tools_registry import tool_by_id as _tool_by_id + _tool = _tool_by_id(tool_id) + if _tool is None or _tool.status == "Ready": + import html as _html + st.markdown( + '
' + '' + '' + '' + '' + '' + f'{_html.escape(_t("home.privacy_pill"))}' + '' + '
', + unsafe_allow_html=True, + ) + else: + # Spacer pushes the popover button down so it sits closer to + # the title's baseline than to its top. + st.write("") body = _t(f"tools.{tool_id}.help_md") # ``src.i18n.t`` falls back to returning the lookup key itself # on miss (see ``_resolve`` → key-as-string fallback). That's diff --git a/src/gui/tools_registry.py b/src/gui/tools_registry.py index c9612ca..615ebc3 100644 --- a/src/gui/tools_registry.py +++ b/src/gui/tools_registry.py @@ -24,7 +24,10 @@ Tier = Literal["core", "pro", "enterprise"] Status = Literal["Ready", "Coming Soon"] # Sidebar grouping. Tools are bucketed by what the user is trying to # accomplish rather than by implementation detail. -Section = Literal["analysis", "cleaners", "transformations", "automations"] +Section = Literal[ + "analysis", "cleaners", "transformations", "automations", + "finance", "coming_soon", +] @dataclass(frozen=True) @@ -42,35 +45,14 @@ class Tool: # Order in this list IS the order shown in each sidebar section, so -# arranging it carefully matters: within "cleaners" we lead with the -# operations a non-technical user is most likely to need (filling -# blanks, flagging outliers) before progressing to format cleanup, -# dedup, and the final quality report. +# arranging it carefully matters. Within "cleaners" the order is the +# recommended PIPELINE order (Clean Text → Standardize → Fix Missing → +# Find Duplicates) so a user running tools by hand follows the sequence +# the orchestrator would. "Coming Soon" tools are grouped at the end in +# their own section so they never interleave with working tools, and the +# finance-oriented tools (Reconcile, PDF to CSV) live in their own group +# (see DECISIONS.md 2026-06-08). TOOLS: list[Tool] = [ - Tool( - tool_id="04_missing_handler", - icon=":material/help_outline:", - name="Fix Missing Values", - description=( - "Find blank cells (even ones written as 'N/A' or '?') and fill " - "them in or remove them." - ), - page_slug="4_Missing_Values", - status="Ready", - section="cleaners", - ), - Tool( - tool_id="06_outlier_detector", - icon=":material/insights:", - name="Find Unusual Values", - description=( - "Spot values that look wrong — way too high, way too low, or " - "breaking your rules." - ), - page_slug="6_Outlier_Detector", - status="Coming Soon", - section="cleaners", - ), Tool( tool_id="02_text_cleaner", icon=":material/text_format:", @@ -95,6 +77,18 @@ TOOLS: list[Tool] = [ status="Ready", section="cleaners", ), + Tool( + tool_id="04_missing_handler", + icon=":material/help_outline:", + name="Fix Missing Values", + description=( + "Find blank cells (even ones written as 'N/A' or '?') and fill " + "them in or remove them." + ), + page_slug="4_Missing_Values", + status="Ready", + section="cleaners", + ), Tool( tool_id="01_deduplicator", icon=":material/search:", @@ -106,18 +100,6 @@ TOOLS: list[Tool] = [ status="Ready", section="cleaners", ), - Tool( - tool_id="08_validator_reporter", - icon=":material/check_circle:", - name="Quality Check", - description=( - "Check your file against rules you set, and export a PDF or " - "Excel report." - ), - page_slug="8_Validator_Reporter", - status="Coming Soon", - section="cleaners", - ), Tool( tool_id="05_column_mapper", icon=":material/view_column:", @@ -130,18 +112,6 @@ TOOLS: list[Tool] = [ status="Ready", section="transformations", ), - Tool( - tool_id="07_multi_file_merger", - icon=":material/account_tree:", - name="Combine Files", - description=( - "Combine several CSV or Excel files into one — even if their " - "columns don't match." - ), - page_slug="7_Multi_File_Merger", - status="Coming Soon", - section="transformations", - ), Tool( tool_id="09_pipeline_runner", icon=":material/auto_awesome:", @@ -154,17 +124,6 @@ TOOLS: list[Tool] = [ status="Ready", section="automations", ), - Tool( - tool_id="10_pdf_extractor", - icon=":material/picture_as_pdf:", - name="PDF to CSV", - description=( - "Pull transactions out of bank-statement PDFs into a clean CSV file." - ), - page_slug="10_PDF_Extractor", - status="Ready", - section="transformations", - ), Tool( tool_id="11_reconciler", icon=":material/compare_arrows:", @@ -175,7 +134,54 @@ TOOLS: list[Tool] = [ ), page_slug="11_Reconciler", status="Ready", - section="analysis", + section="finance", + ), + Tool( + tool_id="10_pdf_extractor", + icon=":material/picture_as_pdf:", + name="PDF to CSV", + description=( + "Pull transactions out of bank-statement PDFs into a clean CSV file." + ), + page_slug="10_PDF_Extractor", + status="Ready", + section="finance", + ), + Tool( + tool_id="06_outlier_detector", + icon=":material/insights:", + name="Find Unusual Values", + description=( + "Spot values that look wrong — way too high, way too low, or " + "breaking your rules." + ), + page_slug="6_Outlier_Detector", + status="Coming Soon", + section="coming_soon", + ), + Tool( + tool_id="08_validator_reporter", + icon=":material/check_circle:", + name="Quality Check", + description=( + "Check your file against rules you set, and export a PDF or " + "Excel report." + ), + page_slug="8_Validator_Reporter", + status="Coming Soon", + section="coming_soon", + ), + Tool( + tool_id="07_multi_file_merger", + icon=":material/account_tree:", + name="Combine Files", + description=( + "Combine several CSV or Excel files into one — even if their " + "columns don't match." + ), + page_slug="7_Multi_File_Merger", + status="Coming Soon", + section="coming_soon", ), ] @@ -187,6 +193,8 @@ SECTION_LABELS: dict[Section, str] = { "cleaners": "Data Cleaners", "transformations": "Transformations", "automations": "Automations", + "finance": "Finance", + "coming_soon": "Coming soon", } diff --git a/src/i18n/packs/en.json b/src/i18n/packs/en.json index f814545..bbbd000 100644 --- a/src/i18n/packs/en.json +++ b/src/i18n/packs/en.json @@ -193,9 +193,12 @@ "section_cleaners": "Data Cleaners", "section_transformations": "Transformations", "section_automations": "Automations", + "section_finance": "Finance", + "section_coming_soon": "Coming soon", "review_page_title": "Review", "home_page_title": "Home", "file_analysis_title": "File Analysis", + "start_here_title": "Start here", "section_account": "Account", "activate_title": "Activate", "close_title": "Close", diff --git a/src/i18n/packs/es.json b/src/i18n/packs/es.json index ce7a9f4..17f9e67 100644 --- a/src/i18n/packs/es.json +++ b/src/i18n/packs/es.json @@ -193,9 +193,12 @@ "section_cleaners": "Limpiadores de datos", "section_transformations": "Transformaciones", "section_automations": "Automatizaciones", + "section_finance": "Finanzas", + "section_coming_soon": "Próximamente", "review_page_title": "Revisión", "home_page_title": "Inicio", "file_analysis_title": "Análisis de archivo", + "start_here_title": "Empezar aquí", "section_account": "Cuenta", "activate_title": "Activar", "close_title": "Cerrar",