@salesforce/vite-plugin-lwc-ui-bundle 1.133.0 → 1.133.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,9 +31,9 @@ function gql(strings, ...values) {
31
31
  async function runQuery(config) {
32
32
  const { query, variables } = config;
33
33
  if (!query) return { data: void 0, errors: void 0 };
34
- if (typeof globalThis.__sfdc_sdk__ === "object" && typeof globalThis.__sfdc_sdk__?.graphql === "function") {
35
- const sdkGraphql = globalThis.__sfdc_sdk__.graphql;
36
- const result = await sdkGraphql({ query, variables: variables ?? {} });
34
+ const sdkRef = globalThis.__sfdc_sdk__;
35
+ if (sdkRef && typeof sdkRef.graphql === "function") {
36
+ const result = await sdkRef.graphql({ query, variables: variables ?? {} });
37
37
  return { data: result?.data, errors: result?.errors };
38
38
  }
39
39
  const sdk = await getChatSDK();
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sources":["../../../src/providers/shared/normalize-mcp-response.ts","../../../src/providers/lightning-graphql/runtime.ts"],"sourcesContent":["/**\n * Copyright (c) 2026, Salesforce, Inc.,\n * All rights reserved.\n * For full license text, see the LICENSE.txt file\n */\n\n/**\n * Unwraps the MCP tool transport envelope and returns the tool's payload as-is.\n *\n * Handles the three surface shapes `sdk.callTool()` can resolve with:\n * - MCP Apps surface: `{ structuredContent, content }`\n * - OpenAI surface: `{ result: \"<JSON string>\" }`, where the JSON may itself be\n * an MCP content array (`[{ type: 'text', text: \"<JSON string>\" }, ...]`)\n * - Fallback: the raw value returned by `callTool`\n *\n * The shape of the unwrapped payload is the tool's responsibility — this helper\n * does not project out `data` / `error` / `errors`. Callers read whichever keys\n * their tool contract defines.\n */\nexport function normalizeMcpResponse(raw: unknown): unknown {\n\tif (raw && typeof raw === \"object\" && \"structuredContent\" in raw) {\n\t\treturn (raw as { structuredContent: unknown }).structuredContent;\n\t}\n\n\tif (raw && typeof raw === \"object\" && typeof (raw as { result?: unknown }).result === \"string\") {\n\t\tconst parsed = JSON.parse((raw as { result: string }).result);\n\t\tif (Array.isArray(parsed)) {\n\t\t\tconst textBlock = parsed.find(\n\t\t\t\t(b: { type?: string; text?: string }) =>\n\t\t\t\t\tb && b.type === \"text\" && typeof b.text === \"string\",\n\t\t\t);\n\t\t\tconst text = textBlock ? textBlock.text : null;\n\t\t\tif (text) {\n\t\t\t\treturn JSON.parse(text);\n\t\t\t}\n\t\t\treturn {};\n\t\t}\n\t\treturn parsed;\n\t}\n\n\treturn raw ?? {};\n}\n","/**\n * Copyright (c) 2026, Salesforce, Inc.,\n * All rights reserved.\n * For full license text, see the LICENSE.txt file\n */\nimport { getChatSDK } from \"@salesforce/sdk-chat\";\nimport { normalizeMcpResponse } from \"../shared/normalize-mcp-response\";\n\n// Overwritten at plugin-load time by the `lightningGraphql` Vite shim, which\n// appends `TOOL_NAME = \"<configured-name>\";` to the bundled output. The initial\n// value is the default.\n// eslint-disable-next-line prefer-const\nexport let TOOL_NAME = \"graphqlQuery\";\n\ninterface GraphqlConfig {\n\tquery?: string;\n\tvariables?: Record<string, unknown>;\n}\n\ninterface GraphqlResult {\n\tdata?: unknown;\n\terrors?: { message: string }[];\n}\n\ntype WireCallback = (result: {\n\tdata: unknown;\n\terrors: { message: string }[] | undefined;\n\trefresh: () => Promise<void>;\n}) => void;\n\nexport function gql(strings: TemplateStringsArray, ...values: unknown[]): string {\n\tlet result = \"\";\n\tstrings.forEach((string, i) => {\n\t\tresult += string;\n\t\tif (i < values.length) result += String(values[i]);\n\t});\n\treturn result;\n}\n\nasync function runQuery(config: GraphqlConfig): Promise<GraphqlResult> {\n\tconst { query, variables } = config;\n\tif (!query) return { data: undefined, errors: undefined };\n\n\t// 1. UIBundle / local dev: use globalThis.__sfdc_sdk__.graphql if available\n\tif (\n\t\ttypeof (globalThis as Record<string, unknown>).__sfdc_sdk__ === \"object\" &&\n\t\ttypeof ((globalThis as Record<string, unknown>).__sfdc_sdk__ as Record<string, unknown>)\n\t\t\t?.graphql === \"function\"\n\t) {\n\t\tconst sdkGraphql = (\n\t\t\t(globalThis as Record<string, unknown>).__sfdc_sdk__ as Record<string, unknown>\n\t\t).graphql as (args: {\n\t\t\tquery: string;\n\t\t\tvariables: Record<string, unknown>;\n\t\t}) => Promise<GraphqlResult>;\n\t\tconst result = await sdkGraphql({ query, variables: variables ?? {} });\n\t\treturn { data: result?.data, errors: result?.errors };\n\t}\n\n\t// 2. MCP surface: use getChatSDK().callTool\n\tconst sdk = await getChatSDK();\n\tif (typeof sdk.callTool !== \"function\") {\n\t\tthrow new Error(\n\t\t\t\"[lightning/graphql] No data surface available. \" +\n\t\t\t\t\"Either initialise globalThis.__sfdc_sdk__ with createDataSDK, \" +\n\t\t\t\t\"or run inside a ChatGPT / MCP Apps context.\",\n\t\t);\n\t}\n\n\tconst raw = await sdk.callTool({\n\t\ttoolName: TOOL_NAME,\n\t\tparams: { query, variables: variables ?? {} },\n\t});\n\n\treturn (normalizeMcpResponse(raw) as GraphqlResult) ?? {};\n}\n\nexport class graphql {\n\t_dataCallback: WireCallback;\n\t_config: GraphqlConfig | undefined;\n\n\tconstructor(dataCallback: WireCallback) {\n\t\tthis._dataCallback = dataCallback;\n\t}\n\n\tconnect() {\n\t\tthis._fetch();\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-empty-function\n\tdisconnect() {}\n\n\tupdate(config: GraphqlConfig) {\n\t\tthis._config = config;\n\t\tthis._fetch();\n\t}\n\n\trefresh() {\n\t\treturn this._fetch();\n\t}\n\n\tasync _fetch() {\n\t\ttry {\n\t\t\tconst result = await runQuery(this._config ?? {});\n\t\t\tthis._emit(result);\n\t\t} catch (error) {\n\t\t\tthis._emit({\n\t\t\t\tdata: undefined,\n\t\t\t\terrors: [{ message: (error as Error).message }],\n\t\t\t});\n\t\t}\n\t}\n\n\t_emit({ data, errors }: GraphqlResult) {\n\t\tthis._dataCallback({\n\t\t\tdata,\n\t\t\terrors: errors?.length ? errors : undefined,\n\t\t\trefresh: () => this.refresh(),\n\t\t});\n\t}\n}\n\nexport async function executeMutation(config: GraphqlConfig): Promise<GraphqlResult> {\n\tif (!config?.query) return { data: undefined, errors: [{ message: \"No query provided\" }] };\n\ttry {\n\t\treturn await runQuery(config);\n\t} catch (error) {\n\t\treturn { data: undefined, errors: [{ message: (error as Error).message }] };\n\t}\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.js","sources":["../../../src/providers/shared/normalize-mcp-response.ts","../../../src/providers/lightning-graphql/runtime.ts"],"sourcesContent":["/**\n * Copyright (c) 2026, Salesforce, Inc.,\n * All rights reserved.\n * For full license text, see the LICENSE.txt file\n */\n\n/**\n * Unwraps the MCP tool transport envelope and returns the tool's payload as-is.\n *\n * Handles the three surface shapes `sdk.callTool()` can resolve with:\n * - MCP Apps surface: `{ structuredContent, content }`\n * - OpenAI surface: `{ result: \"<JSON string>\" }`, where the JSON may itself be\n * an MCP content array (`[{ type: 'text', text: \"<JSON string>\" }, ...]`)\n * - Fallback: the raw value returned by `callTool`\n *\n * The shape of the unwrapped payload is the tool's responsibility — this helper\n * does not project out `data` / `error` / `errors`. Callers read whichever keys\n * their tool contract defines.\n */\nexport function normalizeMcpResponse(raw: unknown): unknown {\n\tif (raw && typeof raw === \"object\" && \"structuredContent\" in raw) {\n\t\treturn (raw as { structuredContent: unknown }).structuredContent;\n\t}\n\n\tif (raw && typeof raw === \"object\" && typeof (raw as { result?: unknown }).result === \"string\") {\n\t\tconst parsed = JSON.parse((raw as { result: string }).result);\n\t\tif (Array.isArray(parsed)) {\n\t\t\tconst textBlock = parsed.find(\n\t\t\t\t(b: { type?: string; text?: string }) =>\n\t\t\t\t\tb && b.type === \"text\" && typeof b.text === \"string\",\n\t\t\t);\n\t\t\tconst text = textBlock ? textBlock.text : null;\n\t\t\tif (text) {\n\t\t\t\treturn JSON.parse(text);\n\t\t\t}\n\t\t\treturn {};\n\t\t}\n\t\treturn parsed;\n\t}\n\n\treturn raw ?? {};\n}\n","/**\n * Copyright (c) 2026, Salesforce, Inc.,\n * All rights reserved.\n * For full license text, see the LICENSE.txt file\n */\nimport { getChatSDK } from \"@salesforce/sdk-chat\";\nimport { normalizeMcpResponse } from \"../shared/normalize-mcp-response\";\n\n// Overwritten at plugin-load time by the `lightningGraphql` Vite shim, which\n// appends `TOOL_NAME = \"<configured-name>\";` to the bundled output. The initial\n// value is the default.\n// eslint-disable-next-line prefer-const\nexport let TOOL_NAME = \"graphqlQuery\";\n\ninterface GraphqlConfig {\n\tquery?: string;\n\tvariables?: Record<string, unknown>;\n}\n\ninterface GraphqlResult {\n\tdata?: unknown;\n\terrors?: { message: string }[];\n}\n\ntype WireCallback = (result: {\n\tdata: unknown;\n\terrors: { message: string }[] | undefined;\n\trefresh: () => Promise<void>;\n}) => void;\n\nexport function gql(strings: TemplateStringsArray, ...values: unknown[]): string {\n\tlet result = \"\";\n\tstrings.forEach((string, i) => {\n\t\tresult += string;\n\t\tif (i < values.length) result += String(values[i]);\n\t});\n\treturn result;\n}\n\nasync function runQuery(config: GraphqlConfig): Promise<GraphqlResult> {\n\tconst { query, variables } = config;\n\tif (!query) return { data: undefined, errors: undefined };\n\n\t// 1. UIBundle / local dev: use globalThis.__sfdc_sdk__.graphql if available.\n\t// Invoke as a method (not a destructured reference) so class-based SDK\n\t// implementations keep their `this` binding some SDKs implement\n\t// `graphql` as a method that calls `this.fetch(...)` internally.\n\tconst sdkRef = (globalThis as Record<string, unknown>).__sfdc_sdk__ as\n\t\t| {\n\t\t\t\tgraphql?: (args: {\n\t\t\t\t\tquery: string;\n\t\t\t\t\tvariables: Record<string, unknown>;\n\t\t\t\t}) => Promise<GraphqlResult>;\n\t\t }\n\t\t| undefined;\n\tif (sdkRef && typeof sdkRef.graphql === \"function\") {\n\t\tconst result = await sdkRef.graphql({ query, variables: variables ?? {} });\n\t\treturn { data: result?.data, errors: result?.errors };\n\t}\n\n\t// 2. MCP surface: use getChatSDK().callTool\n\tconst sdk = await getChatSDK();\n\tif (typeof sdk.callTool !== \"function\") {\n\t\tthrow new Error(\n\t\t\t\"[lightning/graphql] No data surface available. \" +\n\t\t\t\t\"Either initialise globalThis.__sfdc_sdk__ with createDataSDK, \" +\n\t\t\t\t\"or run inside a ChatGPT / MCP Apps context.\",\n\t\t);\n\t}\n\n\tconst raw = await sdk.callTool({\n\t\ttoolName: TOOL_NAME,\n\t\tparams: { query, variables: variables ?? {} },\n\t});\n\n\treturn (normalizeMcpResponse(raw) as GraphqlResult) ?? {};\n}\n\nexport class graphql {\n\t_dataCallback: WireCallback;\n\t_config: GraphqlConfig | undefined;\n\n\tconstructor(dataCallback: WireCallback) {\n\t\tthis._dataCallback = dataCallback;\n\t}\n\n\tconnect() {\n\t\tthis._fetch();\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-empty-function\n\tdisconnect() {}\n\n\tupdate(config: GraphqlConfig) {\n\t\tthis._config = config;\n\t\tthis._fetch();\n\t}\n\n\trefresh() {\n\t\treturn this._fetch();\n\t}\n\n\tasync _fetch() {\n\t\ttry {\n\t\t\tconst result = await runQuery(this._config ?? {});\n\t\t\tthis._emit(result);\n\t\t} catch (error) {\n\t\t\tthis._emit({\n\t\t\t\tdata: undefined,\n\t\t\t\terrors: [{ message: (error as Error).message }],\n\t\t\t});\n\t\t}\n\t}\n\n\t_emit({ data, errors }: GraphqlResult) {\n\t\tthis._dataCallback({\n\t\t\tdata,\n\t\t\terrors: errors?.length ? errors : undefined,\n\t\t\trefresh: () => this.refresh(),\n\t\t});\n\t}\n}\n\nexport async function executeMutation(config: GraphqlConfig): Promise<GraphqlResult> {\n\tif (!config?.query) return { data: undefined, errors: [{ message: \"No query provided\" }] };\n\ttry {\n\t\treturn await runQuery(config);\n\t} catch (error) {\n\t\treturn { data: undefined, errors: [{ message: (error as Error).message }] };\n\t}\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/vite-plugin-lwc-ui-bundle",
3
- "version": "1.133.0",
3
+ "version": "1.133.1",
4
4
  "description": "Vite plugin for compiling LWC components into static bundles for off-platform and MCP use",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "Salesforce",
@@ -73,7 +73,7 @@
73
73
  "magic-string": "^0.30.17"
74
74
  },
75
75
  "devDependencies": {
76
- "@salesforce/sdk-chat": "^1.133.0",
76
+ "@salesforce/sdk-chat": "^1.133.1",
77
77
  "typescript": "^5.9.3",
78
78
  "vite": "^7.0.0",
79
79
  "vite-plugin-dts": "^4.5.4",
@@ -12,123 +12,237 @@ description: >
12
12
 
13
13
  # Setup @salesforce/vite-plugin-lwc-ui-bundle
14
14
 
15
- This skill adds `@salesforce/vite-plugin-lwc-ui-bundle` to an existing LWC project,
16
- producing a single `dist/index.html` that runs in any browser without a Salesforce org.
15
+ Adds `@salesforce/vite-plugin-lwc-ui-bundle` to an existing LWC project,
16
+ producing a single `dist/index.html` that runs in a browser without a
17
+ Salesforce org.
17
18
 
18
- ## What the plugin does
19
-
20
- It wraps the full LWC compilation pipeline behind a single Vite plugin: scoped module
21
- providers (labels, i18n, gates, etc.), Lightning npm resolution, missing CSS handling,
22
- and the Vite/LWC bridge. The output is a self-contained HTML file with all JS and CSS
23
- inlined.
19
+ The plugin wraps the full LWC compilation pipeline behind one Vite plugin:
20
+ scoped module providers (labels, i18n, gates, etc.), Lightning npm
21
+ resolution, missing CSS handling, and the Vite/LWC bridge. Output is a
22
+ self-contained HTML file with all JS and CSS inlined.
24
23
 
25
24
  ## Reference material
26
25
 
27
- The `references/consumer-guide.md` file contains exact file templates, dependency
28
- versions, and config snippets. Read it when you need to generate `vite.config.js`,
29
- `index.html`, `bootstrap.js`, or `package.json` updates. This skill tells you
30
- _when and why_ to use each piece; the consumer guide gives you the exact _what_.
26
+ The skill dispatches to these reference files. Read each one when the
27
+ relevant step arrives don't try to inline everything from here:
28
+
29
+ - **`references/consumer-guide.md`** exact file templates (`vite.config.js`,
30
+ `index.html`, `bootstrap.js`, `package.json`), dependency list, and
31
+ troubleshooting. This is the source of truth for generated files.
32
+ - **`references/chat-wrapper-flow.md`** — the full sub-flow for Step 2 (ask
33
+ about chat wrappers, analyze `@api` surface, confirm mapping, generate
34
+ `chatContextAdapter` + `<component>ChatMapper` + `<component>ChatWrapper`).
35
+ - **`references/bootstrap-js-patterns.md`** — the conditional `callTool`
36
+ shim skeleton, envelope/tool-contract rules, how to handle an existing
37
+ `bootstrap.js`, and why mocks only work in `npm run dev`.
38
+ - **`references/known-pitfalls.md`** — consolidated list of real bugs seen
39
+ in consumer projects, each with symptom / cause / fix. Consult when a
40
+ build or runtime fails with a confusing message.
31
41
 
32
- ## Interactive Setup Flow
42
+ ## Interactive setup flow
33
43
 
34
- Walk the user through these steps in order. Ask questions — don't assume.
44
+ Walk through these steps in order. Ask questions — don't assume. When a
45
+ step points to a reference file, read it before proceeding.
35
46
 
36
47
  ### Step 1: Detect the project
37
48
 
38
- LWC projects come in two layouts, and the directory structure determines how
39
- `modules.dirs` must be configured. Detecting the layout first avoids misconfigured
40
- imports that silently fail at build time.
49
+ LWC projects come in two layouts, and the directory structure determines
50
+ how `modules.dirs` must be configured. Detecting the layout first avoids
51
+ misconfigured imports that silently fail at build time.
41
52
 
42
53
  Check in order:
43
54
 
44
- 1. `sfdx-project.json` in the project root → **SFDX project**. Components live at
45
- `force-app/main/default/lwc/` in a flat structure where every component shares
46
- the `c` namespace. Configure as `dirs: [{ path: "force-app/main/default/lwc", namespace: "c" }]`.
47
- 2. Directories matching `src/lwc/` or `modules/` → **off-core project**. Sub-directories
48
- are namespaces (e.g., `src/lwc/myNs/myComponent/`). Configure as `dirs: ["src/lwc"]`.
55
+ 1. `sfdx-project.json` in the project root → **SFDX project**. Components
56
+ live at `force-app/main/default/lwc/` in a flat structure where every
57
+ component shares the `c` namespace. Configure as
58
+ `dirs: [{ path: "force-app/main/default/lwc", namespace: "c" }]`.
59
+ 2. Directories matching `src/lwc/` or `modules/` → **off-core project**.
60
+ Sub-directories are namespaces (e.g., `src/lwc/myNs/myComponent/`).
61
+ Configure as `dirs: ["src/lwc"]`.
49
62
  3. If neither found, ask the user where their LWC components live.
50
63
 
51
- Read `package.json` to understand what's already installed — avoid adding duplicate
52
- dependencies later.
64
+ Read `package.json` to understand what's already installed — avoid
65
+ duplicates later. If the project already has `vite.config.js`,
66
+ `bootstrap.js`, or `main.js`, read them too so the later steps can merge
67
+ instead of overwriting.
68
+
69
+ Check the user's Node.js version (`node --version`). Vite 8.x requires
70
+ Node `^20.19.0 || >=22.12.0`. On older Node 20.x, builds fail deep inside
71
+ rolldown with "Cannot find native binding" (see `known-pitfalls.md#1`).
72
+ Surface this early:
73
+
74
+ > "You're on Node 20.14.0. Vite 8 needs 20.19+. Options:
75
+ > A) Upgrade Node (recommended — `nvm install 20.19` or `22`)
76
+ > B) Pin `vite@^7` and a compatible `@lwc/rollup-plugin` major"
53
77
 
54
- ### Step 2: Ask for the root component
78
+ ### Step 2: Ask about chat wrappers
79
+
80
+ If the project will run inside a ChatGPT/MCP host (tool output drives
81
+ the UI), components need a thin chat-aware wrapper + mapper. The wrapper
82
+ reads tool output from the host, the mapper normalizes it into the
83
+ component's `@api` props, and the component itself stays unchanged.
84
+
85
+ Ask before proceeding to root-component selection:
86
+
87
+ > "Will any component in this project need to be driven by ChatGPT/MCP
88
+ > tool output? I can generate a chat wrapper + mapper for each one so the
89
+ > component stays unchanged and the wrapper handles host integration.
90
+ >
91
+ > A) Yes — walk me through creating a wrapper
92
+ > B) No — skip this step"
55
93
 
56
- The plugin compiles a tree starting from one root component that gets mounted in the
57
- HTML page. The user needs to tell you which one, since there's no reliable way to
58
- auto-detect the "main" component.
94
+ If **B**, proceed directly to Step 3.
95
+
96
+ If **A**, read `references/chat-wrapper-flow.md` and walk through its
97
+ sub-steps 2a–2e for each component the user wants to wrap.
98
+
99
+ ### Step 3: Pick the root component
100
+
101
+ The plugin compiles a tree starting from one root component that gets
102
+ mounted in the HTML page. The user needs to tell you which one — there's
103
+ no reliable way to auto-detect the "main" component.
59
104
 
60
105
  List the discovered components and ask:
61
106
 
62
- > "Which component should be the root of your app? This is the one that gets
63
- > mounted in the HTML page."
107
+ > "Which component should be the root of your app? This is the one that
108
+ > gets mounted in the HTML page."
64
109
 
65
- Present the component names as a numbered list for easy selection.
110
+ Present the names as a numbered list for easy selection.
66
111
 
67
- ### Step 3: Inspect the component tree
112
+ ### Step 4: Inspect the component tree
68
113
 
69
- This step drives every downstream decision: which providers to include, whether
70
- `lightning-base-components` is needed, and whether to offer Tier 2 (live org). Getting
71
- this right prevents cryptic build errors.
114
+ This step drives every downstream decision: which providers to include,
115
+ which `lightning-base-components` are needed, and which tools need mock
116
+ branches for local dev. Getting this right prevents cryptic build
117
+ errors.
72
118
 
73
119
  Starting from the root component, trace the dependency tree:
74
120
 
75
121
  1. Read the root component's `.html` file — find all `c-*` tags (or other
76
- namespace tags) to identify child components
77
- 2. Recursively read each child component's `.js` and `.html`
122
+ namespace tags) to identify child components.
123
+ 2. Recursively read each child component's `.js` and `.html`.
78
124
  3. Collect all imports across the tree:
