BioFlow Requirements
Integration Test Specifications
  • ARCH-001
    change in progress 1. Local persistence subsystem UID: ARCH-001 TYPE: subsystem SAFETY_CLASS: B STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT:

    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.

    RATIONALE:

    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.

    REVIEWED_HASH:

    cc7fc276970b8e3d7fcf9027298cfa0095eef00c2bb6e8ae4eee52404ffa4fa2

    REVIEWED_BY:

    @DougYoungberg

IT-001
change in progressPassed 1. 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.

  • ARCH-002
    change in progress 2. Local database encryption UID: ARCH-002 TYPE: data STATUS: Approved RELATIONS (Parent): RELATIONS (Child): RELATIONS (File): STATEMENT:

    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.

    RATIONALE:

    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.

    REVIEWED_HASH:

    a62962252646969da606f5d40f8fee50864ace3a43692de8b29c62a1fbeed8fb

    REVIEWED_BY:

    @DougYoungberg

IT-002
change in progressPassed 2. 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).

  • ARCH-003
    change in progress 3. Local database integrity mechanisms UID: ARCH-003 TYPE: data STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT:

    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.

    RATIONALE:

    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.

    REVIEWED_HASH:

    1fb8538abde3bc3860262ab683243fae4ce27045bcc31dc75e427bd65c79a4d2

    REVIEWED_BY:

    @DougYoungberg

IT-003
change in progressPassed 3. 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.

  • ARCH-004
    change in progress 4. Audit-logging subsystem UID: ARCH-004 TYPE: subsystem SAFETY_CLASS: B STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT:

    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.

    RATIONALE:

    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.

    REVIEWED_HASH:

    d5dbb469c6940e7b5d709b6f836a7b6c4096c44582959560c23d9c103c47bc67

    REVIEWED_BY:

    @DougYoungberg

IT-004
change in progressPassed 4. 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.

  • ARCH-005
    change in progress 5. Audit-log record shape and immutability UID: ARCH-005 TYPE: data STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT:

    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.

    RATIONALE:

    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.

    REVIEWED_HASH:

    b4f2ae7558b9c98456db381d46324adf474c77c5b2e4eddd9e825e783339b427

    REVIEWED_BY:

    @DougYoungberg

IT-005
change in progressPassed 5. 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.

  • ARCH-006
    change in progress 6. Audit-trail review-and-export presentation surface UID: ARCH-006 TYPE: subsystem SAFETY_CLASS: B STATUS: Approved RELATIONS (Parent): RELATIONS (Child): STATEMENT:

    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.

    RATIONALE:

    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.

    REVIEWED_HASH:

    5ba229d3b36ffd3ddc7705b5933bb742e3f639fa7fc16d4ad6c759c865b1ab15

    REVIEWED_BY:

    @DougYoungberg

IT-006
change in progressPassed 6. 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.

  • ARCH-008
    reviewed 8. Recording control widget UID: ARCH-008 TYPE: subsystem SAFETY_CLASS: B STATUS: Approved RELATIONS (Parent): RELATIONS (Child): RELATIONS (File): STATEMENT:

    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).

    RATIONALE:

    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.

    REVIEWED_HASH:

    535519a4d087d4a5931deb257e7c48e7266ea4f7b96de89810a49f41b5c2685c

    REVIEWED_BY:

    @DougYoungberg

IT-008
reviewed 7. 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.

IT-009
reviewedPassed 8. 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.

  • ARCH-010
    reviewed 10. Overlay navigation subsystem UID: ARCH-010 TYPE: subsystem SAFETY_CLASS: B STATUS: Approved RELATIONS (Parent): RELATIONS (Child): RELATIONS (File): STATEMENT:

    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.

    RATIONALE:

    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.

    REVIEWED_HASH:

    d9ef3a3e1718ca469e67bab37fc0eedccbbf3fc9aea11b6ff4cce247fa7b3908

    REVIEWED_BY:

    @DougYoungberg

IT-010
reviewedPassed 9. 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.

IT-011
reviewedPassed 10. 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.

  • ARCH-011
    reviewed 11. Theme subsystem UID: ARCH-011 TYPE: subsystem SAFETY_CLASS: B STATUS: Approved RELATIONS (Parent): RELATIONS (Child): RELATIONS (File): STATEMENT:

    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.

    RATIONALE:

    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.

    REVIEWED_HASH:

    500e401aad99ac68139f2fceaebd95c62c8c7bf29f5c215054493b98de99333c

    REVIEWED_BY:

    @DougYoungberg

IT-012
reviewedPassed 11. 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.