--- name: lit-webview-architecture description: Design or refactor a Lit webview's package boundary, Effect service contract, host adapter, lifecycle, registration, or browser bundle. Use for VS Code webviews that separate browser-safe UI from extension-host APIs and message protocols. review: never --- # Lit webview architecture Boundary: UI owns presentation, browser-safe state/actions, and lifecycle coordination; host owns VS Code APIs, transport, metadata/network authorities, and localization. ## Browser-safe contract - Immutable view state, UI actions, and typed errors from Effect Schemas. Tagged/literal unions instead of loose primitives, enums, or duplicated constants when invalid states matter. - Directional host-to-UI and UI-to-host message schemas. Handler variant added with its behavior; exhaustive `Match`; no opposite-direction placeholder arms. - Unknown data validated at entry. DOM `CustomEvent`: structural `type`/`detail` guard, then Schema validation of `detail`; inherited DOM accessors do not form a plain schema struct. - Required localized labels injected by the host; no defaults hiding missing localization. - No VS Code, JSforce, extension service, or extension-message imports in the browser-safe package. Host data normalized and validated before UI state. - Only subpaths with known consumers exported; no root/testing barrels. Type-only dependencies in `devDependencies` with `import type`. ## Effect lifecycle - 1 service contract: initial state, state stream, typed action dispatch. Extension-owned live layer plus deterministic test layers. - 1 composition-root-owned scoped fiber per mounted webview: controller, queues, subscriptions, DOM listeners, finalizers. - Listeners/subscriptions acquired and released as scoped resources. Disconnect interrupts and awaits the session fiber. - Fiber-identity guard on finalizer cleanup; an old disconnect cannot clear a newer rapid reconnection. - Components only render state and dispatch intent—no runtimes, scattered `Effect.runFork`/`Effect.runPromise`, or host calls. ## Registration and bundling - Explicit host-called custom-element registration; every `customElements.define` guarded by `customElements.get`; no registration import side effects. - New entries built with shared browser esbuild config. IIFE retained while HTML loads a classic deferred script; ESM requires matching script/resource-rewrite changes. - Non-published UI workspaces marked `private` and excluded from publication/versioning. Contract tests scan source and bundles for host imports and Effect execution in presentation code. - Compile/bundle/test order expressed in Wireit; browser tests consume the intended emitted application. Examples: `packages/soql-builder-ui/src/application.ts`, `packages/soql-builder-ui/src/register.ts`, `packages/soql-builder-ui/test/package-contract.test.mjs`, `packages/salesforcedx-vscode-soql/src/soql-builder-ui/lit/`.