79
125
  - `@salesforce/label/*` → label keys (need `builtins.label()`)
80
- - `lightning/graphql` or `@wire(graphql` → needs GraphQL support
81
- - `lightning/*` base components → needs `lightning-base-components` npm package,
82
- plus `gate()`, `accessCheck()`, and `primitiveUtils()` providers because
83
- base components use these modules internally even if user code doesn't
84
- - `@salesforce/gate/*`, `@salesforce/accessCheck/*` → handled by providers
85
- - `@salesforce/i18n/*` i18n provider
86
- - `@salesforce/client/*` client provider (provides `formFactor` based on viewport width)
87
- - `lightning/primitiveUtils` primitiveUtils provider
88
-
89
- Report what you found:
126
+ - `lightning/graphql` AND `@wire(graphql` present → needs GraphQL
127
+ support (provider + mock branch)
128
+ - `lightning/uiRecordApi` imported AND `@wire(getRecord` / `@wire(get*`
129
+ actually used needs LDS support (provider + mock branch)
130
+ - `lightning/*` base components needs `lightning-base-components`
131
+ npm package, plus `gate()`, `accessCheck()`, and `primitiveUtils()`
132
+ providers because base components use these modules internally even
133
+ if user code doesn't
134
+ - `@salesforce/gate/*`, `@salesforce/accessCheck/*` → handled by
135
+ those providers
136
+ - `@salesforce/i18n/*` → `i18n` provider
137
+ - `@salesforce/client/*` → `client` provider (provides `formFactor`
138
+ based on viewport width)
139
+ - `lightning/primitiveUtils` → `primitiveUtils` provider
140
+
141
+ **Be precise with LDS detection.** The LDS provider/mock is only needed
142
+ when a component's `@wire` actually consumes `getRecord`/`getRecordUi`/
143
+ similar adapters from `lightning/uiRecordApi`. **Do not trigger the LDS
144
+ provider just because:**
145
+
146
+ - The app has a chat wrapper whose tool output happens to contain a
147
+ record shape (`records[...]`) — that goes through
148
+ `window.openai.toolOutput` + the mapper, not the LDS wire adapter.
149
+ - A component imports `lightning/uiRecordApi` but doesn't `@wire` it.
150
+
151
+ `builtins.lds()` wires up `@wire(getRecord)` specifically. If no
152
+ component calls those wire adapters, omit both the provider and the
153
+ `getRecordMcpTool` mock branch. A chat wrapper that maps
154
+ `window.openai.toolOutput.records[...]` into a component's `@api`
155
+ props is independent of LDS.
156
+
157
+ Report what you found — plainly, so the user can correct misdetections:
90
158
 
