{"templateId":"markdown","sharedDataIds":{},"props":{"metadata":{"markdoc":{"tagList":["ConnectorCatalog"]},"type":"markdown"},"seo":{"title":"Marketplace navigation and searchable catalog — implementation plan","siteUrl":"https://developers.frontegg.com/","llmstxt":{"title":"Frontegg Developer Documentation","description":"Authentication, authorization, and user management for Customer Identity, plus Agen for SaaS and Agen for Work. Guides, SDKs, and API references.","sections":[{"title":"Customer Identity (CIAM)","description":"Auth, SSO, SCIM, entitlements, and user management — guides, SDKs, and APIs.","includeFiles":["ciam/**/*.md"],"excludeFiles":[]},{"title":"Agen for SaaS","description":"Agentic access and authorization for SaaS products.","includeFiles":["agen-for-saas/**/*.md"],"excludeFiles":[]},{"title":"Agen for Work","description":"Agentic access and authorization for internal and workforce use.","includeFiles":["agen-for-work/**/*.md"],"excludeFiles":[]},{"title":"Platform","description":"Shared platform overview.","includeFiles":["platform/**/*.md"],"excludeFiles":[]}],"excludeFiles":["internal-docs/**","ciam/guides/env-settings/inject-client-ip.md","CLAUDE.md",".claude/**","**/images/**"],"hide":false}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"marketplace-navigation-and-searchable-catalog--implementation-plan","__idx":0},"children":["Marketplace navigation and searchable catalog — implementation plan"]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["For agentic workers:"]}," REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["- [ ]"]},") syntax for tracking."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Spec:"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["docs/superpowers/specs/2026-05-13-marketplace-search-design.md"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Goal:"]}," Replace the 96-item flat marketplace sidebar with category groups, and add a searchable catalog page driven by per-file frontmatter and a generated TypeScript data module."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Architecture:"]}," Single source of truth = ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["category:"]}," frontmatter in each marketplace markdown. A Node generator scans frontmatter and emits ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["connectors.generated.ts"]},", which a new Markdoc-registered React component (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<ConnectorCatalog />"]},") imports. The sidebar is restructured into 22 category subgroups via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/sidebars.yaml"]},". A CI validator enforces frontmatter correctness and data-file freshness."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Tech Stack:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Node 22 with the built-in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node:test"]}," runner (no extra test deps)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["gray-matter"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["js-yaml"]}," (already present via Redocly toolchain)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["React 19 + ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["styled-components"]}," (already used by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ProductCard"]},")"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Redocly Realm Markdoc components"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"file-map","__idx":1},"children":["File map"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Created:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/categories.ts"]}," — ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CONNECTOR_CATEGORIES"]}," array and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectorCategory"]}," type"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/connectors.generated.ts"]}," — auto-generated ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CONNECTORS"]}," array (committed)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/ConnectorCatalog.tsx"]}," — React UI"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/ConnectorCatalog.markdoc.ts"]}," — Markdoc schema"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/index.ts"]}," — barrel export"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/generate-connectors.mjs"]}," — frontmatter → data file generator"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/generate-connectors.test.mjs"]}," — unit tests (node:test)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/check-marketplace-frontmatter.mjs"]}," — validator (CI)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/check-marketplace-frontmatter.test.mjs"]}," — unit tests (node:test)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/bootstrap-connector-categories.mjs"]}," — one-time, deleted after PR"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/bootstrap-connector-categories.test.mjs"]}," — one-time, deleted after PR"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/connectors/marketplace.md"]}," — landing page with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{% ConnectorCatalog /%}"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Modified:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/schema.ts"]}," — register ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectorCatalog"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components.tsx"]}," — register ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectorCatalog"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/sidebars.yaml"]}," — replace flat Marketplace list with 22 category subgroups"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/connectors/marketplace/*.md"]}," (96 files) — prepend frontmatter with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["category:"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["package.json"]}," — add ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["generate:connectors"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["check:marketplace-frontmatter"]}," npm scripts"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Not modified:"]}," any non-marketplace markdown; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly.yaml"]},"; CSS theme files (component uses inline styled-components only)."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-ordering","__idx":2},"children":["Task ordering"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Pre-flight checks (deps, sample frontmatter format)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["categories.ts"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Generator (TDD)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Validator (TDD)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["package.json"]}," npm scripts wiring"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Bootstrap script (TDD, one-shot run)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Run bootstrap → commit 96 frontmatter additions"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Run generator → commit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["connectors.generated.ts"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Markdoc schema + React component"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Register component"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Landing page"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Sidebar restructure"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Final validation + smoke test"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each task ends with one atomic commit."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-1-pre-flight-checks","__idx":3},"children":["Task 1: Pre-flight checks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}," none modified — verification only."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Confirm Node version is ≥18 (built-in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node:test"]}," requires it)"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --version"]}," ","Expected: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["v22.x"]}," or higher."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Confirm ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["gray-matter"]}," is already installed"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ls node_modules/gray-matter/index.js"]}," ","Expected: file exists. If missing, run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm install --save-dev gray-matter"]},"."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Confirm ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["js-yaml"]}," is already installed"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ls node_modules/js-yaml/index.js"]}," ","Expected: file exists. If missing, run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm install --save-dev js-yaml"]},"."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Confirm no marketplace MD currently has frontmatter"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"count=0\nfor f in agen-for-work/connectors/marketplace/*.md; do\n  head -1 \"$f\" | grep -q '^---' && count=$((count+1))\ndone\necho \"files with frontmatter: $count\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Expected: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["files with frontmatter: 0"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If the count is non-zero, stop and reconcile manually before proceeding — the bootstrap (Task 6) assumes a clean slate."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["No commit."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-2-create-categoriests","__idx":4},"children":["Task 2: Create ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["categories.ts"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/categories.ts"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Create the directory and file"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/categories.ts"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Source of truth: dashboard repo,\n// app/v2/src/tools/stores/envStore/workforceApp/workforceApp.types.ts\n// → `ConnectorCategory` enum (as of 2026-05-13).\n// Refresh manually when the product taxonomy changes.\n\nexport const CONNECTOR_CATEGORIES = [\n  'Productivity',\n  'Communication',\n  'Project Management',\n  'CRM',\n  'Development',\n  'Storage',\n  'Finance',\n  'Customer Support',\n  'Marketing',\n  'E-commerce',\n  'Analytics',\n  'Monitoring',\n  'DevOps',\n  'HR',\n  'Video Conferencing',\n  'Scheduling',\n  'Identity',\n  'Design',\n  'Forms',\n  'IT Service Management',\n  'Documents',\n  'Automation',\n  'Data',\n  'Social Media',\n  'Feature Flags',\n  'Content Management',\n  'Sales Intelligence',\n  'Product Management',\n  'Infrastructure',\n  'AI',\n] as const\n\nexport type ConnectorCategory = (typeof CONNECTOR_CATEGORIES)[number]\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Verify TypeScript compiles"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npx tsc --noEmit @theme/markdoc/components/ConnectorCatalog/categories.ts"]}," ","Expected: no output, exit code 0."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tsc"]}," complains about lack of a tsconfig context, run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npx tsc --noEmit"]}," from the repo root and confirm the file appears in the program's emit."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add @theme/markdoc/components/ConnectorCatalog/categories.ts\ngit commit -m \"feat(theme): add ConnectorCategory taxonomy for marketplace catalog\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-3-tdd-generate-connectorsmjs","__idx":5},"children":["Task 3: TDD ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["generate-connectors.mjs"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/generate-connectors.mjs"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Test: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/generate-connectors.test.mjs"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Write the failing test"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/generate-connectors.test.mjs"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"import { test } from 'node:test'\nimport assert from 'node:assert/strict'\nimport { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { generateConnectors } from './generate-connectors.mjs'\n\nfunction makeFixture() {\n  const root = mkdtempSync(join(tmpdir(), 'connectors-test-'))\n  const dir = join(root, 'marketplace')\n  mkdirSync(dir, { recursive: true })\n  writeFileSync(\n    join(dir, 'slack.md'),\n    `---\\ncategory: Communication\\n---\\n\\n## Slack integration\\n`,\n  )\n  writeFileSync(\n    join(dir, 'monday.md'),\n    `---\\ncategory: Project Management\\ndisplayName: monday.com\\n---\\n\\n## monday.com integration\\n`,\n  )\n  writeFileSync(\n    join(dir, 'asana.md'),\n    `---\\ncategory: Project Management\\n---\\n\\n## Asana integration\\n`,\n  )\n  return { root, dir }\n}\n\ntest('generateConnectors returns entries sorted by category-then-name', () => {\n  const { root, dir } = makeFixture()\n  try {\n    const entries = generateConnectors({\n      marketplaceDir: dir,\n      hrefPrefix: 'connectors/marketplace',\n    })\n    assert.deepEqual(\n      entries.map((e) => `${e.category}:${e.name}`),\n      [\n        'Communication:Slack',\n        'Project Management:Asana',\n        'Project Management:monday.com',\n      ],\n    )\n  } finally {\n    rmSync(root, { recursive: true, force: true })\n  }\n})\n\ntest('generateConnectors uses displayName when present, otherwise humanizes filename', () => {\n  const { root, dir } = makeFixture()\n  try {\n    const entries = generateConnectors({\n      marketplaceDir: dir,\n      hrefPrefix: 'connectors/marketplace',\n    })\n    const monday = entries.find((e) => e.id === 'monday')\n    assert.equal(monday.name, 'monday.com')\n    const slack = entries.find((e) => e.id === 'slack')\n    assert.equal(slack.name, 'Slack')\n  } finally {\n    rmSync(root, { recursive: true, force: true })\n  }\n})\n\ntest('generateConnectors throws when a file is missing category', () => {\n  const { root, dir } = makeFixture()\n  writeFileSync(join(dir, 'broken.md'), `## Broken integration\\n`)\n  try {\n    assert.throws(\n      () =>\n        generateConnectors({\n          marketplaceDir: dir,\n          hrefPrefix: 'connectors/marketplace',\n        }),\n      /broken\\.md.*category/,\n    )\n  } finally {\n    rmSync(root, { recursive: true, force: true })\n  }\n})\n\ntest('generateConnectors throws when category is not in CONNECTOR_CATEGORIES', () => {\n  const { root, dir } = makeFixture()\n  writeFileSync(\n    join(dir, 'rogue.md'),\n    `---\\ncategory: NotARealCategory\\n---\\n\\n## Rogue\\n`,\n  )\n  try {\n    assert.throws(\n      () =>\n        generateConnectors({\n          marketplaceDir: dir,\n          hrefPrefix: 'connectors/marketplace',\n        }),\n      /rogue\\.md.*NotARealCategory/,\n    )\n  } finally {\n    rmSync(root, { recursive: true, force: true })\n  }\n})\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Run the test and watch it fail"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --test scripts/generate-connectors.test.mjs"]}," ","Expected: FAIL with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Cannot find module './generate-connectors.mjs'"]},"."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Implement ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/generate-connectors.mjs"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/generate-connectors.mjs"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"#!/usr/bin/env node\nimport { readFileSync, readdirSync, writeFileSync } from 'node:fs'\nimport { join, basename, resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport matter from 'gray-matter'\n\nconst HERE = dirname(fileURLToPath(import.meta.url))\nconst ROOT = resolve(HERE, '..')\n\nconst CONNECTOR_CATEGORIES = [\n  'Productivity',\n  'Communication',\n  'Project Management',\n  'CRM',\n  'Development',\n  'Storage',\n  'Finance',\n  'Customer Support',\n  'Marketing',\n  'E-commerce',\n  'Analytics',\n  'Monitoring',\n  'DevOps',\n  'HR',\n  'Video Conferencing',\n  'Scheduling',\n  'Identity',\n  'Design',\n  'Forms',\n  'IT Service Management',\n  'Documents',\n  'Automation',\n  'Data',\n  'Social Media',\n  'Feature Flags',\n  'Content Management',\n  'Sales Intelligence',\n  'Product Management',\n  'Infrastructure',\n  'AI',\n]\n\nfunction humanize(id) {\n  return id\n    .split('-')\n    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n    .join(' ')\n}\n\nexport function generateConnectors({ marketplaceDir, hrefPrefix }) {\n  const files = readdirSync(marketplaceDir)\n    .filter((f) => f.endsWith('.md'))\n    .sort()\n\n  const entries = files.map((file) => {\n    const fullPath = join(marketplaceDir, file)\n    const raw = readFileSync(fullPath, 'utf8')\n    const { data } = matter(raw)\n    const id = basename(file, '.md')\n\n    if (!data.category) {\n      throw new Error(\n        `${file}: missing required frontmatter field \"category\"`,\n      )\n    }\n    if (!CONNECTOR_CATEGORIES.includes(data.category)) {\n      throw new Error(\n        `${file}: category \"${data.category}\" is not in CONNECTOR_CATEGORIES`,\n      )\n    }\n\n    return {\n      id,\n      name: data.displayName || humanize(id),\n      category: data.category,\n      href: `${hrefPrefix}/${file}`,\n    }\n  })\n\n  entries.sort((a, b) => {\n    const ai = CONNECTOR_CATEGORIES.indexOf(a.category)\n    const bi = CONNECTOR_CATEGORIES.indexOf(b.category)\n    if (ai !== bi) return ai - bi\n    return a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })\n  })\n\n  return entries\n}\n\nfunction render(entries) {\n  const lines = [\n    `// AUTO-GENERATED by scripts/generate-connectors.mjs — do not edit by hand.`,\n    `// Re-generate with: npm run generate:connectors`,\n    `import type { ConnectorCategory } from './categories'`,\n    ``,\n    `export interface ConnectorEntry {`,\n    `  id: string`,\n    `  name: string`,\n    `  category: ConnectorCategory`,\n    `  href: string`,\n    `}`,\n    ``,\n    `export const CONNECTORS: ConnectorEntry[] = ${JSON.stringify(entries, null, 2)}`,\n    ``,\n  ]\n  return lines.join('\\n')\n}\n\n// Allow `import { generateConnectors }` from the test file and CLI execution\n// from npm script.\nconst isMain = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)\nif (isMain) {\n  const marketplaceDir = resolve(ROOT, 'agen-for-work/connectors/marketplace')\n  const outFile = resolve(\n    ROOT,\n    '@theme/markdoc/components/ConnectorCatalog/connectors.generated.ts',\n  )\n  const entries = generateConnectors({\n    marketplaceDir,\n    hrefPrefix: 'connectors/marketplace',\n  })\n  writeFileSync(outFile, render(entries))\n  console.log(`Wrote ${entries.length} connectors to ${outFile}`)\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Re-run the test"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --test scripts/generate-connectors.test.mjs"]}," ","Expected: all 4 tests pass."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 5: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add scripts/generate-connectors.mjs scripts/generate-connectors.test.mjs\ngit commit -m \"feat(scripts): add connector frontmatter→data generator\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-4-tdd-check-marketplace-frontmattermjs","__idx":6},"children":["Task 4: TDD ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["check-marketplace-frontmatter.mjs"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/check-marketplace-frontmatter.mjs"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Test: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/check-marketplace-frontmatter.test.mjs"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Write the failing test"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/check-marketplace-frontmatter.test.mjs"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"import { test } from 'node:test'\nimport assert from 'node:assert/strict'\nimport { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { checkMarketplace } from './check-marketplace-frontmatter.mjs'\n\nfunction setup() {\n  const root = mkdtempSync(join(tmpdir(), 'check-test-'))\n  const marketplaceDir = join(root, 'agen-for-work/connectors/marketplace')\n  const sidebarsPath = join(root, 'agen-for-work/sidebars.yaml')\n  const generatedPath = join(\n    root,\n    '@theme/markdoc/components/ConnectorCatalog/connectors.generated.ts',\n  )\n  mkdirSync(marketplaceDir, { recursive: true })\n  mkdirSync(join(root, '@theme/markdoc/components/ConnectorCatalog'), {\n    recursive: true,\n  })\n  return { root, marketplaceDir, sidebarsPath, generatedPath }\n}\n\nfunction writeValidFixture({ marketplaceDir, sidebarsPath, generatedPath }) {\n  writeFileSync(\n    join(marketplaceDir, 'slack.md'),\n    `---\\ncategory: Communication\\n---\\n\\n## Slack\\n`,\n  )\n  writeFileSync(\n    sidebarsPath,\n    `- group: Marketplace\\n  items:\\n    - label: Slack\\n      page: connectors/marketplace/slack.md\\n`,\n  )\n  // generated will be filled by the function under test using the\n  // generator, so we write what generateConnectors would produce.\n  writeFileSync(\n    generatedPath,\n    [\n      '// AUTO-GENERATED by scripts/generate-connectors.mjs — do not edit by hand.',\n      '// Re-generate with: npm run generate:connectors',\n      \"import type { ConnectorCategory } from './categories'\",\n      '',\n      'export interface ConnectorEntry {',\n      '  id: string',\n      '  name: string',\n      '  category: ConnectorCategory',\n      '  href: string',\n      '}',\n      '',\n      'export const CONNECTORS: ConnectorEntry[] = [',\n      '  {',\n      '    \"id\": \"slack\",',\n      '    \"name\": \"Slack\",',\n      '    \"category\": \"Communication\",',\n      '    \"href\": \"connectors/marketplace/slack.md\"',\n      '  }',\n      ']',\n      '',\n    ].join('\\n'),\n  )\n}\n\ntest('checkMarketplace passes on a fully valid fixture', () => {\n  const ctx = setup()\n  try {\n    writeValidFixture(ctx)\n    const result = checkMarketplace({\n      marketplaceDir: ctx.marketplaceDir,\n      sidebarsPath: ctx.sidebarsPath,\n      generatedPath: ctx.generatedPath,\n      hrefPrefix: 'connectors/marketplace',\n    })\n    assert.deepEqual(result.errors, [])\n  } finally {\n    rmSync(ctx.root, { recursive: true, force: true })\n  }\n})\n\ntest('checkMarketplace reports stale generated file', () => {\n  const ctx = setup()\n  try {\n    writeValidFixture(ctx)\n    writeFileSync(ctx.generatedPath, '// stale\\n')\n    const result = checkMarketplace({\n      marketplaceDir: ctx.marketplaceDir,\n      sidebarsPath: ctx.sidebarsPath,\n      generatedPath: ctx.generatedPath,\n      hrefPrefix: 'connectors/marketplace',\n    })\n    assert.equal(result.errors.length, 1)\n    assert.match(result.errors[0], /stale/i)\n  } finally {\n    rmSync(ctx.root, { recursive: true, force: true })\n  }\n})\n\ntest('checkMarketplace reports file present on disk but missing from sidebar', () => {\n  const ctx = setup()\n  try {\n    writeValidFixture(ctx)\n    writeFileSync(\n      join(ctx.marketplaceDir, 'orphan.md'),\n      `---\\ncategory: Communication\\n---\\n\\n## Orphan\\n`,\n    )\n    const result = checkMarketplace({\n      marketplaceDir: ctx.marketplaceDir,\n      sidebarsPath: ctx.sidebarsPath,\n      generatedPath: ctx.generatedPath,\n      hrefPrefix: 'connectors/marketplace',\n    })\n    assert.ok(\n      result.errors.some((e) => /orphan/.test(e)),\n      `expected an error mentioning orphan, got: ${JSON.stringify(result.errors)}`,\n    )\n  } finally {\n    rmSync(ctx.root, { recursive: true, force: true })\n  }\n})\n\ntest('checkMarketplace reports sidebar entry pointing at a missing file', () => {\n  const ctx = setup()\n  try {\n    writeValidFixture(ctx)\n    writeFileSync(\n      ctx.sidebarsPath,\n      `- group: Marketplace\\n  items:\\n    - label: Slack\\n      page: connectors/marketplace/slack.md\\n    - label: Ghost\\n      page: connectors/marketplace/ghost.md\\n`,\n    )\n    const result = checkMarketplace({\n      marketplaceDir: ctx.marketplaceDir,\n      sidebarsPath: ctx.sidebarsPath,\n      generatedPath: ctx.generatedPath,\n      hrefPrefix: 'connectors/marketplace',\n    })\n    assert.ok(\n      result.errors.some((e) => /ghost/.test(e)),\n      `expected an error mentioning ghost, got: ${JSON.stringify(result.errors)}`,\n    )\n  } finally {\n    rmSync(ctx.root, { recursive: true, force: true })\n  }\n})\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Run the test and watch it fail"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --test scripts/check-marketplace-frontmatter.test.mjs"]}," ","Expected: FAIL with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Cannot find module './check-marketplace-frontmatter.mjs'"]},"."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Implement ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/check-marketplace-frontmatter.mjs"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/check-marketplace-frontmatter.mjs"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"#!/usr/bin/env node\nimport { readFileSync, readdirSync, existsSync } from 'node:fs'\nimport { join, resolve, dirname, basename } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport yaml from 'js-yaml'\nimport { generateConnectors } from './generate-connectors.mjs'\n\nconst HERE = dirname(fileURLToPath(import.meta.url))\nconst ROOT = resolve(HERE, '..')\n\nfunction collectMarketplacePagesFromSidebar(node, found = []) {\n  if (!node) return found\n  if (Array.isArray(node)) {\n    for (const item of node) collectMarketplacePagesFromSidebar(item, found)\n    return found\n  }\n  if (typeof node === 'object') {\n    if (typeof node.page === 'string' && node.page.startsWith('connectors/marketplace/')) {\n      found.push(node.page)\n    }\n    if (Array.isArray(node.items)) {\n      collectMarketplacePagesFromSidebar(node.items, found)\n    }\n  }\n  return found\n}\n\nfunction renderGeneratedTs(entries) {\n  const lines = [\n    `// AUTO-GENERATED by scripts/generate-connectors.mjs — do not edit by hand.`,\n    `// Re-generate with: npm run generate:connectors`,\n    `import type { ConnectorCategory } from './categories'`,\n    ``,\n    `export interface ConnectorEntry {`,\n    `  id: string`,\n    `  name: string`,\n    `  category: ConnectorCategory`,\n    `  href: string`,\n    `}`,\n    ``,\n    `export const CONNECTORS: ConnectorEntry[] = ${JSON.stringify(entries, null, 2)}`,\n    ``,\n  ]\n  return lines.join('\\n')\n}\n\nexport function checkMarketplace({\n  marketplaceDir,\n  sidebarsPath,\n  generatedPath,\n  hrefPrefix,\n}) {\n  const errors = []\n\n  let entries\n  try {\n    entries = generateConnectors({ marketplaceDir, hrefPrefix })\n  } catch (err) {\n    errors.push(err.message)\n    return { errors }\n  }\n\n  if (!existsSync(generatedPath)) {\n    errors.push(`Generated file is missing: ${generatedPath}`)\n  } else {\n    const actual = readFileSync(generatedPath, 'utf8')\n    const expected = renderGeneratedTs(entries)\n    if (actual.trim() !== expected.trim()) {\n      errors.push(\n        `connectors.generated.ts is stale — run \\`npm run generate:connectors\\` and commit the result.`,\n      )\n    }\n  }\n\n  const sidebarYaml = readFileSync(sidebarsPath, 'utf8')\n  const sidebar = yaml.load(sidebarYaml)\n  const pagesInSidebar = new Set(\n    collectMarketplacePagesFromSidebar(sidebar).map((p) => basename(p, '.md')),\n  )\n  const filesOnDisk = new Set(\n    readdirSync(marketplaceDir)\n      .filter((f) => f.endsWith('.md'))\n      .map((f) => basename(f, '.md')),\n  )\n\n  for (const id of filesOnDisk) {\n    if (!pagesInSidebar.has(id)) {\n      errors.push(\n        `${id}.md exists on disk but is not referenced in sidebars.yaml under Marketplace.`,\n      )\n    }\n  }\n  for (const id of pagesInSidebar) {\n    if (!filesOnDisk.has(id)) {\n      errors.push(\n        `sidebars.yaml references connectors/marketplace/${id}.md but the file does not exist.`,\n      )\n    }\n  }\n\n  return { errors }\n}\n\nconst isMain = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)\nif (isMain) {\n  const { errors } = checkMarketplace({\n    marketplaceDir: resolve(ROOT, 'agen-for-work/connectors/marketplace'),\n    sidebarsPath: resolve(ROOT, 'agen-for-work/sidebars.yaml'),\n    generatedPath: resolve(\n      ROOT,\n      '@theme/markdoc/components/ConnectorCatalog/connectors.generated.ts',\n    ),\n    hrefPrefix: 'connectors/marketplace',\n  })\n  if (errors.length > 0) {\n    for (const e of errors) console.error(`✗ ${e}`)\n    process.exit(1)\n  }\n  console.log(`✓ Marketplace frontmatter and sidebar are in sync.`)\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Re-run the test"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --test scripts/check-marketplace-frontmatter.test.mjs"]}," ","Expected: all 4 tests pass."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 5: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add scripts/check-marketplace-frontmatter.mjs scripts/check-marketplace-frontmatter.test.mjs\ngit commit -m \"feat(scripts): add marketplace frontmatter and sidebar validator\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-5-wire-npm-scripts","__idx":7},"children":["Task 5: Wire npm scripts"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Modify: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["package.json"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Add two scripts to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["package.json"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Edit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["package.json"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"scripts\""]}," block. Current:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"\"scripts\": {\n  \"dev\": \"redocly preview\",\n  \"start\": \"redocly preview-docs\",\n  \"build\": \"redocly bundle sample@v1 -o dist/bundle.yaml\",\n  \"test\": \"redocly lint\",\n  \"check:content-style\": \"node scripts/check-content-style.mjs\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Change to:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"\"scripts\": {\n  \"dev\": \"redocly preview\",\n  \"start\": \"redocly preview-docs\",\n  \"build\": \"redocly bundle sample@v1 -o dist/bundle.yaml\",\n  \"test\": \"redocly lint\",\n  \"check:content-style\": \"node scripts/check-content-style.mjs\",\n  \"check:marketplace-frontmatter\": \"node scripts/check-marketplace-frontmatter.mjs\",\n  \"generate:connectors\": \"node scripts/generate-connectors.mjs\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Verify the new scripts exist (even though they will fail right now)"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run generate:connectors --silent || true"]}," ","Expected: script runs, then likely fails because frontmatter isn't there yet — that's fine. Confirms the wiring works."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add package.json\ngit commit -m \"chore(scripts): wire generate:connectors and check:marketplace-frontmatter\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-6-tdd-bootstrap-script-one-shot","__idx":8},"children":["Task 6: TDD bootstrap script (one-shot)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/bootstrap-connector-categories.mjs"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Test: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/bootstrap-connector-categories.test.mjs"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The bootstrap script reads ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["IntegrationCategoryMap"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectorCategory"]}," ","from the dashboard repo (path supplied via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DASHBOARD_REPO"]}," env var) and"," ","prepends ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["---\\ncategory: <name>\\n---\\n\\n"]}," to each marketplace markdown file"," ","that does not yet have frontmatter."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Write the failing test"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/bootstrap-connector-categories.test.mjs"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"import { test } from 'node:test'\nimport assert from 'node:assert/strict'\nimport { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { applyBootstrap } from './bootstrap-connector-categories.mjs'\n\nfunction setup() {\n  const root = mkdtempSync(join(tmpdir(), 'bootstrap-test-'))\n  const marketplaceDir = join(root, 'marketplace')\n  mkdirSync(marketplaceDir, { recursive: true })\n  return { root, marketplaceDir }\n}\n\ntest('applyBootstrap prepends frontmatter to a file without it', () => {\n  const ctx = setup()\n  try {\n    writeFileSync(\n      join(ctx.marketplaceDir, 'slack.md'),\n      `## Slack integration\\n\\nDescription.\\n`,\n    )\n    const updated = applyBootstrap({\n      marketplaceDir: ctx.marketplaceDir,\n      idToCategory: { slack: 'Communication' },\n    })\n    assert.equal(updated, 1)\n    const after = readFileSync(join(ctx.marketplaceDir, 'slack.md'), 'utf8')\n    assert.equal(\n      after,\n      `---\\ncategory: Communication\\n---\\n\\n## Slack integration\\n\\nDescription.\\n`,\n    )\n  } finally {\n    rmSync(ctx.root, { recursive: true, force: true })\n  }\n})\n\ntest('applyBootstrap leaves files with existing frontmatter untouched', () => {\n  const ctx = setup()\n  try {\n    writeFileSync(\n      join(ctx.marketplaceDir, 'jira.md'),\n      `---\\ncategory: Old\\n---\\n\\n## Jira integration\\n`,\n    )\n    const updated = applyBootstrap({\n      marketplaceDir: ctx.marketplaceDir,\n      idToCategory: { jira: 'Project Management' },\n    })\n    assert.equal(updated, 0)\n    const after = readFileSync(join(ctx.marketplaceDir, 'jira.md'), 'utf8')\n    assert.equal(after, `---\\ncategory: Old\\n---\\n\\n## Jira integration\\n`)\n  } finally {\n    rmSync(ctx.root, { recursive: true, force: true })\n  }\n})\n\ntest('applyBootstrap throws if a file has no entry in idToCategory', () => {\n  const ctx = setup()\n  try {\n    writeFileSync(join(ctx.marketplaceDir, 'unknown.md'), `## Unknown\\n`)\n    assert.throws(\n      () =>\n        applyBootstrap({\n          marketplaceDir: ctx.marketplaceDir,\n          idToCategory: {},\n        }),\n      /unknown/,\n    )\n  } finally {\n    rmSync(ctx.root, { recursive: true, force: true })\n  }\n})\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Run the test and watch it fail"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --test scripts/bootstrap-connector-categories.test.mjs"]}," ","Expected: FAIL with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Cannot find module './bootstrap-connector-categories.mjs'"]},"."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Implement the bootstrap script"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/bootstrap-connector-categories.mjs"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"#!/usr/bin/env node\nimport { readFileSync, readdirSync, writeFileSync } from 'node:fs'\nimport { join, basename, resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst HERE = dirname(fileURLToPath(import.meta.url))\nconst ROOT = resolve(HERE, '..')\n\nexport function applyBootstrap({ marketplaceDir, idToCategory }) {\n  const files = readdirSync(marketplaceDir).filter((f) => f.endsWith('.md'))\n  let updated = 0\n  for (const file of files) {\n    const id = basename(file, '.md')\n    const full = join(marketplaceDir, file)\n    const text = readFileSync(full, 'utf8')\n    if (text.startsWith('---\\n')) {\n      continue\n    }\n    const category = idToCategory[id]\n    if (!category) {\n      throw new Error(\n        `No category mapping for \"${id}\" — extend IntegrationCategoryMap in the dashboard, or hand-pick a value before continuing.`,\n      )\n    }\n    const frontmatter = `---\\ncategory: ${category}\\n---\\n\\n`\n    writeFileSync(full, frontmatter + text)\n    updated++\n  }\n  return updated\n}\n\n// Parse the dashboard's workforceApp.types.ts to extract the\n// integration-id → category map. Regex-based: brittle but ok for one-shot.\nfunction parseDashboardMap(typesFilePath) {\n  const src = readFileSync(typesFilePath, 'utf8')\n\n  const enumBlock = src.match(/export enum ConnectorCategory \\{([\\s\\S]+?)\\}/)\n  if (!enumBlock) throw new Error('Could not find ConnectorCategory enum')\n  const categoryEnum = {}\n  for (const m of enumBlock[1].matchAll(/(\\w+)\\s*=\\s*'([^']+)'/g)) {\n    categoryEnum[m[1]] = m[2]\n  }\n\n  const intBlock = src.match(\n    /export enum WorkforceIntegrationEnum \\{([\\s\\S]+?)\\}/,\n  )\n  if (!intBlock) throw new Error('Could not find WorkforceIntegrationEnum')\n  const integrationEnum = {}\n  for (const m of intBlock[1].matchAll(/(\\w+)\\s*=\\s*'([^']+)'/g)) {\n    integrationEnum[m[1]] = m[2]\n  }\n\n  const mapBlock = src.match(\n    /export const IntegrationCategoryMap[\\s\\S]+?\\{([\\s\\S]+?)\\};/,\n  )\n  if (!mapBlock) throw new Error('Could not find IntegrationCategoryMap')\n  const idToCategory = {}\n  for (const m of mapBlock[1].matchAll(\n    /WorkforceIntegrationEnum\\.(\\w+)\\]:\\s*ConnectorCategory\\.(\\w+)/g,\n  )) {\n    const id = integrationEnum[m[1]]\n    const category = categoryEnum[m[2]]\n    if (id && category) idToCategory[id] = category\n  }\n  return idToCategory\n}\n\nconst isMain = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)\nif (isMain) {\n  const dashboardRepo = process.env.DASHBOARD_REPO\n  if (!dashboardRepo) {\n    console.error(\n      'Set DASHBOARD_REPO=/path/to/dashboard to point at a local checkout.',\n    )\n    process.exit(1)\n  }\n  const typesFile = resolve(\n    dashboardRepo,\n    'app/v2/src/tools/stores/envStore/workforceApp/workforceApp.types.ts',\n  )\n  const idToCategory = parseDashboardMap(typesFile)\n  const updated = applyBootstrap({\n    marketplaceDir: resolve(ROOT, 'agen-for-work/connectors/marketplace'),\n    idToCategory,\n  })\n  console.log(`Updated ${updated} files with frontmatter.`)\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Re-run the test"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --test scripts/bootstrap-connector-categories.test.mjs"]}," ","Expected: all 3 tests pass."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 5: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add scripts/bootstrap-connector-categories.mjs scripts/bootstrap-connector-categories.test.mjs\ngit commit -m \"chore(scripts): add one-shot bootstrap for connector frontmatter\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-7-run-bootstrap-commit-96-file-changes","__idx":9},"children":["Task 7: Run bootstrap, commit 96 file changes"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}," modifies all ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/connectors/marketplace/*.md"]},"."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Verify dashboard repo path"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ls /Users/serslon/Projects/FrontEgg/dashboard/app/v2/src/tools/stores/envStore/workforceApp/workforceApp.types.ts"]}," ","Expected: file exists."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If the path is different on your machine, adjust the env var below."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Run the bootstrap"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"DASHBOARD_REPO=/Users/serslon/Projects/FrontEgg/dashboard node scripts/bootstrap-connector-categories.mjs\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Expected: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Updated 96 files with frontmatter."]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Sanity-check the diff"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["git status --short agen-for-work/connectors/marketplace/ | wc -l"]}," ","Expected: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["96"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["head -5 agen-for-work/connectors/marketplace/slack.md"]}," (or any file)"," ","Expected: starts with"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"---\ncategory: Communication\n---\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["followed by the original first H2."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Confirm content-style checker still passes"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run check:content-style"]}," ","Expected: no errors. (Frontmatter blocks should be skipped by the content-style checker — see ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scripts/check-content-style.mjs"]},". If new failures appear, stop and fix the script before committing.)"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 5: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add agen-for-work/connectors/marketplace/\ngit commit -m \"docs(marketplace): add category frontmatter to all 96 connector guides\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-8-generate-and-commit-connectorsgeneratedts","__idx":10},"children":["Task 8: Generate and commit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["connectors.generated.ts"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/connectors.generated.ts"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Run the generator"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run generate:connectors"]}," ","Expected: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Wrote 96 connectors to /Users/serslon/Projects/FrontEgg/docs/@theme/markdoc/components/ConnectorCatalog/connectors.generated.ts"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Inspect the output"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["head -20 @theme/markdoc/components/ConnectorCatalog/connectors.generated.ts"]}," ","Expected:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["starts with the auto-generated header comment;"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["declares ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CONNECTORS"]}," as an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectorEntry[]"]},";"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["first entry is from Productivity (e.g. Airtable);"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["pretty-printed JSON, 2-space indent."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Confirm validator passes"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run check:marketplace-frontmatter"]}," ","Expected: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["✓ Marketplace frontmatter and sidebar are in sync."]}," (sidebar check passes against the current 96-item flat structure; we restructure later in Task 12, and the validator will still find every file referenced.)"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add @theme/markdoc/components/ConnectorCatalog/connectors.generated.ts\ngit commit -m \"feat(theme): generate initial connectors.generated.ts (96 entries)\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-9-markdoc-schema-and-react-component","__idx":11},"children":["Task 9: Markdoc schema and React component"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/ConnectorCatalog.markdoc.ts"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/ConnectorCatalog.tsx"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/index.ts"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Create the Markdoc schema"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/ConnectorCatalog.markdoc.ts"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import { Schema } from '@markdoc/markdoc'\n\nexport const ConnectorCatalog: Schema & { tagName: string } = {\n  attributes: {},\n  render: 'ConnectorCatalog',\n  tagName: 'ConnectorCatalog',\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Create the React component"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/ConnectorCatalog.tsx"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","header":{"controls":{"copy":{}}},"source":"import React, { useMemo, useState } from 'react'\nimport styled from 'styled-components'\nimport { CONNECTORS } from './connectors.generated'\nimport { CONNECTOR_CATEGORIES, type ConnectorCategory } from './categories'\n\ntype CategoryFilter = 'all' | ConnectorCategory\n\nexport const ConnectorCatalog = () => {\n  const [query, setQuery] = useState('')\n  const [category, setCategory] = useState<CategoryFilter>('all')\n\n  const activeCategories = useMemo(\n    () =>\n      CONNECTOR_CATEGORIES.filter((c) =>\n        CONNECTORS.some((entry) => entry.category === c),\n      ),\n    [],\n  )\n\n  const filtered = useMemo(() => {\n    const q = query.trim().toLowerCase()\n    return CONNECTORS.filter((entry) => {\n      if (category !== 'all' && entry.category !== category) return false\n      if (q && !entry.name.toLowerCase().includes(q)) return false\n      return true\n    })\n  }, [query, category])\n\n  const sectioned = query.trim() === '' && category === 'all'\n\n  const groups = useMemo(() => {\n    if (!sectioned) {\n      return [\n        {\n          category: category === 'all' ? null : category,\n          entries: [...filtered].sort((a, b) =>\n            a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }),\n          ),\n        },\n      ]\n    }\n    return activeCategories.map((c) => ({\n      category: c,\n      entries: filtered.filter((e) => e.category === c),\n    }))\n  }, [filtered, activeCategories, sectioned, category])\n\n  const reset = () => {\n    setQuery('')\n    setCategory('all')\n  }\n\n  return (\n    <Wrapper>\n      <Controls>\n        <SearchInput\n          type=\"search\"\n          value={query}\n          onChange={(e) => setQuery(e.target.value)}\n          placeholder={`Search ${CONNECTORS.length} connectors…`}\n          aria-label=\"Search connectors\"\n        />\n        <Select\n          value={category}\n          onChange={(e) => setCategory(e.target.value as CategoryFilter)}\n          aria-label=\"Filter by category\"\n        >\n          <option value=\"all\">All categories</option>\n          {activeCategories.map((c) => (\n            <option key={c} value={c}>\n              {c}\n            </option>\n          ))}\n        </Select>\n      </Controls>\n\n      {filtered.length === 0 ? (\n        <Empty>\n          <p>No connectors match your search.</p>\n          <ResetButton type=\"button\" onClick={reset}>\n            Clear filters\n          </ResetButton>\n        </Empty>\n      ) : (\n        groups.map((group, idx) => (\n          <Section key={group.category ?? `flat-${idx}`}>\n            {group.category && <SectionTitle>{group.category}</SectionTitle>}\n            <Grid>\n              {group.entries.map((entry) => (\n                <Tile key={entry.id} href={entry.href}>\n                  {entry.name}\n                </Tile>\n              ))}\n            </Grid>\n          </Section>\n        ))\n      )}\n    </Wrapper>\n  )\n}\n\nconst Wrapper = styled.div`\n  font-family: var(--font-family-base);\n`\n\nconst Controls = styled.div`\n  display: flex;\n  gap: 12px;\n  margin: 16px 0 24px;\n  flex-wrap: wrap;\n`\n\nconst SearchInput = styled.input`\n  flex: 1 1 240px;\n  padding: 10px 14px;\n  font-size: 14px;\n  border: 1px solid var(--card-border-color, #2d2d37);\n  border-radius: 8px;\n  background: transparent;\n  color: inherit;\n\n  &:focus {\n    outline: none;\n    border-color: var(--card-border-color-hover, #3d3d47);\n  }\n`\n\nconst Select = styled.select`\n  flex: 0 0 220px;\n  padding: 10px 14px;\n  font-size: 14px;\n  border: 1px solid var(--card-border-color, #2d2d37);\n  border-radius: 8px;\n  background: transparent;\n  color: inherit;\n`\n\nconst Section = styled.section`\n  margin-bottom: 28px;\n`\n\nconst SectionTitle = styled.h3`\n  font-size: 16px;\n  font-weight: 600;\n  margin: 0 0 12px;\n  color: var(--icon-card-title-color);\n`\n\nconst Grid = styled.div`\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n  gap: 8px;\n`\n\nconst Tile = styled.a`\n  display: block;\n  padding: 12px 14px;\n  border: 1px solid var(--card-border-color, #2d2d37);\n  border-radius: 6px;\n  font-size: 14px;\n  font-weight: 500;\n  text-decoration: none !important;\n  color: inherit;\n  transition: border-color 0.2s ease;\n\n  &:hover {\n    border-color: var(--card-border-color-hover, #3d3d47);\n  }\n`\n\nconst Empty = styled.div`\n  text-align: center;\n  padding: 32px 16px;\n  color: var(--icon-card-description-color);\n`\n\nconst ResetButton = styled.button`\n  margin-top: 12px;\n  padding: 8px 16px;\n  background: transparent;\n  border: 1px solid var(--card-border-color, #2d2d37);\n  border-radius: 6px;\n  cursor: pointer;\n  color: inherit;\n  font-family: inherit;\n  font-size: 14px;\n`\n","lang":"tsx"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Create the barrel export"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components/ConnectorCatalog/index.ts"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"export { ConnectorCatalog } from './ConnectorCatalog'\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Verify TypeScript compiles"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npx tsc --noEmit"]}," ","Expected: no errors."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 5: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add @theme/markdoc/components/ConnectorCatalog/ConnectorCatalog.markdoc.ts\ngit add @theme/markdoc/components/ConnectorCatalog/ConnectorCatalog.tsx\ngit add @theme/markdoc/components/ConnectorCatalog/index.ts\ngit commit -m \"feat(theme): add ConnectorCatalog Markdoc component\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-10-register-the-component","__idx":12},"children":["Task 10: Register the component"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Modify: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/schema.ts"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Modify: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components.tsx"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Add the schema export"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Edit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/schema.ts"]},". After line ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["export { ProductCard } from './components/ProductCard/ProductCard.markdoc'"]},", append:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"export { ConnectorCatalog } from './components/ConnectorCatalog/ConnectorCatalog.markdoc'\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Add the component export"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Edit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/markdoc/components.tsx"]},". After line ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["export { ProductCard } from './components/ProductCard/ProductCard'"]},", append:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"export { ConnectorCatalog } from './components/ConnectorCatalog/ConnectorCatalog'\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Verify TypeScript still compiles"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npx tsc --noEmit"]}," ","Expected: no errors."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add @theme/markdoc/schema.ts @theme/markdoc/components.tsx\ngit commit -m \"feat(theme): register ConnectorCatalog in Markdoc schema and components\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-11-create-the-landing-page","__idx":13},"children":["Task 11: Create the landing page"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/connectors/marketplace.md"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Write the landing page"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Path: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/connectors/marketplace.md"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"markdown","header":{"controls":{"copy":{}}},"source":"---\ntitle: Marketplace\n---\n\n## Marketplace\n\nBrowse all available connectors. Use the search field to find a connector by name, or filter by category.\n\n","lang":"markdown"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Verify content-style checker passes for this file"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run check:content-style"]}," ","Expected: no new errors. The first heading is H2 (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["## Marketplace"]},"); no H1; no emojis; no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["->"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["=>"]}," arrows."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add agen-for-work/connectors/marketplace.md\ngit commit -m \"docs(marketplace): add searchable catalog landing page\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-12-restructure-sidebarsyaml","__idx":14},"children":["Task 12: Restructure ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sidebars.yaml"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Modify: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/sidebars.yaml"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is a mechanical replacement of one block. The current ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["- group: Marketplace"]}," block (between approximately lines 53 and 268, containing 96 flat items) is replaced with a Marketplace group whose ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["page:"]}," points to the landing and whose ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["items:"]}," is 22 category subgroups."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Read the current Marketplace block"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sed -n '53,268p' agen-for-work/sidebars.yaml | head -20"]}," ","Expected: shows ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["- group: Marketplace"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["expanded: true"]},", then 96 ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["- label: ... page: ..."]}," entries."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Build the replacement using the generator output"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The generator already sorts entries by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["(category-index, name)"]},". Use it to build the 22 subgroups. Add this temporary helper (you'll delete it after pasting):"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"node --input-type=module -e \"\nimport { generateConnectors } from './scripts/generate-connectors.mjs'\n\nconst entries = generateConnectors({\n  marketplaceDir: 'agen-for-work/connectors/marketplace',\n  hrefPrefix: 'connectors/marketplace',\n})\n\nconst seen = new Set(entries.map((e) => e.category))\nconst orderedCategories = [\n  'Productivity','Communication','Project Management','CRM','Development','Storage','Finance','Customer Support','Marketing','E-commerce','Analytics','Monitoring','DevOps','HR','Video Conferencing','Scheduling','Identity','Design','Forms','IT Service Management','Documents','Automation','Data','Social Media','Feature Flags','Content Management','Sales Intelligence','Product Management','Infrastructure','AI',\n].filter((c) => seen.has(c))\n\nconst lines = []\nlines.push('    - group: Marketplace')\nlines.push('      page: connectors/marketplace.md')\nlines.push('      expanded: false')\nlines.push('      items:')\nfor (const cat of orderedCategories) {\n  lines.push(\\`        - group: \\${cat}\\`)\n  lines.push('          expanded: false')\n  lines.push('          items:')\n  for (const e of entries.filter((x) => x.category === cat)) {\n    lines.push(\\`            - label: \\${e.name}\\`)\n    lines.push(\\`              page: \\${e.href}\\`)\n  }\n}\nconsole.log(lines.join('\\n'))\n\" > /tmp/marketplace-block.yaml\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Replace the old Marketplace block in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sidebars.yaml"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Open ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agen-for-work/sidebars.yaml"]}," and replace the existing block that begins at:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"    - group: Marketplace\n      expanded: true\n      items:\n        - label: Acuity Scheduling\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["…and ends with the last marketplace entry (e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["page: connectors/marketplace/zoom.md"]},"), with the content of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/tmp/marketplace-block.yaml"]},". Preserve the four-space indentation."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Verify YAML parses"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --input-type=module -e \"import yaml from 'js-yaml'; import { readFileSync } from 'fs'; yaml.load(readFileSync('agen-for-work/sidebars.yaml', 'utf8')); console.log('ok')\""]}," ","Expected: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ok"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 5: Run Redocly lint"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run test"]}," ","Expected: no errors."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 6: Run the marketplace validator"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run check:marketplace-frontmatter"]}," ","Expected: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["✓ Marketplace frontmatter and sidebar are in sync."]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 7: Clean up the temp file"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["rm /tmp/marketplace-block.yaml"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 8: Commit"]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git add agen-for-work/sidebars.yaml\ngit commit -m \"docs(marketplace): group 96 connectors into 22 category subgroups in sidebar\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"task-13-final-smoke-test--cleanup","__idx":15},"children":["Task 13: Final smoke test + cleanup"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Files:"]}," none modified."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Run the full check suite"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run test && npm run check:content-style && npm run check:marketplace-frontmatter"]}," ","Expected: all three pass with no errors."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Run unit tests for the new scripts"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node --test scripts/generate-connectors.test.mjs scripts/check-marketplace-frontmatter.test.mjs scripts/bootstrap-connector-categories.test.mjs"]}," ","Expected: all tests pass."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 3: Smoke test in the browser"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm run dev"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Open the local preview (URL shown in the dev server output, typically ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http://localhost:8080"]},"). Navigate to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/agen-for-work/connectors/marketplace"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Verify:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Page renders without console errors."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["22 H3 section headers visible (Productivity first), 96 tiles total distributed across them."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Typing \"sla\" in the search input narrows the result to Slack."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Selecting \"CRM\" in the dropdown shows only the CRM section."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Combining \"sla\" + \"Communication\" filters correctly."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Empty result (\"zzzz\" in search) shows the \"Clear filters\" button; clicking it resets state."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Sidebar shows ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Marketplace"]}," collapsed; expanding shows 22 collapsed subgroups."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Clicking any connector navigates to its guide and auto-expands its category group in the sidebar."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["[ ] ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 4: Delete the one-shot bootstrap script"]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The bootstrap has been run and committed. It is not used in CI and should not stay in the repo."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"rm scripts/bootstrap-connector-categories.mjs scripts/bootstrap-connector-categories.test.mjs\ngit add -A\ngit commit -m \"chore(scripts): remove one-shot bootstrap after successful run\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 5: Push and open PR (optional, gated on user approval)"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Ask the user before pushing — repo policy says PRs require at least one reviewer and the user prefers explicit confirmation for shared-state operations."]}]},"headings":[{"value":"Marketplace navigation and searchable catalog — implementation plan","id":"marketplace-navigation-and-searchable-catalog--implementation-plan","depth":1},{"value":"File map","id":"file-map","depth":2},{"value":"Task ordering","id":"task-ordering","depth":2},{"value":"Task 1: Pre-flight checks","id":"task-1-pre-flight-checks","depth":2},{"value":"Task 2: Create categories.ts","id":"task-2-create-categoriests","depth":2},{"value":"Task 3: TDD generate-connectors.mjs","id":"task-3-tdd-generate-connectorsmjs","depth":2},{"value":"Task 4: TDD check-marketplace-frontmatter.mjs","id":"task-4-tdd-check-marketplace-frontmattermjs","depth":2},{"value":"Task 5: Wire npm scripts","id":"task-5-wire-npm-scripts","depth":2},{"value":"Task 6: TDD bootstrap script (one-shot)","id":"task-6-tdd-bootstrap-script-one-shot","depth":2},{"value":"Task 7: Run bootstrap, commit 96 file changes","id":"task-7-run-bootstrap-commit-96-file-changes","depth":2},{"value":"Task 8: Generate and commit connectors.generated.ts","id":"task-8-generate-and-commit-connectorsgeneratedts","depth":2},{"value":"Task 9: Markdoc schema and React component","id":"task-9-markdoc-schema-and-react-component","depth":2},{"value":"Task 10: Register the component","id":"task-10-register-the-component","depth":2},{"value":"Task 11: Create the landing page","id":"task-11-create-the-landing-page","depth":2},{"value":"Task 12: Restructure sidebars.yaml","id":"task-12-restructure-sidebarsyaml","depth":2},{"value":"Task 13: Final smoke test + cleanup","id":"task-13-final-smoke-test--cleanup","depth":2}],"frontmatter":{"seo":{"title":"Marketplace navigation and searchable catalog — implementation plan"}},"lastModified":"2026-05-19T15:06:34.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/superpowers/plans/2026-05-13-marketplace-search","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}