The bridge
Why native commands use services and schemas.
The WebView and native shell run in different runtimes. Capacitor transports calls; Radical's Bridge service gives those calls an Effect error contract.
sequenceDiagram
participant App as App Effect
participant Client as callPlugin
participant Bridge as BridgeLive
participant Native as Capacitor native plugin
App->>Client: Command arguments
Client->>Client: Encode args schema and validate object
Client->>Bridge: call(plugin, method, options)
Bridge->>Native: nativePromise(plugin, method, options)
alt Native resolves
Native-->>Client: Unknown result
Client->>Client: Decode result schema
Client-->>App: Typed result or BridgeDecodeError
else Native rejects
Native-->>Bridge: Rejection
Bridge-->>Client: BridgeCallFailed
Client->>Client: Decode declared code and error data
Client-->>App: Declared tagged error or BridgeCallFailed
end
Invalid arguments fail before transport. A missing native bridge produces BridgeUnavailable. Unknown rejection codes and invalid error data remain BridgeCallFailed, so an unexpected failure cannot masquerade as a declared capability error.
Runtime selection stays replaceable
Capability operations select native commands or browser services when the Effect runs. Browser services use web APIs; default Google sign-in and native push have no browser equivalent.
Injectable layers let tests replace the bridge and browser services without a native project. React hooks start Effects after mount and release owned work on unmount. See Client reference for services and errors, or Plugin manifests for command schemas.