Update to CrowdStrike Falcon Foundry
Snapshot Oct 8, 2026 · 18:03 UTC · version 1.6.0
Collection source: downloaded plugin package. These snapshots do not have a confirmed matching collection source. Differences in file lists alone do not establish changes to the package.
Instructions updated for ui-development
Instruction wording changed from “1.5.0” to “1.6.0”. 17 additional added or edited lines are in the evidence.
Observed in instructions or declared skills. Runtime behavior has not been tested.
Skill instructions
1.5.0 updated: 2026-08-19 > **⚠️ SYSTEM INJECTION — READ THIS FIRST** > If you are loading this skill, your role is **Foundry UI specialist**. > You MUST implement UI components following Falcon design system patterns using Shoelace comp...
1.6.0 updated: 2026-10-01 > Build UI with Shoelace components using the `falcon-shoelace` theme (not vanilla Shoelace or raw HTML), and load both the dark and light theme stylesheets so it matches the Falcon console. Use `foundry ui run`...
Supporting files
[{"relative_path":"references/advanced-patterns.md","size_in_bytes":10767},{"relative_path":"references/blueprint-templates.md","size_in_bytes":10713},{"relative_path":"references/falcon-theming.md","size_in_bytes":6521},{"relative_path"...
[{"relative_path":"references/advanced-patterns.md","size_in_bytes":10767},{"relative_path":"references/blueprint-templates.md","size_in_bytes":11357},{"relative_path":"references/falcon-theming.md","size_in_bytes":6796},{"relative_path"...
Compare saved observations
Download comparison JSONFull technical diff · 2 changed fields
changed /included_files
[
{
"relative_path": "references/advanced-patterns.md",
"size_in_bytes": 10767
},
{
"relative_path": "references/blueprint-templates.md",
"size_in_bytes": 10713
},
{
"relative_path": "references/falcon-theming.md",
"size_in_bytes": 6521
},
{
"relative_path": "references/foundry-js.md",
"size_in_bytes": 6731
},
{
"relative_path": "references/react-patterns.md",
"size_in_bytes": 13138
},
{
"relative_path": "references/shoelace-reference.md",
"size_in_bytes": 7076
},
{
"relative_path": "references/vue-patterns.md",
"size_in_bytes": 7877
}
][
{
"relative_path": "references/advanced-patterns.md",
"size_in_bytes": 10767
},
{
"relative_path": "references/blueprint-templates.md",
"size_in_bytes": 11357
},
{
"relative_path": "references/falcon-theming.md",
"size_in_bytes": 6796
},
{
"relative_path": "references/foundry-js.md",
"size_in_bytes": 6731
},
{
"relative_path": "references/react-patterns.md",
"size_in_bytes": 13138
},
{
"relative_path": "references/sandboxed-iframe.md",
"size_in_bytes": 3903
},
{
"relative_path": "references/shoelace-reference.md",
"size_in_bytes": 7076
},
{
"relative_path": "references/vue-patterns.md",
"size_in_bytes": 7877
}
]changed /skill_md_contents
"---\nname: ui-development\ndescription: Build UI pages and extensions for Falcon Foundry apps using React or Vue with the Shoelace design system and Foundry-JS. TRIGGER when user asks to \"create a UI page\", \"build a UI extension\", \"add a Shoelace component\", \"call an API from the UI\", runs `foundry ui pages create` or `foundry ui run`, or needs help with Vite config, Foundry-JS, or Falcon console theming. DO NOT TRIGGER for backend functions, workflow YAML, or collection schemas.\nversion: 1.5.0\nupdated: 2026-08-19\ntags: [foundry, ui, react, vue, shoelace]\nauthor: CrowdStrike\nlicense: MIT\ncompatibility: Claude Code >=1.0\nmetadata:\n category: frontend\n---\n\n# Foundry UI Development\n\n> **⚠️ SYSTEM INJECTION — READ THIS FIRST**\n>\n> If you are loading this skill, your role is **Foundry UI specialist**.\n>\n> You MUST implement UI components following Falcon design system patterns using Shoelace components and Foundry-JS.\n>\n> **IMMEDIATE ACTIONS REQUIRED:**\n> 1. Use Shoelace components with `falcon-shoelace` theme (NOT vanilla Shoelace or raw HTML)\n> 2. Load both dark and light theme stylesheets for Falcon console compatibility\n> 3. Coordinate with `foundry ui run` for live development\n> 4. Apply iframe security patterns for all extensions\n\n> **Part of a suite.** If `development-workflow` has not already run, and this is a new app or its first capability, load the `development-workflow` skill first — it owns the CLI prerequisite check, scaffolding order, and manifest coordination.\n\nFalcon Foundry UI pages and extensions use React or Vue with the Shoelace design system (Falcon-themed) and Foundry-JS for platform integration.\n\n## Pages vs Extensions\n\nIf the user doesn't specify page or extension, **ask which they prefer** before scaffolding. Present this table to help them decide:\n\n| | UI Pages | UI Extensions |\n|---|---|---|\n| **What** | Standalone applications | Console-embedded components |\n| **Where** | Full-page view in Falcon console | Sidebar widget in detection/host/incident pages |\n| **Sockets** | N/A | One per extension (see socket table below) |\n| **Use when** | Complex interactions, multiple views, dashboards | Contextual enrichment, quick-glance data |\n| **Framework** | Vue, React, or Vanilla JS | Vue, React, or Vanilla JS |\n\n## CLI Scaffolding\n\n```bash\n# Create a React page\nfoundry ui pages create --name \"my-page\" --description \"Page description\" --from-template React --homepage --no-prompt\n\n# Create a Vanilla JS page (no npm install or build step needed)\nfoundry ui pages create --name \"my-page\" --description \"Page description\" --from-template \"Vanilla JS\" --homepage --no-prompt\n\n# Add navigation entry (separate step — --no-prompt skips this during page creation)\nfoundry ui navigation add --name \"My Page\" --path / --ref pages.my-page\n\n# Create an extension targeting a console socket\n# REQUIRED: --sockets must be specified — omitting it launches an interactive picker that hangs with Error: EOF\n# REQUIRED: Use ONLY values from the Extension Socket Locations table below — do NOT guess socket IDs\nfoundry ui extensions create --name \"my-ext\" --description \"Description\" --from-template React --sockets \"activity.detections.details\" --no-prompt\n\n# Create a Vanilla JS extension (no npm install or build step needed)\nfoundry ui extensions create --name \"my-ext\" --description \"Description\" --from-template \"Vanilla JS\" --sockets \"activity.detections.details\" --no-prompt\n```\n\n**Vanilla JS** needs no `npm install` or `npm run build`. The importmap loads foundry-js from CDN. Use it for simple extensions that display data or make API calls without complex state management. Deploy works with just the raw `src/` files.\n\nThe blueprint output is deterministic — see [references/blueprint-templates.md](references/blueprint-templates.md) for exact file contents, Shoelace import patterns, and API integration calling examples.\n\n> **🚫 DO NOT MODIFY `path` or `entrypoint` in manifest.yml**\n>\n> The CLI sets `path` and `entrypoint` correctly during scaffolding. **Never edit these values.** The correct CLI-generated format uses full paths from the app root:\n> ```yaml\n> # Page — this is CORRECT, do not shorten\n> path: ui/pages/my-page/src/dist\n> entrypoint: ui/pages/my-page/src/dist/index.html\n>\n> # Extension — this is CORRECT, do not shorten\n> path: ui/extensions/my-ext/src/dist\n> entrypoint: ui/extensions/my-ext/src/dist/index.html\n> ```\n> These long paths are NOT doubled — they are the correct values the CLI generates. Shortening `entrypoint` to `src/dist/index.html` breaks the app. If a deploy error mentions entrypoint or file path, you likely changed `vite.config.js` — revert your changes. The scaffolded config is correct.\n\n## Vite Build Configuration\n\n> **🚫 DO NOT MODIFY `vite.config.js`**\n>\n> The React blueprint's `vite.config.js` is **turnkey** — it works correctly as scaffolded. Do not change ANY values in it. Specifically:\n> - **Do not change `base: './'`** — not to `''`, not to `'/'`, not to anything else. `'./'` is correct.\n> - **Do not change `root: 'src'`** — the manifest expects builds at `src/dist/`.\n> - **Do not remove `noAttr()`** — required for Foundry's sandboxed iframe.\n>\n> The blueprint, manifest, and CLI are coordinated. Changing any config value breaks this coordination and causes deploy failures. Just edit your React/JS component code and deploy.\n\n## Shoelace Design System\n\nInstall the Falcon-themed Shoelace package:\n\n```bash\nnpm install @crowdstrike/falcon-shoelace\n```\n\nImport the Falcon-themed stylesheet. The React blueprint's `index.html` already includes this as a `<link>` tag, so **do not add JS imports for it**:\n\n```css\n/* Already in index.html — no JS import needed */\n<link rel=\"stylesheet\" href=\"../node_modules/@crowdstrike/falcon-shoelace/dist/style.css\" />\n```\n\nIf importing from JS (e.g., Vanilla JS apps without the blueprint's index.html):\n\n```typescript\nimport '@crowdstrike/falcon-shoelace/dist/style.css';\n```\n\nSet the Shoelace asset base path:\n\n```typescript\nimport { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';\nsetBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.x/dist/');\n```\n\nUse `var(--sl-*)` design tokens for all styling instead of hardcoding colors. This ensures the UI adapts correctly when users switch between light and dark mode in the Falcon console:\n\n```css\n.my-component {\n color: var(--sl-color-neutral-900);\n background: var(--sl-color-neutral-0);\n padding: var(--sl-spacing-medium);\n border-radius: var(--sl-border-radius-medium);\n}\n```\n\nFor extended Shoelace component catalog and CSS customization, see [references/shoelace-reference.md](references/shoelace-reference.md).\n\nFor theming, dark/light mode switching, and design token values, see [references/falcon-theming.md](references/falcon-theming.md).\n\n## Foundry-JS\n\n```javascript\nimport FalconApi from '@crowdstrike/foundry-js';\n\nconst falcon = new FalconApi();\nawait falcon.connect();\n\n// Apply Falcon console theme\nconst theme = await falcon.theme();\ndocument.documentElement.classList.add(`sl-theme-${theme}`);\n```\n\n> **⚠️ `connect()` is async.** In React, `falcon.connect()` must be called inside a `useEffect` and navigation must only be accessed AFTER connect resolves. Use React state (`isInitialized`) as the `useMemo` dependency — not `falcon.isConnected` (which is a plain object property, not reactive state):\n>\n> ```jsx\n> const [isInitialized, setIsInitialized] = useState(false);\n> const falcon = useMemo(() => new FalconApi(), []);\n> const navigation = useMemo(() => {\n> return isInitialized ? falcon.navigation : undefined;\n> }, [isInitialized]);\n> ```\n>\n> Gate child rendering on `isInitialized` state. See [references/react-patterns.md](references/react-patterns.md) for the full `FalconApiProvider` pattern.\n\n### Calling API Integrations\n\nUse `falcon.apiIntegration()` for third-party APIs. Use `falcon.api.get()` for CrowdStrike Falcon APIs.\n\n```javascript\nconst apiIntegration = falcon.apiIntegration({\n definitionId: 'Okta', // Must match name in manifest.yml api_integrations\n operationId: 'listUsers' // Must match operationId in OpenAPI spec\n});\nconst result = await apiIntegration.execute({ request: { params: {} } });\n\n// Response is wrapped: access via resources[0]\nconst body = result.resources?.[0]?.response_body;\nconst status = result.resources?.[0]?.status_code;\n```\n\nQuery and path parameters must match the types declared in the OpenAPI spec. `execute()` types params as `Record<string, unknown>`, so a quoted number compiles and ships fine, then fails server-side schema validation at runtime with `got string want integer`. The extension still renders, so this looks like an API or credential error rather than a bug in your code.\n\n```javascript\n// spec declares: - name: limit / schema: { type: integer }\nrequest: { params: { query: { limit: 25 } } } // ✅ number\nrequest: { params: { query: { limit: '25' } } } // ❌ got string want integer\n```\n\nCheck the `schema.type` of each parameter in the spec before passing it. Numbers and booleans are unquoted; only `type: string` parameters take quotes.\n\n### Collection Operations\n\n```javascript\nconst collection = falcon.collection({ collection: 'my_collection' });\n\n// CRUD operations\nawait collection.write('item-key', { name: 'Item 1', status: 'active' });\nconst item = await collection.read('item-key');\nawait collection.delete('item-key');\n\n// List all items (returns IDs, then read each)\nconst result = await collection.list({ start: 0, limit: 100 });\nconst itemIds = result.resources || [];\nfor (const id of itemIds) {\n const item = await collection.read(id);\n // Add 50ms delay between reads to avoid rate limiting\n}\n\n// Search with FQL filter\nconst results = await collection.search({ filter: \"status:'active'\" });\n```\n\n### Workflow Execution\n\n```javascript\n// Execute an on-demand workflow by name\nconst triggerResult = await falcon.api.workflows.postEntitiesExecuteV1(\n { user_name: 'Developer' }, // workflow input parameters\n { name: 'My Workflow', depth: 0 } // config: workflow name + depth\n);\n\n// Poll for results using the execution ID\nconst execId = triggerResult.resources?.[0];\nconst result = await falcon.api.workflows.getEntitiesExecutionResultsV1({\n ids: [execId]\n});\nconst execution = result.resources?.[0];\n// Poll until execution.status is 'Completed', 'Failed', or 'Cancelled'\n```\n\n### Cloud Functions\n\n```javascript\nconst cloudFunction = falcon.cloudFunction({\n name: 'my_function',\n version: 1\n});\n\n// Fluent API — chain .path() with HTTP method\nconst result = await cloudFunction.path('/greet').post({ name: 'User' });\nconst data = await cloudFunction.path('/items?status=active').get();\nawait cloudFunction.path('/items/123').delete();\n```\n\n### LogScale\n\n```javascript\n// Write events\nawait falcon.logscale.write({ event_type: 'user_login', username: 'jdoe' });\n\n// Dynamic query\nconst results = await falcon.logscale.query({\n name: 'LogScaleRepo',\n search_query: 'event_type=user_login',\n start: '24h',\n end: 'now'\n});\n\n// Saved query\nconst saved = await falcon.logscale.savedQuery({\n id: 'saved-query-id',\n start: '7d',\n mode: 'sync',\n parameters: {}\n});\n```\n\n### Events\n\n```javascript\n// Listen to Falcon console events (data, connect, disconnect, error, navigation)\nfalcon.events.on('data', (data) => console.log('Data event:', data));\nfalcon.events.on('navigation', (data) => console.log('Nav event:', data));\n\n// Clean up listeners on unmount\nfalcon.events.off('data', handler);\n```\n\nFor full React component examples, see [references/react-patterns.md](references/react-patterns.md).\nFor full Vue component examples, see [references/vue-patterns.md](references/vue-patterns.md).\n\n## Development Servers\n\n- **`foundry ui run`**: Serves only the UI in Falcon console dev mode (port 25678). Use during UI-focused development.\n- **`foundry apps run`**: Starts the full app locally (validates manifest on startup). Use when testing UI + functions + integrations together.\n\n**CRITICAL: Run from the app root directory.** Both `foundry ui run` and `foundry apps run` resolve manifest paths relative to `os.Getwd()`. After `cd`-ing into a UI page/extension directory for `npm install && npm run build`, always `cd` back to the app root before running any `foundry` app commands. Running from a subdirectory causes \"file not found\" or doubled-path errors.\n\nIf the UI calls API integrations, collections, or functions, deploy those backend capabilities first via `foundry apps deploy`. `foundry ui run` only serves UI assets locally — backend capabilities resolve from the cloud.\n\n### Development Mode vs Preview Mode\n\nToggle via the **Developer tools** (`</>`) icon in the Falcon console toolbar:\n\n| Feature | Development Mode | Preview Mode |\n|---------|-----------------|--------------|\n| Activation | `foundry ui run` + enable in Developer tools | Deploy, then enable in Developer tools |\n| Source | Local build from your machine (polls localhost:25678) | Deployed build in the cloud |\n| Purpose | Live UI iteration with hot reload | Test deployed UI before release |\n\nOnly one mode at a time. Disable one before enabling the other.\n\n## Iframe Communication\n\nExtensions must validate message origins:\n\n```typescript\nconst allowedOrigins = [\n 'https://falcon.crowdstrike.com',\n 'https://falcon.eu-1.crowdstrike.com',\n 'https://falcon.us-gov-1.crowdstrike.com',\n];\n\nwindow.addEventListener('message', (event: MessageEvent) => {\n if (!allowedOrigins.includes(event.origin)) return;\n // Process event.data\n});\n```\n\n## Extension Socket Locations\n\nRun `foundry ui extensions list-sockets` to get the current list of available sockets. Use the **technical ID** (not human-readable name) with `--sockets`. The extension renders in the detail panel reached by the navigation below — open an individual record (detection, case, host, lead, or execution) to see it.\n\n| Display Name | Technical ID for `--sockets` | Console Navigation |\n|-------------|------------------------------|--------------------|\n| Endpoint detection details | `activity.detections.details` | Endpoint security › Monitor › Endpoint detections |\n| Identity Protection detection details | `identity.detections.details` | Identity protection › Detections |\n| Next-Gen SIEM cases details | `xdr.cases.panel` | Next-Gen SIEM › Cases |\n| Next-Gen SIEM workbench details | `ngsiem.workbench.details` | Next-Gen SIEM › Cases › open a case › workbench graph canvas › click a node |\n| Host management host details | `hosts.host.panel` | Host setup and management › Host management |\n| Automated leads details | `automated-leads.leads.details` | Next-Gen SIEM › Automated leads |\n| Workflow execution details | `workflows.executions.execution.details` | Fusion SOAR › Workflows › open an execution |\n\n## Common Pitfalls\n\n> **Note:** The first three pitfalls below about `vite.config.js`, `npm install`, and `npm run build` apply to the React template only. Vanilla JS extensions have no build step — deploy the raw `src/` files directly.\n\n- **Running `foundry` commands from a UI subdirectory.** After `cd ui/extensions/my-ext && npm install && npm run build`, you MUST `cd` back to the app root before running `foundry apps validate`, `foundry apps deploy`, or `foundry ui run`. The CLI resolves manifest paths relative to cwd — running from a subdirectory produces doubled paths like `ui/extensions/my-ext/ui/extensions/my-ext/src/dist/index.html`.\n- **NEVER edit manifest.yml `path` or `entrypoint` values.** The CLI sets these correctly. The format `ui/extensions/my-ext/src/dist/index.html` is NOT a doubled path — it is correct. If you see a deploy path error, you likely changed `vite.config.js` — revert your changes.\n- **NEVER modify `vite.config.js`.** The blueprint is turnkey. Do not change `base: './'` to `''` or anything else. Do not change `root: 'src'`. Do not remove `noAttr()`. Just edit your React/JS code and deploy.\n- **Omitting `--sockets` on extension create.** This launches an interactive picker that hangs with `Error: EOF`. Always provide `--sockets \"socket.id\"` on the command line. Run `foundry ui extensions list-sockets` to see available sockets — do not guess or fabricate socket names.\n- **Importing vanilla Shoelace themes.** Use `@crowdstrike/falcon-shoelace` for Falcon console styling.\n- **Loading only light theme.** The Falcon console supports dark mode — users see broken styling without both themes.\n- **Hardcoding colors.** Use `var(--sl-*)` design tokens so the UI adapts to theme changes.\n- **Expecting backend to work with `foundry ui run`.** The dev server only serves UI — deploy backend capabilities first.\n- **Shoelace dialogs/drawers white in dark mode.** Override `--sl-panel-background-color` and `--sl-color-neutral-0` with `var(--ground-floor)`. See [references/shoelace-reference.md](references/shoelace-reference.md).\n- **Using Tailwind arbitrary values with prebuilt toucan CSS.** Values like `max-h-[400px]` require JIT compilation. Use inline styles instead when using the prebuilt `tailwind-toucan-base/index.css`.\n- **Quoting numeric query parameters.** `execute()` accepts `Record<string, unknown>`, so `limit: '25'` passes type-checking and fails server-side with `got string want integer`. Match the `schema.type` declared in the OpenAPI spec — the extension still renders, so this reads as an API error rather than a code bug.\n- **Missing CSP for Shoelace icons.** The Foundry CSP only allows `assets.foundry.crowdstrike.com`. If using `setBasePath()` with `cdn.jsdelivr.net`, you must add it to `connect-src` and `img-src` in the manifest's `content_security_policy`. Alternatively, copy icon assets to your `dist/` folder and set a relative base path to avoid CDN dependencies entirely.\n\n## Reading Guide\n\n| Task | Reference |\n|------|-----------|\n| Blueprint file contents, editing strategy | [references/blueprint-templates.md](references/blueprint-templates.md) |\n| Shoelace component catalog, CSS customization | [references/shoelace-reference.md](references/shoelace-reference.md) |\n| Dark/light theming, design tokens | [references/falcon-theming.md](references/falcon-theming.md) |\n| React component examples | [references/react-patterns.md](references/react-patterns.md) |\n| Vue component examples | [references/vue-patterns.md](references/vue-patterns.md) |\n| Foundry-JS: workflows, LogScale, cloud functions, collections CRUD | [references/foundry-js.md](references/foundry-js.md) |\n| Framework selection, ExtensionMessaging, E2E testing, Extension Builder, CSP, dev server coordination | [references/advanced-patterns.md](references/advanced-patterns.md) |\n\n## Use Cases\n\nFor real-world implementation patterns, see:\n- `use-cases/detection-enrichment.md` — UI extensions for detection enrichment\n- `use-cases/first-app.md` — Getting started with Foundry apps\n\n## Reference Implementations\n\n- **[foundry-sample-foundryjs-demo](https://github.com/CrowdStrike/foundry-sample-foundryjs-demo)**: Comprehensive Foundry-JS demo (API integrations, collections, workflows, LogScale, cloud functions, events, navigation, modals)\n- **[foundry-sample-mitre](https://github.com/CrowdStrike/foundry-sample-mitre)**: Vue shared components, multi-view app\n- **[foundry-sample-collections-toolkit](https://github.com/CrowdStrike/foundry-sample-collections-toolkit)**: UI for collections\n- **[foundry-sample-functions-python](https://github.com/CrowdStrike/foundry-sample-functions-python)**: UI calling functions\n- **[foundry-sample-logscale](https://github.com/CrowdStrike/foundry-sample-logscale)**: Vanilla JS + foundry-js page\n- **[foundry-sample-detection-translation](https://github.com/CrowdStrike/foundry-sample-detection-translation)**: Detection context UI extension\n""---\nname: ui-development\ndescription: Build UI pages and extensions for Falcon Foundry apps using React or Vue with the Shoelace design system and Foundry-JS. TRIGGER when user asks to \"create a UI page\", \"build a UI extension\", \"add a Shoelace component\", \"call an API from the UI\", runs `foundry ui pages create` or `foundry ui run`, or needs help with Vite config, Foundry-JS, or Falcon console theming. DO NOT TRIGGER for backend functions, workflow YAML, or collection schemas.\nversion: 1.6.0\nupdated: 2026-10-01\ntags: [foundry, ui, react, vue, shoelace]\nauthor: CrowdStrike\nlicense: MIT\ncompatibility: Claude Code >=1.0\nmetadata:\n category: frontend\n---\n\n# Foundry UI Development\n\n> Build UI with Shoelace components using the `falcon-shoelace` theme (not vanilla Shoelace or raw HTML), and load both the dark and light theme stylesheets so it matches the Falcon console. Use `foundry ui run` for live development, and apply the iframe security patterns to every extension.\n\n> **Part of a suite.** If `development-workflow` has not already run, and this is a new app or its first capability, load the `development-workflow` skill first — it owns the CLI prerequisite check, scaffolding order, and manifest coordination.\n\nFalcon Foundry UI pages and extensions use React or Vue with the Shoelace design system (Falcon-themed) and Foundry-JS for platform integration.\n\n## Pages vs Extensions\n\nIf the user doesn't specify page or extension, **ask which they prefer** before scaffolding. Present this table to help them decide:\n\n| | UI Pages | UI Extensions |\n|---|---|---|\n| **What** | Standalone applications | Console-embedded components |\n| **Where** | Full-page view in Falcon console | Sidebar widget in detection/host/incident pages |\n| **Sockets** | N/A | One per extension (see socket table below) |\n| **Use when** | Complex interactions, multiple views, dashboards | Contextual enrichment, quick-glance data |\n| **Framework** | Vue, React, or Vanilla JS | Vue, React, or Vanilla JS |\n\n## CLI Scaffolding\n\n```bash\n# Create a React page\nfoundry ui pages create --name \"my-page\" --description \"Page description\" --from-template React --homepage --no-prompt\n\n# Create a Vanilla JS page (no npm install or build step needed)\nfoundry ui pages create --name \"my-page\" --description \"Page description\" --from-template \"Vanilla JS\" --homepage --no-prompt\n\n# Add navigation entry (separate step — --no-prompt skips this during page creation)\nfoundry ui navigation add --name \"My Page\" --path / --ref pages.my-page\n\n# Create an extension targeting a console socket\n# REQUIRED: --sockets must be specified — omitting it launches an interactive picker that hangs with Error: EOF\n# REQUIRED: Use ONLY values from the Extension Socket Locations table below — do NOT guess socket IDs\nfoundry ui extensions create --name \"my-ext\" --description \"Description\" --from-template React --sockets \"activity.detections.details\" --no-prompt\n\n# Create a Vanilla JS extension (no npm install or build step needed)\nfoundry ui extensions create --name \"my-ext\" --description \"Description\" --from-template \"Vanilla JS\" --sockets \"activity.detections.details\" --no-prompt\n```\n\n**Vanilla JS** needs no `npm install` or `npm run build`. The importmap loads foundry-js from CDN. Use it for simple extensions that display data or make API calls without complex state management. Deploy works with just the raw `src/` files.\n\nThe blueprint output is deterministic — see [references/blueprint-templates.md](references/blueprint-templates.md) for exact file contents, Shoelace import patterns, and API integration calling examples.\n\n> **🚫 DO NOT MODIFY `path` or `entrypoint` in manifest.yml**\n>\n> The CLI sets `path` and `entrypoint` correctly during scaffolding. **Never edit these values.** The correct CLI-generated format uses full paths from the app root:\n> ```yaml\n> # Page — this is CORRECT, do not shorten\n> path: ui/pages/my-page/src/dist\n> entrypoint: ui/pages/my-page/src/dist/index.html\n>\n> # Extension — this is CORRECT, do not shorten\n> path: ui/extensions/my-ext/src/dist\n> entrypoint: ui/extensions/my-ext/src/dist/index.html\n> ```\n> These long paths are NOT doubled — they are the correct values the CLI generates. Shortening `entrypoint` to `src/dist/index.html` breaks the app. If a deploy error mentions entrypoint or file path, you likely changed `vite.config.js` — revert your changes. The scaffolded config is correct.\n\n## Vite Build Configuration\n\n> **🚫 DO NOT MODIFY `vite.config.js`**\n>\n> The React blueprint's `vite.config.js` is **turnkey** — it works correctly as scaffolded. Do not change ANY values in it. Specifically:\n> - **Do not change `base: './'`** — not to `''`, not to `'/'`, not to anything else. `'./'` is correct.\n> - **Do not change `root: 'src'`** — the manifest expects builds at `src/dist/`.\n> - **Do not remove `noAttr()`** — required for Foundry's sandboxed iframe.\n>\n> The blueprint, manifest, and CLI are coordinated. Changing any config value breaks this coordination and causes deploy failures. Just edit your React/JS component code and deploy.\n\n## Shoelace Design System\n\nInstall the Falcon-themed Shoelace package:\n\n```bash\nnpm install @crowdstrike/falcon-shoelace\n```\n\nImport the Falcon-themed stylesheet. The React blueprint's `index.html` already includes this as a `<link>` tag, so **do not add JS imports for it**:\n\n```css\n/* Already in index.html — no JS import needed */\n<link rel=\"stylesheet\" href=\"../node_modules/@crowdstrike/falcon-shoelace/dist/style.css\" />\n```\n\nIf importing from JS (e.g., Vanilla JS apps without the blueprint's index.html):\n\n```typescript\nimport '@crowdstrike/falcon-shoelace/dist/style.css';\n```\n\nSet the Shoelace asset base path:\n\n```typescript\nimport { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';\nsetBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.x/dist/');\n```\n\nUse `var(--sl-*)` design tokens for all styling instead of hardcoding colors. This ensures the UI adapts correctly when users switch between light and dark mode in the Falcon console:\n\n```css\n.my-component {\n color: var(--sl-color-neutral-900);\n background: var(--sl-color-neutral-0);\n padding: var(--sl-spacing-medium);\n border-radius: var(--sl-border-radius-medium);\n}\n```\n\nFor extended Shoelace component catalog and CSS customization, see [references/shoelace-reference.md](references/shoelace-reference.md).\n\nFor theming, dark/light mode switching, and design token values, see [references/falcon-theming.md](references/falcon-theming.md).\n\n## Foundry-JS\n\n```javascript\nimport FalconApi from '@crowdstrike/foundry-js';\n\nconst falcon = new FalconApi();\nawait falcon.connect(); // also applies the console theme (see below)\n```\n\nThere is no `falcon.theme()` method. `connect()` puts `theme-light` or `theme-dark` on `<html>` (the value is also in `falcon.data.theme`) and swaps it whenever the console sends a new `data` message, which includes theme changes. `@crowdstrike/falcon-shoelace` styles off those same two classes, so Shoelace components follow the console with no theme code. Only your own CSS that keys off something else needs to react; listen for `falcon.events.on('data', ...)` or watch the class attribute:\n\n```javascript\nconst consoleTheme = () =>\n document.documentElement.classList.contains('theme-light') ? 'light' : 'dark';\nnew MutationObserver(() => applyTheme(consoleTheme()))\n .observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\n```\n\n> **⚠️ `connect()` is async.** In React, `falcon.connect()` must be called inside a `useEffect` and navigation must only be accessed AFTER connect resolves. Use React state (`isInitialized`) as the `useMemo` dependency — not `falcon.isConnected` (which is a plain object property, not reactive state):\n>\n> ```jsx\n> const [isInitialized, setIsInitialized] = useState(false);\n> const falcon = useMemo(() => new FalconApi(), []);\n> const navigation = useMemo(() => {\n> return isInitialized ? falcon.navigation : undefined;\n> }, [isInitialized]);\n> ```\n>\n> Gate child rendering on `isInitialized` state. See [references/react-patterns.md](references/react-patterns.md) for the full `FalconApiProvider` pattern.\n\n### Calling API Integrations\n\nUse `falcon.apiIntegration()` for third-party APIs. Use `falcon.api.get()` for CrowdStrike Falcon APIs.\n\n```javascript\nconst apiIntegration = falcon.apiIntegration({\n definitionId: 'Okta', // Must match name in manifest.yml api_integrations\n operationId: 'listUsers' // Must match operationId in OpenAPI spec\n});\nconst result = await apiIntegration.execute({ request: { params: {} } });\n\n// Response is wrapped: access via resources[0]\nconst body = result.resources?.[0]?.response_body;\nconst status = result.resources?.[0]?.status_code;\n```\n\nQuery and path parameters must match the types declared in the OpenAPI spec. `execute()` types params as `Record<string, unknown>`, so a quoted number compiles and ships fine, then fails server-side schema validation at runtime with `got string want integer`. The extension still renders, so this looks like an API or credential error rather than a bug in your code.\n\n```javascript\n// spec declares: - name: limit / schema: { type: integer }\nrequest: { params: { query: { limit: 25 } } } // ✅ number\nrequest: { params: { query: { limit: '25' } } } // ❌ got string want integer\n```\n\nCheck the `schema.type` of each parameter in the spec before passing it. Numbers and booleans are unquoted; only `type: string` parameters take quotes.\n\n### Collection Operations\n\n```javascript\nconst collection = falcon.collection({ collection: 'my_collection' });\n\n// CRUD operations\nawait collection.write('item-key', { name: 'Item 1', status: 'active' });\nconst item = await collection.read('item-key');\nawait collection.delete('item-key');\n\n// List all items (returns IDs, then read each)\nconst result = await collection.list({ start: 0, limit: 100 });\nconst itemIds = result.resources || [];\nfor (const id of itemIds) {\n const item = await collection.read(id);\n // Add 50ms delay between reads to avoid rate limiting\n}\n\n// Search with FQL filter\nconst results = await collection.search({ filter: \"status:'active'\" });\n```\n\n### Workflow Execution\n\n```javascript\n// Execute an on-demand workflow by name\nconst triggerResult = await falcon.api.workflows.postEntitiesExecuteV1(\n { user_name: 'Developer' }, // workflow input parameters\n { name: 'My Workflow', depth: 0 } // config: workflow name + depth\n);\n\n// Poll for results using the execution ID\nconst execId = triggerResult.resources?.[0];\nconst result = await falcon.api.workflows.getEntitiesExecutionResultsV1({\n ids: [execId]\n});\nconst execution = result.resources?.[0];\n// Poll until execution.status is 'Completed', 'Failed', or 'Cancelled'\n```\n\n### Cloud Functions\n\n```javascript\nconst cloudFunction = falcon.cloudFunction({\n name: 'my_function',\n version: 1\n});\n\n// Fluent API — chain .path() with HTTP method\nconst result = await cloudFunction.path('/greet').post({ name: 'User' });\nconst data = await cloudFunction.path('/items?status=active').get();\nawait cloudFunction.path('/items/123').delete();\n```\n\n### LogScale\n\n```javascript\n// Write events\nawait falcon.logscale.write({ event_type: 'user_login', username: 'jdoe' });\n\n// Dynamic query\nconst results = await falcon.logscale.query({\n name: 'LogScaleRepo',\n search_query: 'event_type=user_login',\n start: '24h',\n end: 'now'\n});\n\n// Saved query\nconst saved = await falcon.logscale.savedQuery({\n id: 'saved-query-id',\n start: '7d',\n mode: 'sync',\n parameters: {}\n});\n```\n\n### Events\n\n```javascript\n// Listen to Falcon console events (data, connect, disconnect, error, navigation)\nfalcon.events.on('data', (data) => console.log('Data event:', data));\nfalcon.events.on('navigation', (data) => console.log('Nav event:', data));\n\n// Clean up listeners on unmount\nfalcon.events.off('data', handler);\n```\n\nFor full React component examples, see [references/react-patterns.md](references/react-patterns.md).\nFor full Vue component examples, see [references/vue-patterns.md](references/vue-patterns.md).\n\n## Development Servers\n\n- **`foundry ui run`**: Serves only the UI in Falcon console dev mode (port 25678). Use during UI-focused development.\n- **`foundry apps run`**: Starts the full app locally (validates manifest on startup). Use when testing UI + functions + integrations together.\n\n**CRITICAL: Run from the app root directory.** Both `foundry ui run` and `foundry apps run` resolve manifest paths relative to `os.Getwd()`. After `cd`-ing into a UI page/extension directory for `npm install && npm run build`, always `cd` back to the app root before running any `foundry` app commands. Running from a subdirectory causes \"file not found\" or doubled-path errors.\n\nIf the UI calls API integrations, collections, or functions, deploy those backend capabilities first via `foundry apps deploy`. `foundry ui run` only serves UI assets locally — backend capabilities resolve from the cloud.\n\n### Development Mode vs Preview Mode\n\nToggle via the **Developer tools** (`</>`) icon in the Falcon console toolbar:\n\n| Feature | Development Mode | Preview Mode |\n|---------|-----------------|--------------|\n| Activation | `foundry ui run` + enable in Developer tools | Deploy, then enable in Developer tools |\n| Source | Local build from your machine (polls localhost:25678) | Deployed build in the cloud |\n| Purpose | Live UI iteration with hot reload | Test deployed UI before release |\n\nOnly one mode at a time. Disable one before enabling the other.\n\n## Sandboxed Iframe Restrictions\n\nPages and extensions run in an iframe sandboxed with `allow-scripts allow-forms allow-downloads` only:\n\n- **No `allow-same-origin`:** `localStorage`, `sessionStorage`, `document.cookie`, and `indexedDB` throw `SecurityError`. Keep state in memory or a collection.\n- **No `allow-modals`:** `alert()`, `confirm()`, `prompt()`, and `beforeunload` prompts are silently ignored — `confirm()` returns `false`. Show errors inline and confirm with `<sl-dialog>`.\n- **Page lifetime:** a loop over many items stops starting new calls when the user leaves. Save per item, make re-runs resume, or move the loop into a workflow.\n\nSee [references/sandboxed-iframe.md](references/sandboxed-iframe.md) for the errors, a storage fallback, and a resumable bulk-processing pattern.\n\n## Iframe Communication\n\nExtensions must validate message origins:\n\n```typescript\nconst allowedOrigins = [\n 'https://falcon.crowdstrike.com',\n 'https://falcon.eu-1.crowdstrike.com',\n 'https://falcon.us-gov-1.crowdstrike.com',\n];\n\nwindow.addEventListener('message', (event: MessageEvent) => {\n if (!allowedOrigins.includes(event.origin)) return;\n // Process event.data\n});\n```\n\n## Extension Socket Locations\n\nRun `foundry ui extensions list-sockets` to get the current list of available sockets. Use the **technical ID** (not human-readable name) with `--sockets`. The extension renders in the detail panel reached by the navigation below — open an individual record (detection, case, host, lead, or execution) to see it.\n\n| Display Name | Technical ID for `--sockets` | Console Navigation |\n|-------------|------------------------------|--------------------|\n| Endpoint detection details | `activity.detections.details` | Endpoint security › Monitor › Endpoint detections |\n| Identity Protection detection details | `identity.detections.details` | Identity protection › Detections |\n| Next-Gen SIEM cases details | `xdr.cases.panel` | Next-Gen SIEM › Cases |\n| Next-Gen SIEM workbench details | `ngsiem.workbench.details` | Next-Gen SIEM › Cases › open a case › workbench graph canvas › click a node |\n| Host management host details | `hosts.host.panel` | Host setup and management › Host management |\n| Automated leads details | `automated-leads.leads.details` | Next-Gen SIEM › Automated leads |\n| Workflow execution details | `workflows.executions.execution.details` | Fusion SOAR › Workflows › open an execution |\n\n## Common Pitfalls\n\n> **Note:** The first three pitfalls below about `vite.config.js`, `npm install`, and `npm run build` apply to the React template only. Vanilla JS extensions have no build step — deploy the raw `src/` files directly.\n\n- **Running `foundry` commands from a UI subdirectory.** After `cd ui/extensions/my-ext && npm install && npm run build`, you MUST `cd` back to the app root before running `foundry apps validate`, `foundry apps deploy`, or `foundry ui run`. The CLI resolves manifest paths relative to cwd — running from a subdirectory produces doubled paths like `ui/extensions/my-ext/ui/extensions/my-ext/src/dist/index.html`.\n- **NEVER edit manifest.yml `path` or `entrypoint` values.** The CLI sets these correctly. The format `ui/extensions/my-ext/src/dist/index.html` is NOT a doubled path — it is correct. If you see a deploy path error, you likely changed `vite.config.js` — revert your changes.\n- **NEVER modify `vite.config.js`.** The blueprint is turnkey. Do not change `base: './'` to `''` or anything else. Do not change `root: 'src'`. Do not remove `noAttr()`. Just edit your React/JS code and deploy.\n- **Omitting `--sockets` on extension create.** This launches an interactive picker that hangs with `Error: EOF`. Always provide `--sockets \"socket.id\"` on the command line. Run `foundry ui extensions list-sockets` to see available sockets — do not guess or fabricate socket names.\n- **Importing vanilla Shoelace themes.** Use `@crowdstrike/falcon-shoelace` for Falcon console styling.\n- **Loading only light theme.** The Falcon console supports dark mode — users see broken styling without both themes.\n- **Hardcoding colors.** Use `var(--sl-*)` design tokens so the UI adapts to theme changes.\n- **Expecting backend to work with `foundry ui run`.** The dev server only serves UI — deploy backend capabilities first.\n- **Shoelace dialogs/drawers white in dark mode.** Override `--sl-panel-background-color` and `--sl-color-neutral-0` with `var(--ground-floor)`. See [references/shoelace-reference.md](references/shoelace-reference.md).\n- **Using Tailwind arbitrary values with prebuilt toucan CSS.** Values like `max-h-[400px]` require JIT compilation. Use inline styles instead when using the prebuilt `tailwind-toucan-base/index.css`.\n- **Quoting numeric query parameters.** `execute()` accepts `Record<string, unknown>`, so `limit: '25'` passes type-checking and fails server-side with `got string want integer`. Match the `schema.type` declared in the OpenAPI spec — the extension still renders, so this reads as an API error rather than a code bug.\n- **Reading `localStorage` or `sessionStorage`.** The sandbox lacks `allow-same-origin`, so any access throws a `SecurityError` (see [Sandboxed Iframe Restrictions](#sandboxed-iframe-restrictions)). Use in-memory state or a collection; wrap legacy calls in `try/catch`.\n- **Reporting errors with `alert()` or guarding actions with `confirm()`.** The sandbox lacks `allow-modals`, so both are silently ignored: errors vanish and `confirm()`-guarded actions never run. Use inline errors and `<sl-dialog>`.\n- **Missing CSP for Shoelace icons.** The Foundry CSP only allows `assets.foundry.crowdstrike.com`. If using `setBasePath()` with `cdn.jsdelivr.net`, you must add it to `connect-src` and `img-src` in the manifest's `content_security_policy`. Alternatively, copy icon assets to your `dist/` folder and set a relative base path to avoid CDN dependencies entirely.\n\n## Reading Guide\n\n| Task | Reference |\n|------|-----------|\n| Blueprint file contents, editing strategy | [references/blueprint-templates.md](references/blueprint-templates.md) |\n| Shoelace component catalog, CSS customization | [references/shoelace-reference.md](references/shoelace-reference.md) |\n| Dark/light theming, design tokens | [references/falcon-theming.md](references/falcon-theming.md) |\n| React component examples | [references/react-patterns.md](references/react-patterns.md) |\n| Vue component examples | [references/vue-patterns.md](references/vue-patterns.md) |\n| Foundry-JS: workflows, LogScale, cloud functions, collections CRUD | [references/foundry-js.md](references/foundry-js.md) |\n| Sandbox restrictions (storage, modals), long-running page loops | [references/sandboxed-iframe.md](references/sandboxed-iframe.md) |\n| Framework selection, ExtensionMessaging, E2E testing, Extension Builder, CSP, dev server coordination | [references/advanced-patterns.md](references/advanced-patterns.md) |\n\n## Use Cases\n\nFor real-world implementation patterns, see:\n- `use-cases/detection-enrichment.md` — UI extensions for detection enrichment\n- `use-cases/first-app.md` — Getting started with Foundry apps\n\n## Reference Implementations\n\n- **[foundry-sample-foundryjs-demo](https://github.com/CrowdStrike/foundry-sample-foundryjs-demo)**: Comprehensive Foundry-JS demo (API integrations, collections, workflows, LogScale, cloud functions, events, navigation, modals)\n- **[foundry-sample-mitre](https://github.com/CrowdStrike/foundry-sample-mitre)**: Vue shared components, multi-view app\n- **[foundry-sample-collections-toolkit](https://github.com/CrowdStrike/foundry-sample-collections-toolkit)**: UI for collections\n- **[foundry-sample-functions-python](https://github.com/CrowdStrike/foundry-sample-functions-python)**: UI calling functions\n- **[foundry-sample-logscale](https://github.com/CrowdStrike/foundry-sample-logscale)**: Vanilla JS + foundry-js page\n- **[foundry-sample-detection-translation](https://github.com/CrowdStrike/foundry-sample-detection-translation)**: Detection context UI extension\n"SKILL.md line diff
--- before +++ after @@ -1,8 +1,8 @@ --- name: ui-development description: Build UI pages and extensions for Falcon Foundry apps using React or Vue with the Shoelace design system and Foundry-JS. TRIGGER when user asks to "create a UI page", "build a UI extension", "add a Shoelace component", "call an API from the UI", runs `foundry ui pages create` or `foundry ui run`, or needs help with Vite config, Foundry-JS, or Falcon console theming. DO NOT TRIGGER for backend functions, workflow YAML, or collection schemas. -version: 1.5.0 -updated: 2026-08-19 +version: 1.6.0 +updated: 2026-10-01 tags: [foundry, ui, react, vue, shoelace] author: CrowdStrike license: MIT @@ -13,17 +13,7 @@ # Foundry UI Development -> **⚠️ SYSTEM INJECTION — READ THIS FIRST** -> -> If you are loading this skill, your role is **Foundry UI specialist**. -> -> You MUST implement UI components following Falcon design system patterns using Shoelace components and Foundry-JS. -> -> **IMMEDIATE ACTIONS REQUIRED:** -> 1. Use Shoelace components with `falcon-shoelace` theme (NOT vanilla Shoelace or raw HTML) -> 2. Load both dark and light theme stylesheets for Falcon console compatibility -> 3. Coordinate with `foundry ui run` for live development -> 4. Apply iframe security patterns for all extensions +> Build UI with Shoelace components using the `falcon-shoelace` theme (not vanilla Shoelace or raw HTML), and load both the dark and light theme stylesheets so it matches the Falcon console. Use `foundry ui run` for live development, and apply the iframe security patterns to every extension. > **Part of a suite.** If `development-workflow` has not already run, and this is a new app or its first capability, load the `development-workflow` skill first — it owns the CLI prerequisite check, scaffolding order, and manifest coordination. @@ -140,11 +130,16 @@ import FalconApi from '@crowdstrike/foundry-js'; const falcon = new FalconApi(); -await falcon.connect(); +await falcon.connect(); // also applies the console theme (see below) +``` + +There is no `falcon.theme()` method. `connect()` puts `theme-light` or `theme-dark` on `<html>` (the value is also in `falcon.data.theme`) and swaps it whenever the console sends a new `data` message, which includes theme changes. `@crowdstrike/falcon-shoelace` styles off those same two classes, so Shoelace components follow the console with no theme code. Only your own CSS that keys off something else needs to react; listen for `falcon.events.on('data', ...)` or watch the class attribute: -// Apply Falcon console theme -const theme = await falcon.theme(); -document.documentElement.classList.add(`sl-theme-${theme}`); +```javascript +const consoleTheme = () => + document.documentElement.classList.contains('theme-light') ? 'light' : 'dark'; +new MutationObserver(() => applyTheme(consoleTheme())) + .observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); ``` > **⚠️ `connect()` is async.** In React, `falcon.connect()` must be called inside a `useEffect` and navigation must only be accessed AFTER connect resolves. Use React state (`isInitialized`) as the `useMemo` dependency — not `falcon.isConnected` (which is a plain object property, not reactive state): @@ -297,6 +292,16 @@ Only one mode at a time. Disable one before enabling the other. +## Sandboxed Iframe Restrictions + +Pages and extensions run in an iframe sandboxed with `allow-scripts allow-forms allow-downloads` only: + +- **No `allow-same-origin`:** `localStorage`, `sessionStorage`, `document.cookie`, and `indexedDB` throw `SecurityError`. Keep state in memory or a collection. +- **No `allow-modals`:** `alert()`, `confirm()`, `prompt()`, and `beforeunload` prompts are silently ignored — `confirm()` returns `false`. Show errors inline and confirm with `<sl-dialog>`. +- **Page lifetime:** a loop over many items stops starting new calls when the user leaves. Save per item, make re-runs resume, or move the loop into a workflow. + +See [references/sandboxed-iframe.md](references/sandboxed-iframe.md) for the errors, a storage fallback, and a resumable bulk-processing pattern. + ## Iframe Communication Extensions must validate message origins: @@ -343,6 +348,8 @@ - **Shoelace dialogs/drawers white in dark mode.** Override `--sl-panel-background-color` and `--sl-color-neutral-0` with `var(--ground-floor)`. See [references/shoelace-reference.md](references/shoelace-reference.md). - **Using Tailwind arbitrary values with prebuilt toucan CSS.** Values like `max-h-[400px]` require JIT compilation. Use inline styles instead when using the prebuilt `tailwind-toucan-base/index.css`. - **Quoting numeric query parameters.** `execute()` accepts `Record<string, unknown>`, so `limit: '25'` passes type-checking and fails server-side with `got string want integer`. Match the `schema.type` declared in the OpenAPI spec — the extension still renders, so this reads as an API error rather than a code bug. +- **Reading `localStorage` or `sessionStorage`.** The sandbox lacks `allow-same-origin`, so any access throws a `SecurityError` (see [Sandboxed Iframe Restrictions](#sandboxed-iframe-restrictions)). Use in-memory state or a collection; wrap legacy calls in `try/catch`. +- **Reporting errors with `alert()` or guarding actions with `confirm()`.** The sandbox lacks `allow-modals`, so both are silently ignored: errors vanish and `confirm()`-guarded actions never run. Use inline errors and `<sl-dialog>`. - **Missing CSP for Shoelace icons.** The Foundry CSP only allows `assets.foundry.crowdstrike.com`. If using `setBasePath()` with `cdn.jsdelivr.net`, you must add it to `connect-src` and `img-src` in the manifest's `content_security_policy`. Alternatively, copy icon assets to your `dist/` folder and set a relative base path to avoid CDN dependencies entirely. ## Reading Guide @@ -355,6 +362,7 @@ | React component examples | [references/react-patterns.md](references/react-patterns.md) | | Vue component examples | [references/vue-patterns.md](references/vue-patterns.md) | | Foundry-JS: workflows, LogScale, cloud functions, collections CRUD | [references/foundry-js.md](references/foundry-js.md) | +| Sandbox restrictions (storage, modals), long-running page loops | [references/sandboxed-iframe.md](references/sandboxed-iframe.md) | | Framework selection, ExtensionMessaging, E2E testing, Extension Builder, CSP, dev server coordination | [references/advanced-patterns.md](references/advanced-patterns.md) | ## Use Cases
Full snapshot data
{
"description": "Build UI pages and extensions for Falcon Foundry apps using React or Vue with the Shoelace design system and Foundry-JS. TRIGGER when user asks to \"create a UI page\", \"build a UI extension\", \"add a Shoelace component\", \"call an API from the UI\", runs `foundry ui pages create` or `foundry ui run`, or needs help with Vite config, Foundry-JS, or Falcon console theming. DO NOT TRIGGER for backend functions, workflow YAML, or collection schemas.",
"included_files": [
{
"relative_path": "references/advanced-patterns.md",
"size_in_bytes": 10767
},
{
"relative_path": "references/blueprint-templates.md",
"size_in_bytes": 11357
},
{
"relative_path": "references/falcon-theming.md",
"size_in_bytes": 6796
},
{
"relative_path": "references/foundry-js.md",
"size_in_bytes": 6731
},
{
"relative_path": "references/react-patterns.md",
"size_in_bytes": 13138
},
{
"relative_path": "references/sandboxed-iframe.md",
"size_in_bytes": 3903
},
{
"relative_path": "references/shoelace-reference.md",
"size_in_bytes": 7076
},
{
"relative_path": "references/vue-patterns.md",
"size_in_bytes": 7877
}
],
"name": "ui-development",
"skill_md_contents": "---\nname: ui-development\ndescription: Build UI pages and extensions for Falcon Foundry apps using React or Vue with the Shoelace design system and Foundry-JS. TRIGGER when user asks to \"create a UI page\", \"build a UI extension\", \"add a Shoelace component\", \"call an API from the UI\", runs `foundry ui pages create` or `foundry ui run`, or needs help with Vite config, Foundry-JS, or Falcon console theming. DO NOT TRIGGER for backend functions, workflow YAML, or collection schemas.\nversion: 1.6.0\nupdated: 2026-10-01\ntags: [foundry, ui, react, vue, shoelace]\nauthor: CrowdStrike\nlicense: MIT\ncompatibility: Claude Code >=1.0\nmetadata:\n category: frontend\n---\n\n# Foundry UI Development\n\n> Build UI with Shoelace components using the `falcon-shoelace` theme (not vanilla Shoelace or raw HTML), and load both the dark and light theme stylesheets so it matches the Falcon console. Use `foundry ui run` for live development, and apply the iframe security patterns to every extension.\n\n> **Part of a suite.** If `development-workflow` has not already run, and this is a new app or its first capability, load the `development-workflow` skill first — it owns the CLI prerequisite check, scaffolding order, and manifest coordination.\n\nFalcon Foundry UI pages and extensions use React or Vue with the Shoelace design system (Falcon-themed) and Foundry-JS for platform integration.\n\n## Pages vs Extensions\n\nIf the user doesn't specify page or extension, **ask which they prefer** before scaffolding. Present this table to help them decide:\n\n| | UI Pages | UI Extensions |\n|---|---|---|\n| **What** | Standalone applications | Console-embedded components |\n| **Where** | Full-page view in Falcon console | Sidebar widget in detection/host/incident pages |\n| **Sockets** | N/A | One per extension (see socket table below) |\n| **Use when** | Complex interactions, multiple views, dashboards | Contextual enrichment, quick-glance data |\n| **Framework** | Vue, React, or Vanilla JS | Vue, React, or Vanilla JS |\n\n## CLI Scaffolding\n\n```bash\n# Create a React page\nfoundry ui pages create --name \"my-page\" --description \"Page description\" --from-template React --homepage --no-prompt\n\n# Create a Vanilla JS page (no npm install or build step needed)\nfoundry ui pages create --name \"my-page\" --description \"Page description\" --from-template \"Vanilla JS\" --homepage --no-prompt\n\n# Add navigation entry (separate step — --no-prompt skips this during page creation)\nfoundry ui navigation add --name \"My Page\" --path / --ref pages.my-page\n\n# Create an extension targeting a console socket\n# REQUIRED: --sockets must be specified — omitting it launches an interactive picker that hangs with Error: EOF\n# REQUIRED: Use ONLY values from the Extension Socket Locations table below — do NOT guess socket IDs\nfoundry ui extensions create --name \"my-ext\" --description \"Description\" --from-template React --sockets \"activity.detections.details\" --no-prompt\n\n# Create a Vanilla JS extension (no npm install or build step needed)\nfoundry ui extensions create --name \"my-ext\" --description \"Description\" --from-template \"Vanilla JS\" --sockets \"activity.detections.details\" --no-prompt\n```\n\n**Vanilla JS** needs no `npm install` or `npm run build`. The importmap loads foundry-js from CDN. Use it for simple extensions that display data or make API calls without complex state management. Deploy works with just the raw `src/` files.\n\nThe blueprint output is deterministic — see [references/blueprint-templates.md](references/blueprint-templates.md) for exact file contents, Shoelace import patterns, and API integration calling examples.\n\n> **🚫 DO NOT MODIFY `path` or `entrypoint` in manifest.yml**\n>\n> The CLI sets `path` and `entrypoint` correctly during scaffolding. **Never edit these values.** The correct CLI-generated format uses full paths from the app root:\n> ```yaml\n> # Page — this is CORRECT, do not shorten\n> path: ui/pages/my-page/src/dist\n> entrypoint: ui/pages/my-page/src/dist/index.html\n>\n> # Extension — this is CORRECT, do not shorten\n> path: ui/extensions/my-ext/src/dist\n> entrypoint: ui/extensions/my-ext/src/dist/index.html\n> ```\n> These long paths are NOT doubled — they are the correct values the CLI generates. Shortening `entrypoint` to `src/dist/index.html` breaks the app. If a deploy error mentions entrypoint or file path, you likely changed `vite.config.js` — revert your changes. The scaffolded config is correct.\n\n## Vite Build Configuration\n\n> **🚫 DO NOT MODIFY `vite.config.js`**\n>\n> The React blueprint's `vite.config.js` is **turnkey** — it works correctly as scaffolded. Do not change ANY values in it. Specifically:\n> - **Do not change `base: './'`** — not to `''`, not to `'/'`, not to anything else. `'./'` is correct.\n> - **Do not change `root: 'src'`** — the manifest expects builds at `src/dist/`.\n> - **Do not remove `noAttr()`** — required for Foundry's sandboxed iframe.\n>\n> The blueprint, manifest, and CLI are coordinated. Changing any config value breaks this coordination and causes deploy failures. Just edit your React/JS component code and deploy.\n\n## Shoelace Design System\n\nInstall the Falcon-themed Shoelace package:\n\n```bash\nnpm install @crowdstrike/falcon-shoelace\n```\n\nImport the Falcon-themed stylesheet. The React blueprint's `index.html` already includes this as a `<link>` tag, so **do not add JS imports for it**:\n\n```css\n/* Already in index.html — no JS import needed */\n<link rel=\"stylesheet\" href=\"../node_modules/@crowdstrike/falcon-shoelace/dist/style.css\" />\n```\n\nIf importing from JS (e.g., Vanilla JS apps without the blueprint's index.html):\n\n```typescript\nimport '@crowdstrike/falcon-shoelace/dist/style.css';\n```\n\nSet the Shoelace asset base path:\n\n```typescript\nimport { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';\nsetBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.x/dist/');\n```\n\nUse `var(--sl-*)` design tokens for all styling instead of hardcoding colors. This ensures the UI adapts correctly when users switch between light and dark mode in the Falcon console:\n\n```css\n.my-component {\n color: var(--sl-color-neutral-900);\n background: var(--sl-color-neutral-0);\n padding: var(--sl-spacing-medium);\n border-radius: var(--sl-border-radius-medium);\n}\n```\n\nFor extended Shoelace component catalog and CSS customization, see [references/shoelace-reference.md](references/shoelace-reference.md).\n\nFor theming, dark/light mode switching, and design token values, see [references/falcon-theming.md](references/falcon-theming.md).\n\n## Foundry-JS\n\n```javascript\nimport FalconApi from '@crowdstrike/foundry-js';\n\nconst falcon = new FalconApi();\nawait falcon.connect(); // also applies the console theme (see below)\n```\n\nThere is no `falcon.theme()` method. `connect()` puts `theme-light` or `theme-dark` on `<html>` (the value is also in `falcon.data.theme`) and swaps it whenever the console sends a new `data` message, which includes theme changes. `@crowdstrike/falcon-shoelace` styles off those same two classes, so Shoelace components follow the console with no theme code. Only your own CSS that keys off something else needs to react; listen for `falcon.events.on('data', ...)` or watch the class attribute:\n\n```javascript\nconst consoleTheme = () =>\n document.documentElement.classList.contains('theme-light') ? 'light' : 'dark';\nnew MutationObserver(() => applyTheme(consoleTheme()))\n .observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\n```\n\n> **⚠️ `connect()` is async.** In React, `falcon.connect()` must be called inside a `useEffect` and navigation must only be accessed AFTER connect resolves. Use React state (`isInitialized`) as the `useMemo` dependency — not `falcon.isConnected` (which is a plain object property, not reactive state):\n>\n> ```jsx\n> const [isInitialized, setIsInitialized] = useState(false);\n> const falcon = useMemo(() => new FalconApi(), []);\n> const navigation = useMemo(() => {\n> return isInitialized ? falcon.navigation : undefined;\n> }, [isInitialized]);\n> ```\n>\n> Gate child rendering on `isInitialized` state. See [references/react-patterns.md](references/react-patterns.md) for the full `FalconApiProvider` pattern.\n\n### Calling API Integrations\n\nUse `falcon.apiIntegration()` for third-party APIs. Use `falcon.api.get()` for CrowdStrike Falcon APIs.\n\n```javascript\nconst apiIntegration = falcon.apiIntegration({\n definitionId: 'Okta', // Must match name in manifest.yml api_integrations\n operationId: 'listUsers' // Must match operationId in OpenAPI spec\n});\nconst result = await apiIntegration.execute({ request: { params: {} } });\n\n// Response is wrapped: access via resources[0]\nconst body = result.resources?.[0]?.response_body;\nconst status = result.resources?.[0]?.status_code;\n```\n\nQuery and path parameters must match the types declared in the OpenAPI spec. `execute()` types params as `Record<string, unknown>`, so a quoted number compiles and ships fine, then fails server-side schema validation at runtime with `got string want integer`. The extension still renders, so this looks like an API or credential error rather than a bug in your code.\n\n```javascript\n// spec declares: - name: limit / schema: { type: integer }\nrequest: { params: { query: { limit: 25 } } } // ✅ number\nrequest: { params: { query: { limit: '25' } } } // ❌ got string want integer\n```\n\nCheck the `schema.type` of each parameter in the spec before passing it. Numbers and booleans are unquoted; only `type: string` parameters take quotes.\n\n### Collection Operations\n\n```javascript\nconst collection = falcon.collection({ collection: 'my_collection' });\n\n// CRUD operations\nawait collection.write('item-key', { name: 'Item 1', status: 'active' });\nconst item = await collection.read('item-key');\nawait collection.delete('item-key');\n\n// List all items (returns IDs, then read each)\nconst result = await collection.list({ start: 0, limit: 100 });\nconst itemIds = result.resources || [];\nfor (const id of itemIds) {\n const item = await collection.read(id);\n // Add 50ms delay between reads to avoid rate limiting\n}\n\n// Search with FQL filter\nconst results = await collection.search({ filter: \"status:'active'\" });\n```\n\n### Workflow Execution\n\n```javascript\n// Execute an on-demand workflow by name\nconst triggerResult = await falcon.api.workflows.postEntitiesExecuteV1(\n { user_name: 'Developer' }, // workflow input parameters\n { name: 'My Workflow', depth: 0 } // config: workflow name + depth\n);\n\n// Poll for results using the execution ID\nconst execId = triggerResult.resources?.[0];\nconst result = await falcon.api.workflows.getEntitiesExecutionResultsV1({\n ids: [execId]\n});\nconst execution = result.resources?.[0];\n// Poll until execution.status is 'Completed', 'Failed', or 'Cancelled'\n```\n\n### Cloud Functions\n\n```javascript\nconst cloudFunction = falcon.cloudFunction({\n name: 'my_function',\n version: 1\n});\n\n// Fluent API — chain .path() with HTTP method\nconst result = await cloudFunction.path('/greet').post({ name: 'User' });\nconst data = await cloudFunction.path('/items?status=active').get();\nawait cloudFunction.path('/items/123').delete();\n```\n\n### LogScale\n\n```javascript\n// Write events\nawait falcon.logscale.write({ event_type: 'user_login', username: 'jdoe' });\n\n// Dynamic query\nconst results = await falcon.logscale.query({\n name: 'LogScaleRepo',\n search_query: 'event_type=user_login',\n start: '24h',\n end: 'now'\n});\n\n// Saved query\nconst saved = await falcon.logscale.savedQuery({\n id: 'saved-query-id',\n start: '7d',\n mode: 'sync',\n parameters: {}\n});\n```\n\n### Events\n\n```javascript\n// Listen to Falcon console events (data, connect, disconnect, error, navigation)\nfalcon.events.on('data', (data) => console.log('Data event:', data));\nfalcon.events.on('navigation', (data) => console.log('Nav event:', data));\n\n// Clean up listeners on unmount\nfalcon.events.off('data', handler);\n```\n\nFor full React component examples, see [references/react-patterns.md](references/react-patterns.md).\nFor full Vue component examples, see [references/vue-patterns.md](references/vue-patterns.md).\n\n## Development Servers\n\n- **`foundry ui run`**: Serves only the UI in Falcon console dev mode (port 25678). Use during UI-focused development.\n- **`foundry apps run`**: Starts the full app locally (validates manifest on startup). Use when testing UI + functions + integrations together.\n\n**CRITICAL: Run from the app root directory.** Both `foundry ui run` and `foundry apps run` resolve manifest paths relative to `os.Getwd()`. After `cd`-ing into a UI page/extension directory for `npm install && npm run build`, always `cd` back to the app root before running any `foundry` app commands. Running from a subdirectory causes \"file not found\" or doubled-path errors.\n\nIf the UI calls API integrations, collections, or functions, deploy those backend capabilities first via `foundry apps deploy`. `foundry ui run` only serves UI assets locally — backend capabilities resolve from the cloud.\n\n### Development Mode vs Preview Mode\n\nToggle via the **Developer tools** (`</>`) icon in the Falcon console toolbar:\n\n| Feature | Development Mode | Preview Mode |\n|---------|-----------------|--------------|\n| Activation | `foundry ui run` + enable in Developer tools | Deploy, then enable in Developer tools |\n| Source | Local build from your machine (polls localhost:25678) | Deployed build in the cloud |\n| Purpose | Live UI iteration with hot reload | Test deployed UI before release |\n\nOnly one mode at a time. Disable one before enabling the other.\n\n## Sandboxed Iframe Restrictions\n\nPages and extensions run in an iframe sandboxed with `allow-scripts allow-forms allow-downloads` only:\n\n- **No `allow-same-origin`:** `localStorage`, `sessionStorage`, `document.cookie`, and `indexedDB` throw `SecurityError`. Keep state in memory or a collection.\n- **No `allow-modals`:** `alert()`, `confirm()`, `prompt()`, and `beforeunload` prompts are silently ignored — `confirm()` returns `false`. Show errors inline and confirm with `<sl-dialog>`.\n- **Page lifetime:** a loop over many items stops starting new calls when the user leaves. Save per item, make re-runs resume, or move the loop into a workflow.\n\nSee [references/sandboxed-iframe.md](references/sandboxed-iframe.md) for the errors, a storage fallback, and a resumable bulk-processing pattern.\n\n## Iframe Communication\n\nExtensions must validate message origins:\n\n```typescript\nconst allowedOrigins = [\n 'https://falcon.crowdstrike.com',\n 'https://falcon.eu-1.crowdstrike.com',\n 'https://falcon.us-gov-1.crowdstrike.com',\n];\n\nwindow.addEventListener('message', (event: MessageEvent) => {\n if (!allowedOrigins.includes(event.origin)) return;\n // Process event.data\n});\n```\n\n## Extension Socket Locations\n\nRun `foundry ui extensions list-sockets` to get the current list of available sockets. Use the **technical ID** (not human-readable name) with `--sockets`. The extension renders in the detail panel reached by the navigation below — open an individual record (detection, case, host, lead, or execution) to see it.\n\n| Display Name | Technical ID for `--sockets` | Console Navigation |\n|-------------|------------------------------|--------------------|\n| Endpoint detection details | `activity.detections.details` | Endpoint security › Monitor › Endpoint detections |\n| Identity Protection detection details | `identity.detections.details` | Identity protection › Detections |\n| Next-Gen SIEM cases details | `xdr.cases.panel` | Next-Gen SIEM › Cases |\n| Next-Gen SIEM workbench details | `ngsiem.workbench.details` | Next-Gen SIEM › Cases › open a case › workbench graph canvas › click a node |\n| Host management host details | `hosts.host.panel` | Host setup and management › Host management |\n| Automated leads details | `automated-leads.leads.details` | Next-Gen SIEM › Automated leads |\n| Workflow execution details | `workflows.executions.execution.details` | Fusion SOAR › Workflows › open an execution |\n\n## Common Pitfalls\n\n> **Note:** The first three pitfalls below about `vite.config.js`, `npm install`, and `npm run build` apply to the React template only. Vanilla JS extensions have no build step — deploy the raw `src/` files directly.\n\n- **Running `foundry` commands from a UI subdirectory.** After `cd ui/extensions/my-ext && npm install && npm run build`, you MUST `cd` back to the app root before running `foundry apps validate`, `foundry apps deploy`, or `foundry ui run`. The CLI resolves manifest paths relative to cwd — running from a subdirectory produces doubled paths like `ui/extensions/my-ext/ui/extensions/my-ext/src/dist/index.html`.\n- **NEVER edit manifest.yml `path` or `entrypoint` values.** The CLI sets these correctly. The format `ui/extensions/my-ext/src/dist/index.html` is NOT a doubled path — it is correct. If you see a deploy path error, you likely changed `vite.config.js` — revert your changes.\n- **NEVER modify `vite.config.js`.** The blueprint is turnkey. Do not change `base: './'` to `''` or anything else. Do not change `root: 'src'`. Do not remove `noAttr()`. Just edit your React/JS code and deploy.\n- **Omitting `--sockets` on extension create.** This launches an interactive picker that hangs with `Error: EOF`. Always provide `--sockets \"socket.id\"` on the command line. Run `foundry ui extensions list-sockets` to see available sockets — do not guess or fabricate socket names.\n- **Importing vanilla Shoelace themes.** Use `@crowdstrike/falcon-shoelace` for Falcon console styling.\n- **Loading only light theme.** The Falcon console supports dark mode — users see broken styling without both themes.\n- **Hardcoding colors.** Use `var(--sl-*)` design tokens so the UI adapts to theme changes.\n- **Expecting backend to work with `foundry ui run`.** The dev server only serves UI — deploy backend capabilities first.\n- **Shoelace dialogs/drawers white in dark mode.** Override `--sl-panel-background-color` and `--sl-color-neutral-0` with `var(--ground-floor)`. See [references/shoelace-reference.md](references/shoelace-reference.md).\n- **Using Tailwind arbitrary values with prebuilt toucan CSS.** Values like `max-h-[400px]` require JIT compilation. Use inline styles instead when using the prebuilt `tailwind-toucan-base/index.css`.\n- **Quoting numeric query parameters.** `execute()` accepts `Record<string, unknown>`, so `limit: '25'` passes type-checking and fails server-side with `got string want integer`. Match the `schema.type` declared in the OpenAPI spec — the extension still renders, so this reads as an API error rather than a code bug.\n- **Reading `localStorage` or `sessionStorage`.** The sandbox lacks `allow-same-origin`, so any access throws a `SecurityError` (see [Sandboxed Iframe Restrictions](#sandboxed-iframe-restrictions)). Use in-memory state or a collection; wrap legacy calls in `try/catch`.\n- **Reporting errors with `alert()` or guarding actions with `confirm()`.** The sandbox lacks `allow-modals`, so both are silently ignored: errors vanish and `confirm()`-guarded actions never run. Use inline errors and `<sl-dialog>`.\n- **Missing CSP for Shoelace icons.** The Foundry CSP only allows `assets.foundry.crowdstrike.com`. If using `setBasePath()` with `cdn.jsdelivr.net`, you must add it to `connect-src` and `img-src` in the manifest's `content_security_policy`. Alternatively, copy icon assets to your `dist/` folder and set a relative base path to avoid CDN dependencies entirely.\n\n## Reading Guide\n\n| Task | Reference |\n|------|-----------|\n| Blueprint file contents, editing strategy | [references/blueprint-templates.md](references/blueprint-templates.md) |\n| Shoelace component catalog, CSS customization | [references/shoelace-reference.md](references/shoelace-reference.md) |\n| Dark/light theming, design tokens | [references/falcon-theming.md](references/falcon-theming.md) |\n| React component examples | [references/react-patterns.md](references/react-patterns.md) |\n| Vue component examples | [references/vue-patterns.md](references/vue-patterns.md) |\n| Foundry-JS: workflows, LogScale, cloud functions, collections CRUD | [references/foundry-js.md](references/foundry-js.md) |\n| Sandbox restrictions (storage, modals), long-running page loops | [references/sandboxed-iframe.md](references/sandboxed-iframe.md) |\n| Framework selection, ExtensionMessaging, E2E testing, Extension Builder, CSP, dev server coordination | [references/advanced-patterns.md](references/advanced-patterns.md) |\n\n## Use Cases\n\nFor real-world implementation patterns, see:\n- `use-cases/detection-enrichment.md` — UI extensions for detection enrichment\n- `use-cases/first-app.md` — Getting started with Foundry apps\n\n## Reference Implementations\n\n- **[foundry-sample-foundryjs-demo](https://github.com/CrowdStrike/foundry-sample-foundryjs-demo)**: Comprehensive Foundry-JS demo (API integrations, collections, workflows, LogScale, cloud functions, events, navigation, modals)\n- **[foundry-sample-mitre](https://github.com/CrowdStrike/foundry-sample-mitre)**: Vue shared components, multi-view app\n- **[foundry-sample-collections-toolkit](https://github.com/CrowdStrike/foundry-sample-collections-toolkit)**: UI for collections\n- **[foundry-sample-functions-python](https://github.com/CrowdStrike/foundry-sample-functions-python)**: UI calling functions\n- **[foundry-sample-logscale](https://github.com/CrowdStrike/foundry-sample-logscale)**: Vanilla JS + foundry-js page\n- **[foundry-sample-detection-translation](https://github.com/CrowdStrike/foundry-sample-detection-translation)**: Detection context UI extension\n"
}SHA-256 of public snapshot: 80f263649028190b8725f94108fcf5f949ea6e135430f60d820029f215ebdabf