feat(gui): sidebar sections + non-technical tool labels

Sidebar nav now groups tools under Data Review / Data Cleaners /
Transformations / Automations via st.navigation, replacing the flat
auto-discovered list. Tool display names switch to action-first
phrasing (Find Duplicates, Fix Missing Values, Find Unusual Values,
Standardize Formats, Clean Text, Quality Check, Map Columns, Combine
Files, Automated Workflows) in EN + ES packs and on each page's H1.

The Data Cleaners section follows the requested order: Missing
Values → Outliers → Text Cleaner → Format Standardizer → Deduplicator
→ Quality Check. (Text Cleaner kept inside cleaners since the request
didn't list it but the tool still ships.) Registry now carries a
section field; helpers added: tools_in_section(), section_label().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 19:36:01 +00:00
parent 624f99653e
commit 93e43fc0d9
19 changed files with 356 additions and 199 deletions

View File

@@ -168,11 +168,11 @@ class TestHomeToolGridLocalization:
English names. Pin a few representative ones."""
@pytest.mark.parametrize("needle", [
"Eliminador de duplicados",
"Limpiador de texto",
"Estandarizador de formatos",
"Gestor de valores faltantes",
"Mapeador de columnas",
"Buscar duplicados",
"Limpiar texto",
"Estandarizar formatos",
"Corregir valores faltantes",
"Mapear columnas",
])
def test_es_tool_name_on_home_grid(self, home_app, needle):
with_language(home_app, "es")

View File

@@ -113,13 +113,13 @@ class TestGrouping:
labels = [e.label for e in app.expander]
# Two unique tools → two expanders. Each label carries the
# tool's display name + finding count.
text_cleaner_expanders = [lbl for lbl in labels if "Text Cleaner" in lbl]
format_expanders = [lbl for lbl in labels if "Format Standardizer" in lbl]
text_cleaner_expanders = [lbl for lbl in labels if "Clean Text" in lbl]
format_expanders = [lbl for lbl in labels if "Standardize Formats" in lbl]
assert len(text_cleaner_expanders) == 1, (
f"expected one Text Cleaner expander; got: {labels}"
f"expected one Clean Text expander; got: {labels}"
)
assert len(format_expanders) == 1, (
f"expected one Format Standardizer expander; got: {labels}"
f"expected one Standardize Formats expander; got: {labels}"
)
def test_tool_names_localize_in_spanish(self):
@@ -127,7 +127,7 @@ class TestGrouping:
app = _harness(findings, lang="es")
app.run()
labels = [e.label for e in app.expander]
assert any("Limpiador de texto" in lbl for lbl in labels), (
assert any("Limpiar texto" in lbl for lbl in labels), (
f"Spanish tool name missing; expanders: {labels}"
)
@@ -140,7 +140,7 @@ class TestGrouping:
app.run()
labels = [e.label for e in app.expander]
# Pack template: "{tool} — {n} finding(s)"
text_cleaner_label = next(l for l in labels if "Text Cleaner" in l)
text_cleaner_label = next(l for l in labels if "Clean Text" in l)
assert "3" in text_cleaner_label, (
f"expected count '3' in expander label; got {text_cleaner_label!r}"
)
@@ -163,7 +163,7 @@ class TestOpenToolButton:
# raw markdown. We probe both.
text = collected_text(app)
# Pack template: "Open {tool} →"
assert "Open Text Cleaner" in text
assert "Open Clean Text" in text
def test_open_tool_label_spanish(self):
findings = [_make_finding(tool="02_text_cleaner")]
@@ -171,7 +171,7 @@ class TestOpenToolButton:
app.run()
text = collected_text(app)
# Pack template: "Abrir {tool} →"
assert "Abrir Limpiador de texto" in text
assert "Abrir Limpiar texto" in text
# ---------------------------------------------------------------------------

View File

@@ -45,7 +45,7 @@ class TestGateNoUpload:
text = collected_text(app)
# The dedup page title is the unambiguous signal that the gate
# didn't short-circuit.
assert "Deduplicator" in text
assert "Find Duplicates" in text
def test_no_upload_no_gate_warning(self, app_factory):
app = app_factory(GATED_PAGE)

View File

@@ -40,9 +40,9 @@ def lite_license(monkeypatch, tmp_path):
class TestLiteUnlockedPages:
@pytest.mark.parametrize("slug,signal", [
("1_Deduplicator", "Deduplicator"),
("2_Text_Cleaner", "Text Cleaner"),
("3_Format_Standardizer", "Format Standardizer"),
("1_Deduplicator", "Find Duplicates"),
("2_Text_Cleaner", "Clean Text"),
("3_Format_Standardizer", "Standardize Formats"),
])
def test_unlocked_pages_render_body(
self, lite_license, app_factory, slug, signal, small_csv_bytes,

View File

@@ -53,17 +53,17 @@ PAGE_SLUGS = [
# When a page gains real Spanish translation, flip its 'es' entry to
# the localized substring — the test surface stays the same.
EXPECTED_SUBSTRINGS: dict[str, dict[str, str]] = {
"0_Review": {"en": "Review", "es": "Review"},
"1_Deduplicator": {"en": "Deduplicator", "es": "Deduplicator"},
"2_Text_Cleaner": {"en": "Text Cleaner", "es": "Text Cleaner"},
"3_Format_Standardizer": {"en": "Format", "es": "Format"},
"4_Missing_Values": {"en": "Missing", "es": "Missing"},
"5_Column_Mapper": {"en": "Column", "es": "Column"},
"6_Outlier_Detector": {"en": "Outlier", "es": "Outlier"},
"7_Multi_File_Merger": {"en": "Merger", "es": "Merger"},
"8_Validator_Reporter": {"en": "Validator", "es": "Validator"},
"9_Pipeline_Runner": {"en": "Pipeline", "es": "Pipeline"},
"99_Close": {"en": "Close DataTools", "es": "Cerrar DataTools"},
"0_Review": {"en": "Review", "es": "Review"},
"1_Deduplicator": {"en": "Find Duplicates", "es": "Find Duplicates"},
"2_Text_Cleaner": {"en": "Clean Text", "es": "Clean Text"},
"3_Format_Standardizer": {"en": "Standardize", "es": "Standardize"},
"4_Missing_Values": {"en": "Fix Missing", "es": "Fix Missing"},
"5_Column_Mapper": {"en": "Map Columns", "es": "Map Columns"},
"6_Outlier_Detector": {"en": "Unusual", "es": "Unusual"},
"7_Multi_File_Merger": {"en": "Combine Files", "es": "Combine Files"},
"8_Validator_Reporter": {"en": "Quality Check", "es": "Quality Check"},
"9_Pipeline_Runner": {"en": "Automated", "es": "Automated"},
"99_Close": {"en": "Close DataTools", "es": "Cerrar DataTools"},
}
@@ -95,7 +95,7 @@ class TestHomePageRenders:
with_language(home_app, "es")
home_app.run()
text = collected_text(home_app)
assert "Eliminador de duplicados" in text
assert "Buscar duplicados" in text
class TestEveryPageRenders:

View File

@@ -78,7 +78,7 @@ class TestTextCleanerWorkflow:
app.run()
assert not app.exception
text = collected_text(app)
assert "Text Cleaner" in text
assert "Clean Text" in text
def test_preview_or_clean_button_present(self, app_factory, small_csv_bytes):
"""The text cleaner ships a primary action (label varies by
@@ -106,7 +106,7 @@ class TestFormatStandardizerWorkflow:
app.run()
assert not app.exception
text = collected_text(app)
assert "Format Standardizer" in text
assert "Standardize Formats" in text
# ---------------------------------------------------------------------------
@@ -148,7 +148,7 @@ class TestPipelineRunnerWorkflow:
app.run()
assert not app.exception
text = collected_text(app)
assert "Pipeline" in text
assert "Automated Workflows" in text
# ---------------------------------------------------------------------------
@@ -194,9 +194,9 @@ class TestReviewWorkflow:
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("slug,name", [
("6_Outlier_Detector", "Outlier"),
("7_Multi_File_Merger", "Merger"),
("8_Validator_Reporter", "Validator"),
("6_Outlier_Detector", "Unusual Values"),
("7_Multi_File_Merger", "Combine Files"),
("8_Validator_Reporter", "Quality Check"),
])
class TestComingSoonStubs:
def test_stub_renders(self, app_factory, slug, name):