desktop Agent Skills & AI Rules

GitHub

Welcome to a calmer internet

44,902 stars C++ C++ 1 file ¡ ~133,466 tokens #firefox#firefox-based#firefox-browser#zen-browser
RAW Rules
4 Discovered Rules • ~3,061 Estimated Tokens
Quick:
Loading skill content...

Claude Agent Guidelines & System Prompt

CLAUDE.md claude-rule ~2,161 tokens
Raw
# CLAUDE.md

Guidance for AI agents working in this repository. Keep this file lean — it loads
into every session. Deep architecture lives in `ARCHITECTURE.md` (read it when a
change touches the window/service/state/IPC model).

This is **Streamlabs Desktop** (`slobs-client`): an Electron live‑streaming app
built on OBS. Large, old, and mid‑migration on two fronts (Vue → React for UI,
Vuex → Realm/React for state), with several non‑obvious invariants. Read the
relevant service before editing it; prefer targeted reads over broad assumptions.

## Commands

Package manager is **Yarn Berry (3.1.1)** — never use `npm`.

| Task                | Command                 | Notes                                                                                                                         |
| ------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Lint + format       | `yarn eslint`           | Prettier runs _through_ ESLint (`eslint-plugin-prettier`). `eslint --fix` formats.                                            |
| 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. |
| Iterative dev build | `yarn watch`            | Webpack watch; use this while developing.                                                                                     |
| One‑shot dev build  | `yarn compile`          | Slow: clears `bundles/media` and rebuilds everything. Don't run casually.                                                     |
| Run the app         | `yarn start`            | Launches Electron against the last build.                                                                                     |
| Single test file    | `yarn test:file <path>` | Compiles tests, runs one file.                                                                                                |
| Full test suite     | `yarn test`             | **Heavy/slow e2e** — see Testing below. Don't run unless asked.                                                               |

For a fast type check without a full build, run **`yarn typecheck`** — it runs
`tsc --noEmit` for the non‑React app (`tsconfig.json`) and for `app/components-react`
(its stricter config, `strictNullChecks: true`). This is the quickest way to verify
your changes; reserve `yarn compile` and the e2e suite for when you actually need
them. (The build still typechecks via `ts-loader`; tests compile via `tsc -p test`.)

## Code style

- TypeScript, formatted by Prettier via ESLint. **Don't memorize the rules** —
  let `yarn eslint` (`--fix`) apply them, and match the surrounding file.
- `strictNullChecks` is **intentionally OFF** globally. Do **not** enable it
  repo‑wide. A subset of files opts in via `strict-null-check-files/` +
  `SLOBS_STRICT_NULLS`; only add to that set deliberately.

## Architecture in one screen

Multi‑window Electron app. Every window runs the _same_ JS bundle but plays a
different role:

- **worker** — invisible, persistent renderer that runs the **entire services
  layer**. All service methods actually execute here.
- **main** — the primary UI window.
- **child** — kept warm in the background for things like Source Properties.
- plus transient **one‑off** windows (projectors, pop‑outs), apps, webviews.

The UI windows (main, child, one‑off) **don't run services** — they call them
remotely. A call from any non‑worker window is sent to the **Electron main
process** (`main.js`), which forwards it to the **worker** window, then routes the
result back to the originating window. Note: the _main process_ (`main.js`, Node)
is the router — **not** the _main window_, which is just another UI client.

**Services** (`app/services/`) are strict singletons holding all domain logic.
Normal application code reaches a service through the `@Inject()` decorator. They're
registered in `app/app-services.ts`.

## State — know which mechanism to use

State management is **mid‑migration**. For new code, choose by scope:

- **UI‑only state** → React state (`useState` / hooks), local to the component.
- **Service state that must sync across windows/processes** → **Realm**
  (`RealmObject` / `RealmService` in `app/services/realm.ts`), which replicates
  across all processes. React reads it via `app/components-react/hooks/realm.ts`.

Much existing service state still lives in **Vuex** via `StatefulService<TState>`
(read through `this.state`, mutated only via `@mutation()` methods). You'll
maintain it where it already exists, but **don't reach for Vuex for new state.**

See `ARCHITECTURE.md` for the full model and the _why_ behind the sharp edges.

## Hard rules (these are easy to get wrong)

