Layout optimization initial page

This commit is contained in:
2026-05-02 22:19:54 +02:00
parent bf2dc4ae98
commit 2ef9086c75
2 changed files with 92 additions and 32 deletions

View File

@@ -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"""
<tr>
<td><a href="/ui/repos/{repo.id}">{escape(repo.name)}</a></td>
<td><a href="/ui/repos/{repo.id}">{escape(repository_display_name(repo))}</a></td>
<td><span class="pill">{escape(repo.status)}</span></td>
<td class="source">{escape(repo.branch)}</td>
<td class="source">{escape(repo.url)}</td>
@@ -260,6 +279,15 @@ def render_repository_index(
<h1>Repositories</h1>
{error}
<div class="grid">
<section class="panel">
<div class="actions">
<h2 style="margin-right:auto">Registry</h2>
</div>
<table>
<thead><tr><th>Name</th><th>Status</th><th>Branch</th><th>Source</th></tr></thead>
<tbody>{rows or '<tr><td colspan="4" class="muted">No repositories yet.</td></tr>'}</tbody>
</table>
</section>
<section class="panel">
<h2>Register Repository</h2>
<form class="stack" method="post" action="/ui/repos">
@@ -276,16 +304,6 @@ def render_repository_index(
</div>
</form>
</section>
<section class="panel">
<div class="actions">
<h2 style="margin-right:auto">Registry</h2>
<a class="button secondary" href="/ui/discovery">Discovery</a>
</div>
<table>
<thead><tr><th>Name</th><th>Status</th><th>Branch</th><th>Source</th></tr></thead>
<tbody>{rows or '<tr><td colspan="4" class="muted">No repositories yet.</td></tr>'}</tbody>
</table>
</section>
</div>
"""
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"""
<h1>SCOPE.md</h1>
<section class="panel stack">
<p class="muted">Canonical scope summary for the {escape(repository.name)} repository.</p>
<p class="muted">Canonical scope summary for the {escape(display_name)} repository.</p>
{rendered}
</section>
"""
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"""
<div class="actions">
<h1 style="margin-right:auto">{escape(repository.name)}</h1>
<h1 style="margin-right:auto">{escape(display_name)}</h1>
<a class="button secondary" href="/ui/repos/{repository_id}/export">Export</a>
<a class="button secondary" href="/ui/repos/{repository_id}/scope">SCOPE</a>
<a class="button secondary" href="/ui">Back</a>
@@ -889,9 +909,9 @@ def repository_detail(
</section>
"""
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"""
<div class="actions">
<h1 style="margin-right:auto">{escape(repository.name)} · Run #{analysis_run_id}</h1>
<h1 style="margin-right:auto">{escape(display_name)} · Run #{analysis_run_id}</h1>
{render_run_detail_compare_link(repository_id, analysis_run_id, service.list_analysis_runs(repository_id))}
<a class="button secondary" href="/ui/repos/{repository_id}">Repository</a>
</div>
@@ -1346,9 +1367,9 @@ def analysis_run_detail(
</section>
"""
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"""
<div class="actions">
<h1 style="margin-right:auto">{escape(diff.repository.name)} · Change Review</h1>
<h1 style="margin-right:auto">{escape(display_name)} · Change Review</h1>
<a class="button secondary" href="/ui/repos/{repository_id}/analysis-runs/{target_analysis_run_id}">Target Run</a>
<a class="button secondary" href="/ui/repos/{repository_id}">Repository</a>
</div>
@@ -1575,9 +1598,9 @@ def analysis_run_diff_detail(
</div>
"""
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(
<label>
<span>
<input style="width:auto" type="checkbox" name="repository_ids" value="{repository.id}"{disabled}>
{escape(repository.name)}
{escape(repository_display_name(repository))}
</span>
<span class="muted">{escape(status)}</span>
</label>
@@ -2162,7 +2185,7 @@ def render_compared_repositories(repositories: list[dict]) -> str:
rows = "\n".join(
f"""
<tr>
<td><a href="/ui/repos/{repository['id']}">{escape(repository['name'])}</a></td>
<td><a href="/ui/repos/{repository['id']}">{escape(repository_dict_display_name(repository))}</a></td>
<td><span class="pill">{escape(repository['status'])}</span></td>
<td class="source">{escape(repository['branch'])}</td>
</tr>

View File

@@ -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'<a class="header-context" href="/ui/repos/{repository_id}">UI Repo</a>'
f'<a class="header-context" href="/ui/repos/{repository_id}">repo</a>'
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'<a class="header-context" href="/ui/repos/{repository_id}">UI Repo Edited</a>'
f'<a class="header-context" href="/ui/repos/{repository_id}">repo</a>'
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'<a class="header-context" href="/ui/repos/{repository_id}">UI Repo Edited</a>'
f'<a class="header-context" href="/ui/repos/{repository_id}">repo</a>'
in scope_listing.text
)
assert f'<a href="/ui/repos/{repository_id}">UI Repo</a>' 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(