From 2ef9086c751c99454fa3c7d47dd2a73e53a5726c Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 2 May 2026 22:19:54 +0200 Subject: [PATCH] Layout optimization initial page --- src/repo_registry/web_ui/views.py | 77 ++++++++++++++++++++----------- tests/test_web_api.py | 47 +++++++++++++++++-- 2 files changed, 92 insertions(+), 32 deletions(-) diff --git a/src/repo_registry/web_ui/views.py b/src/repo_registry/web_ui/views.py index 21275ec..c2cdaa1 100644 --- a/src/repo_registry/web_ui/views.py +++ b/src/repo_registry/web_ui/views.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import asdict from html import escape from pathlib import Path -from urllib.parse import quote_plus +from urllib.parse import quote_plus, urlparse from fastapi import APIRouter, Depends, Form, HTTPException, Query from fastapi.responses import HTMLResponse, PlainTextResponse, RedirectResponse @@ -17,6 +17,25 @@ router = APIRouter(include_in_schema=False) APP_NAME = "Repository Scoping" +def repository_directory_name(url: str, fallback: str) -> str: + parsed = urlparse(url) + path = parsed.path if parsed.scheme or parsed.netloc else url + normalized = path.replace("\\", "/").rstrip("/") + name = normalized.rsplit("/", 1)[-1] if normalized else "" + if name.endswith(".git"): + name = name[:-4] + return name or fallback + + +def repository_display_name(repository) -> str: + return repository_directory_name(repository.url, repository.name) + + +def repository_dict_display_name(repository: dict) -> str: + fallback = str(repository.get("name") or repository.get("repository_name") or "") + return repository_directory_name(str(repository.get("url") or ""), fallback) + + def page( title: str, body: str, @@ -238,7 +257,7 @@ def render_repository_index( rows = "\n".join( f""" - {escape(repo.name)} + {escape(repository_display_name(repo))} {escape(repo.status)} {escape(repo.branch)} {escape(repo.url)} @@ -260,6 +279,15 @@ def render_repository_index(

Repositories

{error}
+
+
+

Registry

+
+ + + {rows or ''} +
NameStatusBranchSource
No repositories yet.
+

Register Repository

@@ -276,16 +304,6 @@ def render_repository_index(
-
-
-

Registry

- Discovery -
- - - {rows or ''} -
NameStatusBranchSource
No repositories yet.
-
""" response = page("Repositories", body) @@ -323,6 +341,7 @@ def repository_scope_document( service: RegistryService = Depends(get_service), ) -> HTMLResponse: repository = service.get_repository(repository_id) + display_name = repository_display_name(repository) checkout = service.ingestion.cached_checkout(repository.url) if checkout is None: rendered = ( @@ -342,14 +361,14 @@ def repository_scope_document( body = f"""

SCOPE.md

-

Canonical scope summary for the {escape(repository.name)} repository.

+

Canonical scope summary for the {escape(display_name)} repository.

{rendered}
""" return page( "SCOPE.md", body, - selected_repository=repository.name, + selected_repository=display_name, selected_repository_id=repository.id, ) @@ -721,6 +740,7 @@ def repository_detail( repository = service.get_repository(repository_id) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc + display_name = repository_display_name(repository) runs = service.list_analysis_runs(repository_id) ability_map = service.ability_map(repository_id) latest_candidate = latest_completed_candidate_graph( @@ -746,7 +766,7 @@ def repository_detail( ) body = f"""
-

{escape(repository.name)}

+

{escape(display_name)}

Export SCOPE Back @@ -889,9 +909,9 @@ def repository_detail( """ return page( - repository.name, + display_name, body, - selected_repository=repository.name, + selected_repository=display_name, selected_repository_id=repository.id, ) @@ -1254,6 +1274,7 @@ def analysis_run_detail( service: RegistryService = Depends(get_service), ) -> HTMLResponse: repository = service.get_repository(repository_id) + display_name = repository_display_name(repository) candidate_graph = service.candidate_graph(repository_id, analysis_run_id) candidate_graph_data = asdict(candidate_graph) facts = service.list_observed_facts(repository_id, analysis_run_id) @@ -1273,7 +1294,7 @@ def analysis_run_detail( ) body = f"""
-

{escape(repository.name)} · Run #{analysis_run_id}

+

{escape(display_name)} · Run #{analysis_run_id}

{render_run_detail_compare_link(repository_id, analysis_run_id, service.list_analysis_runs(repository_id))} Repository
@@ -1346,9 +1367,9 @@ def analysis_run_detail( """ return page( - f"{repository.name} Run {analysis_run_id}", + f"{display_name} Run {analysis_run_id}", body, - selected_repository=repository.name, + selected_repository=display_name, selected_repository_id=repository.id, ) @@ -1411,9 +1432,10 @@ def repository_element_listing( service: RegistryService = Depends(get_service), ) -> HTMLResponse: repository = service.get_repository(repository_id) + display_name = repository_display_name(repository) if scope in {"approved", "candidate"} and not entry_filter: entry_filter = scope - title = element_listing_title(repository.name, scope, type) + title = element_listing_title(display_name, scope, type) if scope in {"all", "approved", "candidate"}: elements = graph_element_rows( asdict(service.ability_map(repository_id)), @@ -1519,7 +1541,7 @@ def repository_element_listing( return page( title, body, - selected_repository=repository.name, + selected_repository=display_name, selected_repository_id=repository.id, ) @@ -1539,9 +1561,10 @@ def analysis_run_diff_detail( ) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc + display_name = repository_display_name(diff.repository) body = f"""
-

{escape(diff.repository.name)} · Change Review

+

{escape(display_name)} · Change Review

Target Run Repository
@@ -1575,9 +1598,9 @@ def analysis_run_diff_detail(
""" return page( - f"{diff.repository.name} Change Review", + f"{display_name} Change Review", body, - selected_repository=diff.repository.name, + selected_repository=display_name, selected_repository_id=diff.repository.id, ) @@ -2147,7 +2170,7 @@ def render_repository_checkbox_list( @@ -2162,7 +2185,7 @@ def render_compared_repositories(repositories: list[dict]) -> str: rows = "\n".join( f""" - {escape(repository['name'])} + {escape(repository_dict_display_name(repository))} {escape(repository['status'])} {escape(repository['branch'])} diff --git a/tests/test_web_api.py b/tests/test_web_api.py index 2bf502d..dafc8fd 100644 --- a/tests/test_web_api.py +++ b/tests/test_web_api.py @@ -323,6 +323,43 @@ def test_ui_uses_repository_scoping_brand(): assert "Repository Ability Registry" not in response.text +def test_ui_homepage_registry_panel_uses_directory_identifier_and_left_column(tmp_path): + source = tmp_path / "short-dir" + source.mkdir() + (source / "README.md").write_text("# Short Dir\n", encoding="utf-8") + + def override_settings(): + return Settings( + database_path=str(tmp_path / "ui-home.sqlite3"), + checkout_root=str(tmp_path / "ui-home-checkouts"), + ) + + app.dependency_overrides[get_settings] = override_settings + client = TestClient(app) + try: + response = client.post( + "/repos", + json={ + "name": "Very Long Marketing Project Name", + "url": str(source), + }, + ) + assert response.status_code == 201 + + homepage = client.get("/ui") + + assert homepage.status_code == 200 + assert homepage.text.index(">Registry<") < homepage.text.index(">Register Repository<") + registry_panel = homepage.text[ + homepage.text.index(">Registry<") : homepage.text.index(">Register Repository<") + ] + assert "short-dir" in registry_panel + assert "Very Long Marketing Project Name" not in registry_panel + assert "Discovery" not in registry_panel + finally: + app.dependency_overrides.clear() + + def test_ui_scope_page_presents_scope_md(): client = TestClient(app) @@ -1281,10 +1318,10 @@ def test_ui_register_analyze_and_approve_loop(tmp_path): repo_scope_response = client.get(f"/ui/repos/{repository_id}/scope") assert repo_scope_response.status_code == 200 assert ( - f'UI Repo' + f'repo' in repo_scope_response.text ) - assert "Canonical scope summary for the UI Repo repository." in repo_scope_response.text + assert "Canonical scope summary for the repo repository." in repo_scope_response.text assert "UI Repo owns the status reporting scope." in repo_scope_response.text edit_repository_response = client.post( @@ -1378,7 +1415,7 @@ def test_ui_register_analyze_and_approve_loop(tmp_path): approved_detail = client.get(approve_response.headers["location"]) assert approved_detail.status_code == 200 assert ( - f'UI Repo Edited' + f'repo' in approved_detail.text ) assert "Approved Characteristics" in approved_detail.text @@ -1409,7 +1446,7 @@ def test_ui_register_analyze_and_approve_loop(tmp_path): ) assert scope_listing.status_code == 200 assert ( - f'UI Repo Edited' + f'repo' in scope_listing.text ) assert f'UI Repo' in scope_listing.text @@ -2362,7 +2399,7 @@ def test_ui_discovery_compare_gap_and_export(tmp_path): assert discovery.status_code == 200 assert "Compare Repositories" in discovery.text assert "Capability Gap Report" in discovery.text - assert "EmptyProfile" in discovery.text + assert "empty-profile-ui" in discovery.text assert "No approved profile" in discovery.text comparison = client.get(