feat(gui): port journey-level nav + local-first pill to the live app
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) <noreply@anthropic.com>
This commit is contained in:
33
DECISIONS.md
Normal file
33
DECISIONS.md
Normal file
@@ -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`).
|
||||
@@ -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"],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
'<div style="display:flex;justify-content:flex-end">'
|
||||
'<span class="dt-privacy-pill">'
|
||||
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">'
|
||||
'<rect x="4" y="11" width="16" height="10" rx="2"/>'
|
||||
'<path d="M8 11V7a4 4 0 018 0v4"/>'
|
||||
'</svg>'
|
||||
f'{_html.escape(_t("home.privacy_pill"))}'
|
||||
'</span>'
|
||||
'</div>',
|
||||
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
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user