@mica/sdk (SDK contract v1)
    Preparing search index...

    Function useNuiBridge

    @mica/sdk/core — the surface only a core: true app may import.

    useNuiBridge is the raw transport: any registered NUI callback, by name. An add-on with it can reach everything every other permission guards, so it is not on @mica/sdk — boundary.test.ts refuses it to anything core: false. The specifier is unresolvable out-of-tree for a Store-installed bundle, and since MICA-16 Step 4 that is also a runtime refusal: a core: false bundle runs in a sandboxed iframe with no NUI at all, only the postMessage protocol in sdk/host/iframe/. In-process, useNuiBridge is gated only by the static core:-only import check; it has no host-protocol permission row and is not guarded().

    useCaptureZoomBoost is here for the same reason, not because it needs the transport: it is a lever on the shell's own rendering with no legitimate reason for a sandboxed add-on to pull it.

    NowPlayingCard is the third kind: not dangerous, not general. It is the transport for the phone's own player, drawn by the Music app and by the notification shade, and it is the reason Music is core: true at all — the player is hardware. Putting it on the public @mica/sdk would make a screen-sized component with one subject into an API commitment to every add-on; putting it here keeps it reachable by the two things that draw it. It grants nothing on its own: it is presentational, and the stores it renders from are useMusic()'s, which is permission-gated as it always was.

    useCapabilities (MICA-169) is the server's requires answer, for the phone's own catalog to explain an app it would refuse. Harmless to disclose, but with no add-on asking for it — useCapabilities.ts has the reasoning and why it has no facet.

    The seed-theming set — DEFAULT_SEED, sanitizeSeed, seedFromRgbString, buildSchemes, cssVarBlock, backgroundForScheme — is the fourth kind, and it is here for cost rather than for danger. MICA-187 decided where it publishes, with the number from MICA-181 in hand: buildSchemes drags @material/material-color-utilities into whatever bundle can reach it, about 21 kB gzipped, and Rollup does not shake it back out of a core: false add-on that never calls it. @mica/sdk/core has no alias in vite.addon.config.ts at all, so nothing published here can enter an add-on bundle — MICA-187 rebuilt all four add-ons before and after this export landed and the bundles were byte-identical. The only consumer in the tree, NowPlayingCard, is already on this entry point, so a core app deriving a scheme from a different seed than the phone's pays for an engine the shell has loaded anyway. An add-on theming itself to the phone gets the current scheme through useTheme().schemeStore, which is what it actually needs; deriving one from an arbitrary seed stays off @mica/sdk until a real add-on asks and the 21 kB is weighed against it. host/seam/theme.ts keeps the longer account.

    • OS Service Hook for the FiveM NUI transport.

      core: true only — exported from @mica/sdk/core, not @mica/sdk. A core: false add-on runs in a sandboxed iframe with no NUI to bridge to; its build swaps fetchNui for the postMessage twin in sdk/host/iframe/fetchNui.ts instead of this hook.

      This used to return the imported functions directly, which made destructuring at module scope quietly wrong:

      const { fetchNui } = useNuiBridge();   // captures the transport as it is *right now*
      

      That snapshot is taken when the module is imported. A test that later installs a spy on the transport never sees it, so the store goes on talking to the real mock registry while the test asserts against its own stubs — and the failure reads as wrong fixture data rather than as a stale binding. It cost two passes to recognise while migrating Blabber's store, which is a good sign it would cost an add-on author more.

      A wrapper makes the wrapper the stable thing and re-reads the module binding on every call. So destructuring is now safe, at module scope or anywhere else — which is the point: the ergonomic move and the correct move should be the same one. Telling every app "do not destructure this" would be a rule waiting to be broken, and the SDK is exactly the layer that should absorb that rather than export it.

      The cost is one function call per transport call, against a round trip to the game.

      Returns {
          fetchNui: <T = unknown>(
              eventName: string,
              data?: unknown,
              options?: { defaultValue?: T },
          ) => Promise<T>;
          useNuiEvent: <T = unknown>(
              action: string,
              handler: (data: T) => void,
          ) => () => void;
      }