-
change in progress1. First-launch database initialisation UID: SRS-001 TYPE: functional STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: On first launch, when no database file exists at the configured application data path, the software shall create the local database, apply the current schema version, and create a default local clinic record that serves as the container for locally-created patient records.
RATIONALE: Refines SYS-001: the software-level mechanism that realises the device's first-launch readiness commitment. Establishes the minimum persistent state required for any subsequent clinical operation — without it, patient records cannot be stored and recording sessions cannot be attached to a patient.
REVIEWED_HASH: 98b05f66e64fdd504ec9ab458d051d1bf7ac6ef1cc6703c83eec0bbfcfa52d83
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST-001 on clean install — fresh workstation, first launch, verify the local clinic is visible in the Patients menu.
The local persistence subsystem owns the application's local database — a SQLite file encrypted at rest via SQLCipher, located under the configured AppData path — applies schema migrations via Drift on first open, and exposes a typed API to the application layer for the clinic, patient, and recording domain entities.
Hexagonal layer realising the broader local persistence concern. Encapsulates file lifecycle, encryption (SQLCipher), schema migration (Drift), and the typed domain-API contract — keeps the rest of the application unaware of storage details.
cc7fc276970b8e3d7fcf9027298cfa0095eef00c2bb6e8ae4eee52404ffa4fa2
@DougYoungberg
-
change in progressPassed1. First-launch DB initialisation UID: IT-001 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Instantiate AppDatabase against a temporary filesystem path at which no database file exists. Trigger the initialisation pathway by performing any query that forces Drift's onCreate migration to run. Inspect the resulting state of the database on disk and of the seeded tables.
ACCEPTANCE_CRITERIA: The database file exists at the temporary path after the first query. PRAGMA user_version on the opened database equals the value of AppDatabase.schemaVersion declared in application code. The clinics table contains at least one row whose origin is "local", confirming that the bootstrap seed step executed.
REVIEWED_HASH: f3983783ae35415f11933728d91e2f1136516b980063f2d16dedacf6d32b4c47
REVIEWED_BY: @DougYoungberg
NOTES: Test harness couples to the StrictDoc item via tags: ['IT-001'] on the flutter_test test entry. Test file location: test/integration/first_launch_db_initialization_integration_test.dart. Verifies the architectural integration of the local persistence subsystem (ARCH-001); contributes evidence to SRS-001 / SYS-001 indirectly via the typed-link graph.
-
change in progress2. Local database confidentiality at rest UID: SRS-002 TYPE: security STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall keep the local database file unreadable to any process that opens it without supplying the database credential, and shall reject database read and write operations when the credential is missing or incorrect.
RATIONALE: Refines SYS-002. A workstation that is lost, stolen, imaged, or backed up off-site shall not yield PHI from the local database file alone. Tying readability to a credential held outside the database file ensures offline copies of the file cannot be opened independently of the BioFlow application's credential pathway.
STANDARD_REF: NIST SP 800-111; IEC 81001-5-1 §5.5
THREAT_REF: PHI exfiltration from a lost, stolen, or imaged Windows host
REVIEWED_HASH: bbce0aa8a344b16781b9aa4fd86855e614b296b31d614039b4a2221e7e5bda23
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: IT-002 attempts to read the on-disk file without a credential and confirms the read does not yield plaintext records, then opens it through the application's pathway and confirms the records are readable.
The local SQLite database is encrypted on disk via the SQLCipher extension. The encryption key is held by flutter_secure_storage, which on Windows persists it as an entry in a DPAPI-encrypted file (flutter_secure_storage.dat) under the user's local app-data directory. On application bootstrap, the key is retrieved from secure storage and generated-and-persisted on first launch if absent. On every database connection open, the database connection service applies the key as the first statement (PRAGMA key) before any other operation; SQLCipher then transparently encrypts and decrypts SQLite pages.
Page-level encryption via SQLCipher avoids per-record encryption logic in application code. Holding the key in flutter_secure_storage (rather than alongside the database file) ensures a copy of the database file alone cannot be opened. Bootstrap-time key generation lets the application be self-installing on a fresh workstation without a manual setup step.
a62962252646969da606f5d40f8fee50864ace3a43692de8b29c62a1fbeed8fb
@DougYoungberg
-
change in progressPassed2. Database file is encrypted at rest UID: IT-002 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Initialise an AppDatabase against a temporary filesystem path through the production openEncryptedConnection pathway with a known test key. Write a canonical row through the typed clinic API and close the database. Attempt to open the resulting file with the plain sqlite3 driver (no credential supplied) and read any user table. Attempt to open the same file through the production pathway with an incorrect key and read any user table. Then re-open the file through the production pathway with the correct key and read the canonical row.
ACCEPTANCE_CRITERIA: The unkeyed open and the wrong-key open both return a SQLCipher error (typically "file is not a database" or "file is encrypted"), demonstrating the file cannot be read with any arbitrary key. The correct-key open succeeds and the canonical row written before close is readable.
REVIEWED_HASH: 66f90f4e85e357a9c6e4cd2a2ee9f83e20f87c4f8f909318847148c7f44060c9
REVIEWED_BY: @DougYoungberg
NOTES: Test name carries the UID prefix (IT-002) so the Flutter/JUnit join channel resolves. Test file location: test/integration/database_encryption_at_rest_integration_test.dart (distinct from test/integration/encryption_test.dart, which covers EncryptionService primitives only).
-
change in progress3. Local database integrity enforcement UID: SRS-003 TYPE: data STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall enforce referential integrity between clinic, patient, and recording records through database foreign-key constraints; shall wrap any multi-row or cross-table mutation in a single transaction such that a crash or process termination during the mutation leaves the database in either the pre-mutation state or the post-mutation state and never a partial state; and shall reject — rather than silently truncate or drop — any write that would violate a NOT NULL, FOREIGN KEY, or CHECK constraint declared in the schema.
RATIONALE: Refines SYS-003. Three independent failure modes can corrupt the local store: (1) concurrent or crashing writes that complete only part of a multi-row update; (2) referential drift where a child row outlives its parent or never had one; (3) constraint-violating inserts that older code paths or migrations may attempt. Each is addressed by a specific software mechanism — schema-declared constraints applied by the persistence layer, transactional wrappers around use-case writes, and the underlying database engine's constraint enforcement — and each is observable through a failed write or a post-condition query. The requirement names the three mechanisms together so that removing any one of them (for example, disabling foreign-key enforcement for a performance change) is a visible regression against SRS-003 rather than a silent loosening of guarantees relied on by the rest of the application.
STANDARD_REF: 45 CFR §164.312(c)(1); GDPR Art. 5(1)(f)
REVIEWED_HASH: 0de558744d983b3d37d5f67856e20552eba80bae3e3f3fb4a235b12bbb886ee3
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: IT-003 exercises each rejection category named in this requirement against the production encrypted-connection pathway: (a) FK violation — attempt to insert a patient with a non-existent clinic_id; (b) transactional atomicity — perform two writes inside a transaction, throw, and assert no partial state remains visible on re-open; (c) CHECK violation — attempt to insert a clinics row with an out-of-set origin value; (d) NOT NULL violation — issue a raw SQL INSERT (bypassing the typed API which guards NOT NULL at compile time) that supplies NULL for a non-nullable column. Test file location: test/integration/database_integrity_integration_test.dart.
The local SQLite database enforces referential integrity and write atomicity through three schema- and connection-level mechanisms. First, the Drift-declared schema carries foreign-key constraints linking patient rows to their clinic and to their assigned clinical user, and recording rows to their patient. Column-level constraints declared in Dart (NOT NULL by default, plus CHECK on enumerated text columns such as clinics.origin, patients.origin, and recordings.upload_status) cause the SQLite engine to reject schema-violating writes at insert time. Second, the database connection service enables foreign-key enforcement on every connection by issuing PRAGMA foreign_keys = ON immediately after applying the encryption key, so the schema-declared FK relations are enforced by the SQLite engine for every read and write throughout the application's lifetime. Third, application-layer use cases that perform multi-row or cross-table writes execute them inside a single Drift transaction, so a crash or process termination during the mutation leaves the database in either the pre-mutation or the post-mutation state and never a partial state. Together, these three properties of how the persistence subsystem is configured realise SRS-003.
Integrity could in principle be realised at the application layer through per-use-case validation, but is enforced at the schema and connection layer here so that any new use case, any data-migration script, and any future maintenance write inherits the same constraints automatically without duplicating logic. Schema-level FK constraints and SQLite's built-in enforcement engine are battle-tested mechanisms; lifting them into application code would invite duplication and gradual drift. The per-connection PRAGMA foreign_keys = ON statement is non-negotiable because SQLite's default is OFF — an omission would parse FK declarations and silently ignore them, leaving the integrity story broken at runtime. Drift transactions wrap the multi-row case at the use-case boundary because that is the smallest granularity at which atomicity is meaningfully observable to the rest of the application. Audit-log rows intentionally use a polymorphic (entity_type, entity_id) reference rather than a foreign key because audit entries cover events on clinic, patient, recording, and clinical-user subjects — an FK on entity_id could only target one table — and integrity of the audit chain is preserved instead by making audit_id the immutable primary key with the row content fixed at insert time.
1fb8538abde3bc3860262ab683243fae4ce27045bcc31dc75e427bd65c79a4d2
@DougYoungberg
-
change in progressPassed3. Database integrity mechanisms reject corrupt writes and roll back partial state UID: IT-003 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Initialise an AppDatabase against a temporary filesystem path through the production openEncryptedConnection pathway with a known test key, forcing first-launch onCreate so the seeded LOCAL_CLINIC and the schema (including foreign-key declarations) are in place. Then, in four independent scenarios, exercise each of the rejection categories named in SRS-003 / ARCH-003.
(a) Foreign-key enforcement — attempt to insert a patient row whose clinic_id references a clinic that does not exist, and verify the SQLite engine rejects the write.
(b) Transactional atomicity — open a Drift transaction that performs two writes (insert a new clinic, then insert a patient referencing that clinic), then throw an exception inside the transaction; re-open the database and verify neither row is present.
(c) CHECK constraint enforcement — attempt to insert a clinic row whose origin column value is outside the enumerated set ('local', 'remote', 'cloud') declared on the schema, and verify the SQLite engine rejects the write.
(d) NOT NULL constraint enforcement — issue a raw INSERT (via customStatement, bypassing Drift's typed companion which guards NOT NULL at compile time) that supplies NULL for the clinics.name column, and verify the SQLite engine rejects the write at runtime. Bypassing Drift is necessary to prove the engine — not the compile-time guard — is what protects the data when a future migration, an unsafe raw write, or another customStatement caller bypasses the type system.
ACCEPTANCE_CRITERIA: (a) The orphan-patient insert raises a SqliteException whose message contains "FOREIGN KEY constraint failed" (case-insensitive) and the patients table contains no row with the orphan local_patient_id after the failed call. (b) After the throwing transaction completes and the database is re-opened through the same encrypted pathway, neither the new clinic row nor the new patient row inserted inside the transaction is present in their respective tables. (c) The invalid-origin clinic insert raises a SqliteException whose message contains "CHECK constraint failed" (case-insensitive) and the clinics table contains no row with the rejected clinic_id. (d) The raw NULL-name insert raises a SqliteException whose message contains "NOT NULL constraint failed" (case-insensitive) and the clinics table contains no row with the rejected clinic_id. The original seeded LOCAL_CLINIC remains visible throughout all four scenarios, demonstrating that the failed writes did not collaterally damage pre-existing rows.
REVIEWED_HASH: 668ffee93cca42e8d4c968988c9e417caace6e8172edb7a9f40cc86de4bd6c56
REVIEWED_BY: @DougYoungberg
NOTES: Test file location: test/integration/database_integrity_integration_test.dart. Each scenario runs as a separate flutter_test case with its own temp directory for failure isolation. The test exercises the production openEncryptedConnection pathway, so removing the PRAGMA foreign_keys = ON statement, regressing Drift's FK-bearing schema, or weakening the transaction wrapper would be visible as IT-003 failures.
Coverage strategy: each rejection category named in SRS-003 (FOREIGN KEY, CHECK, NOT NULL) is exercised by one representative case rather than enumerating every FK relation or every CHECK-bound column in the schema. The same SQLite enforcement engine handles all FK constraints and all CHECK columns identically — proving the mechanism on one example proves it for all. This representative-coverage approach is appropriate for IEC 62304 Class B integration testing; if BioFlow is reclassified to Class C, an exhaustive matrix of (relation x constraint type) would be required.
-
change in progress4. Audit logging of clinical record operations UID: SRS-004 TYPE: functional STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall write one audit-log entry for each of the following operations on local clinical records: creation, modification, and deletion of a patient; start, stop, and deletion of a recording; and successful upload of a recording to the cloud. Each entry shall carry the action, the identifier of the affected record, and a system-generated UTC timestamp. After an entry is written, the software shall not modify it in a way that obscures previously recorded information, and shall not delete audit entries.
RATIONALE: Refines SYS-004. The list of audited operations corresponds to the operator-initiated mutations and exports of patient or recording data that the application currently exposes. Clinic and clinical-user mutations are not in scope because the application does not permit those operations on the local workstation — clinics are read-only mirrors of an external cloud directory the device is configured to sync with; clinical users are authenticated rather than created in-app. The required content per entry (action, target, time) is the minimum that lets an audit reviewer reconstruct what happened: which clinical record was affected, what action was performed, and when. The immutability clause expresses the underlying invariant that an audit entry must remain a faithful record of the operation it describes; an application-layer path that overwrites the details of a prior entry would functionally be a record change that obscures original information, and is therefore prohibited.
REVIEWED_HASH: 8670efd17561286bd5396fe9d81ff480539400a00719b06c3f4619af3242feb4
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: IT-004 exercises the audit chain end-to-end by invoking each of the audited operations through the application's use-case pathway (or the corresponding production caller when no use case exists) and asserting that the audit-log entry is written with the correct action, target identifier, and system-generated timestamp. IT-005 verifies the data-shape and immutability commitments — entries have the required fields and an existing entry's content cannot be changed after write.
The audit-logging subsystem realises SRS-004 through three software items in the hexagonal architecture. A domain-layer audit-repository port whose surface offers create and read operations only — no update or delete is exposed to the application. A service in the application/infrastructure boundary that exposes one semantic logging method per audited operation (logPatientCreated, logPatientUpdated, logPatientDeleted, logRecordingStarted, logRecordingStopped, logRecordingDeleted, logRecordingUploaded). A Drift-backed adapter that persists entries into the audit_logs table of the local persistence subsystem (ARCH-001). Application- layer use cases invoke the service after the corresponding record mutation; the service composes an entry (action, entity type, entity identifier, actor identifier when available, JSON details, and timestamp) and delegates persistence to the port.
Routing every audit call through one semantic service keeps the audit concern decoupled from each use case: the use case calls "a patient was created" without assembling the entry itself. Limiting the port surface to create-and-read is what enforces the SRS-004 immutability clause at the architectural level — no application-layer code can mutate or delete an entry because no such method is exposed by the port. Composing entries inside the service rather than inside each use case prevents drift in the shape of entries across operations. The persistence handoff to ARCH-001 means audit entries inherit the same encryption-at-rest (ARCH-002) and integrity (ARCH-003) properties as the rest of the local store.
d5dbb469c6940e7b5d709b6f836a7b6c4096c44582959560c23d9c103c47bc67
@DougYoungberg
-
change in progressPassed4. Audit-log entries are written for each clinical record operation UID: IT-004 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Initialise an AppDatabase against a temporary filesystem path through the production encrypted-connection pathway with a known test key, forcing first-launch onCreate so the schema and seeded LOCAL_CLINIC are in place. Construct the use cases or the production callers that participate in the audit chain, wired with a real AuditService backed by the production audit-repository adapter against the same database. Then, in seven independent scenarios, exercise the SRS-004 commitment that each named operation writes its audit entry.
(a) Patient creation: invoke the create-patient use case for a fresh patient; query audit_logs for entries on that patient; verify exactly one entry of action "PATIENT_CREATED" exists with the patient's identifier and a non-null timestamp.
(b) Patient update: create a patient (setup), invoke the update-patient use case with a changed field; verify exactly one entry of action "PATIENT_UPDATED" exists with the patient's identifier.
(c) Patient deletion: create a patient (setup), invoke the delete-patient use case; verify exactly one entry of action "PATIENT_DELETED" exists with the patient's identifier.
(d) Recording start: drive the recording-start pathway through the production recording-notifier (with the signal-processing engine and sleep-prevention port replaced by test doubles); verify one entry of action "RECORDING_STARTED" exists with the recording identifier.
(e) Recording stop: drive the recording-start then recording-stop pathways through the production recording-notifier with test doubles for the heavy collaborators; verify one entry of action "RECORDING_STOPPED" exists with the recording identifier (in addition to the "RECORDING_STARTED" entry).
(f) Recording deletion: create a recording (setup), invoke the delete-recording use case; verify one entry of action "RECORDING_DELETED" exists with the recording identifier.
(g) Upload: insert a recording (setup), drive the upload pathway through the production upload-manager with a test stub for the cloud client; verify one entry of action "RECORDING_UPLOADED" exists with the recording identifier.
ACCEPTANCE_CRITERIA: For each scenario: the corresponding audit_logs row exists after the operation, with the expected action string, the expected entity identifier in entity_id, and a non-null UTC timestamp. The seeded LOCAL_CLINIC and any unrelated rows remain unchanged.
REVIEWED_HASH: f1d13532cf6bb1be63bb95f47499f5c328ddb234b2072e85f072ac4734cfd34f
REVIEWED_BY: @DougYoungberg
NOTES: Test file location: test/integration/audit_log_integration_test.dart. Each scenario runs as a separate flutter_test case with its own temp directory for failure isolation. The test exercises the production code that ultimately invokes the audit service, so removing the audit call from any use case, the recording-notifier, or the upload-manager would be visible as a missing-entry failure on the corresponding scenario.
Coverage strategy: each scenario verifies that a specific audited operation produces the SRS-004-required entry. Representative coverage is appropriate for the device's current software-safety classification; if the device is reclassified upward, an exhaustive matrix across time-zone edge cases and persistence failure modes would be required.
-
change in progress4. Audit logging of clinical record operations UID: SRS-004 TYPE: functional STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall write one audit-log entry for each of the following operations on local clinical records: creation, modification, and deletion of a patient; start, stop, and deletion of a recording; and successful upload of a recording to the cloud. Each entry shall carry the action, the identifier of the affected record, and a system-generated UTC timestamp. After an entry is written, the software shall not modify it in a way that obscures previously recorded information, and shall not delete audit entries.
RATIONALE: Refines SYS-004. The list of audited operations corresponds to the operator-initiated mutations and exports of patient or recording data that the application currently exposes. Clinic and clinical-user mutations are not in scope because the application does not permit those operations on the local workstation — clinics are read-only mirrors of an external cloud directory the device is configured to sync with; clinical users are authenticated rather than created in-app. The required content per entry (action, target, time) is the minimum that lets an audit reviewer reconstruct what happened: which clinical record was affected, what action was performed, and when. The immutability clause expresses the underlying invariant that an audit entry must remain a faithful record of the operation it describes; an application-layer path that overwrites the details of a prior entry would functionally be a record change that obscures original information, and is therefore prohibited.
REVIEWED_HASH: 8670efd17561286bd5396fe9d81ff480539400a00719b06c3f4619af3242feb4
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: IT-004 exercises the audit chain end-to-end by invoking each of the audited operations through the application's use-case pathway (or the corresponding production caller when no use case exists) and asserting that the audit-log entry is written with the correct action, target identifier, and system-generated timestamp. IT-005 verifies the data-shape and immutability commitments — entries have the required fields and an existing entry's content cannot be changed after write.
The audit_logs table in the local SQLite database stores one row per audit entry. Each row carries an audit identifier (UUID, primary key, immutable), an action string drawn from the application's enumerated set of audited operations, an entity_type and entity_id polymorphic reference to the subject record, a nullable user_id for the authenticated actor, a JSON-serialised details payload, and a UTC timestamp defaulted at the database level. No production code path issues UPDATE or DELETE against the table; the audit-repository port surface (ARCH-004) does not expose those operations.
Polymorphic (entity_type, entity_id) is used in place of a foreign key because audit entries cover events on multiple subject tables (patient, recording, system bootstrap, future clinical_user) — an FK on entity_id could only target one. Integrity instead rests on the immutable primary key being chosen at insert time and the database-side default for timestamp preventing application code from back-dating entries. The JSON details payload allows operation-specific context (e.g., the changed-field list on a patient update, the file size and target clinic on an upload) without inflating the schema with per-operation columns; the trade-off is that detail content is not directly indexable and is intended for human-readable reconstruction of what happened rather than for analytical queries.
b4f2ae7558b9c98456db381d46324adf474c77c5b2e4eddd9e825e783339b427
@DougYoungberg
-
change in progressPassed5. Audit-log records have the required shape and are immutable UID: IT-005 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Initialise an AppDatabase against a temporary filesystem path through the production encrypted-connection pathway with a known test key. Construct the audit chain (AuditService + production audit-repository adapter against the same database). Then, in two scenarios, verify the ARCH-005 commitments on the audit_logs row shape and the SRS-004 immutability rule.
(a) Record shape: write a known audit entry via the audit service and query audit_logs for it; verify the row has a non-empty audit identifier, an action string drawn from the application's enumerated audit actions, the expected entity_type and entity_id, a non-null UTC timestamp generated by the database (not by the caller), and a parseable JSON details payload.
(b) Write-then-read sanity check: write an audit entry via the audit service, re-read it, and assert the details payload and system timestamp match what was written. Immutability is enforced structurally — the audit-repository port (SRS-004 / ARCH-005) exposes only create-and-read methods, so no application path can mutate a row. This scenario is the runtime corollary, confirming that the storage layer round-trips an entry faithfully.
ACCEPTANCE_CRITERIA: (a) The queried audit_logs row carries: a non-empty audit identifier, an action drawn from the enumerated audit actions, the entity_type and entity_id supplied to the audit service, a timestamp within a small tolerance of the test wall-clock at insert (demonstrating database-side generation), and a JSON details payload that round-trips through a JSON decoder. (b) Re-reading the written entry returns the same JSON-serialised details payload and the same timestamp as the original write.
REVIEWED_HASH: 5dd9f184de1f449f74826c5449c666ed8a06cc7b84486d3cf415c0b9940dda06
REVIEWED_BY: @DougYoungberg
NOTES: Test file location: test/integration/audit_log_record_shape_integration_test.dart. Each scenario runs as a separate flutter_test case with its own temp directory for failure isolation.
Immutability is enforced structurally at the port surface: IAuditRepositoryPort exposes only create-and-read operations, so no application path can change a written entry. Code review of the port shape is the primary defence; IT-005 (b) is the runtime sanity check that storage round-trips entries faithfully.
-
change in progress5. Audit-trail review and export UID: SRS-005 TYPE: functional STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall present the local audit trail to an authorised operator in a form that lists each entry's action, affected record, and system timestamp, ordered by time. The software shall additionally provide a means to export the current audit trail to a file in a portable, human-readable format that preserves the same fields as the on-screen presentation, so that the exported file can be examined off the device by an auditor or by the clinical organisation's own information-security review.
RATIONALE: Refines SYS-004. Capturing audit entries in the local database is necessary but not sufficient: the entries live inside the encrypted application database, which an internal reviewer or external auditor is not expected to access directly. The device therefore surfaces the trail to an authorised operator inside the application and provides an exportable copy that can be examined off the device. Without a viewer-and-export surface, the audit trail is opaque to the clinical organisation's own review duties — the entries exist but cannot be examined without direct access to the encrypted database file. The "human-readable" wording is deliberate: a binary or schema-coupled export defeats the off-device review use case, which is conducted by people reading entries on a workstation other than the device. The "ordered by time" presentation rule keeps the on-screen view faithful to the chronology of operations; the export preserves the same fields so that the exported copy is a copy of the on-screen trail rather than a transformation of it.
REVIEWED_HASH: 3da5c78892fc7f563c2fd96cb6b7e93ad305672d437b12b2a59dd039044ad207
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: IT-006 verifies the export round-trip — written audit entries appear in the exported file with the same fields and content. ST-002 (planned, to be authored once the viewer surface ships) verifies an operator can locate the audit trail in the application, see the expected entries, and invoke the export from the workstation.
The audit-trail review-and-export surface realises SRS-005 by extending the Activities overlay (lib/presentation/overlays/activities_overlay.dart) with a tab titled "Audit Log". The pre-existing first tab is renamed from "Recent Activities" to "Recent Recordings" so that its name matches what it actually lists (rows from the recordings table) and does not collide with the new audit-trail tab. The resulting tab set, in order, is: Recent Recordings, Audit Log, Statistics, Archive. The Audit Log tab subscribes to the auditLogListProvider exposed by the application/providers layer, which streams the audit_logs rows from the local persistence subsystem (ARCH-001) via the audit-repository read side (ARCH-004). Entries are rendered as a scrollable list ordered by timestamp descending, each row showing the action, the entity_type/entity_id pair, and the UTC timestamp. The tab carries an Export button whose on-press handler serialises the currently-loaded entries to a file in one of two portable, human-readable formats — CSV (one row per entry, fields: audit_id, timestamp, action, entity_type, entity_id, user_id, details) or JSON (an array of entry objects with the same fields) — and writes the file to a workstation path chosen by the operator through the platform's standard save-file dialog.
The audit-trail viewer is placed in the existing Activities overlay because that overlay already groups operator-visible event surfaces (recent activities, statistics, archive) and audit-log review is a peer concern — adding a parallel tab there preserves the operator's mental model rather than introducing a separate menu entry. The auditLogListProvider is already exposed by the application layer for unrelated reasons, so the viewer attaches to existing wiring rather than introducing a new data path; the read side of the audit-repository port (ARCH-004) is already permitted by the SRS-004 immutability rule (the port surface offers create and read only — no mutating method exists for the viewer to invoke). CSV and JSON are both portable, human-readable, and parseable without specialised tooling on a clinical workstation; the export deliberately writes the same fields shown on screen so the exported file is a faithful copy of what the operator reviewed — a transformation rather than a copy would defeat the off-device review use case. The save-file dialog is the platform-native path because it lets the operator choose a destination outside the application's working directory (typically a clinical share or removable media), which is the realistic workflow for handing the file to an auditor.
5ba229d3b36ffd3ddc7705b5933bb742e3f639fa7fc16d4ad6c759c865b1ab15
@DougYoungberg
-
change in progressPassed6. Audit-trail export produces a faithful copy of the on-device entries UID: IT-006 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Initialise an AppDatabase against a temporary filesystem path through the production encrypted-connection pathway with a known test key. Construct the audit chain (AuditService + production audit-repository adapter against the same database) and exercise two distinct audited operations through their use cases (a patient creation and a patient deletion) so that at least two distinguishable entries are present in audit_logs. Then, in two scenarios, exercise the SRS-005 / ARCH-006 commitment that the review-and-export surface produces a faithful copy of the trail.
(a) CSV export: invoke the production export pathway requesting CSV output to a temporary file path; re-open the file with a CSV reader and assert that each row in audit_logs has a corresponding row in the file with the same audit_id, action, entity_type, entity_id, user_id, timestamp, and details content.
(b) JSON export: invoke the production export pathway requesting JSON output to a temporary file path; re-open the file with a JSON parser and assert that each row in audit_logs has a corresponding object in the resulting array with the same audit_id, action, entity_type, entity_id, user_id, timestamp, and details content.
ACCEPTANCE_CRITERIA: (a) The CSV file exists at the requested path, parses without error, contains one data row per audit_logs row, and each row's fields match the source row's values after CSV unescape. (b) The JSON file exists at the requested path, parses without error as an array, contains one object per audit_logs row, and each object's fields match the source row's values exactly. For both scenarios: no audit_logs rows are missing from the export, and the export does not introduce entries not present in audit_logs.
REVIEWED_HASH: a102a8238f8bb687ac942b3c3b884f9d38525563cad6a13c569d2541b05c464f
REVIEWED_BY: @DougYoungberg
NOTES: Test file location: test/integration/audit_log_export_integration_test.dart. Each scenario runs as a separate flutter_test case with its own temp directory for failure isolation. The test exercises the production export code path, so a serialisation regression (omitting a field, dropping a row, mis-formatting a timestamp) would be visible as either a parse failure or a field-mismatch failure on the corresponding scenario.
Coverage strategy: each scenario verifies one export format end-to-end. The fields verified are the fields named in ARCH-006 (audit_id, timestamp, action, entity_type, entity_id, user_id, details). Representative coverage across two formats is appropriate for the device's current software-safety classification; if the device is reclassified upward, an exhaustive matrix across timestamp edge cases, multi-byte detail payloads, and very large exports would be required.
-
reviewed6. Maximized main window on launch UID: SRS-006 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: On application launch, the software shall display the main application window in a maximized state.
RATIONALE: Refines SYS-005. Maximising the main window on launch gives the EEG signal display the full available screen area from the start of a session, so the operator can monitor the live signal without first resizing the window manually.
REVIEWED_HASH: dc7ca8a24c6e21afa7a978cbb02263cfeab63085a843e6e7432c25b610be934a
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST item (planned) — verify the main application window is maximized immediately after launch.
-
reviewed16. Minimum main window size UID: SRS-016 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall prevent the main application window from being resized smaller than 1024 by 768 pixels.
RATIONALE: Refines SYS-005. 1024 by 768 is the minimum size at which the operator workstation remains usable; preventing the window from shrinking below it keeps the live signal display and recording controls legible on lower-resolution laptops.
REVIEWED_HASH: 4200fb2d0be3aee20b1455b9e0faba8eab18e82f2097544c2a0921230dd51767
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST-012 (planned) resizes the window below the minimum and asserts the reported window bounds are clamped to at least 1024 by 768 at the operator-facing tier. No flutter_test integration test is feasible — the minimum is enforced natively by the window_manager package, not in a widget tree.
The application's top-level desktop window is shown maximised at launch by the native Windows runner: Win32Window::Show() (windows/runner/win32_window.cpp) calls ShowWindow with SW_SHOWMAXIMIZED, invoked from flutter_window.cpp during startup. The Dart application bootstrap additionally calls windowManager.maximize() after the window_manager package initialises, reinforcing the maximised state once the Flutter engine is running. Runtime window state — size, position, and maximised flag — is owned by the WindowStateNotifier (application layer), which applies changes through the window_manager package and persists them to SharedPreferences for restoration on the next launch. The window's minimum size is configured as 1024x768 through the window_manager package's WindowOptions in main.dart, which the package enforces against resize requests so the window cannot be made smaller than that floor.
Launching maximised is set at the native window layer so the window is already maximised the instant it first paints, before the Flutter engine and Dart layer are ready to act; the Dart bootstrap maximise call then keeps the state correct once window_manager owns the window. Window-state persistence is kept in the application-layer notifier so size and position survive across sessions independently of the launch behaviour.
59e14f00ae2b25d6492e269b9ff3fe6debc9ec0a0f261bd3eba952eb582fe3fa
@DougYoungberg
coverage-plan: verified at system level by ST-004, which observes the maximised window in the running application. No flutter_test integration test is feasible for the launch maximise itself: it is performed by the native Win32 runner and by the window_manager package singleton at bootstrap, neither of which is injectable in a widget test. The minimum window size is verified the same way — at system level by ST-012 (planned), which resizes the window below the minimum and asserts the bounds are clamped — since it too is enforced by the window_manager singleton rather than in a widget tree.
-
reviewed7. Always-visible recording control widget UID: SRS-007 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall display a recording control widget anchored to the top of the main window, and shall keep that widget visible in all interaction states, including signal-control mode, menu mode, and while any overlay is open.
RATIONALE: Refines SYS-005. The recording controls govern the active session — its start, its stop, and its running status. Keeping the widget visible in every interaction state ensures the operator can always see and reach the recording controls without first dismissing a menu or overlay.
REVIEWED_HASH: bccb9886c2528cb600bccee06daef365798ce97ad2887816160909f6270bd10f
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST item (planned) — verify the recording control widget remains visible in signal-control mode, menu mode, and while an overlay is open.
-
reviewed13. Recorded-duration readout format UID: SRS-013 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: While a recording session is active, the software shall display the recorded duration in HH:MM:SS format.
RATIONALE: Refines SYS-005. A recorded-duration readout in a fixed HH:MM:SS format lets the operator see how much signal has been captured so far while keeping the live signal in view.
REVIEWED_HASH: 44a71204c417957885ee30f52c99c5b19e8e51c0e3da1d1cda7243ae850f84c6
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: UT-001 verifies the duration-to-HH:MM:SS formatting at the unit tier; ST-009 observes the readout shown in HH:MM:SS format in the running application during an active recording.
-
reviewed14. Patient name in recording widget UID: SRS-014 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: While a patient is selected, the software shall display the selected patient's name in the recording control widget.
RATIONALE: Refines SYS-005. Showing the selected patient's name in the always-visible recording widget lets the operator confirm at a glance which patient the session is being recorded for while keeping the live signal in view.
REVIEWED_HASH: 3e52266db5d5faf9cfd839c7b4b8808bea0f43f535ef0af241b4cb0e72ffe88f
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST-010 observes the selected patient's name shown in the recording widget in the running application; IT-008 confirms the widget renders the patient name from seeded state.
The recording control widget occupies the fixed top region of the main window. It is a provider-bound presentation container (RecordingWidget) that subscribes to the recording, selected-patient, and signal-engine application providers and renders, through its view (RecordingWidgetView) and the recording sub-components (status badge, action buttons, center content, and menu panel), the recording controls, the current patient name and recording status, and the elapsed-recording-time readout. It dispatches operator actions — start and stop recording — to the recording view-model and holds no recording domain logic itself. The recorded duration shown in that readout is derived from the native recording engine's sample-accurate timing, pushed into the recording view-model as signal is captured rather than from a wall-clock timer, and is formatted as zero-padded HH:MM:SS by the recording widget state (RecordingWidgetState.formattedElapsedTime).
Keeping the recording widget as a thin provider-bound container, with rendering delegated to a separate view and sub-components, decouples the always-visible top region from recording domain logic. The widget is driven entirely by provider state, so its presentation can be exercised by pumping it with seeded provider values independently of how a recording is actually performed.
535519a4d087d4a5931deb257e7c48e7266ea4f7b96de89810a49f41b5c2685c
@DougYoungberg
-
reviewed7. Recording widget renders controls and context UID: IT-008 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Pump the RecordingWidgetView — the rendering view that the provider-bound RecordingWidget container drives — in a flutter_test widget test with a seeded RecordingWidgetState representing a recording in progress for a known patient with a known elapsed time. Render the view and inspect its widget tree.
ACCEPTANCE_CRITERIA: The view renders without error. It displays the seeded patient name, the elapsed-recording-time readout in HH:MM:SS form matching the seeded duration, and a recording control (the pause control shown while a recording is active).
REVIEWED_HASH: c8989a1ef86c501d3791f6dcf8940e4043b0fdc0e2018e989fe5cd746a9db218
REVIEWED_BY: @DougYoungberg
NOTES: Test file: test/widget/recording_widget_test.dart, in the IT-008-prefixed test case (the test name carries the UID prefix for the Flutter/JUnit join channel). The test exercises RecordingWidgetView, which the provider-bound RecordingWidget container renders from application state (ARCH-008); testing the view directly avoids standing up the container's full provider graph. The always-visible-across-states commitment of SRS-007 is observed end-to-end in the running application by ST-005.
-
reviewed8. Bottom-bar signal and menu mode toggle UID: SRS-008 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall provide a bottom bar that switches between a signal-control mode and a menu mode when the operator activates the bottom-bar mode toggle.
RATIONALE: Refines SYS-005. A single bottom bar that switches between signal-control functions and menu functions keeps both sets of controls reachable from one fixed location, without permanently consuming screen area that would otherwise reduce the live signal display.
REVIEWED_HASH: a2bd1b9f841162a89db68d43b7a5e544d65872f6e094573158396d0f6e6f4943
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST item (planned) — verify activating the bottom-bar toggle switches the bar between signal-control mode and menu mode.
-
reviewed11. Signal-mode bottom-bar controls UID: SRS-011 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: In signal mode, the bottom bar shall present the signal-parameter controls: a high-pass filter, a low-pass filter, a notch filter, a sensitivity control, a timebase control, and a montage selector.
RATIONALE: Refines SYS-005. These are the per-recording signal adjustments the operator reaches without leaving the signal view; presenting them on the bottom bar in signal mode keeps them one interaction away while the live signal remains visible.
REVIEWED_HASH: 87a993af0ca2801932f9df334a3860d5e9de62d699450987430e7dd994b65a07
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: IT-011 asserts the six controls render in signal mode; ST-006 confirms the signal-parameter controls are shown at the operator-facing tier.
-
reviewed12. Menu-mode bottom-bar navigation buttons UID: SRS-012 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: In menu mode, the bottom bar shall present the navigation buttons: Patients, Montages, Settings, and Activities.
RATIONALE: Refines SYS-005. Menu mode replaces the signal-parameter controls with the four primary navigation destinations, each of which opens its overlay, so the operator reaches patients, montages, settings, and activities from the same fixed bar.
REVIEWED_HASH: 68a5f126aba7210ab6bfdf3c3998fa61d7efa723f3b4cd42215e59a1f7fd2099
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: IT-011 asserts the four navigation buttons render in menu mode; ST-006 confirms the four items are shown at the operator-facing tier.
-
reviewed15. Bottom bar default and resting mode UID: SRS-015 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The bottom bar shall be in signal mode when the application launches, and shall return to signal mode whenever the open navigation overlay is dismissed.
RATIONALE: Refines SYS-005. Signal mode is the operator's working view, so the bottom bar opens there and falls back to it once a navigation overlay is closed, keeping the signal-parameter controls available by default.
REVIEWED_HASH: 9c6b9513d6834a87fc4441c8cea891e78a3add74b32609de21884fb0a816ea9e
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: IT-009 asserts signal mode at launch and IT-010 asserts the return to signal mode after the overlay is dismissed; ST-011 observes the launch-to-menu-to-signal cycle at the operator-facing tier.
The bottom-bar mode controller owns the bottom bar's signal/menu mode. The mode is modelled as a domain value object (BottomBarMode) carrying the two modes and the pure toggle and per-mode content definitions; an application view-model (BottomBarNotifier) holds the current mode — signal mode at launch — and switches it via toggleMode(). The bottom bar widget (BottomBar) watches the mode provider and renders the controls for the current mode: the signal-parameter controls (high-pass, low-pass and notch filters, sensitivity, timebase, and montage) in signal mode, and the Patients/Montages/Settings/Activities navigation buttons in menu mode. A single toggle control invokes toggleMode() when the operator activates it; switching to menu mode opens the corresponding navigation overlay (the last-opened one, or Patients by default) and switching back to signal mode closes it. Dismissing the open overlay — by Escape, a click outside it, or re-selecting the active navigation item — clears the navigation selection, which resets the controller to signal mode.
Modelling the mode and its toggle as a pure domain value object, with the view-model holding only the current-mode state, keeps the mode-switch rule free of presentation and platform concerns so it can be verified at unit level, while the widget stays a thin renderer of whichever mode is current.
ea1afa48d51bd8a9eb5e14ecb9ebf753683c392a6412f641e1257291efd544ac
@DougYoungberg
-
reviewedPassed8. Bottom bar switches rendered mode on toggle UID: IT-009 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Pump the BottomBar in a flutter_test widget test with the bottom-bar mode provider in its default state. Inspect the rendered controls in signal mode. Activate the mode toggle. Inspect the rendered controls again, then activate the toggle a second time and inspect once more.
ACCEPTANCE_CRITERIA: In the default (signal) mode the bottom bar renders the signal-parameter controls. After the mode toggle is activated, the bottom bar renders the menu-mode controls (the Patients, Montages, Settings, and Activities navigation items). Activating the toggle a second time returns the bar to the signal-mode controls.
REVIEWED_HASH: 7d9d0fd6c986e6bb3721765d6ba65507de9c5bc356c0edd45cf30716d2d04f99
REVIEWED_BY: @DougYoungberg
NOTES: Test file: test/integration/bottom_bar_integration_test.dart, in the IT-009-prefixed test case (the test name carries the UID prefix for the Flutter/JUnit join channel). Verifies the architectural integration of the bottom-bar mode controller (ARCH-009) — the widget-to-view-model wiring that switches the rendered controls between signal and menu mode. Contributes evidence to SRS-008 via the typed-link graph; ST-006 observes the toggle in the running application.
-
reviewedPassed10. Bottom bar renders the per-mode controls UID: IT-011 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Pump the BottomBar in a flutter_test widget test with the bottom-bar mode provider in its default state. Inspect the rendered controls in signal mode. Activate the mode toggle to switch to menu mode. Inspect the rendered controls again.
ACCEPTANCE_CRITERIA: In signal mode the bottom bar renders the six signal-parameter controls labelled HP (high-pass filter), LP (low-pass filter), SENS (sensitivity), TB (timebase), MONT (montage), and NOT (notch filter). After the toggle, in menu mode the bottom bar renders the four navigation buttons labelled Patients, Montages, Settings, and Activities.
REVIEWED_HASH: 24e4409f0c8b2365b21455b17f6af2e095db2affca043e03f6c4e714f918957d
REVIEWED_BY: @DougYoungberg
NOTES: Test file: test/integration/bottom_bar_integration_test.dart, in the IT-011-prefixed test case. Verifies the architectural integration of the bottom-bar mode controller (ARCH-009) rendering the per-mode content; contributes evidence to SRS-011 (signal-mode controls) and SRS-012 (menu-mode buttons). ST-006 confirms the same content at the operator-facing tier.
-
reviewed9. Dismiss overlay on ESC or click-outside UID: SRS-009 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall dismiss the active navigation overlay when the operator either presses the ESC key or clicks outside the overlay's content area.
RATIONALE: Refines SYS-005. Navigation overlays float above the live EEG signal; providing the conventional dismissal gestures — ESC and click-away — lets the operator return to the unobstructed signal view quickly. An overlay with in-progress edits is guarded against accidental dismissal (see ARCH-010), so the dismissal applies to overlays that permit it.
REVIEWED_HASH: b262c063b65c057df8f8d5f2d6aaaa0541cee3f7eb9887b695ffdbca7577e649
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST-007 opens a navigation overlay and verifies it is dismissed both by pressing ESC and by clicking outside the overlay content.
-
reviewed10. At most one overlay open at a time UID: SRS-010 TYPE: functional STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall display at most one navigation overlay at a time; opening an overlay shall replace any overlay that is currently open.
RATIONALE: Refines SYS-005. Limiting the interface to a single open overlay keeps the operator's focus clear and preserves the maximum visible area of the underlying signal display. The constraint is realised structurally by a single selected-overlay index rather than a navigation stack (see ARCH-010), so a second overlay cannot be layered over the first.
REVIEWED_HASH: 8b339f513a68052b72a0dab34f0ebc46b778848c38b8552a70abe5c96c660985
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST-008 opens one overlay, then opens a second, and verifies only the second is shown.
The overlay navigation subsystem manages the application's modal navigation overlays (Patients, Montages, Settings, Activities). The open-overlay state is held as a single selected index in the navigation view-model (NavigationNotifier), which drives which overlay the main page renders. A companion overlay view-model (OverlayNotifier) tracks overlay visibility, the dismissible flag, and per-overlay data. Because the rendered overlay is keyed off one index rather than a stack, at most one overlay is shown at a time and selecting another replaces it: MainPage renders the selected overlay in a single AnimatedSwitcher slot above a dimmed signal area, with the recording widget and bottom bar layered above it. Dismissal is wired two ways: a KeyboardListener closes the active overlay on the ESC key, and a full-screen barrier behind the overlay closes it when the operator taps outside the overlay content. Both paths call clearSelection(), which is gated by a dismissible flag (overlayDismissibleProvider) so an overlay with in-progress edits (the montage editor) intercepts the close attempt instead of discarding unsaved work.
Modelling the open overlay as one selected index — rather than a navigation stack — is what structurally enforces the single-overlay rule (SRS-010): there is no representation in which two overlays are open. Concentrating dismissal in clearSelection(), invoked by both the ESC handler and the tap-outside barrier, keeps the two dismissal triggers (SRS-009) behaviourally identical and routed through one place, and the dismissible gate keeps that single path safe for edit-bearing overlays.
d9ef3a3e1718ca469e67bab37fc0eedccbbf3fc9aea11b6ff4cce247fa7b3908
@DougYoungberg
-
reviewedPassed9. Overlay open/replace/dismiss state machine UID: IT-010 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Construct a ProviderContainer and exercise the overlay navigation subsystem through its view-models without a running UI. (a) From the initial state, call selectItem(0) on the navigation notifier and inspect the navigation and overlay state. (b) With overlay 0 open, call selectItem(1) and inspect the state. (c) With an overlay open and the bottom bar in menu mode, call clearSelection() and inspect the navigation, overlay, and bottom-bar state.
ACCEPTANCE_CRITERIA: (a) After selectItem(0): the navigation selectedIndex is 0 and the overlay state reports an overlay visible. (b) After selectItem(1): the selectedIndex is 1 — the previous overlay is replaced, not stacked — and the overlay state still reports a single overlay visible. (c) After clearSelection(): selectedIndex is null, the overlay state reports no overlay visible, and the bottom bar has returned to signal mode.
REVIEWED_HASH: 3fef2793b0ca54cf8bd8f7bd8454fc944d37fe9356a515a0722bf092f54b709e
REVIEWED_BY: @DougYoungberg
NOTES: Planned test file: test/integration/overlay_navigation_integration_test.dart. The flutter_test test name carries the UID prefix (IT-010: ...) for the Flutter/JUnit join channel. Verifies the architectural integration of the overlay navigation subsystem (ARCH-010) — the NavigationNotifier / OverlayNotifier / bottom-bar coordination that enforces the single-overlay rule (SRS-010) and routes dismissal through clearSelection (SRS-009). The ESC and click-outside gestures that trigger clearSelection in the running application are verified at the system tier by ST-007.
-
reviewed17. Selectable application themes UID: SRS-017 TYPE: ui STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT: The software shall provide three selectable visual themes — Light, Dark, and Dark Glass — and shall apply the operator's selected theme across the application.
RATIONALE: Refines SYS-005. Offering Light, Dark, and Dark Glass themes lets the operator pick the appearance that suits the room's lighting and their preference, while the selection applies consistently across the workstation.
REVIEWED_HASH: 5e1badbc2e307e77ef9e1d06d69e677c0706f21a32b6a1d144b64768213b4160
REVIEWED_BY: @DougYoungberg
NOTES: coverage-plan: ST-013 observes the three themes offered and a theme change taking effect in the running application; IT-012 verifies that each theme type drives its application theme at the integration tier.
The theme subsystem owns the application's visual theming. The available themes are modelled as a domain value object (ThemeType: minimalLight, minimal, and darkGlass — surfaced to the operator as Light, Dark, and Dark Glass). AppTheme.forPreset builds the Flutter ThemeData for a given type, and application providers expose the active selection: the UISettingsNotifier holds the selected ThemeType, currentThemeTypeProvider reads it, and the theme-data and theme-mode providers derive the rendered theme from it, so the whole application re-themes when the selection changes.
Modelling the theme set as a domain value object with a pure type-to-ThemeData builder keeps the available themes and their construction free of presentation state, while the active selection lives in one application provider so the entire widget tree reads a single source of truth for the current theme.
500e401aad99ac68139f2fceaebd95c62c8c7bf29f5c215054493b98de99333c
@DougYoungberg
-
reviewedPassed11. Theme selection drives the application theme UID: IT-012 TEST_METHOD: automated TEST_TOOL: flutter_test STATUS: Approved RELATIONS (Parent): STATEMENT: Construct a ProviderContainer overriding the current-theme-type selection to each of the three theme types in turn, and read the theme-mode and theme-data providers for each.
ACCEPTANCE_CRITERIA: For ThemeType.minimal (Dark) the theme mode is dark and the built theme has dark brightness; for ThemeType.minimalLight (Light) and ThemeType.darkGlass (Dark Glass) the theme mode is light and the built theme has light brightness. Each of the three types resolves to a theme without error.
REVIEWED_HASH: 9562e0bfb19f555af441e01fec7921a69a881014e29539a51852e0f376e15e9b
REVIEWED_BY: @DougYoungberg
NOTES: Test file: test/integration/theme_integration_test.dart, in the IT-012-prefixed test case. Verifies the theme subsystem (ARCH-011) maps each selectable theme type to its application theme; ST-013 confirms theme selection end-to-end at the operator-facing tier.