> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lodemc.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Internals

> How the app runs all this. For maintainers.

<Note>
  For maintainers of the app. Expansion authors do not need any of this.
</Note>

## Shape

```
MAIN (trusted)                                RENDERER (the app window)
backend/controllers/sdk/                      renderer/src/sdk/
  manifest.ts  meta.json + config.json          host.js      lifecycle, logs, event fan-out
  resolve.ts   the import resolver              runtime/     evaluation, hidden globals, tracker
  libs.ts      lib/*/lib.ts discovery           sdk/         the `sdk` module
  css.ts       scoping and CSS modules          ui/          the `sdk/ui` module, error boundary
  graph.ts     transpile + module table         registry.js  components, by qualified id
  verify.ts    NMCrate checks
  types.ts     .ldstudio/ type files
  index.ts     records, grants, disabled
```

**Main never runs expansion code.** It validates, verifies, compiles and resolves, then hands the renderer a table of modules. The renderer evaluates it.

## Main

* `readManifest` collects every problem before throwing, and flags an incompatible `sdkVersion` separately so that message is never buried.
* `buildGraph` walks from the start file. Babel (already bundled for legacy modulars) strips types and compiles JSX with `retainLines`, which is why error cards can name the author's line without a source map. Requires are collected from the **output AST**, so a string containing `require(...)` is not mistaken for an import.
* Records are `{ kind: source | json | css | asset, hash, deps, … }`, plain data that crosses IPC. `hash` is what the renderer diffs on reload.
* `TranspileCache` keys on `sha1(transpiler version + extension + content)`, in memory and on disk under `userData/sdk-cache`.
* `verifyExpansionKey` asks `/api/expansions/verify`; on its deliberately uniform 403 it asks `/api/secrets/verify` (needs `SECRETS_API_KEY`) to tell "unpublished" from "disabled or someone else's key". Legacy `main.js` loading uses the same function.
* Grants and the disabled list live in `userData/sdk-expansions.json`.
* `writeSdkTypes` writes `.ldstudio/sdk.d.ts`, `react.d.ts` (unless the developer has `@types/react`) and `tsconfig.libs.json`. The dot-folder is ignored by the watcher, so writing never triggers a reload.

## Renderer

* `ModuleRuntime` compiles each module as a function whose parameters shadow every global except an allowlist of standard JavaScript, then passes `undefined` for them. Endowments (`console`, timers, `fetch` when granted) are passed by name. This is a contract boundary, not a sandbox.
* `Tracker` holds a disposer per registration and runs them newest first on teardown.
* `host.js` owns start, reload, stop, per-expansion logs, app-event fan-out and the statuses the Expansions page renders. `activate()` decides what a default export is: class, object, function or nothing.
* `registry.js` holds components by qualified id. `createModularComponent` checks it first, which is why every existing mount point (pages, editors, creators, injections, item-editor modules) renders SDK components without knowing they exist.
* Hot reload re-evaluates changed modules, their importers, and every module importing `sdk`, so registrations come back after teardown. CSS keeps a `<style>` per module path, replaced in place.

## IPC

| Channel                   | Direction       |                                                  |
| ------------------------- | --------------- | ------------------------------------------------ |
| `sdk:list`                | renderer → main | the known expansions                             |
| `sdk:load`                | renderer → main | build and return a module graph                  |
| `sdk:grant`, `sdk:revoke` | renderer → main | permission grants                                |
| `sdk:set-enabled`         | renderer → main | disable / enable                                 |
| `sdk:changed`             | main → renderer | an expansion's files changed, or its record did  |
| `sdk:app-event`           | main → renderer | an app event, forwarded so SDK listeners hear it |

## Adding an SDK point

<Steps>
  <Step title="Implement it">
    Implement it in `renderer/src/sdk/sdk/createSdk.js`. Register through the existing app registry or `postMessage`, and give the tracker a disposer.
  </Step>

  <Step title="Gate it">
    Gate it with `need('permission', 'point.name')` if it touches user data, and add the permission to `src/shared/sdk/permissions.js` with a plain-language label.
  </Step>

  <Step title="Declare it">
    Declare it in `packages/sdk-types/index.d.ts`.
  </Step>

  <Step title="Document it">
    Document it in the [`sdk` reference](/sdk-reference), and in [migration](/migration) if it replaces a legacy call.
  </Step>

  <Step title="Test it">
    Add a test in `tests/unit/sdk/createSdk.test.jsx`, including that teardown undoes it.
  </Step>
</Steps>

Bump `SDK_VERSION` in `src/shared/sdk/version.js`: minor for an addition, major for a change in shape or a removal.

## Tests

`tests/unit/sdk/` covers the manifest, resolver, libraries, CSS scoping, graph and runtime (including circular imports, hidden globals and hot-reload dirtiness), the `sdk` module, the host lifecycle, verification and the generated type files. Every shipped example and template is built as part of the suite, so an example that stops loading fails CI.

## Still missing

Export formats and server reload commands (legacy-only today), a CLI, background scripts, the NMCrate library system, and hard isolation. See the repository's plan notes.
