High-resolution image viewer powered by OpenSeadragon.
Built with Vite, React 19, TypeScript, Material UI, FastAPI, and PostgreSQL.
docker compose up --buildOpen http://localhost:5173 to view the app.
Source files in frontend/src/ and backend/app/ are bind-mounted into the containers, so edits are reflected immediately via hot-reload. The local compose seed also generates a Synthetic Monitoring Image tile set from db/seed-assets/synthetic-monitoring-image.jpeg for the synthetic Playwright journey.
All seed users share the password password.
No passwordless guest is seeded. Create a disposable guest through People or the API for the password-management validation flow.
| User | Password | Role | |
|---|---|---|---|
| Haruki Tanaka | admin@example.ca | password | admin |
| Carlos Henrique Souza | instructor@example.ca | password | instructor |
| Devon Staff | staff@example.ca | password | staff |
| Mira Patel | student@example.ca | password | student |
| Synthetic Student | synthetic.student@example.ca | password | student |
| Capability | Admin | Instructor | Staff | Student |
|---|---|---|---|---|
| Browse categories & view images | Yes | Yes | Yes | Yes† |
| Count objects in a selection | Yes | Yes | Yes | Yes† |
| View image uploader name | Yes | Yes | No | No |
| View collections (API; student deployment gate¶) | Yes | Yes | Yes | Yes¶ |
| Create collections‖ | Yes | Yes | Yes | Yes |
| Edit co-owned collections‖ | Yes | Yes | Yes | Yes |
| Delete own collections‖ | Yes | Yes | Yes* | Yes* |
| Restrict collections to programs/groups‖ | Yes | Yes* | No | No |
| Manage collection owners‖ | Yes | Yes* | No | No |
| Hide/show collections‖ | Yes | Yes | No | No |
| Manage any collection (incl. orphaned)‖ | Yes | No | No | No |
| Create/update categories | Yes | Yes | No | No |
| Delete categories | Yes | Yes | No | No |
| Manage page (image table) | Yes | Yes | No | No |
| Bulk import images | Yes | Yes | No | No |
| Manage programs | Yes | No | No | No |
| Manage groups | Yes | Yes* | No | No |
| Restrict categories to groups | Yes | Yes* | No | No |
| Manage announcement | Yes | Yes | No | No |
| Read announcements (bell, Inbox, reactions) | Yes | Yes | Yes | Yes |
| Author announcements (Mine, composer) | Yes | Yes | No | No |
| View component versions (About) | Yes | Yes | No | No |
| Post release notes / warning announcements | Yes | No | No | No |
| Post announcements (targeted to own programs/groups) | Yes | Yes | No | No |
Post everyone/staff/release/warning announcements |
Yes | No | No | No |
| Read + react to announcements in their audience | Yes | Yes | Yes | Yes |
| See announcement seen counts | Yes | Yes | Yes | No |
| Admin tab (backups) | Yes | No | No | No |
| User management (add/delete) | Yes | No | No | No |
| Mint local password-setup links (People and API) | Yes | No | No | No |
| Change own password (local accounts) | Yes | Yes | Yes | Yes |
| Set/regenerate/force another local password (People) | Yes | No | No | No |
| People tab (view users) | Yes | No | Yes‡ | No |
| List users (API) | Yes | Yes§ | Yes‡ | No |
* Instructors manage only the groups they co-own and may attach only groups they manage; admins manage and attach any group. Inherited program and group restrictions are treated as pre-existing when an instructor creates a child category, so carrying an ancestor restriction does not require new attach authority. See docs/groups.md.
† Students are subject to the program/group dual-gate visibility filter.
¶ Students see collections they co-own, public collections, and restricted collections that pass both the program and group gates — except curatorially hidden ones, which drop out of student view unless the student co-owns them; images they cannot open are omitted from collection responses. Admins, instructors and staff see every collection. Edit/delete/owner-management authority (admin, co-owner, or instructor in the owning program) is described in docs/collections.md.
All collections capabilities require COLLECTIONS_ENABLED=true; student
capabilities additionally require COLLECTIONS_STUDENT_ACCESS=true (default).
The stable overlay pins student access off: students receive 404 even for
owned collections and see no collections UI; other roles are unchanged.
‖ Collections support multiple user co-owners plus, or instead of, a program
owner. Every role may create collections (the creator becomes the first user
owner). Students may own at most 10 collections of each type and may add at
most 20 images to a sequence collection; synchronized collections keep the
four-image cap for every role. Admins and instructors may edit/delete
collections they co-own or whose owning program they belong to; students and
staff share one rule —
they may edit collections they co-own, but may change scope or delete only
when they are the sole user owner and no program owns the collection (*
on the student delete cell; staff follow the same rule). Staff also see
every collection like instructors, and create collections like students.
Instructor authority is program-bound (the * rows):
an instructor may restrict a collection to programs/groups only when they
belong to the program or manage the group, and may manage owners only for a
collection they co-own or one owned by a program they belong to — and may
assign only a program they belong to. Only admins can reassign collections
orphaned by a program deletion (no user owners and no program owner).
Staff and students can never manage the owner set, even on their own
collections. See docs/collections.md.
‡ Staff are a mostly view-only role for authenticated non-students. They see all content (no program/group visibility filter) and get a read-only People tab — the full user list with filters, but no add/edit/delete, bulk actions, or selection controls. They cannot be group members or group instructors. Collections are the exception: staff create them and hold the same owner rights as students (see ‖).
§ Instructors list only students and other instructors, with a minimal projection (no metadata/last_access) — enough to populate group pickers.
A program is a flat, admin/OIDC-managed access-control unit that gates category/image visibility for students. Only admins create, rename, and delete programs; a program may carry an oidc_group so membership is provisioned automatically by the IdP. A category tagged with one or more programs is visible to a student only if they belong to at least one of those programs (enforced by the student visibility filter). Programs are not hierarchical.
A group is an instructor-managed visibility dimension, independent of programs. Any admin or instructor can create a group and add student members (and instructor co-owners); a category restricted to one or more groups is visible to a student only if they belong to at least one of them. Visibility is a dual gate: a student sees a category only if it passes both the program gate and the group gate. Group memberships also surface as read-only chips in the student profile menu. See docs/groups.md for the model, authorization rules, API, and frontend behaviour, and docs/category-visibility-and-programs.md for the combined visibility evaluation.
Announcements are one stream for every role (epic #1832): a bell with a peek of
the newest unseen posts, an Announcements page (Inbox for everyone, Mine for
admins and instructors), a composer dialog with Markdown, media and emoji
reactions, a banner slot for posts flagged banner, a Home Notices shelf of
pinned posts and a login-screen broadcast. Release notes are announcements of
kind='release' (default audience admins + instructors + staff) — there is no
separate changelog. See
docs/announcements.md.
# Get a JWT token
TOKEN=$(curl -s http://localhost:8000/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.ca","password":"password"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")# Use the token on any protected route
curl -s http://localhost:8000/api/categories/ -H "Authorization: Bearer $TOKEN"cd frontend
npm ci
npm run devNote: the Vite proxy target uses the Docker service name (http://backend:8000). For local development without Docker, you need to run the backend separately and update the proxy target.
See docs/TESTING.md for the full test plan and verification procedures.
See docs/RELEASE_AND_DEPLOY_FLOW.md for how
a merged PR becomes a running pod in latest or stable: PR-title →
release-please, image/chart artifact contract, and the flux-fleet
reconciliation model for both environments.
| Doc | Covers |
|---|---|
| docs/groups.md | Groups model, authorization, API surface, and frontend behaviour |
| docs/collections.md | Collections model, ownership, visibility/authorization rules, and API surface |
| docs/announcements.md | Announcements model, audience union rule, kinds, seen-state, reactions, API |
| docs/object-counting.md | Object counting: selection stitching, ephemeral Redis jobs, count-job API |
| docs/presentation-tools.md | Full-screen laser pointer and ephemeral ink tools for presenting images |
| docs/user-deactivation.md | Admin user deactivation flow, API, auth enforcement, and People page behavior |
| docs/feedback-subsystem.md | In-app feedback routing, provider contract, and environment policy |
| docs/category-visibility-and-programs.md | Dual-gate student visibility (programs AND groups), cascade rules, tree loading |
| docs/domain-model.md | Data model reference (entities, junctions, conventions) |
| docs/jobs.md | Durable supervisor/child job model for long-running operations |
| docs/admin-import-export.md | Admin import/export task lifecycle and data round-trip |
| docs/agent-feature-map.md | "Where to change what" map across frontend/backend/tests/docs |
| docs/agent-test-matrix.md | "I changed X → run Y" decision tree for targeted test runs |
| docs/TESTING.md | Test plan, API endpoint → minimum-role table |
| docs/OIDC_SETUP.md | OIDC / auth configuration |
| docs/password-setup.md | Admin-issued, single-use local password setup links |
| docs/password-management.md | Credential states, administrator password runbook, and stable rollout |
| docs/tile-delivery-boundary.md | ADR: authorized tile delivery via HMAC tile tokens + nginx auth_request |
| docs/unauthenticated-routes.md | Standing rule and audit for unauthenticated routes and enforcement layers |
| docs/drag-and-drop.md | Tile drag-and-drop move-vs-reorder contract |
| docs/reorder-fixture.md | Production-scale reorder fixture and regression scaffolding (epic #975) |
| docs/reorder-telemetry.md | Reorder operation correlation, timing, and diagnostic telemetry (epic #975) |
| docs/tile-ordering.md | Atomic, revisioned tile-order API and ordering normalization (epic #975) |
| frontend/guide/ | Instructor/admin user guide (Markdown rendered in-app at ?page=guide) |
| docs/browse-state.md | Monotonic browse revision and GET /categories/tree 304 short-circuit (#1066) |
| docs/reorder-performance.md | Browse drag-and-drop profiling results and optimization record (epic #975) |
| docs/image-metadata-and-versioning.md | Image metadata, versioning, and optimistic concurrency control |
| docs/measurement-review.md | Measurement-scale findings: RBC detector, review/approve API, metadata promotion |
| docs/image-processing-lifecycle.md | Image processing pipeline stages, tile generation, and worker configuration |
| docs/tile-cache-provenance.md | Tile-cache provenance fields and current/missing/stale/failed status rules |
| docs/backup-and-disaster-recovery.md | Production backup and DR strategy, volume layout, restore order, and runbook |
| docs/longhorn-storage-classes.md | Longhorn StorageClass guidance per HRIV volume, tradeoffs, and example manifests |
| docs/recovery-set-contract.md | Normative backup boundary, mismatch policy, recovery metadata, and restore rules |
| docs/ui-behaviour-spec.md | UI behaviour spec (role gating, browse, dialogs, viewer, file drop) |
| docs/RELEASE_AND_DEPLOY_FLOW.md | Release-please + Flux deploy flow |
| docs/security-scanning.md | Trivy code-scanning sources, .trivyignore review, Dependabot PR handling |
| docs/deployment-proxy-chain.md | Proxy chain, TRUSTED_PROXY_HOPS client-IP resolution, login rate-limit buckets |
| docs/observability-conventions.md | OTel spans, telemetry ingestion, canonical auth logs, usage dashboard |
| docs/observability-operations.md | Alert catalogue, routing policy, validation drills, and readiness checklist |
| docs/observability-runbooks.md | Alert-linked operator runbooks for availability, storage, backups, and load |
| docs/synthetic-monitoring.md | Synthetic Playwright journey, configuration, and operator runbook |
| docs/per-file-restore-design.md | Per-file restore design and implemented backend/UI shape |
| docs/rebuild-architecture-design.md | Design plan for scalable, parallel tile rebuilds after backup restores |
| docs/3d-slice-reconstruction-exploration.md | Exploratory RFC for 2D slice-stack navigation and 3D anatomical reconstruction |
| AGENTS.md | Contributor setup, workflow, and Critical Invariants |
HRIV is licensed under the Mozilla Public License 2.0 (MPL-2.0).
Third-party open-source software distributed with HRIV is acknowledged in
per-component THIRD-PARTY-LICENSES.txt files, generated from each component's
production/runtime dependency tree:
| Component | Notices file | Regenerate |
|---|---|---|
| Frontend | frontend/public/THIRD-PARTY-LICENSES.txt |
cd frontend && npm run licenses:generate |
| Backend | backend/THIRD-PARTY-LICENSES.txt |
cd backend && poetry run python scripts/generate_third_party_licenses.py |
| Backup | backup/THIRD-PARTY-LICENSES.txt |
cd backup && poetry run python scripts/generate_third_party_licenses.py |
| Restore validation | restore-validation/THIRD-PARTY-LICENSES.txt |
cd restore-validation && poetry run python scripts/generate_third_party_licenses.py |
The frontend file is bundled into the production image and served at
/THIRD-PARTY-LICENSES.txt (linked from the in-app About dialog); the backend
and backup files are copied into their respective images. Regenerate and commit
the relevant file whenever dependencies change — CI verifies the frontend file
is in sync (npm run licenses:check).