# Bridgit Builder recipes

Documentation revision **1**. Contract reference **API revision 54**. These are six source-validated compositions, not live device test results. Documentation revisions change when recipes or explanations change; they do not imply an API revision or a new installed app. The canonical operation schemas remain [OpenAPI](https://bridgit.cc/learn/openapi.json), [endpoint catalog](https://bridgit.cc/learn/endpoints.json), [installed guide](https://bridgit.cc/learn/guide.md), [v2 tool guide](https://bridgit.cc/learn/control.md) and [changes](https://bridgit.cc/learn/changes.json). Recipes add orchestration, not a competing endpoint catalog.

[Recipe index](https://bridgit.cc/learn/builder/recipes.json) · [Human examples](https://bridgit.cc/recipes/) · [Decision contracts and current costs](https://bridgit.cc/learn/decisions.md)

## Choose a composition

| Stable recipe ID | Result | Runs on phone | Controller work |
|---|---|---|---|
| [pet-monitor](https://bridgit.cc/recipes/pet-monitor/) | Photo-based activity tally | Camera timer, local filter, selected remote model, tally, pause, offline card actions | Create/configure, retain watch ID, archive history, interpret gaps |
| [receipt-scanner](https://bridgit.cc/recipes/receipt-scanner/) | Owner-confirmed OCR draft | Selected-image OCR, card editing/answer | Validate totals, deduplicate, store approved receipt |
| [voice-notebook](https://bridgit.cc/recipes/voice-notebook/) | One bounded transcript | Explicit 10-second capture, offline speech, editable card | Save confirmed text, optional summarizer |
| [room-change-watcher](https://bridgit.cc/recipes/room-change-watcher/) | Image-change events | Timer, camera baseline/comparison, event, pause | Interpret selected events, retain event history |
| [daily-briefing-panel](https://bridgit.cc/recipes/daily-briefing-panel/) | Daily reminder and weather panel | Approved reminder, explicit weather refresh | Merge manifests, publish card; multi-source briefing is an extension |
| [external-device-dashboard](https://bridgit.cc/recipes/external-device-dashboard/) | Enrolled-device inventory | Authorized inventory read, optional authorized status read | Configure enrollment separately; retain observation timestamps |

These examples do not automatically install an applet, enable switches, enroll devices, move a camera, or capture anything on page load. Local cards do not become background controllers. The six JSON recipes list their `capability_ids`, machine-readable `operations`, `execution`, `lifecycle`, cost/failure behavior and source `evidence`. Canonical IDs are `v1.<capability>`; v2 tools have distinct `v2.<capability>` authorization and are not interchangeable.

## Source, released, installed, ready

1. **Source:** the public recipe and linked implementation describe a contract in the current source. `api_revision` in the recipe index is the reference used to check its examples.
2. **Released:** compare [public API changes](https://bridgit.cc/learn/changes.json) and the official download. A current website can precede an APK release.
3. **Installed:** after the owner authorizes connection, read `GET /v1/health` for the installed API revision and actual base URL. These starter recipes conservatively require revision 54 as a bundle; individual primitives may exist earlier (watch tally is revision 44+, for example).
4. **Ready:** inspect the installed `/v1/capabilities` and `/v1/guide`, the calling agent's grants, feature switches, Android permission/resource readiness, unlock requirements, and enrollment where relevant. A supported feature can still be off, ungranted or unavailable. Public JSON leaves ready unknown.

Read-only discovery never grants permission to run a recipe. Send the owner's real key only to the authorized device/relay; do not embed it in HTML. Controller HTTP requests use the paired base URL and Authorization header. A card calls `Bridgit.act` as its posting agent, with the same grants and switches; the card holds no key. A team decision key belongs to the optional server API and is a different credential.

## Create and render a card

Every `/recipes/<id>/request.json` is a **complete body** for `POST /v1/interactions`, including the full offline HTML. It is not an HTML URL for the phone to fetch. `/card.html` is a browser preview with no device bridge and no captures. `/recipe.json` includes complete synthetic request/response examples and the same card body. Fictional IDs in fixtures must be replaced with returned IDs.

For watches, the controller first sends the `start` example, stores the returned `watch_id`, and fills the card's `RECIPE.resource_id`. For receipt OCR, fill `RECIPE.gallery_id` with the authorized selected image ID. Programmatic authors can import `renderCard(template, recipe, {resource_id: returned.watch_id})` from `website/scripts/builder-recipes.mjs` before constructing the interaction body. `template` is the authored recipe-card.html string and `recipe` is the recipe object from content/builder/recipes.json. `renderCard` JSON-encodes data and escapes `<` to prevent a title/value closing its script tag. In another language do the equivalent; do not concatenate untrusted source strings into HTML. Never put keys in that configuration.

Post the complete interaction body, retain `interaction_id`, inspect `display` (a delivered notification is not proof of an open card), then wait with `GET /v1/interactions/{id}?wait=55` or one `/v1/events` loop. An HTML answer is in `answer.data`. A successful bridge action resolves to `{ok:true,status:200,body:...}`; errors use `{ok:false,status,error,body}`. Inspect the body and expected ID/state before changing the UI to stopped or paused. A status 200 alone is not proof of the desired resource state.

```javascript
// Inside a card, with a real controller-supplied watch ID:
const bridge = window.Bridgit || window.Bridget;
const result = await bridge.act({
  method: 'POST', path: '/v1/watches/' + watchId + '/pause',
  body: {paused: true}
});
if (!result.ok || result.body.user_paused !== true) {
  // Show an error; do not claim the watch paused.
}
// A response is accepted once per question view. This requests controller work.
bridge.respond(JSON.stringify({recipe_id:'pet-monitor',action:'archive'}));
document.addEventListener('bridgit:answered', () => {
  // Bridgit persisted the answer; controller processing remains unconfirmed.
});
```

`Bridgit.respond(JSON.stringify(value))` is compatible with the old `window.Bridget` alias (the only name on 1.6.269 and older). The templates require a build with `Bridgit.act` for live actions and conservatively gate the recipe bundle at revision 54; absence of the bridge is an explicit error, never a simulated success. HTML is limited to 256 KiB, answers to 16 KiB, no network/frames/external images/scripts/fonts. Only the native bridge reaches the posting agent's endpoints. Each successful or refused action is visible through `action.executed`; optional server calls initiated by an endpoint still require internet even while the controller is offline.

## Timer → sense → local filter → model → history → UI

The native [DogMonitor](https://github.com/sponicgarden/bridgit/blob/main/app/src/main/java/cc/bridgit/app/dog/DogMonitor.kt) takes a bounded 640-pixel photo on a ten-second timer, one photo at a time. Slow work skips slots instead of stacking captures. Its [DogChecks](https://github.com/sponicgarden/bridgit/blob/main/app/src/main/java/cc/bridgit/app/dog/DogChecks.kt) has a separate change gate for each camera/model pair. A locally unchanged image can reuse the previous classification; a fresh sample timestamp on a reused answer is **not** the timestamp of a new model inference. Native high-sensitivity comparison uses a one-hour maximum reuse interval. Noise masking temporarily ignores regions that changed without changing the model answer. Failure clears the affected gate so it cannot certify uninterrupted continuity.

Selected Clef and/or Jev calls classify visible activity; one primary result feeds native activity totals. The latest photo stays in memory; the native tracker saves activity history locally. Native audio is a separate opt-in loop: [DogAudio](https://github.com/sponicgarden/bridgit/blob/main/app/src/main/java/cc/bridgit/app/dog/DogAudio.kt) records five-second samples, never continuous coverage; the audio loop waits sixty seconds after sampling the selected sources. It uses Clef, and low confidence/silence/ambiguity becomes `unclear`. Missing microphone data is explicitly an unavailable sample, never a silence reading. Its finite recent readings do not establish a full-day bark count or continuous sound log.

The public `pet-monitor` recipe uses a generic camera watch with an explicit 30-second interval, five-minute reuse ceiling, 20 paid calls/hour cap and two-hour expiry. The timer, capture/filter, optional decision request and tally run on the phone. Paid inference runs on the remote decision service. The offline card reads `/v1/watches/{id}/tally` through the bridge. The controller stores IDs, creates/replaces cards, archives history before deleting, and handles optional integrations. Cache/tally behavior is the generic watch implementation, not inherited from DogMonitor.

The `room-change-watcher` uses `model:none`: a first photo establishes the baseline and later image changes produce events at the configured cooldown. It does not recognize a person, pet or danger. An optional controller can ask a model about a fresh capture after a change. No model call is silently included in the free recipe.

## Native versus publicly composable

| Behavior | Native Pet Monitor | Public composition |
|---|---|---|
| Ten-second photo loop | Native serialized loop | Generic watch supports 10 s only for eligible camera tally with change check and explicit paid cap; starter uses 30 s |
| Per-camera/model cache | Native DogChecks, one-hour max reuse | Generic watch local gate; recipe five-minute ceiling; not the same cache |
| Model classification | Native selected Clef/Jev, primary reading totals | Watch typed questions and tally, one selected model per watch |
| Activity history | Native tracker and saved history | Generic tally with retained episodes; export before delete, no automatic native history migration |
| Auto best-camera selection | DogCamera scoring and rotation, hysteresis/rescan rules | Controller must implement selection; setting a watch camera does not invoke Auto |
| Microphone | Opt-in five-second Clef samples, bounded recent list | Bounded `/v1/mic/record` or enrolled-camera audio plus a separate authorized server/controller decision call; not built into this watch |
| Last-seen pan search | PetCameraSearch: near-anchor bounded poses, timeout/cooldown, authorization and motor stop | Not included; generic watch never moves a camera |
| Remote owner portal | Native sharing/photo policy, authenticated viewer and native state | A card is offline UI, not a portal replacement; native viewer identity/tokens cannot be reproduced by HTML |
| Native applet reads/actions | `/v1/applets/dog-monitor`, history, cached frame; owner-approved applet actions where installed | Separate native read/configuration API; do not confuse it with the generic watch ID |
| Talk, feeding, physical device actions | Separate native/camera authorization and supported hardware | Explicit additional integration; starter performs none |

The native search starts only from a measured recent last-seen pose, waits at least a minute after loss, uses a five-minute cooldown and a thirty-minute anchor age, checks proximity to the anchor, uses at most seven nearby poses, times out after sixty seconds and stops the motor on cancellation. These are source constraints, not measurements from a real camera. Do not run physical search just to verify this document.

## Freshness, uncertainty and cost

Every reading needs its source time, observation type (new model inference, local reuse, inventory read or unavailable), and coverage. Cards explicitly show loading, empty, stale, error, paused and stopped states. Watch tally uses last_seen_at and the watch status uses last_check.at. Weather uses fetched_at/expires_at plus the service's stale flag, and preserves attribution. Device inventory is `not_checked` until a separate successful status action. OCR and transcripts are drafts requiring owner confirmation; they do not acquire a trustworthy image observation time by being rendered now.

Do not convert camera errors, occlusion, a confidence below threshold, missing audio, a skipped paid call, or a controller disconnection into a negative finding. A cached answer may generate callbacks, but it is still reused evidence. Episode totals approximate observations at a chosen interval/gap; they do not prove continuous behavior. Watch retained windows, ten recent checks and finite tally retention are bounded; they are not an unlimited event database.

Watch `usage` reports paid calls/cost, locally answered checks and skipped-over-cap checks. Limits are shared with the phone/team budget. The per-watch paid cap reduces consumption but does not reserve quota or guarantee successful inference. Check current decision documentation for prices and budgets. Camera/microphone sampling also consumes battery. No examples here are measured cost, latency or accuracy benchmarks.

On failures, inspect machine-readable errors, recent_checks, watch.ended and schedule status. Honor access revocation and owner stop. Back off transient busy/network/rate-limit errors rather than increasing sample rate. A quota/budget or terminal model failure can end a watch; start a replacement only with continuing owner authorization. Do not retry captures blindly after an uncertain transport result. Use endpoint-supported idempotency for mutations that support it and preserve the same logical key/input. Watch create is not an idempotent endpoint: inspect existing watches before retrying uncertain creation.

## Lifecycle and control verification

Watch pause/resume changes `user_paused` and preserves tally without extending expiry. There is no generic watch update/PATCH: archive the old tally, delete, recreate and rebind the card. Confirm deletion returns the saved ID, then consume watch.ended when available; final history can arrive in the end event. Deleting a watch and cancelling its interaction are separate operations. Cards stop new requests and refuse late results once stopped; a local card stop does not prove an external controller stopped.

The non-watch starter cards have no automatic background job. Pause is local UI suspension, resume allows another explicit read, stop suspends the card and sends one controller shutdown request. An in-flight capture may finish; no cancellation endpoint is invented. The controller must acknowledge any actual notebook/receipt/dashboard job shutdown separately and cancel the card with `DELETE /v1/interactions/{id}`. Never display an external action as complete just because respond was called or persisted.

Schedules belong to an agent, so inspect `/v1/schedule/manifest` and merge with existing actions. New manifests await owner approval. The daily reminder starter is bounded to seven executions. The weather card is independent: pausing the card does not pause the reminder schedule. `/v1/schedule/pause` affects the agent’s whole manifest. To remove only this recipe from a shared manifest, submit a replacement without that action and wait for approval. Only DELETE the entire schedule when dedicated to this recipe; `keep_assets=true` preserves saved files. Check `active` digest, next run and last status, never assume `awaiting_owner` means active.

## Validation and future measurements

The generator verifies all request operation refs against generated OpenAPI, checks required fields/types/enums/query names, bounded recipe watch settings and evidence paths, and regenerates complete offline HTML bodies. Generated files are compared byte for byte in tests. Card script tests mock the real `{ok,status,body}` bridge shape, check acknowledgement/failed stop/paused/stale behavior, and prove no call happens on load. OpenAPI has intentionally shallow nested request and descriptive response contracts; Node checks alone are not proof of Android handler acceptance. The recipe Kotlin tests additionally pass watch/schedule examples through canonical pure parsers and card HTML through CardRules. Synthetic responses illustrate actual source shapes; they are not captured device responses or complete runtime smoke tests.

Before promoting a real integration, collect owner-authorized labeled samples, expected/actual answers, missing coverage, source age/cache reuse, paid calls/cost, stop/pause confirmations and controller/device/server connectivity. Keep contract checks separate from task-success evidence. Do not claim physical camera testing, real notebook persistence, live OCR accuracy or benchmark success unless it was actually observed. Add a future recipe by updating authored metadata, using existing API schemas/IDs and publication tests, documenting every lifecycle boundary, then attaching evidence from separately authorized functional testing.
