{"id":18991,"plugin_id":"plugins_6a8f6d7f7fac819191e3eba5a7a2e0df","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:15:08.010Z","digest":"30c7b1f5245177f84599741e62d6491a33d2eb56811eeb981f010ef9dff57167","against":null,"payload":{"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.","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}],"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"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}