91
159
  > "I traced your component tree from `c/app`. Here's what I found:
92
160
  >
93
161
  > - 5 components total
94
162
  > - 3 label imports: `c.appTitle`, `c.greeting`, `c.save`
95
- > - Uses `lightning-card` (needs lightning-base-components)
96
- > - Uses `lightning/graphql` (needs lwcProxy for live data)
97
- > - No gate/accessCheck imports in your code, but lightning-base-components
98
- > uses them internally I'll include those providers automatically."
99
-
100
- ### Step 4: Decide the tier
101
-
102
- The plugin supports two tiers. This determines which dependencies and bootstrap
103
- code to generate.
104
-
105
- | Tier | What you get | Org required? |
106
- | ------------------------------- | ------------------------------------------ | ------------------ |
107
- | **Tier 1: Off-Platform Build** | Static bundle with mock data, labels, i18n | No |
108
- | **Tier 2: Live Org Connection** | Real GraphQL queries via `lwcProxy()` | Yes (via `sf` CLI) |
163
+ > - Uses `lightning-card` needs lightning-base-components + the
164
+ > gate/accessCheck/primitiveUtils providers
165
+ > - Uses `lightning/graphql` needs `builtins.lightningGraphql()` + a
166
+ > graphql mock branch in `bootstrap.js`
167
+ > - Uses `lightning/uiRecordApi` → needs `builtins.lds()` + a
168
+ > `getRecordMcpTool` mock branch"
169
+
170
+ ### Step 5: Decide the runtime mode
171
+
172
+ Two runtime contexts, one compiled bundle:
173
+
174
+ | Context | Data source | Notes |
175
+ | ---------------------- | -------------------------------------------------------- | ------------------------------------------------------------- |
176
+ | **Local dev** | Mock `window.openai` shim in `bootstrap.js` | `npm run dev` only. No org, no ChatGPT host. |
177
+ | **ChatGPT / MCP host** | Host-provided `window.openai` (`callTool`, `toolOutput`) | Production. Same compiled bundle; mocks skipped by the guard. |
178
+
179
+ The `if (!window.openai?.callTool)` guard is what makes one bundle work
180
+ in both contexts. ChatGPT/MCP hosts set `window.openai` before loading
181
+ the bundle, so the guard short-circuits.
182
+
183
+ > `lwcProxy()` is intentionally not offered. It doesn't currently support
184
+ > `lightning/uiRecordApi`, so it's not a reliable local-dev substitute
185
+ > for components that use LDS. Keep the workflow simple: mock locally,
186
+ > real host in ChatGPT.
187
+
188
+ Ask the user whether to generate mocks. Mocks serve two independent
189
+ purposes — list each one **only if Step 4 / Step 2 produced something
190
+ to mock**, and skip lines that don't apply:
191
+
192
+ - **`callTool` branches for wire adapters** — one branch per wire
193
+ provider actually detected in Step 4:
194
+ - `graphqlQuery` — only if Step 4 found `@wire(graphql)` in a
195
+ component's `.js`. Not needed otherwise.
196
+ - `getRecordMcpTool` — only if Step 4 found `@wire(getRecord)` in
197
+ a component's `.js`. Not needed just because a chat wrapper
198
+ passes record-shaped data; the wrapper reads `toolOutput`, not
199
+ `callTool`.
200
+ - **Seed `window.openai.toolOutput`** — only if Step 2 generated a
201
+ chat wrapper. This is what drives the wrapper's initial render; the
202
+ mapper consumes it. It has nothing to do with `callTool`.
203
+
204
+ Build the question by combining whichever applies. For example, a
205
+ project with graphql only:
206
+
207
+ > "For local dev (`npm run dev`) I can add mocks in `bootstrap.js`. The
208
+ > shim only activates when no host bridge is present, so the same
209
+ > compiled bundle runs inside ChatGPT unchanged.
210
+ >
211
+ > Based on Step 4, the mock will need:
212
+ >
213
+ > - `callTool` branch for `graphqlQuery`
214
+ >
215
+ > A) Yes — generate mocks with sample data I'll describe
216
+ > B) No — skip mocks
217
+ >
218
+ > If B, `@wire(graphql)` will return no data under `npm run dev`, but
219
+ > the bundle will still render correctly when deployed to a real MCP
220
+ > server as a UI resource — the host provides a real `window.openai`
221
+ > there."
109
222
 
