Skip to content

Response dispatch

Outputs subscribe to the bus independently. Each output is a ReactiveOutput that pattern-matches ReactionKind and consults its row of the per-output toggle matrix before firing. A reaction with LED + sound enabled and flash off produces a pulse and a clip and nothing on screen. The matrix is a hard gate, not a hint.

Each output independently subscribes to the bus via bus.subscribe(), which returns a fresh AsyncStream<FiredReaction>. The consumer loop reads a live config snapshot on every reaction and gates by: the output's master enable, the per-output per-reaction toggle matrix, and an internal playback-ends-at guard that prevents overlapping responses.

      Output consumer loop (same structure for all four outputs)
OutputConfig typeActionNotes
AudioPlayerAudioOutputConfigPlays the pre-selected fired.soundURL on each enabled Core Audio device. Volume = volumeMin + intensity * range.For non-impact reactions, soundURL is nil. audio skips.
ScreenFlashFlashOutputConfigRenders a borderless NSWindow overlay per enabled screen: radial gradient + face image. Fade-in / hold / fade-out envelope, timing scaled to intensity.Window pool reused across reactions. Face pulled from fired.faceIndices.
LEDFlashLEDOutputConfigPWM-dithers the Caps Lock LED via IOKit HID at 60 Hz. Optionally animates keyboard backlight via KeyboardBrightnessClient (CoreBrightness private framework) with a spring oscillation envelope. Restores both to pre-pulse state on completion.Crash-recovery sentinel file written before first pulse; deleted on clean restore.
NotificationResponderNotificationOutputConfigPosts a UNMutableNotificationContent with a tier-matched phrase from a 40-locale string table. Auto-dismisses after dismissAfter.Locale: user override or system fallback.

MenuBarFace also subscribes to the bus independently. It only reacts to .impact reactions, swaps the NSStatusItem icon to the face at fired.faceIndices[0] for max(0.5, debounce) seconds, then restores the template icon. It is not an output in the sense of the four above. it has no config provider and no toggle matrix.

Engineered with excessive care for a deeply unnecessary purpose.