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.
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.)
- TypeScript, formatted by Prettier via ESLint. Don't memorize the rules —
let
yarn eslint(--fix) apply them, and match the surrounding file. strictNullChecksis intentionally OFF globally. Do not enable it repo‑wide. A subset of files opts in viastrict-null-check-files/+SLOBS_STRICT_NULLS; only add to that set deliberately.
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 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/RealmServiceinapp/services/realm.ts), which replicates across all processes. React reads it viaapp/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.
- Vuex mutations are pure. When editing an existing
StatefulService, a@mutation()method may touch onlythis.stateand 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). - Calling a service from a UI window — pick the right form:
Service.actions.method()— default. Async, fire‑and‑forget, returnsvoid.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.
- Register new services in
app/app-services.ts— it's hand‑maintained, not generated. A service that isn't registered won't resolve. - 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. - Don't edit generated/build output:
bundles/,*.g.less,updater/build/,dist/,test-dist/,docs/dist/. - Honor in‑code warnings. Respect
DO NOT CALL/@warningmarkers — e.g.app/services/sources/sources.tsupdatePropertiesManagerSettingsInStore, and the deprecated method inapp/services/video.ts.
- 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
Subjects.
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.
| 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. |
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.