Files
sim/scripts
Vikhyath Mondreti cbf3aad569 fix(workflows): scope the canonical sub-block index to the active surface (#6990)
* fix(workflows): scope the canonical sub-block index to the active surface

A block that is both an action and a trigger holds one `subBlocks` array — its
own fields plus its trigger's, spread in after them. The two sets routinely
share a `canonicalParamId` under different ids, so indexing them together
collapses a trigger field into an action pair whose `basicId` it can never be.
Every group-relative question about that field then answers for the dormant
surface.

The serializer was never affected: `shouldSerializeSubBlock` drops the inactive
surface before the canonical collapse reads it, so it resolves against a value
map the dormant surface cannot appear in. Every other caller resolves against
the block's full value map, so the scoping has to live in the index.

- add `getCanonicalSubBlocksForSurface` / `buildCanonicalIndexForSurface`, and
  move the three sites that already had the filter inline onto them
- `getCardSubBlocks` derives its own index instead of accepting one; it already
  took `triggerMode`, and accepting an index is what let all three callers pass
  one built for the other surface
- scope the remaining consumers that resolve against a full value map: the
  canvas card, autolayout, both preview surfaces, the dependsOn gate, the
  canonical value hook, reactive conditions, and the copilot selector lint
- keep a canonical group with no advanced member out of the legacy
  `advancedMode` path, which deleted its basic member and republished nothing
- merge legacy type-scoped tool modes as a baseline under index-scoped ones, so
  the first re-toggle stops reverting the ids the user has not touched

* fix(workspace-forking): scope the canonical gates to the block's active surface

`createCanonicalModeGates` indexed a block's whole `subBlocks` array, so on a
mixed action/trigger block a trigger field sharing a `canonicalParamId` with an
action pair was read as a member of THAT pair. Being neither its `basicId` nor
in its `advancedIds`, `isDormantMember` answered true the moment the shared mode
resolved to advanced — and a fork acts on that by clearing the value, so a
configured trigger field was silently wiped on fork/sync.

Reachable without any explicit toggle: a block configured as an action with a
manual id and then switched to trigger mode leaves the pair's value heuristic
resolving to advanced on its own.

- `createCanonicalModeGates` takes the surface and scopes its index
- thread `triggerMode` through `RemapForkContext`, `SubBlockTransform`,
  `clearDependentsOnRemap`, `collectClearedDependents`, the reference scanners,
  and the promote cleared-ref collectors
- nested tool params and the dependent scan are unchanged: a tool is always the
  action surface, and the dependent scan already narrows its configs

* fix(workspace-forking): keep the dormant surface classified as it was before scoping

Surface scoping decides canonical membership for the ACTIVE surface. Applying it
to every key also re-classified the dormant surface's own values: they stopped
being dormant members, which meant the remap no longer cleared them AND started
detecting them as references — turning a stale action selector on a trigger-mode
block into a mapping requirement that can block promote/sync.

The gates now pick the index per key: the scoped one for anything the active
surface defines (the fix — a trigger field gets its own group instead of being
read as a stranded member of an action pair), the whole array for everything
else, which is byte-for-byte the pre-scoping behavior.

Also adds `check:canonical-index`, an audit that fails any call building a
canonical index off a config's whole `subBlocks`, or calling the fork gates
without a surface, unless annotated with why. This defect shipped three times in
three subsystems; the 14 sites that legitimately mean one fixed surface now say
so at the call.

* fix(audits): stop the canonical-index guard flagging its own source

The audit holds `buildCanonicalIndex(` and `createCanonicalModeGates(` as string
literals to search for, and its own regex matched them — the arg-count rule then
fired on the literal. It passed locally only because the file was still untracked
when it ran, so `git ls-files` did not list it; committing it made the audit scan
itself and fail CI on the first run.

Exempts the audit's own source alongside the module that defines the primitives.
Verified the guard still fails on both regression shapes after the exemption.
2026-08-22 16:33:19 -07:00
..

Integration documentation generator

generate-docs.ts compiles the per-service integration pages under apps/docs/content/docs/en/integrations/ from the block/tool/trigger registry in apps/sim. The ontology it encodes: everything is a block, and an integration is one block that has Actions and, optionally, a Trigger.

Golden rule: the generated .mdx files are derived artifacts, not the source of truth. Do not hand-edit them — your changes are overwritten on the next run. The only editable region is the MANUAL-CONTENT block (see below). To change what a page says, edit the TypeScript in apps/sim and regenerate.

Where an integration lives canonically

For a service like Gmail, three TS sources define it:

Source What it is What it feeds in the page
apps/sim/blocks/blocks/<service>.ts The block: type, name, category (tools for integrations), bgColor, config sub-blocks, tools.access (which actions it exposes), an optional triggers capability, outputs Header / BlockInfoCard, Usage Instructions, and which actions + trigger appear
apps/sim/tools/<service>/*.ts Each action's params + outputs Every ### <action> → #### Input / #### Output under ## Actions
apps/sim/triggers/<provider>/ The trigger's config fields + outputs The ## Triggers section
apps/sim/components/icons.tsx The brand glyph The page icon

The block references actions by id in tools.access; the generator looks each one up in apps/sim/tools/.

What the generator does

Run with cd apps/sim && bun run generate-docs (or bun run scripts/generate-docs.ts from the repo root). One pass (generateAllBlockDocs):

  1. Copies icons apps/sim/components/icons.tsx → apps/docs/components/icons.tsx and builds apps/docs/components/ui/icon-mapping.ts.
  2. Block pass — for each integration block (category: 'tools', plus the memory / knowledge / table exceptions), writes integrations/<service>.mdx: BlockInfoCard + Usage Instructions + ## Actions.
  3. Trigger pass (generateAllTriggerDocs) — reads apps/sim/triggers/<provider>/ and appends a ## Triggers section to that service's page, or writes a standalone page for trigger-only services.
  4. Writes integrations/meta.json and regenerates the landing page's integrations.json.

Hand-written pages it never touches

Core block pages (blocks/*), the native trigger pages (triggers/{start,schedule,webhook,rss,table}), the integrations overview (integrations/index.mdx), and the service-account pages are fully hand-written. The generator skips them via HANDWRITTEN_INTEGRATION_DOCS, HANDWRITTEN_TRIGGER_DOCS, and SKIP_TRIGGER_PROVIDERS. Add a page name to those sets if you hand-author a page the generator would otherwise produce.

Manual content (the one editable region)

Each generated page may carry hand-written prose inside marker comments. The generator preserves anything between the markers and overwrites everything else, so this survives every regeneration:

{/* MANUAL-CONTENT-START:intro */}
[AgentMail](https://agentmail.to/) is an API-first email platform…
{/* MANUAL-CONTENT-END */}

Supported section names: intro (after the BlockInfoCard — the most common), usage, configuration, outputs, notes. The merge is by marker name (extractManualContent + mergeWithManualContent), so a section is re-inserted at the matching spot in the freshly generated structure.

If you move the output folder, reseed manual content from the old location first — the generator only preserves markers it finds in the existing output file, so a fresh folder starts with none.

Practical: to change…

  • An action's params/outputs, a trigger, or to add a service → edit apps/sim/{blocks,tools,triggers} and re-run the generator.
  • A page's prose intro → edit its MANUAL-CONTENT:intro block directly; it survives regen.
  • The overview / service-account / core-block / native-trigger pages → hand-edit freely.

Gotchas

  • Never hand-edit apps/docs/components/icons.tsx — step 1 overwrites it from the sim app. Components that need an icon the sim app lacks should define it locally or use @sim/emcn/icons (see components/workflow-preview/block-icons.tsx).
  • The generator is the source of truth for integrations/ and its meta.json; manual edits there are transient.

CI

The generator runs in CI on pushes to the main branch and commits the regenerated docs back. Keep block/tool/trigger metadata accurate in apps/sim and the docs follow.