110
- If GraphQL usage was detected in Step 3, ask:
223
+ For a project with a chat wrapper **and** `@wire(graphql)`, include
224
+ both the `callTool` branch and the `toolOutput` seed, but keep them
225
+ as distinct items in the list:
111
226
 
112
- > "Your components use `lightning/graphql`. Do you want to connect to a real
113
- > Salesforce org for live data during development?
227
+ > Based on Step 2 / Step 4, the mocks will include:
114
228
  >
115
- > A) Yes I have an org connected via `sf` CLI (adds lwcProxy + Data SDK)
116
- > B) No — use mock data for now (GraphQL wire adapter returns empty results)"
229
+ > - `callTool` branch for `graphqlQuery` (for the `@wire(graphql)` call)
230
+ > - Seed for `window.openai.toolOutput` (drives the
231
+ > `<bcx-record-detail-chat-wrapper>` initial render)
117
232
 
118
- If no GraphQL detected, default to Tier 1 — there's no benefit to Tier 2 without
119
- GraphQL or live data needs.
233
+ ### Step 6: Resolve label values
120
234
 
121
- ### Step 5: Resolve label values
235
+ Labels need explicit values because the plugin can't read them from an
236
+ org at build time. Getting them right here means the user sees realistic
237
+ text immediately instead of placeholder keys.
122
238
 
123
- Labels need explicit values because the plugin can't read them from an org at build
124
- time. Getting them right here means the user sees realistic text immediately instead
125
- of placeholder keys.
126
-
127
- 1. **SFDX project**: Check for `force-app/main/default/labels/CustomLabels.labels-meta.xml`.
128
- If found, parse it and extract `<fullName>` → `<value>` pairs for the label
129
- keys discovered in Step 3.
239
+ 1. **SFDX project**: Check for
240
+ `force-app/main/default/labels/CustomLabels.labels-meta.xml`. If found,
241
+ parse it and extract `<fullName>` → `<value>` pairs for the label keys
242
+ discovered in Step 4.
130
243
  2. **Off-core project or no labels XML**: For each label key found, use a
131
- placeholder value derived from the key (e.g., `c.appTitle` → `"App Title"`).
244
+ placeholder value derived from the key (e.g., `c.appTitle` → `"App
245
+ Title"`).
132
246
 
133
247
  Tell the user what labels you found and the values you'll use:
134
248
 
@@ -140,103 +254,186 @@ Tell the user what labels you found and the values you'll use:
140
254
  >
141
255
  > You can change these in `vite.config.js` later."
142
256
 
143
- If no labels were found at all, still include `builtins.label()` with no overrides.
144
- The plugin returns a human-readable fallback for unknown keys, and
145
- `lightning-base-components` may use labels internally.
257
+ If no labels were found at all, still include `builtins.label()` with no
258
+ overrides. The plugin returns a human-readable fallback for unknown keys,
259
+ and `lightning-base-components` may use labels internally.
260
+
261
+ ### Step 7: Generate or update files
262
+
263
+ Read `references/consumer-guide.md` for the exact file templates. For
264
+ each file below, check whether it already exists:
265
+
266
+ - **If it exists**: read it first, then merge changes instead of
267
+ overwriting. Ask the user before making destructive edits to
268
+ hand-written sections.
269
+ - **If it doesn't exist**: generate from the consumer-guide template,
270
+ adapted to what Steps 1–6 found.
271
+
272
+ #### `package.json`
273
+
274
+ Merge in dependencies and scripts. Don't overwrite existing.
275
+
276
+ - Add `"type": "module"` if not present.
277
+ - Only add `lightning-base-components` and `@salesforce-ux/design-system`
278
+ if the component tree uses `lightning/*` base components.
279
+ - **Do not hardcode versions from the consumer guide.** Pinned versions
280
+ drift out of date (prerelease / alpha tags get unpublished). Look up
281
+ the current version with `npm view <pkg> version` and pin using
282
+ caret-major. At minimum, look up:
283
+ - `@salesforce/vite-plugin-lwc-ui-bundle`
284
+ - `@lwc/rollup-plugin`
285
+ - `lwc`
286
+ - `lightning-base-components` (if used)
287
+ - `@salesforce-ux/design-system` (if used)
288
+ - `vite`
289
+ - `vite-plugin-singlefile`
290
+
291
+ If a registry lookup fails (offline / auth issue), ask the user for
292
+ the version rather than falling back to the stale snippet.
293
+
294
+ #### `vite.config.js`
295
+
296
+ See "Step 2" in the consumer guide for the template.
297
+
298
+ **Import providers from the plugin, do not reimplement them.** All
299
+ provider names (`label`, `i18n`, `client`, `gate`, `accessCheck`,
300
+ `primitiveUtils`, `lightningGraphql`, `lds`) must come from the `builtins`
301
+ export of `@salesforce/vite-plugin-lwc-ui-bundle`. Calling them invokes
302
+ the plugin's real runtime — which includes
303
+ `lightning/graphql` wire adapter, `normalizeMcpResponse` envelope
304
+ parsing, LDS wire adapter, and more. Hand-rolling any of them bypasses
305
+ all that and breaks host integration (see
306
+ `references/known-pitfalls.md#3`).
307
+
308
+ ```js
309
+ import lwcVitePlugin, { builtins } from "@salesforce/vite-plugin-lwc-ui-bundle";
310
+
311
+ // inside plugins -> lwcVitePlugin({ providers: [...] })
312
+ builtins.lightningGraphql(), // default tool name: "graphqlQuery"
313
+ builtins.lightningGraphql({ toolName: "yourCustomTool" }), // override
314
+ builtins.lds(), // default registry
315
+ ```
316
+
317
+ Adapt based on earlier findings:
146
318
 
147
- ### Step 6: Generate files
319
+ - Set `dirs` for the detected project structure (SFDX vs namespaced).
320
+ - Populate `builtins.label({...})` with values from Step 6.
321
+ - Include `builtins.primitiveUtils()` if using `lightning-base-components`.
322
+ - Include `builtins.lightningGraphql()` only if GraphQL was detected.
323
+ - Include `builtins.lds()` if any component uses `lightning/uiRecordApi`.
324
+ - Include `viteSingleFile()` — without it, `vite build` emits multi-file
325
+ output and the MCP host can't inline the bundle. See
326
+ `known-pitfalls.md#7` for the full config block.
327
+ - Remove `npm: ["lightning-base-components"]` if no base components used.
148
328
 
149
- Read `references/consumer-guide.md` for the exact file templates. Generate these
150
- files, adapting the templates based on what you learned in Steps 1-5:
329
+ **Tool names** (canonical list referenced elsewhere):
151
330
 
152
- #### Files to create or update
331
+ - `builtins.lds()` default for `lightning/uiRecordApi.getRecord`:
332
+ `getRecordMcpTool`. Override via
333
+ `builtins.lds({ "lightning/uiRecordApi": { getRecord: { toolName: "..." } } })`.
334
+ - `builtins.lightningGraphql()` default: `graphqlQuery` (**not**
335
+ `"graphql"` or `"lightningGraphql"`). Override via
336
+ `builtins.lightningGraphql({ toolName: "..." })`.
153
337
 
