Brisket
Brisket
Section titled “Brisket”Responsibility
Section titled “Responsibility”Brisket is the Next.js user-facing frontend. It owns app routes, feature UI, tRPC integration, product and billing UI, and browser-side user workflows.
Client logs and errors in Axiom
Section titled “Client logs and errors in Axiom”For browser errors, Sentry-style tags, breadcrumbs, user IDs, and frontend
releases, use the Brisket client-log runbook.
Start with service brisket and event source browser in the logs/traces
dataset; the Node runtime of the forwarding server does not identify the origin.
Onboarding funnels
Section titled “Onboarding funnels”Brisket serves the public /f/{funnel-id} onboarding journey and signup. See Onboarding Funnels for visitor progress, attribution and current creation limits.
Runtime
Section titled “Runtime”Brisket runs on Next.js with Turbopack. It uses Clerk middleware and auth() at the frontend edge.
Primary Source Paths
Section titled “Primary Source Paths”- apps/brisket/src/app/
- apps/brisket/src/features/
- apps/brisket/src/components/
- apps/brisket/src/server/
- apps/brisket/pkg/
Design System
Section titled “Design System”Shared React components are moving to the foxy-ds package under packages/foxy-ds/, which brisket consumes as a path dependency rather than a published package. The move is partial and stays that way for a while, so most of what brisket renders still lives under apps/brisket/src/components/. Read packages/foxy-ds/AGENTS.md, then the component inventory in packages/foxy-ds/AUTHORING.md for current exports. This page does not maintain a second inventory.
Choose icon families by product role. System and utility controls, including photo guidance and settings, use Streamline Material Rounded Fill or Rounded Line to match surrounding controls. Primary product icons, including bottom navigation, generation, and credits, use Streamline Flex Solid. The current exports and artwork are listed in the design-system authoring guide and Storybook icon catalogue.
While components exist in both places, the design system is checked first. A component present in foxy-ds is imported from there. A brisket component marked deprecated has been superseded, and the marker names the replacement import path. New shared components do not originate in brisket.
Anything the design system does not already cover stops for a decision before it is built, whether it exists in brisket or nowhere at all. The proposal is written in plain language and names the screens a component appears on, because designers decide what the library holds alongside engineers.
A component lands in foxy-ds before its brisket call sites move, so the two copies can coexist during migration. The superseded export carries a deprecated marker naming its replacement, and brisket’s type-aware lint reports remaining imports and usages at warning level. No per-component lint configuration is involved.
Page headings use @foxy/ds/page-header in standard or compact presentation. Gallery and Characters import the design-system heading directly. The app hook useMobilePageTitle publishes their titles to mobile navigation while the inline heading is hidden. Gallery and Explore selection use the same compact, translucent white @foxy/ds/bottom-bar. At viewport widths of 767px or less, the floating selection panel replaces mobile navigation until selection closes. Screens own selection state and actions. Explore places credits below the media count and reuses Create’s FacetedCharacterPicker, including its ChoicePicker trigger, desktop menu, and mobile sheet. Brisket loads both component CSS subpaths in its root layout.
The lookup order, the promotion gate, and the composition rules live in apps/brisket/AGENTS.md and packages/foxy-ds/AGENTS.md. The full procedure is the sourcing-ui-components skill under .agents/skills/.
Styling overrides
Section titled “Styling overrides”Use Tailwind utilities directly in component markup for static styling, including responsive variants and approved overrides. Do not introduce custom CSS classes in Brisket components. Keep runtime-calculated values in inline styles or CSS custom properties consumed by those utilities.
Do not add CSS !important declarations or Tailwind important modifiers such as
!p-0 or p-0!. Use the component API first, then utility variants and the
existing cascade layers for an approved override. Brisket declares
foxy, theme, base, components, utilities in
globals.css.
For normal declarations, later layers override earlier layers: application
component rules and utility classes can override the Foxy DS layer without
!important. Check computed styles at the affected viewport and state after
changing an override. Existing legacy declarations are not evidence that new
important modifiers are permitted; removing unrelated legacy usage needs its
own scoped validation.
Explore media loading
Section titled “Explore media loading”Explore uses one quality profile and width ladder for photos, active carousel slides, video posters and autoplay clips. The loop is: read browser hints → choose a rendition → load visible media → observe requests and playback → reconsider quality and preloading. A slow source pauses extra preloading; three distinct slow sources within 12 seconds can lower standard quality to balanced.
Custom prompt’s Get inspired tiles reuse this feed rendering and playback policy. Visible videos inside the active generation dialog may play; covered Explore videos and videos beneath a nested dialog pause. Closing the covering dialog resumes eligible previews. The tiles keep the related-example navigation action. Sources: Custom prompt composition, related-example cards and playback coordinator.
Recovery follows the same shared decision. Three successful fast loads plus ten quiet seconds, ten seconds of advancing visible playback, or an improved browser hint tier can clear the adaptive cap. These observations measure loading and playback health, not bytes per second; there is no separate speed test. Quality changes affect future requests and image upgrades; they do not restart healthy playing videos or replace sharp images with smaller ones.
flowchart TD
subgraph Choose["1. Choose one quality profile for all feed media"]
Hints["Browser connection hints<br/>Data Saver, effective type and estimated Mbps"]
Base["Base profile<br/>Data Saver, 2G/3G or positive estimate up to 2 Mbps: economy<br/>Above 2 to 5 Mbps: balanced<br/>Above 5 Mbps or unknown: standard"]
Quality["Apply the adaptive balanced cap, if active<br/>Standard becomes balanced; hinted economy stays economy"]
Size["Choose CDN width from tile width × display density<br/>Density limits: standard 3×, balanced 1.5×, economy 0.75×<br/>Round to the shared width ladder; maximum 1920px"]
Hints --> Base --> Quality --> Size
end
subgraph Load["2. Load eligible media and keep a visible fallback"]
Images["Photo, active carousel slide or video poster<br/>Keep a decoded image or show a 24px blur while loading"]
Show["Decode the selected full image, then reveal it<br/>Keep the current image visible during an upgrade"]
Fallback["Failed photo: one smaller CDN variant, then original<br/>Failed frame poster: one 1080px frame fallback"]
Allowed{"Video visible, tab visible,<br/>Data Saver off and no covering dialog?<br/>Its own dialog, if any, is open and visible"}
Clip["Start the selected muted 8s clip<br/>At most 2 starts per frame; 8s attempt deadline"]
Watch["Playing: sample currentTime every 500ms"]
Retry["Retry this video at economy if a smaller source exists<br/>The retry has its own 8s deadline"]
Poster["Release video source and keep poster<br/>After failure: leave and re-enter view<br/>After a visibility block: resume when it clears"]
Size --> Images -->|"Full image decoded"| Show
Show -->|"Higher profile still wanted"| Images
Images -->|"Carousel only: standard autoplay every 3s; manual navigation at every tier"| Images
Images -->|"Load error: bounded fallback chain"| Fallback --> Images
Size --> Allowed
Allowed -->|Yes| Clip -->|"Playback starts"| Watch
Allowed -->|No| Poster
Poster -->|"Recheck playback permission"| Allowed
Watch -->|"Hidden, offscreen or blocked"| Allowed
Clip -->|"8s deadline or autoplay denied"| Poster
Clip -->|"Native media error: local retry only"| Retry
Watch -->|"Native media error: local retry only"| Retry
Retry -->|"Starts or resumes"| Watch
Retry -->|"8s deadline or failed retry"| Poster
end
subgraph Feedback["3. Shared slowdown and recovery loops"]
VideoSlow["No startup or playback progress for 2s"]
Slow["Record the canonical source once<br/>Reset fast-load evidence and healthy playback streak"]
Count{"3 distinct slow sources within 12s?"}
Cap["Enable the balanced cap"]
Fresh["Successful visible load with matching resource timing<br/>At most 2s; exclude known cache hits<br/>Hidden byte counts are loading-health evidence, not Mbps"]
Fast{"3 distinct successful fast loads within 12s<br/>and 10s since the last slow report?"}
Healthy{"Visible playback advances for 10s with no new slow report?<br/>A progress gap over 2s restarts the streak"}
Clear["Clear the adaptive cap and old evidence<br/>Use the current browser-hinted profile"]
Images -->|"Still visible and unfinished at 2s; no completed transfer record"| Slow
Clip -->|"2s startup wait"| VideoSlow
Watch -->|"2s without progress"| VideoSlow
VideoSlow --> Retry
VideoSlow --> Slow
Slow --> Count
Count -->|Yes| Cap --> Quality
Count -->|"No: keep current profile"| Quality
Images -->|"Successful timed load"| Fresh
Clip -->|"Usable video and completed timed load"| Fresh
Retry -->|"Successful timed load"| Fresh
Fresh --> Fast
Fast -->|"Not yet: keep observing; recheck at the quiet deadline"| Fresh
Fast -->|Yes| Clear --> Quality
Watch -->|"Playback time actually advances"| Healthy
Healthy -->|"Not yet"| Watch
Healthy -->|Yes| Clear
Base -->|"Hint improves to a higher tier"| Clear
end
subgraph Preload["4. Spend spare bandwidth only after visible videos resolve"]
Pause["Every slow report pauses extra preloading for 60s<br/>This timer does not restore quality"]
Ahead{"Standard profile and known estimate above 5 Mbps?<br/>All visible videos resolved, no starts queued, playback allowed?<br/>No active preload pause?"}
Margin["All true: photos and posters may load 300px ahead<br/>Otherwise: visible media only"]
Slow --> Pause
Quality --> Ahead
Watch -->|"Readiness changes"| Ahead
Poster -->|"Fallback resolves the video"| Ahead
Pause -->|"Starts or expires"| Ahead
Ahead --> Margin --> Images
end
A poster and its video clip share one canonical source ID. Different widths, qualities, retries and tile instances of the same original do not provide independent votes. Tiny blurred previews are not observed. Sources: shared quality policy and shared request observer.
Signals and recovery
Section titled “Signals and recovery”| Signal | What is observed | Effect |
|---|---|---|
| Browser connection hint | Read on subscription and the connection’s change event: Data Saver, effective connection type and valid positive estimated Mbps. |
Select the base profile. A higher hint tier clears stale adaptive evidence immediately; repeated equal-tier hints retain it. |
| Pending photo, active carousel slide or poster | At two seconds after admitting the full source, it is still attached, visible and incomplete, with no matching completed Resource Timing record. The observer stops on cancellation, replacement, error or abort; a finished transfer waiting for decode is excluded. | Record the canonical source as slow and pause extra preloading. It contributes to the same three-source count as videos. |
| Video startup or frozen playback | Two seconds without startup or playback progress. After a valid playing event, the coordinator samples currentTime every 500ms; a change of at least 0.01s counts, including a loop wrapping. |
Report the same canonical source as its poster and try a smaller clip locally. Three distinct slow media sources within 12 seconds enable the balanced cap. Native errors and autoplay denial alone do not count as slow evidence. |
| Successful fast load | A successful, still-visible full image or video has a matching recent Resource Timing entry with duration greater than zero and at most two seconds. Exclude known cache delivery and revalidation: a cache delivery marker, or exposed body bytes with transferSize <= 300. An already complete cached image is not observed. |
Three distinct fast sources in a rolling 12-second window clear the cap after ten seconds without a new slow report. If the three loads arrive earlier, a timer rechecks freshness at that quiet deadline. |
| Advancing visible playback | Actual playback progress continues for ten seconds with no new slow report. Reports can come from one or more visible videos. A gap over two seconds restarts the streak; playing events alone do not complete it. |
Clear the cap and old evidence without needing detailed network timing. The current browser hint still determines the resulting profile. |
| New slow report or feed unmount | A slow report clears fast-load evidence and the playback streak. The last feed unsubscribe clears all adaptive evidence and its cap. | Recovery starts again after a new slowdown. An earlier visit cannot keep the next visit degraded. The separate preload pause survives remounts. |
The CDN can hide byte counts when cross-origin Timing-Allow-Origin details are unavailable. A matching request duration and successful visible load can still count toward recovery, so an image-only feed can recover without exposed byte totals. Known cache hits are excluded, but an opaque cached response cannot always be distinguished from a network transfer. This is evidence that visible loads are completing quickly, not a measurement of available bandwidth. Without a usable positive request duration, load completion alone does not count.
Playback recovery is deliberately a playback-health signal: buffered or cached clips can advance while the connection is still slow. It does not guarantee that a new large download will be fast. Later slow sources run the same downgrade loop again. Sources: request observer, quality policy and playback coordinator.
Loading and transitions
Section titled “Loading and transitions”| Media state | Current behavior |
|---|---|
| New photo, poster or newly active carousel slide | Use the current profile and tile geometry. Photos/slides use profile quality; frame posters keep quality 75. |
| Image already downloading | Keep its admitted request stable until it completes or leaves the allowed loading area. If the profile improves, finish that request first, then load the better rendition behind the visible image. |
| Decoded image with the same source and tile geometry | Keep it on downgrade. On upgrade, reveal the better image only after decoding; an already ready full image skips the blurred start. |
| Carousel | Load only the active slide. In standard quality, autoplay switches every three seconds while in view without waiting for download or decode. Balanced/economy pause autoplay; arrows, dots, keyboard and swipe still work. Show the next preview as it arrives; fade a completed outgoing image for 600ms, then release it. Pending inactive slides unmount. Reduced motion disables autoplay and fades. |
| Video already playing | Keep its rendition through shared profile changes unless its own recovery retries smaller. Re-entering view chooses the current profile. Hidden tabs, covering dialogs and Data Saver stop autoplay and release the source; closing or hiding the video’s own dialog also stops it. OS Reduce motion alone does not. |
A failed ordinary photo tries one smaller supported CDN width, capped at 1080px, before trying its original source. At the smallest width it goes directly to the original. Video-frame posters retain the single legacy 1080px frame fallback. These error fallbacks are bounded and do not themselves count as slow-network evidence. Sources: image source builder, ProgressiveImage, CarouselCard and Explore carousel wrapper.
Extra preloading needs standard quality and a known estimate above 5 Mbps. Every registered video must have an initial visibility result, visible videos must have reported playing or fallen back to their posters, the start queue must be empty, playback must be allowed, and no dialog may be open. Buffering or pause makes a video unresolved again. Photos and posters may then load up to 300px below the viewport; inactive carousel slides and offscreen video clips never preload. Every slow media report pauses this extra work for 60 seconds from the latest report. That pause can outlast quality recovery and never locks image quality. Sources: preload hook and video hook.
Quality and resolution
Section titled “Quality and resolution”The first matching connection condition selects the base profile. Missing or invalid estimates use standard quality. Repeated slow media sources can cap standard at balanced; the adaptive cap never selects economy.
| Profile | Browser connection hint | Maximum pixel density | Photo quality |
|---|---|---|---|
| Economy | Data Saver, slow-2g / 2g / 3g, or a positive estimate up to 2 Mbps |
0.75× tile width | 60 |
| Balanced | Estimate above 2 and up to 5 Mbps | 1.5× tile width | 70 |
| Standard | Faster estimate, or no usable estimate | 3× tile width | 75 |
The target width is the tile’s CSS width multiplied by the smaller of display density and the profile’s density limit. Display density is clamped to 1–3×; a missing tile width initially uses 480px. The shared ladder is 256, 384, 480, 512, 640, 720, 960, 1080, 1280, 1536, 1920.
Choose the next supported width, capped at 1920px. Balanced mode can take the preceding width when rounding up would overshoot the target by more than 25%, provided it still covers the CSS width. Economy can request fewer pixels than the CSS tile width. Resizing or changing display density selects sources for the new geometry again; the browser may reuse its cache. Source: shared sizing policy.
| Output | Cloudflare endpoint | Parameters beyond width |
|---|---|---|
| Photos and active carousel images | /cdn-cgi/image/ |
quality=60/70/75, format=auto |
| Video frame posters | /cdn-cgi/media/ |
mode=frame, time=0s, fit=scale-down, quality=75 |
| Autoplay clips | /cdn-cgi/media/ |
mode=video, time=0s, duration=8s, fit=scale-down, audio=false |
Photos and carousel slides use a 24px blurred preview at the current profile’s quality, with format=auto. Video-frame posters, including their 24px previews, keep quality 75 and use the media endpoint. Autoplay clip transforms omit explicit quality and format parameters; full-media playback elsewhere retains audio. Source builders: image and poster helpers and video preview URLs.
Browser Analytics And Tracking
Section titled “Browser Analytics And Tracking”Third-party browser trackers mount from the App Router root layout in apps/brisket/src/app/layout.tsx, and their components live under apps/brisket/src/features/analytics/.
- Google Tag Manager loads through a Stape container and renders only when BRISKET_STAGE is production.
- PostHog wraps the tree for product analytics on every stage, with autocapture off when NEXT_PUBLIC_DEV_FEATURES is true.
- Klaviyo identifies signed-in users and reports the signup event. It needs NEXT_PUBLIC_KLAVIYO_PUBLIC_KEY and stays off in development unless NEXT_PUBLIC_FORCE_DEV_KLAVIYO is true.
- FirstPromoter attributes referral clicks and signups, production only. apps/brisket/public/fprmain.js installs the call queue and issues the click call, and the CDN script drains that queue. The referral tracker sends the Clerk user id rather than the email address, and marks the signup only once FirstPromoter confirms the submission, so a blocked script is retried on the next visit that still falls inside the one-hour post-signup window. Deduplication is per browser profile, through a
localStoragemarker, not per account.
Referral links carry two unrelated mechanisms. FirstPromoter owns affiliate attribution, while a ref query parameter is stored in the browser and redeemed against sirloin as an internal referral code.
Contracts And Generated References
Section titled “Contracts And Generated References”Brisket uses generated proto packages under apps/brisket/pkg/. Billing and product UI consume sirloin billing product APIs.
Related Flows
Section titled “Related Flows”Related Standards
Section titled “Related Standards”Related Decisions
Section titled “Related Decisions”Frontend-affecting ADRs live under docs/src/content/docs/decisions/.
Operations
Section titled “Operations”Build and runtime environment validation is handled by the app-local package scripts and env schema.
Media CDN allowlist
Section titled “Media CDN allowlist”The copy-paste source of truth is apps/brisket/cloudflare-media-allowlist.rules. It matches requests outside the allowlist. Update this file alongside new rendition values, then copy its entire contents into the existing Cloudflare media rule without changing its configured action or scope. Committing it does not deploy Cloudflare configuration.
The baseline is Law’s live expression from September 10, 2026, with the following explicit Explore entries added. Existing values remain because other screens use the same CDN.
| Endpoint | Added width entries | Other changes |
|---|---|---|
/cdn-cgi/image/ |
384, 480, 960, 1280, 1536 |
None; existing image qualities remain |
/cdn-cgi/media/ |
24, 384, 960, 1280, 1536, 1920 |
None; audio and other media parameters remain unrestricted |
The expression retains the existing Cloudflare contains matching. This matches substrings throughout the path, so width=128 already incidentally accepts width=1280; it is not strict validation of transformation parameters. Tightening that behavior requires a separate audit of existing callers.
Local Commands
Section titled “Local Commands”- cd apps/brisket && pnpm lint
- cd apps/brisket && pnpm typecheck
- cd apps/brisket && pnpm test
- cd apps/brisket && pnpm check