Updated the documentation of the README file in the project.

This commit is contained in:
2026-07-07 01:12:20 +02:00
parent 3dc5b2b7fc
commit 5c05eeb318
2 changed files with 32 additions and 31 deletions
@@ -418,8 +418,8 @@ private extension RecordingView.Model {
}
/// Starts listening to the commands sent through the commander, unless already listening: every received command is translated
/// into the button press it mirrors ``RecordingCommand/toggle`` presses the main button, and ``RecordingCommand/discard``
/// presses the discard button.
/// into the button press it mirrors ``RecordingCommand/toggle`` presses the main button, ``RecordingCommand/discard``
/// presses the discard button, and ``RecordingCommand/process`` presses the send button.
func listenToCommands() {
guard taskCommands == nil else {
return
+30 -29
View File
@@ -54,11 +54,11 @@ Attendi/
│ └── Attendi/ # The widget extension target
│ ├── Sources/
│ │ ├── Bundle/ # The @main widget bundle
│ │ ├── Extensions/ # The system symbol images shared across the extension
│ │ ├── Extensions/ # The shared symbol images, and the state-to-symbol/tint presentation mapping
│ │ ├── Intents/ # The toggle, process, and discard App Intents behind the activity's buttons
│ │ ├── Styles/ # The ActionButtonStyle button style
│ │ ├── Views/ # The activity's timer text, state label, controls, and Lock Screen banner
│ │ └── Widgets/ # The RecordingLiveActivity configuration, and the template AttendiWidget placeholder
│ │ └── Widgets/ # The RecordingLiveActivity configuration
│ ├── Catalogs/ # The extension's assets and localized strings
│ └── Resources/ # The extension's Info.plist
└── Packages/
@@ -76,7 +76,6 @@ Attendi/
│ │ └── View Models/ # The Notifier view model
│ └── Recording/ # The Recording feature target
│ ├── Catalogs/ # The record, pause, and send icons; the localized strings
│ ├── Clocks/ # The AnyClock eraser behind the model's injectable clock
│ ├── Errors/ # The AudioCapturingError, AudioTranscribingError, RecordingError errors
│ ├── Models/ # The Transcription model
│ ├── Protocols/ # The Capturing, Preinstalling, Reporting, Transcribing protocols
@@ -189,11 +188,10 @@ machine as a tap in the app.
### Timer
The recording time is measured against a monotonic clock injected at initialization — the `ContinuousClock` by
default, a manual clock in the tests: an anchor marks the start of the current recording
stretch, and pausing folds the stretch into an accumulated duration, so scheduling latency never accumulates as drift.
While recording, an async task republishes the measured time once per second. The view formats the count as `mm:ss`
and animates digit changes with a numeric text content transition.
The recording time is measured against a `ContinuousClock`: an anchor marks the start of the current recording
stretch, and pausing folds the stretch into an accumulated duration, so scheduling latency never accumulates as
drift. While recording, an async task republishes the measured time once per second. The view formats the count as
`mm:ss` and animates digit changes with a numeric text content transition.
### Errors
@@ -208,10 +206,9 @@ distinguished from an ordinary transcription failure. The underlying errors are
the model's current state, forwards every transcription to a closure given at initialization, and presents the
error alert. All controls carry localized accessibility labels matching their current action, and the timer
exposes its value to assistive technologies spelled out in full units.
- **`RecordingButtonStyle`** — a custom `ButtonStyle` used by the controls: the label sits on a padded, circular red
Liquid Glass background that reacts fluidly to presses, and dims while disabled. The label and its padding scale
with Dynamic Type via `@ScaledMetric`, and an `invertStyle` flag controls whether the label's color scheme is
inverted for contrast.
- **`RecordingButtonStyle`** — a custom `ButtonStyle` used by the controls: the label sits on a padded, circular
Liquid Glass background — tinted by the color given at initialization, red by default — that reacts fluidly to
presses, and dims while disabled. The label and its padding scale with Dynamic Type via `@ScaledMetric`.
### Services
@@ -249,11 +246,12 @@ mocks, and other backends can be plugged in the same way without touching the fe
`ContentView` hosts the feature inside a navigation stack and presents the transcribed text of every processed
recording in a modal sheet — or a content-unavailable message when the transcription is empty. A toolbar menu picks
the locale of the spoken language from the locales the `Preinstalling` service supports; the pick persists across
launches in the user defaults and is restored — realigned to the supported locales — before the feature loads,
starting from the closest supported equivalent of the user's locale on a first launch. Picking a locale preinstalls
its speech model assets in the background through the service, so the first transcription does not have to download
them mid-processing.
the locale of the spoken language from the locales the `Preinstalling` service supports, each named by its localized
language name paired with its region's flag emoji; the pick persists across launches in the user defaults and is
restored — realigned to the supported locales — before the feature loads, starting from the closest supported
equivalent of the user's locale on a first launch. The navigation title tracks the pick, reading "Transcribe to" the
locale's name. Picking a locale preinstalls its speech model assets in the background through the service, so the
first transcription does not have to download them mid-processing.
The preinstallation runs as a cancellable task owned by the model: picking another locale cancels a download still in
flight before starting the new one. The model maps the service's started, cancelled, and failed download events to the
@@ -274,7 +272,9 @@ On iOS, the recording flow surfaces outside the app through a Live Activity, bui
- **`ActivityReporting`** (app target) implements the `Recording` feature's `Reporting` port over `ActivityKit`: it
starts a Live Activity when a recording starts, updates its content on every pause, resumption, and processing
transition, and ends it — dismissing it immediately — when the flow ends. The activity is an auxiliary surface,
never a required one: a start the user disallowed or the system refused is silently ignored.
never a required one: a start the user disallowed or the system refused is silently ignored. Every update carries
a stale date, and a launch-time reset ends any activity a previous run left behind — a kill before it could end
its own — so an abandoned activity never lingers.
- **The `Commanding` target** (package) is the vocabulary the app and the widget extension share: the activity's
attributes and content state — the flow's state, the timer anchor, and the frozen elapsed time — the commands
that control the flow, and the `RecordingCommander` bridge that carries them. The commander exists because the
@@ -285,9 +285,10 @@ On iOS, the recording flow surfaces outside the app through a Live Activity, bui
system itself counts up from the content state's anchor, formatted as `mm:ss`, so a running recording needs no
content updates to tick — `RecordingStateLabel`, and `RecordingControls`, whose buttons wear the circular,
color-coded `ActionButtonStyle` and fire the toggle, process, and discard `LiveActivityIntent`s. The system
executes those intents in the app's process, where each sends its command through the shared commander and the
view model handles it exactly like a press of the matching button — closing the loop. The Lock Screen banner
keeps the system's default background material, Liquid Glass.
executes every intent in the app's process, where it sends its command through the shared commander and the
view model handles it exactly like a press of the matching button; the toggle intent runs there silently,
pausing or resuming without leaving the Live Activity, while the process and discard intents bring the app to
the foreground. The Lock Screen banner keeps the system's default background material, Liquid Glass.
## Testing
@@ -298,12 +299,11 @@ model. In the `Notifying` target, `NotifierTests.swift` covers the posting and a
injected, shortened dismissal delay, and `AppNotificationTests.swift` covers the `AppNotification` model. In the
`Commanding` target, `RecordingCommanderTests.swift` covers the ordered delivery of the commands. The state-dependent
behaviors are exercised with parameterized tests across all four states, driven through the model's public press
handlers, and the service mocks record their invocations in call order. The timer behaviors run against a manual test
clock advanced by hand, so no test sleeps in real time to see a tick. `AudioCapturingTests.swift` covers the one piece of pure logic in
the audio services — the parsing of audio session interruption notifications into capturing events, extracted so it
compiles on every platform. The rest of the system-bound service code, `AssetPreinstalling` included, is deliberately
untested: thin wrappers over device-global frameworks like `AssetInventory` and `SpeechAnalyzer`, whose behavior mocks
cannot meaningfully verify.
handlers, and the service mocks record their invocations in call order. `AudioCapturingTests.swift` covers the one
piece of pure logic in the audio services — the parsing of audio session interruption notifications into capturing
events, extracted so it compiles on every platform. The rest of the system-bound service code, `AssetPreinstalling`
included, is deliberately untested: thin wrappers over device-global frameworks like `AssetInventory` and
`SpeechAnalyzer`, whose behavior mocks cannot meaningfully verify.
Run the package tests with the `Features` scheme in Xcode (`⌘U`), or from the command line. The suites run on any of the
three platforms; macOS needs no simulator:
@@ -316,8 +316,9 @@ For iOS, point the destination at a booted simulator, for example `-destination
The app has its own `AttendiTests` unit-test bundle, hosted by the app: `ContentViewModelTests.swift` drives
`ContentView.Model` with a mocked `Preinstalling` service, covering the locale loading and alignment, the
cancellable preinstallation guard, and the mapping of the download events to the notifier's notifications. Run it
with the `Attendi` scheme in Xcode (`⌘U`).
cancellable preinstallation guard, the mapping of the download events to the notifier's notifications, the locale
naming — pairing the localized language name with its region's flag emoji — and the navigation title tracking the
picked locale. Run it with the `Attendi` scheme in Xcode (`⌘U`).
## Localization