@drawbridge/drawbridge-agents 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,6 +54,7 @@ skill) so it stops living only in tribal memory.
54
54
  index manually is not enough; until the boot collMod lands, every insert carrying the field
55
55
  fails Mongo 121 (not E11000), so keyed-idempotency writers silently drop their writes
56
56
  (2026-07-30 `notification.key` dev outage, Sentry DRAWBRIDGE-SYNC-32).
57
+ - Scraped-asset file reuse keys on `organization + meta.origin + meta.element` **plus
57
58
  `meta.render` for pipeline-rendered marks** (sync `resolveAsset` writes/queries it; the field
58
59
  and its index live in drawbridge-api `schema/file.js`). `origin` identifies the source asset,
59
60
  `render` content-hashes the rendered output — drop `render` from either side and a rendering
@@ -99,6 +100,32 @@ skill) so it stops living only in tribal memory.
99
100
  `createdAt`. Re-adding a TTL shorter than any billing interval silently under-bills long cycles
100
101
  and blanks reporting past the window — any retention change must clear billing + reporting.
101
102
 
103
+ ## Query params + the OAuth resume target (utils ↔ app-web ↔ api ↔ shopify-app)
104
+
105
+ - `request()` in `@drawbridge/drawbridge-utils` (`lib/fetch.js`) builds EVERY family GET query and
106
+ MUST percent-encode param values — `qs.stringify( params, { encodeValuesOnly : true } )`, never
107
+ `encode : false` (≥0.0.90; pinned by `test/fetch.test.js`). Param values routinely carry user input
108
+ and whole URLs, and with encoding off the first `&` inside a value ends that value while its
109
+ remainder arrives as sibling params. This silently truncated every list search (`search.value` of
110
+ `Smith & Sons` reached the api as `Smith `) and cost the Shopify account link its `redirect_uri`.
111
+ `encodeValuesOnly` — not full encoding — keeps the bracket notation (`search[keys][0]=`) so every
112
+ query the api already parses is byte-identical whenever values needed no escaping.
113
+ - The OAuth resume target (`redirectTo`) is **a URL carrying its own query string**, not a scalar:
114
+ `/api/oauth/authorize?client_id=…&redirect_uri=…&state=…&code_challenge=…`. Every hop must preserve
115
+ it whole — app-web's authorize BFF requires BOTH `client_id` and `redirect_uri` and otherwise falls
116
+ back to `/organizations`, which reads to a merchant as "connecting silently did nothing" rather
117
+ than as an error. Hops that carry it: the `/auth` ↔ `/auth/signup` links, `POST /api/auth/signin`,
118
+ `/oauth/google/start` → the api's `oauth.state` token row → `/api/auth/google`, `/api/switch/:id`,
119
+ and app-web's `proxy.js` auth gate.
120
+ - app-web's `/api/oauth/authorize` stamps `resumed=1` on the resume url when it bounces to `/auth`.
121
+ `getSession()` returns null both for "no session" and for "the api session lookup failed", so a
122
+ blip sends the browser to `/auth`, which resolves the session itself and sends it straight back;
123
+ the marker makes a second unresolved arrival stop instead of trading redirects until the browser
124
+ gives up.
125
+ - The api's `POST /oauth/google/callback` returns `redirectTo` in its 400 body when it read the state
126
+ row before failing, so app-web can keep the resume target across `/auth?error=oauth`. A
127
+ `oauth.state.invalid` failure cannot — that row is what carried the target.
128
+
102
129
  ## Reporting (api ↔ app-web)
103
130
 
104
131
  - Every reporting request (usage/revenue/redemptions/analytics, org + admin) carries the viewer's
@@ -116,6 +143,41 @@ skill) so it stops living only in tribal memory.
116
143
  in the tree. Keep the pins aligned.
117
144
  - Shared packages that wrap a stateful SDK (Sentry, redis, mongodb) must direct-dep it at an
118
145
  exact pin, never a loose peer range (see `sentry-sdk.md`).
146
+ - `@drawbridge/components` `Video` (≥0.0.62) owns the whole pre-playback story; consumers must NOT
147
+ re-add their own poster layer or mount gate. `poster` renders as an SSR placeholder (opt into
148
+ `priority` for fetchpriority=high when it's the LCP element), `defer` gates only the player —
149
+ never the placeholder, so deferring can't blank the box — and the native poster attribute is
150
+ kept so unmounting the placeholder at player-ready isn't frame-timing-critical. `onError` fires
151
+ exactly once, only after the hls→mp4 retry is exhausted. drawbridge-share's gallery carousel
152
+ depends on BOTH callbacks: it advances slides off `onDuration` OR `onError` registering a
153
+ duration — remove or re-fire either and a broken video stalls the whole carousel (share
154
+ `template-subdomain-gallery.js`, components `video.js`).
155
+ - `@drawbridge/components` `Video` ≥0.0.63 mounts a deferred player when the PLACEHOLDER PAINTS,
156
+ not on a timer: video bytes racing the poster on a throttled link is what delays LCP (measured
157
+ 2.8s → 7.5s when mounting at hydration), and a fixed dwell held the poster ~3s even when the
158
+ video was cache-warm.
159
+
160
+ ## Video poster frames (api ↔ sync ↔ share ↔ app-web)
161
+
162
+ - `file.frames[0]` MUST be the `00:00:00` frame — the one playback starts on. drawbridge-api
163
+ stamps `posterTimestamp = '00:00:00'` on upload and refuses to regenerate a timestamp that
164
+ already exists; drawbridge-share's gallery hero renders `frames[0]` (NOT `frames[poster]`, which
165
+ is the user's thumbnail — a 14s thumbnail jumps the instant playback begins). app-web reads
166
+ `frames[poster] || frames[0]`. See `drawbridge-docs/reference/media-assets.md`.
167
+ - The poster's `1080x1920` tier resizes with `fit: 'inside'`, never `cover`: the poster and the
168
+ `<video>` share a box and an `object-fit: cover`, whose crop depends on SOURCE aspect vs box
169
+ aspect — cropping the poster to 9:16 against a natively-shaped video makes the hero visibly jump
170
+ on any viewport wider than 9:16 (desktop). Changing this needs a poster regeneration, not just a
171
+ deploy.
172
+ - The poster worker `$set`s `poster : 0` on every run and APPENDS to `frames[]`. Any script that
173
+ generates a frame must snapshot and restore `poster`/`posterTimestamp`, **including on the
174
+ failure path**, or a retry silently discards the user's thumbnail choice.
175
+ - **Never compare frame timestamps as strings.** drawbridge-api writes `'HH:MM:SS'` (upload
176
+ default `'00:00:00'`) while app-web's scrubber writes `'HH:MM:SS.mmm'`, so `=== '00:00:00'`
177
+ misses the starting frame. That single mistake defeated the api's duplicate guard AND app-web's
178
+ own zero-check on the same day, letting the picker create a second starting frame. Compare
179
+ parsed milliseconds — `frameMilliseconds` in api `route/organization-file.js`, `lib/frames.js`
180
+ in app-web.
119
181
 
120
182
  ## Import surfaces
121
183
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawbridge/drawbridge-agents",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {