{"owner":"streamlabs","repo":"desktop","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nGuidance for AI agents working in this repository. Keep this file lean — it loads\ninto every session. Deep architecture lives in `ARCHITECTURE.md` (read it when a\nchange touches the window/service/state/IPC model).\n\nThis is **Streamlabs Desktop** (`slobs-client`): an Electron live‑streaming app\nbuilt on OBS. Large, old, and mid‑migration on two fronts (Vue → React for UI,\nVuex → Realm/React for state), with several non‑obvious invariants. Read the\nrelevant service before editing it; prefer targeted reads over broad assumptions.\n\n## Commands\n\nPackage manager is **Yarn Berry (3.1.1)** — never use `npm`.\n\n| Task | Command | Notes |\n| --- | --- | --- |\n| Lint + format | `yarn eslint` | Prettier runs *through* ESLint (`eslint-plugin-prettier`). `eslint --fix` formats. |\n| Typecheck | `yarn typecheck` | **Fast self-verify.** `tsc --noEmit` for the app + React. Use this to check changes instead of a full build or the e2e suite. |\n| Iterative dev build | `yarn watch` | Webpack watch; use this while developing. |\n| One‑shot dev build | `yarn compile` | Slow: clears `bundles/media` and rebuilds everything. Don't run casually. |\n| Run the app | `yarn start` | Launches Electron against the last build. |\n| Single test file | `yarn test:file <path>` | Compiles tests, runs one file. |\n| Full test suite | `yarn test` | **Heavy/slow e2e** — see Testing below. Don't run unless asked. |\n\nFor a fast type check without a full build, run **`yarn typecheck`** — it runs\n`tsc --noEmit` for the non‑React app (`tsconfig.json`) and for `app/components-react`\n(its stricter config, `strictNullChecks: true`). This is the quickest way to verify\nyour changes; reserve `yarn compile` and the e2e suite for when you actually need\nthem. (The build still typechecks via `ts-loader`; tests compile via `tsc -p test`.)\n\n## Code style\n\n- TypeScript, formatted by Prettier via ESLint. **Don't memorize the rules** —\n  let `yarn eslint` (`--fix`) apply them, and match the surrounding file.\n- `strictNullChecks` is **intentionally OFF** globally. Do **not** enable it\n  repo‑wide. A subset of files opts in via `strict-null-check-files/` +\n  `SLOBS_STRICT_NULLS`; only add to that set deliberately.\n\n## Architecture in one screen\n\nMulti‑window Electron app. Every window runs the *same* JS bundle but plays a\ndifferent role:\n\n- **worker** — invisible, persistent renderer that runs the **entire services\n  layer**. All service methods actually execute here.\n- **main** — the primary UI window.\n- **child** — kept warm in the background for things like Source Properties.\n- plus transient **one‑off** windows (projectors, pop‑outs), apps, webviews.\n\nThe UI windows (main, child, one‑off) **don't run services** — they call them\nremotely. A call from any non‑worker window is sent to the **Electron main\nprocess** (`main.js`), which forwards it to the **worker** window, then routes the\nresult back to the originating window. Note: the *main process* (`main.js`, Node)\nis the router — **not** the *main window*, which is just another UI client.\n\n**Services** (`app/services/`) are strict singletons holding all domain logic.\nNormal application code reaches a service through the `@Inject()` decorator. They're\nregistered in `app/app-services.ts`.\n\n## State — know which mechanism to use\n\nState management is **mid‑migration**. For new code, choose by scope:\n\n- **UI‑only state** → React state (`useState` / hooks), local to the component.\n- **Service state that must sync across windows/processes** → **Realm**\n  (`RealmObject` / `RealmService` in `app/services/realm.ts`), which replicates\n  across all processes. React reads it via `app/components-react/hooks/realm.ts`.\n\nMuch existing service state still lives in **Vuex** via `StatefulService<TState>`\n(read through `this.state`, mutated only via `@mutation()` methods). You'll\nmaintain it where it already exists, but **don't reach for Vuex for new state.**\n\nSee `ARCHITECTURE.md` for the full model and the *why* behind the sharp edges.\n\n## Hard rules (these are easy to get wrong)\n\n1. **Vuex mutations are pure.** When editing an existing `StatefulService`, a\n   `@mutation()` method may touch only `this.state` and its own arguments — no side\n   effects, no calling other services, no async. In dev a Proxy enforces this and\n   throws (`app/services/core/stateful-service.ts:49`).\n2. **Calling a service from a UI window — pick the right form:**\n   - `Service.actions.method()` — **default**. Async, fire‑and‑forget, returns\n     `void`.\n   - `Service.actions.return.method()` — async **with** a return value (resolves\n     when the worker finishes). Use sparingly.\n   - `Service.method()` (omit `.actions`) — **synchronous**; it blocks the calling\n     UI process and logs a console warning. Avoid except where absolutely\n     necessary — it degrades the user experience.\n   - Reads: use `views` (Vuex) or read Realm objects directly.\n3. **Register new services** in `app/app-services.ts` — it's hand‑maintained, not\n   generated. A service that isn't registered won't resolve.\n4. **New UI is React.** Functional components + hooks only, in\n   `app/components-react/` (`.tsx`). Vue (`app/components/`, `.vue`) is legacy and\n   frozen — only touch it to migrate a component to React.\n5. **Don't edit generated/build output:** `bundles/`, `*.g.less`, `updater/build/`,\n   `dist/`, `test-dist/`, `docs/dist/`.\n6. **Honor in‑code warnings.** Respect `DO NOT CALL` / `@warning` markers — e.g.\n   `app/services/sources/sources.ts` `updatePropertiesManagerSettingsInStore`,\n   and the deprecated method in `app/services/video.ts`.\n\n## Conventions\n\n- Filenames map to classes: `foo-bar.ts` → `class FooBarService`.\n- Common decorators: `@Inject()` (DI), `@mutation()` (Vuex mutation),\n  `@InitAfter('OtherService')` (ordered init), `@InheritMutations()`.\n- Service lifecycle hooks: `init()` (once per app) → `mounted()` (once per window)\n  → `afterInit()`.\n- Cross‑service / cross‑window events use **RxJS** `Subject`s.\n\n## Testing\n\nTests are **integration/e2e via WebdriverIO** — they launch the real Electron app\nand drive it, run **serially**, and are slow. They are not fast unit tests. Run a\nsingle file with `yarn test:file <path>` when iterating; avoid the full `yarn test`\nsuite (and `yarn package`) unless explicitly asked.\n\n## Where things live\n\n| Path | What |\n| --- | --- |\n| `main.js` | Electron main process: windows, IPC routing, updater, logging. |\n| `app/app.ts` | Renderer bootstrap (services, i18n, Sentry). |\n| `app/app-services.ts` | Central service registry. |\n| `app/services/` | All services (domain logic). |\n| `app/services/core/` | Service base, `StatefulService`, DI, mutations. |\n| `app/services/realm.ts` | Realm‑backed cross‑process state. |\n| `app/services-manager.ts` | Service instantiation + IPC proxying. |\n| `app/services/api/internal-api-client.ts` | Client side of cross‑window service calls. |\n| `app/store/` | Vuex store + cross‑window mutation sync (legacy). |\n| `app/components-react/` | React UI (current). |\n| `app/components/` | Vue UI (legacy). |\n| `test/` | e2e/stress/screen/performance tests + helpers. |\n\n## Useful env vars (dev)\n\n`SLOBS_REPORT_TO_SENTRY`, `SLOBS_PRODUCTION_DEBUG` (open dev tools on start),\n`SLOBS_CACHE_DIR`, `SLOBS_FORCE_AUTO_UPDATE`, `SLOBS_STRICT_NULLS`. See README for\nthe full list.\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nGuidance for AI agents working in this repository. Keep this file lean — it loads\ninto every session. Deep architecture lives in `ARCHITECTURE.md` (read it when a\nchange touches the window/service/state/IPC model).\n\nThis is **Streamlabs Desktop** (`slobs-client`): an Electron live‑streaming app\nbuilt on OBS. Large, old, and mid‑migration on two fronts (Vue → React for UI,\nVuex → Realm/React for state), with several non‑obvious invariants. Read the\nrelevant service before editing it; prefer targeted reads over broad assumptions.\n\n## Commands\n\nPackage manager is **Yarn Berry (3.1.1)** — never use `npm`.\n\n| Task | Command | Notes |\n| --- | --- | --- |\n| Lint + format | `yarn eslint` | Prettier runs *through* ESLint (`eslint-plugin-prettier`). `eslint --fix` formats. |\n| Typecheck | `yarn typecheck` | **Fast self-verify.** `tsc --noEmit` for the app + React. Use this to check changes instead of a full build or the e2e suite. |\n| Iterative dev build | `yarn watch` | Webpack watch; use this while developing. |\n| One‑shot dev build | `yarn compile` | Slow: clears `bundles/media` and rebuilds everything. Don't run casually. |\n| Run the app | `yarn start` | Launches Electron against the last build. |\n| Single test file | `yarn test:file <path>` | Compiles tests, runs one file. |\n| Full test suite | `yarn test` | **Heavy/slow e2e** — see Testing below. Don't run unless asked. |\n\nFor a fast type check without a full build, run **`yarn typecheck`** — it runs\n`tsc --noEmit` for the non‑React app (`tsconfig.json`) and for `app/components-react`\n(its stricter config, `strictNullChecks: true`). This is the quickest way to verify\nyour changes; reserve `yarn compile` and the e2e suite for when you actually need\nthem. (The build still typechecks via `ts-loader`; tests compile via `tsc -p test`.)\n\n## Code style\n\n- TypeScript, formatted by Prettier via ESLint. **Don't memorize the rules** —\n  let `yarn eslint` (`--fix`) apply them, and match the surrounding file.\n- `strictNullChecks` is **intentionally OFF** globally. Do **not** enable it\n  repo‑wide. A subset of files opts in via `strict-null-check-files/` +\n  `SLOBS_STRICT_NULLS`; only add to that set deliberately.\n\n## Architecture in one screen\n\nMulti‑window Electron app. Every window runs the *same* JS bundle but plays a\ndifferent role:\n\n- **worker** — invisible, persistent renderer that runs the **entire services\n  layer**. All service methods actually execute here.\n- **main** — the primary UI window.\n- **child** — kept warm in the background for things like Source Properties.\n- plus transient **one‑off** windows (projectors, pop‑outs), apps, webviews.\n\nThe UI windows (main, child, one‑off) **don't run services** — they call them\nremotely. A call from any non‑worker window is sent to the **Electron main\nprocess** (`main.js`), which forwards it to the **worker** window, then routes the\nresult back to the originating window. Note: the *main process* (`main.js`, Node)\nis the router — **not** the *main window*, which is just another UI client.\n\n**Services** (`app/services/`) are strict singletons holding all domain logic.\nNormal application code reaches a service through the `@Inject()` decorator. They're\nregistered in `app/app-services.ts`.\n\n## State — know which mechanism to use\n\nState management is **mid‑migration**. For new code, choose by scope:\n\n- **UI‑only state** → React state (`useState` / hooks), local to the component.\n- **Service state that must sync across windows/processes** → **Realm**\n  (`RealmObject` / `RealmService` in `app/services/realm.ts`), which replicates\n  across all processes. React reads it via `app/components-react/hooks/realm.ts`.\n\nMuch existing service state still lives in **Vuex** via `StatefulService<TState>`\n(read through `this.state`, mutated only via `@mutation()` methods). You'll\nmaintain it where it already exists, but **don't reach for Vuex for new state.**\n\nSee `ARCHITECTURE.md` for the full model and the *why* behind the sharp edges.\n\n## Hard rules (these are easy to get wrong)\n\n1. **Vuex mutations are pure.** When editing an existing `StatefulService`, a\n   `@mutation()` method may touch only `this.state` and its own arguments — no side\n   effects, no calling other services, no async. In dev a Proxy enforces this and\n   throws (`app/services/core/stateful-service.ts:49`).\n2. **Calling a service from a UI window — pick the right form:**\n   - `Service.actions.method()` — **default**. Async, fire‑and‑forget, returns\n     `void`.\n   - `Service.actions.return.method()` — async **with** a return value (resolves\n     when the worker finishes). Use sparingly.\n   - `Service.method()` (omit `.actions`) — **synchronous**; it blocks the calling\n     UI process and logs a console warning. Avoid except where absolutely\n     necessary — it degrades the user experience.\n   - Reads: use `views` (Vuex) or read Realm objects directly.\n3. **Register new services** in `app/app-services.ts` — it's hand‑maintained, not\n   generated. A service that isn't registered won't resolve.\n4. **New UI is React.** Functional components + hooks only, in\n   `app/components-react/` (`.tsx`). Vue (`app/components/`, `.vue`) is legacy and\n   frozen — only touch it to migrate a component to React.\n5. **Don't edit generated/build output:** `bundles/`, `*.g.less`, `updater/build/`,\n   `dist/`, `test-dist/`, `docs/dist/`.\n6. **Honor in‑code warnings.** Respect `DO NOT CALL` / `@warning` markers — e.g.\n   `app/services/sources/sources.ts` `updatePropertiesManagerSettingsInStore`,\n   and the deprecated method in `app/services/video.ts`.\n\n## Conventions\n\n- Filenames map to classes: `foo-bar.ts` → `class FooBarService`.\n- Common decorators: `@Inject()` (DI), `@mutation()` (Vuex mutation),\n  `@InitAfter('OtherService')` (ordered init), `@InheritMutations()`.\n- Service lifecycle hooks: `init()` (once per app) → `mounted()` (once per window)\n  → `afterInit()`.\n- Cross‑service / cross‑window events use **RxJS** `Subject`s.\n\n## Testing\n\nTests are **integration/e2e via WebdriverIO** — they launch the real Electron app\nand drive it, run **serially**, and are slow. They are not fast unit tests. Run a\nsingle file with `yarn test:file <path>` when iterating; avoid the full `yarn test`\nsuite (and `yarn package`) unless explicitly asked.\n\n## Where things live\n\n| Path | What |\n| --- | --- |\n| `main.js` | Electron main process: windows, IPC routing, updater, logging. |\n| `app/app.ts` | Renderer bootstrap (services, i18n, Sentry). |\n| `app/app-services.ts` | Central service registry. |\n| `app/services/` | All services (domain logic). |\n| `app/services/core/` | Service base, `StatefulService`, DI, mutations. |\n| `app/services/realm.ts` | Realm‑backed cross‑process state. |\n| `app/services-manager.ts` | Service instantiation + IPC proxying. |\n| `app/services/api/internal-api-client.ts` | Client side of cross‑window service calls. |\n| `app/store/` | Vuex store + cross‑window mutation sync (legacy). |\n| `app/components-react/` | React UI (current). |\n| `app/components/` | Vue UI (legacy). |\n| `test/` | e2e/stress/screen/performance tests + helpers. |\n\n## Useful env vars (dev)\n\n`SLOBS_REPORT_TO_SENTRY`, `SLOBS_PRODUCTION_DEBUG` (open dev tools on start),\n`SLOBS_CACHE_DIR`, `SLOBS_FORCE_AUTO_UPDATE`, `SLOBS_STRICT_NULLS`. See README for\nthe full list.\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nGuidance for AI agents working in this repository. Keep this file lean — it loads\ninto every session. Deep architecture lives in `ARCHITECTURE.md` (read it when a\nchange touches the window/service/state/IPC model).\n\nThis is **Streamlabs Desktop** (`slobs-client`): an Electron live‑streaming app\nbuilt on OBS. Large, old, and mid‑migration on two fronts (Vue → React for UI,\nVuex → Realm/React for state), with several non‑obvious invariants. Read the\nrelevant service before editing it; prefer targeted reads over broad assumptions.\n\n## Commands\n\nPackage manager is **Yarn Berry (3.1.1)** — never use `npm`.\n\n| Task | Command | Notes |\n| --- | --- | --- |\n| Lint + format | `yarn eslint` | Prettier runs *through* ESLint (`eslint-plugin-prettier`). `eslint --fix` formats. |\n| Typecheck | `yarn typecheck` | **Fast self-verify.** `tsc --noEmit` for the app + React. Use this to check changes instead of a full build or the e2e suite. |\n| Iterative dev build | `yarn watch` | Webpack watch; use this while developing. |\n| One‑shot dev build | `yarn compile` | Slow: clears `bundles/media` and rebuilds everything. Don't run casually. |\n| Run the app | `yarn start` | Launches Electron against the last build. |\n| Single test file | `yarn test:file <path>` | Compiles tests, runs one file. |\n| Full test suite | `yarn test` | **Heavy/slow e2e** — see Testing below. Don't run unless asked. |\n\nFor a fast type check without a full build, run **`yarn typecheck`** — it runs\n`tsc --noEmit` for the non‑React app (`tsconfig.json`) and for `app/components-react`\n(its stricter config, `strictNullChecks: true`). This is the quickest way to verify\nyour changes; reserve `yarn compile` and the e2e suite for when you actually need\nthem. (The build still typechecks via `ts-loader`; tests compile via `tsc -p test`.)\n\n## Code style\n\n- TypeScript, formatted by Prettier via ESLint. **Don't memorize the rules** —\n  let `yarn eslint` (`--fix`) apply them, and match the surrounding file.\n- `strictNullChecks` is **intentionally OFF** globally. Do **not** enable it\n  repo‑wide. A subset of files opts in via `strict-null-check-files/` +\n  `SLOBS_STRICT_NULLS`; only add to that set deliberately.\n\n## Architecture in one screen\n\nMulti‑window Electron app. Every window runs the *same* JS bundle but plays a\ndifferent role:\n\n- **worker** — invisible, persistent renderer that runs the **entire services\n  layer**. All service methods actually execute here.\n- **main** — the primary UI window.\n- **child** — kept warm in the background for things like Source Properties.\n- plus transient **one‑off** windows (projectors, pop‑outs), apps, webviews.\n\nThe UI windows (main, child, one‑off) **don't run services** — they call them\nremotely. A call from any non‑worker window is sent to the **Electron main\nprocess** (`main.js`), which forwards it to the **worker** window, then routes the\nresult back to the originating window. Note: the *main process* (`main.js`, Node)\nis the router — **not** the *main window*, which is just another UI client.\n\n**Services** (`app/services/`) are strict singletons holding all domain logic.\nNormal application code reaches a service through the `@Inject()` decorator. They're\nregistered in `app/app-services.ts`.\n\n## State — know which mechanism to use\n\nState management is **mid‑migration**. For new code, choose by scope:\n\n- **UI‑only state** → React state (`useState` / hooks), local to the component.\n- **Service state that must sync across windows/processes** → **Realm**\n  (`RealmObject` / `RealmService` in `app/services/realm.ts`), which replicates\n  across all processes. React reads it via `app/components-react/hooks/realm.ts`.\n\nMuch existing service state still lives in **Vuex** via `StatefulService<TState>`\n(read through `this.state`, mutated only via `@mutation()` methods). You'll\nmaintain it where it already exists, but **don't reach for Vuex for new state.**\n\nSee `ARCHITECTURE.md` for the full model and the *why* behind the sharp edges.\n\n## Hard rules (these are easy to get wrong)\n\n1. **Vuex mutations are pure.** When editing an existing `StatefulService`, a\n   `@mutation()` method may touch only `this.state` and its own arguments — no side\n   effects, no calling other services, no async. In dev a Proxy enforces this and\n   throws (`app/services/core/stateful-service.ts:49`).\n2. **Calling a service from a UI window — pick the right form:**\n   - `Service.actions.method()` — **default**. Async, fire‑and‑forget, returns\n     `void`.\n   - `Service.actions.return.method()` — async **with** a return value (resolves\n     when the worker finishes). Use sparingly.\n   - `Service.method()` (omit `.actions`) — **synchronous**; it blocks the calling\n     UI process and logs a console warning. Avoid except where absolutely\n     necessary — it degrades the user experience.\n   - Reads: use `views` (Vuex) or read Realm objects directly.\n3. **Register new services** in `app/app-services.ts` — it's hand‑maintained, not\n   generated. A service that isn't registered won't resolve.\n4. **New UI is React.** Functional components + hooks only, in\n   `app/components-react/` (`.tsx`). Vue (`app/components/`, `.vue`) is legacy and\n   frozen — only touch it to migrate a component to React.\n5. **Don't edit generated/build output:** `bundles/`, `*.g.less`, `updater/build/`,\n   `dist/`, `test-dist/`, `docs/dist/`.\n6. **Honor in‑code warnings.** Respect `DO NOT CALL` / `@warning` markers — e.g.\n   `app/services/sources/sources.ts` `updatePropertiesManagerSettingsInStore`,\n   and the deprecated method in `app/services/video.ts`.\n\n## Conventions\n\n- Filenames map to classes: `foo-bar.ts` → `class FooBarService`.\n- Common decorators: `@Inject()` (DI), `@mutation()` (Vuex mutation),\n  `@InitAfter('OtherService')` (ordered init), `@InheritMutations()`.\n- Service lifecycle hooks: `init()` (once per app) → `mounted()` (once per window)\n  → `afterInit()`.\n- Cross‑service / cross‑window events use **RxJS** `Subject`s.\n\n## Testing\n\nTests are **integration/e2e via WebdriverIO** — they launch the real Electron app\nand drive it, run **serially**, and are slow. They are not fast unit tests. Run a\nsingle file with `yarn test:file <path>` when iterating; avoid the full `yarn test`\nsuite (and `yarn package`) unless explicitly asked.\n\n## Where things live\n\n| Path | What |\n| --- | --- |\n| `main.js` | Electron main process: windows, IPC routing, updater, logging. |\n| `app/app.ts` | Renderer bootstrap (services, i18n, Sentry). |\n| `app/app-services.ts` | Central service registry. |\n| `app/services/` | All services (domain logic). |\n| `app/services/core/` | Service base, `StatefulService`, DI, mutations. |\n| `app/services/realm.ts` | Realm‑backed cross‑process state. |\n| `app/services-manager.ts` | Service instantiation + IPC proxying. |\n| `app/services/api/internal-api-client.ts` | Client side of cross‑window service calls. |\n| `app/store/` | Vuex store + cross‑window mutation sync (legacy). |\n| `app/components-react/` | React UI (current). |\n| `app/components/` | Vue UI (legacy). |\n| `test/` | e2e/stress/screen/performance tests + helpers. |\n\n## Useful env vars (dev)\n\n`SLOBS_REPORT_TO_SENTRY`, `SLOBS_PRODUCTION_DEBUG` (open dev tools on start),\n`SLOBS_CACHE_DIR`, `SLOBS_FORCE_AUTO_UPDATE`, `SLOBS_STRICT_NULLS`. See README for\nthe full list.\n","category":"root","tokens":1827}]}