154
- 1. **`package.json`** merge in dependencies and scripts (don't overwrite existing).
155
- See "Step 1: Install Dependencies" in the consumer guide for the full dep list.
156
- - Add `"type": "module"` if not present
157
- - Only add `lightning-base-components` and `@salesforce-ux/design-system` if
158
- the component tree uses `lightning/*` base components
159
- - For Tier 2, also add `@salesforce/sdk-data` and `@salesforce/ui-bundle`
338
+ Whatever tool names you settle on here **must match** the branches in
339
+ `bootstrap.js` mocks (Step 8).
160
340
 
161
- 2. **`vite.config.js`** — see "Step 2" in the consumer guide for the template.
162
- Adapt based on your findings:
163
- - Set `dirs` for the detected project structure (SFDX vs namespaced)
164
- - Populate `builtins.label({...})` with values from Step 5
165
- - Include `builtins.primitiveUtils()` if using `lightning-base-components`
166
- - Include `builtins.lightningGraphql()` only if GraphQL was detected
167
- - Remove `npm: ["lightning-base-components"]` if no base components used
168
- - For Tier 2, add `lwcProxy()` before `lwcVitePlugin()` in the plugins array
341
+ #### `index.html`
169
342
 
170
- 3. **`index.html`** — see "Step 3" in the consumer guide.
343
+ See "Step 3" in the consumer guide.
171
344
 
172
- 4. **`bootstrap.js`** (project root) — see "Step 4" (Tier 1) or "Step 3" (Tier 2)
173
- in the consumer guide. Replace the component name with the actual root from
174
- Step 2. Remove the SLDS CSS import if `lightning-base-components` is not used.
345
+ #### `bootstrap.js`
175
346
 
176
- #### Advanced options (use only when needed)
347
+ This file has its own dedicated reference. Read
348
+ `references/bootstrap-js-patterns.md` now for:
177
349
 
178
- The plugin accepts additional options for complex projects. Only add these if the
179
- component tree inspection reveals a need:
350
+ - The skeleton structure (mock data conditional shim dynamic imports).
351
+ - The tool-contract / envelope rules (what shape each mock branch must
352
+ return, and why double-wrapping breaks the graphql wire adapter).
353
+ - Guidance on merging into an existing `bootstrap.js` or `main.js`.
354
+ - Why production builds don't work with mocks (rolldown module-init
355
+ order defeats the shim; mocks are dev-server only).
180
356
 
181
- - **`stubs`**: Map bare module specifiers to stub files for core-only modules
182
- not available off-platform (e.g., `{ "force/someModule": "./stubs/someModule.js" }`).
183
- Needed when components import platform modules like `aura`, `logger`, or
184
- `force/*` that don't exist outside Salesforce.
185
- - **`lwcOptions`**: Pass-through options for `@lwc/rollup-plugin` to configure
186
- LWC compiler behavior (e.g., `{ enableDynamicComponents: true }`). Only needed
187
- for projects using advanced LWC features like dynamic component creation.
188
- - **`passthroughRules`**: Let specific imports bypass the provider system. Each
189
- rule has a `specifierPrefix` and `importerPattern` — when both match, the import
190
- resolves normally. Useful when an npm package (like `lightning-base-components`)
191
- has its own label definitions that shouldn't be intercepted by your label provider.
192
- - **`ignorePatterns`**: Specifier prefixes that providers should never intercept.
193
- Defaults include `@salesforce/sdk-*` and `@salesforce/core`. Extend this if
194
- you have imports that look like provider-handled patterns but should resolve
195
- through normal Node module resolution.
357
+ Use that reference to write the file; don't reconstruct the skeleton
358
+ from memory.
196
359
 
197
- ### Step 7: Install and build
360
+ If the user chose **B** in Step 5 (no mocks), skip the shim entirely.
361
+ `@wire(graphql)` / `@wire(getRecord)` will return no data under
362
+ `npm run dev`, but the bundle will render correctly once deployed to a
363
+ real MCP server as a UI resource — the host provides `window.openai`
364
+ there.
198
365
 
199
- Run:
366
+ #### Advanced plugin options
367
+
368
+ Only add these if the component tree inspection reveals a need:
369
+
370
+ - **`stubs`**: Map bare module specifiers to stub files for core-only
371
+ modules not available off-platform (e.g., `{ "force/someModule":
372
+ "./stubs/someModule.js" }`). Needed when components import platform
373
+ modules like `aura`, `logger`, or `force/*`.
374
+ - **`lwcOptions`**: Pass-through options for `@lwc/rollup-plugin`.
375
+ Only for advanced LWC features like dynamic component creation.
376
+ - **`passthroughRules`**: Let specific imports bypass the provider
377
+ system. Useful when an npm package has its own label definitions that
378
+ shouldn't be intercepted.
379
+ - **`ignorePatterns`**: Specifier prefixes that providers should never
380
+ intercept. Defaults include `@salesforce/sdk-*` and `@salesforce/core`.
381
+
382
+ ### Step 8: Install and build
200
383
 
201
384
  ```bash
202
385
  npm install
203
386
  npm run build
204
387
  ```
205
388
 
206
- If the build succeeds, report the output file size and open it:
389
+ If the build succeeds, report the output file size:
207
390
 
208
391
  ```bash
209
392
  open dist/index.html
210
393
  ```
211
394
 
212
- If the build fails, diagnose and fix. See the "Common Errors" section in
213
- `references/consumer-guide.md` for the full troubleshooting guide. The most
214
- frequent issues:
395
+ A correctly built `dist/index.html` is typically 100 KB+. A 1-2 KB file
396
+ is the un-inlined stub (see `known-pitfalls.md#7`).
397
+
398
+ If the build fails, diagnose via `references/known-pitfalls.md`. The most
399
+ frequent issues have symptoms that map directly to specific entries:
215
400
 
216
- - **`Cannot read properties of undefined (reading 'isOpen')`**: Missing `gate()`
217
- or `accessCheck()` provider `lightning-base-components` use gates internally.
218
- - **`Rollup failed to resolve import "lightning/button"`**: Missing
219
- `npm: ["lightning-base-components"]` in modules config.
220
- - **`Unhandled import: @salesforce/label/...`**: Expected warning the plugin
221
- returns the key as display text. Add overrides to `builtins.label()` to customize.
222
- - **`Top-level await is not available`**: Missing `target: "esnext"` in build config.
223
- - **`Rollup failed to resolve import "force/someModule"`**: Core-only module. Add
224
- a stub via the `stubs` option.
225
- - **Component renders but looks unstyled**: Missing SLDS CSS import in bootstrap.js.
401
+ - "Cannot find native binding" inside rolldown pitfall #1 (Node)
402
+ - "No matching version found for \<pkg\>" → pitfall #2 (stale dep pins)
403
+ - Missing `isOpen` / missing `lightning/button` → pitfalls #9 / #7
404
+ - `force/someModule` not resolved → pitfall #10
405
+ - Component renders but unstyled missing SLDS CSS import in
406
+ `bootstrap.js`
226
407
 
227
- ### Step 8: Test dev server (Tier 2 only)
408
+ ### Step 9: Verify local dev run
228
409
 
229
- If lwcProxy was configured:
410
+ Start the dev server:
230
411
 
231
412
  ```bash
232
413
  npm run dev
233
414
  ```
234
415
 
