<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Usman Adamu</title>
    <description>The latest articles on DEV Community by Usman Adamu (@othmanimam).</description>
    <link>https://dev.to/othmanimam</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4204406%2F6442cd64-ec68-4985-81a6-64a759ea57f4.jpg</url>
      <title>DEV Community: Usman Adamu</title>
      <link>https://dev.to/othmanimam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/othmanimam"/>
    <language>en</language>
    <item>
      <title>Breathe Together: a camera-driven breathing coach where two people share one pacer</title>
      <dc:creator>Usman Adamu</dc:creator>
      <pubDate>Mon, 09 Nov 2026 22:53:20 +0000</pubDate>
      <link>https://dev.to/othmanimam/breathe-together-a-camera-driven-breathing-coach-where-two-people-share-one-pacer-1m13</link>
      <guid>https://dev.to/othmanimam/breathe-together-a-camera-driven-breathing-coach-where-two-people-share-one-pacer-1m13</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/presage-2026-11-04"&gt;Presage SmartSpectra SDK Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Breathe Together is a camera-driven breathing coach built on the Presage SmartSpectra SDK. It estimates your breathing rhythm from your webcam and then eases a pacing guide gently toward slower breathing over a short session — a slow control loop, confidence-gated, that never scolds. Solo mode is a one-person session; "Breathe with someone" mode gives two people one shared pacer, with two orbs on screen that drift closer as the two measured rates get more similar.&lt;/p&gt;

&lt;p&gt;The experience: consent (nothing opens the camera until you say so) → setup with live framing guidance ("Center your face", "Move to better light", then "Ready") → a session with a luminous pacer orb, synthesized inhale/exhale tones, small rate readout, and friendly state labels ("Settling in", "Matching your pace", "Slowing gently", "Staying with you", "Winding down") → an observation-only summary: what the pacing did, what the camera saw, and nothing beyond that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/pwJvF4yj_JE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/6-xrVq0Uw3Y" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;What to look for in the video: the observed breathing rate tracking the camera, the pacer starting at the gentle default during calibration and then leading down, the Hold behavior when the person deliberately diverges (the coach slows to "Staying with you" instead of pushing), and the final observation-only summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/OthmanImam/breathe-together" rel="noopener noreferrer"&gt;https://github.com/OthmanImam/breathe-together&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm install
cp .env.example .env   &lt;span class="c"&gt;# no key needed for the mock demo&lt;/span&gt;

