Commit Graph
1230 Commits
Author SHA1 Message Date
Miguel ÁngelandClaude Sonnet 4.6 9e51f5cfaa feat(lint): add gsap_from_opacity_noop rule — catch invisible elements
Detects when an element has CSS `opacity: 0` (inline or style block) AND
is targeted by gsap.from({opacity: 0}). Since from() animates FROM the
specified value TO the CSS value, this produces a 0→0 animation where
the element never becomes visible.

Root cause of all-black renders from the product-launch-video skill:
every text element had opacity:0 in CSS + gsap.from({opacity:0}),
making all text permanently invisible despite the timeline "working."

Fires as error (not warning) to block the render pipeline. Includes
actionable fix hint. 4 test cases: inline style, style block, clean
code (no false positive), and gsap.to() exit (no false positive).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:58:44 +00:00
Miguel ÁngelandClaude Sonnet 4.6 dc864f3e6c test(producer): regenerate sub-comp-t0 baseline + improve visual contrast
The text-rendering:geometricPrecision rule (b7bd9565) shifted glyph advances
~1% in headless Chrome. Baselines for png-sequence, heygen-promo-preview-assets,
and sub-composition-video were already regenerated — sub-comp-t0 was missed.

Also improves the fixture's visual contrast:
- Background: #07110d (near-black) → #0f172a (slate-900)
- Hook scene: background #1e293b, text #ef4444/#ffffff
- Later scene: background #334155, text #ffffff
- Absolute positioning for text elements (compiler inlines sub-comp
  content at intrinsic width, collapsing the flex container)

Baseline regenerated inside Dockerfile.test per CLAUDE.md. Test passes:
0 failed frames, 100/100 checkpoints green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:19:02 +00:00
Ular Kimsanov 12808fd38f Merge pull request #987 from heygen-com/feat/capture-font-extractor
feat(capture): identify hashed fonts via OpenType name table
2026-05-21 10:09:25 -07:00
Miguel Ángel 90a4e4b1c5 chore: bump version to 0.6.31 v0.6.31 2026-05-21 12:22:12 -04:00
Miguel Ángel 25d01db973 Merge pull request #999 from heygen-com/worktree-feat+studio-crash-stack-traces
feat(studio): add stack traces to error telemetry
2026-05-21 18:20:53 +02:00
Miguel Ángel 6a868b3787 feat(studio): add stack traces to all error telemetry events
Send `stack_trace` (up to 4KB) on crash, unhandled_error, and
unhandled_promise_rejection events so PostHog captures the full JS
call stack for debugging. Also bump `component_stack` from 500→2000
chars, and add `error_name` to promise rejections.
2026-05-21 12:18:55 -04:00
Miguel Ángel 6b541b7cf4 Merge pull request #997 from heygen-com/feat/studio-fullscreen-preview
feat(studio): fullscreen preview mode (F key)
2026-05-21 18:17:30 +02:00
Miguel Ángel 9e7eb10edd feat(studio): fullscreen preview mode (F key)
Add distraction-free fullscreen mode using the HTML5 Fullscreen API.
Press F to enter fullscreen (Esc to exit). When active, the composition
fills the screen — timeline, sidebars, and editing overlays are hidden
while all playback shortcuts (Space, J/K/L, arrows, etc.) remain
functional. A fullscreen toggle button is also added to player controls.

Closes #995
2026-05-21 11:11:28 -04:00
Miguel Ángel 114b83bbf6 chore: bump version to 0.6.30 v0.6.30 2026-05-21 00:05:27 -04:00
Miguel Ángel 099e1257ee Merge pull request #994 from heygen-com/worktree-feat-timeline-ui
feat(studio): timeline UI overhaul — flat clips, thumbnails, visual cleanup
2026-05-21 06:03:53 +02:00
Miguel Ángel 8c3dfb8d40 fix: format studio.css keyframes 2026-05-21 00:00:22 -04:00
Miguel Ángel 36898f7681 fix(studio): address timeline PR review — drop dead code, add fadeIn keyframes, compSrc test 2026-05-20 23:54:19 -04:00
Miguel Ángel ef2ff298b6 feat(studio): timeline UI overhaul — flat clips, unified color, working thumbnails
Visual redesign of the timeline:
- Flat solid clip backgrounds, no gradients or multi-layer shadows
- Unified neutral color palette — all clips use the same base color
- Clean 6px border-radius instead of organic asymmetric radii
- Single-line labels, no redundant tag badge or time range
- 3px teal accent stripe on the left edge of every clip
- Simplified trim handles (2px accent bars)

Thumbnail fixes:
- Fixed broken thumbnail URLs — compositionSrc was an absolute URL
  that got nested inside the preview/comp path. Now normalized to
  relative path before constructing the thumbnail URL
- Thumbnail background changed from #000 to #1c2028 so transparent
  overlay compositions render visible content against a matching
  dark background
- mix-blend-mode: lighten on thumbnail layer — dark backgrounds blend
  away, bright content shows through
- Removed double-label in CompositionThumbnail (was showing both a
  badge at top and text at bottom)
2026-05-20 23:54:19 -04:00
Miguel Ángel be9b61a8c9 Merge pull request #986 from heygen-com/fix/studio-edit-persistence-and-render-css
fix(studio): server-side DOM patching, render CSS scoping, and resilience
2026-05-21 05:53:32 +02:00
Miguel Ángel 3a23542a14 test(producer): regenerate sub-composition-video baseline for Chrome frame-timing drift 2026-05-20 23:44:28 -04:00
ukimsanov 5e7a7a8956 fix(capture): address review feedback on font extractor
Five fixes from Copilot's inline review + Miguel's note on PR #987:

1. inferWeightFromSubfamily — only matched concatenated forms
   ("extralight", "semibold"). Spaced ("Extra Light") and
   hyphenated ("Extra-Light") variants fell through to the 400
   default, misreporting 200-weight fonts as 400. Now normalizes
   `[\s-]+` out of the subfamily before matching.

2. meta.tool — was hardcoded to "fontkit@2.0.4" but
   `packages/cli/package.json` allows ^2.0.4, so the manifest
   string would drift on every dep bump. Now records just
   "fontkit"; the version moves with the dep and can be discovered
   from package.json at debug-time if needed.

3. FontFileMetadata.rawFamily — docstring said "nameID 16 preferred,
   then nameID 1" but the code also derives from PostScript via
   deriveFamilyFromPostscript when both name-table fields are
   missing. Doc now reflects the actual three-step precedence.

4. FontFileMetadata.weight — docstring said "100-900" but the code
   emits 0 (when identified: false) and 950 (when
   canonicalizeFamily picks ExtraBlack/UltraBlack). Doc now
   documents both edge values explicitly.

5. sharp ^0.34.5 — bumped from ^0.34.0 on this PR but font
   extraction doesn't use sharp; the bump is needed by the contact
   sheet code in PR #988. Reverted on #987; will re-bump on #988
   where it's actually consumed.

Also adds vitest coverage:
- 34 tests in fontMetadataExtractor.test.ts
- Covers inferWeightFromSubfamily for concatenated, spaced, and
  hyphenated forms (including composite styles like "Bold Italic"
  and case-insensitivity)
- Covers canonicalizeFamily for unchanged families, stripped
  weight tokens, preserved width modifiers, and the 950 emit
- Integration tests for extractFontMetadata (non-existent dir,
  empty dir) verifying the meta.tool / generatedAt shape

Exported `inferWeightFromSubfamily` and `canonicalizeFamily` for
testing. Pure functions, internal helpers, but exporting is the
clean way to pin their behavior against regressions.
2026-05-20 17:54:32 -07:00
JamesandClaude Opus 4.7 129a7e3902 test(regression): regenerate baselines for png-sequence + heygen-promo-preview-assets
The text-rendering:geometricPrecision rule injected by the previous commit
shifts glyph advances by ~1% on chrome-headless-shell (was optimizeSpeed
under text-rendering:auto). Two fixtures with strict gates tripped:

- distributed/png-sequence: maxFrameFailures=0 byte-identity gate, all 60
  frames now differ. The fixture's own meta.json already documents this
  as the expected response to renderer-pixel changes.
- heygen-promo-preview-assets: minPsnr=30, maxFrameFailures=0; one frame
  dropped to 27.67 dB after the layout shift.

Full local regression run (47 fixtures): 45 passed, only these 2 needed
regeneration — the text-rendering change passes through the rest without
PSNR impact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:44:41 -04:00
James b7bd956583 fix(producer): force text-rendering:geometricPrecision so headless-shell matches Chrome 2026-05-20 18:44:41 -04:00
Miguel Ángel 93728d7223 Merge pull request #992 from HOSS1E/add-mute-loop-shortcuts
feat(studio): add M (mute) and Shift+L (loop) keyboard shortcuts
2026-05-21 00:02:32 +02:00
HOSS1E c99dabc87d feat(studio): add M (mute) and Shift+L (loop) keyboard shortcuts
Adds NLE-style hotkeys for muting audio and toggling loop playback in the
Studio player, matching the workflow conventions in DaVinci Resolve,
Premiere, and Final Cut.

- M toggles audio mute (no-op above 1x playback, matching the mute button's
  existing gating behavior).
- Shift+L toggles loop. Ctrl/Cmd+L was considered but is filtered out by
  shouldIgnorePlaybackShortcutEvent and conflicts with the browser address
  bar; Shift+L is also consistent with the existing Shift+I / Shift+O
  modifier pattern.

The Shift+L handler runs before the existing plain-L shuttle case so it
doesn't also start forward playback.

Fixes #905
2026-05-20 17:50:04 -04:00
Miguel Ángel 692c1431c3 fix(ci): resolve puppeteer from producer package in studio smoke test 2026-05-20 17:32:55 -04:00
Miguel Ángel 1d6ed9f41a ci: add studio load smoke test — catches runtime errors on first page load 2026-05-20 17:30:05 -04:00
Miguel Ángel 28fdd6181d fix(studio): guard __STUDIO_VERSION__ reference for dev server without restart 2026-05-20 17:27:40 -04:00
Miguel Ángel 2bd16a5d3e test(producer): add wysiwyg-subcomp-css regression baseline video 2026-05-20 17:24:27 -04:00
Miguel Ángel 0224239268 fix(core): harden html-attribute allowlist with on* prefix block, data:text/html URI rejection 2026-05-20 17:21:37 -04:00
Miguel Ángel addc6ec9cd fix: allowlist html-attribute names to prevent stored XSS surface 2026-05-20 17:17:46 -04:00
Miguel Ángel a58a881d2e fix: address review — PostHog key comment, flushTimer cleanup, TOCTOU, type guard 2026-05-20 17:10:58 -04:00
Miguel Ángel 45999226a3 fix(studio): server-side DOM patching, render CSS scoping, and resilience
Root-cause fix for edits being wiped after refresh: the studio's
inspector edits were patched client-side via regex matching in
sourcePatcher.ts, which silently failed for many compositions ("Unable
to patch" toast). Replaced with a server-side patch-element API endpoint
using linkedom for proper DOM parsing via querySelector.

Also fixes the WYSIWYG render bug where sub-composition CSS was not
applied. The CSS scoping generated descendant selectors when both
attributes coexist on the same host element. Fixed to use compound
selectors for the authored root.

Edit persistence:
- New POST /file-mutations/patch-element endpoint using linkedom
- persistDomEditOperations calls server instead of client regex
- 15 tests covering all patch operation types

Render CSS scoping:
- Compound selector for authored root on host element
- Regression test: wysiwyg-subcomp-css (baseline pending Docker)
- 3 unit tests + 1 integration test

GSAP CDN fallback:
- Preview: error-handler catches gsap 404 and loads from CDN
- Producer: rewrites missing local gsap paths to CDN before compile

Studio resilience:
- Error boundary with recoverable UI
- Lazy mediabunny import prevents crash cascade
- Hash routing listens for hashchange events
- Sub-composition duration reads data-hf-authored-duration fallback
- Save debounce 600ms to requestAnimationFrame

Observability:
- PostHog telemetry for crashes, save failures, tab switches, playback,
  toolbar actions, navigation, and render starts
2026-05-20 17:07:31 -04:00
ukimsanov db94b505dd feat(capture): identify hashed fonts via OpenType name table
Modern frameworks (Next.js, Webpack) hash font filenames like
`f9b8e1e8d4c3f0a7-s.woff2`, so the capture pipeline can't tell which
file belongs to which family by reading the filename. Sub-agents
authoring DESIGN.md were guessing or falling back to system fonts.

This adds `fontMetadataExtractor.ts`: reads the binary OpenType `name`
table via `fontkit`, identifies each downloaded font by its real
family name, and writes `capture/extracted/fonts-manifest.json` with
per-file metadata + per-family aggregates (weights, variable-font
axes, file counts).

- Canonicalizes static-weight family-name packaging: "Inter Medium"
  resolves to family "Inter" with weight 500, "Semi Bold" normalizes
  to "SemiBold", etc. Width modifiers ("Tight", "Condensed") are NOT
  stripped — they denote separate typographic families.
- Reads variable-font axes from `fvar` so a single .woff2 carrying a
  full weight range is identified as variable (e.g. "Inter (100-900
  variable)").
- Uses `@types/fontkit` properly (no `unknown` cast), with a
  Font/FontCollection type guard. fontkit API drift surfaces as a
  compile error rather than silent undefined.
- Wired into `capture/index.ts` after `downloadAndRewriteFonts` so it
  runs after fonts are already on disk. Non-fatal try/catch — capture
  succeeds even if extraction fails.

Tested against 9 captures: 132/132 fonts identified by real family
name, including hashed Next.js builds.
2026-05-20 13:55:07 -07:00
James da38de1b12 test+fix(telemetry): address PR review — dev-mode gate, session-storage dedupe, payload tests
Addresses review comments on #982:

- studio shouldTrack(): adds VITE_HYPERFRAMES_NO_TELEMETRY (mirrors CLI's
  HYPERFRAMES_NO_TELEMETRY) and import.meta.env.DEV gates so dev / CI
  studio builds don't pollute production telemetry. shouldTrack() is now
  exported for testability.
- App.tsx session dedupe: moves the once-per-session check from a useRef
  (which resets on HMR / remount) to sessionStorage via new
  hasFiredSessionStart / markSessionStartFired helpers in config.ts.
- studioRenderTelemetry.ts: documents why `workers` is intentionally
  omitted from emitStudioRenderError (studio renders don't accept a
  user-supplied worker count, so early failures genuinely don't know one).
- client.ts flush(): documents fire-and-forget no-retry design so future
  hands don't accidentally add retry logic that double-counts.

Tests:
- studioRenderTelemetry.test.ts (8 tests): perfPayload mapping for every
  RenderPerfSummary field, undefined-perf path, missing-extract path,
  zero-elapsed edge case, error event shape.
- studio/telemetry/events.test.ts (4 tests): pin event names
  (studio_session_start, studio_render_start) and payload shape.
- studio/telemetry/client.test.ts (9 tests): shouldTrack() returns false
  for non-phc_ key, opt-out, doNotTrack, build-time env, vite dev mode;
  memoization.
2026-05-20 14:53:38 -04:00
James 3cc4c82f9e refactor(cli): minimize studioServer.ts diff for telemetry wiring
Net diff is now +3 lines: import line and the two emit calls. Hoisted
startTime out of the inner try so the catch can use it without a separate
elapsed tracking variable.

Pre-existing complexity findings in studioServer.ts (generateThumbnail,
the startRender arrow) are now properly attributed as inherited rather
than new by CI fallow.
2026-05-20 14:53:38 -04:00
James 50ade616a8 refactor(cli): extract studio render telemetry helpers to own file
Moves StudioRenderOpts, memSnapshot, perfPayload, stagesPayload,
extractPayload, emitStudioRenderComplete, emitStudioRenderError to
packages/cli/src/server/studioRenderTelemetry.ts. studioServer.ts now
has a single-line import diff.

Localizes the change so fallow correctly attributes pre-existing
complexity findings in studioServer.ts (generateThumbnail, the
startRender arrow) as inherited rather than new.
2026-05-20 14:53:38 -04:00
James a2453c803d feat(telemetry): differentiate studio vs CLI renders, add studio frontend events
Adds 'source' property (cli|studio) to render_complete/render_error events,
makes studioServer.ts emit them for studio-triggered renders, and adds a
studio frontend telemetry module mirroring the CLI pattern.

studio_session_start and studio_render_start are emitted from the browser
as user-intent signals; completion stays server-side for unified rich
perf data. OSS-safe: no-op when VITE_HYPERFRAMES_POSTHOG_KEY is unset.
Opt-out via localStorage or navigator.doNotTrack.

Bypassed lefthook fallow check at commit time — it failed under lefthook
but passes standalone with the same args; all 3 reported findings are
pre-existing (audit gate excludes 4 inherited). CI will run the
authoritative check.
2026-05-20 14:53:38 -04:00
Miguel Ángel d64de2b84d chore: release v0.6.29 v0.6.29 2026-05-20 07:21:20 +00:00
Miguel Ángel d9157ef1ad feat: data-timeline-locked, fix sub-comp fonts, caption overlay UX (#981)
## Summary

### `data-timeline-locked` attribute
- Clips with this attribute are fully locked in the Studio timeline (no move, no trim-start, no trim-end)
- Parsed in `timelineDOM.ts`, checked in `getTimelineEditCapabilities`
- Runtime propagates the attribute from loaded sub-composition roots to host elements
- All 15 caption components carry the attribute on their composition root

### Locked composition child protection
- Elements inside a `data-timeline-locked` sub-composition cannot be moved, resized, or style-edited on the canvas — prevents "Unable to patch" errors for JS-generated content
- TEXT property panel (Content, Color, Size, Weight) is hidden for these elements
- Implemented via `isInsideLockedComposition` flag on `DomEditSelection`, checked in both `resolveDomEditCapabilities` and `isTextEditableSelection`

### Fix font loss in sub-compositions
- Both runtime (`compositionLoader.ts`) and compiler (`inlineSubCompositions.ts`, `htmlBundler.ts`, `htmlCompiler.ts`) now extract `<link rel="stylesheet">` and `<link rel="preconnect">` from sub-composition `<head>` alongside existing `<style>`/`<script>` extraction
- Fixes Google Fonts loaded via `<link>` tags being silently dropped when a component is used as a sub-composition

### Transparent caption overlays
- All 15 caption components: opaque backgrounds and dark rgba overlays replaced with `transparent`
- `pointer-events: none` added to composition roots so captions don't intercept clicks

### Caption catalog reference
- Table of all 15 caption components with style descriptions and CLI commands added to `skills/hyperframes/references/captions.md`

## Test plan

- [x] Open a composition with caption-highlight as sub-composition — font (Montserrat) renders correctly
- [x] Caption overlays transparently on the video (no black background)
- [x] Click on text inside a locked caption sub-composition — TEXT panel is hidden
- [x] Try to move/resize a caption element on canvas — blocked, no "Unable to patch" error
- [x] `bunx vitest run packages/studio/src/player/components/timelineEditing.test.ts` — 37 tests pass
- [x] In Studio timeline, verify a `data-timeline-locked` clip cannot be moved or trimmed
2026-05-20 09:06:02 +02:00
Miguel Ángel 3e17ef7447 fix: propagate data-timeline-locked in compiler inliners
The runtime path (compositionLoader.ts) already propagated
data-timeline-locked from inner root to host, but both compiler
inliners (bundler + producer) did not. Bundled output re-opened in
Studio would lose the lock. Now propagated in inlineSubCompositions
alongside the existing data-hf-authored-id propagation.
2026-05-20 02:40:49 -04:00
Miguel Ángel 9c159ad96d fix: escape href in compiler link dedup + add font-link extraction tests
Escape href values in querySelector calls for link dedup in both
htmlBundler.ts and htmlCompiler.ts to match the runtime path (which
uses CSS.escape). Prevents SyntaxError on hrefs containing quotes.

Add two tests for inlineSubCompositions font-link extraction:
- Verifies <link> elements are extracted with original rel + crossorigin
- Verifies dedup across multiple sub-compositions sharing the same font
2026-05-20 02:36:58 -04:00
Miguel Ángel 5d501a1628 fix: preserve original rel attribute on sub-composition link extraction
Store {href, rel, crossorigin} from source <link> elements instead of
re-deriving rel from a URL substring heuristic. Fixes preview-vs-render
parity: a stylesheet link whose href lacks ".css" or "css2?" was
emitted as preconnect in the compiled output, silently dropping the font.

Also documents that caption components ship with transparent backgrounds
intentionally — users add contrast layers in the host composition.
2026-05-20 02:33:40 -04:00
Miguel Ángel 576598f5ad feat(studio): disable move/resize/style editing for locked composition children
Elements inside a data-timeline-locked composition now have all
canvas interactions disabled (move, resize, manual offset/size/rotation,
style editing). Prevents "Unable to patch" errors when trying to move
JS-generated caption elements that can't be patched back to source.
2026-05-20 02:23:57 -04:00
Miguel Ángel ec66d59caa fix(registry): add pointer-events: none to caption component roots
Caption components are overlays — their root element should not
intercept pointer events, allowing clicks to pass through to the
underlying composition content in Studio.
2026-05-20 02:20:16 -04:00
Miguel Ángel 8371f42aed feat(studio): hide text panel for elements inside locked compositions
Elements inside a data-timeline-locked sub-composition now have the
TEXT property panel hidden. These elements are JS-generated — editing
them in the panel won't persist since the script rebuilds the DOM on
load. Uses findClosestByAttribute to detect the locked ancestor.
2026-05-20 02:13:25 -04:00
Miguel Ángel 335ec45bc2 style(registry): format caption component HTML files 2026-05-20 02:08:57 -04:00
Miguel Ángel 0187a82a6f fix(registry): transparent backgrounds on all caption components
Replace opaque backgrounds (#0a0a0a, #000, #000000) with transparent
on both html/body and the composition root div for all 15 caption
components. Captions are overlays — opaque backgrounds cover the
underlying video when loaded as sub-compositions.
2026-05-20 02:02:12 -04:00
Miguel Ángel 471b4efb4b feat: data-timeline-locked + fix font loss in sub-compositions
Timeline locking:
- Add data-timeline-locked attribute support — fully disables move,
  trim-start, and trim-end in Studio for clips that carry this attr
- Runtime propagates the attribute from inner composition root to host
  element so component authors control it from their HTML
- All 15 caption components in the registry now carry the attribute

Font fix:
- Extract <link rel="stylesheet"> and <link rel="preconnect"> from
  sub-composition <head> alongside existing <style>/<script> extraction
- Fixes caption components (and any sub-comp using Google Fonts via
  <link> tags) losing their font-family when loaded as sub-compositions
- Applied in both runtime (compositionLoader) and compiler
  (inlineSubCompositions) paths
2026-05-20 01:55:17 -04:00
Miguel Ángel 6d9670fd58 docs(skill): add pre-built caption component catalog to captions reference
Move catalog references from SKILL.md (reverted) into captions.md where
they're contextually relevant. Adds a table of all 15 caption components
with style descriptions and use-case guidance, plus CLI commands for
browsing and installing.
2026-05-20 01:38:04 -04:00
Miguel Ángel c9a94b5c45 Revert "docs(skill): add catalog references for blocks and components"
This reverts commit 552b9828f6.
2026-05-20 01:37:33 -04:00
Miguel Ángel 771abe8373 Revert "fix(skill): correct catalog table — fix caption-texture name, add 6 missing blocks"
This reverts commit 4e8d9f2a18.
2026-05-20 01:37:33 -04:00
Miguel Ángel 4e8d9f2a18 fix(skill): correct catalog table — fix caption-texture name, add 6 missing blocks
- caption-texture-lava → caption-texture (matches registry-item.json)
- Add transitions-destruction, transitions-other to Scene transitions
- Add vpn-youtube-spot to Social media cards
- Add blue-sweater-intro-video to Product & device showcases
- Add north-korea-locked-down, nyc-paris-flight as Narrative showcases row
- All 77 registry items now verified against registry/registry.json
2026-05-20 01:19:34 -04:00
Miguel Ángel 552b9828f6 docs(skill): add catalog references for blocks and components
Surface the full registry of pre-built blocks and components directly
in the main authoring skill so agents know what's available before
building from scratch. Adds CLI commands, use-case tables grouped by
category (transitions, social, data, maps, VFX, captions, overlays),
and quick-pick suggestions for common user requests.
2026-05-20 01:10:36 -04:00
James 07bcb4f73b fix(cli): stop dropping CI/agent telemetry, suppress HeyGen CI at workflow level
The CI=true early-exit in shouldTrack() was hiding most modern usage
(coding agents in Codespaces, CI pipelines, agent sandboxes). Remove it.
Each event still carries is_ci/is_docker/is_tty from system.ts, so CI vs
laptop traffic can be separated in PostHog without being dropped at
ingestion.

HeyGen's own CI is suppressed via HYPERFRAMES_NO_TELEMETRY=1 added to
each workflow that exercises the CLI.
2026-05-20 01:03:41 -04:00