V5.1 Update tests and documentation
Quality Gate / gate (push) Failing after 11s

This commit is contained in:
Jim Lancaster
2026-08-23 16:01:53 -05:00
parent ebf659b26c
commit ed6998d8da
18 changed files with 184 additions and 385 deletions
+4 -4
View File
@@ -1,6 +1,6 @@
# System Architecture (Version 4)
# System Architecture (Current Baseline: V5.1)
This document defines the current Version 4 architecture baseline.
This document defines the current V5.1 architecture baseline.
## Architecture Objectives
@@ -165,5 +165,5 @@ Current architecture rules live in `docs/*`.
- [System Requirements](requirements.md)
- [Data Model](schema.md)
- [Error Handling Policy](error_handling.md)
- [Error Handling invariant](../invariant/error_handling.md)
- [AI evidence invariant](../invariant/ai_evidence_and_provenance.md)
- [Error Handling invariant](./invariant/error_handling.md)
- [AI evidence invariant](./invariant/ai_evidence_and_provenance.md)
+3 -3
View File
@@ -1,6 +1,6 @@
# Error Handling Policy (Version 4)
# Error Handling Policy (Current Baseline: V5.1)
This policy defines the active Version 4 error taxonomy, translation boundaries, and retry semantics.
This policy defines the active V5.1 error taxonomy, translation boundaries, and retry semantics.
## Error Categories
@@ -113,6 +113,6 @@ taxonomy to the six canonical categories at the API/UI envelope boundary.
## Cross-Reference
- [Error Handling invariant](../invariant/error_handling.md)
- [Error Handling invariant](./invariant/error_handling.md)
- [System Requirements](requirements.md)
- [Data Model](schema.md)
+8 -8
View File
@@ -1,6 +1,6 @@
# Document Transcription System Overview (Version 4)
# Document Transcription System Overview (Current Baseline: V5.1)
This directory is the single source of truth for current Version 4 behavior and architecture.
This directory is the single source of truth for current V5.1 behavior and architecture.
## Canonical Reading Order
@@ -11,13 +11,13 @@ This directory is the single source of truth for current Version 4 behavior and
## Cross-Version Invariants
- [Historical Document Transcription Design Intent](../invariant/intent.md)
- [Transcription Methodology](../invariant/transcription_methodology.md)
- [Error Handling](../invariant/error_handling.md)
- [Digital Evidence and AI Processing Provenance](../invariant/ai_evidence_and_provenance.md)
- [UI Style Guide](../invariant/ui_style_guide.md)
- [Historical Document Transcription Design Intent](./invariant/intent.md)
- [Transcription Methodology](./invariant/transcription_methodology.md)
- [Error Handling](./invariant/error_handling.md)
- [Digital Evidence and AI Processing Provenance](./invariant/ai_evidence_and_provenance.md)
- [UI Style Guide](./invariant/ui_style_guide.md)
## Baseline Statement
The current Version 4 baseline includes behavior delivered through the architectural cleanup phases.
The current V5.1 baseline includes behavior delivered through the architectural cleanup phases and person-schema redesign.
Use this `docs/*` canonical set for active design and implementation decisions.
+7 -7
View File
@@ -4,14 +4,14 @@
This guide defines non-negotiable UI styling rules for the transcription application.
The design system is token-first and class-driven:
1. Theme tokens are defined in [src/transcription/ui/static/theme.css](src/transcription/ui/static/theme.css).
1. Theme tokens are defined in [src/transcription/ui/static/theme.css](../../src/transcription/ui/static/theme.css).
2. Python UI code composes semantic classes instead of inline color values.
3. Pages and components should share a single visual language across Documents, Jobs, People, and Sources flows.
## 2. Source of Truth
Use these files as the style authority:
1. [src/transcription/ui/static/theme.css](src/transcription/ui/static/theme.css) for color tokens, semantic utility classes, table styles, and viewer surfaces.
2. [src/transcription/ui/theme.py](src/transcription/ui/theme.py) for runtime NiceGUI theme bridge and shared UI helpers.
1. [src/transcription/ui/static/theme.css](../../src/transcription/ui/static/theme.css) for color tokens, semantic utility classes, table styles, and viewer surfaces.
2. [src/transcription/ui/theme.py](../../src/transcription/ui/theme.py) for runtime NiceGUI theme bridge and shared UI helpers.
If this document conflicts with implementation, update this document to match the code immediately after intentional style changes.
@@ -25,7 +25,7 @@ If this document conflicts with implementation, update this document to match th
## 4. Token System
### 4.1 Palette Tokens
Base palette variables live under :root in [src/transcription/ui/static/theme.css](src/transcription/ui/static/theme.css):
Base palette variables live under :root in [src/transcription/ui/static/theme.css](../../src/transcription/ui/static/theme.css):
1. --palette-carbon-black: #1c2321
2. --palette-cool-steel: #7d98a1
3. --palette-blue-slate: #5e6572
@@ -81,7 +81,7 @@ Semantic tokens currently include:
2. ui-table-header
3. ui-table-body
Use existing class combinations from [src/transcription/ui/components](src/transcription/ui/components) and [src/transcription/ui/pages](src/transcription/ui/pages) as reference implementations.
Use existing class combinations from [src/transcription/ui/components](../../src/transcription/ui/components) and [src/transcription/ui/pages](../../src/transcription/ui/pages) as reference implementations.
## 6. Legacy Class Policy
Legacy `vibe-` presentation classes are prohibited. Use `ui-` semantic classes from `theme.css`.
@@ -96,7 +96,7 @@ Legacy `vibe-` presentation classes are prohibited. Use `ui-` semantic classes f
## 8. Implementation Rules For Contributors
1. Prefer composing existing semantic classes before creating new ones.
2. If a new class is required, add it to [src/transcription/ui/static/theme.css](src/transcription/ui/static/theme.css) with a semantic name, then reuse it.
2. If a new class is required, add it to [src/transcription/ui/static/theme.css](../../src/transcription/ui/static/theme.css) with a semantic name, then reuse it.
3. Keep behavior ownership in Python and appearance ownership in CSS.
4. Update UI tests that assert exact text or labels when intentional copy changes are made.
5. Avoid introducing class churn unrelated to the feature being changed.
@@ -104,7 +104,7 @@ Legacy `vibe-` presentation classes are prohibited. Use `ui-` semantic classes f
## 9. Verification Checklist
Before merging UI changes, verify:
1. No new inline hex colors were introduced in UI pages/components.
2. New styles are token-backed and added to [src/transcription/ui/static/theme.css](src/transcription/ui/static/theme.css).
2. New styles are token-backed and added to [src/transcription/ui/static/theme.css](../../src/transcription/ui/static/theme.css).
3. Primary buttons, links, cards, and tables still render with consistent semantics.
4. Keyboard focus ring visibility is preserved.
5. Relevant UI and integration tests pass.
+4 -4
View File
@@ -1,14 +1,14 @@
# System Requirements (Version 4)
# System Requirements (Current Baseline: V5.1)
These requirements define the active Version 4 contract and align to current implementation.
These requirements define the active V5.1 contract and align to current implementation.
## Functional Requirements
### Domain and Record Management
- **REQ-4-001 Document Registry:** The system must create and update `Document` records with title, type, language, comments, date metadata, and optional location.
- **REQ-4-001 Document Registry:** The system must create and update `Document` records with title, type, date metadata, optional location, optional archive identifier, and optional notes.
- **REQ-4-002 Source Registry:** The system must create and update `Source` records linked to exactly one `Document`.
- **REQ-4-003 People Registry:** The system must create and update `Person` records and support many-to-many links to `Document` with role and confidence.
- **REQ-4-003 People Registry:** The system must create and update `Person` records, support many-to-many links to `Document` with role, and support many-to-many Person tagging via the shared Tag registry.
- **REQ-4-004 Registry Semantics:** Document types and person roles must support optional immutable semantic keys and hard-delete only when unreferenced.
### Job and Workflow Behavior
+63 -216
View File
@@ -1,239 +1,86 @@
# Roadmap Review: Recommendations & Version Plan
# Roadmap Plan (Starting at V6.0)
This is a review/consulting deliverable. It organizes your near-term changes,
nice-to-haves, and long-range goals into version buckets, building on
`./docs/ver4.8/feature_backlog_v4_8.md` (already-scoped image work) and
folding in the additional items from your original list.
This roadmap starts at **V6.0** and tracks forward-looking work only.
Versioning convention (per your direction): **4.84.11** are incremental
feature/fix releases; **5.x and 6.x are reserved for major changes** to the
app (a shared data-model overhaul, and the hosting migration, respectively).
## V6.0 - Hosting Migration
Final deliverable will be saved to `./docs` in the repo per your instruction.
Objective: move from local-only operation to secure, stable remote hosting.
---
### Scope
1. Containerize app runtime for production deployment.
2. Run PostgreSQL in Docker and migrate from SQLite.
3. Add Cloudflare Tunnel exposure with Access protection.
4. Add operational safeguards (health checks, restart policies, backups).
## V4.8 — Bug fixes + Image Experience
### Deliverables
- Production-ready `docker-compose` deployment for app + database + tunnel.
- Environment-based configuration for DB, uploads, prompts, and logging.
- Verified data migration path into PostgreSQL.
- Runbook updates for deploy, rollback, and backup/restore.
**Bug fixes (do first within this release — data correctness, not features):**
1. **Stale Error Detail after resubmit.** Root cause found in `db/models.py`:
`Source.latest_error_detail` sorts all `execution_attempts` on the latest
job by `attempt_number` descending and returns the **first attempt with
any `error_detail`**, even if that's an older attempt and the latest
attempt succeeded. Fix: only report error detail from the latest attempt,
don't fall through to earlier ones.
2. **"View Jobs" ≠ "View Sources" behavior.** `/documents/{id}/sources`
already redirects to the filtered Transcription Pipeline Jobs page;
`/documents/{id}/jobs` renders its own bare two-field custom page instead.
Fix: make `/documents/{id}/jobs` redirect the same way, filtered by document.
### Exit Criteria
- `/healthz` reports healthy app and worker in deployed environment.
- One end-to-end document -> source -> job workflow succeeds remotely.
- Backup and restore procedure is tested.
**Image Experience (Track A from the V4.8 backlog doc — already scoped, high
value/low risk, storage layer already exists):**
3. **Homepage Image Gallery** (recommended first feature in that doc):
`list_homepage_images()` already returns every stored image; only the
multi-image carousel UI and optional slideshow rotation are missing.
4. **Pan and Zoom on Source Detail**: recover `document_panzoom.py` from git
history (deleted in V4.6 Phase 5, `6a3ee26`), but vendor the Panzoom
library locally instead of the unpkg CDN, and reuse `resolve_media_url`
from `media_urls.py` instead of recreating its old helper. Scope to Source
Detail only — do not add it to the shared `dark_room_viewer`.
## V6.1 - Reporting Features
**Note:** the backlog doc's model-performance/telemetry item still applies as
stated there — it must wait for V4.7 Phase 4 to land first, since `duration_ms`
today mixes provider latency with preprocessing/DB commit time.
Objective: improve research value with person-centric outputs.
---
### Scope
1. Person timeline views using document dates and linked records.
2. AI-assisted biography/family-history generation from curated sources.
3. Exportable report views (human-readable, print-oriented).
## V4.9 — Detail Page Parity Pass
### Deliverables
- Timeline UI and service queries with clear ordering/filters.
- Prompted narrative generation workflow using existing evidence-safe patterns.
- Saved/printable report presentation for review and sharing.
Small, low-risk structural-parity fixes across Person/Job/Document detail pages:
- Hide "Maiden Name" on Person detail when empty.
- Job Record detail "Document Links" box → mirror the Document detail page's
"Sources & Pipeline Jobs" box (clickable name, Sources count, single
"View Sources" button, no "+Add Job").
- Transcription Pipeline Jobs page: add a Document Name column; resolve
Created vs. Updated by keeping **Updated** as the primary/visible field
(more actionable) and de-emphasizing Created rather than deleting it outright.
- Person detail "Linked Documents" → 3-column table (Document Name, Role,
Number of Pages), with clickable document names, dropping the "Open" button.
### Exit Criteria
- Timelines are reproducible from persisted records.
- Narrative generation is traceable to source records and prompts.
- Reports can be reviewed without modifying archival source data.
---
## V6.2 - Access Control and Multi-User Readiness
## V4.10 — Settings Consolidation & Small Enhancements
Objective: prepare for managed collaboration beyond single-user operation.
- **Settings page → tabs**, not sub-pages (per your confirmation). Convert the
existing stacked cards (Document Types, Person Roles, Prompts) into
`ui.tabs`/`ui.tab_panels`, and add a new **Home Page Text** tab. One route,
no navigation overhead, scales cleanly as more settings are added.
- **Google Maps links** for Person birth/death locations — cheap, no
scraping, a formatted place string becomes a Maps search link.
- **FamilySearch ID lookup** (scope confirmed as narrow): on the Person
create/edit form, add a FamilySearch ID field and an "Auto-fill from
FamilySearch" button that fetches only birth date/place, death date/place,
and marriage date/spouse for that specific ID — a single-record lookup, not
a crawler. This keeps FamilySearch.org as a companion reference rather than
a data source the app tries to replace. Because it's ID-driven and
single-record, it's a much smaller, safer feature than open-ended scraping —
worth doing at this scope, revisit if FamilySearch's page structure changes
and breaks the parser.
### Scope
1. Introduce application-level authentication.
2. Add role-based authorization (admin/editor/contributor/viewer).
3. Add audit visibility for user-attributed write actions.
---
### Deliverables
- User identity model and login/session flow.
- Route/page/service authorization enforcement.
- Audit metadata for sensitive create/update/delete workflows.
## V4.11 — Approved Scope
### Exit Criteria
- Unauthorized operations are blocked consistently across UI/API.
- Role policies are enforced by deterministic tests.
- User-attributed changes are visible for audit/review.
- **Tags** (supersedes "collections"): many-to-many tagging for Documents with
Settings-style management (same pattern as Document Types and Person Roles),
autocomplete-capable assignment, and a dedicated **Tags** entry point for
browse/filter-by-tag workflows.
- **Source Detail simplification**: remove the separate **Transcription Text**
card; show Source image + Editable Revision + Source/SourceJob metadata in a
3-column top layout, then keep Candidate Machine Transcriptions below the
image/revision area.
- **Integrity reconciliation checks in tests**:
- document folder count under `UPLOAD_DIR/documents` must equal `document`
row count.
- source file count under each `UPLOAD_DIR/documents/{document_id}` folder
must equal `source` row count for that Document.
- failures should include actionable mismatch details (missing row/folder or
file/source mapping).
- **UI table updates**:
- Archival Documents: remove **Archive Ref**, add **# Sources**.
- Archival Entities: People: remove **Display Name** and **Maiden Name**
columns, add **FamilySearch ID**.
- Transcription Pipeline Jobs: add **# Sources**.
- **Create Processing Job page**: Provider and Model must be selectable for new
job creation.
## V6.3 - Scalability and Multi-Tenant Direction (Optional)
Deferred out of this release:
Objective: keep architecture ready for broader deployment footprints.
- UI theme selection.
- Settings-based `.env` editing and runtime controls.
- Person table structural redesign (removing/splitting name fields).
### Scope
1. Evaluate per-tenant or per-user data partitioning strategy.
2. Formalize connection/runtime strategy for tenant-aware DB selection.
3. Expand operational telemetry for throughput and cost monitoring.
---
### Deliverables
- Decision document for tenancy model and migration strategy.
- Prototype-safe runtime boundary for selecting data targets.
- Monitoring baseline for queue depth, job latency, and provider cost trends.
## V5.0 — Unified Photos Table (major data-model change)
### Exit Criteria
- Selected tenancy strategy is documented and testable.
- Operational metrics support capacity planning.
V5.0 standardizes homepage images and Person portraits into one shared `photo`
table and one storage layout.
## Planning Notes
Finalized shape:
- `photo`: `id`, nullable `person_id`, `path`, `description`, `is_primary`,
timestamps.
- `person_id IS NULL` = homepage photos; non-null = Person photos.
- `is_primary` is the featured/first photo for that owner (homepage or Person).
- No separate context enum; ownership is derived from `person_id`.
- All image files are stored under `UPLOAD_DIR/photos/{photo_id}{suffix}`.
- `Person.portrait_path` is removed.
- `HOMEPAGE_DIR` is retired; homepage markdown remains file-backed at
`UPLOAD_DIR/homepage.md`.
Migration policy for legacy installs:
- Export/import rebuild remains the migration mechanism.
- Legacy `person.portrait_path` values are backfilled into `photo` rows.
- Legacy homepage images under `UPLOAD_DIR/homepage` are backfilled into
homepage `photo` rows.
- Legacy homepage markdown is relocated to `UPLOAD_DIR/homepage.md`.
---
## V5.1 — Person table structural redesign
- Replace `person.full_name` with split required fields:
- `last_name`
- `given_names` (first + middle)
- Remove `display_name` and `maiden_name` from active schema/UI.
- Keep `family_search_id` optional and unique (not required in this version).
- Add `person_tag` many-to-many links so People use the same Tag registry as Documents.
- Update Archival Entities: People list columns to:
- Last Name
- First & Middle
- FamilySearch ID
- Birth Date
- Death Date
- # Documents
- Migration/export-import behavior backfills split names from legacy `full_name` values.
---
## V6.0 — Server Hosting Migration
Your stated approach (Postgres in Docker, app in Docker, Cloudflare Tunnel) is
the standard, low-maintenance way to get secure remote access without exposing
ports or running your own VPN/reverse-proxy TLS setup. Sequential stages
(each de-risks the next):
**Stage 0 — Prerequisites**
- Confirm data access already goes through the service layer (it does, per
`.github/instructions/services.instructions.md`) — this is what makes the
DB swap and later auth additive rather than a rewrite.
- Confirm upload/file storage path (`settings.upload_dir`) is Docker-volume-friendly.
- Confirm DB URL and upload dir are both env-overridable (`.env`/`.env.example`
already exist).
**Stage 1 — Containerize** against the *existing* SQLite file first (smallest
possible change) to validate the container boundary (networking, volumes,
permissions) before also switching databases. `Dockerfile`/`docker-compose.yml`
already exist in the repo — confirm current intent vs. extend for production.
**Stage 2 — Migrate to PostgreSQL.** Since the app uses SQLModel/SQLAlchemy,
this should mostly be a connection string/dialect change plus a data migration
script. Do this as its own isolated step so a regression is attributable to
the DB swap alone. Resolve the Postgres connection through a small
factory/indirection point rather than a single global engine constant — this
costs nothing now and keeps the door open for per-user databases later (see
Beyond, below).
**Stage 3 — Expose via Cloudflare Tunnel.** Add a `cloudflared` container
pointed at the app's internal port (no public port exposure needed). Put the
tunnel hostname behind Cloudflare Access (free tier, email OTP/SSO) as the
**first layer of remote-access auth**, even before the app has its own login
system — gets secure remote access working quickly and buys time to do
Stage 4 and later user-auth properly instead of rushed.
**Stage 4 — Backups & operational hygiene.** Automated Postgres backups
(scheduled `pg_dump`) and uploaded-file backups *before* exposing this to the
internet — this is the point where a data-loss incident would be most
damaging. Basic container health checks/restart policies in compose.
---
## V6.1 — Reporting Features
Independent of hosting — can be built any time, including in parallel with
V6.0:
- **Person timelines**: a query/aggregation feature (documents by person,
ordered by document date), no hosting dependency.
- **AI-written biographies/family histories**: likely reuses the existing
prompt/provider abstraction (`services/prompts.py`) already built for
transcription, rather than needing new infrastructure.
---
## Beyond / Pie-in-the-sky (informational only — no version assigned)
Not being built now, but the plan above is checked against these so nothing
forecloses them later:
- **Individual user logins + roles** (admin/editor/contributor/view-only):
Cloudflare Access (V6.0 Stage 3) is a stopgap, not this. When you get here,
add a real `User`/`Role` model and auth middleware. Because the codebase
already funnels data access through the service layer, adding a
"current user + role check" later is additive — this is the reason to keep
service-layer discipline intact through all the versions above, so
authorization can be bolted on without touching every page.
- **Per-user databases**: protected for by the Stage 2 connection-factory
recommendation above (schema-per-tenant or database-per-tenant becomes
much easier if the DB connection is already resolved through an
indirection point rather than hardcoded at startup).
---
## Current status
- V5.0 unified photos has been implemented with:
- shared `photo` table (`person_id` nullable for homepage ownership),
- flat media storage under `UPLOAD_DIR/photos/{photo_id}{suffix}`,
- migration backfill from legacy Person portraits and homepage images,
- homepage markdown relocated to `UPLOAD_DIR/homepage.md`.
- Version numbering/grouping is now established by implementation and can
proceed to V5.1 planning/execution.
- Keep architecture, schema, and UI contracts synchronized in `docs/` as each version lands.
- Prefer explicit schema migration over runtime compatibility write paths.
- Preserve evidence/provenance guarantees when adding new AI-powered features.
+3 -3
View File
@@ -1,6 +1,6 @@
# Data Model and Persistence Schema (Version 4)
# Data Model and Persistence Schema (Current Baseline: V5.1)
This document is the field-accurate Version 4 schema contract aligned to `src/transcription/db/models.py`.
This document is the field-accurate V5.1 schema contract aligned to `src/transcription/db/models.py`.
## Source of Truth Anchors
@@ -291,4 +291,4 @@ Constraint:
- [System Architecture](architecture.md)
- [System Requirements](requirements.md)
- [Error Handling Policy](error_handling.md)
- [AI Evidence and Provenance Invariant](../invariant/ai_evidence_and_provenance.md)
- [AI Evidence and Provenance Invariant](./invariant/ai_evidence_and_provenance.md)
+1 -1
View File
@@ -56,4 +56,4 @@ Each page contract contains:
## Current Baseline
These contracts describe the current flattened Version 4 baseline.
These contracts describe the current V5.1 baseline.
+2 -2
View File
@@ -19,8 +19,8 @@ People manages reusable historical-person records. A Person may appear in many D
- The title is **Archival Entities: People**.
- **Create new person** opens the create route.
- The table defaults to Last Name order and supports search and column sorting.
- Columns are Last Name, First & Middle, Tags, FamilySearch ID, Birth Date, Death Date, and # Documents.
- The table defaults to Name order (`Last Name, First & Middle`) and supports search and column sorting.
- Columns are Last Name, First & Middle; Tags; FamilySearch ID; Birth Date; Death Date; and # Documents.
- Name and Tags are left-aligned; FamilySearch ID, date columns, and # Documents are centered.
- # Documents reflects how many linked Documents each Person is connected to.
- Birth and death values independently prefer exact date, then approximate date, then `Unknown`.
-84
View File
@@ -1,84 +0,0 @@
# V4.8 Feature Backlog
**Status: not scoped.** This is a parking document, not a frozen boundary. It records feature work deferred out of V4.6 and V4.7 together with the evidence gathered so far, so that scoping V4.8 does not start from a blank page.
V4.8 is the first release since V4.5 to add **new user-facing behavior**. V4.6 was pure remediation and V4.7 is architectural cleanup; both were held to "no new features." That constraint ends here, which means V4.8 needs a different verification gate: V4.6 and V4.7 could be validated by "the suite still passes unchanged," and V4.8 cannot.
## Dependency on V4.7
**The model-performance rollup below must not begin until V4.7 Phase 4 lands.** `duration_ms` currently measures provider call *plus* image normalization, artifact persistence, and a DB commit, while the timeout governs only the provider call. A rollup built on it would chart preprocessing time mixed with provider latency and look authoritative while quietly misleading. V4.7 Phase 1 removes normalization and artifact persistence from that window, but the commit remains inside it until Phase 4.
## Candidate Features
### 1. Pan and Zoom on Source Detail
**Practicality: high. Effort: S.**
`ui/components/document_panzoom.py` existed and was **deleted in V4.6 Phase 5** (`6a3ee26`) because it was exported but wired to no page. It is 136 lines and recoverable:
```
git show 6a3ee26^:src/transcription/ui/components/document_panzoom.py
```
It already handled both images and PDFs (the latter via an iframe).
Two things must change on reintroduction - this is not a straight revert:
- It loaded Panzoom from the **unpkg CDN**. For an archival application the library should be vendored locally, otherwise the viewer breaks offline and depends on a third party staying available.
- It carried its own `_document_url()` helper. V4.6 Phase 5 extracted exactly that logic into `ui/components/media_urls.py` as `resolve_media_url`. Reintroducing the old helper would recreate the duplication Phase 5 removed.
Scope note: apply it to **Source Detail only**. `dark_room_viewer` (`ui/components/viewers.py`) is shared by four pages - `sources_page.py:268`, `home_page.py:25` and `:88`, `people_page.py:453`, `documents_page.py:524` - so a flag on it would leak pan-zoom into the homepage and document detail, which is not wanted. Add a separate component and use it only at `sources_page.py:268`.
Numbering note: the Phase 5 commit message states pan-zoom would return "in V4.7 alongside the other photo/image work." Moving it to V4.8 preserves that **intent** - it stays grouped with the photo work - and changes only the release number.
### 2. Homepage Image Gallery
**Practicality: high. Effort: S. Recommended first feature.**
The storage layer is already built:
- `ui/homepage_store.py:82` `list_homepage_images()` already returns **every** stored image, sorted by modification time.
- `store_homepage_image()` already accumulates files rather than overwriting.
- Today the UI calls only `latest_homepage_image()` and displays one image. `list_homepage_images()` is currently exercised **only by tests**.
So multi-image upload is effectively done; what is missing is presentation. NiceGUI 3.13.0 provides `ui.carousel` for left/right navigation and `ui.timer` for rotation.
Sub-items:
- Multi-image display with left/right navigation - small, mostly wiring.
- Optional slideshow rotating every ~10 minutes.
**Performance caveat:** `list_homepage_images()` performs a directory scan with a `stat()` per file on every call, and `home_page.py` already performs blocking I/O in the page handler (V4.6 review log [25], which was deliberately left alone). A rotating timer that re-enumerates on every tick would repeat that scan indefinitely. Enumerate once at page load and cache the list.
### 3. Multiple Person Portraits
**Practicality: medium. Effort: M/L. Defer behind item 2.**
`Person.portrait_path` is a **single string column**. Supporting multiple portraits requires a new table, a data migration, and upload UI - a materially larger job than item 2, and a different one.
### 4. Image Descriptions
**Practicality: medium, conditional. Effort: M.**
Homepage images are **filesystem-only with no metadata store**, so a caption has nowhere to live today. This needs either a sidecar JSON file or a real table.
This is cheap **only if** item 3 is being done at the same time, since both need the same metadata layer. Designing that layer twice would be wasteful; design it once or not at all.
## Suggested Grouping
If V4.8 is scoped as one release, the natural split is:
**Track A - image experience:** items 1 and 2. Both are small, both are self-contained UI work, and item 2's storage layer already exists. This is the highest value for the least risk.
**Track B - metadata layer:** items 3 and 4 together, since they share a table. Only worth starting if both are wanted.
**Track C - telemetry:** item 5, gated on V4.7 Phase 4.
Item 6 is not recommended.
## Open Questions for Scoping
- Should Track B happen at all, or is one portrait per person sufficient?
- Should the slideshow interval be configurable, or fixed?
- Should vendored Panzoom be committed to the repository, or fetched at build time?