npm run serve          &lt;span class="c"&gt;# mock mode: everything works with zero credentials&lt;/span&gt;
&lt;span class="c"&gt;# open http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real measurement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# free key from https://physiology.presagetech.com/auth/login into .env&lt;/span&gt;
&lt;span class="nv"&gt;SIGNAL_SOURCE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sdk npm run serve    &lt;span class="c"&gt;# on a machine with a camera&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pair mode (both mock and SDK): choose &lt;em&gt;Breathe with someone&lt;/em&gt; → Create room&lt;br&gt;
→ join from a second browser (needs HTTPS off the host machine) → lobby →&lt;br&gt;
host presses Start. Pair sessions are capped at 5 minutes and count&lt;br&gt;
against the daily SDK budget twice (one worker per person) — the lobby&lt;br&gt;
shows the remaining budget before starting. Test commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;                                   &lt;span class="c"&gt;# 128 tests&lt;/span&gt;
npm run simulate:pair -- --a agitated_then_settling --b calm --minutes 3 --seed 7
npm run smoke:sdk                          &lt;span class="c"&gt;# SDK native-load + camera discovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Responds to Human Signals
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Signal used:&lt;/strong&gt; breathing rate — the SDK's 30-second trailing average —&lt;br&gt;
plus its documented validation statuses (&lt;code&gt;ok&lt;/code&gt;, &lt;code&gt;no_face&lt;/code&gt;, &lt;code&gt;poor_light&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;off_center&lt;/code&gt;, &lt;code&gt;starting&lt;/code&gt;, &lt;code&gt;lost&lt;/code&gt;), which drive setup guidance, the signal-&lt;br&gt;
loss behavior, and when the coach trusts a reading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration:&lt;/strong&gt; the browser owns the camera (consent prompt and preview&lt;br&gt;
only exist there), downscales frames to 320×240 RGBA at 15 fps, and pushes&lt;br&gt;
them over WebSocket into &lt;code&gt;useCustomInput()&lt;/code&gt; / &lt;code&gt;sendFrame()&lt;/code&gt; in a &lt;strong&gt;dedicated&lt;br&gt;
child process&lt;/strong&gt; — the native SDK state is process-global, so the engine&lt;br&gt;
gets its own process that can be restarted and supervised without touching&lt;br&gt;
the app. Only the breathing-metrics bundle is requested; aggregate&lt;br&gt;
telemetry is disabled (&lt;code&gt;enableTelemetry: false&lt;/code&gt;), credibly matching the&lt;br&gt;
SDK's own privacy documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt; the coach is a slow control loop over windowed evaluations,&lt;br&gt;
never per-second reactions. Calibrate (45 s — comfortably covers the 30 s&lt;br&gt;
reporting window), match, lead (one 0.5 bpm step at a time toward a 7 bpm&lt;br&gt;
floor), hold (sustained divergence ≥ 35 s, trend-aware so the reporting&lt;br&gt;
lag alone can't trigger it), and a signal-lost overlay where the pacer keeps&lt;br&gt;
cycling steadily. The shared pair pacer is the maximum of the two solo&lt;br&gt;
coaches' proposals — the gentler person leads it, and it never speeds up to&lt;br&gt;
chase anyone. Alignment is a separate slow metric (EMA with τ ≥ 30 s,&lt;br&gt;
hysteresis, freeze-on-loss) describing rate similarity only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Entering &lt;strong&gt;both&lt;/strong&gt; prize categories (and the overall pool).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most Creative or Delightful Use.&lt;/strong&gt; The question we started from: what&lt;br&gt;
does a shared breathing guide &lt;em&gt;look like&lt;/em&gt;? Pair mode answers with two orbs&lt;br&gt;
of light that breathe one shared pacer — phase-locked to within 100 ms&lt;br&gt;
across the two screens — and drift closer with a strengthening glow as the&lt;br&gt;
two measured rates converge. It's playful but honest: the visual can't&lt;br&gt;
fake togetherness; it only shows rate similarity, slowly, with hysteresis&lt;br&gt;
so nothing flickers. The most creative constraint, though, is negative:&lt;br&gt;
the shared pacer never pushes anyone. If one person diverges, the guide&lt;br&gt;
holds steady — delight without pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Thoughtful Real-World Use.&lt;/strong&gt; The real-world need is wellbeing that&lt;br&gt;
doesn't surveil: people want breathing exercises without trusting a camera&lt;br&gt;
feed to a server, a dashboard, or a verdict about their emotions. So the&lt;br&gt;
defaults are the product: consent per person per visit (nobody consents&lt;br&gt;
for someone else), frames discarded in memory (never stored, never&lt;br&gt;
relayed to the partner), per-person rates private unless explicitly shared,&lt;br&gt;
a visible Stop, guide-only mode when the camera is denied, and a summary&lt;br&gt;
that says exactly what it says — "a plain rundown of the session, with&lt;br&gt;
nothing more implied." Room hygiene matches: in-memory rooms, expiring&lt;br&gt;
codes, expiring credentials-free design, quota counted per person so free-&lt;br&gt;
trial credits can't be silently burned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent and Data Handling
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The camera opens &lt;strong&gt;only after an explicit consent click&lt;/strong&gt;, per person,
per page load. Revoking ends the session and stops sampling; Stop is
always visible while the camera is on; cameras stop when the room
closes, consent is revoked, or the server shuts down.&lt;/li&gt;
&lt;li&gt;Frames travel browser → host server → measurement worker on the &lt;strong&gt;same
machine&lt;/strong&gt; (the architecture is localhost; deployments elsewhere would
move frames onto that infrastructure and are out of scope). They are
consumed in memory and discarded — never written to disk, never relayed
to the other participant. The server never relays raw frames, and never
relays breathing rates unless that person opted in (off by default).&lt;/li&gt;
&lt;li&gt;Vendor traffic (observed in live SDK logs): the engine contacts Presage
for account gating only — API-key check, device pairing, metrics
authorization, usage verification. No frames, no measured values, no
identifiers. The aggregate telemetry the SDK defaults &lt;strong&gt;on&lt;/strong&gt; is disabled
in this build (&lt;code&gt;enableTelemetry: false&lt;/code&gt;); that is stronger than disclosure.&lt;/li&gt;
&lt;li&gt;Storage: nothing by default. No history, no database, no server logs
beyond counters and ids. An optional local history (off) stores summary
text lines in &lt;code&gt;localStorage&lt;/code&gt;, deletable in-app ("Delete my data").&lt;/li&gt;
&lt;li&gt;Retention/deletion: rooms vanish when empty; closing the tab discards
the in-memory session; there is nothing server-side to delete.&lt;/li&gt;
&lt;li&gt;Third-party/AI service use: none. The SDK's natural-language "insight"
feature is deliberately unused (free-form output can't be constrained
to observation-only wording). The build itself used Presage's agent
skill and docs (see below), which doesn't affect your data.&lt;/li&gt;
&lt;li&gt;Live behavior with denials: camera denied → guide-only (steady pacer,
zero sensing); signal lost mid-session → pacer continues, gentle notice,
automatic recovery; source crash → supervised restart with backoff, then
guide-only with an honest notice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full current document: &lt;code&gt;docs/CONSENT_AND_DATA.md&lt;/code&gt; in the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Session
&lt;/h2&gt;

&lt;p&gt;Built with AI assistance (OpenCode agent + the Presage &lt;code&gt;using-smartspectra&lt;/code&gt;&lt;br&gt;
agent skill and published docs). A curated, publication-ready record of the&lt;br&gt;
working sessions — including honest debugging beats, the real bugs found,&lt;br&gt;
and the SDK findings — is at &lt;code&gt;docs/BUILD_LOG.md&lt;/code&gt; in the repository.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>presagechallenge</category>
    </item>
  </channel>
</rss>
