Minor UI refinement
Quality Gate / gate (push) Failing after 49s

This commit is contained in:
Jim Lancaster
2026-08-24 12:58:34 -05:00
parent 9a7970c533
commit 96af7b2dc0
8 changed files with 85 additions and 16 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ Rules:
- The heading shows name, type, and internal ID. - The heading shows name, type, and internal ID.
- The header includes a contextual back action: **Back to Documents** by default, **Back to Person** when opened from Person Detail, and **Back to Job** when opened from Job Detail. - The header includes a contextual back action: **Back to Documents** by default, **Back to Person** when opened from Person Detail, and **Back to Job** when opened from Job Detail.
- The first Source, when present, appears in the dark-room viewer. - The first Source, when present, appears in the dark-room viewer.
- Archival Metadata shows authors, Document Type, tags, Document date (`MM-DD-YYYY` for exact dates), location, and archive identifier. Notes appear in a separate archival-notes block within the same card. - Archival Metadata shows authors, Document Type, tags, Document date (`MM-DD-YYYY` for exact dates), location (linked to Google Maps when present), and archive identifier. Notes appear in a separate archival-notes block within the same card.
- System Logistics shows created and updated timestamps. - System Logistics shows created and updated timestamps.
- Related People are grouped by role and link to Person Detail. - Related People are grouped by role and link to Person Detail.
- **Sources & Pipeline Jobs** shows counts and actions for filtered Sources, Document Jobs, and adding a Job. - **Sources & Pipeline Jobs** shows counts and actions for filtered Sources, Document Jobs, and adding a Job.
+1 -1
View File
@@ -20,7 +20,7 @@ The list accepts optional `document_id` and `job_id` query parameters. Document
- Global context provides **Create Job**. - Global context provides **Create Job**.
- Filtered context provides **Back to Document** or **Back to Job**. - Filtered context provides **Back to Document** or **Back to Job**.
- Rows are ordered by page number and then upload name. - Rows are ordered by page number and then upload name.
- Columns are Document Name, Page Number, Upload Title, Status, and Error Detail. - Columns are Upload Title, Page Number, Document Name, Status, and Error Detail.
- Document Name, Upload Title, and Error Detail are left-aligned; Status is centered. - Document Name, Upload Title, and Error Detail are left-aligned; Status is centered.
- Status labels are presented in uppercase for consistency with Jobs. - Status labels are presented in uppercase for consistency with Jobs.
- Stored Filename is intentionally absent from the list. - Stored Filename is intentionally absent from the list.
@@ -53,9 +53,9 @@ def render_sources_table(rows: Sequence[SourceTableRow]) -> None:
rows=_serialize_rows(rows), rows=_serialize_rows(rows),
columns=[ columns=[
{ {
"name": "document_name", "name": "upload_name",
"label": "Document Name", "label": "Upload Title",
"field": "document_name", "field": "upload_name",
"sortable": True, "sortable": True,
"classes": "font-serif text-left ui-table-cell-wrap", "classes": "font-serif text-left ui-table-cell-wrap",
"align": "left", "align": "left",
@@ -69,9 +69,9 @@ def render_sources_table(rows: Sequence[SourceTableRow]) -> None:
"style": "width: 10%;", "style": "width: 10%;",
}, },
{ {
"name": "upload_name", "name": "document_name",
"label": "Upload Title", "label": "Document Name",
"field": "upload_name", "field": "document_name",
"sortable": True, "sortable": True,
"classes": "font-serif text-left ui-table-cell-wrap", "classes": "font-serif text-left ui-table-cell-wrap",
"align": "left", "align": "left",
+10 -1
View File
@@ -27,10 +27,12 @@ from transcription.ui.components.confirm_delete import dependency_summary
from transcription.ui.components.confirm_delete import render_delete_actions from transcription.ui.components.confirm_delete import render_delete_actions
from transcription.ui.components.confirm_delete import render_delete_blocked_notice from transcription.ui.components.confirm_delete import render_delete_blocked_notice
from transcription.ui.components.data_display import archival_badge from transcription.ui.components.data_display import archival_badge
from transcription.ui.components.data_display import metadata_link_row
from transcription.ui.components.data_display import metadata_row from transcription.ui.components.data_display import metadata_row
from transcription.ui.components.error_presenter import run_ui_action from transcription.ui.components.error_presenter import run_ui_action
from transcription.ui.components.error_presenter import show_error from transcription.ui.components.error_presenter import show_error
from transcription.ui.components.formatters import compact_date from transcription.ui.components.formatters import compact_date
from transcription.ui.components.formatters import google_maps_search_url
from transcription.ui.components.formatters import parse_iso_date from transcription.ui.components.formatters import parse_iso_date
from transcription.ui.components.formatters import parse_uuid from transcription.ui.components.formatters import parse_uuid
from transcription.ui.components.guards import parsed_record_id from transcription.ui.components.guards import parsed_record_id
@@ -560,7 +562,14 @@ def _render_bento_metadata_zone(document: Document) -> None:
) )
metadata_row("Tags:", ", ".join(tags) if tags else "Not set") metadata_row("Tags:", ", ".join(tags) if tags else "Not set")
metadata_row("Document Date:", _detail_document_date(document.document_date, document.document_date_raw)) metadata_row("Document Date:", _detail_document_date(document.document_date, document.document_date_raw))
metadata_row("Location Created:", document.location_created or "Not set") if document.location_created:
metadata_link_row(
"Location Created:",
document.location_created,
google_maps_search_url(document.location_created),
)
else:
metadata_row("Location Created:", "Not set")
metadata_row("Archive Identifier:", document.archive_identifier or "Not set") metadata_row("Archive Identifier:", document.archive_identifier or "Not set")
with ui.column().classes("w-full mt-2"): with ui.column().classes("w-full mt-2"):
+16 -7
View File
@@ -655,13 +655,22 @@ async def _render_person_photo_zone(
active_index = [0] active_index = [0]
with ui.column().classes("col-span-12 lg:col-span-4"), archival_card(title="Photos", extra_classes="gap-3"): with ui.column().classes("col-span-12 lg:col-span-4"), archival_card(title="Photos", extra_classes="gap-3"):
_render_photo_viewer_with_navigation(
photos=photos, @ui.refreshable
active_index=active_index, def render_photo_viewer() -> None:
settings=settings, def refresh_photo_viewer() -> None:
request=request, render_photo_viewer.refresh()
empty_message="No portrait photo uploaded yet.",
) _render_photo_viewer_with_navigation(
photos=photos,
active_index=active_index,
settings=settings,
request=request,
empty_message="No portrait photo uploaded yet.",
on_change=refresh_photo_viewer,
)
render_photo_viewer()
def _shift_gallery_index(*, photos: list, active_index: list[int], step: int) -> None: def _shift_gallery_index(*, photos: list, active_index: list[int], step: int) -> None:
+2
View File
@@ -37,6 +37,7 @@ async def seed_person_and_document():
document_type_id=letter_type.id, document_type_id=letter_type.id,
archive_identifier="ZC-1924-001", archive_identifier="ZC-1924-001",
document_date=date(1924, 7, 4), document_date=date(1924, 7, 4),
location_created="Salt Lake City, Utah",
) )
session.add(doc) session.add(doc)
await session.flush() await session.flush()
@@ -151,6 +152,7 @@ class TestDocumentsPageRendering:
assert "Letter" in response.text assert "Letter" in response.text
assert "07-04-1924" in response.text assert "07-04-1924" in response.text
assert "1924-07-04" not in response.text assert "1924-07-04" not in response.text
assert "google.com/maps/search/?api=1&query=Salt+Lake+City%2C+Utah" in response.text
assert "PIPELINE JOBS" in response.text.upper() assert "PIPELINE JOBS" in response.text.upper()
assert "Edit Document" in response.text assert "Edit Document" in response.text
assert "Back to Documents" in response.text assert "Back to Documents" in response.text
+39
View File
@@ -186,6 +186,45 @@ class TestPeoplePageRendering:
assert response.text.count("Edit Photo(s)") == 1 assert response.text.count("Edit Photo(s)") == 1
assert "Upload photo(s)" not in response.text assert "Upload photo(s)" not in response.text
@pytest.mark.asyncio
async def test_person_detail_page_shows_gallery_navigation_for_multiple_photos(self, app_client):
app, client = app_client
upload_dirs = {app.state.settings.upload_dir, get_settings().upload_dir}
for upload_dir in upload_dirs:
primary_file = upload_dir / "photos" / "portrait-primary.png"
secondary_file = upload_dir / "photos" / "portrait-secondary.png"
primary_file.parent.mkdir(parents=True, exist_ok=True)
primary_file.write_bytes(b"portrait-primary")
secondary_file.write_bytes(b"portrait-secondary")
async with session_scope() as session:
person = Person(given_names="Gallery", last_name="Navigation")
session.add(person)
await session.flush()
session.add_all(
[
Photo(
person_id=person.id,
path="photos/portrait-primary.png",
is_primary=True,
),
Photo(
person_id=person.id,
path="photos/portrait-secondary.png",
is_primary=False,
),
]
)
await session.commit()
person_id = str(person.id)
response = client.get(f"/ui/people/{person_id}")
assert response.status_code == 200
assert "Previous" in response.text
assert "Next" in response.text
assert "1 of 2" in response.text
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_person_photos_page_renders_photo_management_controls(self, app_client): async def test_person_photos_page_renders_photo_management_controls(self, app_client):
app, client = app_client app, client = app_client
+10
View File
@@ -1,5 +1,6 @@
"""Tests for the sources page routes and Source model properties.""" """Tests for the sources page routes and Source model properties."""
import re
from datetime import UTC from datetime import UTC
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@@ -198,6 +199,15 @@ class TestSourcesPageRendering:
assert "page_one.png" in response.text assert "page_one.png" in response.text
assert "Source Document" in response.text assert "Source Document" in response.text
assert "Stored Filename" not in response.text assert "Stored Filename" not in response.text
assert re.search(
r'"name":"upload_name","label":"Upload Title".*'
r'"name":"page_number","label":"Page Number".*'
r'"name":"document_name","label":"Document Name".*'
r'"name":"job_source_status","label":"Status".*'
r'"name":"job_source_error_detail","label":"Error Detail"',
response.text,
re.DOTALL,
)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_sources_page_filters_to_document_context(self, app_client): async def test_sources_page_filters_to_document_context(self, app_client):