1. **Vuex mutations are pure.** When editing an existing `StatefulService`, a
   `@mutation()` method may touch only `this.state` and its own arguments — no side
   effects, no calling other services, no async. In dev a Proxy enforces this and
   throws (`app/services/core/stateful-service.ts:49`).
2. **Calling a service from a UI window — pick the right form:**
   - `Service.actions.method()` — **default**. Async, fire‑and‑forget, returns
     `void`.
   - `Service.actions.return.method()` — async **with** a return value (resolves
     when the worker finishes). Use sparingly.
   - `Service.method()` (omit `.actions`) — **synchronous**; it blocks the calling
     UI process and logs a console warning. Avoid except where absolutely
     necessary — it degrades the user experience.
   - Reads: use `views` (Vuex) or read Realm objects directly.
3. **Register new services** in `app/app-services.ts` — it's hand‑maintained, not
   generated. A service that isn't registered won't resolve.
4. **New UI is React.** Functional components + hooks only, in
   `app/components-react/` (`.tsx`). Vue (`app/components/`, `.vue`) is legacy and
   frozen — only touch it to migrate a component to React.
5. **Don't edit generated/build output:** `bundles/`, `*.g.less`, `updater/build/`,
   `dist/`, `test-dist/`, `docs/dist/`.
6. **Honor in‑code warnings.** Respect `DO NOT CALL` / `@warning` markers — e.g.
   `app/services/sources/sources.ts` `updatePropertiesManagerSettingsInStore`,
   and the deprecated method in `app/services/video.ts`.

## Conventions

- Filenames map to classes: `foo-bar.ts` → `class FooBarService`.
- Common decorators: `@Inject()` (DI), `@mutation()` (Vuex mutation),
  `@InitAfter('OtherService')` (ordered init), `@InheritMutations()`.
- Service lifecycle hooks: `init()` (once per app) → `mounted()` (once per window)
  → `afterInit()`.
- Cross‑service / cross‑window events use **RxJS** `Subject`s.

## Alert type `apiKey` field naming

When adding a new alert type to `alerts-config.ts`, the `apiKey` field must match
the **backend field prefix**, not the internal alert type name. For example,
an alert previously had the alert type `twitter_subscription` in the repository
but because the backend returns settings with keys like `twitter_sub_image_href`,
not `twitter_subscription_image_href`, it requires `apiKey: 'twitter_sub'`,
not `twitter_subscription_*`. The variation field parser in `AlertBoxModule.setData()`
uses `${apiKey}_` prefix matching (see `app/components-react/widgets/useAlertBox.tsx`
line 207). Mismatching this causes variation settings to fail initialization silently,
leading to UI freezes. Also map internal alert types to API endpoints in
`WidgetsService.playAlert()` (`app/services/widgets/widgets.ts`) if the backend uses
a different name there.

## Testing

Tests are **integration/e2e via WebdriverIO** — they launch the real Electron app
and drive it, run **serially**, and are slow. They are not fast unit tests. Run a
single file with `yarn test:file <path>` when iterating; avoid the full `yarn test`
suite (and `yarn package`) unless explicitly asked.

## Where things live

| Path                                      | What                                                           |
| ----------------------------------------- | -------------------------------------------------------------- |
| `main.js`                                 | Electron main process: windows, IPC routing, updater, logging. |
| `app/app.ts`                              | Renderer bootstrap (services, i18n, Sentry).                   |
| `app/app-services.ts`                     | Central service registry.                                      |
| `app/services/`                           | All services (domain logic).                                   |
| `app/services/core/`                      | Service base, `StatefulService`, DI, mutations.                |
| `app/services/realm.ts`                   | Realm‑backed cross‑process state.                              |
| `app/services-manager.ts`                 | Service instantiation + IPC proxying.                          |
| `app/services/api/internal-api-client.ts` | Client side of cross‑window service calls.                     |
| `app/store/`                              | Vuex store + cross‑window mutation sync (legacy).              |
| `app/components-react/`                   | React UI (current).                                            |
| `app/components/`                         | Vue UI (legacy).                                               |
| `test/`                                   | e2e/stress/screen/performance tests + helpers.                 |

## Useful env vars (dev)

`SLOBS_REPORT_TO_SENTRY`, `SLOBS_PRODUCTION_DEBUG` (open dev tools on start),
`SLOBS_CACHE_DIR`, `SLOBS_FORCE_AUTO_UPDATE`, `SLOBS_STRICT_NULLS`. See README for
the full list.