235
- Confirm the terminal shows `[lwc-proxy] Connected to https://...`. Open
236
- `http://localhost:5173` and verify GraphQL queries return real data.
416
+ Open `http://localhost:5173` and confirm:
417
+
418
+ - The root component renders.
419
+ - If `bootstrap.js` has mocks, the wrappers show mapped data and wire
420
+ adapters return the mock payloads.
421
+ - If no mocks were generated, chat wrappers show "Waiting for tool
422
+ output..." and `@wire` adapters return no data. That's expected; the
423
+ bundle will work once deployed to a real MCP server.
424
+
425
+ If something renders wrong, check `references/known-pitfalls.md`. The
426
+ most common dev-time issues are graphql wire adapters returning empty
427
+ data (pitfalls #3, #4, #5) and mapper returning `null` on Avro-wrapped
428
+ payloads (#6).
429
+
430
+ The same compiled `dist/index.html` runs in ChatGPT — the guard in
431
+ `bootstrap.js` ensures local mocks are skipped when the host provides
432
+ the real bridge.
237
433
 
238
434
  ## Reference
239
435
 
240
436
  - npm: https://www.npmjs.com/package/@salesforce/vite-plugin-lwc-ui-bundle
241
437
  - README: https://github.com/salesforce-experience-platform-emu/webapps/tree/main/packages/vite-plugin-lwc-ui-bundle
242
438
  - Consumer Guide: https://github.com/salesforce-experience-platform-emu/webapps/blob/main/packages/vite-plugin-lwc-ui-bundle/docs/consumer-guide.md
439
+ - Chat Wrapper Guide: https://github.com/salesforce-experience-platform-emu/webapps/blob/main/packages/vite-plugin-lwc-ui-bundle/docs/chat-wrapper-guide.md
@@ -0,0 +1,215 @@
1
+ # `bootstrap.js` Patterns
2
+
3
+ Read this reference when writing or editing the project's `bootstrap.js`
4
+ entry file. The skill dispatches here from Step 5 after deciding whether
5
+ the project needs local-dev mocks.
6
+
7
+ ## Runtime contexts
8
+
9
+ The goal is one compiled bundle that runs in either context:
10
+
11
+ | Context | Data source | Notes |
12
+ | ---------------------- | -------------------------------------------------------- | ------------------------------------------------------------- |
13
+ | **Local dev** | Mock `window.openai` shim in `bootstrap.js` | `npm run dev` only. No org, no ChatGPT host. |
14
+ | **ChatGPT / MCP host** | Host-provided `window.openai` (`callTool`, `toolOutput`) | Production. Same compiled bundle; mocks skipped by the guard. |
15
+
16
+ The guard `if (!window.openai?.callTool)` is what makes the bundle work
17
+ in both contexts: ChatGPT sets `window.openai` before loading the
18
+ bundle, so the guard short-circuits and real host calls run.
19
+
20
+ ### ⚠ Mocks are for `npm run dev` only
21
+
22
+ Do **not** expect `npm run build && open dist/index.html` to render mock
23
+ data. Rollup hoists module-init code to the top of the bundle, which
24
+ means `@salesforce/sdk-chat`'s `detectSurface()` runs _before_ the
25
+ `main.js` / `bootstrap.js` body executes the `if (!window.openai?.
26
+ callTool)` block. The SDK caches surface = `"WebApp"` and never observes
27
+ the mock — LDS / graphql wire adapters silently return empty data.
28
+
29
+ - Dev server (`npm run dev`) works because Vite serves modules on demand
30
+ in source order; the entry file runs first.
31
+ - Production builds only target the real MCP host (where the host sets
32
+ `window.openai` before the bundle loads, so surface detection sees
33
+ `OpenAI` correctly).
34
+
35
+ When previewing production bundles locally (rare), drive the wrappers
36
+ by deploying the bundle to a real MCP server as a UI resource; the host
37
+ provides `window.openai` and wire adapters work there.
38
+
39
+ ## Authoring pattern
40
+
41
+ Structure `bootstrap.js` as three ordered sections:
42
+
43
+ 1. **Define mock data** at the top — hardcoded sample objects shaped like
44
+ the real tool output. This is the part the user edits per project.
45
+ 2. **Install the shim** inside the `if (!window.openai?.callTool)`
46
+ guard. The shim returns responses shaped like the real MCP server's
47
+ responses.
48
+ 3. **Dynamic-import LWC / SDK modules _after_ the shim** — SDK surface
49
+ detection runs at module load and reads `window.openai` once. Static
50
+ imports at the top of the file would fire the detection before the
51
+ shim is installed.
52
+
53
+ ### Handling an existing `bootstrap.js`
54
+
55
+ If the project already has `bootstrap.js`, `main.js`, or another entry
56
+ file, do not overwrite it. Merge instead:
57
+
58
+ - **Static → dynamic imports:** If the existing file statically imports
59
+ `lwc` / LWC components, move those imports to dynamic `await
60
+ import(...)` calls after the shim install.
61
+ - **Existing `window.openai`:** If the file already installs a mock,
62
+ extend it rather than replace. Verify any guard already uses
63
+ `if (!window.openai?.callTool)` semantics.
64
+ - **Multiple roots:** If the existing file mounts more than one root
65
+ component, keep all of them; just make sure the shim install runs
66
+ before any dynamic import.
67
+
68
+ When in doubt, read the existing file top-to-bottom first, then ask the
69
+ user whether to edit or replace.
70
+
71
+ ## Skeleton
72
+
73
+ Generate the following skeleton, adapting the mocked tool branches to
74
+ what was found in Step 4 of SKILL.md:
75
+
76
+ ```js
77
+ // 1) Mock data — shape after the real tool's sample output
78
+ const mockAccounts = [
79
+ { id: "001a", name: "Acme", industry: "Technology", employees: 8500 },
80
+ { id: "001b", name: "Global Media", industry: "Media", employees: 3200 },
81
+ ];
82
+
83
+ // 2) Install the shim only when no host bridge exists.
84
+ // In ChatGPT / MCP context, the host already set window.openai, so
85
+ // this block is skipped and the real bridge is used.
86
+ if (!window.openai?.callTool) {
87
+ window.openai = {
88
+ ...window.openai,
89
+ callTool: async (name, args) => {
90
+ // ── LDS (lightning/uiRecordApi.getRecord) ──────────────────────
91
+ if (name === "getRecordMcpTool") {
92
+ const a = mockAccounts.find((m) => m.id === args?.recordId);
93
+ if (!a) return { structuredContent: { data: null, error: "Record not found" } };
94
+ return {
95
+ structuredContent: {
96
+ data: {
97
+ fields: {
98
+ Name: { value: a.name },
99
+ Industry: { value: a.industry },
100
+ },
101
+ },
102
+ },
103
+ };
104
+ }
105
+
106
+ // ── lightning/graphql (default tool name "graphqlQuery") ───────
107
+ if (name === "graphqlQuery") {
108
+ const query = args?.query ?? "";
109
+ if (query.includes("Account")) {
110
+ return {
111
+ result: JSON.stringify({
112
+ data: {
113
+ uiapi: {
114
+ query: {
115
+ Account: {
116
+ edges: mockAccounts.map((a) => ({
117
+ node: {
118
+ Id: a.id,
119
+ Name: { value: a.name },
120
+ Industry: { value: a.industry },
121
+ NumberOfEmployees: { value: a.employees },
122
+ },
123
+ })),
124
+ },
125
+ },
126
+ },
127
+ },
128
+ }),
129
+ };
130
+ }
131
+ return { result: JSON.stringify({ data: {} }) };
132
+ }
133
+
134
+ console.warn("[mock] unhandled tool:", name, args);
135
+ return { result: JSON.stringify({ data: {} }) };
136
+ },
137
+ };
138
+
139
+ // For chat wrappers: seed toolOutput so the wrapper renders immediately
140
+ // in local dev instead of sitting in the "waiting" state.
141
+ window.openai.toolOutput = {
142
+ // Shape after the mapper's confirmed contract from Step 2d.
143
+ // e.g. { records: { "001a": { apiName: "Account", id: "001a", fields: { ... } } } }
144
+ };
145
+ }
146
+
147
+ // 3) Dynamic-import AFTER the shim so SDK surface detection sees the mock
148
+ const { createElement } = await import("lwc");
149
+ const { default: App } = await import("c/app");
150
+ document.getElementById("app").appendChild(createElement("c-app", { is: App }));
151
+ ```
152
+
153
+ ## How response envelopes are unwrapped
154
+
155
+ The plugin's providers call `callTool()` and pass the result through
156
+ `normalizeMcpResponse()` before handing it to the wire adapter. That
157
+ helper accepts two shapes:
158
+
159
+ - `{ structuredContent: <payload> }` — MCP Apps surface shape. Returns
160
+ `<payload>` directly.
161
+ - `{ result: "<JSON string>" }` — OpenAI surface shape. Returns
162
+ `JSON.parse(<string>)`. (If the parsed value is an MCP-style content
163
+ array with a `text` block, it parses the inner JSON too.)
164
+ - Anything else — returned as-is.
165
+
166
+ **The wire adapter reads its tool-specific fields off the unwrapped
167
+ payload.** That payload must match the tool's contract:
168
+
169
+ - `getRecordMcpTool` (LDS wire adapter) reads `.data` and `.error` off
170
+ the unwrapped payload. Mock payload: `{ data: {...}, error?: ... }`.
171
+ - `graphqlQuery` (graphql wire adapter) reads `.data` and `.errors`
172
+ (plural, graphql-shaped) off the unwrapped payload. Mock payload:
173
+ `{ data: {...}, errors?: [...] }`.
174
+
175
+ Either envelope can carry either tool's payload. The tool-contract shape
176
+ is what matters. If the mock returns
177
+ `{ structuredContent: { data: {...} } }` for graphql, the wrapper reads
178
+ `.data` off `{ data: {...} }` which is **one level too shallow** — it
179
+ gets the real data. But if the mock instead nests another level, like
180
+ `{ structuredContent: { structuredContent: { data: {...} } } }` or
181
+ `{ structuredContent: { data: { data: {...} } } }`, the wire adapter
182
+ sees `data: { data: {...} }` and the wrapped component receives
183
+ `undefined` as graphql data.
184
+
185
+ Summary:
186
+
187
+ - **Don't re-wrap.** One envelope layer (`structuredContent` _or_
188
+ `result`), then the raw tool contract. No nested layers.
189
+ - **Mirror the real MCP server's response** when the mock differs from
190
+ what you see in production. Both envelopes work; pick whichever the
191
+ real server uses for the closest parity.
192
+
193
+ ## Graphql query-based branching
194
+
195
+ Because the graphql wire adapter sends every query through a single tool
196
+ (`graphqlQuery` by default), the mock needs to branch on the query
197
+ string to return different data for different queries:
198
+
199
+ ```js
200
+ if (name === "graphqlQuery") {
201
+ const query = args?.query ?? "";
202
+ if (query.includes("Account")) {
203
+ /* account edges */
204
+ }
205
+ if (query.includes("Contact")) {
206
+ /* contact edges */
207
+ }
208
+ if (query.includes("Opportunity")) {
209
+ /* opportunity edges */
210
+ }
211
+ return { result: JSON.stringify({ data: {} }) };
212
+ }
213
+ ```
214
+
215
+ `args.variables` is also available if queries differ only by variables.
@@ -0,0 +1,227 @@
1
+ # Chat Wrapper Flow
2
+
3
+ Walk through this reference whenever the user answers "Yes" to the Step 2
4
+ question in SKILL.md (any component needs to be driven by ChatGPT/MCP tool
5
+ output).
6
+
7
+ The chat-wrapper pattern inserts a thin adapter layer between the host's
8
+ `window.openai.toolOutput` and your LWC's `@api` props, so the component
9
+ stays unchanged and works in both standalone and host contexts:
10
+
11
+ ```
12
+ tool output ─▶ chatContextAdapter ─▶ <component>ChatMapper ─▶ <component>ChatWrapper ─▶ <your-core-component>
13
+ ```
14
+
15
+ Run the following sub-steps for each component the user wants to wrap.
16
+ Repeat for additional components if requested.
17
+
18
+ **Reference implementation:** the `recordDetail` example under
19
+ `examples/lwc-axl/lwc-records/sf/lwc/bcx/` in the webapps repo shows all
20
+ three bundles working together:
21
+
22
+ - `recordDetail/` — the unchanged core component.
23
+ - `recordDetailChatMapper/` — the full UI-API → `@api` mapper, including
24
+ `unwrapPropertiesMap`, envelope fallbacks, and GraphQL-edge support.
25
+ - `recordDetailChatWrapper/` — the wrapper with the initial-payload
26
+ debug log, loading/waiting states, and core-component mount.
27
+ - `chatContextAdapter/` — reusable across all wrappers in a project.
28
+
29
+ Copy patterns from those files when generating new bundles. The
30
+ instructions below describe the decision points; the reference code
31
+ fills in the exact implementation.
32
+
33
+ ## 2a. Pick the target component
34
+
35
+ List the discovered components and ask:
36
+
37
+ > "Which component should get a chat wrapper? (Can repeat for more.)"
38
+
39
+ ## 2b. Analyze the component's `@api` surface
40
+
41
+ Read the target component's `.js` file and extract the required prop
42
+ contract:
43
+
44
+ 1. Collect every `@api <name>` declaration (and `@api get <name>()` pairs).
45
+ 2. For each prop, note whether it's primitive, object, or array from its
46
+ usage (`wire({recordId: '$recordId', fields: '$fields'})` →
47
+ `recordId: string`, `fields: string[]`).
48
+ 3. Note any `@wire` usage that consumes these props — those are the props
49
+ the mapper must populate.
50
+
51
+ Report what you found:
52
+
53
+ > "`bcx-record-detail` requires these `@api` props:
54
+ >
55
+ > - `recordId` (string) — identifies the record being displayed
56
+ > - `data` (object) — the record value (fields + apiName + id)
57
+ > - `fieldMetadata` (object) — per-field metadata from the org's objectInfos
58
+ > - `layout` (object) — per-record layout descriptor
59
+ > - `picklistValues` (object, optional)
60
+ > - `changedFields` (array, optional)
61
+ > - `expandable` (boolean, optional)"
62
+
63
+ ## 2c. Ask about tool output structure
64
+
65
+ The mapper shape depends entirely on what the tool returns. Ask the user
66
+ for a sample payload:
67
+
68
+ > "What does the MCP tool output look like for this component? Paste a
69
+ > sample JSON payload, or give me the path to a sample file (e.g. an
70
+ > example file in the MCP server repo)."
71
+
72
+ Parse the payload and identify where each required prop lives. For
73
+ `getRecordDetails`-shaped output:
74
+
75
+ - `recordId` → `records[firstKey].id` (or the first key of the `records`
76
+ map)
77
+ - `fields` (qualified names like `"Account.Name"`) → derived from
78
+ `records[firstKey].apiName` + keys of `records[firstKey].fields`
79
+ - Full record data (for components that render the record directly) →
80
+ `records[firstKey]` (possibly after `normalizeFieldValue` unwrapping)
81
+
82
+ Accept common envelope variations: raw tool output, `structuredContent`
83
+ wrapper, nested `recordDetail`/`record_detail` keys.
84
+
85
+ ### `.properties` unwrap for map-shaped fields
86
+
87
+ Some MCP hosts re-serialize `structuredContent` through an Avro /
88
+ JSON-schema view that wraps every map-typed field in a `.properties` key.
89
+ So instead of `records: { "001…": {...} }` the wrapper sees
90
+ `records: { properties: { "001…": {...} } }`. Without unwrapping, the
91
+ mapper's "first key" becomes the literal string `"properties"`.
92
+
93
+ **Rule:** every map-shaped field (`records`, `fields`, `objectInfos`,
94
+ `layouts`, …) must go through an `unwrapPropertiesMap(value)` helper that
95
+ returns `value.properties` when it looks like a properties-wrapped map,
96
+ else `value`. Apply this before reading keys or selecting the first
97
+ entry. The helper is small and always the same:
98
+
99
+ ```js
100
+ function unwrapPropertiesMap(value) {
101
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
102
+ if (value.properties && typeof value.properties === "object") return value.properties;
103
+ return value;
104
+ }
105
+ ```
106
+
107
+ ## 2d. Confirm the mapping with the user
108
+
109
+ Before generating files, show the planned mapping and ask for confirmation:
110
+
111
+ > "Here's how I'll map tool output → `bcx-record-detail` props:
112
+ >
113
+ > | Prop | Source in tool output |
114
+ > | -------------- | -------------------------------------------------------------- |
115
+ > | recordId | `records[firstKey].id` (falls back to first key) |
116
+ > | data | `records[firstKey]` (after `unwrapPropertiesMap` on `.fields`) |
117
+ > | fieldMetadata | `objectInfos[apiName]` |
118
+ > | layout | `layouts[apiName][recordTypeId].Full.View` |
119
+ > | picklistValues | derived from `objectInfos[apiName].fields[*].picklistValues` |
120
+ >
121
+ > Fallback: if the envelope wraps in `structuredContent` or
122
+ > `recordDetail`/`record_detail`, unwrap before reading.
123
+ >
124
+ > Does this mapping look correct? (yes / tell me what to change)"
125
+
126
+ Iterate until the user confirms. Do not proceed to file generation until
127
+ confirmation is explicit. Users frequently want to tweak field coverage or
128
+ add aliases at this step — taking the correction once saves a round of
129
+ re-generation.
130
+
131
+ ## 2e. Generate the three bundles
132
+
133
+ Create three LWC bundles (adjust folder layout for the project type
134
+ detected in SKILL.md Step 1 — SFDX uses `force-app/main/default/lwc/`,
135
+ off-core uses `src/lwc/<namespace>/`):
136
+
137
+ ### `chatContextAdapter/` — one per project
138
+
139
+ Reusable across every chat-wrapped component. Exports
140
+ `accessToolOutput()` and `subscribeToolOutput(listener)`. Reads
141
+ `window.openai.toolOutput`, subscribes to `openai:set_globals` and
142
+ `ui/notifications/tool-result` postMessages. If `chatContextAdapter`
143
+ already exists in the project, reuse it — don't regenerate.
144
+
145
+ Copy the reference implementation verbatim from
146
+ `docs/chat-wrapper-guide.md` in the plugin repo. It's stable and should
147
+ not be customized.
148
+
149
+ ### `<component>ChatMapper/` — one per wrapped component
150
+
151
+ Exports `map<Component>ToolOutput(toolOutput)` that returns the confirmed
152
+ prop shape or `null` for invalid payloads. Keep it:
153
+
154
+ - **Pure and side-effect free** — no console logs, no DOM access.
155
+ - **Tolerant of envelope variations** — unwrap `structuredContent`,
156
+ `recordDetail`, `record_detail` where applicable.
157
+ - **Null-on-invalid** — never throw. The wrapper renders a waiting state
158
+ on `null`.
159
+
160
+ Always include the `unwrapPropertiesMap(value)` helper (see 2c) and call
161
+ it before reading any map-shaped field.
162
+
163
+ ### `<component>ChatWrapper/` — one per wrapped component
164
+
165
+ On `connectedCallback`:
166
+
167
+ 1. Call `subscribeToolOutput()` to listen for updates.
168
+ 2. Call `accessToolOutput()` for the initial state.
169
+ 3. Apply both through the mapper.
170
+
171
+ Renders:
172
+
173
+ - `loading` state while initializing
174
+ - `waiting` state when the mapper returns `null`
175
+ - the wrapped component with mapped props when mapping succeeds
176
+
177
+ **Important debugging aid:** Always log the raw initial tool output in
178
+ the wrapper's `connectedCallback`:
179
+
180
+ ```js
181
+ const initial = accessToolOutput();
182
+ console.log(`[${wrapperName}] initial tool output`, initial);
183
+ ```
184
+
185
+ The shape varies by host (Avro/`.properties` wrap, `structuredContent`
186
+ envelope, nested `recordDetail`, etc.). Seeing the real payload once tells
187
+ the user exactly which keys their mapper needs to unwrap. Leave this log
188
+ in by default — removing it makes iterative debugging much harder for
189
+ little benefit.
190
+
191
+ Each bundle needs the matching `.js-meta.xml` (SFDX) with
192
+ `<isExposed>false</isExposed>` unless the user needs it exposed.
193
+
194
+ ### Announce the wrapper tag name
195
+
196
+ Mention the wrapper tag name so it can be referenced in Step 3 if the
197
+ user wants the wrapper itself (or a parent embedding it) as the root:
198
+
199
+ > "Created `bcx-record-detail-chat-wrapper`. You can drop
200
+ > `<bcx-record-detail-chat-wrapper>` into any parent (e.g. your root app
201
+ > component), or pick it directly as the root in the next step."
202
+
203
+ ## Debugging the mapping
204
+
205
+ If the wrapper stays in the "Waiting for tool output..." state even
206
+ though the tool fires, **the initial-payload log is the single most
207
+ useful debugging tool.** Check the DevTools console (or the MCP Jam
208
+ widget log) for the `[…ChatWrapper] initial tool output` line and compare
209
+ the actual shape to what the mapper expects.
210
+
211
+ Common reasons the mapper returns `null`:
212
+
213
+ - **`.properties` wrap:** `records` arrives as
214
+ `{ properties: { "001…": {...} } }` instead of `{ "001…": {...} }`.
215
+ Fix: add/verify `unwrapPropertiesMap` on every map-shaped field.
216
+ - **Envelope mismatch:** payload is wrapped in `structuredContent`,
217
+ `recordDetail`, or `record_detail` and the mapper reads from the top
218
+ level. Fix: add the missing unwrap branch in `unwrapEnvelope`.
219
+ - **Key-alias mismatch:** camelCase vs snake_case (`recordId` vs
220
+ `record_id`, `objectInfos` vs `object_infos`).
221
+ - **Empty fields map:** the tool returned a record with no `fields`,
222
+ usually because the tool's `outputSchema` doesn't project the fields
223
+ the component needs. Fix the tool, not the mapper.
224
+
225
+ Once the real shape is visible, update the mapper's unwraps to match,
226
+ and confirm the mapper returns a non-null object by checking that the
227
+ `[…ChatWrapper] mapper returned null` warning disappears.
@@ -0,0 +1,215 @@
1
+ # Known Pitfalls
2
+
3
+ Consolidated list of bugs and confusions that have surfaced in real
4
+ consumer projects. Read this when setting up a new project, and consult
5
+ it when debugging a project that's already set up but misbehaving.
6
+
7
+ Each entry has a **Symptom**, **Cause**, and **Fix** so the skill can
8
+ recognize an issue from console output alone.
9
+
10
+ ## 1. Node version mismatch (Vite 8 + rolldown)
11
+
12
+ **Symptom:** `vite build` crashes inside `rolldown` with "Cannot find
13
+ native binding. npm has a bug related to optional dependencies."
14
+
15
+ **Cause:** Vite 8.x requires Node `^20.19.0 || >=22.12.0`. On older
16
+ Node 20.x (e.g. 20.14) the rolldown optional dependency for the
17
+ platform binary doesn't install, producing an opaque error instead of a
18
+ clear engine mismatch.
19
+
20
+ **Fix:** Either upgrade Node (`nvm install 20.19` or `22`), or pin
21
+ `vite@^7` and a compatible `@lwc/rollup-plugin` major.
22
+
23
+ ## 2. Hardcoded dep versions (prerelease / alpha unpublished)
24
+
25
+ **Symptom:** `npm install` fails with
26
+ `No matching version found for <pkg>@^1.28.17-alpha` or similar.
27
+
28
+ **Cause:** The consumer guide's package.json snippet includes example
29
+ version pins (like `^1.28.17-alpha` for `lightning-base-components`).
30
+ Prerelease tags get unpublished from the registry over time.
31
+
32
+ **Fix:** Look up the current published version with
33
+ `npm view <pkg> version` and pin to `^<that>` instead of copying from
34
+ the snippet.
35
+
36
+ ## 3. Hand-rolled `lightningGraphql` in `vite.config.js`
37
+
38
+ **Symptom:** `@wire(graphql)` returns empty data; console shows
39
+ `No window.openai.callTool available` or a hand-written
40
+ `[lightningGraphql] ...` log message.
41
+
42
+ **Cause:** The generator treated the "include `builtins.lightningGraphql()`"
43
+ instruction as "write a provider called `lightningGraphql`" and inlined
44
+ a custom provider factory in `vite.config.js` instead of importing
45
+ `builtins` from the plugin. The hand-rolled version bypasses the
46
+ plugin's real runtime (which handles `globalThis.__sfdc_sdk__`,
47
+ `getChatSDK()`, and `normalizeMcpResponse`).
48
+
49
+ **Fix:** Replace with:
50
+
51
+ ```js
52
+ import lwcVitePlugin, { builtins } from "@salesforce/vite-plugin-lwc-ui-bundle";
53
+ // inside lwcVitePlugin({ providers: [...] })
54
+ builtins.lightningGraphql(),
55
+ builtins.lds(),
56
+ ```
57
+
58
+ Same rule for `lds` and every other provider — never hand-write them.
59
+
60
+ ## 4. `this`-binding bug in `lightning/graphql` runtime
61
+
62
+ **Symptom:** `@wire(graphql)` with a class-based SDK (e.g.
63
+ `@salesforce/sdk-data`'s `WebApp`) silently emits
64
+ `{data: undefined, errors: [TypeError]}`; component falls through to an
65
+ empty state.
66
+
67
+ **Cause:** Plugin versions ≤ 1.132.0 destructure `graphql` off
68
+ `globalThis.__sfdc_sdk__` before calling it, which loses the method's
69
+ `this` binding. The method internally calls `this.fetch(...)` and
70
+ blows up.
71
+
72
+ **Fix:** Fixed in webapps PR #477 (plugin version > 1.132.0). Until the
73
+ fix ships in a release, add this to `bootstrap.js` after creating the
74
+ SDK:
75
+
76
+ ```js
77
+ sdk.graphql = sdk.graphql.bind(sdk);
78
+ sdk.fetch = sdk.fetch.bind(sdk);
79
+ globalThis.__sfdc_sdk__ = sdk;
80
+ ```
81
+
82
+ ## 5. Wrong tool name in mock branch (graphql foot-gun)
83
+
84
+ **Symptom:** `@wire(graphql)` returns empty data even though the mock
85
+ shim is installed; console shows "[mock] unhandled tool: graphqlQuery"
86
+ or the graphql mock branch is never entered.
87
+
88
+ **Cause:** The wire adapter calls `callTool("graphqlQuery", { query,
89
+ variables })` by default, but the mock branches on `"graphql"` or
90
+ `"lightningGraphql"`.
91
+
92
+ **Fix:** Name the branch `"graphqlQuery"` (literal default) — or pass
93
+ the same custom name to both the provider and the mock:
94
+
95
+ ```js
96
+ // vite.config.js
97
+ builtins.lightningGraphql({ toolName: "myCustomGraphql" }),
98
+
99
+ // bootstrap.js
100
+ if (name === "myCustomGraphql") { /* ... */ }
101
+ ```
102
+
103
+ The vite-config tool name and the mock-branch string must match
104
+ exactly.
105
+
106
+ ## 6. `.properties` wrap in Avro/JSON-schema-serialized tool output
107
+
108
+ **Symptom:** Chat wrapper renders "Waiting for tool output..." even
109
+ though the host fired tool output; mapper's `Object.keys(records)[0]`
110
+ returns the literal string `"properties"`.
111
+
112
+ **Cause:** Some MCP hosts re-serialize `structuredContent` through an
113
+ Avro / JSON-schema view that wraps every map-typed field in a
114
+ `.properties` key. So `records: { "001…": {...} }` becomes
115
+ `records: { properties: { "001…": {...} } }`.
116
+
117
+ **Fix:** Every map-shaped field (`records`, `fields`, `objectInfos`,
118
+ `layouts`, …) must go through an `unwrapPropertiesMap(value)` helper
119
+ before reading keys:
120
+
121
+ ```js
122
+ function unwrapPropertiesMap(value) {
123
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
124
+ if (value.properties && typeof value.properties === "object") return value.properties;
125
+ return value;
126
+ }
127
+ ```
128
+
129
+ Apply this in every mapper that reads map-shaped fields.
130
+
131
+ ## 7. Missing `viteSingleFile()`
132
+
133
+ **Symptom:** `dist/index.html` is 1-2 KB (just a stub); component
134
+ doesn't render when opened locally.
135
+
136
+ **Cause:** `viteSingleFile()` is not in the Vite plugins array, so
137
+ Vite emits its default multi-file output (HTML stub + separate
138
+ `assets/*.js` + `assets/*.css`).
139
+
140
+ **Fix:** Add to `vite.config.js`:
141
+
142
+ ```js
143
+ import { viteSingleFile } from "vite-plugin-singlefile";
144
+ // ...
145
+ plugins: [
146
+ lwcVitePlugin({ /* ... */ }),
147
+ viteSingleFile(),
148
+ ],
149
+ build: {
150
+ target: "esnext",
151
+ cssCodeSplit: false,
152
+ rollupOptions: { output: { inlineDynamicImports: true } },
153
+ },
154
+ ```
155
+
156
+ A correctly built `dist/index.html` is typically 100 KB+.
157
+
158
+ ## 8. `@salesforce/label/...` warnings
159
+
160
+ **Symptom:** Build warns `Unhandled import: @salesforce/label/...`,
161
+ but the build succeeds.
162
+
163
+ **Cause:** Expected. The plugin returns the label key as display text
164
+ when no override is provided.
165
+
166
+ **Fix:** Not a bug. Add overrides to `builtins.label({...})` to
167
+ customize the displayed text.
168
+
169
+ ## 9. Missing gate / accessCheck providers with lightning-base-components
170
+
171
+ **Symptom:** Build fails with `Cannot read properties of undefined
172
+ (reading 'isOpen')`.
173
+
174
+ **Cause:** `lightning-base-components` use `@salesforce/gate/*` and
175
+ `@salesforce/accessCheck/*` internally, even if user code doesn't
176
+ import them.
177
+
178
+ **Fix:** Always include `builtins.gate()` and `builtins.accessCheck()`
179
+ (and `builtins.primitiveUtils()`) whenever `lightning-base-components`
180
+ is in the modules config.
181
+
182
+ ## 10. Core-only imports
183
+
184
+ **Symptom:** `Rollup failed to resolve import "force/someModule"` (or
185
+ `aura`, `logger`, etc.).
186
+
187
+ **Cause:** Platform-only modules that don't exist off-platform.
188
+
189
+ **Fix:** Provide a stub via the plugin's `stubs` option:
190
+
191
+ ```js
192
+ lwcVitePlugin({
193
+ // ...
194
+ stubs: { "force/someModule": "./stubs/someModule.js" },
195
+ });
196
+ ```
197
+
198
+ Point the stub at a minimal file that exports the shape the importer
199
+ expects.
200
+
201
+ ## 11. Mock data doesn't work in production build
202
+
203
+ **Symptom:** `npm run build && open dist/index.html` shows wrappers in
204
+ "Waiting" state; `sdk.callTool is not available on this surface` in
205
+ the console. Dev server (`npm run dev`) works fine.
206
+
207
+ **Cause:** Rollup hoists module-init code to the top of the bundle.
208
+ `@salesforce/sdk-chat`'s top-level `const surface = detectSurface()`
209
+ runs before `main.js` / `bootstrap.js` installs the mock, so the SDK
210
+ caches `surface = "WebApp"` forever.
211
+
212
+ **Fix:** Not supported — mocks are for `npm run dev` only. Production
213
+ builds only target real MCP hosts (where the host sets `window.openai`
214
+ before the bundle loads, so surface detection correctly resolves to
215
+ `OpenAI`). See `bootstrap-js-patterns.md` for the full explanation.