Commit Graph
75 Commits
Author SHA1 Message Date
Waleed 3ff91f0439 improvement(docs): clean up leftovers from the code-block alignment PR (#6825)
* improvement(docs): clear leftovers from the reverted revisions

A cleanup pass over the final state. Every finding was residue from an approach
this PR tried and abandoned, or a claim that stopped being true when it did.

- Delete the copy-button svg sizing rule: a later rule sets `display: none` on
  that same element ungated, so sizing it was never observable. Superseded by
  the mask approach.
- Drop the paragraph in page.tsx arguing about a custom Shiki factory. The
  factory was deleted; nothing configures one now.
- Correct shiki-curl-json.ts, which still claimed the grammar "reaches the
  client path too". It does not — that was the justification for choosing a
  grammar over a transformer, so leaving it stated the opposite of the truth.
  Now records where it applies, where it does not, and why not to retry.
- Correct the global.css section header, which claimed the component owns the
  shell while the next rule defines it here.
- Qualify the `--copy-glyph` declarations with `:has(> svg[class*="lucide"])`,
  which the group's own comment asserts of every rule in it.
- Correct `getCode`'s TSDoc: the gutter is a `::before`, and pseudo-element
  content never reaches `textContent`, so line numbers were never what the
  clone guards. It guards transformer-emitted `.nd-copy-ignore` nodes.
- Compose `chipGeometryClass` and emcn's `ChipChevronDown` in the API example
  selector instead of restating their literals.
- Merge the duplicated `div[role="region"]` rule. The tablist pair stays split:
  biome's `noDuplicateProperties` reads a nested `@variant` setting the same
  property as a duplicate and fails the build — recorded so it is not remerged.
- Note that fumadocs ships its own gutter for `lines`-meta fences, which cannot
  be suppressed from here and would paint a second column.

* fix(docs): drop a highlighter registration that can never fire

fumadocs-openapi calls `renderCodeBlock` with a hard-coded `"json"` from both of
its call sites (`request-tabs.js:76`, `response-tabs.js:48`), so the docs
`CodeBlock` it routes through never receives a shell language. The
`getHighlighter('js', { langs: [curlJsonBodyGrammar] })` registering the
shell-scoped JSON-body injection therefore did nothing but await on every API
sample render, and the docblock claiming the grammar covers those samples was
wrong.

- Delete the call and its imports.
- State the grammar's real coverage: prose fences only, via `langs`. Both API
  reference paths are unreachable — samples are JSON, and the cURL usage tabs
  highlight client-side off fumadocs' own factory.
- Correct `code-block.tsx`'s TSDoc, which still said API samples come from
  fumadocs' own renderer. They come through this component; `UsageTab` is the
  renderer that bypasses it.
- Re-home a comment orphaned when two CSS rules merged — it had drifted onto
  the rule below and read as documenting it.
- Drop a `.nd-copy-ignore` claim about transformers emitting those nodes;
  nothing here does, and upstream parity is the reason the clone exists.
2026-08-18 15:28:02 -07:00
Waleed c17043a8b7 improvement(docs): align code blocks with the platform design system (#6810)
* improvement(docs): align code blocks with the platform design system

Docs code blocks rendered in stock `github-light`/`github-dark` on fumadocs
chrome, sharing no colors, typeface, metrics, or corner radius with the app.

- Add Sim Shiki themes transcribed from emcn's Prism token colors, shared by
  the MDX pipeline and fumadocs-openapi (which highlights through its own
  instance, so the API reference was left on the GitHub palette).
- Use the mono stack the app actually renders. `tailwind.config.ts` points
  `font-mono` at `--font-martian-mono`, but nothing defines that variable, so
  every code surface in the product resolves to the system stack.
- Give blocks the platform's field chrome — `rounded-lg`, a `--border-1`
  hairline, a `--surface-5`/`--code-bg` fill — and the 13px/21px metrics of
  `Code.Viewer`. The rule keys on `figure.shiki` because two renderers emit
  these figures and that is the only join point they share.
- Number every line, from the same tokens as the in-app gutter. Padding sits
  on `.line` rather than fumadocs' `--padding-left`: that property is
  re-declared on the inner `pre` for API samples, which dropped the digits on
  top of the code.
- Collapse tabbed fences into one box with the strip as the title row, and
  align the inline-code chip with the app's markdown renderer.
- Reuse emcn's `Button`, `useCopyToClipboard`, and chip chrome constants
  instead of re-deriving them, and drop ~90 lines of `!important` overrides,
  including a rule that could never match.

* fix(docs): stop line numbers overlapping code, unify the copy glyph

The gutter opened its column by setting `padding-left` on `.line`, which never
applied: fumadocs' own rule is `.shiki:not(.not-fumadocs-codeblock *) .line`,
and `:not()` carries its argument's specificity, putting it at (0,3,0). Every
code block rendered its line number on top of the first characters.

- Drive fumadocs' `--padding-left` / `--padding-right` instead of overriding
  `.line`. Declared on the figure, the viewport, and any inner `.shiki`,
  because the variable is inherited and the nearest declaration wins — the
  class sits on the figure alone for prose fences but on the figure and the
  inner `pre` for API samples, and `--padding-right` is also written as an
  inline style on the viewport.
- Route API request/response samples through the docs `CodeBlock` via
  fumadocs-openapi's `renderCodeBlock`, so they carry the emcn copy control
  rather than fumadocs' lucide clipboard.
- Mask the emcn glyph over the one block `renderCodeBlock` cannot reach — the
  usage tabs hardcode `ClientCodeBlock` and `OperationClientOptions` exposes
  only `APIExampleSelector` — so the copy icon is identical everywhere.

* improvement(docs): reserve the gutter column without numbering one-liners

A line number on a single-line shell command has nothing to reference, and the
CLI pages are mostly single-line commands. Dropping the gutter on those blocks
was the original behaviour, but it made adjacent fences start their code 28px
apart wherever a command sat next to its output.

Reserve the column on every block so all code shares a left edge, and paint the
digit only when the fence has more than one line.

* fix(docs): drop the gutter entirely on single-line fences

Reserving the column but leaving it blank gave one-line commands a 44px indent
with nothing in it, which reads as a rendering fault rather than as alignment.

Gate the column and the digit together, so a single-line fence keeps fumadocs'
default padding and a multi-line one gets both.

* fix(docs): stop the copy-button CSS restyling emcn's own Button

The rules added for fumadocs' copy button matched on `aria-label` alone, so
they also hit the emcn `Button` this app renders — re-declaring geometry,
radius, color, and a `background: none` that killed its hover, and pinning docs
to today's `buttonVariants` values with no failure signal if those change.

- Qualify every one with `:has(> svg[class*="lucide"])`, the same scoping the
  mask rules already used, so they reach only the block fumadocs renders.
- Stroke the masked glyph at 1.25 to match `Button size='icon'`, which
  overrides the icon's authored 1.55. The two copy glyphs were rendering at
  different weights — the mismatch the mask exists to remove.
- Drop `.line::after { content: none }`: it cannot outrank fumadocs' (0,4,1)
  rule, and no fence in `content/` uses the `lines` meta it guarded against.
- Drop the `!important` and the redundant viewport selector on `--padding-left`;
  nothing declares it between the figure and the region, and nothing contests
  it at equal specificity. `--padding-right` keeps both — its inline style is
  real.
- Use emcn's `cn` where emcn class constants are merged, so they go through the
  merger that knows the `text-micro|caption|small|md` scale.
- Correct the comments the review disproved: two claimed the API reference
  still renders fumadocs' CodeBlock, which `renderCodeBlock` changed.

* fix(docs): keep the gutter padding override belt-and-braces

A simplify pass removed the `!important` and the viewport selector from
`--padding-left` as provably redundant, and on the numbers they are: fumadocs
declares the property at (0,2,0) while these selectors are (0,3,1) and (0,4,1),
and nothing declares it on the viewport.

Restore both anyway. Getting this wrong paints the line numbers on top of the
code — a regression this PR already shipped once — and the specificity of
`:has()` and `:not()` is easy to miscount in exactly that direction. The comment
now records both that the override is redundant on paper and why it stays.

* feat(docs): highlight the curl JSON request body as JSON

A `curl -d '{…}'` payload is one single-quoted string to a shell, so the same
JSON that renders with colored keys in a response sample rendered as one flat
block of string color in the request sample directly above it.

Fixed with a TextMate injection rather than the two approaches that don't work:

- `{ include: 'source.json' }` attaches the JSON grammar but its object pattern
  only assigns `support.type.property-name.json` — the scope that colors keys —
  when it owns the opening brace. Entering mid-string, keys stay string-colored,
  which is the whole difference. So the key/value/array patterns are written out
  and name that scope directly.
- A Shiki transformer tokenizes it correctly but is a function, and the request
  tabs highlight in the browser off a `shikiOptions` object passed through RSC,
  where functions cannot cross. A grammar is plain data and reaches both sides.

An injection has to be registered on the highlighter, not passed per call, so
the API page moves to `createAPIPage` from `fumadocs-openapi/ui/base` with our
own factory, and `ApiShikiProvider` hands that same factory to the client code
blocks — both public API. The MDX pipeline preloads it through `langs`.

The opening brace requires `}`, a quoted key, or end-of-line after it, which
keeps `awk '{print $1}'` out; the end-of-line case is needed because Oniguruma
matches line by line. Verified against `jq '.[0]'`, `awk '{print $1}'`,
`grep -o 'foo'` and `echo '{}'` — none are re-colored.

* fix(docs): paint the code fill on the viewport, not the figure

The request and response panels on an API reference page rendered on different
backgrounds. Sampled from screenshots: the request panel showed the page
background (#ffffff light, --bg dark) while the response panel showed the code
surface (--surface-5 / --code-bg).

The fill was left to show through from the figure or the tab group, and those
diverge per renderer. fumadocs gives a standalone figure `bg-fd-card` but an
in-tab figure `bg-fd-secondary`, and this app forces
`--color-fd-card: transparent` on API reference pages — so zeroing the in-tab
figure's fill, expecting its group to supply one, left the request panel
transparent while the response panel's `bg-fd-secondary` group kept ours.

Paint it on the scroll viewport instead. That is the innermost box all three
renderers wrap code in, so it cannot diverge, and it no longer matters what any
ancestor sets.

* fix(docs): hide the code tab strip's scrollbar

fumadocs makes the strip `overflow-x-auto`, and an endpoint with ten status
codes overflows it in the API reference's narrow rail — leaving a scrollbar
across the bottom of a 34px header, which reads as the header being clipped
rather than as something scrollable.

Hidden the way the platform hides it on a scrolling tab strip: emcn's `TabStrip`
carries `overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden`.

The code viewport below keeps its scrollbar. There the overflow is content, and
the platform's own `Code.Container` shows one for the same reason — hiding it
would hide that a line continues.

* fix(docs): keep fumadocs-openapi's server graph out of the client bundle

The Vercel deployment went red at the commit that added `ApiShikiProvider`, and
stayed red for three commits. That component is `'use client'` and imported
`ClientCodeBlockProvider` from `fumadocs-openapi/ui/base` — an entry that also
pulls `remark`, `remark-rehype`, `@fumari/json-schema-ts` and `github-slugger`.
Importing it from a client module forces that whole server graph into the
browser bundle. Measured in `.next/static/chunks`: `json-schema-ts` in 1 chunk,
`github-slugger` in 3, `remark-rehype` in 4. A local build tolerates the weight;
a deployment with size limits does not.

`ClientCodeBlockProvider` lives in a `"use client"` module that the package does
not expose through its `exports` map, so there is no client-safe path to it.

Back to `createAPIPage` from `fumadocs-openapi/ui`, dropping the custom factory
and the provider. After: `json-schema-ts` 0 chunks, `github-slugger` 1,
`remark-rehype` 1 — the remainder is fumadocs' own client-side markdown.

The server path keeps the injection by registering it on the shared highlighter
`highlight` already resolves. What is given up is the API reference's cURL usage
tabs, which highlight in the browser off fumadocs' own factory. Prose fences
keep it, and that is where the `curl -d '{…}'` examples live — getting-started,
authentication, workflows/deployment, passing-files, triggers/webhook, in every
locale.
2026-08-18 11:12:44 -07:00
Waleed 025ea4d2bd fix(docs): serve JSON-LD in the HTML, fix sidebar spacing, and tighten the CLI guides (#6763)
* docs(cli): use -g for the install, and cut the prose that was not pulling weight

`--global` is valid but `-g` is what every comparable CLI documents, and the
long form only came from the package README. Also drops the yarn tab: it read
`yarn global add sim`, which works on Yarn 1 only — Yarn 2 removed global
installs, so that command fails for anyone on a modern Yarn. Adds `npx sim` for
running without installing.

The guides had accumulated design rationale that belongs in code comments rather
than user docs — why the filter grammar is JSON, why the config section naming
is asymmetric, why an unexpected error keeps its stack trace. Surveying how gh,
Vercel, Turborepo, Deno, Bun and Supabase write theirs, none carry that kind of
justification, and callouts are reserved for content whose absence produces a
wrong result rather than for general asides.

So: 1016 lines to 763, and 12 callouts to 3. The three that remain are the
pairing-code check, that `sim logout` does not revoke the key, and the
`--limit 100` default on `batch-delete`/`batch-update`, which silently truncates
a larger match. Troubleshooting drops the entries whose error message already
contained its own fix and keeps the seven whose cause is not obvious.

* fix(docs): render JSON-LD as native script tags so it reaches the HTML

All four structured-data blocks — WebSite, TechArticle, BreadcrumbList,
SoftwareApplication — were rendered with `next/script`, which never emitted a
script tag. Measured on a production build, `/api-reference/getting-started`
contained zero `<script type="application/ld+json">` elements; the payload
existed only in the `__next_s` client-injection queue and the RSC flight data,
so anything reading the served HTML saw no structured data at all. React was
also logging "Encountered a script tag while rendering React component" on every
page.

`next/script` is for loading and executing JavaScript. JSON-LD is data, and
Next's own guidance is a native `<script>` in the component. `serializeJsonLd`
already escapes the `<` character to its unicode form, which is the
sanitization that guidance calls for, so only the element changes.

Same build, after: three valid tags per page with `WebSite` in `<head>`, and the
injection queue gone entirely.

* fix(docs): scope the flush-separator rule to a container's first separator

`[data-separator]:not([data-separator] ~ [data-separator])` was meant to keep the
first sidebar group flush against the top padding, but `~` only reaches siblings,
so it also matched the first separator inside every expanded folder. Under
Self-Hosting, "Install" lost its top margin and crowded the "Architecture" link
above it — 25px of gap where "Configure" and "Operate" below it had 40px.

`:first-child` expresses the intent directly. Only the four sidebar roots open
with a separator; every nested folder starts with a page, so the intended case
still goes flush and nothing else changes.

* fix(docs): move the flush-separator rule onto the separator component

Keeps the styling with the component that owns it, per the repo standard, and
lets the global rule be deleted outright rather than corrected — `global.css`
now only loses a rule in this PR. Tailwind's `first:` variant compiles to the
same `:first-child` selector, so behavior is unchanged: the build emits
`.first\:mt-0:first-child{margin-top:0}` and the prerendered HTML carries the
class on the separator.
2026-08-15 20:39:34 -07:00
Waleed fed891f69d docs(cli): add a CLI docs section generated from the command tree (#6762)
* docs(cli): add a CLI section, generated from the command tree

The `sim` CLI shipped with no coverage in the docs site. Adds a fourth
top-level tab for it, and moves Academy last.

The command reference is generated. `sim` exposes 147 leaf commands across
33 groups, most of them derived at runtime from the v2 route contracts, so a
hand-written reference would be wrong the week after it was written. The
generator walks the command tree `buildProgram()` hands to commander — the
same tree the terminal parses — rather than re-deriving it from the contract,
which would be a second implementation free to describe commands nobody can
invoke. `check:cli-docs` is a zero-arg `check:*` script, so the existing audit
runner picks it up and stale pages fail CI.

Generating against the real tree surfaced a collision it had been hiding:
`bulkUpdateKnowledgeDocuments` and `updateKnowledgeDocument` both derived to
`sim knowledge documents update`. Commander resolves a duplicate to the first
match, so the bulk form shadowed the single-document one and its flags were
unreachable while still appearing in `--help`. The bulk form is now
`batch-update`, matching how `tables rows batch-delete`/`batch-update` already
handle the same REST overload, and the generator fails on any duplicate path
so the next one cannot land silently.

Five hand-written guides cover install, auth, configuration, output formats,
and scripting. Also corrects two commands in the package README that do not
exist as documented (`tables columns <tableId>`, and `--sort score:desc`,
which is JSON).

* docs(cli): document every flag from the contracts, add troubleshooting and a single-page reference

The command reference was structurally complete but said almost nothing: 223 of
377 flags rendered as "Set sort by" because the CLI only ever read flag help
from its own contract overrides, and fell back to restating the flag name.

The prose already existed. The v2 route contracts carry 931 `.describe()` calls
and the OpenAPI specs publish all of them — 327 parameters and 282 body
properties, 100% coverage — but the generated operation table dropped every one,
carrying only a per-operation summary. It now carries the field descriptions,
the path-parameter descriptions, and positional help, so `--help` and the docs
explain a flag the same way the API reference does. Placeholder descriptions are
now zero, and 147/147 commands, 377/377 flags and 130/130 arguments are
documented.

`check:cli-docs` fails on a request field with no `.describe()` rather than
letting it render as documentation that says nothing.

Also in this pass:

- Commands are root-level sidebar entries under a Commands heading rather than
  a folder, and headings are the command's description, so the table of
  contents distinguishes entries at the first word instead of repeating
  "sim knowledge documents …" fourteen times. A guard fails the build if two
  descriptions on a page collide, since they would share an anchor.
- A single-page `Complete reference` carrying all 147 commands, for in-page
  search and for agents fetching `/cli/reference.mdx`. It keys on exact command
  paths because descriptions are only unique within a group.
- A troubleshooting page, with every message copied from the source.
- Table columns are sized by a local component; the flag column was starved
  while descriptions kept most of the row empty.
- The prerelease install channels are dropped from the docs and the package
  README, which is what npm renders.

* fix(docs): match the CLI tab by path segment, and escape backslashes before pipes

`pathname.includes('/cli')` also matches `/integrations/clickup` and
`/integrations/clickhouse`, so both existing integration pages lit the CLI tab
and unlit Documentation. Matching is now per path segment. Anchoring to the
start would not work either — a non-default locale prefixes the path, as in
`/ja/cli` — so the segment is matched wherever it sits.

Table cells now double a backslash before escaping pipes. A value ending in one
turned `a\` + `|` into `a\\|`, which the table parser reads as an escaped
backslash followed by an unescaped pipe, splitting the cell early. Nothing in
the command surface contains a backslash today, so this was latent rather than
visible.

The reference page's global options table is two-column and was being wrapped in
`CommandTable`, which sizes the second column for the `Required` cell of the
three-column tables and crushed the description into 5.5rem. It now matches the
overview page, which leaves that table unsized.
2026-08-15 19:25:34 -07:00
Waleed 29853fbbcf improvement(docs): make the API reference read as code and unify its type token (#6653)
* improvement(docs): make the API reference read as code, and unify the type token

The API-page font override matched every span/div/p inside the page, which
outranks the .font-mono class on specificity, so every parameter name, type,
and identifier silently rendered in the body sans face. Exclude .font-mono so
code tokens stay monospace.

Consolidate the three divergent type-slot treatments — plain scalar, union,
and schema reference each carried their own chip definition, differing in
size, weight, face, and box height — onto one code token that reuses the docs
inline-code recipe and the platform's 20px chip height.

Demote the row metadata: 'required' and 'header' were filled pills, 'required'
on the error token, making a constraint the loudest element on the page and a
page of required parameters read as a page of alarms. Both are now uncontained
text, leaving the type token as the only box on the row.

Pin the two 'application/json' labels to one treatment; the Request Body and
Response headers rendered the same string at different weights and faces.

* improvement(docs): mono status-code tabs, and match fumadocs' lucide icons to emcn

Status codes in the example panel are numeric literals and render as code
everywhere else on the page, including the Response header's own trigger, but
fumadocs rendered the strip in the body sans face. Language tabs sit in a
separate container and stay sans — those are product names, not code.

fumadocs draws a few lucide glyphs on API pages that its client-component
overrides do not expose (the heading anchor and the code-block copy button).
emcn strokes at 1.55 and lucide at 2, so those icons read heavier than every
icon around them; match the weight.

* fix(docs): align the auth type chip with every other property row, and wrap example code

The auth row collapses its real `<token>` type and renders the chip through
::after, so the span is only a wrapper — but it still matched the type-token
rule and kept that rule's border, height, and gap. The border drew a second
empty box around the real chip and the gap opened in front of it, because the
collapsed text remains an anonymous flex item; together they pushed the chip
right by roughly 8px that no other row had.

Example-panel code overflowed sideways instead of wrapping: fumadocs sizes the
block with `w-max`, so it grew to its longest line inside a 400px scroller and
the existing pre-wrap never applied. Cap the width, switch break-all to
overflow-wrap anywhere so only unfittable tokens split, and reserve room for
the copy button fumadocs floats over the first line.

* revert(docs): let example code overflow instead of wrapping

Wrapping restarts every continuation line at column zero, and in a JSON body
indentation is what carries nesting depth — so a wrapped response misreports
its own structure. A hanging indent keeps the depth but needs the shiki lines
forced from flex rows to blocks, which breaks the line rhythm.

Removes the pre-wrap rules rather than repointing them: fumadocs sizes the
block with w-max, so the previous rule never took effect and overflow was
already the behaviour on the page.

* fix(docs): tighten array type tokens and keep the union separator legible

An `array<T>` slot holds its angle brackets as bare text nodes, which become
anonymous flex items, so the slot's gap prised `array<` and `>` away from the
type they wrap. Drop the gap and let the union separator carry its own margin;
this also makes the auth row's gap override redundant.

The separator was dimmed twice, by a muted token and again by opacity, which
on the dark chip fill left `string | null` reading as `string null`.

* fix(docs): restore the hidden API key description, and drop dead API-reference CSS

The rule hiding the trailing `In: header` line matched `p:has(> code)`, which
is a shape, not a target — every scheme description in our specs cites a status
code, so the whole explanation of personal vs workspace-scoped keys was
display:none on every API reference page. Match the last child instead, and
shorten the description to one line now that it renders.

The dropdown trigger's hover rule had been left below a new id-qualified base
rule that outranked it, so the trigger could no longer change colour on hover.

Removes what does not run: the four `::-webkit-scrollbar` rules (specifying a
non-auto scrollbar-width makes Chromium ignore them, and Firefox never had
them) and an `order: 2` block whose selectors and declaration the type-token
rule above it already carried.

Names the two values the API reference repeats — the monospace stack, written
out eleven times, and the 12.5px code size, written nine — as --font-mono-stack
and --text-code. Also drops four !important declarations that already won on
specificity, a --text-muted fallback that can never fire, and a lucide selector
subsumed by the one beside it.

* refactor(docs): define the API-reference chrome once, and cut the commentary

The metadata face — size, leading, weight, mono stack — was written out in seven
rules that a comment asked future readers to keep in sync by hand; it is now one
rule those seven consume, each adding only its own colour, content, and order.
The auth row's chip likewise re-derived all eleven declarations of the type
token and now joins that rule, keeping only its label.

Comments were running longer than the rules they documented — 88 added comment
lines against 73 declarations. Trimmed to the load-bearing facts: cascade traps,
browser behaviour, and the bugs a rule prevents. Dropped the block narrating why
the wrap rules were reverted, which duplicated its own commit message.

Also retires a scrollbar token left unreferenced by the webkit removal, moves
the last two fumadocs colours in our own components onto platform tokens, and
brings the callout icon to 1.55 so the docs really do have one icon weight.

* fix(docs): keep the union separator in the type token's own face

The `|` between union members is a classless span, so the page-wide
`span:not(.font-mono)` rule assigned it the body sans face while the members
beside it stayed mono — one chip rendering in two faces.

Applies the inherit reset to every descendant of a type token rather than just
its links, so anything fumadocs nests there later is covered too.
2026-08-12 19:27:32 -07:00
Theodore Li afa02939bb fix(docs): include API key header in generated code samples (#6630) 2026-08-12 13:58:39 -04:00
263e3ca67e improvement(external-endpoints): v2 versions with clean signatures + updated docs based on openapi spec (#5273)
* v0.6.29: login improvements, posthog telemetry (#4026)

* feat(posthog): Add tracking on mothership abort (#4023)

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(login): fix captcha headers for manual login  (#4025)

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* improvement(external-endpoints): v2 versions with clean signatures + updated docs

* feat(usage): accept X-API-Key on usage-logs list + export

/api/users/me/usage-logs and /export now use checkHybridAuth — the same
auth /api/users/me/usage-limits already accepts — so external monitors
can read summary.bySourceCredits (the source breakdown of usage-limits'
aggregate currentPeriodCost) instead of estimating Copilot spend by
subtraction. Workspace-scoped keys are pinned to their own workspace's
slice of the ledger: the filter defaults to the key's workspace and an
explicit mismatch 403s. Both endpoints documented in openapi-core.json.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(billing): dedicated v2 usage endpoints; keep internal usage routes session-only

Replaces the earlier X-API-Key enablement on /api/users/me/usage-logs
with a dedicated public surface, so the internal Billing-settings
endpoints can evolve with the UI while external monitors get a stable
versioned contract:

- GET /api/v2/billing/usage — current-billing-period summary with
  bySourceCredits (the source breakdown external monitors need to watch
  e.g. Copilot consumption without estimating by subtraction), plus
  limitCredits and plan
- GET /api/v2/billing/usage/logs — cursor-paged credit ledger in the v2
  envelope
- workspace-scoped keys are pinned to their own workspace's slice;
  personal keys read the account ledger

The public wire is credits-only: usage-logs rows now carry a hasCost
boolean instead of dollarCost (the Billing UI only needed the >0
signal), and the rateLimit block is removed from the usage-limits
response and docs (deploy-modal tab relabeled accordingly).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(docs): validate OpenAPI specs against the Zod contracts in CI

The specs in apps/docs are hand-authored because they carry what Zod
never defines — error envelopes, status codes, prose, examples — so
they can't be generated; check:openapi validates them instead:

- spec integrity: $refs resolve, operationIds unique, 2xx documented,
  no orphaned component schemas
- v2 conventions: every /api/v2 operation documents 401 + 429 and every
  4xx/5xx resolves to the canonical { error: { code, message } } envelope
- contract cross-check: contracts are auto-discovered from
  lib/api/contracts/v2 (each carries its method + path); doc<->contract
  coverage both ways, query/body/response field diffs via z.toJSONSchema
- examples: documented request/response examples must parse with the
  matching contract's actual Zod schemas

First run caught real drift, fixed here: 16 stale orphaned schemas in
the core spec, the v2 billing ops referencing v1-shaped error
components, deploy/rollback examples missing the required nullable
lifecycle keys, CreateTableBody missing folderId, a legacy-grammar
delete-rows example, and four knowledge document ops missing their
required workspaceId query param.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* fix(docs): recursive field diff in check:openapi + the deep drift it found

A mutation test showed the doc<->contract field diff only compared
top-level properties, so a typo inside the { data } envelope passed.
The diff now descends through matching object properties and array
items (both sides must expose a property set — passthrough contracts
and prose-only docs end the descent instead of false-positive), with
the Zod JSON-schema root doubling as the $defs context.

Deep drift it immediately caught, fixed here: select-column config
(options/multiple) missing from every tables column schema, AddColumnBody
hand-rolling a third column shape (now composed from ColumnInput, with
position/workflowGroupId as the per-op extensions the contracts actually
admit), chunking strategyOptions undocumented, and the deployment
lifecycle fields (activeDeployment/latestDeploymentAttempt) missing from
DeploymentState.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* fix(security): close the triggerType rate-limit bypass on workflow execute

Caller-supplied triggerType flowed unchecked into preprocessExecution,
whose checkRateLimit default turns OFF for 'manual'/'chat' — so any
API-key caller, and any anonymous public-API caller billed to the
workspace owner, could execute unthrottled by sending
{"triggerType":"manual"} (async runs also skipped the worker-side check
via admissionCompleted). External callers may now only send the
redundant 'api' value; internal JWT callers ('workflow'/'mcp') are
unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* refactor(execution): extract enqueue/status/cancel into shared libs

Prepares the v2 execution surface: handleAsyncExecution's queue logic
moves to lib/workflows/executor/enqueue-execution.ts (slot/claim
semantics encoded in a discriminated outcome, not HTTP statuses), the
execution-status read to execution-status.ts, and the order-sensitive
cancel machinery to lib/execution/cancel-workflow-execution.ts. The v1
routes re-render identically — their suites pass unmodified.

Also: preprocessExecution gains rateLimitCounter ('sync'|'async') and
its 429 now carries code RATE_LIMIT_EXCEEDED + retryAfterMs (previously
indistinguishable from the concurrency 429 and Retry-After was
discarded); and the duplicate cancel contract in contracts/logs.ts is
unified on the full 5-value reason enum — its narrower copy made
requestJson throw a client ZodError when cancelling a paused HITL run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(execution): callable execution service + structured error classifier

executeWorkflowService composes the same libs the v1 route holds inline
(call-chain guard, execution-id claim, LoggingSession, preprocessing,
deployed-state load + file-field processing, timeout-bound
executeWorkflowCore, output hydration/compaction) for the deployed-state
caller class — the seam the v2 execute route and in-process internal
callers share, making the HTTP endpoint syntactic sugar.

classifyExecutionError stops discarding the block context that
buildBlockExecutionError already attaches at throw sites: failed runs
now yield {message, code, blockId, blockName, blockType} with a stable
append-only code enum (TIMEOUT/CANCELLED/USAGE_LIMIT_EXCEEDED/
INVALID_INPUT/BLOCK_EXECUTION_FAILED/CHILD_WORKFLOW_FAILED/
OUTPUT_TOO_LARGE/EXECUTION_FAILED), so callers route on error class
instead of substring-matching messages — the single place raw errors
are interpreted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(api): POST /api/v2/workflows/[id]/execute

Thin route over executeWorkflowService: X-API-Key or anonymous
public-API auth (sync/stream only for anonymous), strict body with
body-flag async (no mode headers on v2), SSE passthrough for stream,
and the execution resource response — executionId always present,
in-band run failures are status:'failed' with the structured
{message, code, blockId, blockName, blockType} error, sync timeout is
status:'failed' + TIMEOUT instead of v1's 408, and a Response block's
payload stays inside output (authors never control response
status/headers on this origin). Async debits the async bucket and the
202 statusUrl points at the v2 executions resource. Adds
CLIENT_CLOSED_REQUEST/SERVICE_UNAVAILABLE to the v2 error codes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(api): v2 executions status + cancel with queued backfill

GET /api/v2/workflows/[id]/executions/[executionId] is the single
status URL for sync and async runs: before the async worker writes the
durable log row, status is backfilled from the job queue (deterministic
job id) as 'queued'/'running' — closing v1's 202-to-pickup 404 window —
and failed runs carry the structured error object. POST .../cancel
renders the shared cancellation lib in the v2 envelope with the
tightened 5-value reason enum. Both authenticate via the shared
resolveV2WorkflowAccess (X-API-Key, authz masked as 404,
allowPersonalApiKeys honored).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(execution): workflow tool + MCP bridge run in-process

workflow_executor (workflow-as-agent-tool) short-circuits in executeTool
through WorkflowBlockHandler — the same invocation boundary canvas child
workflows use — mirroring the deployed_block_executor precedent. The
MCP serve bridge calls executeWorkflowService directly instead of
fetching its own execute endpoint; deployment-version pinning, MCP
response-size rejection, and the actor override become typed options
instead of header sniffing. Both callers drop the double admission slot
and duplicate top-level log row the HTTP hop cost, and failed child
runs now surface the structured error + child executionId so parents
and MCP clients can route on error class and hand providers a
reproducible handle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(infra): CORS + CSP coverage for the v2 execute path

/api/v2/workflows/:id/execute gets the same wildcard-origin,
credential-free CORS policy as v1 (the default credentialed policy
would block browser API-key calls and open a cookie CSRF surface) with
X-Sim-Stream-Protocol allowed and no X-Execution-Mode (async is
body-selected on v2), plus the COEP/COOP/CSP header block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(ui): deploy modal + copilot advertise the v2 execute surface

All 20 API-tab snippets move to POST /api/v2/workflows/{id}/execute with
the nested {"input": ...} body, async as the "async": true body flag
(X-Execution-Mode gone), status polling against the v2 executions
resource, the third tab renamed Usage and pointed at
/api/v2/billing/usage, and {data} envelope unwraps in the printed
responses. Fixes the latent baseUrl derivation
(endpoint.split('/api/workflows/')) that would have silently built
garbage URLs under a v2 endpoint, and deletes dead code (exampleCommand
across 3 sites, getAsyncExampleTitle). Copilot deploy/manage/serializer
endpoint builders and the api_trigger bestPractices example follow (the
latter also drops its hardcoded staging host).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* docs(api): document the v2 execution surface

Adds execute, execution status, and cancel to openapi-v2-workflows.json
with the structured ExecutionError schema (append-only code enum + block
attribution) and the ExecutionResource contract, documenting the rules
that differ from v1: modes are body-selected, a failed run is HTTP 200
with status 'failed', an executionId always means data (never the error
envelope), queued status is visible immediately, and Response-block
payloads stay inside output. Registers the three pages in the generated
workflows meta.json and bumps the route-count baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* feat(api): gate the whole /api/v2 surface behind one flag; UI stays on v1

Every v2 route now runs exactly one check immediately after auth —
v2ApiGateError — and answers 404 when the `v2-api` flag is off, so the
surface is invisible until it is deliberately rolled out. The gate is
keyed on userId only: a workspace/org-keyed check would have to read
membership for a caller-supplied id before authorization runs, and its
404-vs-403 split would leak cohort membership (the trap the per-domain
table gate worked around by running late). The two executions routes
inherit it from the shared access resolver; the tables-specific gate is
removed so no route checks twice.

`tables-v2-api` stays, now gating only the internal predicate-grammar
route /api/table/[tableId]/query — note v2 tables routes move to the
unified flag, so enabling them is a `v2-api` decision now.

Reverts the deploy modal, copilot handlers, and api_trigger example to
the v1 execute endpoint: v1 works unchanged, and the UI must not
advertise a surface most users would get a 404 from.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz

* fix(executor): restore child-cost aggregation dropped by the staging merge

Staging's custom-block rewrite deleted `aggregateChildCost` from
workflow-handler.ts, and git merged that file cleanly — but this branch's
workflow-tool-runner.ts, added for the v2 execute migration, still imports it.
A silent semantic conflict: no marker, broken build.

Taking staging's rewrite is correct, so the helper is defined locally in its
one remaining consumer rather than resurrected in the file staging just
rewrote. Same four lines over the still-exported `calculateCostSummary`, so a
failed child workflow keeps billing the hosted-key spend it consumed instead
of reporting $0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(tables): make lib/table/orchestration the single implementation (#6134)

* refactor(orchestration): move the shared error contract out of lib/workflows

OrchestrationErrorCode and statusForOrchestrationError are the contract every
lib/[resource]/orchestration module returns against, but they lived inside the
workflows module, so resource-neutral code (lib/folders) already had to import
from a workflow path. Moved to lib/core/orchestration/types.

Adds a 'locked' class mapping to 423. Both tables and workflows have a lock
that forbids a mutation, and each caller was translating that to a status
itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(tables): make lib/table/orchestration the single implementation

Column update was implemented four times — the UI route, v1, v2, and the
copilot table tool — each calling the same column services but owning its own
guards, error mapping, and audit. The copies had drifted, and the drift was the
bug: v2 was missing both guards, only the copilot copy minted stable option
ids, and only v1/v2 audited.

performUpdateTableColumn, performDeleteTable, and performDeleteTableRow now own
that logic; all ten call sites reduce to auth, parse, call, render. The guards
are asserted once in lib/table/orchestration rather than four times against
four routes.

Behavior this consolidates, previously true on only some paths:

- The typeChanging guard. updateColumnType early-returns on an unchanged type
  and drops any options sent with it, so restating the current type alongside
  new options silently discarded them. v2 had no guard at all and, since its
  contract shares v1's body schema, accepted options and ignored them.
- The select-unique guard. Each write is its own locked transaction, so a
  rename or type change paired with a constraint write that is going to fail
  commits first and then throws, half-applying the schema change.
- Stable select-option ids. Cells reference the option id, so an edit that
  re-sends an option by name has to reuse it or every cell holding it is
  orphaned. Only the copilot path did this; normalizeSelectOptionsInput moves to
  lib/table/select-options and now covers every caller. It preserves a supplied
  id, so it is a no-op for the fully-formed options the HTTP contracts accept.
- required forwarded into the type and options writes, so a conversion
  validates against the constraint the same request is setting.
- An audit on every successful update. The UI route and the copilot tool
  emitted none.
- Single-row delete through the row service. v2 did a raw db.delete, skipping
  assertRowDelete and deleteOrderedRow, so a delete-locked table returned 200
  and the row-count bookkeeping never ran.
- The delete actor handed to deleteTable, which audits only when a row was
  actually archived. v1 and v2 omitted it and audited themselves outside that
  check, emitting TABLE_DELETED for a no-op delete of an archived table.

Failure classes come back as OrchestrationErrorCode; v2 renders them through a
new v2ErrorForOrchestration, mirroring statusForOrchestrationError on the v1
and UI surfaces, so a given failure maps to the same status everywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(tables): bind the column-update tests to the orchestration function

The base's route tests assert which column service each payload reaches — the
behavior that now lives in performUpdateTableColumn. They mocked the `@/lib/table`
barrel; the orchestration module imports the service directly, so they mock that
too and keep asserting the same thing through the extracted implementation.

The orchestration tests move onto the base's semantics: writes address the
stable column id, a rename rides inside the write it accompanies rather than
running first, and the currency guards replace the non-select options guard the
service now owns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(copilot): drop the column-type import the delegation made dead

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(tables): move the audit log out of the table service

`lib/table/service.ts` wrote its own audit rows, so whether an operation was
audited depended on which function a caller reached for rather than on a user
having performed it. That is what let v1 and v2 audit a no-op delete, and what
made `deleteTable`'s optional `actingUserId` double as an audit opt-out flag.

Worse, most sites fell back to `actingUserId ?? createdBy`, so an unattributed
call was logged against the table's *creator*. The copilot `mv` path passed no
actor at all: renaming someone else's table recorded them as the renamer.

Audit now lives in the orchestration functions — performDeleteTable,
performRenameTable, performMoveTableToFolder, performUpdateTableLocks — and
the services just write. Internal callers (folder cascade, import rollback)
keep calling the service and are silent by construction rather than by
remembering to omit an argument.

Two services now return what the audit needs: `deleteTable` reports whether it
actually archived a row, so a repeat delete logs nothing; `updateTableLocks`
returns the before/after locks, since only the locked write can observe the
transition its description names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(tables): restore audit provenance and conflict status in orchestration

Moving the audits into the orchestration functions dropped three things the
routes had been carrying, and added one the orchestration now owns twice.

- The v1 and v2 column-update routes passed `request` to `recordAudit`, so
  their audit rows recorded the caller's IP and user-agent. The orchestration
  function had no way to receive it. Every table orchestration function now
  takes an optional `OrchestrationRequestContext` and every HTTP route
  forwards it; the copilot and VFS callers, which have no request, omit it.
- `classifyTableMutation` matched `TableConflictError` on "already exists"
  appearing in the message and reported it as `validation`, turning the UI
  route's 409 on a duplicate table rename into a 400. It now matches the type,
  the way `performRestoreTable` already did.
- `captureServerEvent` ran on every delete while the audit was gated on a row
  actually being archived, so a repeat delete of an archived table still
  reported `table_deleted`. Both now hang off the same evidence.
- The copilot delete path kept its own `captureServerEvent` from when the
  service did not emit one, double-counting every copilot table delete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGzbVDZpe2dEALbu2BUU8a

* fix(tables): say which type a no-op column update restated

A copilot `update_column` payload whose only content was the column's current
type used to return success with the live schema, while the v1, v2, and UI
routes rejected the same payload with "No updates specified". Delegating to
`performUpdateTableColumn` unified them onto the routes' rejection — correct,
but the message tells the caller its request was empty when it named a type.

The orchestration function now reports the same thing `updateColumnType` reports
when it loses this race concurrently: the column is already that type, re-issue
without the type change. An empty payload still reads "No updates specified".

Drops the copilot's `outcome.table ?? tableForUpdate` fallback with it — the
comment described the no-op that can no longer reach that line, and a success
always carries a table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGzbVDZpe2dEALbu2BUU8a

* refactor(tables): classify failures by type instead of by message text

The table module decided HTTP statuses by searching error messages for
phrases. `VALIDATION_MESSAGE_FRAGMENTS` and `ROW_WRITE_ERROR_PATTERNS` held 32
substrings between them, and fifteen more lists were inlined in routes — 83
matchers over 17 files, each its own copy of the guesswork and already drifted
apart. It made message wording load-bearing: `TableRowLimitError`'s own doc
comment noted that its text had to contain "row limit" for a route to answer
400, and adding "already exists" to a rename message silently demoted a 409 to
a 400 (the bug fixed one commit ago, by adding another special case).

Services now throw `OrchestrationError`, which carries the transport-neutral
`OrchestrationErrorCode` the layers above already speak. Classification is one
`instanceof` in `orchestrationErrorResponse` (UI + v1) and
`v2CaughtOrchestrationError` (v2). Every pattern list is gone. Wording is free
to change; an unclassified error still becomes a generic 500, which is what an
unexpected fault should be.

`asOrchestrationError` walks the `cause` chain rather than testing the caught
value directly: drizzle wraps a throw raised inside a transaction callback in a
`DrizzleQueryError` whose own message is the failed SQL, so a bare `instanceof`
would drop every failure raised inside `withLockedTable`. That is the same
reason `rootErrorMessage` had to dig for a root cause before.

Three throws stay bare `Error` deliberately — `Table ID mismatch`, `Workspace
ID mismatch`, and `Failed to build upsert conflict predicate` are internal
invariants no consumer classified, and they keep falling through to a 500.
`Insufficient capacity` was in the pattern list with no producer anywhere in
the codebase.

Status changes, all deliberate:

- `'forbidden'` joins the code union so the table-row-limit ceiling keeps its
  403; without it this refactor would have flattened it to 400.
- import-async's table-limit rejection: 400 -> 403, matching the two other
  create routes it had drifted from.
- Renaming a table to an invalid name: 500 -> 400. `validateTableName`
  messages don't contain "Invalid", so no matcher ever caught them.
- Restoring a table that isn't archived, or into an archived workspace:
  500 -> 400.
- A duplicate *column* name stays `validation`/400 rather than becoming a 409
  like a duplicate table name. Both v1 and the orchestration have always
  answered 400 for it; changing a published status is not this refactor's job.

The twelve tests that changed were asserting the substring mechanism itself,
constructing plain `Error`s with magic strings. They now assert the real
contract, plus new cases pinning that identical wording carrying no
classification stays internal and keeps its message off the wire.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGzbVDZpe2dEALbu2BUU8a

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* feat(api): add v2 endpoints for MCP servers, skills, custom tools, folders, and credentials (#6150)

* feat(api): add v2 endpoints for MCP servers, skills, custom tools, folders, and credentials

* fix(api): correct credential role, skill permission bar, MCP url identity, and custom-tool conflict mapping

* fix(api): align credential mutation gating, provider-outage status, and unique-violation conflicts

* fix(api): close unique-violation, revival, orphan-write, and env-rename gaps

* fix(api): treat every provider-outage code as unavailable on create and update

* fix(credentials): use the shared outage predicate on the session update path

* fix(contracts): anchor the predicate double-cast annotation to the cast

`check:api-validation:strict` counted 9 unannotated double-casts against a
baseline of 8, failing CI. The predicate leaf schema was annotated, but the
annotation sat above the declaration while the checker anchors on the line
carrying the cast — five lines below, at the close of the object literal. The
scanner walks back at most three lines and stops at the first non-comment one,
so it hit `value: z.unknown().optional(),` and never saw the reason.

Splitting the object schema from the cast puts them adjacent, so the existing
reason binds. No behavior change — the cast, the schema, and the reasoning are
unchanged.

Also lowers the rawJsonReads ratchet 6 -> 5 to match the current count, which
had drifted down; leaving it high lets a removed raw read silently come back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(skills): point the orchestration error contract at its moved module

#6150 branched before #6134, so skill-lifecycle.ts imports
@/lib/workflows/orchestration/types — the module #6134 moved to
@/lib/core/orchestration/types. Git merged a file deletion on one side with a
new file referencing it on the other: no textual conflict, broken build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(knowledge): make lib/knowledge/orchestration the single implementation (#6154)

* refactor(knowledge): make lib/knowledge/orchestration the single implementation

Knowledge base create was implemented four times — the internal route, v1, v2,
and the copilot tool — and the orchestration around the shared write had
drifted. Extract it the same way lib/table/orchestration was: services write,
orchestration decides which writes run, guards them, audits them, and returns a
transport-neutral failure.

Behavior converged, not preserved:

- One chunking default (DEFAULT_CHUNKING_CONFIG). The agent defaulted minSize to
  1 against the API's 100, so identical input produced differently-chunked
  knowledge bases depending on who created it. The agent path now chunks at 100.
- Every successful mutation is audited inside the orchestration function. The
  copilot tool called recordAudit zero times, so agent-created knowledge bases,
  document uploads, updates and deletes left no audit trail at all.
- Failures classify by class, not by message text. The knowledge service errors
  are OrchestrationError subclasses and storage-quota rejections throw a shared
  StorageLimitExceededError, replacing four separate message greps for
  "already exists" / "does not have permission" / "storage limit".

delete_connector reported the opposite of what happened. It reached the route
through an internal HTTP self-call that sent no query string, so the route's
keep-documents default always applied while the agent told the user the
documents had been removed. The self-call is gone — all four connector
operations run in-process — and the orchestration returns the real counts.

Also:

- OrchestrationErrorCode gains 'payload_too_large' (413 / PAYLOAD_TOO_LARGE).
  Without it, dropping the storage-limit message match would have regressed the
  documented 413 on knowledge base create and document upload to a 500.
- messageForOrchestrationError renders a route's own wording for an unclassified
  fault, so a driver's message no longer reaches the client on a 500.
- v1 and v2 knowledge base update now forward actorUserId, which the service
  requires for a workspace move; both omitted it.
- The connector DELETE route reads deleteDocuments through parseRequest. Its
  contract declared z.boolean(), which would have rejected the string a query
  param actually is.
- Drop the 409 from POST /api/v2/knowledge/{id}/documents in the OpenAPI spec.
  Nothing on the upload path throws a conflict; it was only ever reachable by
  the message match this change removes.

Behavior change worth noting: a v1/v2 PUT carrying only the workspaceId scope
field and no actual updates now returns 400 rather than 200 with the unchanged
knowledge base.

Deliberately deferred: document update remains internal-only. Extracting
performUpdateKnowledgeDocument makes exposing it on v1/v2 a contract and a route
away, but that is a new public surface rather than part of this consolidation.

* fix(knowledge): make connector create atomic and stop flattening failures

Review round 1 on #6154.

- Resolve the billing payer before the connector is committed, not after. A
  malformed attribution header rejected post-commit left a live connector behind
  a 500, and a retry created a duplicate plus duplicate sync work. Manual sync
  resolves before writing its audit for the same reason.
- Let the source-config validator carry its own failure class. Collapsing every
  rejection to `validation` flattened the connector PATCH route's 401 (stale
  stored credential) and 409 (missing workspace context) into a 400.
- Add `unauthorized` to OrchestrationErrorCode. It is the class that 401 was
  already expressing on this route, and the v2 vocabulary already had
  UNAUTHORIZED; only the shared union was missing it.
- Report a knowledge base that exists but failed to archive as failed, with the
  reason, rather than as not found. The copilot delete loop folded every
  non-not-found failure into `notFound`, telling the user it was never there.
- Route copilot failures through the same message helper the HTTP surfaces use,
  so an unclassified fault's raw text (a driver's failed SQL) no longer reaches
  the agent verbatim while the UI and public APIs get the generic wording.

* feat(api): expand the public v2 files surface (#6160)

* feat(api): expand the public v2 files surface

Adds folder support, rename/restore, move, bulk archive, share, and content
replace to /api/v2/files, so managing files by API no longer stops at
upload + download + archive-one.

Routes are thin: auth -> parse -> perform* -> serialize. Share and content
replace get their orchestration extracted first so the session routes and
the public ones cannot diverge on the effective-authType resolution, the
EE public-sharing gate, or the storage-quota classification.

Presigned upload stays session-only: presign does an advisory quota check
and the real debit happens in the separate register step, so a caller that
never registers leaves unaccounted bytes with no reaper. The buffered
multipart path debits inside uploadWorkspaceFile's own transaction.

* fix(files): classify folder and content failures instead of 500ing them

Bugbot round 1. The v2 routes map errorCode straight to a status, so every
manager failure that arrived unclassified became a 500 for what is really a
caller-fixable 400 or 404.

- Folder manager throws OrchestrationError: missing target/folder -> not_found,
  reparent cycle / self-parent / restore-into-archived-workspace -> validation.
- File manager does the same for the in-transaction 'File not found' paths that
  the earlier pass missed.
- updateWorkspaceFileContent's outer catch re-wrapped everything in a bare
  Error, which stripped the class off StorageLimitExceededError and the new
  not_found alike. It now rethrows a classified failure untouched and attaches
  cause to the generic wrap, so asOrchestrationError can still walk the chain.
- Every remaining perform* gained the asOrchestrationError branch.
- renameWorkspaceFile returned the pre-update read, so the v2 PATCH reported a
  stale updatedAt; it now returns the timestamp it actually wrote.

Docs: upload auto-suffixes a duplicate name rather than rejecting it, matching
the in-app uploader. The description claimed 409 and was simply wrong.

* fix(files): surface a failed upload read-back as the real error

getWorkspaceFile swallows a query failure and returns null unless throwOnError
is set, so a transient blip on the post-upload read reported as 'file could not
be read back'. Distinguish the two: a real null after a just-committed write is
an invariant break, a query failure is itself.

* revert(api): drop the dedicated v2 file-folder routes

File folders already live in the shared folder table as resourceType 'file'
(#6045 cut them over, #6051 dropped workspace_file_folders), and the remaining
file-specific folder machinery is being folded into the generic folder engine.
Publishing /api/v2/files/folders/** would pin that transitional split into a
public contract we'd then have to keep or break.

Files stay folder-aware — folderId/folderPath on the projection, folderId on
upload, and the move route — because a folder id is a folder.id and survives
the unification untouched. Folder management belongs on /api/v2/folders once
that surface serves resourceType 'file'; until then there is no v2 way to
enumerate file folders, which is the deliberate gap.

The orchestration classification fixes stay: the internal routes and the
copilot file-folder tools still call those perform* functions.

* fix(files): classify upload failures instead of matching their wording

Bugbot round 2. uploadWorkspaceFile had the same outer-catch rewrap that
updateWorkspaceFileContent did, so a blown storage quota reached the route as a
bare Error and the v2 handler recovered the status by substring-matching the
message. Any rewording silently demoted a 413 to a 500.

- uploadWorkspaceFile rethrows a classified failure untouched and attaches cause
  to the generic wrap.
- FileConflictError is now an OrchestrationError('conflict'), so a duplicate name
  classifies like every other conflict. Its 'FILE_EXISTS' discriminator had no
  readers and is gone; the instanceof checks elsewhere still hold.
- The v2 upload handler uses v2CaughtOrchestrationError, dropping all three
  string matches.

Also documents that bulk-archive is best-effort: unknown or already-archived ids
are skipped rather than failing the call, and deletedItems is what actually
happened. That asymmetry with the single-id DELETE was undocumented.

* feat(api): add search, filtering, and sorting to the v2 list endpoints (#6189)

* feat(api): add search, filtering, and sorting to the v2 list endpoints

One convention across every v2 list, documented on lib/api/contracts/v2/shared.ts:
`search` (case-insensitive substring on the resource's natural name field),
`sortBy` + `sortOrder` (per-resource enum, never a free string), and enumerated
resource-specific filters. Reuses the sortBy/sortOrder pair v2 logs and v2
knowledge-documents already ship rather than inventing a third dialect
alongside the Logs filters and the Tables predicate grammar.

Every filter and sort is pushed into SQL. GET /api/v2/files previously read the
whole scope and sorted/sliced it in JS; it now goes through a new
queryWorkspaceFiles that filters, orders, and bounds the page in one query.

Cursors are stamped with the sort they were minted under, so replaying one
under a different sort is a 400 instead of silently duplicated or skipped rows.

* fix(api): validate v2 cursor key values and compare timestamps at ms precision

Two review findings, fixed at the root by making a keyset key own its cursor
codec instead of hand-writing a decoder per sort.

Cursor key values are caller-controlled, and matching the sort stamp and key
count was not enough: an unparseable timestamp or a non-numeric size reached
the query as an Invalid Date or NaN and surfaced as a 500. Each key now type-
checks its own value and rejects a cursor it cannot hold, which both routes
render as the documented 400.

Timestamp keys now order and compare on date_trunc('milliseconds', col).
Postgres keeps microseconds and defaultNow() populates them, but a cursor value
round-trips through a millisecond-only JS Date — comparing the raw column
against the truncated value re-admitted the page's own last row, duplicating it
and stalling pagination outright at a page size of one. Reachable today via
workspace_files.updated_at, which insertFileMetadata leaves to defaultNow().

* feat(api): complete the v2 workflows resource with versions and CRUD (#6184)

* feat(api): complete the v2 workflows resource with versions and CRUD

Adds version listing/detail plus create, update, and delete to the v2
workflows surface, which previously covered only execution and deployment.

- GET /api/v2/workflows/[id]/versions — cursor-paginated, newest first
- GET /api/v2/workflows/[id]/versions/[version] — version + pinned state
- POST /api/v2/workflows, PATCH and DELETE /api/v2/workflows/[id]

All six delegate to the existing orchestration and persistence helpers;
no new domain logic.

* fix(api): check folder containment before lock state; reject malformed version cursors

assertFolderMutable walks a folder's ancestor chain without filtering on
workspace, so inspecting it before containment let a caller tell a locked
folder in someone else's workspace (423) from a nonexistent one (400).
Create and update now assert containment first, matching the ordering
import-workflow.ts already uses.

A version cursor that decodes to JSON without a numeric version filtered
every row out and returned an empty page with nextCursor null, which reads
as a clean end-of-list. Malformed cursors are now a 400.

* refactor(api): page workflow versions in the persistence helper

listWorkflowVersions read every version row and the route filtered and
sliced the result in memory, so the response was bounded but the query
was not. It now takes optional limit/afterVersion, turning the cursor
into a real keyset query; the route asks for limit + 1 and only trims
the has-more probe. Both params are optional, so the internal, v1 admin,
and copilot callers are unchanged.

Also restores the untouched GET handler in [id]/route.ts to its original
formatting — collapsing its signature had re-indented the whole body and
buried the actual additions in whitespace churn.

* feat(api): expand v2 tables with stateless multipart transfers (#6188)

* feat(api): expand the public v2 tables surface

Adds 16 operations so a v2 caller can do what the internal surface can:
rename/move/lock a table, restore it, manage saved views, run enrichment
columns, look up rows, and import/export with observable job control.

Extracts lib/table/orchestration/import.ts (performTableCsvImport,
performCreateTableFromCsv) and lib/table/export-stream.ts from the
first-party routes, then repoints those routes at them, so v1 and v2
cannot drift on what an import or export actually does.

events/stream, metadata and dispatches stay internal — they are editor
state, not public API.

* fix(api): make v2 table PATCH all-or-nothing and name the lock in every 423

Greptile P1: PATCH applied locks, rename and move as three sequential
transactions, so a folder rejected mid-request left the earlier writes
persisted while the response reported failure — and the schema-changed
signal was skipped, leaving open clients on stale state. Every rejectable
condition now runs before the first write, and the signal fires whenever
anything did land.

Cursor: v2TableLockError dropped the lock kind, so async import, column
run, enrichment and table mutations returned a bare LOCKED. A table has
four independent locks, so the caller could not tell which to clear.

* fix(api): report the lock kind on classified 423s too, not just thrown ones

The previous commit named the lock only where the rejection was thrown and
caught at the route boundary. Where it instead arrives as a classified
`errorCode: 'locked'` outcome — delete table, delete row, update column,
and the table mutations — the kind was dropped, so those 423s stayed
unactionable while their neighbours improved.

The orchestration results now carry `lock`, and a shared
`v2TableOrchestrationError` renders both arrival paths into the same
`{ code, message, details: { lock } }` body. `details` is omitted rather
than sent null when the kind is unknown, so a caller branching on it sees
absence instead of a phantom value.

* fix(api): make async table imports observable, not just startable

`POST /import-async` pointed callers at `GET /api/v2/tables/jobs` to track
progress, but that endpoint filters to `type = 'export'` — imports are
derived onto the table itself, one write job at a time, and exports get a
separate list precisely because they are excluded from that derivation.
The public Table shape omitted those derived fields, so an async import
could be started and cancelled but never observed to completion, failure,
or progress. That is the gap the import/export/job-control set was meant
to close.

Table now carries `job` — id, type, status, rowsProcessed, error, or null
when idle — and the import-async docs point at the table rather than the
export list.

* feat(api): make v2 table PATCH state which operations landed on failure

Greptile held the PR at 4/5 on the residual non-atomicity and named two
acceptable resolutions: make PATCH atomic, or have the contract adopt and
expose partial-success explicitly. Atomicity would mean threading one
transaction through renameTable, moveTableToFolder and updateTableLocks —
three shared service functions with four non-test callers including the
first-party route and two copilot tools — and deferring their per-operation
audits to commit time. That is a refactor of shared write paths well
outside this PR.

So the contract states it instead. Every rejectable condition is already
pre-validated, so a failure here is a genuine fault; when one follows a
successful operation the error now carries `details.applied` listing what
is live. Absent when nothing applied, so its presence always means "these
changes took effect despite the error". Documented on the operation.

`v2ErrorForOrchestration` gained the optional `details` this needs.

* fix(api): make table lock flags read-only on the public v2 surface

The new PATCH /api/v2/tables/[tableId] accepted a `locks` object, gated
on workspace admin plus the table-locks feature. That still lets an API
key clear the guard placed there to stop it: `write` is the floor for
the endpoint, and admin keys are ordinary API keys, so a lock is no
longer a boundary the key cannot cross.

Locks stay readable on the table resource and enforcement is unchanged
(a locked verb still returns 423). Changing one is now a first-party
admin action only.

The v2 body is declared here rather than reusing the first-party
updateTableBodySchema, which keeps its `locks` field so the UI can still
toggle them. It is .strict(), so a request carrying `locks` is rejected
with a 400 naming the field instead of silently succeeding without
applying it.

* fix(api): keep reporting applied operations when the PATCH re-read fails

The composite table PATCH promises that `error.details.applied` names the
operations that are live despite an error, but `applied` was scoped
inside the try. A rename or move that committed and was then followed by
a throw in the final re-read — or a re-read finding the table archived —
returned a bare 500/404 with no details, telling the caller nothing had
landed. It would then retry into a duplicate-name conflict or repeat the
move.

`applied` is now function-scoped so every post-write exit carries it: the
404 on a missing re-read, a thrown lock error, a classified orchestration
error, and the generic 500. `v2TableLockError` gains the same
`extraDetails` parameter `v2TableOrchestrationError` already had.

* feat(api): add workflow group writes to the v2 tables surface

v2 exposed GET /groups but none of the writes, so the public API could
run an enrichment or workflow column and read its binding, but never
create one. A caller could add a plain data column and trigger the
machine; wiring the two together still required the UI.

Adds POST/PATCH/DELETE on /api/v2/tables/[tableId]/groups. The group is
the unit that fills columns — one group feeds several — so creating one
creates its output columns in the same call, matching the first-party
shape rather than inverting it onto the column endpoint.

Four departures from the first-party body, all public-surface concerns:
- group.id is optional and server-generated. The UI mints an id to render
  optimistically; a public caller has no such need and a client-chosen id
  is a collision waiting to happen.
- outputColumns[].workflowGroupId is dropped from the body and stamped
  from the resolved group, so it cannot disagree with it.
- autoRun defaults to false. First-party defaults true so a UI add fills
  cells immediately; here it would make one POST fan out a metered run
  across every existing row.
- A group naming neither a workflowId (type manual) nor an enrichmentId
  (type enrichment) is a 400 rather than a half-specified group the route
  has to guess about.

Also rejects an outputColumns entry no group output feeds — the two
arrays are joined by column name, and the first-party client builds both
from one picker so it cannot desync, but a public caller can.

Workspace containment on workflowId is asserted before it is persisted,
on create and on any update that re-points the group; without it a table
becomes a way to invoke workflows the key cannot otherwise reach.

* improvement(api): make v2 table import and export async-only

Drops the three synchronous entry points: POST /tables/[tableId]/import,
POST /tables/import-csv, and GET /tables/[tableId]/export.

Sync import tied a write to the lifetime of an HTTP request. The body
*was* the data, so it carried a 10 MB cap that Next silently truncates
past — a partial import reporting success. It also had no job, so a
timeout mid-write left rows in place with nothing to poll and nothing to
cancel. The async path reads the file from storage instead: upload via
POST /api/v2/files for a key, start with POST /import-async, watch
GET /tables/[tableId] -> job, stop with POST /job/cancel.

Sync export carried no such hazard, but one shape per operation beats
two: with both removed the surface has exactly one way to move a table
in or out, and the CLI wraps the extra calls.

This also removes the last multipart handling in v2 tables. Those were
the only routes bypassing parseRequest — form fields were parsed by hand
against separate form schemas, outside the contract system every other
v2 write goes through.

Create-a-table-from-CSV is now two calls: POST /tables, then
/import-async with createColumns. csvImportModeSchema is append|replace,
so there is no single-call create.

Route baseline 1064 -> 1061.

* docs(api): correct the import-async note about upload size limits

The docstring claimed there is no synchronous upload endpoint and so no
request-body size cliff. Both are wrong: POST /api/v2/files is a
synchronous multipart upload with a 100 MB cap, and it is the only v2
upload path (presigned is deliberately absent).

What async-only actually bought: the cap went 10 MB -> 100 MB, it fails
on an explicit size check and a bounded body read rather than a proxy cap
that silently truncates, authorization completes before any body is
buffered, and the table write is a job that can be watched and cancelled.

* feat(api): unify file and table transfers

* improvement(api): make multipart transfers stateless

* fix(api): make table import completion retries idempotent

* feat(v2-tables): paginate the table list

`GET /api/v2/tables` returned every table in the workspace in one response —
it used the cursor envelope but hardcoded `nextCursor: null`, and had no
`limit`. That was defensible when tables were only created through the UI;
`POST /api/v2/tables` is public now, so a script can create them in bulk and
the list has no way to ask for less.

Adds `queryTables` alongside `listTables` rather than changing it, so the
internal callers that genuinely want the whole scope are untouched — the same
split `queryWorkspaceFiles` / `listWorkspaceFiles` already uses. Filter, order
and slice all run in the query, so a `search` never costs a full-workspace read.

A cursor whose values don't bind raises a validation error instead of being
coerced to "no filter", which would have silently served page 1 under a resumed
cursor. The keyset closes on `id` so a page boundary inside a run of equal names
or timestamps stays stable.

The shared `LimitQuery` doc component said "Maximum rows to return"; it now
serves the table list too, so the wording is resource-neutral.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(api): add multipart knowledge document uploads

* fix(api): keep usage admission at knowledge upload session creation

* feat(knowledge): wire knowledge base uploads to multipart sessions

* fix(knowledge): refuse to abort an upload once a document is bound

* fix(uploads): prevent multipart cleanup races

* Unify file creation and signed upload sessions (#6264)

* feat(uploads): unify signed upload sessions

* fix(uploads): preserve attachment storage semantics

* feat(files): add authored file creation

* fix(uploads): omit hoisted S3 metadata headers

* feat(api): add file metadata endpoint

* improvement(api): scope folders to resource paths (#6284)

* improvement(api): scope folders to resource paths

* fix(files): serialize folder resolution with uploads

* fix(files): release folder lock before upload setup

* fix(api): normalize folder paths and unblock resource mutations

* fix(api): make resource cleanup and metadata consistent

* improvement(uploads): persist multipart sessions in postgres

* fix(db): store table row trigger timestamps in UTC

* improvement(api): default folder deletion to non-recursive

* fix(billing): unify chat usage source

* improvement(logs): expose trace spans on log detail

* fix(logs): parse list trace spans

* improvement(api): replace workflow jobs with execution resources (#6294)

* improvement(api): replace workflow jobs with execution resources

* fix(api): preserve legacy jobs while preferring v2 executions

* fix(api): make execution polling resume-aware

* fix(ui): hide async examples for public workflows

* fix(api): bridge resume queue visibility lag

* feat(api): add v2 workflow resume endpoint

* fix(api): project pending resume attempts

* fix(api): prefer terminal logs over stale resumes

* improvement(api): unify v2 resource query layers (#6319)

* improvement(api): unify v2 resource query layers

* fix(api): address v2 review findings

* fix(api): preserve cancelled queue status

* fix(api): guard cancelled job transitions

* fix(api): close v2 resume and log gaps

* feat(api): rename v2 executions to runs

* feat(api): split credentials and secrets

* feat(api): add workspace metadata and email attribution

* improvement(api): consolidate public v2 route handling

* improvement(files): centralize operations across APIs and Copilot (#6392)

* improvement(files): unify rename authorization

* chore(skills): add file operation migration guide

* improvement(files): consolidate file operation authorization

* improvement(files): extract shared operation foundation

* improvement(api): simplify internal route declarations

* improvement(files): centralize application authorization

* refactor(api): share workspace file name validation

* refactor(files): centralize copilot application calls

* docs(skills): generalize application operation migration

* improvement(api): centralize remaining v2 resource operations (#6412)

* improvement(api): centralize v2 resource operations

* fix(api): preserve custom tool conflict errors

* improvement(api): migrate policy-sensitive v2 reads (#6410)

* improvement(workflows): centralize v2 application operations (#6411)

* refactor(api): migrate v2 knowledge operations (#6413)

* refactor(api): migrate v2 knowledge operations

* fix(knowledge): fail upload completion on dispatch errors

* fix(knowledge): preserve upload retry and VFS errors

* improvement(tables): centralize v2 application operations (#6414)

* improvement(tables): centralize v2 application operations

* fix(tables): preserve run validation and signals

* feat(auth): add scoped internal executor delegation (#6459)

* feat(auth): add scoped internal executor delegation

* fix(auth): derive delegation lifetime from one timestamp

* Include share status in file metadata

* feat(auth): centralize delegated identity policy (#6462)

* improvement(copilot): consolidate application adapters (#6450)

* improvement(api): harden application route boundaries (#6451)

* improvement(api): harden application route boundaries

* fix(folders): reject creates at workspace cap

* fix(knowledge): enforce trusted workspace scope (#6452)

* fix(knowledge): enforce trusted workspace scope

* refactor(knowledge): declare v2 body lifecycle

* finish knowledge application migration

* refactor(knowledge): compose copilot batch commands

* fix(knowledge): parse connector query flags

* fix(knowledge): finalize partial batch effects

* fix(knowledge): align merged application boundaries

* fix(knowledge): close application boundary review gaps

* style(knowledge): satisfy branch biome checks

* fix(knowledge): page connector documents in editor

* refactor: enforce Copilot table application boundary (#6453)

* refactor: enforce copilot table application boundary

* fix(tables): finish application boundary migration

* fix(tables): restore scoped copilot imports

* fix(tables): compose copilot commands atomically

* fix(tables): preserve workflow group scheduling

* fix(tables): complete fixed copilot composition

* fix(tables): reject enrichment output mutation

* fix(tables): complete authorized application boundary

* fix(workflows): migrate Copilot application boundary (#6455)

* fix(workflows): migrate Copilot application boundary

* fix(workflows): finish delegated application migration

* fix(workflows): encode VFS folder aliases

* fix(workflows): close application composition gaps

* fix(workflows): preserve VFS validation errors

* fix(workflows): complete application boundary migration

* test(workflows): format canonical binding coverage

* fix(workflows): scope executor metadata reads

* fix(workflows): bind executor metadata targets

* improvement(skills): align application operation guidance (#6532)

* feat(api): expose v2 resource owners

* fix(api): distinguish visible resource authorization failures (#6537)

* feat(api): generate v2 OpenAPI from contracts (#6509)

* feat(api): generate v2 OpenAPI from contracts

* fix(api): preserve string boolean wire defaults

* fix(api): document file download headers

* fix(docs): use TypeScript CLI with Next.js

* fix(docs): avoid client-rendered theme script

* fix(api): document departed audit default

* feat(api): replace legacy core docs with v2

* feat(api): generate v2 OpenAPI from contracts

* feat(api): refine generated v2 OpenAPI docs

* fix(docs): align localized v2 execution examples

* fix(ci): restore Helm diff and sync audit mock

* fix CI regressions after staging merge

---------

Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 05:45:25 -04:00
Waleed 2f43148d60 improvement(ui): align terminal with the workflow design system, fix row hover states (#6534)
* improvement(ui): align terminal with the workflow design system, fix row hover states

Terminal:
- derive log-row block tiles the way the canvas does (role accent for core
  blocks and subflows, provider colour only for role-less integrations)
- compose rows from chipGeometryClass, chipContentLabelClass and
  disclosureChevronClass instead of re-deriving the pill
- align the output tree's greys with the log rows; share ROW_STYLES.nested
  and BADGE_STYLE instead of duplicating the literals
- neutralise value-type badges so red is the only colour in the tree
- unify the row/separator gutter; normalise icon sizes to size-[14px]
- drop dead flattenEntryTree and the RunningBadge re-exports

Hover model (chipVariants, PopoverItem, Combobox, docs sidebar, terminal):
- hover paints --surface-hover, one step below the --surface-active a
  selected row keeps, so a hovered row no longer impersonates the selected one
- an active row holds its surface through hover instead of brightening

Deploy modal:
- move the footer actions onto the Chip family, primary action as
  Chip variant='primary' to match every other modal footer

* improvement(emcn): extract the row-state surface pair, simplify the terminal rows

Review follow-ups from /simplify and /cleanup:

- add chipHoverSurfaceClass / chipActiveSurfaceClass to chip-chrome as the one
  home for the two-surface row model, and route chipVariants, PopoverItem,
  Combobox, the docs sidebar, the landing preview and queued messages through
  them instead of restating the literals
- terminal ROW_STYLES now renders chipVariants rather than re-deriving its
  output, and the four rows share content/label/status classes resolved once
- structured-output composes chipGeometryClass with an h-auto override rather
  than restating four of its literals
- getEntryAccentType collapses to one expression, dropping the SYNTHETIC_BLOCK
  types coupling
- collapse the chip compound variants to two array-matched entries
- deploy modal: hoist the shared loader adornment, size it with
  chipContentIconClass so it matches every other chip icon
- landing preview drops its --c-active/--c-hover inline aliases for the tokens
- trim the rationale to one canonical copy with cross-references, and convert
  the block comments on declarations to TSDoc

* improvement(workflow): align canvas controls with the canvas surface and icon scale

- floating controls sit on --surface-2, the surface the block cards use, rather
  than --surface-1 (the sidebar/panel surface)
- undo/redo/fit glyphs drop 16px -> the platform's 14px default, and the mode
  dropdown's own 12px icons come up to match; the control had three icon sizes
- inactive buttons hover to --surface-hover instead of --surface-5, which was
  the active mode button's resting fill, so hovering one looked selected
- inner radius goes concentric with the 4px padding (rounded-sm inside
  rounded-lg)

* improvement(workflow): give the canvas-mode chevron the same treatment as its siblings

It was the only control in the cluster with no hover fill and a different rest
colour (--text-muted against the others' --text-secondary), so it read fainter
and behaved differently under the pointer. It is also a disclosure chevron, so
it now uses disclosureChevronClass instead of a hand-rolled duration-100 copy,
and a real 20px box instead of the !p-1.5 override plus -m-1 hit-area hack.

* test(terminal): lock the log-row accent rule against the block toolbar

getEntryAccentType encodes a cross-surface rule — a block must be accented the
same way in the terminal as in the block toolbar — and nothing enforced it. The
table covers every branch: core blocks mapped and unmapped, role-bearing and
role-less integrations and triggers, the config-less subflows, and the
synthesized error/validation/cancelled rows that must keep their status fill.

Verified failing: reverting the guard to an unconditional return reds two of
the four cases.

* fix(workflow): make notifications track panel and terminal resize live

The toast stack insets by --panel-width / --terminal-height, but a resize drag
writes those to the resized subtree only (.panel-container / .terminal-container)
rather than to :root, because a custom-property write on :root recalculates the
whole document (~150x slower). The stack is portalled to <body>, so it shares no
ancestor with either and kept reading the stale :root value — it held its
pre-drag position and jumped once the drag committed, while the canvas controls,
which are laid out inside the shrinking canvas, tracked the drag in realtime.

useDragResize now accepts several target subtrees and writes each one, so the
scoped recalc is preserved and every consumer follows the drag frame by frame.
The stack is found through a new data-toast-viewport attribute.

Also drops the canvas controls from bottom-4 to bottom-2: the toast clears the
terminal by 8px (it anchors from the viewport, and the terminal is inset by
CONTENT_WINDOW_GAP), where the controls measure from the canvas floor and so
sat at twice the gap.

* improvement(workflow): inset the canvas controls 8px off both edges

The toast stack clears the terminal and the panel by 8px — it anchors from the
viewport at --terminal-height/--panel-width + 16px, and both are themselves
inset by CONTENT_WINDOW_GAP (8px). The controls measure from the canvas floor
and wall instead, so their 16px read as twice the gap on both axes.

* improvement(workflow): lift the canvas controls and toasts to a 12px clearance

8px sat them too close to the terminal. 12 is on the same 4px grid as the
surrounding spacing, where 10 would have been the only off-grid value in the
area. Both surfaces clear the terminal and the panel by the same amount, so they
read as one row; the toast's literals move into named insets rather than staying
bare numbers in a style object.

* refactor(hooks): split the drag's resize target from its other var consumers

getTarget briefly accepted a list, which made the first entry both the resized
element and the drag's liveness reference. A toast auto-dismisses after 5s, so
had one ever led that list, its mid-drag unmount would have read as the drag
target detaching and skipped the final recompute on release. The co-consumers
now come through getExtraTargets, which is written but never consulted for
liveness, and can come and go freely.

* fix(emcn): stop the combobox cursor diverging from what Enter commits

The option rows painted --surface-active from CSS :hover as well as from
isHighlighted. CSS :hover tracks the pointer continuously while highlightedIndex
only advances on mouseenter, so once the list scrolled under a stationary
pointer the row that looked selected was not the one Enter would commit —
Enter reads filteredOptions[highlightedIndex].

isHighlighted is now the single source of truth for the cursor, so paint and
commit cannot disagree. The row under a stationary pointer may lag a scroll
until the mouse moves, but it lags in agreement with what Enter will do, which
is the invariant worth keeping. Disabled options also stop painting on hover,
matching the mouseenter guard that already refused to highlight them.

The 'All' row keeps its own hover: it clears the highlight rather than taking
it, so it has no isHighlighted paint to fall back on.

* fix(toast): derive the workflow inset from the shell's actual padding

WORKFLOW_INSET_PX baked in the 8px the workspace shell normally insets the panel
and terminal by, so the stack's 20px resolved to a 12px clearance — matching the
canvas controls. But the shell drops to p-0 on the desktop title-bar shell with a
collapsed sidebar, and there the stack would have sat 20px out while the
controls, laid out inside the shell, stayed at 12.

The stack now adds --workspace-content-gap (published on :root, zeroed by the
same condition that zeroes the padding) to a flat 12, so the two surfaces hold
the same clearance in both configurations. Before this PR they matched in the
p-0 case at 16px each, so this closes a divergence the PR would otherwise have
introduced.
2026-08-11 00:28:08 -07:00
Waleed 533afaaedc fix(docs): stop the pinned sidebar running under the site footer (#6422)
The sidebar and its divider are fixed to the viewport, so at the end of the
page the footer was drawn over them and the lower part of the nav list became
unreachable.

FooterOverlapProbe publishes how far the footer reaches into the viewport as
`--docs-footer-overlap`. The sidebar reads it as `bottom`, so it keeps its full
height and slides up out of view as the footer arrives; the divider reads it too
but is shortened rather than slid, so it terminates on the footer's top border
instead of stopping short.

Measured against the viewport rather than the document on purpose: the value is
a constant 0 while the footer is off screen, so a content-height change higher up
the page cannot move the sidebar. Verified with Playwright at 1280x800 and
2000x1100 — expanding/collapsing an FAQ with the footer off screen moves the
sidebar 0px/0px and leaves the content column unchanged, and at the page bottom
the sidebar's bottom edge lands within ~1px of the footer's top.
2026-08-08 12:10:31 -07:00
Waleed a7d6b96132 fix(docs): stop the sidebar drifting when page content resizes (#6301)
* fix(docs): stop the sidebar drifting when page content resizes

The sidebar used fumadocs' `sticky` positioning, and a sticky box is
bottom-limited by its containing block. #nd-docs-layout ends ~660px above the
document bottom because the site footer is a sibling of the layout rather than
a grid child, so across the whole footer the sidebar was pushed progressively
upward. Any content-height change while the reader sat in that zone then moved
it: expanding one FAQ row shifted the sidebar 16.8px at a fixed scroll offset,
and collapsing it shifted it back.

Pin the sidebar and its divider to the viewport instead. A fixed box ignores
both the container's end and the document height, so neither the drift nor the
jump can happen. The grid columns are explicit (`0px 300px 1fr 268px 0px`), so
taking the placeholder out of flow leaves its track intact and the content
column does not move. The footer is already opaque and now out-stacks both, so
it slides over them at the end of the page.

Measured with Playwright before and after: sidebar delta on expand/collapse
16.8px/-16.8px -> 0px/0px, content column left and width unchanged, and the
sidebar holds top:92px at the page bottom on the docs, API-reference, academy
and integrations layouts. Mobile is untouched (the rule is desktop-only).

* refactor(docs): drop dead grid placement, move footer stacking to the component

Review follow-ups. The divider's `grid-row`/`grid-column` stopped doing anything
the moment it became `position: fixed` — a fixed box is out of grid layout
entirely — so they and the comment explaining the grid span were describing
positioning that no longer happens. Verified inert: the divider still computes
to left 300px / width 1px / z-index 21 without them.

The footer's stacking context also belongs on the footer, not in a global rule
matching every desktop `footer` element, so it moves to the component as
`relative z-[22]` with the reason in its TSDoc.
2026-08-05 16:26:29 -07:00
Waleed dcaa118752 improvement(docs): restructure sidebar, align chrome, rename Mothership to Chat (#6296)
Sidebar: 11 separator groups become 5, with each module a collapsible folder
that auto-opens on the active page. 61 always-visible rows drop to 16. Groups
mirror the app's own nav (Chats/Workspace/Workflows) rather than inventing a
taxonomy; Enterprise and Self-Hosting are hoisted out of Platform.

Chrome: register the `hover-hover` variant, without which every @sim/emcn hover
state silently compiled to nothing; restore the sidebar's Geist font stack; add
11 emcn tokens that were falling back to currentColor; adopt the named type
scale; align row geometry, hover tokens and group labels with the app.

Rename: mothership/ -> chat/ with redirects for the old URLs. Asset paths,
the @mothership.sim.ai domain and the `mothership` log-trigger enum value are
deliberately left alone -- they are CDN objects, a real domain, and a live
product value.

Also removes the page-type badge, drops the "Next" heading from the ToC, and
lets FAQ rows open independently so expanding one no longer shifts the page.
2026-08-05 13:49:17 -07:00
Waleed 39c3fe60d9 improvement(docs): remove Ask AI, add the missing platform surfaces, align the sidebar (#6259)
* improvement(docs): remove Ask AI, add missing surfaces, align sidebar to the app

- Removes the Ask AI widget, its /api/chat route, and the four deps exclusive
  to it (@ai-sdk/openai, @ai-sdk/react, ai, streamdown). lib/embeddings and
  docsEmbeddings stay — /api/search uses them.
- Adds --surface-7 and --surface-hover, the last two platform surfaces docs
  lacked.
- Aligns the sidebar with the app's canonical nav chrome: px-2.5 -> px-2,
  text-small -> text-sm, hover --surface-3 -> --surface-active, and an active
  hover of --surface-6, matching the Chip the app's sidebar items are built on.

* fix(docs): make the sidebar hover CSS agree with the utilities

Review caught that the sidebar hover alignment in this PR had no visual
effect. `global.css` carries !important rules for both the link and button
sidebar items — they exist to beat fumadocs' own styles — and they were still
forcing the pre-alignment values: --surface-3 on an inactive hover, and
--surface-active on an active hover.

So the Tailwind utilities were dead on arrival. The global rules now carry the
app's values instead (--surface-active inactive, --surface-6 active), matching
the utilities rather than fighting them, with a comment noting the two must
move together.
2026-08-04 12:45:10 -07:00
Waleed 3d8b2ed050 improvement(docs): inherit the platform border and font-weight scales (#6252)
* improvement(docs): inherit the platform border and font-weight scales

#6241 consolidated the app's neutral border tokens and flattened its
font-weight scale. `apps/docs` was carrying an untouched copy of the
pre-migration values, so the two have visibly drifted — the docs `@theme`
block already declares it "mirrors apps/sim/tailwind.config.ts", so the
drift is against stated intent rather than a deliberate divergence.

Borders — same consolidation as the app:
- `--border` #dedede -> #d8d8d8 (light), #333333 -> #444444 (dark)
- `--border-1` and `--border-muted` become aliases of `--border`, so the 19
  existing `var(--border-1)` consumers pick up the unified colour without
  being touched
- `--divider` is retired; its single consumer moves to `--border`

Font weights — the arbitrary values the app dropped:
- `font-[480]`/`font-[470]`/`font-[500]` -> `font-medium`, `font-[430]` ->
  `font-normal`, `font-[600]` -> `font-semibold`

The navbar's active tab and its invisible width-reserving ghost both used
`font-[480]`; they move to `font-medium` together, so the anti-layout-shift
trick still holds.

Deliberately NOT ported: the app's `--border-width` hairline (0.5px on hi-dpi).
The app wires it through `borderWidth.DEFAULT` in a Tailwind v3 JS config; docs
is Tailwind v4 CSS-first, which hardcodes `border: 1px` in the utility with no
theme key, so matching it means overriding a Tailwind utility. That is a
site-wide visual change and wants its own PR with visual review.

Also unchanged: the inline SVGs. `components/icons.tsx` (328) is the brand and
integration set, `sim-logo` is a brand mark, and the handful of remaining
shapes are bespoke and positioned by hand. Docs already consumes
`@sim/emcn/icons` in the 15 places where a shared icon is the right call, and
imports zero lucide.

* improvement(docs): finish the platform token sweep

Follow-on within the same PR. A full comparison of every custom token docs
rolls against the platform found three more classes of drift.

Text scale — #6241 retuned these and docs kept the old values:
- `--text-body` #3b3b3b -> #434343 (light), #cdcdcd -> #c1c1c1 (dark)
- `--text-muted` #707070 -> #7a7a7a (light), #787878 -> #6e6e6e (dark)
- `--text-icon`  #525252 -> #5a5a5a (light), #a0a0a0 -> #969696 (dark)

Docs and the app now agree on all 62 shared token names, with zero divergent
values.

Missing shared-component tokens. Docs renders `@sim/emcn` (Badge, Chip,
ChipLink) and `@sim/workflow-renderer` (block, subflow and note views), but
never defined 14 of the tokens those components reference — an undefined
`var()` silently falls back to `currentColor`, so the failure is invisible
until the branch that uses it renders. Several are live: `--warning` on an
edited subflow, `--caution` on inline code inside a note, `--text-placeholder`
on an empty note, `--border-success` on a successful run edge. Added with the
app's values, along with `--text-icon-muted` (new in #6241) and the four Badge
palettes docs lacked (teal, cyan, pink, blue-secondary), so any variant renders
correctly rather than being one prop away from black.

Type scale — docs declares micro/xs/caption/small/base/md in `@theme` but 20
call sites bypassed it with identical raw values (`text-[13px]`,
`text-[0.8125rem]`, `text-[12px]`, `text-[15px]`, `text-[10px]`). Each now uses
the token; every value is byte-identical, so this is a rename, not a restyle.
The class reordering in the same files is biome's `useSortedClasses` reacting
to the rename — verified as a pure permutation, with the class multiset
unchanged in every file.

Deliberately left alone:
- `#33C482` / `#2FB3FF` — brand-mark SVG fills and default props in demo data,
  not styling.
- The Ask AI button's inverted `#383838`/`#575757`/`#e0e0e0`/`#cfcfcf`. The
  platform's `--surface-inverted`/`--surface-inverted-hover` hold *different*
  values, so adopting them would restyle the control rather than tokenize it.
  Worth doing, but as a visual change with review.
2026-08-04 10:54:02 -07:00
Vikhyath MondretiandClaude 11c0d3b75d feat(organizations): sweep a joiner's owned workspaces into the org on join, disclose it at accept, and add external workspace invites (#5918)
* feat(invites): explicit external members

* update docs

* fix(organizations): atomic admin workspace sweep, removal-impact status in dialog, and preview-unavailable disclosure

Review round 1: the v1 admin add-member now commits membership and the
workspace sweep in one transaction; the remove-member dialog holds confirm
while the credential-impact check loads and shows a caution when it fails;
a failed join preview flags joinPreviewUnavailable so the accept screen
falls back to a generic migration notice. Also aligns the invite test's
react-query mock and repairs two pre-existing docs type errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(organizations): close the concurrent-workspace escape in the join sweep

Personal workspace creation now serializes with organization joins on the
user's billing-identity lock and re-verifies membership inside its
transaction; both join paths (invite acceptance and the v1 admin add)
re-read the owned-workspace set under that lock after the member insert
and roll the whole join back when it diverged from the advisory-lock plan,
so a workspace created mid-join can never land outside the organization.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(organizations): fail stale-grant member joins and re-resolve the creation race client-side

A member-role org acceptance whose grants all turned stale now rolls back
with workspace-not-found instead of stranding a workspace-less member, and
the workspace resolver treats the creation-vs-join 409 as a signal to
re-resolve (the user is authenticated with org workspaces) rather than
falling into the login path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(invitations): mirror the stale-grant gate in the join preview

The accept-screen preview now returns no-join for a member-role org
invite whose grants all left the stamped organization, matching the
acceptance-side rollback so the disclosure never promises a migration
that acceptance would refuse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(workspaces): survive the join race on lazy default creation and gate removal on live impact data

The workspace list GET now re-lists (returning the join sweep's
workspaces) when lazy default creation loses the race to an organization
join instead of failing with a 500, and the remove-member dialog gates
its confirm on isFetching so a background refetch can never let an admin
confirm against a stale credential-impact list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(invitations): surface the accept conflict message and refresh workspace caches post-accept

The accept route now carries the human-readable message alongside the
machine-readable error kind (the client prefers it for server-error), and
a successful accept invalidates workspace queries so the swept workspaces
appear immediately instead of after the stale window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(billing): sweep archived workspaces in Pro-to-Team conversion and org creation

Every attach call site now passes includeArchived so the archived escape
hatch is closed uniformly — join-attach, admin move, subscription-driven
org provisioning, and manual org creation all sweep archived personal
workspaces into the organization.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(invitations): reject acceptance when the sweep set differs from the disclosed set

The join preview now carries the workspace ids it disclosed, the accept
screen echoes them back as a disclosure token, and acceptance rolls back
with disclosure-outdated (409) whenever the set it would sweep no longer
matches — a workspace created after the preview rendered can never move
without the user seeing the refreshed notice first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(invitations): send the disclosure token for no-join previews too

A preview that predicted no join still tells the user nothing moves — the
empty disclosed set is now echoed on accept, so a join that becomes
possible between preview and accept (left another org, billing turned
usable, grants un-staled) conflicts with disclosure-outdated instead of
sweeping workspaces without a rendered notice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(invitations): gate all-stale member joins before disclosure and always refetch removal impact

The all-stale check for member-role org invites now runs before any
mutation and before the disclosure comparison, so an invite whose grants
all left the org fails with workspace-not-found instead of trapping
owners of personal workspaces in a disclosure-outdated retry loop. The
removal-impact query drops its stale window (staleTime 0): every dialog
open refetches while the confirm is held on isFetching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(organizations): keep-external on org recovery and label archived move candidates

Org creation/recovery now uses the keep-external collaborator policy
(matching Pro-to-Team conversion) so different-org collaborators on
archived workspaces cannot abort it with a conflict, and the admin
workspace-move search and preflight expose an archived flag so internal
tooling can label archived targets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(invitations): guard the reverse disclosure direction

A will-join notice whose acceptance downgrades to no-join (stale
escalation denial, concurrent other-org membership) now fails with
disclosure-outdated instead of silently succeeding as an external grant —
the disclosure token binds the outcome in both directions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* address comments

* fix

* fix(invitations): one invite surface, coalesced grants, coherent seat model

Consolidate the two invite modals into a single surface and fix the
semantics the split had been hiding.

Invite flow:
- One InviteModal for all three entry points (workspace header, workspace
  settings, organization settings), with a workspace multi-select and an
  explicit Membership choice that states the seat consequence.
- Coalesce grants instead of 500ing. A partial unique index allows one
  pending invitation per (email, organization), so inviting someone to a
  second workspace raised a raw 23505. New workspaces now merge into the
  pending invitation (with a retry for the concurrent-insert race) and the
  invitee gets one link covering everything.
- External collaborators require their own paid plan, checked at invite
  time and re-checked on accept, since the invitation lives for 7 days.
  Imposed externality is exempt in both places: an invitee already in
  another organization is forced external regardless of the inviter's
  choice, so the plan gate must not apply to them.
- Every invitation must grant at least one workspace, so accepting always
  lands somewhere. Enforced at creation for all roles.
- Revocation is grant-scoped. Since one invitation can span workspaces,
  revoking from a workspace's member list withdraws only that grant and
  cancels the invitation just when the last one goes. Whole-invitation
  revocation now requires authority over all of it rather than admin on
  any single granted workspace.
- The accept screen names every granted workspace and states whether the
  invitee joins as a member, an admin, or an external collaborator, and
  whether that uses a seat.

Seats:
- One rule for the pending-invitation predicate, seat capacity, and the
  derived figures; the three counting sites now share it.
- Team seats are elastic (subscription.seats tracks the member count), so
  treating that as a cap reported negative headroom on any outstanding
  invite. Available seats are clamped and gates branch on whether the plan
  actually has a fixed cap.
- POST /api/v1/admin/organizations/[id]/members could never succeed on
  Team: it validated N members against N seats. It now skips the cap for
  elastic plans, matching invitation acceptance, and reconciles seats
  after a committed add.

Also surfaces the External label on the workspace Teammates list, which
already received the flag and dropped it, and removes dead code: the
grantless organization-invite route and contract, three unreferenced
invitation helpers, and the unreachable
ensureUserInOrganization/addUserToOrganization/validateMembershipAddition
cluster.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): close the two accept-disclosure gaps Bugbot found

Membership notice ignored the join preview. `buildMembershipNotice` keyed off
the invitation's sent `membershipIntent`, but acceptance resolves an internal
invite to external when the invitee already belongs to another organization, or
when the granted workspace changed organizations after the invite went out. The
screen therefore promised "you'll join as a member, which uses one of their
seats" to people who would neither join nor consume a seat. It now keys on the
preview's `willJoinOrganization` — the same signal the migration notice already
used — and falls back to the sent intent only when no preview could be computed.

In-app accept skipped the disclosure entirely. `useAcceptMyInvitation` posted an
empty body, so `disclosedWorkspaceIds` was absent and the server's consent guard
was skipped, and the pending-invitations modal never showed which owned
workspaces would move. Accepting from the workspace switcher (including the
desktop path) could silently sweep personal workspaces into the organization,
bypassing the consent model this PR adds on /invite. The list endpoint now
returns each invitation's join preview, the modal renders the same
membership/migration disclosure as /invite, and accept echoes
`disclosedWorkspaceIds` so the guard applies on both paths.

Both notices moved into lib/invitations/disclosure-copy.ts and are consumed by
/invite and the modal, so the two accept surfaces cannot drift into disclosing
different outcomes for the same invitation — which is how this gap arose.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): carry the membership outcome in the disclosure token

Empty disclosure skipped membership consent. The token was only the
workspace-id list, so a no-join preview and a will-join preview for someone who
owns nothing both echoed `[]`. Neither guard could tell those apart: the forward
check compares sweep sets, and the reverse check required a non-empty disclosed
set. An invitee who left their other organization between preview and accept
would therefore be silently made a seat-consuming member after being told they
would stay external, and the mirror case could silently demote a promised join.

The accept body now also carries `disclosedWillJoinOrganization`, compared
against the resolved outcome before any write, so consent covers the membership
decision and not just the migration. Both accept surfaces send it.

This was widened by the previous commit: keying the membership notice on the
preview made the screen promise a join outcome the token never verified.

In-app accept errors lacked copy. `getInvitationErrorMessage` omitted
`external-requires-paid-plan`, `disclosure-outdated`, and
`workspace-not-found`, so those failures fell through to the generic "may have
expired" fallback. `disclosure-outdated` became newly reachable in-app the moment
that path started sending the token, so the gap arrived with the fix for it.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): compare the join disclosure against new-membership creation

The membership consent guard compared the disclosed outcome against
`shouldJoinOrganization`, which stays true for an invitee who already belongs to
the target organization — the invitation's intent is still internal. The join
preview reports no-join for exactly that case, because nothing changes for them.
Every such acceptance therefore failed `disclosure-outdated`, and the retry
re-rendered the same preview, so the invitation became permanently unacceptable.

The guard now compares against whether acceptance creates a NEW membership
(`shouldJoinOrganization && !alreadyMemberOfTargetOrganization`), which is what
the disclosure actually promises and what the preview reports. The
already-a-member predicate is hoisted and shared with the join block below so
the guard and the billing path cannot disagree about it.

Regression test asserts a pre-existing member accepts with a no-join disclosure;
it fails with `disclosure-outdated` against the previous comparison.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): tell an existing member their standing is unchanged

The join preview reported the same no-join shape for two different outcomes: an
external collaborator, and an invitee who already belongs to the organization
acceptance lands in. `buildMembershipNotice` rendered both as "you'll join as an
external collaborator ... everything you own stays yours", which is wrong for an
existing member — they stay an internal member and simply gain the granted
workspaces.

The preview now reports `alreadyMemberOfOrganization` for that case (a
membership in a DIFFERENCE organization is still the external path, since
acceptance downgrades), and the notice states that standing is unchanged. This is
the same conflation behind the accept loop fixed in 4eb7725f1a, now removed from
the shape itself rather than worked around per consumer.

Also re-verify the removal-impact disclosure at the moment of confirmation.
`isFetching` only holds the confirm button while a request is in flight, so an
identity-bound credential the member gained after the fetch settled would break
on removal without ever being disclosed. Confirm now refetches and, if the set
changed, keeps the dialog open on the refreshed warning instead of proceeding.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): serialize the join-preview reads, revert the removal refetch

Two corrections to this branch's own review fixes.

The pending-invitations list computed each row's join preview with Promise.all.
Every preview issues several queries, and the endpoint is hit whenever the
workspace switcher opens, so that held one pooled connection per pending
invitation for as long as the slowest one took. The loop is sequential now; the
list is a handful of rows, so the latency is not worth the pool pressure.

The removal-impact refetch on confirm is reverted. It changed behaviour — a
click could silently do nothing — and it did not actually close the window it
targeted: the credential set can still change between the refetch and the
independent DELETE, because the removal endpoint neither receives nor
revalidates the disclosed set. Closing that properly means passing the
disclosure to the endpoint and revalidating there, which is a feature rather
than a review fix, so the prior behaviour stands until it is done deliberately.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): disclose the seat on a personal-workspace join

Both accept surfaces scoped the membership notice on `organizationId` or the
preview's `organizationName`. A personal-workspace invite has neither until
acceptance runs — it creates the organization by converting the billed owner's
Pro to Team — so the seat and membership disclosure was suppressed for exactly
the case that creates the membership. They now also scope on the preview's
`willJoinOrganization`, which is the authoritative signal.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): make the join preview a discriminated outcome

The preview returned one no-join shape for five different results — external
intent, already a member, a membership in another organization, a dead-grant
rejection, and a billing rejection. Two accumulating booleans could not separate
them, and the accept screen rendered the external copy for all of them: it told
people whose acceptance would fail with `upgrade-required` or
`workspace-not-found` that they were getting workspace access without a seat.

It now reports one `outcome`: `will-join` (a seat is taken), `already-member`
(only workspace access changes), `external` (never a seat), or `blocked`
(acceptance fails, so nothing is promised). `blocked` renders no membership
notice — silence is accurate where the external claim was false. The accept
button is deliberately left enabled: those cases already fail closed with the
correct error, and choosing what to actively tell someone whose organization's
payment lapsed is a product decision, not a review fix.

This also fixes a live mis-attribution the previous commit's guard introduced.
The consent check ran before the gates that produce the real cause, so a blocked
invitation returned `disclosure-outdated` — and the retry re-rendered the same
preview, leaving the invitee looping with no explanation. The guard now sits
after the dead-grant gate, and a disclosed `blocked` skips the comparison so the
billing gate below can surface `upgrade-required` instead.

The accept body carries `disclosedOutcome` in place of the boolean; the
membership comparison is unchanged (`will-join` versus a new membership being
created), so no acceptance that previously succeeded now fails.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): let billing-disabled personal invites be accepted

The consent guard derived "a membership will be created" from
`shouldJoinOrganization`, which is still true at that point — it is only cleared
much later, after provisioning fails to yield a target organization. With billing
disabled and no organization on the workspace there is nothing to provision and
nothing to join, so the preview correctly reports `external` while the guard
computed `will-join`, rejecting every personal and grandfathered workspace invite
as `disclosure-outdated`. The retry rendered the same preview, so those invites
could not be accepted at all on billing-disabled deployments.

The predicate now mirrors the preview's own condition, so the two cannot drift.
Regression test asserts acceptance succeeds with billing off; it fails with
`disclosure-outdated` against the previous predicate.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): allow External with billing off, mark cross-org org invites blocked

The External paid-plan requirement is seat economics — an external collaborator
takes no seat, so somebody else must be paying for them. With billing disabled
there are no seats and no subscription rows at all, so every account resolves as
`free` and choosing External failed at send time and would have failed at accept.
Member and Admin still worked, so a self-hosted deployment had no way to grant
workspace-only access without an organization join and a workspace sweep. Both the
invite-time and accept-time gates now short-circuit when billing is off.

A route test asserted that rejection without setting `isBillingEnabled`, which
the shared mock defaults to false — it passed only because the gate ignored the
flag. It now opts in explicitly, since the rule it covers is billing-only.

Separately, the preview reported `external` for any invitee already in a
different organization, but acceptance only downgrades a workspace-kind invite
with live grants; an organization-kind invite hard-fails with
`already-in-organization`. Those now report `blocked`, so the screen stops
promising external access that acceptance can never grant. Legacy
organization-kind rows still exist and coalescing preserves that kind, so this is
reachable.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): require org admin to grant org Admin, and two disclosure gaps

Privilege escalation. `createWorkspaceInvitation` stamped organization role
`admin` whenever the caller passed `membership: 'admin'`, but authorization only
checked workspace admin access — and unifying the invite modals exposed the Admin
option to any workspace admin, where it had previously been reachable only from
organization settings. A workspace-scoped administrator could therefore invite
someone who joins as an organization Admin, gaining admin on every workspace the
organization owns plus member and billing management. The inviter must now already
hold organization owner/admin, checked server-side because the batch endpoint is
reachable without the modal, and the modal no longer offers Admin to anyone else.

The preview promised external access without mirroring acceptance's
`external-requires-paid-plan` gate, so a free invitee — one who cancelled Pro, or
left the organization that forced the external invite — was told they had
workspace access and then refused. It now mirrors that gate, including its
exemptions (billing on, organization-owned workspace, externality not imposed),
and reports `blocked`.

The modal's Enterprise seat check counted every non-External email as a seat. The
server does not: an existing organization member is granted access directly, and
an invitee already in another organization is forced external. The hard block
refused batches the API would have accepted, so it is advisory now — per-email
failures already come back with reasons.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(invitations): restore the invite admin gate, hedge an unknown outcome

Consolidating the modals dropped a permission check. The old workspace-header
modal derived `canInviteMembers` from `userPermissions.canAdmin` internally and
disabled its field and button; the shared modal takes `canInvite` as a prop that
defaults to true, and no call site passed it. Non-admins therefore saw a fully
enabled invite form and only learned otherwise when the server refused the send.
All three entry points now supply it: workspace admin for the header, the
existing `canManage` for the workspace Teammates page, and organization
owner/admin for organization settings.

When the join preview cannot be computed the outcome is unknown, and the callers
send no disclosure token — so acceptance runs without the consent guards. The
membership notice nevertheless asserted a seat-taking join from the sent intent,
which acceptance may resolve to external, already-a-member, or a failure. It is
conditional now ("If you're added to X as a member, that uses one of their
seats"), so the consequence is still disclosed without being claimed as settled.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 20:27:55 -07:00
Waleed fc25cfb3c8 fix(docs): fix Core Web Vitals regressions on docs.sim.ai (#5630)
* fix(docs): fix Core Web Vitals regressions on docs.sim.ai

Empirically measured under real trace-based (devtools) CPU/network
throttling against the live site: mobile Performance 59, LCP 9.2s
(TTFB 745ms + 8.4s element render delay).

- sidebar-components.tsx / [lang]/layout.tsx: the docs sidebar renders
  every page in the doc tree as a link at once. Next's default
  viewport-prefetch fired an RSC payload fetch for every one of them on
  initial load - dozens of concurrent requests competing with the page's
  own content for bandwidth. Wired fumadocs' documented `sidebar.prefetch`
  option through to the custom SidebarItem/SidebarFolder components (which
  were bypassing it entirely, using next/link directly with no prefetch
  prop) via the `useSidebar()` context hook.
- video.tsx: `autoPlay` forces browsers to fetch the full video file
  immediately on mount regardless of `preload`. Gated actual src loading
  behind an IntersectionObserver so a page with several of these doesn't
  pull down every video up front (5MB across 3 requests, in this case).
  Single shared component - fixes every doc page that embeds one.
- proxy.ts: the i18n middleware matcher excluded favicon/robots.txt/etc
  but not `icon.svg`, so every request for it got routed through i18n
  negotiation instead of served as a static file, 404ing in production.
- next.config.ts: enable productionBrowserSourceMaps - safe since this
  repo's source is already fully public, real debuggability benefit,
  zero performance cost.
- shiki 4.0.0 -> 4.3.1 (verified: syntax highlighting still renders
  correctly). Attempted a coordinated fumadocs-core/ui/mdx/openapi
  upgrade to latest; fumadocs-openapi's v11 factory function became
  client-only (breaking change beyond its declared peer deps, requiring
  a component-boundary restructure), so only the safe, verified,
  docs-exclusive bumps (fumadocs-core/ui/mdx, shiki) are included here -
  the openapi major bump needs its own dedicated migration PR.

Verified via a real production build (dummy env, all 3974 pages
including API reference render/build cleanly) and a clean (non-stale)
local server: Performance 59 -> 71 measured under real devtools
throttling, RSC prefetch requests 63 -> 11, video requests/bytes 3/5MB
-> 0. A pre-existing React hydration warning (#418) was found and
confirmed present on live production before any of these changes,
unrelated to this diff - documented, not blocking.

* fix(docs): fall back to eager video loading without IntersectionObserver

The lazy-load gate from the previous commit threw before isInView could
ever become true in environments lacking IntersectionObserver (older
browsers, some embedded webviews), leaving videos permanently
source-less instead of falling back to eager loading.

* chore(docs): drop non-TSDoc inline comments

Repo convention is TSDoc-only, no plain // comments.

* fix(docs): accessibility and SEO defects across the docs app

Audited with parallel subagents against the accessibility and SEO skill
checklists, each fix verified by reading the actual code (not assumed):

Accessibility:
- lightbox.tsx: focus was never captured/restored on close, and Tab
  escaped the modal to the page behind it (no focus trap on the single
  focusable element)
- heading.tsx: the per-heading copy-link icon only appeared on hover,
  invisible to keyboard-only navigation (added peer-focus-visible)
- navbar.tsx: active nav tab had no aria-current
- response-section.tsx: the status-code dropdown had no
  aria-haspopup/aria-expanded/role, and no Escape-to-close
- workflow-preview.tsx: same focus-trap gap as lightbox.tsx on the
  expanded-canvas modal

SEO:
- page.tsx: generateMetadata's hreflang/canonical URLs used a naive
  String.replace to strip the locale prefix, which also matched "/en"
  inside unrelated slugs (platform/enterprise, integrations/enrich,
  platform/self-hosting/environment-variables), corrupting those pages'
  canonical and alternate-language URLs. Replaced with a prefix-only strip.
- structured-data.tsx: the SoftwareApplication JSON-LD block compared
  url === baseUrl (no trailing slash) against the homepage's actual url
  (always has a trailing slash), so the condition was always false and
  this structured data never rendered anywhere, including the homepage.
- structured-data.tsx: "Mothership" in the indexed featureList violated
  the constitution's required language (the agent is "Sim", the surface
  is "Chat") - this ships in JSON-LD search engines parse.

* fix(docs): defer the Ask Sim chat widget's heavy deps until opened

The chat panel (useChat from @ai-sdk/react, Streamdown + its CSS) was
mounted unconditionally in the root layout on every single page, so
its full weight loaded and executed even though the widget starts
closed on every page view.

Traced via the LCP breakdown insight under real devtools CPU/network
throttling: the LCP text element (the intro paragraph) had a ~8s
element render delay despite a ~13ms TTFB, and bootup-time attributed
~4.3s of scripting time to a single chunk containing React/ReactDOM's
own runtime plus this widget's eagerly-bundled dependencies.

Split into a lightweight ask-ai.tsx (just the toggle button + open
state) and ask-ai-panel.tsx (the actual chat UI, useChat, Streamdown),
loaded via next/dynamic(..., { ssr: false }) only when the user opens
the widget. Verified: the panel's chunk now has zero network requests
on initial page load.

Measured (mobile, devtools throttling, /introduction):
- Performance: 69 -> 75
- LCP: 8.0s -> 6.4s
- TBT: 260ms -> 130ms

The remaining ~6.4s LCP delay traces to the same shared chunk, now
identified as core React/ReactDOM hydration cost for this page's
sidebar/TOC/breadcrumb tree rather than an isolated bug - a real,
larger initiative (hydration architecture, not a surgical fix),
documented here rather than rushed.

* fix(docs): preserve Ask Sim chat state across close/reopen

The panel split unmounted AskAIPanel entirely on close, discarding
useChat's message state - reopening always started an empty
conversation, unlike the original single-component layout where
useChat lived in a component that never unmounted.

Fixed by keeping the panel mounted (via a hasOpened flag that never
resets) once first opened, and having the panel itself return null
when closed rather than being conditionally removed from the tree by
its parent - hooks still run every render, so useChat's state persists
across visibility toggles. The dynamic import still only fires on the
first open, so the initial-load win is unchanged.

Verified via a real click-through (open, type, close, reopen): input
persists correctly, and the panel chunk still has zero network
requests on initial page load. Performance unchanged at 75.

* chore(docs): lint fixes (import order, formatting)

* fix(docs): fill the Ask Sim UI gap while the panel chunk loads

handleOpen set open=true synchronously, hiding the trigger button
before the dynamically imported panel had a chance to render anything
(next/dynamic renders null by default with no loading option) - on a
slow connection neither the button nor the panel was visible.

Added a loading fallback in the same fixed position so there's no gap
between the button disappearing and the real panel appearing.
2026-07-13 09:10:33 -07:00
Waleed fcf4e02930 fix(landing): repair Lighthouse-flagged CWV audits on production (#5605)
* fix(landing): repair Lighthouse-flagged CWV audits on production

Empirically verified against a live full Lighthouse run of www.sim.ai
(production, pre-fix) plus a local build of the exact deployed commit with
source maps temporarily enabled for root-causing. Distinguished genuinely
failing audits from passing ones already misread as broken.

- fetchPriority missing on every LCP hero image: `priority` generates a
  preload <link> but Next does not auto-add fetchpriority=high to it -
  confirmed via raw deployed HTML diff. Added explicit fetchPriority='high'
  to all 5 priority Image usages (hero, enterprise, blog/library post +
  index cards).
- valid-source-maps failing: production ships no source maps at all
  (productionBrowserSourceMaps defaults false). Enabled it - safe here since
  this repo's frontend is already fully open source, so no incremental
  exposure versus Next's default.
- image-delivery-insight (55.8KB wasted): feature-integrate-ui.png's `sizes`
  hint was a flat 1050px regardless of viewport, so mobile fetched the
  1920w variant for a ~423px real render. Replaced with a responsive sizes
  expression derived from the sibling backdrop image's own (already
  correct) hint, scaled by the callout's documented 125% overhang.
- cache-insight (best-fixable portion): _next/static/* filenames are
  content-hashed and immutable per deploy, but shared one cache rule with
  unhashed /public assets, capping both at 1-day max-age. Split into two
  rules - hashed assets now get 1-year immutable, unhashed assets keep the
  shorter revalidating TTL. Verified via a real build + server that both
  paths now return the correct distinct header.

Investigated and NOT changed (documented, not assumed):
- legacy-javascript-insight (14KB): traced via sourcemap to
  next/dist/build/polyfills/polyfill-module.js - Next's own built-in
  polyfill bundle, not our code or a dependency, and not exposed via any
  next.config.ts option. No browserslist misconfiguration on our end (none
  exists; Next already defaults to its modern target).
- forced-reflow-insight: even with source maps present locally, the
  dominant cost (335-417ms) stayed [unattributed] by Chrome's own profiler,
  and the small attributed slice was non-deterministic between our own
  chunk and a third-party script (HubSpot analytics) across runs - not a
  confident single root cause worth a targeted fix.
- render-blocking-insight / network-dependency-tree / bf-cache: bf-cache's
  actual failure reason is Cache-Control: no-store on the main document -
  the exact root cause already fixed on staging (PR #5522/#5528, the
  PublicEnvScript/unstable_noStore fix) but not yet promoted to main/prod.
  Resolves once that ships, not additional work here.

* fix(landing): convert mothership cover from PNG to JPEG (/blog LCP 6.6s -> 2.8s)

Ran a full Lighthouse sweep across every public page as requested. /blog
scored 73 (LCP 6.6s) while every other page scored 95+ - reproduced
consistently across 3 runs, not noise. Traced via lcp-breakdown-insight:
the LCP image (mothership/cover.png, 241KB even after the earlier palette
compression pass) took 6+ seconds to download on simulated mobile
throttling, well beyond what its size should cost.

PNG is a poor fit for this illustration's subtle gradients versus JPEG's
lossy compression. Verified empirically before converting: same 1920x1080
resolution, visually identical (spot-checked), 241KB -> 65KB (73% smaller).
No other cover in the content set uses PNG and benefits the same way
(checked copilot/cover.png, the only other PNG cover - already optimal at
64KB, converting it yielded no improvement, left unchanged).

Verified fix: /blog score 73->93, LCP 6.6s->2.8s, reproduced across 3 runs.

* fix(landing): correct mobile sizes tier, drop non-functional cache rule

- integrations-callout: account for FeatureCard's max-lg:grid-cols-1 mobile
  stack in the sizes hint, verified against Lighthouse's measured mobile
  render width.
- next.config: remove a custom _next/static cache-control rule that never
  actually fired (confirmed via header-marker test) - Next's own built-in
  default already applies the correct immutable 1yr cache to that path.

* fix(landing): correct sizes underestimate + fix dead .map header rule

- integrations-callout: derive sizes from the section's actual grid math
  (fixed 386px copy column, 40px gap, section gutters) instead of an
  approximated vw fraction. Verified against a static reproduction of the
  layout rendered at each Tailwind breakpoint - the old 110vw mobile tier
  underestimated real render width by ~3% right at the 1023px stack
  boundary, which could cause the browser to pick a too-small srcset
  candidate and upscale.
- next.config: the .map header rule's trailing `$` was read as a literal
  character by Next's path-to-regexp source matcher, not a regex anchor,
  so the rule never matched a real .map URL (confirmed via routes-manifest
  regex + a live header check). Removed the dead anchor and added a
  bounded Cache-Control so a future decision to stop shipping source maps
  isn't undermined by a 1yr immutable cache on already-fetched maps.

* fix(llms): serve well-formed llms.txt, remove Mothership + dead static files

Both the marketing site and docs site's llms.txt validator errors ("does
not appear to contain any links") traced to the same root cause: a static
public/llms.txt shadowed a better-written, already-existing dynamic
app/llms.txt route, and every "link" in the static files (and in the
docs app's auto-generated route) was bare `label: url` text, not Markdown
link syntax - so a strict Markdown-link parser found zero matches even
though URLs were visibly present.

- apps/sim: delete public/llms.txt (dead code, shadowing the properly
  Markdown-linked app/llms.txt route.ts, confirmed via production headers
  showing the static file was what actually served). Fix llms-full.txt's
  Links/Support/Legal sections to use [label](url) syntax, correct a
  stale "Next.js 15" reference, and replace "Mothership" with "Chat" per
  the constitution's language rules.
- apps/docs: same shadowing issue - delete the orphaned public/llms.txt
  (also still said "Mothership"). Fix the auto-generated per-page link
  list in app/llms.txt/route.ts to emit [title](url) instead of
  "title: url" for every documentation page.

* fix(llms): actually include the route.ts fixes from the prior commit

The prior commit (3b2d35c99) only staged the two deleted public/llms.txt
files - these two modified route.ts files (the Mothership/link-format
fixes they were meant to accompany) were left unstaged. No new changes,
just completing that commit's intent.
2026-07-11 17:50:00 -07:00
Waleed def2d5299a fix(docs-og-image): match reference cover template typography exactly (#5598)
* fix(docs-og-image): match reference cover template typography exactly

- swap Season Sans for Söhne Kräftig (500) — the reference cover's actual
  brand font, confirmed by letterform comparison; recovered from git
  history since it was removed as an unused static asset
- fix ink/background colors to exact reference hex values
- square caps + miter join on the corner arrow to match the reference's
  sharp corners instead of rounded ones
- recalibrate title font size, line height, and wrap width for the new
  font's metrics

* fix(docs-og-image): estimate CJK glyph width separately to avoid under-wrap

wrapTitleLines budgeted a flat 0.42em/char, tuned for Latin text. Docs
ships ja/zh locales — CJK glyphs render near-square (~1em), so a CJK
title could overflow the fixed-width title box uncaught. Sum per-char
em-width with a CJK-range check instead of counting characters.

* fix(docs-og-image): fall back to character-level wrap for oversized CJK words

wrapTitleLines only splits at spaces, so a space-free CJK run (common
for Chinese titles) still arrived as a single word wider than the
title box and rendered as one overflowing line. Falls back to
character-level chunking for any word that alone exceeds maxWidthEm.
2026-07-11 11:17:54 -07:00
Waleed f3582ed197 feat(branding): sim wordmark favicon/OG, docs footer parity, footer peel (#5587)
* feat(branding): sim wordmark favicon/OG, docs footer parity, footer peel

- replace apps/sim favicon and default OG image with the sim wordmark
  logo (OG image widened, logo kept at native size)
- swap the docs navbar logo to the icon-only mark (no wordmark text)
- add a scroll "peel" reveal effect to the landing footer using a
  sticky-positioned illustration, pure CSS, no scroll listeners
- port the same footer (link directory + peel effect) to the docs app
  so both apps are visually consistent; add Academy to Resources
- rebuild the docs OG image template to match the site's existing
  blog/library cover style (wordmark top-left, arrow top-right, title
  bottom-left), working around a Satori text-measurement bug that
  doubled the gap after certain words

* fix(docs): correct OG font, mobile logo, and footer stacking

- switch the docs OG image title font from Geist to the site's real
  brand font (Season Sans), instantiated as a static TTF weight since
  Satori can't parse WOFF2 or variable fonts; served from /static/
  so the i18n proxy's matcher (which excludes static but not fonts)
  doesn't intercept it
- fix DocsLayout's nav.title (fumadocs' own mobile menu slot) to show
  the wordmark instead of the icon mark
- add an isolated stacking context + higher z-index to both the docs
  and sim app footers so fumadocs' sticky z-20 sidebar can't paint
  over the footer content or the peel reveal

* fix(docs): match OG template exactly, fix gradient/origin bugs

- recalibrate the OG image to the reference cover template's actual
  measured values: 1200x675 canvas (was 630), ~26px margins (was
  56-64px), ink #525252 (was #3f3f3f), larger wordmark/arrow/title
  sizing — confirmed by direct pixel measurement of the reference
  cover.jpg, not estimation
- fix SimLogoIcon/SimLogoFull's SVG gradient ids to be unique via
  useId() instead of a fixed string, so multiple instances on one
  page don't collide (Greptile P2)
- fix SIM_SITE_URL to be a hardcoded sim.ai constant instead of
  deriving from NEXT_PUBLIC_APP_URL, which reflects wherever this
  deployment runs, not the fixed public marketing site (Greptile P1)

* fix(docs): route Jira footer link to the docs guide, not sim.ai

Every other integration in the footer's Integrations column links to
its own docs.sim.ai guide; Jira was the only one pointing at the
marketing site's landing page instead, despite docs having its own
/integrations/jira guide. Matches the established pattern.

* fix(docs): fix sidebar-divider grid regression, footer-peel path/positioning, OG sizing, and prune stray comments

- #nd-docs-layout::before divider now spans the full grid explicitly
  (grid-row/grid-column: 1 / -1) instead of being auto-placed into a
  real content cell, which was pushing page content down
- footer-peel.jpg moved under /static/landing/ (was 404ing behind the
  i18n proxy's non-static path matcher) and wrapped in a relative div
  so next/image's fill positioning is valid under the sticky container
- OG route: corrected title font sizes and char-width ratio so long
  titles wrap to 2 lines instead of 3, and resized the corner arrow to
  match the reference cover template's proportions
- swapped the icon-only desktop navbar logo back to the wordmark
- removed stray non-TSDoc comments, folded into TSDoc where the
  explanation was worth keeping

* fix(footer): remove sticky peel reveal, keep clean footer link directory

The peel's "reveal window" relied on position: sticky bottom-detaching
into a containing block whose extra height came from padding-bottom —
that combination doesn't reliably work in WebKit/Safari (sticky never
gets room to engage when the surplus height is padding rather than an
explicit height or content), so the peel stayed permanently covered by
the footer regardless of viewport size. Rather than carry that
unreliable technique further, removing it entirely from both apps and
reverting to the plain footer link directory.
2026-07-10 20:44:55 -07:00
Waleed 8e7e2db35e feat(docs): update favicon, fix icon contrast, add integration intros (#5581)
* feat(docs): update favicon, fix icon contrast, add integration intros

- replace docs favicon/icon assets with new sim logo
- fix light-tile icon contrast in BlockInfoCard so icons like Daytona
  no longer render invisible (white-on-white); matches sim toolbar's
  brightness-based contrast logic
- add missing MANUAL-CONTENT-START:intro sections to 30 integration
  docs pages that lacked context/links

* chore(docs): normalize spacing from generate-docs pass

Ran the docs generator to verify our new intro sections survive
regeneration cleanly. It reformats the blank line before "## Usage
Instructions" to match every other manually-annotated page.

* fix(context-dev): remove prefetch and simplified-brand tools

- remove context_dev_prefetch_domain, context_dev_prefetch_by_email,
  and context_dev_get_brand_simplified tools and their block operation
  entries; these aren't meant for general use
- regenerate docs to drop their sections from context_dev.mdx
2026-07-10 17:12:41 -07:00
Waleed 7545391cb3 feat(docs): render workflow previews with the shared editor renderer (#5277)
* chore(workflow-renderer): declare @sim/emcn dep + wire the package into docs

Adds the missing @sim/emcn peer/dev dependency to @sim/workflow-renderer (it imports @sim/emcn in every View but resolved only via workspace hoisting). Wires apps/docs to consume @sim/workflow-renderer (dependency, transpilePackages, Tailwind @source) and adds remark-breaks (pulled transitively via the barrel's NoteBlockView export) — mirroring the @sim/emcn integration. Foundation for migrating the docs workflow-preview fork onto the shared Views. Build resolves the package/@source/remark-breaks cleanly.

* feat(docs): render loop/parallel containers with the shared SubflowNodeView

Replaces the forked PreviewContainerNode with a thin DocsContainerNode that maps the static preview data to SubflowNodeView's read-only (isPreview) props — no stores or hooks. Adds the block size to the preview node data so the view can size itself, and corrects the parallel example's start-edge handle id to 'parallel-start-source' (the view derives the handle id from kind). Deletes preview-container-node.tsx. Container colors/icons are now owned by the shared view (loop=blue, parallel=yellow).

* feat(docs): render block nodes with the shared WorkflowBlockView

Replaces the forked PreviewBlockNode with a thin DocsBlockNode that maps the static preview data to WorkflowBlockView's props — store-free, builds the subblock rows (condition/router Context+routes/default + tools + error) via SubBlockRowView, strips branch-id prefixes so the view's regenerated handle ids match, remaps router->router_v2, and keeps the framer-motion dim/stagger wrapper. Promotes resolveIcon into block-icons.tsx, adds the --workflow-edge token to docs global.css, deletes preview-block-node.tsx. The canvas diagrams now render with the real editor's view.

* refactor(workflow-renderer): make editor-only WorkflowBlockView props optional

The child-deploy, schedule, and webhook badge props (and their callbacks) only matter in the editor. Mark them optional and optional-chain the three callbacks so read-only consumers (docs, academy) can omit the whole group instead of passing ~18 explicit off-values. The editor still passes them, so its behavior is byte-identical (verified: apps/sim type-check clean). DocsBlockNode drops the off-props.

* feat(docs): replace how-it-runs static diagrams with live WorkflowPreview

Swaps the four static PNGs on the how-it-runs page for live, app-styled WorkflowPreview diagrams (concurrency, combination, condition+router branching, error path). Adds the four example workflows and renders error-port edges red to match the editor. The English page only; the translated execution/basics pages keep the PNGs.

* refactor(workflow-renderer): the view owns condition/router/error rows

Both the editor container and the docs adapter hand-built the condition/router/error summary rows in an order that had to stay in lockstep with the view's absolute handle-offset math — a three-way coupling with nothing enforcing it. The view now renders those rows itself from the conditionRows/routerRows it already receives (plus a routerContextValue prop for the router's Context row), so row order and handle geometry live together in one place. Both containers pass only data and their non-branch rows.

Editor is byte-identical: getDisplayValue moves to where conditionRows/routerRows are built; the no-subBlock SubBlockRow path is already an exact SubBlockRowView(title, value) passthrough; the error row stays gated on shouldShowDefaultHandles. Verified apps/sim type-check clean. Docs now also renders the error row on condition/router blocks, which the real editor already did (shouldShowDefaultHandles is true for them) — an alignment fix.

* refactor(docs): drop the parallel --wp-* token layer for the app/emcn tokens

The workflow-preview ran a 25-token --wp-* mirror (22 were pure aliases of app tokens docs already defines) plus a .wp-scope wrapper class. Replaces every var(--wp-X) with its canonical app/emcn token (--wp-edge->--workflow-edge, --wp-highlight->--brand-secondary, badges->--badge-*, etc.), adds the one missing token (--divider), and deletes the .wp-scope blocks + class. Visually identical (aliases resolve to the same values); the preview now inherits the same design tokens as the shared views and the rest of the app instead of a hand-rolled parallel set.

* refactor(docs): adopt emcn Badge + dedup resolveIcon in workflow-preview

output-bundle's hand-rolled type badge (BADGE_COLORS + a styled span) becomes the emcn Badge (its green/blue/orange/purple/gray variants use the identical --badge-* tokens). resolveIcon, which had three copies, is now imported once from block-icons by output-bundle and block-inspector.

* refactor(docs): rebuild the preview inspector on emcn chip primitives

The lightbox inspector was a hand-rolled facsimile (raw divs + a CONTROL class string + inline dashed borders). It now composes from the same @sim/emcn primitives the live editor's sub-block controls wrap — ChipSelect/ChipInput/ChipTextarea(viewOnly)/ChipSwitch/ChipTag/FieldDivider/Label — so it reads as the real editor panel, fed example data (read-only, full opacity via readOnly/viewOnly, not greyed). Slider stays minimal (no emcn equivalent) but on app tokens. Props API and embedded/standalone modes unchanged.

* refactor(docs): render the block-reference hero through the shared View

Retires the hand-rolled BlockCard (a parallel reimplementation of WorkflowBlockView) and the BlockDisplaySpec data model. Each block hero is now a single-block PreviewWorkflow (block-display-workflows.ts) rendered through the same toReactFlowElements -> DocsBlockNode -> WorkflowBlockView pipeline as the diagrams, mounted in a minimal fitView ReactFlow (maxZoom 1.3, no canvas chrome). A single block can no longer drift from the canvas.

* fix(docs): define sim's type scale + align the preview inspector to the editor

Docs Tailwind v4 never defined sim's custom font sizes (text-small/caption/md/micro), so emcn components (Label, Badge, the shared views) fell back to inherited sizes — the inspector labels rendered huge. Adds the type scale to the docs @theme. Also aligns the inspector header to the real editor panel (surface-4 bar, size-[18px] rounded-sm icon, text-sm name) and removes the Connections section (and its now-dead prop/wiring).

* fix(docs): inspector shows the full field list + dragged positions persist

Inspector: shows the block type's full field list (from the reference data) with the example's values overlaid, so it reads like the editor panel instead of only the canvas summary rows. Drag: selecting another block no longer relayouts the canvas — node positions the viewer dragged are preserved across highlight/selection changes (only a different workflow relayouts).

* feat(docs): highlight <> references + env vars; hide Ask AI over the lightbox; respace blocks

Inspector text fields render the value with <...> block references and {{...}} environment variables highlighted in brand-secondary (a lean read-only port of the editor's formatDisplayText), in the canonical chip field chrome. The floating Ask AI widget is hidden while a preview lightbox is open. Plus the example-data respacing so the editor-faithful Error row no longer makes stacked blocks overlap.

* fix(docs): make per-type field templates match the real block registry

Audited every block type's field list (the source the inspector + block-reference heroes render) against apps/sim/blocks/blocks/*. Corrected drift to the registry's default-visible fields, titles, and order: agent gains Temperature; router gains Model; wait gains Async; schedule rewritten (default is Daily, not minutes); webhook_trigger expanded to its real default-visible set; human_in_the_loop notification title fixed. Provider-credential and advanced-mode fields stay hidden, matching the editor. Canvas diagrams keep their clean curated rows; the inspector now shows the full, real field list per the chosen clean-canvas/full-inspector split.

* improvement(docs): taller default preview height so respaced diagrams aren't shrunk

Bumps the default WorkflowPreview height 260->300 (the respaced, editor-faithful blocks are taller, so fitView was shrinking diagrams that relied on the default). The tall how-it-runs routing diagram gets 400.

* improvement(docs): zoomable inline preview + taller default + themed controls

The inline preview is now zoomable outside the lightbox: adds react-flow zoom/fit Controls (themed to the dark canvas chrome) and enables pinch-zoom, while keeping scroll-zoom off so the page still scrolls over the diagram. Pan-drag and click-block-to-inspect already worked. Default height 300->340.

* improvement(docs): click canvas to expand; click empty lightbox to deselect

Clicking the inline preview canvas opens the full lightbox; clicking empty space in the lightbox clears the selection, matching the real editor.

* improvement(docs): reveal inline zoom controls on hover only

The always-visible zoom controls felt heavy on the inline preview; they now fade in on hover (matching the expand button) and stay visible in the lightbox.

* improvement(docs): drop zoom controls on the inline preview

Inline preview keeps pinch-zoom, pan, drag, and click-to-expand; zoom buttons stay in the lightbox only.

* improvement(docs): remove zoom controls from the lightbox too

Both previews zoom via scroll/pinch and pan via drag; no on-canvas zoom buttons. Drops the Controls import and its theming CSS.

* improvement(docs): match the real canvas — flat background + editor edge geometry

Closes the last faithfulness gaps the audit found: removes the dot grid (the real editor hides its background — flat bg), aligns PreviewEdge to the editor's smoothstep math (borderRadius 8, offset 30) and 2px stroke (default + error edges), the selection ring to 1.75px, and minZoom to 0.1. Structural parity (blocks/handles/containers/colors/tokens) was already shared. Kept PreviewEdge rather than swapping to WorkflowEdgeView, which would clobber the docs-only highlight/dim/animate for no visual gain.

* improvement(docs): rebrand the docs assistant as 'Ask Sim', styled like the real chat input

Renames the floating assistant from 'Ask AI' to 'Ask Sim' (matching the platform's voice — you talk to Sim) and restyles the composer to mirror the home chat input: a rounded-2xl bordered field with the toolbar inside, and the same 28px circular send/stop button (the home's exact active/disabled colors + white/black arrow). Updates the lightbox hide-selector to the new label.

* improvement(docs): match Ask Sim message styling to the mothership chat

Aligns the user bubble (rounded-[16px] surface-5, text-base/primary, leading-23, max-w-85%) and the assistant markdown (text-base, 600 headings/strong, text-primary dashed-underline links, surface-5 code blocks) to the real mothership chat's user-message + chat-content treatment, instead of the prior generic text-sm rendering. The composer already mirrors the home user-input (rounded-2xl field + 28px circular send button).

* improvement(docs): compact single-row Ask Sim composer

The two-row layout left a tall dead gap (the docs widget has no toolbar buttons to fill the second row). The composer is now a single row — textarea with the circular send button inline — so it sits at the natural input height.

* fix(docs): pass the router Context value to the shared view

DocsBlockNode never set routerContextValue, so the view (which renders the router's Context row from that prop, not from rows) showed a blank Context even when the preview data authored a value like <start.input>. Extract it from the block's Context row and pass it through.

* fix(docs): don't apply a block-type field template that doesn't match the block

inspectorFieldsFor keyed the full field template purely off block.type, but some types are reused across roles (a table action block vs the table trigger, a webhook trigger vs the webhook action), so the wrong template was applied. Only use the template when the block's authored rows are actually a subset of it; otherwise fall back to the block's own rows.

* fix(docs): connect preview edges to subflow container handles

toReactFlowElements hardcoded targetHandle to 'target' and defaulted source handles to 'source', but Loop/Parallel containers (SubflowNodeView) expose a 'loop-end-source'/'parallel-end-source' output handle and a left input handle with no id. Edges into and out of containers therefore failed to connect. Resolve each edge end to the block's real handle based on whether it's a container.

* fix(docs): don't expand the inspector template for blocks with no rows

block.rows.every(...) is vacuously true for an empty rows array, so a block defined only by branches (e.g. a router in ROUTING_WORKFLOW) inherited the type template's invented field defaults. Require non-empty authored rows before applying the template.

* fix(docs): render blank branch/router-context values as '-' like the editor

The editor maps condition/router branch values and the router Context through getDisplayValue, which renders '-' for a blank value. DocsBlockNode mapped them to an empty string, so else branches and unset routes looked blank instead of matching the editor. Mirror getDisplayValue's empty-value handling.

* fix(docs): show '-' for blank inspector branch values, matching the canvas

inspectorFieldsFor passed raw branch.value into the lightbox branch fields, so an unset else route read blank in the inspector while DocsBlockNode (and the editor's getDisplayValue) render '-' on the canvas. Normalize the same way; drop the now-redundant placeholder.
2026-06-29 20:27:33 -07:00
Waleed f5f87de96d fix(emcn): repair app-wide crash and unstyled UI after package extraction (#5258)
Two regressions from moving emcn into @sim/emcn:

1. optimizePackageImports['@sim/emcn'] rewrote barrel imports to direct subpaths, duplicating the toast module so ToastProvider (layout) and useToast (workspace permissions provider) resolved different ToastContext objects — useToast threw 'must be used within <ToastProvider>' on every workspace route. Removed @sim/emcn from optimizePackageImports.

2. emcn's source left apps/sim's Tailwind content globs (and docs' v4 auto-content scope, which excludes node_modules), so utility classes used only inside emcn components stopped generating and components rendered unstyled. Added the package to apps/sim's Tailwind content and a @source to docs.
2026-06-28 23:52:47 -07:00
Waleed bcf6a804f9 improvement(emcn): extract design system into shared @sim/emcn package (#5257)
Moves apps/sim/components/emcn into a shared @sim/emcn package consumed directly by apps/sim and apps/docs. cn/keyboard/use-copy-to-clipboard move into the package; all imports become direct @sim/emcn (icons via @sim/emcn/icons, CSS via file path). ChipModal email validation is now prop-driven (quickValidateEmail stays in apps/sim, injected via validate). Docs drops its local chip/chip-dropdown/dropdown-menu mirrors and consumes @sim/emcn.
2026-06-28 22:50:48 -07:00
Waleed 954de0559b improvement(docs): align components with the platform design system (#5227)
* improvement(docs): align components with the platform design system

Bring the docs app's chrome in line with the main Sim design system,
validated against the canonical emcn source in apps/sim.

- ask-ai: fix undefined tokens (--text-base x6, --text-link) that broke
  the send-button fill and link/text colors; send button now matches the
  canonical primary fill (text-primary/text-inverse, dark:bg-white); use
  --shadow-medium and chip gap rhythm
- not-found: replace the hand-rolled brand pill with <ChipLink variant='brand'>
  and swap fumadocs tokens for platform tokens
- search-trigger: compose the exported chip chrome constants instead of
  re-spelling them (single source of truth)
- what-you-will-learn, video-chapters: fumadocs fd-* tokens -> platform tokens
- workflow-preview: add --wp-highlight token; route the #33b4ff highlight,
  #ef4444 error dots, and toggle/slider green through tokens
- video-placeholder: tokenize the status pill (bespoke illustration art
  intentionally left as-is)

dropdown-menu, faq, theme-toggle, and page-type-badge were deliberately
left at their canonical values (14px row icons, rounded-md badge) after
validation showed those match the platform, not the chip-pill, standard.

* improvement(docs): neutral primary chip for nav CTA + fix cluster spacing

Align the docs navbar with the main app, which reserves green for
accents/status and uses a neutral high-contrast CTA in nav.

- add a canonical `primary` chip variant (inverse fill: dark in light
  mode, white in dark mode), mirroring the emcn chip's primary action
- "Get started" and the 404 "Go home" now use variant='primary' instead
  of the green brand surface
- retire the now-unused `brand` chip variant (no parallel path left behind)
- fix navbar right-cluster spacing: gap-2 to match the landing navbar and
  drop the asymmetric ml-1 on the CTA
2026-06-26 17:24:09 -07:00
Will ChenandClaude Opus 4.8 6355c8e699 improvement(docs): Ask AI chat grounded in the docs vector store (#5172)
* docs: Ask AI chat grounded in the docs vector store

Adds an Ask AI chat to the docs site. A floating launcher opens a chat panel
backed by the Vercel AI SDK (OpenAI provider, OPENAI_API_KEY from the
environment). A searchDocs tool runs locale-scoped vector/keyword search over
the existing docs embeddings so answers cite real pages.

The public endpoint is hardened: per-request size/token/step caps, message
sanitization (no client-injected tool results or system prompts), origin
checks, and a per-IP rate limit. Non-English retrieval uses keyword search;
English vector search applies a similarity threshold.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: harden Ask AI retrieval + fix stale loading state

- searchDocs: wrap the keyword query in try/catch too, so each retrieval path
  (keyword, vector) is independent best-effort
- ask-ai: gate the loading ellipsis to the in-progress (last) message so older
  empty bubbles don't re-show it while a later request streams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:32:29 -07:00
Will ChenandClaude Opus 4.8 d20deedf99 improvement(docs): add Academy learning surface (#5213)
Adds the Academy section to the docs: video-first lessons (self-hosted MP4 on
Vercel Blob), organized into Workflows, Agents, Tables, Files, and Knowledge
Bases, each linking back to the reference docs. Lessons use a course layout
(hero video with chapter seek, "what you'll learn", block diagrams).

Docs only — no runtime or auth changes. The content may move to a separate CMS
or its own site (academy.sim.ai) later; the docs are a starting point.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:44:14 -07:00
Waleed aa39974d7b improvement(docs): align docs UI with the platform emcn design system (#4962)
* improvement(docs): align docs UI with the platform emcn design system

* improvement(docs): build language dropdown options in render scope
2026-06-10 19:40:31 -07:00
Will ChenandClaude Opus 4.8 bc55fc3b50 improvement(docs): builder-first IA reorganization of the English docs (#4896)
* docs: reorganize into topic/ontology IA with a builder-first rewrite

Restructure the English docs from internal product categories into a
topic-based information architecture, and rewrite the conceptual pages
to install a mental model first rather than enumerate features.

Structure & navigation
- Reorder the sidebar to follow how someone builds: Get Started ->
  Workflows -> Tables -> Files -> Knowledge Bases -> Logs ->
  Building agents -> Mothership -> Workspaces -> Platform -> Reference.
- Demote the generated blocks/tools/triggers catalogs to a Reference
  section at the bottom.
- Break up the monolithic execution/ folder into deployment/ and
  logs-debugging/; collapse connections/* and variables/* into single
  pages under workflows/.
- Rename capabilities/ to building-agents/; relabel the integration
  catalog as "Integrations". Remove deprecated copilot and form
  deployment. Redirects added in next.config.ts for every moved URL.

Conceptual rewrites
- Workflows core (index, how-it-runs, data-flow, connections,
  variables): one mental model, one running example, terser prose.
- New building-agents overview distinguishes an agent (a workflow you
  build) from an Agent block (one reasoning step), plus a "choosing
  what to use" guide.
- Concept-trim passes on Knowledge Base, Tables, Blocks, Triggers
  overviews; new task pages for KB, Tables, and Files.
- New code-verified Alerts page.

Infrastructure
- pageType frontmatter (concept/guide/reference) + badge render.
- WorkflowPreview / OutputBundle components to embed real, app-styled
  workflow diagrams (adds framer-motion + reactflow to apps/docs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(docs): spec-driven BlockPreview for block reference heroes

Replace the static screenshot hero on each block reference page with a
<BlockPreview> that renders the block exactly as the builder canvas shows
it — header icon, sub-block rows, and branch/error handles — from a
hand-authored display spec. Static and non-interactive (no ReactFlow), so
it can't be panned or dragged, and self-updating to edit.

- block-display-specs.ts: one editable spec per block (rows, branches, handles)
- block-preview.tsx: static scaled card renderer with decorative handles
- block-icons.tsx: brand glyphs for the core block types; icons.tsx adds WaitIcon
- 14 block + 3 trigger pages swapped from <Image> to <BlockPreview>

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs): correct stale navigation and removed-feature references

Audited the docs against the product changelog (GitHub releases / staging
git history) for content that misleads readers — features that moved, were
renamed, or removed — rather than cosmetic drift. Fixes:

- Skills: no longer a Settings tab. It was promoted to its own workspace
  page (#4354), so "Settings → Skills under the Tools section" sent readers
  to a tab that no longer exists. (skills/index.mdx)
- Env vars: the workspace tab is "Secrets", not "Environment Variables"
  (credentials→secrets rename, #4364). (quick-reference/index.mdx)
- Mothership FAQ pointed to "Settings → Credentials" for integration
  connections; integrations moved to their own page and there is no
  Credentials tab. (mothership/tasks.mdx)
- Vision block was retired (#4684); a tip still named it. Reworded to
  "an Agent using a vision-capable model". (files/passing-files.mdx)
- Getting-started FAQ told new users to "use the Copilot feature" to build
  in natural language — that surface is Mothership. (getting-started)
- Removed the dead "Mod+Y → Go to templates" shortcut; the templates
  gallery was removed (#4354). (keyboard-shortcuts)

Note: MCP "tools" (Settings → Tools, for consuming) and MCP "servers"
(Settings → System, for exposing) are distinct surfaces — both doc
references are correct and were intentionally left as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs): repair broken /docs-prefixed enterprise links

The enterprise overview linked to /docs/enterprise/* (access-control, sso,
whitelabeling, audit-logs, data-retention, data-drains), but the docs site
is served at root — those 6 links 404'd. Now root-relative /enterprise/*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs): refresh stale workflow-preview example blocks

The /workflows diagram blocks are hand-authored (separate from the
spec-driven BlockPreview heroes) and had drifted from the real UI:
- Agent color purple #6f3dfa -> green #33C482 (the var(--brand) rebrand)
- Model gpt-4o -> claude-sonnet-4-6 (current default)
- "Prompt" row -> "Messages" (the actual agent sub-block)
- Start color #34B5FF -> #2FB3FF (real starter bgColor)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs): align BlockPreview input/output handles to the card edge

The header (input/output) handles are positioned relative to the card and
used a -16px offset, so they floated 8px past the edge. Row/error handles
are -16px relative to a row that's already inset 8px by content padding, so
they sit correctly. Header handles are now -8px, so every handle sticks out
the same 8px and hugs the block edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite Agent reference to match the current block

The page documented the old UI (System/User Prompt, no Files or Skills, Memory
taught as a separate block — contradicting its own FAQ). Rewritten to the real
sub-blocks (Messages, Model, Files, Tools, Skills, Memory, Response Format) in
the builder voice of the workflows exemplars: oriented opening, agent vs
Agent-block callout, outputs table, a live WorkflowPreview example, FAQ kept and
corrected (tool control "Force", not "Required"). pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite API reference to match the current block

Tightened to the builder voice and the real config (URL, Method, Query Params,
Headers, Body + Advanced timeout/retries/backoff). Dropped the off-topic
"Dynamic URL Construction" / "Response Validation" sections (those are
Function-block techniques, not API config). Outputs table, FAQ kept. The example
is now a live WorkflowPreview (new API_FETCH_WORKFLOW in examples.ts, exported
via the barrel). pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite Condition reference to match the current block

Tightened to the builder voice: oriented opening (branches on boolean
expressions, no model call, vs Router), the real branch model (if / else if /
else, checked top to bottom), connection-tag expression examples, an error-path
callout, outputs table, and a live branching WorkflowPreview example
(CONDITION_ROUTE_WORKFLOW). FAQ kept. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): restore Best Practices + multi-example workflows on Condition

Recalibration: reference pages keep genuine substance (Best Practices, every
distinct example), cutting only redundancy and verbose register. Restores the
Best Practices section and turns the three use cases into three rendered
WorkflowPreview examples (route by priority, moderate content, branch
onboarding). Adds CONDITION_MODERATE_WORKFLOW and CONDITION_ONBOARD_WORKFLOW.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): restore Best Practices on Agent reference

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): restore Best Practices on API reference

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite Function reference to match the current block

Fixed the verbose register and dropped the duplicated outputs section + the
stale Python screenshot/TODO, while keeping the real substance: JS vs Python
(local vs E2B sandbox), the large-inputs sim.files/sim.values helpers, the
worked loyalty-score example, and Best Practices. The use cases are now two
rendered WorkflowPreview examples (reshape an API response, validate input).
Adds FUNCTION_RESHAPE_WORKFLOW and FUNCTION_VALIDATE_WORKFLOW. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite Router reference to match the current block

Cleaned the register, generalized the drifting model list, and folded the
Router-vs-Condition guidance into a callout. Kept the substance (routes as
output ports, NO_MATCH error path, all seven outputs, Best Practices, FAQ). The
three same-shape use cases collapse to one rendered triage WorkflowPreview
(ROUTER_TRIAGE_WORKFLOW), which the prose notes stands for the pattern.
pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): restore the classify and lead-qual examples on Router

I wrongly folded two distinct Router scenarios into a note. Restored all three
as their own rendered WorkflowPreview examples: triage a support ticket,
classify feedback (to child workflows), qualify a lead (sales vs self-serve).
Adds ROUTER_CLASSIFY_WORKFLOW and ROUTER_LEAD_WORKFLOW. (Also exports
RESPONSE_API_WORKFLOW for the next page.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite Response reference to match the current block

Cleaned the register and broadened "Variable References" to connection tags
(any output, not just workflow variables). Kept the substance: exit-point
semantics, Builder/Editor mode, status codes, headers, the parallel-branch
warning, Best Practices, FAQ. All three use cases are now rendered
WorkflowPreview examples (API endpoint, webhook ack, status-per-branch). Adds
RESPONSE_API/WEBHOOK/ERROR_WORKFLOW. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite Variables reference to match the current block

Cleaned the register, corrected the outputs (each assignment is also exposed as
<variables.name>, not "no outputs"), and kept the substance: assignments
reference earlier outputs and current values, global <variable.name> access,
Best Practices, FAQ. Two use cases now render as WorkflowPreview examples (count
retries, hold config). Adds VARIABLES_RETRY_WORKFLOW and VARIABLES_CONFIG_WORKFLOW.
pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite Wait reference to match the current block

Corrected a real staleness: the block now has an Async mode that suspends the
run for minutes/hours/days (not a hard 10-minute cap), plus a resumeAt output.
Documents Wait Amount / Unit / Async, the sync-vs-async distinction, all three
outputs, Best Practices, and updated FAQ. Two rendered WorkflowPreview examples
(space out API calls, delayed follow-up). Adds WAIT_RATELIMIT_WORKFLOW and
WAIT_FOLLOWUP_WORKFLOW. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): polish Credential reference (frontmatter, fold redundant tabs)

The page was already accurate to the block (Select/List operations, the outputs
tabs, the wiring steps). Light touch only: added description + pageType, made the
header consistent, and folded the two identical Gmail/Slack "how to wire" tabs
into one line. Examples stay as labeled flows + the List/ForEach screenshot,
since they use integration blocks and a Loop the WorkflowPreview can't render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): render the shared-credential example + icon fallback for integrations

Addressing the gap: WorkflowPreview block nodes now fall back to the integration
icon map, so diagrams can show Gmail/Drive/Slack/etc. with their real glyphs, not
just core blocks. Renders the Credential "share one account across blocks" example
as a WorkflowPreview (CREDENTIAL_SHARE_WORKFLOW). The multi-account and
List+ForEach examples stay as labeled flows + screenshot (the latter uses a Loop
container the preview can't render). Also exports EVALUATOR_GATE_WORKFLOW for the
next page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rewrite Evaluator reference to match the current block

Cleaned the register, generalized the drifting model list, and documented the
per-metric outputs (<evaluator.metricname>), which the page omitted. Kept the
substance (metrics with name/description/range, structured-output guarantee,
Best Practices, FAQ). The quality-gate example renders as a WorkflowPreview;
the same shape covers the parallel-variations and support-QC patterns, noted in
prose. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): render the Credential route-by-logic example too

The icon fallback unblocked it: the "route to a different account by logic"
example now renders as a WorkflowPreview (CREDENTIAL_ROUTE_WORKFLOW), a Condition
selecting a production vs staging credential. The List + ForEach example stays a
screenshot because it nests blocks in a Loop container the flat WorkflowPreview
can't represent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): render Guardrails examples + light accuracy pass

Kept the full substance (four validation types, PII entity/language detail,
the PII screenshot and video, outputs, Best Practices, FAQ). Light fixes:
frontmatter, and generalized the drifting model names (GPT-4o / Claude 3.7) to
"a strong reasoning model" with the current default. The three use cases now
render as WorkflowPreview examples (validate JSON, check grounding, block PII).
Adds GUARDRAILS_JSON/HALLUCINATION/PII_WORKFLOW. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): render Human-in-the-Loop examples + frontmatter

Kept all the substance (Display Data, Notification, Resume Form, the Approval
Methods and API Execute Behavior tabs, outputs, the paused/resume example).
Added frontmatter and rendered the use cases as WorkflowPreview examples
(approve before publish, two-stage approval, verify extracted data); Quality
Control folds into the approval note as the same approve-then-act shape. Adds
HITL_APPROVAL/MULTISTAGE/VALIDATE_WORKFLOW. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): render Webhook examples + frontmatter

The page was already accurate (Webhook URL/Payload/Signing Secret/Headers, the
automatic-headers table, HMAC details, outputs, POST-only callout, FAQ). Added
frontmatter and rendered the two use cases as WorkflowPreview examples (notify a
service, fire on a check). Adds WEBHOOK_NOTIFY_WORKFLOW and
WEBHOOK_TRIGGER_WORKFLOW. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): add example + pageType to Workflow block reference

The page was already accurate and well-structured (Configure It, outputs,
deployment-status badge, execution notes, FAQ). Added pageType: reference and a
rendered WorkflowPreview example showing a parent calling the child workflow
enrich-lead and reading its result. Adds WORKFLOW_CALL_WORKFLOW.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): container rendering for Loop/Parallel + render the Loop example

Adds subflow/container support to WorkflowPreview, modeled on the app's
subflow-node.tsx: a solid-bordered box with a header (icon + name), an internal
"Start" pill whose handle feeds the first nested block, and target/source
handles at the vertical center. PreviewBlock gains size/parentId; edges gain an
optional sourceHandle; nodes render nested children via React Flow parentNode.
Renders the Loop reference's ForEach example (LOOP_WORKFLOW) and keeps the four
loop-type sections + inside/outside referencing + caps. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): fix the Loop container's Start-pill connector

The Start pill -> first-block edge wasn't rendering: it was a React Flow
parent->child edge (unreliable), and the opaque container body hid it. Nested
blocks now render as absolute-positioned top-level nodes (container below at
zIndex 0, blocks above at zIndex 1), so the connector is an ordinary edge, and
the container body is see-through so it's visible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): render the Parallel example + frontmatter (last core block)

Reuses the container rendering for the Parallel reference. Kept all substance
(count/collection types, inside/outside referencing, batch size of 20, instance
isolation, the Parallel-vs-Loop table, Best Practices, FAQ). Added frontmatter
and a rendered container WorkflowPreview (PARALLEL_WORKFLOW: distribute tasks,
call concurrently, aggregate <parallel.results>); the two use cases stay as
labeled flows. Adds PARALLEL_WORKFLOW. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): prose glow-up for Guardrails to match the agent/condition voice

Rewrote the listy register (**Use Cases:** / **How It Works:** / **Configuration:**
scaffolding, "Use this when you need to..." filler) into the plain builder voice,
matching the depth of the Agent/Condition/Function rewrites. Kept every
validation type, option, range, the full PII entity/region list, the screenshot
and video, the outputs table, the rendered examples, Best Practices, and FAQ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): prose glow-up for Loop to match the agent/condition voice

Rewrote into the plain builder voice and cut the filler: dropped the "Use this
when you need to..." lines and the ASCII "Example: Iteration 1, 2, 3" pseudo-code,
and folded the duplicated Inputs/Outputs tabs into Configuration + Referencing
sections. Kept all four loop types with their screenshots, the inside/outside
reference rules, the 1,000-iteration cap, sequential-vs-parallel guidance, the
rendered example, and FAQ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): prose glow-up for Parallel to match the agent/condition voice

Same treatment as Loop: plain builder voice, dropped the ASCII pseudo-code and
the duplicated Inputs/Outputs tabs, folded the verbose Advanced Features into
tight Configuration + Referencing sections. Kept both types with screenshots,
the batch-size-of-20 cap, instance isolation, large-result indexing, the
Parallel-vs-Loop table, the rendered example, and FAQ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): prose glow-up for Human-in-the-Loop

Tightened the register: folded the pause sentence into the intro, made the
section headers consistent (Configuration, Outputs), converted the bold-list
Block Outputs into a table, condensed the Notification channel bullets to a
line, and renamed the second "Example" so it no longer collides with the
rendered Examples. Kept all the substance — Display Data / Notification / Resume
Form, the Approval Methods and API Execute Behavior tabs, the portal video, and FAQ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): re-enrich Loop prose (fuller, explanatory — not terse)

The first glow-up overcorrected into terse fragments. Restored proper
docs-quality prose at the Agent/Condition level: each loop type now explains
what it does, when to use it, and the relevant reference; Configuration,
Referencing, nesting, and Best Practices give context and the "why," not just
bullets. Same substance, readable depth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): re-balance Parallel prose to the Agent/Condition register

Calibrated to the level signed off on elsewhere: each concept explained in a
couple of clear sentences with a concrete detail — informative, not terse, not
padded. Kept both types with screenshots, batch-size cap, isolation, large-result
indexing, the Parallel-vs-Loop table, the rendered example, and FAQ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): restore the Notification channel detail on HITL

The glow-up over-compressed: it flattened the five notification channels (each
with what they do) into one sentence. Restored them as a list in plain voice —
tightening register shouldn't drop genuinely useful reference detail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): builder-voice polish on the Credential intro

Light touch only — the page was already well-structured and explanatory, so just
led the intro with what the block does (and bolded the name) to match the other
references. No content changed elsewhere.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(triggers): rewrite Start trigger in the builder voice

Tightened the register, swapped the <code>&lt;&gt;</code> noise for backticks,
added pageType + an outputs table, and kept all substance: Input Format types,
chat-only outputs (input/conversationId/files), the editor/API/chat tabs, and
best practices.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(triggers): rewrite Schedule trigger in the builder voice

Plain voice and clean markdown (dropped the raw <ul>/<div> lists). Kept all
substance: simple intervals, cron examples, timezone, deploy-tied activation,
the 100-failure auto-disable, and FAQ. Added pageType.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(triggers): refocus Webhook trigger on the generic (native) trigger

Rewrote in the builder voice and separated out the integration content: the
page now documents the generic Webhook trigger (URL, Input Format, auth, custom
response, outputs, dedup/rate-limit/deploy/no-auto-disable). The "trigger mode
for service blocks" section is reduced to a short pointer + the demo video, and
the long supported-services catalog and vague use-case bullets are dropped in
favor of the Triggers index. Fixed the title (Webhook) and added pageType.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(triggers): builder-voice glow-up for RSS

Light pass: added pageType + description, tightened the intro, and presented the
output fields as an <rss.*> outputs table. Kept the polling config, use cases,
the published-after-save callout, and the FAQ (poll cadence, dedup, 25-item cap,
auto-disable, Atom support).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(triggers): rewrite Table trigger off the auto-generated card

Replaced the BlockInfoCard/'provides 1 trigger' auto-gen format with a real
builder-voice page: a spec-driven BlockPreview hero (added a 'table' spec),
plain-language Configuration (table, event type, watch columns, include
headers), and a full <table.*> outputs table. pageType: reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(triggers): frame the index around native triggers + separate the catalog

Reframed "generic" as native (no connected account) and promoted RSS and Table
into the native set alongside Start/Schedule/Webhook — cards, comparison table,
and integration paragraph updated to match. In the sidebar, grouped the five
native triggers under a "Native triggers" header and divided the ~44 service
triggers under "Integration triggers" (nav-only — no files moved, URLs stable;
the move to integrations/ is a later, separate change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: promote Core Blocks + Core Triggers into the Workflows area

Restructured the Documentation sidebar (meta-only — no files moved, URLs stable):
after Deployment, the 16 core block pages now live under a "Core Blocks" section
and the 5 native trigger pages under "Core Triggers", instead of buried in the
bottom Reference catalog. Removed the now-redundant blocks tree from Reference,
and retitled the Reference triggers tree "Integration triggers" so it holds just
the service catalog (the native ones are promoted up top).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: merge block/trigger overviews into the Workflows overview; Core accordions

Restructured the sidebar and overview hub (meta + content only, no integration
files moved):

- Folded the /blocks and /triggers overview pages into /workflows: the overview
  now carries the core-block catalog (do work / direct flow / shape run), the
  Integrations-and-triggers families framing, the native + integration trigger
  framing, the trigger comparison, manual-run priority, and email-polling groups.
  Deleted blocks/index.mdx and triggers/index.mdx as redundant.
- Promoted the 16 core blocks into a "Core Blocks" folder accordion and the
  native triggers into a "Core Triggers" accordion, both under Workflows after
  Deployment. Integration triggers stay inside Core Triggers under a labeled
  divider, temporary until they move to integrations/<service> (tabs) later.
- Repointed every /blocks and /triggers index link to the /workflows#blocks and
  /workflows#triggers sections.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: split integration triggers into their own Reference accordion

Core Triggers is now the 5 native triggers only. Moved the 43 service triggers
out of triggers/ into a new integration-triggers/ folder, surfaced as an
"Integration triggers" accordion under Reference (an accordion must be its own
folder in Fumadocs). In Workflows, Core Triggers now sits before Core Blocks.
URLs: /triggers/<service> -> /integration-triggers/<service> (native /triggers/*
unchanged); the integrations/<service> tabbed-page migration remains the later step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): trim the overview back to an introduction

It had drifted from a concept intro into a catalog. Kept the spine (the four
parts with their previews, how-it-runs, workflows-in-context) and compressed the
merged-in material: the full 16-block enumeration becomes a three-kind taxonomy
with examples, the trigger section a short native/integration framing. Cut the
anxious in-between — manual-run trigger priority, the niche email-polling-groups
feature (belongs on the Gmail/Outlook trigger pages), the redundant block-def
line, the Start-outputs callout half, the connections video, and the catalog-y
FAQ items. Dropped the unused Video import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: relocate email-polling + trigger-priority out of the overview

Moved the two bits cut from the workflows overview to durable, generator-safe
homes: email-polling groups -> the Integrations (connecting accounts) page;
manual-run trigger priority -> the Start trigger page. Also added 'table' to the
generator's HANDWRITTEN_TRIGGER_DOCS / SKIP_TRIGGER_PROVIDERS so the hand-written
Table trigger page is no longer overwritten by generate-docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(docs-gen): emit per-service integration pages (actions + Trigger section)

Rewrites the generator to output one page per service under integrations/
instead of split tools/ + triggers/. Block pass writes the service's actions;
trigger pass appends a '## Triggers' section (badged) to the same page, or writes
a standalone page for trigger-only services. Meta is written after both passes;
hand-written integration pages are preserved; docsUrl repointed to /integrations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(docs): unify tools + triggers into per-service /integrations pages

Encodes the ontology "everything is a block; some blocks are triggers." The
generator now emits one page per service under integrations/ — the service's
Actions plus, when it has one, a Triggers section on the same page — replacing
the split tools/<service> + triggers/<service>. No "Tools" terminology.

- generate-docs.ts: output to integrations/, merge trigger sections into each
  service page (standalone for trigger-only services), Actions heading, table
  block now generated, docsUrl -> /integrations, hand-written pages preserved.
- Nuked tools/ (213) and the interim integration-triggers/ (43); moved the
  custom-tools guide to building-agents/; knowledge/memory/file/table links and
  meta repointed to /integrations.
- Sidebar: integrations catalog now under Reference (was tools); removed the
  Workspaces integrations entry and the integration-triggers tree.
- block-icons: wait uses lucide Clock (the generated icons.tsx no longer carries
  a hand-added WaitIcon). Landing integrations data regenerated.

No redirects (fresh start). Native Core Blocks/Core Triggers unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs): recover the hand-written manual-content intros on integration pages

The tools->integrations relocation generated fresh pages, so the generator never
saw the old tools/<service>.mdx to preserve its {/* MANUAL-CONTENT */} sections —
198 curated intros (AgentMail, etc.) were dropped. Reseeded each integrations
page from the pre-move tools page in git, re-ran the generator (which now merges
the manual intro into the new Actions/Triggers format), and repointed /tools/
links inside the recovered prose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(scripts): rewrite the generator README for the integrations model

Brings scripts/README.md current: integration pages are derived from the
apps/sim block/tool/trigger registry (canonical-sources map), the golden rule
not to hand-edit generated pages, the MANUAL-CONTENT escape hatch, which pages
are hand-written/skipped, and the icons.tsx-overwrite gotcha.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: regenerate integration docs from staging-synced apps/sim

After merging staging, regenerated so the integration pages reflect current
source: correct block colors/configs (e.g. Gmail #FFFFFF), the new integrations
(sendblue, millionverifier, neverbounce, zerobounce), and staging's icon set.
Pages for integrations staging hid are removed; manual-content intros preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs-gen): don't let stale-doc cleanup delete hand-written integration pages

Staging's cleanupStaleToolDocs removes any integrations/*.mdx that isn't a visible
tools block — it only guarded `index`, so it deleted the hand-written
google/atlassian service-account pages. Now guards all HANDWRITTEN_INTEGRATION_DOCS.
Restored the two pages, and repointed /integrations/file links to /files (staging
hides the file block, so it has no integration page).

Note: staging recategorized a2a/mysql/postgresql tools -> 'blocks' (and hid file),
so they correctly drop out of the integration catalog and are currently
undocumented — an IA decision to revisit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs-gen): stop cleanup/writer filter mismatch from eating manual content

Comprehensive-review findings, all generator-consistency bugs:
- cleanup used staging's isIntegrationBlock while the writer kept the legacy
  filter, so integrations/{knowledge,memory,table}.mdx were deleted then
  regenerated without their manual intros every run. Both now honor a shared
  NATIVE_RESOURCE_BLOCK_TYPES set; intros reseeded.
- Trigger-only services (imap, circleback; category 'triggers') were likewise
  deleted each run; the canonical set now includes visible trigger-category
  blocks, the standalone writer preserves manual content, and their intros are
  reseeded.
- Mapped jsm -> jira_service_management, so JSM triggers merge into the JSM
  integration page instead of an orphan jsm.mdx (removed).
- Repointed lingering bare /tools links to /integrations; added missing
  pageType to integrations/index and building-agents/custom-tools.
Double-regen is now churn-free (idempotent) with all manual content intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs): recover staging's enriched Table doc + never drop manual content

The merge resolution deleted staging's relocated blocks/table.mdx, which carried
substantial enrichment our integrations/table.mdx (reseeded from the older
tools/ version) lacked: Creating Tables (column types/constraints), Filter
Operators, Combining Filters, Sort Specification, Built-in Columns, Limits, and
Notes. Recomposed integrations/table.mdx with that content — Creating Tables
inside the intro manual section, the reference tail in a notes manual section.

Generator fix uncovered en route: a manual section whose insertion anchor is
missing in the generated markdown (e.g. notes with no "## Notes" heading) was
silently dropped on regen. Unplaceable sections now append at the end instead —
manual content is never lost. Verified idempotent across double regeneration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workspaces): de-philosophize the fundamentals prose

Rewrote in the plain register of the workflows overview: 'draws the boundary
for access' / 'Nothing crosses the boundary' / 'follow the same edge' become
direct statements (only members can access it; a workflow in one workspace
cannot read a table in another). '## The boundary' is now '## Access and
isolation'. All substance kept: every resource type, permission levels,
personal/organization/grandfathered kinds, deployments callout, VISUAL markers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(docs): restore #blocks and #triggers anchors on the workflows overview

The editorial trim renamed '## Blocks' -> '## Kinds of blocks' and
'## Triggers' -> '## How a workflow starts', silently breaking the ten
/workflows#blocks and /workflows#triggers anchor links pointed there when the
old index pages were folded in. Pinned the original ids with explicit heading
anchors. Found by the comparative prose review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: restore the genuinely useful reference bits the rewrite dropped

From the comparative prose review, restored in guidance register (no spec
dumps): temperature tiers on Agent (low/middle/high with ranges), loop/parallel
iteration references in the variables syntax-at-a-glance table, and a short
"Test it" section on the Webhook trigger (curl + check the run in Logs). The
fourth flagged loss (tag-resolver mechanics on connections) turned out to be
already covered — name normalization, case-sensitive paths, missing-output
behavior, and value formatting are all on the page; only the internal resolver
precedence chain was dropped, deliberately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): rework the Agent intro — encyclopedia register

Replaced the flat opening with a denser, factual one (no metaphor): what the
block does, and its centrality stated as fact — 'Most workflows are built
around one or more Agent blocks.' The agent-vs-Agent-block disambiguation moves
from an info callout into a second paragraph on the block's role in building
agents. Dropped the now-unused Callout import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(integrations): add the HubSpot setup guide for the Marketplace listing

Addresses HubSpot Marketplace review item A1: a public, HubSpot-specific setup
guide following their template — what the app does, install + connect through
the current flow (sidebar Integrations page -> HubSpot -> Add to Sim -> connect
dialog -> HubSpot OAuth), with real screenshots of each step and a placeholder
for the scope-approval shot; configure in a workflow (one-click skills/templates
+ the HubSpot block + trigger mode), use, disconnect (with data consequences),
uninstall from the HubSpot side, troubleshooting. Capability wording is by CRM
object rather than scope enumeration, so it stays accurate after the A2 scope
trim. Lives at /integrations/hubspot-setup, guarded as hand-written,
cross-linked from the HubSpot reference page's intro.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(integrations): rewrite the Integrations guide for the sidebar flow

Integrations moved out of Settings to a top-level sidebar page. Rewrote the
guide to the current journey: the Integrations page (Connected/Featured/search),
service pages with one-click skills and templates, + Add to Sim -> connect
dialog (display name + permissions) -> provider OAuth. Replaced the four
Settings-era screenshots with current captures (connect dialog illustrated via
HubSpot); block-side screenshots (account selector, manual credential ID) kept;
one VISUAL marker for the connection detail view pending a fresh capture.
Members/roles, credential-ID, reconnect/disconnect, email polling, and FAQ
substance unchanged apart from navigation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: move Building agents directly after Workflows in the sidebar

The agent-building journey follows straight from workflows (blocks, triggers,
deployment) rather than after the tour of every resource type. Tables/Files/
Knowledge Bases/Logs now follow it. Meta-only reorder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: fill the visual slots coverable by existing components

Six VISUAL markers filled with no new captures needed:
- building-agents overview: rendered the minimal lead-scoring agent
  (Start -> Agent with tool chips -> Response, Agent highlighted) as a
  WorkflowPreview (BUILD_AGENT_WORKFLOW)
- files guide: the read -> summarize -> write chain as a WorkflowPreview
  (FILE_SUMMARY_WORKFLOW)
- tables guide: the query -> classify -> write-back roundtrip as a
  WorkflowPreview (TABLE_ROUNDTRIP_WORKFLOW)
- choosing guide: the six-kind comparison grid as a markdown table
- knowledgebase guide: the Knowledge block's output as an OutputBundle
- workspace fundamentals: removed a duplicate nesting-diagram marker

42 -> 39 VISUAL markers remaining (screenshots + designed diagrams).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(components): run-inspector OutputBundle + lightbox with block inspector

Two visual-component upgrades, both mirroring the real app:

- OutputBundle is now a miniature of the run inspector: a Logs column (block
  rows with icon chips and durations, source selected) beside the Output panel's
  typed tree — keys with the app's type-badge semantics (string green, number
  blue, object gray, array purple, boolean orange), chevrons, indent guides,
  primitive values. Styling lifted from the terminal's structured-output.
  Dropped the "Read one value by name" footer (the prose teaches the tag).
  The three usages (data-flow, tables, knowledgebase) get real typed trees;
  data-flow's stale purple/gpt-4o example corrected en route.

- WorkflowPreview gains a lightbox + read-only block inspector: clicking a
  block (or the expand control) opens a 92vw/86vh overlay with zoom and pan,
  and a right-hand inspector panel showing the selected block's full
  configuration — canvas rows truncate, the inspector doesn't. Fields render as
  app-style controls (dropdown/textarea/input by heuristic) with dashed
  dividers, tool chips, and a Connections footer computed from the edges.
  Selection rings without dimming (new selectedBlock option in workflow-data).
  Esc/backdrop closes; body scroll locks while open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: regenerate after staging merge — AppConfig joins integrations/

Staging's new AWS AppConfig integration (#4928) generated its docs into the old
tools/ layout; re-homed to integrations/appconfig.mdx (Actions heading, meta
entry) via the generator. tools/ stays deleted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: redirect the retired tools/ and trigger URLs to integrations/

Revises the earlier fresh-start call: /tools/* are ~200 live, indexed URLs
referenced by deployed app versions' docsLink fields and marketplace listings,
so dropping them cold would 404 from the live product. next.config now 308s:
- /tools -> /integrations, /tools/:slug -> /integrations/:slug
  (custom-tools -> building-agents/custom-tools first)
- old /triggers/<service> -> /integrations/<service>, enumerated so the native
  trigger pages keep resolving; provider-slug mappings for jsm and the
  hyphenated Google/Microsoft slugs
- /blocks and /triggers index URLs -> the workflows overview anchors
Verified every class + native passthroughs against the dev server. Spec updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(getting-started): rewrite — current UI, cut the post-tutorial padding

The last old-guard page. Accuracy: Agent config now uses Messages (System/User
message) instead of the removed System Prompt/User Prompt fields, the default
model instead of GPT-4o, the banned 'no-code' phrasing is gone, the deploy card
points at /deployment, and frontmatter gets description + pageType. Weight: cut
the 'What You've Built' checklist, the 'Key Concepts You Learned' re-teach
section, the duplicate 'Resources' links, the Start-block hand-holding, and ten
dead icon imports; tightened every step preamble. 203 -> 113 lines with the
full 5-step tutorial, videos, and FAQ intact. (Videos still show the old UI
until the re-recording pass.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: de-fluff the Tier-1 heavy pages (logging, mcp, passing-files, permissions)

From the exhaustive fluff audit, keeping all substance:
- logging: merged the duplicated Console/Logs-page structure, snapshot concept
  stated once instead of three times, cut the generic Best Practices, trivial
  tab walkthrough condensed. Frontmatter added.
- mcp: intro + "What is MCP?" generic bullets folded into two sentences, cut
  the Common Use Cases catalog and the verify-your-config Troubleshooting
  checklists, merged the twice-stated Refresh behavior, security kept as one
  real warning.
- passing-files: marketing opener replaced with a factual lead, fixed the stale
  retired-Vision-block reference (now Agent with a vision model), dropped the
  FAQ item that restated the block catalog verbatim.
- permissions: heading-restating intro replaced with the two-layer model, cut
  the three "Perfect for: stakeholders..." persona lines and the generic Best
  Practices section, dropped the FAQ restating the limits table.
- connectors: audit over-flagged it — the categorized support matrix, API-key
  table, and config examples are genuine reference; frontmatter only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: tier-2 fluff trims (costs, enterprise, mailer, skills)

Conservative sweep from the audit, unambiguous cuts only: the costs CYA opener
and formula restatement, the enterprise marketing intro (now a functional
summary), mailer's restated convenience line and chat-upload comparison, and
skills' third restatement of progressive disclosure. Audit flags screened out
as misfires: mothership/tasks (immediate-vs-scheduled are two facts, not a
duplicate), self-hosting telemetry (real sizing data), and the recently
approved credential/HITL/workflow-block/trigger pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(skills): update to the Skills tab on the Integrations page + document import

Skills moved again — they now live on the Integrations page's Skills tab in the
workspace sidebar (the doc said "Open the Skills page"). Updated the create flow
(+ Add to Sim -> Add Skill dialog) with fresh screenshots of the tab and both
dialog tabs, and documented the previously-missing Import flow: upload a .md
with YAML frontmatter or a .zip containing SKILL.md, fetch from a GitHub URL, or
paste SKILL.md content (verified against the import route/component; name 64 /
description 1024 limits verified against the contract). Noted the curated
skills suggested on integration pages, cross-linked the Skills tab from the
Integrations guide, and refreshed the location FAQ. Mechanics (progressive
disclosure, load_skill, agent-block attachment) unchanged and still accurate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(building-agents): render the lead-scorer running example on choosing

The page narrated its running example through six sections without ever showing
it. Authored LEAD_SCORER_WORKFLOW (Start -> Enrich workflow-as-tool -> Function
reshape -> Agent with Search/Send Email/CRM tool chips -> Google Sheets append)
and rendered it after the intro, with highlightBlock re-renders in the three
sections that map to a node (deterministic block -> the Sheets append, agent
tool -> the Agent, workflow-as-tool -> Enrich) — the same pattern as the
workflows overview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(tables): rewrite workflow columns around the real lead-scoring example

Rebuilt the page on the ai_startup_customers screenshots instead of captioning
them onto the old hypothetical: one running example throughout — Company Domain
fills domain, Company Info reads it into employee_count/description, Lead Score
Enrichment writes lead_score/priority/score_reasoning. Every section now
describes the actual UI: the grid with group headers, per-row run buttons, and
the 21-running toolbar; the Configure workflow panel (picker, column inputs,
output selection, Auto-run, Run after); the Company Info input/output mapping;
Not found cells explained where the screenshot shows them; the cascade section
describes the example itself. All placeholder markers on the page resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: regenerate after staging merge — Slack trigger update + file block re-visible

Staging's mothership v0.2 (#4923) expanded the Slack trigger payload
(interactivity, slash commands: event_type, command, action_id/value/actions,
response_url, trigger_id, callback_id, ...) — regenerated so it lands on the
unified integrations/slack page; the old-layout triggers/slack.mdx from
staging's generator was dropped in the merge. The file block is visible again
upstream, so integrations/file.mdx is back in the catalog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(tables): playbook prose pass on workflow columns + restore File block links

Workflow columns, against the docs-writing playbook: killed the banned
'Term — desc' bullets in the Configure list (term + verb form), restored the one
universal analog (spreadsheet macro), fixed the clipped 'On,/Off,' fragments,
replaced an invented <start.companyDomain> tag with the verified description,
and thinned em-dashes to four page-wide with no clustering. Also repointed
[File] block mentions back to /integrations/file now that the page exists again
(FileV5 is visible upstream); the Files-store links stay on /files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(tables): per-row execution inspection on workflow columns

Two new captures: the cell menu (View execution, Re-run cell, row actions) and
the Log Details trace for a single row's run. New 'Inspecting a row's run'
section ties cell values to real, traceable runs; corrected the re-run guidance
now that Re-run cell exists (the page previously said Run all rows was the only
way to retry).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): drop the confusing 'order by hand' sentence

'You never set the order by hand' read wrong (wiring connections is setting it
by hand), and the replacement was over-explanation. The first sentence already
carries it: Sim works out the order from the connections.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): fix the over-claim about independent blocks

'Two blocks that don't depend on each other run at the same time' is wrong —
independent blocks at different depths run at different times. Concurrency
follows from readiness, not independence: blocks whose dependencies have all
finished run together. Reworded to say that, tied to the image's two agents.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): accuracy audit of how-it-runs against the executor

Verified every claim on the page against apps/sim/executor. One claim was
materially false: "a failed block stops its own path but leaves independent
paths running" — in the engine, an unhandled block failure sets the error flag
and stops scheduling entirely (in-flight blocks finish, nothing new starts);
only a connected error port routes the failure and keeps the run alive. Now
says that. Two imprecisions tightened: a join waits for every feeder *that is
going to run* (deactivated-branch feeders don't hold it up, per the
edge-manager cascade), and Loop also repeats while a condition holds. Confirmed
accurate: per-block readiness scheduling (readyQueue + race, not layers),
branch-skip cascade and empty tags, the 25-hop call-chain cap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(logs): real captures on the overview + prose matched to the UI

The logs-debugging overview had six visual placeholders and no visuals. Three
real captures placed: the workspace Logs page as the hero (rows with status,
credits, trigger, duration), Log Details' Trace tab at the blocks section (the
CRM sync run's spans, with a one-line read of where the time went), and the
editor's live run console at the input/output section. Prose corrected to what
the UI shows: cost is in credits, failed runs are badged Error (dropped the
five-state enum the list doesn't display), and the Trace tab is named. The
row-anatomy marker is covered by the hero; the two designed-diagram markers
(debug-loop flowchart, failed-vs-success comparison) remain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): one reference syntax, named sources — untangle variables vs connection tags

An exhaustive sweep of "connection tag" found the docs asserting both that a
workflow variable is a connection tag (response.mdx used it as the umbrella for
all angle-bracket references) and that it isn't (variables.mdx). Ruled the
narrow definition canonical — a connection tag reads a block's output; the name
follows the connection — and restructured around the real model:

- variables.mdx: new "One syntax, named sources" section states that everything
  in angle brackets is one mechanism whose first segment names the source, with
  the load-bearing fact stated plainly: `variable` is literal, a connection tag
  starts with the block's own name. The syntax table drops the redundant
  dot-notation row, gets one row per source, and is ordered by resolution
  precedence with the order explained beneath it (absorbing the old Name
  conflicts section). The credentials pointer folds into the env-var section;
  trimmed the "never appears in outputs" overclaim.
- response.mdx: no longer calls a workflow variable a connection tag.
- connections.mdx: the owner page closes the loop — same syntax also reads
  variables and loop/parallel context; a connection tag is the block-output case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): verify the reference model against the resolver; fix one imprecision

Checked every claim in the new 'One syntax, named sources' section against
apps/sim/executor/variables: resolver chain order is Loop -> Parallel ->
WorkflowVariables -> Env -> Block (matches the table); 'variable'/'loop'/
'parallel' are literal prefixes (REFERENCE.PREFIX); block names normalize via
toLowerCase + strip spaces; an unmatched reference is genuinely left in place
(resolver returns undefined -> the replacer emits the raw match). One claim
tightened: {{KEY}} is a different syntax and can never collide with
angle-bracket references, so the precedence sentence now scopes collisions to
the angle-bracket sources with a concrete example (a block named 'variable').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(building-agents): workflow-as-tool is agent-decided, not the Workflow block

The choosing page defined workflow-as-tool as the Workflow block (path-decided),
contradicting its own name and the comparison table's premise. Verified against
the product: workflow_executor is an agent tool — you pick the workflow in the
Agent block's tool list, the model decides when to call it and supplies the
inputMapping (user-or-llm), inputs arrive at the child's Start trigger.

Rewritten agent-first: the section defines it as a workflow handed to an agent
as one callable tool, the lead scorer gains a Deep Enrich workflow tool chip on
the agent (diagram updated), and the deterministic Workflow block becomes the
explicit contrast in a callout — same child workflow, the difference is who
decides, mirroring the block/agent-tool contrast. Table row corrected to
"The agent"; the summary paragraph follows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: theme-aware previews + enrichments vs workflow groups split

Light-mode support for every preview component (WorkflowPreview canvas, nodes,
containers, edges, lightbox, BlockPreview, OutputBundle, BlockInspector): a
wp-scope token block in the docs global stylesheet whose values mirror the OG
repository's globals.css in both modes (surfaces, borders, --workflow-edge,
text tiers, the --badge-* type-badge palette). Every hardcoded hex swapped to a
--wp-* var; brand colors, selection blue, and error red stay literal.

tables/workflow-columns: separated the two group kinds per the contract's
workflowGroupType enum ('manual' | 'enrichment'). New "Two kinds of groups"
section opens with the + New column menu capture (Enrichments above the types,
Workflow below); Enrichments documented from the code-defined registry (company
domain, company info, email verification, phone number, work email) including
the provider-cascade behavior that produces Not found cells; the Company Info
panel capture is now correctly labeled as an enrichment config; workflow groups
keep the Configure workflow panel. Shared machinery generalized under "How
groups run"; the cascade section names which stage is which kind; the two
portrait screenshots render smaller.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(tables): don't enumerate the enrichment catalog; don't assert a group's kind

Two corrections on workflow columns: the prose no longer lists the enrichment
catalog (growable, not procedurally tracked — it now describes the category and
points at the Enrichments panel; the provider-cascade/Not-found explanation
stays, it's behavior not catalog), and the page no longer asserts which kind
the example's Company Domain / Company Info groups are (Company Info may be a
user-built workflow, not the built-in). The input/output bindings capture moved
to "How groups run" as the kind-agnostic illustration; only Lead Score — whose
panel shows the workflow picker — is named as a workflow group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(components): per-branch source handles — conditions and routers finally branch

WorkflowPreview's node only ever had one header source handle, so every
condition/router example fanned both edges out of a single point and never
showed the if/else rows the real canvas (and the BlockPreview hero specs)
render. PreviewBlock now supports `branches` (each rendered as a row with its
own right-edge source handle, id `branch-<id>`) and `showError` (red error
handle), mirroring the executor's per-branch condition-true/condition-false and
router-<route> handle model. A block with branches emits from them, not the
header.

Every affected example rewired (13 workflows): the three condition examples,
status-per-branch, credential routing, and the webhook-trigger check route
their edges through branch-if/branch-else with the expression on the If row and
an explicit else; the three router examples list their actual routes as branch
rows (Sales/Support/Billing, Product/Bug report, Enterprise/Self-serve); the
terminal gates (variables retry, evaluator gate, the three guardrails gates)
show dangling if/else branch rows like the canvas does.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(components): inspector shows branch rows

Moving condition expressions from rows into branches emptied the lightbox
inspector for condition/router blocks — it only mapped rows to fields. Branches
now map too: each branch renders as a field (If with its expression as code,
else as an empty control, router routes by name).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(components): branch handle ids match the app's workflow representation

Verified against the source after the branch-handles work: the canvas emits
condition-${cond.id} handles per condition row (workflow-block.tsx) and Router
V2 uses router-${routeId} port handles, and edges carry those ids as
sourceHandle — the docs' invented branch- prefix was a gratuitous divergence
that the planned fromWorkflowState() adapter would have had to translate. The
node now uses the authored branch id as the handle id directly, and every
example authors ids in the app's own scheme (condition-if/condition-else,
router-<route>), so example edges now match real workflow edges verbatim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: agent skills mint /integrations/ docs links and describe the new output

The add-integration/add-block/validate-integration skills — what Claude Code
follows when integrations land on staging — still taught the old layout:
docsLink templates pointing at docs.sim.ai/tools/{service} and 'generates
tools/{service}.mdx'. Updated so that once this PR merges, the instructions on
staging produce the new way by themselves: /integrations/ docsLinks, the
per-service page description, and the don't-hand-edit/manual-content pointer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): execution semantics, not simultaneity

The concurrency section drifted into 'run at the same time' framing across two
accuracy passes — but the semantics are non-blocking execution: a block starts
the moment its dependencies finish and waits on nothing else. Section retitled
'Blocks run as soon as they can', the rule stated in two plain sentences, the
duplicated pre-image example narration gone (the post-image caption carries it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): errors are execution semantics — own section on how-it-runs

Failure behavior was buried inside 'Watching a run' (the live-UI section). Now
a first-class 'When a block fails' section in the execution story: an error
fails the run (in-flight blocks finish, nothing new starts) unless the block's
error port is connected, in which case the run follows the error path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: data-driven additions from the platform-metrics read

Three targeted edits from the sim-internals analysis, each carrying an inline
{/* why */} provenance comment so future editorial passes know the data behind
it:

- workflows/how-it-runs gains "How long a run can take" — run timeouts are the
  only hard-error class provable at scale (2,415 five-minute timeouts in 14
  days); limits verified in lib/core/execution-limits/types.ts (5 min free /
  50 min paid sync, 90 min async, env-overridable).
- getting-started gains an "if the run doesn't go green" callout at the Test
  step — the largest funnel drop is created-workflow -> first-successful-run
  (92% -> 49%), and this is the stall point.
- function/api Best Practices: the existing error-path bullets get a guard
  comment (<1% of deployed workflows connect an error port — under-adopted,
  not under-needed) instead of duplicate bullets.
- visuals manifest: capture priority reordered by integration adoption
  (Sheets, Gmail, Telegram, WhatsApp, ...).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: regenerate after staging merge (integration validation batch + Gong tools)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: rename Building agents -> Agents; URLs match the settled IA

The section's pages now live where the sidebar says they do:
building-agents/ -> agents/, and the stray top-level /mcp and /skills fold in
as /agents/mcp and /agents/skills (they were always part of the agents story —
the URLs predated the IA settling). Sidebar section header is now "Agents",
link labels updated, and every old URL 308s: /building-agents(/*) -> /agents(/*),
/mcp, /skills, plus the existing capabilities/ and tools/custom-tools redirect
destinations retargeted. Verified: all five new pages render and every old-URL
class redirects correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(workflows): connections gets its video, an FAQ, and accurate output examples

The reorg dropped two things from the old tags page that belonged on the
connections reference: the connections.mp4 walkthrough (restored after the
intro) and the FAQ (rebuilt in the robust JSX form — resolver order, name
normalization, env-var syntax pointer, didn't-run behavior, array indexing,
Function-block formatting; answers aligned with the since-verified resolver
facts, including unmatched-references-left-in-place).

Editorial/accuracy pass on the output-shape tabs while in there: stale gpt-4o
and gpt-5 examples now claude-sonnet-4-6, the Agent tokens shape corrected to
the verified { input, output, total } (the page contradicted blocks/agent), and
the dubious cost: [] line dropped — the example now matches the real run
inspector.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: regenerate after staging merge — sim trigger, enrichment + logs blocks, re-shown DB integrations

Staging's #4941 added the Sim workspace-event trigger (hand-written page adopted
into Core Triggers), the Enrichment and Logs blocks (category 'blocks' — added
to NATIVE_RESOURCE_BLOCK_TYPES so they live in the integrations catalog like
table/knowledge/memory), and re-categorized mysql/postgresql/sftp/smtp/ssh back
to visible tools (their pages return to the catalog). Generator sets merged as
the union of both sides (sim in HANDWRITTEN_TRIGGER_DOCS + SKIP_TRIGGER_PROVIDERS,
enrichment in the icon allowlist).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: regenerate after staging merge (CodePipeline); suppress sim trigger from catalog

The native Sim workspace-event trigger is documented at triggers/sim — the
block writer no longer emits an integrations page for it (skip + canonical-set
exclusion). CodePipeline (#4945) lands in the catalog in the Actions format.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(blocks): cross-link the Memory block from the Agent memory section

Final loss audit found the old page's pointer from built-in agent memory to the
standalone Memory block had been dropped; one line restores it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: URLs now mirror the sidebar — sections own their pages

Every page lives at a path matching its meta.json section, done now while none
of these URLs are publicly live (the last free window before merge):

- Workflows owns its accordions: /blocks/* -> /workflows/blocks/*,
  /triggers/{start,schedule,webhook,rss,table,sim} -> /workflows/triggers/*,
  /deployment/* -> /workflows/deployment/*
- Mothership owns Mailer: /mailer -> /mothership/mailer
- Workspaces & Access folds into Platform, sequenced concept-first with the
  reference tail last: /platform/{workspaces,organization,permissions,
  credentials,costs}, then platform/self-hosting/*, platform/enterprise/*
  (from /workspaces/fundamentals+organization, /permissions/roles-and-
  permissions, /credentials, /costs, /self-hosting/*, /enterprise/*)

All internal links swept (0 broken in a full-tree resolver sweep), root
meta.json repointed, and every previously-live URL 308s to its new home —
including retargeted destinations of existing redirects so chains stay
single-hop (verified: /execution/chat reaches /workflows/deployment/chat in
one hop), and the native-trigger rule ordered after the enumerated
integration-trigger redirects so /triggers/gmail still reaches
/integrations/gmail. Production build passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: untrack .plans/ (local agent planning files)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(preview): tool chips use the EMCN ChipTag chrome

The canvas previews' tool chips were ad-hoc (5px radius, header surface, plain
border). The app's canonical chip chrome is the ChipTag family: 20px tall,
rounded-md, px-1, gap-1.5, --surface-5 light / --surface-4 dark with an inset
--border-1 ring and --text-body label. Mirrored those values into --wp-chip-*
tokens (both modes) and restyled the chip; the integration's brand-color icon
square stays, sized to the chip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(blocks): one-time shift of all docsLinks to the new docs URLs

Block definitions are the patterns coding agents copy from, so redirects alone
leave new blocks minting dead conventions. Every docs.sim.ai link in apps/sim
now points at the final URL scheme: /tools/<slug> -> /integrations/<slug>
(433 links), /blocks/<core> -> /workflows/blocks/<core> (knowledge/enrichment/
logs -> /integrations/*), native /triggers/* -> /workflows/triggers/*,
/mcp -> /agents/mcp, /self-hosting + /enterprise -> /platform/*, plus the
llms.txt listings and the blocks.test.ts assertions.

Verified every rewritten target against the docs tree: all resolve except ten
hidden blocks (vision, spotify, thinking, tts...) and a2a whose links were
already dead pre-reorg — no regressions introduced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: ignore .plans/ (local agent planning files)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(files): align every File-block claim with the shipped file_v5 block

Accuracy audit against apps/sim/blocks/blocks/file.ts (FileV5Block, the visible
block) and tools/file/*:

- The block has FIVE operations, not four — Get Content was missing entirely.
- Read outputs file objects only; the page claimed it also returned extracted
  text. Text comes from Get Content (contents, per file) or Fetch
  (combinedContent) — table, prose, and the Fetch callout corrected.
- Functions CAN read files: sim.files.readText/readBase64 exist in the sandbox
  (isolated-vm-worker.cjs), so "doesn't reach into workspace storage" is gone;
  the section now teaches Get Content text or sim.files on the file object.
- Workspace file IDs are wf_<shortId> (workspace-file-manager.ts:511), not f_.
- Stale "such as Claude or GPT-4o" vision parenthetical dropped.
- "File block reference" card pointed at /files (the section overview); now
  /integrations/file.
- FILE_SUMMARY example agent consumed <file.combinedContent>, which Read never
  produces — now binds the file object to the Files input.
- passing-files.mdx: combinedContent scoped to Fetch, contents documented.

Verified intact: Write's numeric-suffix collision behavior, Fetch's auth
headers, Append-by-name, and the file-object shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: keyboard-shortcuts audited against the command registry; cut legacy workspace detail

Every binding verified against commands-utils.ts (the global registry),
workflow.tsx, and table-grid.tsx. Three fixes: tables Mod+A (select all rows)
doesn't exist — the real bindings are Shift+Space (select row, was misworded
as a toggle) and the undocumented Mod+Space (select column); the global
Mod+Shift+A row conflated two commands — add-agent (Mod+Shift+A) and
add-workflow (Mod+Shift+P) are separate. All 29 other documented shortcuts
confirmed accurate, including tables clipboard (native copy/cut/paste events)
and Mod+Y redo (tables only — correctly absent from the workflow editor
section).

Also drops the grandfathered_shared workspace paragraph — internal billing
taxonomy, not something a reader can act on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: apply Theodore's accuracy feedback

- getting-started: workflow creation is the + button next to Workflows in the
  sidebar (no "New Workflow" button exists); Exa/Linkup no longer need
  user-supplied API keys on hosted Sim (apiKey is hideWhenHosted in the Exa
  block) — step and FAQ updated.
- workflows overview: chat and API are entry points of the Start trigger, not
  separate triggers — the "swap in a chat/API trigger" sentence now matches
  triggers/start's own model.
- variables: names cannot contain periods — the resolver reads everything
  after the first dot as a path into the value (executor/variables/resolvers/
  workflow.ts splits on dots) — constraint now stated where name normalization
  is taught.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: Python sandbox package list (verified) + agent/agents cross-linking

Function block: the Python callout's "common packages like matplotlib" becomes
the actual package list, grouped by use. Sources verified 2026-06-10 and cited
in an inline provenance comment: E2B's code-interpreter template requirements
(the base Sim's mothership-shell template builds from) plus Sim's three pip
additions (awscli/yq/csvkit, per the copilot repo's template.ts via
sim-internals). Versions omitted so the list doesn't rot on routine bumps.

Agent surfaces deduplicated by direction: blocks/agent's Tools section now
links custom tools and MCP and points at the Agents concept page for tool
sourcing; agents/index drops its duplicated Auto/Force/None enumeration in
favor of the block reference, which owns config mechanics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 18:39:57 -07:00
0075ab9cf6 improvement(platform): remove tour, simplify sidebar/header, drop loading skeletons (#4354)
* improvement(platform): workspace UI/UX overhaul + integrations catalog

Rework the workspace around the AI-workspace model: a Mothership home, a
top-level Skills route, connected-credential and integration-detail pages,
and a polished sidebar/settings surface. Replace the notifications store
with a unified toast system (provider-level dismiss/pause, countdown ring).

Integrations & catalog:
- Add a BlockMeta layer (tags + catalog templates) scoped to catalog-visible
  integrations; every catalog integration carries >=7 grounded templates.
- Rework the taxonomy: each block declares category tools|blocks|triggers.
  3rd-party services are 'tools'; first-party primitives (postgres, mysql,
  knowledge, file, search, stt/tts, image/video generators, thinking, etc.)
  are 'blocks'. Versioned blocks follow the upgrade paradigm (old hidden,
  latest in toolbar/docs).
- Generate integrations.json + tool docs canonically from block configs.

Architecture & cleanup:
- Consolidate block data extraction behind a single latest-version strategy
  (getCanonicalBlocksByCategory; version-consistent getBlockMeta).
- Unify version-suffix handling in @sim/utils/string (stripVersionSuffix /
  isVersionedType, with tests); registry, generate-docs, tools/utils, and
  integrations all route through it.
- Repair latent broken barrels, remove dead code, fix BlockMeta-related type
  errors and 5 broken docs links.

Behavior-preserving for block execution and the toolbar's tool/block listing.

* refactor(platform): remove forms, templates, and creators features

Remove three standalone features and their supporting code:
- Forms: form-deployment pages, API routes, execution path, and docs.
- Templates: the template gallery (landing + workspace) and template APIs.
- Creators: creator-profile routes and contracts.

Add a super-user permissions module (lib/permissions/super-user) and an
organizations API contract; update the audit/db/testing packages, billing,
and the session/theme providers accordingly.

* test(workflows): update archiveWorkflow update count after forms removal

The forms feature was removed, dropping the form-table update from
archiveWorkflow. Update the stale assertion from 8 to 7 tx.update calls.

* upgrade

* improvement(knowledge): polish tag filter dropdowns (#4816)

* improvement(logs): object storage backed tracespans (#4787)

* improvement(logs): obj storage backed tracespans

* fix storage write context

* fix tests

* address comments

* address comments

* chore(db): remove migration 0219 to regenerate after staging merge

Drops the 0219_robust_shard SQL, its snapshot, and the journal entry so the
trace-spans/cost schema migration can be regenerated on top of the latest
staging migration chain (avoids a number collision with staging's migrations).

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(billing): accurate per-member usage via shared ledger helper

Per-member/per-user usage in the org-member routes now adds the usage_log
ledger to the currentPeriodCost baseline (which is no longer incremented),
via a shared getOrgMemberLedgerByUser helper to avoid repeating the
subscription→period→ledger lookup across the admin and member-facing routes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* regen migrations

* update migration

* address comments

* more code cleanup

* incorrect type cast

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(providers): harden OpenAI-compatible providers + add tests (#4796)

* improvement(providers): harden OpenAI-compatible providers + add tests

* fix(vllm): let tool-loop errors propagate instead of returning silent partial success

* fix(litellm): force tool_choice 'none' on final structured-output call

The deferred final call used tool_choice 'auto', so the model could emit
another tool_calls round instead of the structured answer, leaving content
stale. Use 'none' (matching vLLM/Fireworks) on both the streaming and
non-streaming final calls so the model must return the structured response.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(providers/ollama): drop tools from post-tool streaming call

Ollama ignores tool_choice (not in its supported fields), so vLLM/Fireworks'
tool_choice:'none' guard is a no-op here. Omit tools from the final streaming
payload instead so the summarization turn can't emit dropped tool calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(litellm): spread payload into deferred final call so reasoning_effort carries over

The non-streaming deferred finalPayload hand-picked fields and dropped
reasoning_effort (and any future payload field), diverging from the streaming
path which spreads ...payload. Spread payload here too for consistency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(providers/ollama): restore enrichment TSDoc block

Keeps parity with sibling Chat Completions providers (cerebras/mistral/xai).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(fireworks): restore TSDoc on utils helpers

Restore the TSDoc blocks on supportsNativeStructuredOutputs,
createReadableStreamFromOpenAIStream, and checkForForcedToolUsage —
TSDoc is the codebase documentation standard and should not have been
stripped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(litellm): remove inline rationale comments (codebase uses TSDoc)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(providers/ollama): drop orphaned enrichment TSDoc

The block documented a function that now lives in trace-enrichment.ts, so it
documents nothing in this file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* chore(copilot): deprecate mcp server (#4797)

* chore(copilot): deprecate mcp

* update error codes

* deprecate copilot api v1 route

* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza (#4777)

* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza

Add hosted-key support across all credit-consuming Findymail, Prospeo, and Wiza operations so Sim provides the key when a workspace has not brought its own. Register the three BYOK providers, consolidate Wiza's two-step reveal into a single polling wiza_individual_reveal op, and hide the API key field on hosted Sim for hosted operations.

* fix(integrations): harden Wiza reveal polling, soften enrichment getCost guards

Address Greptile + Cursor Bugbot review on #4777: return explicit failures from the Wiza individual_reveal poller instead of throwing (thrown errors were swallowed into a false queued success), short-circuit when the initial reveal is already terminal, tolerate transient 5xx/429 during polling, and return 0 (not throw) from Findymail getCost when the contacts/employees array is absent.

* chore(integrations): biome formatting after wiza merge resolution

* fix(wiza): type isTerminalReveal param structurally for next build typecheck

* feat(enrichments): add Findymail, Prospeo, Wiza to work-email waterfall

* feat(enrichments): add Wiza + Prospeo phone reveal to phone-number waterfall

* feat(enrichments): opportunistic identifiers + LinkedIn URL input across work-email & phone cascades

* fix(tables): reduce column header chevron size and fix sidebar shadow bleed (#4800)

* feat(slack): add install + privacy section to integration landing page (#4799)

* feat(slack): add install + privacy section to integration landing page

Adds a hand-authored, slug-keyed landing-content module (separate from the generated integrations.json so it survives regeneration) and renders an install walkthrough + privacy-policy link on integration pages when present. Also refreshes generated docs (data-enrichment entry, icon mappings, tool mdx).

* fix(landing): render privacy section independently, align CTA analytics label

* docs(landing): clarify the Slack install button is behind sign-in

* refactor(landing): bake integration landing content into generated json via docs-gen

Moves landing content (install walkthrough + privacy) out of a render-time augment and into the generation pipeline: generate-docs reads the pure-data content map and writes landingContent into integrations.json, so the page reads a single source (integration.landingContent). Canonical types live in integrations/data/types.ts.

* improvement(enrichments): align enrichments sidebar with design system (#4801)

* improvement(enrichments): align enrichments sidebar with design system

* fix(enrichments): consistent close button pattern and fix url link hover

* fix(misc): upgrade path change for new better-auth version, billing issue for workflow block agent usage (#4803)

* fix(misc): upgrade path change for new better-auth version, double-billing for workflow block agent usage

* fail loudly if stripe sub id missing

* fix(copilot): seq migration (#4804)

* chore(db): drop redundant idx_webhook_on_workflow_id_block_id index (#4809)

Removed because (workflow_id, block_id) is a left-prefix of idx_webhook_on_workflow_id_block_id_updated_at_desc, which fully covers it. The dropped index was non-unique and enforced no constraint.

* perf(copilot): read chat transcripts from copilot_messages (R+1 cutover) (#4808)

* perf(copilot): read chat transcripts from copilot_messages, not JSONB

Flip user-facing chat reads from the legacy copilot_chats.messages JSONB
array (5.7GB, 99% TOAST) to the normalized copilot_messages table via a
new loadCopilotChatMessages helper ordered by seq NULLS LAST, created_at,
id — the verified canonical order. Both chat-detail getters
(getAccessibleCopilotChat, getAccessibleCopilotChatWithMessages) now drop
the messages column from their metadata select (no more whole-array
detoast on every load) and assemble the transcript from the table after
authorization. This cascades to the copilot + mothership GET endpoints
and to resolveOrCreateChat's conversationHistory (the LLM payload).

The normalize/effective-transcript pipeline is source-agnostic
(copilot_messages.content == a JSONB array element), so transcripts are
byte-identical. Dual-write and the JSONB column stay in place as the
internal-logic source and fallback; removing JSONB writes is a later step.

Prod integrity verified before cutover: 0 messages missing, 0 NULL-seq,
0 dup keys/seq, 0 orphans, order-parity vs JSONB = 0 mismatches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(copilot): cover auth-deny on a found row skips the messages query

Address PR review: exercise the `if (!authorized) return null` contract —
when the chat row exists but authorization fails, the getter returns null
and never issues the copilot_messages read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(tables): right-align run/stop in embedded toolbar; workflow cells format like normal cells (#4806)

* fix(tables): right-align run/stop in the embedded table toolbar

Add a right-aligned `trailing` slot to ResourceOptionsBar and move the embedded
mothership table's run/stop control into it, so Filter + Sort stay left-aligned
and run/stop sits opposite on the right. No-op for the search-bearing consumers
(logs, resource list), which don't pass `trailing`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): workflow-output cells format values like normal cells

Workflow-output columns short-circuited in resolveCellRender and rendered their
value as plain text, so a sim-resource URL / external URL / JSON / date produced
by a workflow never got the chip, favicon link, or typed formatting a normal
cell gets. Factor value formatting into a shared `resolveValueKind` helper used
by both the workflow-value branch and the plain-cell branch; the workflow branch
keeps the typewriter reveal for plain streaming text via a `typewriter` flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): detect resource/URL links on workflow output regardless of column type

Workflow output columns default to `json` (columnTypeForLeaf), so routing their
values through the type-based formatter (a) gated chip/URL promotion behind
`column.type === 'string'` — a URL produced by a json-typed output never became
a chip — and (b) JSON.stringify'd plain string values, adding quotes and losing
the typewriter reveal. Detect links (sim-resource chip / favicon URL) on the
value string directly for workflow outputs, falling back to the plain `value`
kind; plain cells keep the type-based formatting. Addresses Greptile P2 on #4806.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(icons): repair broken integration icon rendering (#4810)

* fix(icons): repair broken integration icon rendering

Two distinct bugs left integration icons broken on the /integrations page
(visible at 32-40px, hidden at the toolbar's 16px):

1. Corrupted SVG paths (Notion, Greptile, Granola, Calendly, Grafana, Bedrock):
   over-minified data dropped elliptical-arc flag digits (e.g. `A1 1 0 5.9 7`
   instead of `A1 1 0 0 0 5.9 7`); Granola's cubic stream was truncated. Browsers
   abort path parsing at the first invalid arc flag, so each rendered as a fragment
   or blank. Replaced with correct path data from canonical sources, preserving each
   icon's existing fill/gradient and bgColor.

2. Invisible glyph (Bright Data): its icon uses fill='currentColor' but bgColor was
   '#FFFFFF', and every surface forces text-white on the glyph - white-on-white.
   Changed bgColor to Bright Data's brand blue (#3d7ffc) so the white glyph reads,
   matching the white-glyph-on-brand-chip convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): restore Calendly dual-tone brand colors

Addresses review feedback: the previous fix replaced the broken Calendly icon
with a monochrome #006BFF path, dropping the cyan #0ae8f0 accent from the
original dual-tone mark. Restored the two-tone logo (blue + cyan) using clean,
valid path data, cropped to a tight square viewBox so it fills the chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge icons, fix Zoom contrast and Quiver chip

- Zoom: glyph was blue-on-blue (#0B5CFF on #2D8CFF chip); switched to
  currentColor so it renders as a white glyph on the blue chip.
- Quiver: chip bgColor #000000 -> #FFFFFF to match the icon's near-white box,
  and enlarged the mark slightly (viewBox crop).
- Enlarged (tightened viewBox, verified no clipping): RevenueCat, Prospeo,
  Granola, Firecrawl, Enrich.so, and the AWS icons (RDS, DynamoDB, SQS,
  CloudFormation, Athena, CloudWatch, SES, Bedrock, S3).
- ZoomInfo left unchanged: it is a full red rounded-square logo that already
  fills its frame, so a crop would clip it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): use Bright Data wordmark on white chip; repair Circleback

- Bright Data: replaced the flame glyph with the official two-tone 'bright data'
  wordmark (provided asset), centered in a symmetric viewBox. Reverted the chip
  bgColor from #3d7ffc to #FFFFFF since the blue wordmark is invisible on a blue
  chip (the wordmark is designed for a light background).
- Circleback: a minifier had rounded the pattern's image scale to scale(0),
  collapsing the embedded logo to zero size (invisible). Restored the correct
  scale (1/280 = 0.00357142857) so the C. mark renders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(docs): sync Quiver block color card to white chip

Reflects the Quiver bgColor change (#000000 -> #FFFFFF) in the docs block info card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge AWS/Cloudflare/Dagster icons, fully white Zoom

- Enlarged (tighter viewBox, render-verified, no clipping): Cloudflare, Dagster,
  and the red AWS icons AWS IAM, Identity Center, Secrets Manager, SES, STS.
  Identity Center was anomalously small (filled ~32% of its frame); the group is
  now sized consistently (~80% fill).
- Zoom: the camera lens triangle was still #0B5CFF (blue-on-blue); switched it to
  currentColor so the whole camera renders white on the blue chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(wiza): consolidate individual reveal into a single operation

Merges the separate Start/Get Individual Reveal operations into one Individual
Reveal operation in the Wiza docs and integrations data (operationCount 5 -> 4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): size remaining AWS icons to match the set (~80% fill)

Bring RDS, DynamoDB, SQS, CloudFormation, Athena, CloudWatch and S3 up to the
same ~80% fill as the AWS IAM/Identity Center/Secrets Manager/SES/STS group, so
all AWS icons are visually consistent. Bedrock left as-is (already ~92% fill).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): use Bright Data flame mark, enlarge ZoomInfo

- Bright Data: the full 'bright data' wordmark was illegible at chip size.
  Replaced with just the flame-'i' brand mark (blue #4280f6 on the white chip),
  centered.
- ZoomInfo: cropped the viewBox toward the white 'Zi' so it's larger; the red
  rounded-square background still fills the chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge CrowdStrike icon

The falcon mark sat small in its chip because the icon used a wide 768x500
viewBox (letterboxed in the square chip). Switched to a square viewBox centered
on the mark so it fills ~80%, consistent with the other icons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(tables): serialize schema mutations to prevent parallel column clobber (#4812)

* Make workflow description nullable

* fix(tables): serialize schema mutations to prevent parallel column clobber

* fix(tables): load workflow outside schema lock; use DbOrTx for getTableById

* fix(tables): scale idle timeout in updateColumnType to avoid aborting large type changes

* fix(tables): skip stale remap types when workflowId changes concurrently

* fix(tables): scale idle timeout in updateColumnConstraints for large tables

* fix(wait): resume live/draft async waits and preserve cell context on chained waits (#4814)

* Make workflow description nullable

* fix(wait): resume live/draft async waits and preserve cell context on chained waits

* improvement(knowledge): polish tag filter dropdowns

* improvement(knowledge): soften filter section labels

* improvement(knowledge): soften list filter labels

* fix(security): harden SSO domain registration, webhook path isolation, and CSV export (#4813)

* fix(security): harden KB file access, SSO domain registration, webhook path isolation, env secrets, and CSV export

* fix(sso): scope domain conflict query with indexed lower(domain) filter

Address PR review: avoid a full-table scan on every SSO provider
registration by filtering candidate rows in SQL with
lower(domain) = <normalized>, keeping the in-memory ownership check.
Also tighten the normalizeSSODomain TSDoc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: condense env route security comments

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* icons update

* chore(security): tighten inline comments in CSV export and KB file authorization

Condense verbose comment blocks to concise TSDoc/single-line form; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): validate internal serve origin in KB file authorization

Replace the bypassable isInternalFileUrl substring check in resolveInternalKbKey
with an origin allow-list (base URL, internal API base URL, TRUSTED_ORIGINS).
A crafted external host whose path is /api/files/serve/<victim-key> no longer
resolves to the victim key. Relative same-origin URLs are unaffected.

* style(sso): use idiomatic sql lower() comparison for domain conflict query

Match the repo's prevailing `sql`lower(col) = value`` idiom for the
case-insensitive SSO domain conflict lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): align workspace env admin gate with hasWorkspaceAdminAccess

Use the same admin check the secrets UI uses (owner, admin permission, or
org-admin) so owners and org-admins are not wrongly denied their own decrypted
workspace secrets, while read-only members remain restricted to names only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(sso): rely on lower(domain) match for conflict detection, drop dead in-memory recheck

Address PR review: the SQL `lower(domain) = <normalized>` predicate already
excludes rows that the in-memory `normalizeSSODomain(...) === domain` recheck
claimed to catch, making that recheck dead/misleading code. Match on the
canonical lower-cased domain and filter purely by ownership. Malformed legacy
values (wildcards, schemes, ports) never match an email domain at sign-in, so
excluding them is not a gap. Test DB mock now applies the lower() predicate so
the casing-variant case is genuinely exercised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): scope webhook deploy path conflict to active webhooks

findConflictingWebhookPathOwner omitted the isActive filter that the
runtime dispatcher (findAllWebhooksForPath) applies, so an inactive but
non-archived webhook from another workflow (e.g. after undeploy or
failure auto-disable) would permanently block any new deployment on that
path even though it never receives deliveries. Align the guard with the
runtime isActive + archivedAt filter; the earliest-owner runtime check
remains the authoritative cross-tenant protection. Also trims verbose
TSDoc on the webhook path-isolation helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): exclude archived workflows from webhook deploy path conflict

findConflictingWebhookPathOwner now joins workflow and filters
isNull(workflow.archivedAt), matching the runtime dispatcher
(findAllWebhooksForPath). A webhook on an archived workflow can never
receive deliveries at runtime, so it must not block legitimate path reuse
with a 409.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): anchor KB file ownership to earliest document in any state

A KB file's owner is now the earliest document referencing its key regardless of
state (active/archived/deleted/excluded); access is granted only when that owning
document is still active. Closes the residual where an attacker could plant an
active document to claim a file whose original document was archived or deleted.

* updated greptile icon

* revert(security): drop KB file authorization changes

Reverts the knowledge-base file-access work (origin-pinning / owner-pinning /
origin allow-list in verifyKBFileAccess) and its test. The other hardening fixes
(SSO domain registration, webhook path isolation, workspace env secrets, CSV
export) are unchanged. apps/sim/app/api/files/authorization.ts is restored to its
origin/staging baseline.

* fix(sso): treat caller's own user-scoped provider as owned during conflict check

Self-hosters often register SSO user-scoped via the CLI script (no
SSO_ORGANIZATION_ID). If they later enable organizations and reconfigure the
same domain org-scoped through the UI, the conflict check previously treated
their own user-scoped row as another tenant's and returned a misleading 409.
Recognize the caller's own user-scoped provider as owned so that migration is
allowed, while still blocking another user's or another org's domain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* revert(security): remove workspace-env admin gate

Defer to a credential-based access model (separate change). Restores
GET /api/workspaces/[id]/environment to main behavior and removes the test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(security): consolidate webhook path-collision check into one helper

Extract findConflictingWebhookPathOwner to lib/webhooks/utils.server.ts as
the single source of truth for cross-tenant path-collision detection, used by
both webhook creation paths (deploy sync and the manual /api/webhooks route).

This also repairs two latent issues in the manual route's previous inline
check, which queried with limit(1) and only webhook.archivedAt:
- limit(1) inspected one arbitrary row, so a same-workflow row could mask a
  foreign collision (false negative). The shared helper scans all matching
  rows.
- It omitted isActive/workflow.archivedAt, so inactive or archived-workflow
  webhooks (which never receive deliveries) permanently blocked path reuse.
  The helper mirrors the runtime dispatcher's filter.

Same-workflow webhook reuse for upsert is now a separate, explicit lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF (#4818)

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF

* test(security): use real isPrivateOrReservedIP and cover IPv6 edge cases

* improvement(integrations): validate and expand devin, cursor, and greptile (#4820)

* improvement(integrations): validate and expand devin, cursor, and greptile

- devin: fix missing org_id path segment on all session endpoints, add 7 session sub-resource tools (list messages/attachments, get/append/replace tags, archive, terminate), pagination, and is_archived output
- cursor: add get_api_key_info, list_models, list_repositories tools
- greptile: align block and docs
- normalize array outputs to default [] and tighten types

* refactor(cursor): simplify list_repositories v2 array normalization

Collapse the redundant `?? []` + `Array.isArray` double-guard into a
single Array.isArray check, per PR review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(devin): scope session-tag mapping to tag ops and normalize array tag inputs

- Only map sessionTags into the tools tags param for append/replace operations,
  preventing stale sessionTags state from clobbering create_session tags
- Fall back to a wired tags value when sessionTags is empty for tag operations
- Normalize tag inputs (string or wired string[]) via normalizeTags so array
  values from other blocks no longer throw on .split

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cursor): restore base64 file data in legacy download_artifact metadata

The legacy CursorBlock exposes only content + metadata (no v2 file
output), so metadata.data was the only way legacy-block workflows could
access downloaded artifact bytes. Restore the base64 data field and
document it in the outputs/type instead of dropping it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(devin): coerce terminateArchive to archive flag for boolean-wired input

* docs(integrations): regenerate tool docs for new devin and cursor operations

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(search-replace): don't auto-navigate when content edits invalidate the active match (#4819)

* fix(search-replace): don't auto-navigate when content edits invalidate the active match

* fix(search-replace): clear afterReplaceIndexRef on apply failure and zero matches

* fix(search-replace): remove duplicate setActiveSearchTarget(null) on close

* fix(search-replace): move afterReplaceIndexRef write inside handleApply past the guard

* fix(search-replace): auto-navigate when hydration resolves with no prior active match

* chore(search-replace): remove inline comments

* fix(search-replace): revert !activeMatchId guard that caused immediate re-navigation after deselect

* improvement(enrichments): limit company-info to fields both providers return (#4817)

Hunter's company dataset returns null industry/foundedYear for many large companies (verified against the live API for Microsoft, Amazon, Google), so under the first-non-empty-wins cascade those columns appeared inconsistently across rows. Limit company-info outputs to employee count and description — the fields Hunter and PDL both reliably return — so every row is consistent. employeeCount is a string so Hunter's range bucket and PDL's exact count share the column.

* fix(files): don't reject external URLs containing '..' in file parse validation (#4821)

* fix(files): don't reject external URLs containing '..' in file parse validation

The file block's file_fetch operation rejected any external URL whose path
contained '..' (e.g. Slack files-pri slugs with a literal '...') with
'Access denied: path traversal detected'. Traversal checks only apply to
local paths — external http(s) URLs are fetched with SSRF protection
downstream and are never resolved against the filesystem, so they now
short-circuit as valid. Internal /api/files/serve/ URLs keep full traversal
protection.

* test(files): fix external-URL assertion to handle undefined error

* test(files): assert success explicitly in external-URL traversal test

* fix(files): keep traversal protection for https URLs matching internal serve paths

* feat(google-sheets): add row filtering to read with numeric operators (#4822)

* feat(google-sheets): add row filtering to read with numeric operators

Adds client-side row filtering to the Google Sheets read (v2) operation.
Filter the returned rows by a header column using text operators
(contains, not_contains, exact, not_equals, starts_with, ends_with) and
numeric/ordering operators (gt, gte, lt, lte). Filtering lives in a pure,
unit-tested helper (filterSheetRows) and runs over the fetched read range;
an optional `filter` output reports whether the column was found and how
many rows matched.

Also hardens the surrounding tools:
- trim spreadsheetId in write/update/append URL builders (matches read)
- URL-encode the v1 read default range
- expose valueInputOption for the update operation in the block

Backwards compatible: with no filter requested, read output is byte-
identical and the `filter` field is omitted. The filterMatchType union is
widened additively (4 -> 10 values).

* fix(google-sheets): correct filter metadata for missing column and header-only sheets

- matchedRows is now 0 (not totalRows) when the filter column is not found,
  so it no longer contradicts applied=false / columnFound=false
- columnFound now reflects an actual header lookup for empty/header-only
  sheets instead of being hardcoded true
- add tests covering header-only and empty sheets with present/absent columns

* fix(selectors): fetch all pages for paginated dropdown list routes (#4823)

* fix(selectors): fetch all pages for paginated dropdown list routes

Dropdown selectors fetched only the first page of paginated provider
APIs, silently hiding results past page one. Add bounded server-side
draining to the list routes across Microsoft Graph, Google, Notion,
Atlassian, Linear, AWS CloudWatch, and offset/token REST APIs, plus a
shared client-side drain cap in the selector hook. Response shapes,
stored values, and tool execution are unchanged; CloudWatch list tools
still honor a caller-supplied limit. Also fixes the Word file picker
that was searching for .xlsx files.

* fix(selectors): harden JSM and Monday pagination draining

- JSM service-desk/request-type drains advance `start` by the actual row
  count returned (not the fixed page size) and stop on an empty page, so a
  short non-final page can't skip items.
- Monday boards drain now checks `response.ok` per page, surfacing a
  mid-drain HTTP failure instead of treating it as an empty final page and
  returning a partial 200.

* docs(selectors): clarify JSM drain advances start by actual row count

The offset-advancement fix (advance `start` by the rows returned, not the
fixed page size) landed in 7b19788a8; update the TSDoc to match so it no
longer reads as advancing by `limit`.

* fix(selectors): drain fetchPage in direct fetchList callers

Making `fetchList` optional left three direct callers (outside the
useSelectorOptions hook) calling it unguarded, which broke the build's
type check. Route them through a shared `loadAllSelectorOptions` helper
that uses `fetchList` when present and otherwise drains `fetchPage`.
This also prevents a regression: `confluence.spaces` / `knowledge.documents`
now paginate via `fetchPage` only, and these callers (search/replace,
value resolution) would otherwise have silently returned no options.

* chore(selectors): rename MAX_PAGE_PAGES to MAX_NOTION_PAGES for readability

* fix(sso): re-check domain conflict before write and reject IP-address domains (#4825)

* improvement(copilot): make copilot_messages the sole transcript store, remove JSONB dual-write (#4826)

Stop writing/reading the legacy copilot_chats.messages JSONB column now that
reads are cut over to copilot_messages. Make appendCopilotChatMessages the
primary write (throws on failure instead of swallowing), repoint peripheral
readers (workspace VFS, chat cleanup, data drains, fork, superuser import) to
copilot_messages, and persist the assistant turn inside finalizeAssistantTurn's
transaction so it commits atomically with the stream-marker clear. The column
itself is dropped in a follow-up migration after this bakes.

* feat(tables): expand filter operators (not-contains, starts/ends-with, not-in, empty) (#4827)

Add does-not-contain ($ncontains), starts-with ($startsWith), ends-with
($endsWith), not-in-array ($nin, previously executed server-side but unexposed
in the UI), and is-empty/is-not-empty ($empty) filter operators end-to-end —
SQL builder, condition types, query-builder converters/constants, the filter
UI, the Table tools/block descriptions, and docs.

Also fix correctness bugs in the filter builder surfaced by the wider operator
set:
- Same-column AND rules (e.g. age > 18 AND age < 65, or name startsWith 'A'
  AND name endsWith 'Z') silently overwrote each other because the AND group
  was keyed by column name. They now merge into one operator object, which
  also makes Filter -> rules -> Filter round-trip losslessly for multi-operator
  columns.
- $nin values were not split into an array like $in, and textual-match values
  like "123" were numeric-coerced (breaking the ILIKE path).
- A non-boolean $empty operand from the raw API silently inverted the check; it
  now coerces 'true'/'false' strings and otherwise returns a 400.

* improvement(copilot): stop persisting tool-call result outputs in transcripts (#4829)

Opening a Mothership task could take many seconds because a single persisted
assistant message in copilot_messages.content can reach hundreds of MB, almost
entirely inside contentBlocks[].toolCall.result.output (e.g. a get_workflow_logs
or run_workflow result). The DB query is ~2ms; the cost is detoasting that
payload, shipping it to the browser, and parsing it.

These outputs are dead weight on the Sim side: they are never rendered (the
thread shows only tool name/title/status) and never replayed to the model (the
upstream copilot service owns conversation memory). So drop result.output before
it is persisted, keeping result.success/error plus the tool metadata.

- add stripToolResultOutput() in persisted-message.ts
- apply it in messages-store toRow (covers every write path) and in
  loadCopilotChatMessages (existing rows render fast on read)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers (#4830)

* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers

* fix(providers): guard Ollama streaming fast-path with hasActiveTools

Match Together/Baseten/Fireworks: when tools are supplied but all are
filtered out (usageControl 'none'), take the single streaming call instead
of an extra non-streaming round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(providers): filter non-chat model types from Together model list

* refactor(providers): dedupe Ollama Cloud upstream schema

ollamaCloudUpstreamResponseSchema was byte-for-byte identical to
ollamaUpstreamResponseSchema (both /api/tags endpoints return the same
{ models: [{ name }] } shape). Drop the duplicate and reuse the shared schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(knowledge): calendar view sync, deduplicate popover animation classes, type-safe filter cast

* cleanup(knowledge): remove TRIGGER_BORDER_CLASS duplication, inline displayLabel, drop enabledFilterParam alias

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>

* feat(blocks): add BlockMeta to Quiver and Linq; fix invalid block config fields; update skills

Block fixes:
- Add QuiverBlockMeta (tags + 3 templates: icon generator, diagram creator, vectorizer)
- Fix QuiverBlock: remove invalid tags field from BlockConfig, IntegrationType.Design →
  IntegrationType.AI (Design doesn't exist in the enum)
- Fix GreptileBlock: remove invalid tags field from BlockConfig,
  IntegrationType.DeveloperTools → IntegrationType.DevOps
- Fix LinqBlock: remove invalid tags field from BlockConfig (tags belong only in BlockMeta)

Skills:
- add-block: add dedicated BlockMeta section with structure, rules, and registration
  pattern; add BlockMeta checklist items
- add-integration: add BlockMeta to block structure template, add rules clarifying
  that tags must NOT appear on BlockConfig and integrationType must be a valid enum
  value; update registry snippet to include blocksMeta; add checklist items

* fix(integrations): fix category dropdown by defining missing LANDING_INTEGRATIONS_DATA_PATH and regenerating integrations.json

The staging merge introduced landing-content.ts but forgot to define
LANDING_INTEGRATIONS_DATA_PATH in generate-docs.ts, causing the script
to crash before writing integrations.json.

The stale JSON had integrationTypes (plural array) from an older script
version, while the Integration type and workspace UI both read
integrationType (singular string) — so ALL_CATEGORY_SECTIONS bucketed
to undefined and the category filters never appeared in the dropdown.

Fixed by adding the missing path constant and re-running the generator.
integrations.json now has 192 entries with the correct integrationType field.

* fix(sidebar): restore resize handle on all pages

commit 3109104582 wrapped the resize handle in {(isCollapsed ||
isOnWorkflowPage) && ...} and added a useEffect that resets sidebar
width to SIDEBAR_WIDTH.MIN whenever the user navigates away from a
workflow page. Together these made the sidebar non-resizable on Tasks,
Tables, Knowledge Base, and every other non-workflow page.

Restore the staging behavior: always render the resize handle and
remove the effect that forced the width reset on page transitions.

* fix(sidebar): match staging onKeyDown and tabIndex on resize handle

The resize handle was still conditionalizing onKeyDown and tabIndex
on isCollapsed, blocking keyboard accessibility of the separator role
when expanded. Staging always attaches both unconditionally.

onKeyDown={isCollapsed ? handleEdgeKeyDown : undefined} → onKeyDown={handleEdgeKeyDown}
tabIndex={isCollapsed ? 0 : undefined}                 → tabIndex={0}

* feat(integrations): show connected credentials on integration detail page

When navigating to /integrations/google-docs (or any integration), a
Connected section now appears above the templates listing all workspace
credentials tied to that provider. Each row links back to the credential
detail page (/integrations/connected/${id}) for management actions.

Pairs with the earlier change that routes connected items from the
integrations list to the provider detail page instead of directly to
the credential detail page.

* fix(integrations): rename Add in chat to Add to Sim

* fix(skills): rename Add button to Add to Sim

* fix(platform): restore M1/M2/M3 regressions and LazyMotion on landing page

M1 — Invitation guard: re-introduce usePermissionConfig().isInvitationsDisabled
alongside the workspace inviteDisabledReason check. The flag now also
respects NEXT_PUBLIC_DISABLE_INVITATIONS and EE permission-group
disableInvitations, not just billing policy.

M2 — Settings redirects: /settings/integrations and /settings/skills
now server-redirect to /integrations and /skills respectively so old
bookmarks and emails don't silently land on General.

M3 — Starter block search exclusion: restore block.type !== 'starter'
guard in the search store so users cannot add a duplicate Starter block
via the command palette.

LazyMotion: restore LazyMotion + domMax/domAnimation wrappers and m.*
components in landing-preview-panel and landing-preview-home. The
removal was accidental (the full motion bundle was left after an
import cleanup), which caused the entire framer-motion feature set to
load eagerly on the landing page.

* fix(integrations): revert connected list to credential detail; remove settings redirects

* feat(sidebar): restore workspace switcher search with updated styling

Shows a search input in the workspace dropdown when the user has more
than 3 workspaces (WORKSPACE_SEARCH_THRESHOLD). Keyboard navigation:
ArrowDown/Up to move through results, Enter to switch, resets on close.

Styled to match the current branch (border-1/surface-5 tokens, sm text,
11px Search icon) rather than the old staging styles. Highlight state
is wired through chipVariants active prop so it follows the same active
appearance as clicked/hovered items.

* fix(sidebar): clean up workspace search — layout, memo, and effect guard

* fix(sidebar): align workspace rename input selection style with workflow rename

* perf(sidebar): eliminate React re-renders during sidebar drag resize

Previously, every mousemove during resize called setSidebarWidth(), which
both updated the --sidebar-width CSS variable (sync) and set Zustand state
(async). This caused:
  1. A 1-frame transition flash on mousedown — isResizing state had to
     round-trip through React before the is-resizing CSS class was applied,
     so the width transition fired for the first pixel of movement.
  2. A React re-render per pixel dragged — components reading sidebarWidth
     from the store (avatars, usage-indicator) lagged one frame behind the
     container, making the + and ... buttons appear to jump ahead.

New approach:
  - handleMouseDown adds is-resizing directly to the sidebar DOM node before
    any React involvement (synchronous, no frame lag).
  - mousemove writes only to the CSS custom property (zero React renders).
  - mouseup persists the final width to Zustand exactly once.
  - isResizing / setIsResizing state removed from the store and hook — they
    are no longer needed since the class is managed via direct DOM mutation.

* perf(sidebar): add requestAnimationFrame throttle to resize mousemove handler

* fix(sidebar): fix drag-right lag caused by WorkspaceChrome overflow-hidden transition

The sidebar-container's is-resizing class correctly suppressed its own width
transition, but the two wrapper divs in WorkspaceChrome both have
transition-[width]/transition-transform with a 175ms ease. The outer wrapper
also has overflow-hidden, so while the sidebar content was at the correct width
instantly, it was visually clipped by the outer wrapper which was still
animating — causing the + and ... buttons to appear to lag behind the resize
line on drag-right (not on drag-left, since shrinking doesn't clip content).

Fix: add sidebar-shell-outer/sidebar-shell-inner class names to both chrome
wrappers, and suppress their transitions via html.sidebar-resizing rule when
a drag is active. The html.sidebar-resizing class is toggled directly in the
resize hook alongside is-resizing, so it takes effect synchronously on mousedown.

* fix(icons): redesign Download icon to match Upload style; fix Upload/Download confusion

Download icon was missing the tray/shelf line at the bottom that Upload has,
making it look like a plain arrow rather than a matched pair. Updated Download
to use the same viewBox, stroke weight, and three-path structure as Upload
(tray + stem + arrowhead), just pointing down.

Also fix 5 places where Upload (↑) was incorrectly used for download/export
actions:
  - files.tsx: two Download action rows in toolbar and context menu
  - tables/table.tsx: Export CSV toolbar button
  - table-context-menu.tsx: Export CSV context menu item
  - logs.tsx: Export toolbar button
  - landing-preview-logs.tsx: decorative Export button

Import CSV and actual upload actions correctly keep the Upload icon.

* fix(icons): replace Upload with Download on all remaining export/download actions

- panel.tsx: Export workflow dropdown item
- context-menu.tsx: Export in sidebar workflow context menu
- chat.tsx: Export chat button
- output-panel.tsx: Export console CSV button
- terminal.tsx: Export console CSV button
- resource-content.tsx: Export table as CSV + Download file buttons

* fix(icons): fix remaining Upload→Download on download actions in files and logs

- action-bar.tsx: download button in files toolbar
- file-row-context-menu.tsx: Download item in file context menu
- file-download.tsx: both download buttons in log details file viewer

* updated block skills, settings pages, modals, buttons -> chips, blocks missing metadata

* updated skill modal

* improvement(resource-header): refine breadcrumb truncation ux

* improvement(resource): add floating overflow text tooltips

* wire up credits counter

* improvement(resource-header): mute path dropdown title

* refactor(resource-header): share floating-tooltip engine, prune dead overlay tooltips (#4844)

Clean up the breadcrumb truncation feature for reuse and correctness:

- Extract useFloatingTooltip / useIsOverflowing / FloatingTooltip into a shared
  floating-tooltip module. BreadcrumbSegment and FloatingOverflowText now consume
  one implementation instead of duplicating ~150 lines of positioning, velocity,
  overflow-detection, and portal logic.
- Replace the hardcoded terminal-label regex in ResourceHeader with a typed
  `terminal` flag on BreadcrumbItem (set by the document chunk/loading crumbs),
  decoupling the generic header from knowledge-base copy.
- Clear the path-popover close timeout on unmount and reuse the shared
  POPOVER_ANIMATION_CLASSES constant.
- Drop the redundant manual overflow-state writes (fixes a sticky fade mask).
- Revert FloatingOverflowText inside Combobox `overlayContent` back to plain
  truncating spans across files/logs/tables/scheduled-tasks/document: the combobox
  overlay is pointer-events-none, so the tooltip handlers never fired there.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(emcn,resource-header): address PR #4844 review feedback

- useIsOverflowing now uses a callback ref so the ResizeObserver follows the
  element across mount/unmount/reassignment instead of capturing it once at mount.
  Safe for conditionally rendered consumers of the shared hook. (greptile P2)
- Move POPOVER_ANIMATION_CLASSES out of chip-date-picker implementation internals
  into emcn/components/popover/popover-animation.ts, exported from the
  @/components/emcn barrel. Consumers now import from the module boundary.
  (greptile P2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* upgrade table and styling upgrade

* fix schema to include integration

* fix(files): align delete icon with tables view (Trash → Trash2)

Co-Authored-By: waleed <waleed@simstudio.ai>

* fix(mothership): preserve blockType for integration contexts in sent messages

Integration mention chips were missing their provider icons in sent messages
because blockType was dropped when mapping ChatContext to messageContexts.
renderIntegrationTile returns null without blockType, silently hiding the icon.

* fix(mothership): allow 'integration' resource type in chat resources API

The VALID_RESOURCE_TYPES allowlist was missing 'integration', causing a
400 error when adding integrations to the Mothership resource tab — so
they never persisted and disappeared on refresh.

* fix(ui): Add "File" title next to file resource header

* fix(ui): fix resource header columns being bolded

* fix(resource): keep the floating tooltip from jumping on click

Gate the focus-driven show behind :focus-visible so a mouse click (which
focuses the trigger) no longer re-shows the tooltip anchored to the element's
bottom edge. On click the tooltip now hides cleanly instead of jumping down;
keyboard focus still shows it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(sidebar): eliminate unnecessary re-renders in workspace switcher for non-search users

- onMouseEnter: only set highlightedIndex when showSearch is true, preventing
  a state update + re-render on every workspace row hover for users with ≤ 3
  workspaces where the search is never shown
- onOpenChange: only reset workspaceSearch and highlightedIndex when showSearch
  is true, since both values are always already at their defaults for non-search
  users and setting them triggers a pointless re-render during dropdown close
- data-workspace-row-idx: only set when showSearch is true since the scroll
  effect that reads this attribute is already gated on showSearch

* feat(search): context-aware cmd-k results on the integrations page

When cmd-k is opened on the integrations page, show two new result
groups: connected accounts (visible even with empty input) and catalog
integrations (appear once the user types). Selecting an OAuth integration
deep-links to its detail page with ?connect=oauth so the connect modal
auto-opens. Non-OAuth integrations navigate to the plain detail page.

Both groups are gated to the integrations page only and respect the
hideIntegrationsTab permission. The credentials fetch shares the same
React Query cache key as the integrations page itself (no double fetch).

* refactor(emcn): make the floating tooltip the one canonical Tooltip

Replace the Radix-based emcn Tooltip with the cursor-following floating tooltip so
every tooltip in the app uses one consistent style. Built on the shared
floating-tooltip engine (relocated into emcn), not a parallel implementation.

- Move the floating-tooltip engine into emcn/components/tooltip and export it from
  the barrel; re-point its consumers (FloatingOverflowText, resource-header)
- Extend the FloatingTooltip bubble to render arbitrary children (+ role/id for
  a11y) so it can back general tooltips, not just overflow text
- Rebuild emcn Tooltip (Root/Trigger/Content/Provider/Shortcut/Preview) on
  useFloatingTooltip — compound API preserved, ~350 call sites unchanged, legacy
  side/align props accepted and ignored (the tooltip follows the cursor). Removes
  @radix-ui/react-tooltip usage (package kept for a later cleanup; react-slot
  retained for asChild)

Note: general tooltips now show instantly (no hover delay) and follow the cursor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style(emcn): put tooltip text on the design scale (text-caption)

Replace the tooltip's ad-hoc `text-xs` + `leading-[18px]` with the semantic
`text-caption` (12px) font-size token so the text styling is fully on the design
scale and self-documenting, matching how the rest of the system is set up. The
color already used the global `--text-body` token. No visual change (still 12px
with a ~18px line height).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(sidebar): add empty task state and inline task creation

- Show "No tasks yet" in the Tasks section (expanded and collapsed) when the list is empty
- Clicking + now creates a task via the API and navigates directly to it, rather than navigating to home
- Add isCreatingTaskRef guard to prevent double-click from spawning multiple tasks
- Disable + button while creation is pending
- Fall back to home navigation on creation error

* invite, billing, home

* improvement(seats): auto purchase seats on invitations into workspace (#4857)

* improvement(seats): auto purchase seats on invitations into workspace

* improve sampling for seat drift reconciler

* address comments

* feat(knowledge): align connector UI with integrations page styling

- ConnectorTypeCard now matches integration rows: brand-colored rounded-xl tile, ArrowRight, title/subtitle hierarchy
- ConnectorCard icon upgraded from flat surface-4 to branded tile (white icon on brand bg, graceful fallback)
- Connector header badges use chipVariants instead of custom Button classes
- Add-connector search input aligned to integrations style (h-[30px], rounded-lg, border-1)

* fix(icons): trim Folder SVG viewBox to remove right-side whitespace

The folder path only extends to x≈14.33 in a 15-unit viewBox, leaving
~0.5 units of empty space on the right. At 12px rendered size this
produces ~0.4px extra gap (visible as ~1px on retina displays) compared
to solid icons like the workflow color square. Trimming the viewBox to
14.5 units makes the folder fill its chip slot evenly.

* fix(user-input): restore draft text synchronously to preserve contexts on nav

The SSR-safe approach (empty useState + effect restore) created a timing
window where the sync effect in useContextManagement fired with message=''
before the value was set, clearing any restored contexts. Folder and workflow
contexts (not re-added by applyAutoMentions) were lost on every nav-back.

Revert to the staging approach: initialize value synchronously from the
draft store so message is already populated when effects run, matching
the behavior on staging.

* fix(queue): render context chips in queued messages

- Remove plainMentions from queued message rows so context chips render
  with icons, consistent with sent messages
- Fix computeMentionRanges to use '/' prefix for skill contexts (content
  has the slash trigger restored at submit time, not '@')

* fix(mothership): remove integrations from add-resource dropdown

* fix(mothership): comment out integrations from add-resource dropdown

* chore(db): drop form, templates, template_creators, template_stars tables

These tables backed the Forms and Templates platform features which were
intentionally removed from this branch. Clean up the DB schema to match.

* chore(db): add migration metadata for 0224 drop tables

* block icons, sidebar, toolbar

* chore: remove remaining dead code for template-profile feature

- Remove 'template-profile' from SettingsSection union type
- Remove 'template-profile' entry from SECTION_TITLES
- Remove now-redundant template-profile guard in settings sidebar
- Remove commented-out template-profile nav item

* fix(multi-select): preserve anchor on range selection for tasks and folders

After a shift+click range, the anchor (lastSelectedTaskId / lastSelectedFolderId)
was being updated to the end of the range (toId). This caused subsequent
shift+clicks to extend from the wrong point instead of the original click.

Standard behavior: anchor stays at the initial click (fromId) so repeated
shift+clicks always expand/contract relative to where you started.

* feat(emcn): add SearchInput component and unify search bars platform-wide

- Add SearchInput to emcn: 30px chip-family filled search input matching the
  integrations page pattern (border-1, surface-5, leading Search icon)
- Migrate all 22 search bars across settings, EE pages, and integrations to
  SearchInput (only layout classes allowed at callsites)
- Rename Sim Keys -> Sim API Keys in nav/title; page copy now says API key
- Remove components/ui input, label, and verified-badge; migrate consumers
  to emcn equivalents or raw inputs (table cell editor, wand prompt bar)
- Delete dead EE skeleton files (data-drains, data-retention)
- General settings: Home Page chip moves to header left as navigation

* fix(files,tables): restore new-file editor autofocus and CSV import error toasts

Both were dropped on the staging line and regressed vs production (main):

- Files: the new-file editor autofocus chain (files.tsx -> file-viewer ->
  text-editor) was stripped by the react-doctor dead-code pass in #4544,
  which misread the prop-drilled `autoFocus` (consumed by an imperative
  `editor.focus()` effect) as unused. Restored the prop through all three
  layers and the one-shot focus effect so creating a new file focuses the
  editor immediately.
- Tables: CSV import failures were silently logged with no user feedback.
  Restored the per-file and generic `toast.error` surfacing.

* feat(home): score suggested actions by workspace signals

- Derive the suggestion pool from the curated block template catalog
  (1,343 prompts across 172 blocks) instead of 15 hardcoded entries
- Fix inverted relevance: prompts for connected providers are now boosted
  4x (instantly runnable) instead of excluded; unconnected discounted 0.4x
- Weight by featured (3x), popular category (1.5x), and resource gaps
  (no tables -> boost table starters; has KBs -> dampen KB-creation prompts)
- Weighted sampling without replacement, max one suggestion per block
- Connect rows weighted by catalog template count; 2 for fresh workspaces,
  1 once something is connected
- Key the catalog map by both versioned and base block types so gmail_v2
  templates resolve (gmail, github, notion, linear were silently dropped)
- Replace derive-in-effect state with a useMemo keyed by a shuffle nonce
- Add suggested_action_clicked / suggested_actions_shuffled /
  suggested_actions_toggled PostHog events

* billing, teammates

* improvement(credentials): credentials invites, secrets tab wiring up (#4874)

* improvement(credentials): move away from invite notion

* wire up secrets ui/ux

* address comments

* get consistent styling by removing emcninput + text area

* styling consistency

* remove fallback

* address comment:

* refactor(ui): migrate settings & workspace UI to chip design system

Migrate modals to ChipModal (showDivider, hint, resizable, size, leading,
ChipModalTabs), standardize Chip variants, add ChipCombobox wrapper, and apply
chip inputs/dropdowns across settings, knowledge, logs, tables, inbox, EE tabs.
Render ChipModalTabs as a ChipSwitch segmented control. Align /settings/secrets
detail with /integrations, refresh whitelabeling, and restore file-editor
autofocus and CSV import error toasts.

* fix(mothership): restore integrations to useAvailableResources for @ mention

Integrations were fully removed from useAvailableResources which broke
the @ mention menu since user-input shares the same hook. Now integrations
are always included in the hook but excluded at the AddResourceDropdown
component level, keeping them out of the sidebar + menu while remaining
available for @ mention autocomplete.

* refactor(settings): chip design-system consistency pass across all tabs

Extract a shared chip-field shell (CHIP_FIELD_SHELL/CHIP_FIELD_INPUT) mirroring
Input variant='chip' and route secrets, credential detail, and integrations
credential detail through it (30px height, font-medium, focus ring). Add a
Discard action to the secrets header when dirty. Group BYOK providers into
Models/Search & web/Enrichment sections and align its tiles to the integrations
tile.

Normalize list-row typography to text-[14px]/text-[12px] and icon tiles to
rounded-xl + border across api-keys, copilot, custom-tools, mcp,
workflow-mcp-servers, credential-sets, and access-control. Tone the secrets
Details chip and per-row affordances to ghost. Fix token correctness: raw
tailwind colors to design tokens (data-drains), missing chip variant on the
Snowflake role input, ColorInput chip-field reuse (whitelabeling), error token
and icon sizes (workflow-mcp-servers), border token (mcp), no-results sizing
(secrets), row chrome (recently-deleted), hover token and Button to Chip
(access-control), and deduped textarea chrome (sso).

* feat(settings): unify filter dropdowns on ChipSelect (integrations style)

Add a ChipSelect emcn component — a filled chip trigger + chevron opening a
DropdownMenu, matching the integrations category filter — supporting single
select, multi-select (checkbox rows), grouped options, and optional in-menu
search. Migrate every settings/EE filter dropdown off ChipCombobox to it:
audit-logs (resource-type multi + time-range), data-retention, data-drains,
general, admin (grouped tool picker), inbox status filter, and the workflow
MCP-server pickers. The SSO provider-id field stays an editable combobox since
it accepts free-text slugs.

Also fix audit findings: chip the MCP client-secret input, normalize an MCP
error-text size, and drop now-dead destination-icon code in data-drains.

* fix(mentions): require explicit @ for integration mentions; decorate sent messages robustly

- Bare integration names in prose (Monday, Notion, Clay) are no longer
  auto-converted to mentions or chipped — mention treatment is strictly
  opt-in via a token-starting @ (fixes the scunthorpe problem)
- @-prefixed mentions still canonicalize casing (@slack -> @Slack) on both
  the keystroke fast-path and bulk paths (paste, template, draft, STT)
- Sent/queued messages now self-sufficiently decorate @IntegrationName
  tokens via a text scan, covering messages sent before the input pass
  ran or authored outside the chat input
- Integration contexts missing a resolvable blockType (messages persisted
  before blockType was saved) are backfilled by label lookup so their
  mention pills render the brand icon again

* refactor(settings): section the API keys page like secrets

Wrap Workspace, Personal, and the allow-personal-keys toggle in SettingsSection
(muted label + divider) instead of bare bold headers, matching the secrets and
BYOK pages.

* fix(settings): ChipSelect renders above modals + full-width form mode

Raise the ChipSelect menu to --z-popover so it layers above modal surfaces
(--z-modal) instead of opening behind them. Add a fullWidth prop that stretches
the trigger and right-aligns the chevron for form-field use, and apply it to the
workflow MCP-server pickers.

* fix(emcn): ChipSelect uses the emcn flat chevron, not lucide's square one

The lucide ChevronDown is square; rendering it at the chip's 9x7 footprint
stretched it. Switch to the custom emcn ChevronDown (built for that wide aspect),
matching the integrations filter and ChipDropdown.

* fix(emcn): ChipSelect trigger hugs its content (w-fit)

In a stacked form layout the trigger was stretched by align-items: stretch,
leaving an empty gap to the right of the value. Add w-fit so the chip sizes to
its content (a compact pill) everywhere; fullWidth form selects are unaffected.

* fix(emcn): ChipSelect uses a square lucide chevron

Revert to lucide's ChevronDown sized square (size-[14px]) so it renders crisp,
matching the standard select chevron used by Combobox.

* renamed tasks to chats

* rename and file change

* improvement(billing): wire up billing, org, teammates tabs + remove deprecated subscription tab (#4887)

* improvement(billing): wire up billing, org, teammates tabs + remove depr subscription tab

* pass exec timeout to tool routes

* reuse helper

* address comments

* address disable comment

* chore(db): remove migration 0224 to regenerate on top of staging

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix type errors and regen migration?

* chore(db): drop branch migration 0226 ahead of staging merge; will regenerate

* chore(db): regenerate migration 0226 after staging merge

* externalize before compaction in fallback'

* fix save/discard chips to be consistent

* fix(ui): remove smodal tabs in favor of chip modal tabs

* fix(ui): skip auto-scrolling on mouse highlight of workspace

* fix(platform): restore settings redirects, forgot-password Enter submit, and tag tooltip visibility

- Re-add SETTINGS_REDIRECTS so /settings/integrations and /settings/skills
  deep links redirect to their top-level routes instead of rendering an
  empty settings panel (accidentally removed in 86da193cc3 one minute
  after cca5054cf6 added it)
- Add opt-in onSubmit to ChipModalField input/email variants and wire it
  in the forgot-password modal so Enter submits again (lost in the
  ChipModal conversion)
- Knowledge tag tooltip: drop the max-h/overflow-y-auto clamp that the
  pointer-events-none floating tooltip made unreachable; truncate each
  tag row instead so all tags stay visible with bounded height

* chore(db): remove migration 0226_third_spot before staging merge

* chore(db): regenerate migration as 0227 after staging merge

* feat(telemetry): add posthog + audit coverage for new platform actions

Audit log (compliance/permission-relevant only):
- org_seat.provisioned — seat auto-purchased when an invite acceptance
  grows the org (actor = accepting user, includes seat delta)
- org_plan.converted — Pro→Team conversion triggered by invite acceptance
- org_seat.drift_reconciled — hourly cron healed a drifted seat count
- credential_member.added/removed/role_changed — credential sharing
  surface was previously fully unaudited
- table.created — parity with existing table.updated/deleted
- skill updates now record skill.updated instead of mislabeled
  skill.created

PostHog:
- seats_provisioned, credential_shared/unshared,
  environment_updated/deleted (key counts only, never names/values)
- table_import_started/completed — background CSV imports previously had
  zero failure observability
- table_exported, file_downloaded, skill_updated
- credential_connected now fires for OAuth completions (draft-hooks),
  credential_deleted for OAuth disconnects; previously only manual
  credentials were tracked
- table_workflow_run gains deployment_mode (live/deployed/mixed)

Also: logger.warn on all new credential-admin 403 denials (members +
environment routes); invite-created audit enriched with
enforcedFixedSeats/plan.

Deliberately excluded as noise: per-hour dead-letter audit rows (would
re-record the same stuck event every cron run) and a duplicate
system-actor org_plan.converted in the Stripe outbox handler.

* feat(home): fill textarea on suggested prompt click instead of sending

Clicking a prompt action in the Suggested Actions panel now populates
the Mothership user-input textarea (via applyAutoMentions) and focuses
it with the caret at end, rather than immediately submitting. The user
can review, edit, and send manually.

* fix(templates): name owning integration in featured block template prompts

Four featured prompts omitted their owning integration name, making them
unbranded and disconnected from their title. Each prompt now explicitly
names GitHub or Google Sheets so mentionifyIntegrations renders the chip
and the copy reads as a self-contained agent-building instruction.

* fix(templates): rewrite fragment prompts so each names its integration and reads as a complete instruction

Featured and non-featured block template prompts that either omitted
the owning integration's canonical name or were phrased as marketing
fragments rather than natural user instructions have been rewritten.
Each updated prompt now starts with an imperative verb ("Build a
workflow that…"), names the owning integration explicitly so the
@-mention chip renders correctly, and aligns with the entry's title.

Files changed: salesforce, hubspot (×2), github, slack, airtable,
firecrawl, iam.

* fix(templates): name integration in remaining block template prompts

- google_docs.ts: replace 'Google Doc' with 'Google Docs document' in 4 prompts; update title 'Meeting notes to Google Doc' to 'Meeting notes to Google Docs'
- google_sheets.ts: replace 'Google Sheet' with 'Google Sheets spreadsheet/Google Sheets' in 3 non-featured prompts
- slack.ts: replace 'Google Doc' with 'Google Docs document' in 'Daily standup summary'
- stripe.ts: replace 'Google Sheet'/'Slacks' with 'Google Sheets'/'Slack' in 'Weekly metrics report'
- reddit.ts: add 'Reddit' to 3 prompts that only referenced subreddits
- notion.ts: rewrite featured prompt to start with a verb and name Notion
- jira.ts: rewrite featured marketing-fragment prompt to start with a verb
- linear.ts: rewrite featured marketing-fragment prompt to start with a verb
- gmail.ts: rewrite featured marketing-fragment prompt to start with a verb and name Gmail

* fix(icons): convert monochrome dark brand icons to currentColor for dark mode

LinkupIcon, InfisicalIcon, IntercomIcon, LumaIcon, GranolaIcon, OnePasswordIcon,
and RailwayIcon were hardcoded to black/near-black fills/strokes, making them
invisible in bare DARK mode. Convert all to currentColor so they follow the
theme-aware text color.

Add iconColor: '#286efa' to IntercomBlock (Intercom brand blue is a confident
mid-tone, safe on both themes).

Two-tone icons (StagehandIcon, AgentPhoneIcon, QuiverIcon) are left unchanged
because their white details are structural — converting to single-tone would
destroy logo legibility.

* removed color, user input, sidebar, suggested actions, chips/emcn

* removed color migration

* improve(blocks): audit block catalog metadata for accuracy and fill gaps

- Fix template prompts that claimed capabilities blocks don't have
  (fabricated triggers, non-existent tools) across ~98 integrations
- Normalize integration tags to family conventions and valid union values
- Align alsoIntegrations and modules with template prompt content
- Add BlockMeta for circleback, imap, and rss trigger integrations
- Add templates to clickhouse and greptile metas
- Remove duplicate PageSpeed deploy-gate template

* chore(telemetry): drop org_seat.drift_reconciled audit

System self-heal bookkeeping doesn't belong in the user-facing audit
trail — membership and seat-purchase changes are already audited, and
the cron's logger output covers ops visibility.

* chore(db): consolidate branch migrations into single 0227

* fix(emcn): make ChipModal scroll internally when content exceeds viewport

The Modal→ChipModal migration dropped the old ModalBody scroll container:
ChipModal renders ModalContent bare (no overflow-hidden) and its wrappers
had no min-h-0 chain, so tall modals (e.g. New data drain with an S3
destination) overflowed max-h-[84vh] off-screen with no way to scroll.

Complete the flex min-h-0 chain through ChipModal's frame and give
ChipModalBody flex-1 min-h-0 overflow-y-auto — header/footer stay pinned,
body scrolls only when constrained. Short modals are unaffected (max-h
caps, it doesn't stretch), and dropdowns inside the body are Radix-portaled
so the new scroll container cannot clip them. Five modals that had locally
patched this with max-h-[Nvh] overrides keep working unchanged.

* fix(emcn): don't close modal when dismissing a dropdown via outside click

Radix dispatches pointer-down-outside to every open dismissable layer at
once, so clicking outside an open dropdown/select inside a modal closed
both the dropdown and the modal in one jarring step. ModalContent now
prevents its own dismissal while a portaled popper layer is open — the
first outside click closes just the popper, the next one closes the
modal.

* docs(skills): de-duplicate and correct agent docs; canonical styling tokens; mirror sim-sandbox rule

* docs(skills): broaden boundary-raw-fetch scope note, sync cursor rule mirrors

* fix(emcn): harden modal popper guard and exempt caret-anchored dropdowns from body scroll

Adversarial review follow-ups to the ChipModal scroll + dismissal fixes:

- Popper guard now requires data-state="open" inside the popper wrapper,
  so a dropdown that is merely animating closed no longer swallows the
  next outside click on the modal (DropdownMenu has an exit animation
  that keeps its wrapper mounted briefly)
- Port the same guard to SModalContent for consistency
- custom-tool-modal: opt its body out of the chrome scroll container
  (flex-none + overflow-visible); the caret-anchored EnvVar/Tag
  autocomplete dropdowns are absolute-positioned inside the body and
  must spill past its bounds rather than clip against a scroll boundary

* refactor(billing): drop unnecessary useCallback wrappers from event handlers

* fix(data-drains): complete chip migration of destination forms and fill missing placeholders

Finishes the in-flight FormField → ChipModalField conversion for all
destination form specs and adds the placeholders that several inputs
never had (S3 bucket/region/access keys, Azure account key, Datadog API
key, webhook signing secret/bearer token) — the cause of the New Data
Drain modal showing placeholder-less inputs inconsistently.

* fix(emcn): broaden modal popper guard to onInteractOutside

Covers the focusOutside dismissal path too: when a popper's focus scope
unwinds on close, the transient focus shift could still dismiss the
modal (and simultaneous body pointer-events lock teardown could freeze
the page). Same data-state="open" scoping as the pointer guard.

* fix(emcn): harden modal outside interactions

* update audit mock

---------

Co-authored-by: andres <k62hc5kjst@privaterelay.appleid.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: waleed <waleed@simstudio.ai>
2026-06-06 11:39:48 -07:00
Waleed 2ccd9a3ac5 feat(connectors): add 7 knowledge base connectors (Google Forms, Typeform, Azure DevOps, YouTube, JSM, S3, Sentry) (#4880)
* feat(connectors): add 7 knowledge base connectors (Google Forms, Typeform, Azure DevOps, YouTube, JSM, S3, Sentry)

* fix(connectors): tighten listingCapped semantics per review (WIQL cap, batch omissions, cap-vs-exhaustion)

* fix(connectors): google-forms listingCapped must fire on slice regardless of hitLimit (404-null-filter gap)

* fix(connectors): s3 streaming size cap for chunked responses without content-length

* fix(connectors): ado byte-exact file content fetch, google-forms hash-poisoning on listing failure

* fix(connectors): ado auth-failure deletion guard, jsm last-page slice flag, google-forms response cap in hash

* fix(connectors): shared streaming size-cap reader for ado file hydration (promote from s3)

* fix(knowledge): flag incomplete listings at engine level when pagination is truncated

* fix(connectors): ado flags listing incomplete when a non-empty repo has no resolvable branch

* fix(knowledge): engine truncation flag is an absolute deletion block (fullSync cannot override); s3 byte-exact size fallback; ado tsdoc accuracy

* improvement(knowledge): extract shouldReconcileDeletions gate as tested pure function, tighten engine comments

* test(connectors): mapTags coverage for the 7 new connectors

* fix(connectors): ado probes past the wiql 20k cap before flagging; document custom-wiql full-listing behavior

* fix(connectors): ado flags partial repo trees when items listing emits a continuation token

* fix(connectors): ado discards foreign-phase cursors; google-forms scans all response pages for change detection

* fix(connectors): audit fixes across new connectors

- registry: register x connector (was dead code, never wired in)
- google-docs/google-drive/google-forms: gate deletion reconciliation on
  Drive incompleteSearch; google-docs also now sets listingCapped on its
  maxDocs cap path
- jsm: add read:jira-user scope so reporter resolves on requests
- gong: only set listingCapped on genuine truncation, not exact-cap
  source exhaustion
- gitlab: issues phase switched to keyset pagination (removes ~50k
  offset ceiling), matching the repo-tree phase
- grain: parallelize recording + transcript fetch in getDocument
- ashby: document updatedAt-based content-hash limitation for
  notes/feedback change detection
- tests: mapTags coverage for x, granola, greenhouse, fathom, rootly
2026-06-04 14:49:41 -07:00
WaleedandCursor 3ed8615b5d chore(cleanup): react-doctor dead code elimination, landing + docs overhaul, component modernization (#4544)
* fix(react-doctor): remove unused export types, useEffect clearTimeout missing, a11y fixes

* fix(react-doctor): strip unused export types from contracts, copilot, stores, and components

Remove export keyword from type/interface declarations confirmed to have zero importers
across lib/api/contracts/tools/aws/, lib/api/contracts/*.ts, lib/copilot/generated/,
stores/workflows/workflow/types.ts, ee/access-control, ee/data-retention, lib/logs/types.ts,
and app/workspace component files. TypeScript and API validation both pass clean.

Reduces unused-types count from 394 → 181 and fully eliminates the ✗ critical
dead-code categories (exports, types, files now show as ⚠ warnings not ✗ errors).

* docs improvements

* fix(react-doctor): delete 50 unused files (dead barrels, unreachable components, stale utilities)

Remove confirmed-unused barrel index.ts files across stores/, connectors/, executor/,
lib/, and app/workspace/ that had zero importers. Also delete unreachable components
(chat-history-skeleton, trace-spans, logs-list, template-profile, enterprise landing
sections), stale utilities (buffered-stream, blob-to-data-url, queued-workflow-execution,
compute-edit-sequence), and obsolete generated/contract files. TypeScript passes clean.

* remove dead code

* cleanup

* more

* fix(blog): restore DiffControlsDemo for v0-5 blog post

* fix: restore ContactButton for enterprise blog post, export WIKIPEDIA_PAGE_CONTENT_OUTPUT_PROPERTIES

* fix(react-doc): restore stripped exports and remove server-only dependency

* chore(deps): update lockfile after removing server-only

* added back some exports

* docs

* more

* fix type issues

* tc

* fix docs search route

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(tag-dropdown): add missing isEqual import from es-toolkit

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 12:00:05 -07:00
Waleed d517415c77 chore(docs): upgrade fumadocs to latest minor versions (#4462)
* chore(docs): upgrade fumadocs to latest minor versions

- fumadocs-core: 16.6.7 -> 16.8.5
- fumadocs-ui: 16.6.7 -> 16.8.5
- fumadocs-mdx: 14.2.8 -> 14.3.2
- fumadocs-openapi: 10.3.13 -> 10.8.1
- migrate deprecated sidebar.tabs to top-level tabs prop
- fix pre-existing typo (slots.paremeters) surfaced by stricter openapi types

* fix(docs): revert sidebar.tabs migration to keep deploy compat

The top-level tabs prop is only on fumadocs-ui 16.7+; deploy env was
still resolving an older type and failing typecheck. sidebar.tabs is
deprecated but still functional — keep it for now.
2026-05-05 17:54:18 -07:00
Waleed 9f41736d50 fix(misc): remove duplicate docs page, update clopus 4.7 (#4200)
* fix(misc): remove duplicate docs page, update clopus 4.7

* fix(docs): consolidate duplicate docs and fix SDK API signatures

- Remove duplicate custom-tools page (custom-tools/index.mdx → tools/custom-tools.mdx is canonical)
- Remove comparison table from custom-tools per product preference
- Fix permissions inconsistency: delete now requires Admin across all docs
- Consolidate sdks/ into api-reference/ (sdks/ directory deleted)
- Fix Python SDK docs: correct param is `input`, not `input_data`
- Fix TypeScript SDK docs: correct signature is executeWorkflow(id, input, options) not options-object form
- Add FAQ sections to both SDK reference pages

* fix(docs): update SDKs card links from /sdks to /api-reference

* fix(docs): update /sdks references to /api-reference in llms.txt files
2026-04-16 12:33:25 -07:00
WaleedandTheodore Li 147ac89672 feat(docs): fill documentation gaps across platform features (#4110)
* feat(docs): fill documentation gaps across platform features

* fix(docs): address PR review comments on chat OTP cookies and MCP env var placeholders

* fix(docs): replace smart quotes with straight quotes in JSX attributes

* update(docs): update mcp, custom tools, and variables docs

* Fix grammar

* mothership docs, tags, connectors, api, chat deploy, etc

* more info

* more

* feat(docs): auto-generate per-provider trigger documentation

Extends scripts/generate-docs.ts to produce one MDX page per trigger
provider (39 pages) in apps/docs/content/docs/en/triggers/. The 5
hand-written pages (index, start, schedule, webhook, rss) are never
touched.

Key additions to the generation script:
- resolveConstVariable() resolves module-level const spreads so
  providers like Vercel that build outputs from const variables (not
  just functions) are fully documented
- resolveTriggerBuilderFunction() extended to expand variable spreads
  (...varName) in addition to function-call spreads (...fn())
- groupTriggersByProvider() deduplicates v1/v2 trigger variants by
  name, keeping the highest-versioned one per provider
- writeIconMapping() adds bare-name aliases for versioned block types
  (github_v2 → github, fireflies_v2 → fireflies, etc.) so
  BlockInfoCard resolves icons for all 39 trigger providers
- extractTriggerConfigFields() filters readOnly display blocks (webhook
  URL displays, sample payloads, curl examples) from config tables

Each generated page includes: BlockInfoCard with correct icon/color,
trigger count, polling note where applicable, Configuration table, and
Output table for every trigger. No "Type:" lines.

* refactor(docs): align trigger docs structure with tools docs

- Use ### `trigger_id` headings (matching ### `tool_id` in tools docs)
- Wrap all trigger sections under a ## Triggers header
- Rename Configuration/Output to #### level (matching #### Input/Output)
- Use Parameter column header to match tools docs table style
- Map UI widget types to semantic types: short-input/long-input/dropdown
  → string, switch → boolean, slider → number, oauth-input → string

* refactor(docs): use human-readable names for trigger section headings

Trigger IDs are internal identifiers; users scan by name. Switch from
### `trigger_id` to ### Trigger Name for cleaner sidebar navigation
and better readability.

* fix(docs): resolve subBlock builder functions for all trigger Config sections

Extends generate-docs.ts to parse subBlock builder functions so all 15
providers previously missing Configuration sections now generate them.

Handles three patterns:
- `buildTriggerSubBlocks({extraFields: buildX(...)})` — extracts extra
  fields from the call site and resolves them from the provider's utils.ts
- `return [...]` — direct array return (Attio, Confluence, etc.)
- `blocks.push(...)` — imperative push pattern (Linear, Ashby)

Also resolves const-reference field IDs (SCREAMING_CASE) by searching
the webhook provider constants cache, fixing Gong's `gongJwtPublicKeyPem`
field which was previously unresolvable. Adds title-as-description fallback
for OAuth credential fields that have no explicit description.

* fix(docs): correctly destructure nested implicit-object trigger outputs

Fixes a parser bug where output fields with no top-level `type` key but
child fields each having their own `type`/`description` were incorrectly
parsed. The `type:` and `description:` regex matches were not
depth-aware, so values from nested children bled into the parent field.

Changes:
- Add `isAtDepthZero()` helper for brace-depth-aware regex matching
- Fix `parseFieldContent` to only match `type:` at brace depth 0
- Fix `extractDescription` to only match `description:` at brace depth 0
- Add implicit-object fallback: when no top-level `type` exists but child
  fields have their own types, treat as `object` with `properties`
- Regenerate all affected trigger docs (Cal.com payload, Linear data,
  Jira issue.fields, Ashby application, Greenhouse candidate, etc.)

* chore(docs): update static trigger and start page images

* feat(providers): add claude-opus-4-7 model with adaptive thinking support

* Add workflow version screenshots

* Add function block screenshots

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-16 11:51:49 -07:00
Emir Karabeg 5274efd8f9 improvement(seo): optimize sitemaps, robots.txt, and core web vitals across sim and docs (#4170)
* improvement(seo): optimize sitemaps and robots.txt across sim and docs

- Add missing pages to sim sitemap: blog author pages, academy catalog and course pages
- Fix 6x duplicate URL bug in docs sitemap by deduplicating with source.getLanguages()
- Convert docs sitemap from route handler to Next.js metadata convention with native hreflang
- Add x-default hreflang alternate for docs multi-language pages
- Remove changeFrequency and priority fields (Google ignores both)
- Fix inaccurate lastModified timestamps — derive from real content dates, omit when unknown
- Consolidate 20+ redundant per-bot robots rules into single wildcard entry
- Add /form/ and /credential-account/ to sim robots disallow list
- Reference image sitemap in sim robots.txt
- Remove deprecated host directive from sim robots
- Move disallow rules before allow in docs robots for crawler compatibility
- Extract hardcoded docs baseUrl to env variable with production fallback

* fix(seo): remove homepage new Date(), guard latestModelDate empty array

* improvement(seo): consolidate DOCS_BASE_URL, optimize core web vitals

Extract hardcoded https://docs.sim.ai into shared DOCS_BASE_URL constant
in lib/urls.ts and replace all 20+ instances across layouts, metadata,
structured data, LLM manifest, sitemap, and robots files. Remove
OneDollarStats analytics script and tighten CSP for improved core web vitals.

* fix: removed onedollarstats from bun lock

* fix(seo): guard per-provider Math.max, consolidate docs robots to single wildcard
2026-04-15 12:13:30 -07:00
bc31710c1c improvement(landing): rebrand to AI workspace, add auth modal, harden PostHog tracking (#4116)
* improvement: seo, geo, signup, posthog

* fix(landing): address PR review issues and convention violations

- Fix auth modal race condition: show loading state instead of redirecting when provider status hasn't loaded yet
- Fix auth modal HTTP error caching: reject non-200 responses so they aren't permanently cached
- Replace <img> with next/image <Image> in auth modal
- Use cn() instead of template literal class concatenation in hero, footer-cta
- Remove commented-out dead code in footer, landing, sitemap
- Remove unused arrow property from FooterItem interface
- Convert relative imports to absolute in integrations/[slug]/page
- Remove no-op sanitizedName variable in signup form
- Remove unnecessary async from llms-full.txt route
- Remove extraneous non-TSDoc comment in auth modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(landing): apply linter formatting fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(landing): second pass — fix remaining code quality issues

- auth-modal: add @sim/logger, log social sign-in errors instead of swallowing silently
- auth-modal: extract duplicated social button classes into SOCIAL_BTN constant
- auth-modal: remove unused isProduction from ProviderStatus interface
- auth-modal: memoize getBrandConfig() call
- footer: remove stale arrow destructuring left after interface cleanup, use cn() throughout
- footer-cta: replace inline styles on submit button with Tailwind classes via cn()
- footer-cta: replace caretColor inline style with caret-white utility
- templates: fix incorrect section value 'landing_preview' → 'templates' for PostHog tracking
- events: add 'templates' to landing_cta_clicked section union
- integrations: replace "canvas" with "workflow builder" per constitution rules
- llms-full: replace "canvas" terminology with "visual builder"/"workflow builder"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(landing): point Mothership and Workflows footer links to docs root

These docs pages don't exist yet — link to docs.sim.ai until they are published.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(landing): complete rebrand in blog fallback description

Remove "workflows" from the non-tagged blog meta description to
align with the AI workspace rebrand across the rest of the PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(landing): strip isProduction from provider response and handle late-resolve redirect

- Destructure only githubAvailable/googleAvailable from getOAuthProviderStatus
  so isProduction is not leaked to unauthenticated callers.
- Add useEffect to redirect away from the modal if provider status resolves
  after the modal is already open and no social providers are configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(landing): align auth modal with login/signup page logic

- Add SSO button when NEXT_PUBLIC_SSO_ENABLED is set
- Gate "Continue with email" behind EMAIL_PASSWORD_SIGNUP_ENABLED
- Expose registrationDisabled from /api/auth/providers and hide
  the "Sign up" toggle when registration is disabled
- Simplify skip-modal logic: redirect to full page when no social
  providers or SSO are available (hasModalContent)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(landing): force login view when registration is disabled

When a CTA passes defaultView='signup' but registration is disabled,
the modal now opens in login mode instead of showing "Create free
account" with social buttons that would fail on the backend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* fix(landing): correct signup view when registrationDisabled loads late

When the user opens the modal before providerStatus resolves and
registrationDisabled comes back true, the view was stuck on 'signup'.
Now the late-resolve useEffect also forces the view to 'login'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(landing): add click tracking to integration page CTAs

Create IntegrationCtaButton client component that wraps AuthModal
and fires trackLandingCta on click, matching the pattern used by
every other landing section CTA.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(landing): prevent mobile auth modal from unmounting on open

Remove setMobileMenuOpen(false) from mobile AuthModal button onClick
handlers. Closing the mobile menu unmounts the AuthModal before it
can open. The modal overlay or page redirect makes the menu
irrelevant without needing to explicitly close it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 20:37:18 -07:00
Emir Karabeg ad100fa871 improvement(docs): ui/ux cleanup (#4016)
* improvement(landing, blog): SEO and GEO optimization

* improvement(docs): ui/ux cleanup

* chore(blog): remove unused buildBlogJsonLd export and wordCount schema field

* fix(blog): stack related posts vertically on mobile and fill all suggestion slots

- Add flex-col sm:flex-row and matching border classes to related posts
  nav for consistent mobile stacking with the main blog page
- Remove score > 0 filter in getRelatedPosts so it falls back to recent
  posts when there aren't enough tag matches
- Align description text color with main page cards
2026-04-07 11:05:58 -07:00
e2be99263c feat(academy): Sim Academy — interactive partner certification platform (#3824)
* fix(import): dedup workflow name (#3813)

* feat(concurrency): bullmq based concurrency control system (#3605)

* feat(concurrency): bullmq based queueing system

* fix bun lock

* remove manual execs off queues

* address comments

* fix legacy team limits

* cleanup enterprise typing code

* inline child triggers

* fix status check

* address more comments

* optimize reconciler scan

* remove dead code

* add to landing page

* Add load testing framework

* update bullmq

* fix

* fix headless path

---------

Co-authored-by: Theodore Li <teddy@zenobiapay.com>

* fix(linear): add default null for after cursor (#3814)

* fix(knowledge): reject non-alphanumeric file extensions from document names (#3816)

* fix(knowledge): reject non-alphanumeric file extensions from document names

* fix(knowledge): improve error message when extension is non-alphanumeric

* fix(security): SSRF, access control, and info disclosure (#3815)

* fix(security): scope copilot feedback GET endpoint to authenticated user

Add WHERE clause to filter feedback records by the authenticated user's
ID, preventing any authenticated user from reading all users' copilot
interactions, queries, and workflow YAML (IDOR / CWE-639).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(smtp): add SSRF validation and genericize network error messages

Prevent SSRF via user-controlled smtpHost by validating with
validateDatabaseHost before creating the nodemailer transporter.
Collapse distinct network error messages (ECONNREFUSED, ECONNRESET,
ETIMEDOUT) into a single generic message to prevent port-state leakage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): add SSRF validation to SFTP/SSH and access control to workspace invitations

Add `validateDatabaseHost` checks to SFTP and SSH connection utilities to
block connections to private/reserved IPs and localhost, matching the
existing pattern used by all database tools. Add authorization check to
the workspace invitation GET endpoint so only the invitee or a workspace
admin can view invitation details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(smtp): restore SMTP response code handling for post-connection errors

SMTP 4xx/5xx response codes are application-level errors (invalid
recipient, mailbox full, server error) unrelated to the SSRF hardening
goal. Restore response code differentiation and logging to preserve
actionable user-facing error messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): use session email directly instead of extra DB query

Addresses PR review feedback — align with the workspace invitation
route pattern by using session.user.email instead of re-fetching
from the database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* fix(auth): revert lint autofix that broke hasExternalApiCredentials return type

Biome auto-fixed `return auth !== null && auth.startsWith(...)` to
`return auth?.startsWith(...)` which returns `boolean | undefined`,
not `boolean`, causing a TypeScript build failure.

* fix(smtp): pin resolved IP to prevent DNS rebinding (TOCTOU)

Use the pre-resolved IP from validateDatabaseHost instead of the
original hostname when creating the nodemailer transporter. Set
servername to the original hostname to preserve TLS SNI validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(security): extract createPinnedLookup helper for DNS rebinding prevention

Extract reusable createPinnedLookup from secureFetchWithPinnedIP so
non-HTTP transports (SSH, SFTP, IMAP) can pin resolved IPs at the
socket level. SMTP route uses host+servername pinning instead since
nodemailer doesn't reliably pass lookup to both secure/plaintext paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): pin IMAP connections to validated resolved IP

Pass the resolved IP from validateDatabaseHost to ImapFlow as host,
with the original hostname as servername for TLS SNI verification.
Closes the DNS TOCTOU rebinding window.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* fix(auth): revert lint autofix on hasExternalApiCredentials return type

Also pin SFTP/SSH connections to validated resolved IP to prevent DNS rebinding.

* fix(security): short-circuit admin check when caller is invitee

Skip the hasWorkspaceAdminAccess DB query when the caller is already
the invitee, avoiding an unnecessary round-trip. Aligns with the org
invitation route pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(worker): dockerfile + helm updates (#3818)

* fix(worker): dockerfile + helm updates

* address comments

* update dockerfile (#3819)

* fix dockerfile

* fix(security): pentest remediation — condition escaping, SSRF hardening, ReDoS protection (#3820)

* fix(executor): escape newline characters in condition expression strings

Unescaped newline/carriage-return characters in resolved string values
cause unterminated string literals in generated JS, crashing condition
evaluation with a SyntaxError.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): prevent ReDoS in guardrails regex validation

Add safe-regex2 to reject catastrophic backtracking patterns before
execution and cap input length at 10k characters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): SSRF localhost hardening and regex DoS protection

Block localhost/loopback URLs in hosted environments using isHosted flag
instead of allowHttp. Add safe-regex2 validation and input length limits
to regex guardrails to prevent catastrophic backtracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): validate regex syntax before safety check

Move new RegExp() before safe() so invalid patterns get a proper syntax
error instead of a misleading "catastrophic backtracking" message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): address PR review feedback

- Hoist isLocalhost && isHosted guard to single early-return before
  protocol checks, removing redundant duplicate block
- Move regex syntax validation (new RegExp) before safe-regex2 check
  so invalid patterns get proper syntax error instead of misleading
  "catastrophic backtracking" message

* fix(security): remove input length cap from regex validation

The 10k character cap would block legitimate guardrail checks on long
LLM outputs. Input length doesn't affect ReDoS risk — the safe-regex2
pattern check already prevents catastrophic backtracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tests): mock isHosted in input-validation and function-execute tests

Tests that assert self-hosted localhost behavior need isHosted=false,
which is not guaranteed in CI where NEXT_PUBLIC_APP_URL is set to the
hosted domain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* improvement(worker): configuration defaults (#3821)

* improvement(worker): configuration defaults

* update readmes

* realtime curl import

* improvement(tour): remove auto-start, only trigger on explicit user action (#3823)

* fix(mcp): use correct modal for creating workflow MCP servers in deploy (#3822)

* fix(mcp): use correct modal for creating workflow MCP servers in deploy

* fix(mcp): show workflows field during loading and when empty

* mock course

* fix(db): use bigint for token counter columns in user_stats (#3755)

* mock course

* updates

* updated X handle for emir

* cleanup: audit and clean academy implementation

* fix(academy): add label to ValidationRule, fix quiz gating, simplify getRuleMessage

* cleanup: remove unnecessary comments across academy files

* refactor(academy): simplify abstractions and fix perf issues

* perf(academy): convert course detail page to server component with client island

* fix(academy): null-safe canAdvance, render exercise instructions, remove stale comments

* fix(academy): remove orphaned migration, fix getCourseById, clean up comments

- Delete 0181_academy_certificate.sql (orphaned duplicate not in journal)
- Add getCourseById() to content/index.ts; use it in certificates API
  (was using getCourse which searches by slug, not stable id)
- Remove JSX comments from catalog page
- Remove redundant `passed` recomputation in LessonQuiz

* chore(db): regenerate academy_certificate migration with drizzle-kit

* chore: include blog mdx and components changes

* fix(blog): correct cn import path

* fix(academy): constrain progress bar to max-w-3xl with proper padding

* feat(academy): show back-to-course button on first lesson

* fix(academy): force dark theme on all /academy routes

* content(academy): rewrite sim-foundations course with full 6-module curriculum

* fix(academy): correct edge handles, quiz explanation, and starter mock outputs

- Fix Exercise 2 initial edge handles: 'starter-1-source'/'agent-1-target' → 'source'/'target' (React Flow actual IDs)
- Fix M1-L4 Q4 quiz explanation: remove non-existent Ctrl/Cmd+D and Alt+drag shortcuts
- Add starter mock output to all exercises so run animation shows feedback on the first block

* refine(academy): fix inaccurate content and improve exercise clarity

- Fix Exercise 3: replace hardcoded <agent-1.content> (invalid UUID-based ref) with reference picker instructions
- Fix M4 Quiz Q5: Loop block (subflow container) is correct answer, not the Workflow block
- Fix M4 Quiz Q4: clarify fan-out vs Parallel block distinction in explanation
- Fix M4-L2 video description: accurately describe Loop and Parallel subflow blocks
- Fix M2 Quiz Q3: make response format question conceptual rather than syntax-specific
- Improve Exercise 4 branching instructions: clarify top=true / bottom=false output handles
- Improve Final Project instructions: step-by-step numbered flow

* fix(academy): remove double border on quiz question cards

* fix(academy): single scroll container on lesson pages — remove nested flex scroll

* fix(academy): remove min-h-screen from root layout — fixes double scrollbar on lesson pages

* fix(academy): use fixed inset-0 on lesson page to eliminate document-level scrollbar

* fix(academy): replace sr-only radio/checkbox inputs with buttons to prevent scroll-on-focus; restore layout min-h-screen

* improvement(academy): polish, security hardening, and certificate claim UI

- Replace raw localStorage with BrowserStorage utility in local-progress
- Pre-compute slug/id Maps in content/index for O(1) course lookups
- Move blockMap construction into edge_exists branch only in validation
- Extract navBtnClass constant and MetaRow/formatDate helpers in UI
- Add rate limiting, server-side completion verification, audit logging, and nanoid cert numbers to certificate issuance endpoint
- Add useIssueCertificate mutation hook with completedLessonIds
- Wire certificate claim UI into CourseProgress: sign-in prompt, claim button with loading state, and post-issuance view with link to certificate page
- Fix lesson page scroll container and quiz scroll-on-focus bug

* fix(academy): validate condition branch handles in edge_exists rules

- Add sourceHandle field to edge_exists ValidationRule type
- Check sourceHandle in validation.ts when specified
- Require both condition-if and condition-else branches to be connected in the branching and final project exercises

* fix(academy): address PR review — isHosted regression, stuck isExecuting, revoked cert 500, certificate SSR

- Restore env-var-based isHosted check (was hardcoded true, breaking self-hosted deployments)
- Fix isExecuting stuck at true when mock run fails validation — set isMockRunningRef immediately and reset both flags on early exit
- Fix revoked/expired certificate causing 500 — any existing record (not just active) now returns 409 instead of falling through to INSERT
- Convert certificate verification page from client component to server component — direct DB fetch, notFound() on missing cert, generateMetadata for SEO/social previews

* fix(auth): restore hybrid.ts from staging to fix CI type error

* fix(academy): mark video lessons complete on visit and fix sign-in path

* fix(academy): replace useEffect+setState with lazy useState initializer in CourseProgress

* fix(academy): reset exerciseComplete on lesson navigation, remove unused useAcademyCertificate hook

* fix(academy): useState for slug-change reset, cache() for cert page, handleMockRunRef for stale closure

* fix(academy): replace shadcn theme vars with explicit hex in LessonVideo fallback

* fix(academy): reset completedRef on exercise change, conditional verified badge, multi-select empty guard

* fix(academy): type safety fixes — null metadata fallbacks, returning() guard, exhaustive union, empty catch

* fix(academy): reset ExerciseView completed banner on nav; fix CourseProgress hydration mismatch

* fix(lightbox): guard effect body with isOpen to prevent spurious overflow reset

* fix(academy): reset LessonQuiz state on lesson change to prevent stale answers persisting

* fix(academy): course not-found metadata title; try-finally guard in mock run loop

* fix(academy): type safety, cert persistence, regex guard, mixed-lesson video, shorts support

- Derive AcademyCertificate from db $inferSelect to prevent schema drift
- Add useCourseCertificate query hook; GET /api/academy/certificates now accepts courseId for authenticated lookup
- Use useCourseCertificate in CourseProgress so certificate state survives page refresh
- Guard new RegExp(valuePattern) in validation.ts with try/catch; log warn on invalid pattern
- Add logger.warn for custom validation rules so content authors are alerted
- Add YouTube Shorts URL support to LessonVideo (youtube.com/shorts/VIDEO_ID)
- Fix mixed-lesson video gap: render videoUrl above quiz when mixed has quiz but no exercise
- Add academy-scoped not-found.tsx with link back to /academy

* fix(academy): reset hintIndex when exercise changes

* chore: remove ban-spam-accounts script (wrong branch)

* fix(academy): enforce availableBlocks in toolbar; fix mixed exercise+quiz rendering

- Add useSandboxBlockConstraints context; SandboxCanvasProvider provides exerciseConfig.availableBlocks so the toolbar only shows permitted block types. Empty array hides all blocks (configure-only exercises); non-null array restricts to listed types; triggers always hidden in sandbox.
- Fix mixed lesson with both exerciseConfig and quizConfig: exercise renders first, quiz reveals after exercise completes (sequential pedagogy). canAdvance now requires both exerciseComplete && quizComplete when both are present.

* chore(academy): remove extraneous inline comments

* fix(academy): blank mixed lesson, quiz canAdvance flag, empty-array valueNotEmpty

* prep for merge

* chore(db): regenerate academy certificate migration after staging merge

* fix(academy): disable auto-connect in sandbox mode

* fix(academy): render video in mixed lesson with no exercise or quiz

* fix(academy): mark mixed video-only lessons complete; handle cert insert race

* fix(canvas): add sandbox and embedded to nodes useMemo deps

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
2026-03-28 12:46:04 -07:00
Adithya Krishna 5aa0b4d5d4 fix: emcn component library design engineering polish (#3672)
* chore: fix conflicts

* chore: fix conflicts

* chore: pause marquee

* chore: fix conflicts

* chore: fix conflicts

* chore: address review comments

* chore: fix conflicts

* chore: other bug fixes

* chore: fix conflicts

* chore: fix radius

* chore: fix review changes

* chore: fix conflicts

* chore: revert other

* chore: fix conflicts

* chore: fix review changes

* chore: fix conflicts

* chore: fix review changes

* chore: fix button state

* chore: fix button state

* chore: fix review changes

* chore: fix lint

* chore: fix conflicts

* chore: add metadata

* chore: fix things

* chore: fix overwritten states

* chore: fix warnings

* chore: fix button state

* chore: fix review changes

* chore: fix review changes

* chore: fix hover state

* chore: fix popover

* chore: fix review changes

* chore: fix review changes
2026-03-26 11:40:23 -07:00
Siddharth GanesanandEmir Karabeg 8fa4f3fdbb fix(mothership): thinking and subagent text (#3613)
* Thinking v0

* Change

* Fix

* improvement(ui/ux): mothership chat experience

* user input animation

* improvement(landing): desktop complete

* auth and 404

* mobile friendliness and home templates

* improvement(home): templates

* fix: feature flags

* address comments

---------

Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
2026-03-17 07:55:50 -07:00
5b9f0d73c2 feat(mothership): mothership (#3411)
* Fix lint

* improvement(sidebar): loading

* fix(sidebar): use client-generated UUIDs for stable optimistic updates (#3439)

* fix(sidebar): use client-generated UUIDs for stable optimistic updates

* fix(folders): use zod schema validation for folder create API

Replace inline UUID regex with zod schema validation for consistency
with other API routes. Update test expectations accordingly.

* fix(sidebar): add client UUID to single workflow duplicate hook

The useDuplicateWorkflow hook was missing newId: crypto.randomUUID(),
causing the same temp-ID-swap issue for single workflow duplication
from the context menu.

* fix(folders): avoid unnecessary Set re-creation in replaceOptimisticEntry

Only create new expandedFolders/selectedFolders Sets when tempId
differs from data.id. In the common happy path (client-generated UUIDs),
this avoids unnecessary Zustand state reference changes and re-renders.

* Mothership block logs

* Fix mothership block logs

* improvement(knowledge): make connector-synced document chunks readonly (#3440)

* improvement(knowledge): make connector-synced document chunks readonly

* fix(knowledge): enforce connector chunk readonly on server side

* fix(knowledge): disable toggle and delete actions for connector-synced chunks

* Job exeuction logs

* Job logs

* fix(connectors): remove unverifiable requiredScopes for Linear connector

* fix(connectors): remove legacy requiredScopes from Jira and Confluence connectors

Jira and Confluence OAuth tokens don't return legacy scope names like
read:jira-work or read:confluence-content.all, causing the 'Update access'
banner to always appear. Set requiredScopes to empty array like Linear.

* feat(tasks): add rename to task context menu (#3442)

* Revert "fix(connectors): remove legacy requiredScopes from Jira and Confluence connectors"

This reverts commit a0be3ff414.

* fix(connectors): restore Linear connector requiredScopes

Linear OAuth does return scopes in the token response. The previous
fix of emptying requiredScopes was based on an incorrect assumption.
Restoring requiredScopes: ['read'] as it should work correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(knowledge): pass workspaceId to useOAuthCredentials in connector card

The ConnectorCard was calling useOAuthCredentials(providerId) without
a workspaceId, causing the credentials API to return an empty array.
This meant the credential lookup always failed, getMissingRequiredScopes
received undefined, and the "Update access" banner always appeared.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix oauth link callback from mothership task

* feat(connectors): add Fireflies connector and API key auth support (#3448)

* feat(connectors): add Fireflies connector and API key auth support

Extend the connector system to support both OAuth and API key authentication
via a discriminated union (`ConnectorAuthConfig`). Add Fireflies as the first
API key connector, syncing meeting transcripts via the Fireflies GraphQL API.

Schema changes:
- Make `credentialId` nullable (null for API key connectors)
- Add `encryptedApiKey` column (AES-256-GCM encrypted, null for OAuth)

This eliminates the `'_apikey_'` sentinel and inline `sourceConfig._encryptedApiKey`
patterns, giving each auth mode its own clean column.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(fireflies): allow 0 for maxTranscripts (means unlimited)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Add context

* fix(fireflies): correct types from live API validation (#3450)

* fix(fireflies): correct types from live API validation

- speakers.id is number, not string (API returns 0, 1, 2...)
- summary.action_items is a single string, not string[]
- Update formatTranscriptContent to handle action_items as string

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(fireflies): correct tool types from live API validation

- FirefliesSpeaker.id: string -> number
- FirefliesSentence.speaker_id: string -> number
- FirefliesSpeakerAnalytics.speaker_id: string -> number
- FirefliesSummary.action_items: string[] -> string
- FirefliesSummary.outline: string[] -> string
- FirefliesSummary.shorthand_bullet: string[] -> string
- FirefliesSummary.bullet_gist: string[] -> string
- FirefliesSummary.topics_discussed: string[] -> string

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(knowledge): add connector tools and expand document metadata (#3452)

* feat(knowledge): add connector tools and expand document metadata

* fix(knowledge): address PR review feedback on new tools

* fix(knowledge): remove unused params from get_document transform

* refactor, improvement

* fix: correct knowledge block canonical pair pattern and subblock migration

- Rename manualDocumentId to documentId (advanced subblock ID should match
  canonicalParamId, consistent with airtable/gmail patterns)
- Fix documentSelector.dependsOn to reference knowledgeBaseSelector (basic
  depends on basic, not advanced)
- Remove unnecessary documentId migration (ID unchanged from main)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* fix: resolve post-merge test and lint failures

- airtable: sync tableSelector condition with tableId (add getSchema)
- backfillCanonicalModes test: add documentId mode to prevent false backfill
- schedule PUT test: use invalid action string now that disable is valid
- schedule execute tests: add ne mock, sourceType field, use
  mockReturnValueOnce for two db.update calls
- knowledge tools: fix biome formatting (single-line arrow functions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fixes

* Fixes

* Clean vfs

* Fix

* Fix lint

* fix(connectors): add rate limiting, concurrency controls, and bug fixes (#3457)

* fix(connectors): add rate limiting, concurrency controls, and bug fixes across knowledge connectors

- Add Retry-After header support to fetchWithRetry for all 18 connectors
- Batch concurrent API calls (concurrency 5) in Dropbox, Google Docs, Google Drive, OneDrive, SharePoint
- Batch concurrent API calls (concurrency 3) in Notion to match 3 req/s limit
- Cache GitHub tree in syncContext to avoid re-fetching on every pagination page
- Batch GitHub blob fetches with concurrency 5
- Fix GitHub base64 decoding: atob() → Buffer.from() for UTF-8 safety
- Fix HubSpot OAuth scope: 'tickets' → 'crm.objects.tickets.read' (v3 API)
- Fix HubSpot syncContext key: totalFetched → totalDocsFetched for consistency
- Add jitter to nextSyncAt (10% of interval, capped at 5min) to prevent thundering herd
- Fix Date consistency in connector DELETE route

* fix(connectors): address PR review feedback on retry and SharePoint batching

- Remove 120s cap on Retry-After — pass all values through to retry loop
- Add maxDelayMs guard: if Retry-After exceeds maxDelayMs, throw immediately
  instead of hammering with shorter intervals (addresses validate timeout concern)
- Add early exit in SharePoint batch loop when maxFiles limit is reached
  to avoid unnecessary API calls

* fix(connectors): cap Retry-After at maxDelayMs instead of aborting

Match Google Cloud SDK behavior: when Retry-After exceeds maxDelayMs,
cap the wait to maxDelayMs and log a warning, rather than throwing
immediately. This ensures retries are bounded in duration while still
respecting server guidance within the configured limit.

* fix(connectors): add early-exit guard to Dropbox, Google Docs, OneDrive batch loops

Match the SharePoint fix — skip remaining batches once maxFiles limit
is reached to avoid unnecessary API calls.

* improvement(turbo): align turborepo config with best practices (#3458)

* improvement(turbo): align turborepo config with best practices

* fix(turbo): address PR review feedback

* fix(turbo): add lint:check task for read-only lint+format CI checks

lint:check previously delegated to format:check which only checked
formatting. Now it runs biome check (no --write) which enforces both
lint rules and formatting without mutating files.

* upgrade turbo

* improvement(perf): apply react and js performance optimizations across codebase (#3459)

* improvement(perf): apply react and js performance optimizations across codebase

- Parallelize independent DB queries with Promise.all in API routes
- Defer PostHog and OneDollarStats via dynamic import() to reduce bundle size
- Use functional setState in countdown timers to prevent stale closures
- Replace O(n*m) .filter().find() with Set-based O(n) lookups in undo-redo
- Use .toSorted() instead of .sort() for immutable state operations
- Use lazy initializers for useState(new Set()) across 20 components
- Remove useMemo wrapping trivially cheap expressions (typeof, ternary, template strings)
- Add passive: true to scroll event listener

* fix(perf): address PR review feedback

- Extract IIFE Set patterns to named consts for readability in use-undo-redo
- Hoist Set construction above loops in BATCH_UPDATE_PARENT cases
- Add .catch() error handler to PostHog dynamic import
- Convert session-provider posthog import to dynamic import() to complete bundle split

* fix(analytics): add .catch() to onedollarstats dynamic import

* improvement(resource): tables, files

* improvement(resources): all outer page structure complete

* refactor(queries): comprehensive TanStack Query best practices audit (#3460)

* refactor: comprehensive TanStack Query best practices audit and migration

- Add AbortSignal forwarding to all 41 queryFn implementations for proper request cancellation
- Migrate manual fetch patterns to useMutation hooks (useResetPassword, useRedeemReferralCode, usePurchaseCredits, useImportWorkflow, useOpenBillingPortal, useAllowedMcpDomains)
- Migrate standalone hooks to TanStack Query (use-next-available-slot, use-mcp-server-test, use-webhook-management, use-referral-attribution)
- Fix query key factories: add missing `all` keys, replace inline keys with factory methods
- Fix optimistic mutations: use onSettled instead of onSuccess for cache reconciliation
- Replace overly broad cache invalidations with targeted key invalidation
- Remove keepPreviousData from static-key queries where it provides no benefit
- Add staleTime to queries missing explicit cache duration
- Fix `any` type in UpdateSettingParams with proper GeneralSettings typing
- Remove dead code: loadingWebhooks/checkedWebhooks from subblock store, unused helper functions
- Update settings components (general, debug, referral-code, credit-balance, subscription, mcp) to use mutation state instead of manual useState for loading/error/success

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove unstable mutation object from useCallback deps

openBillingPortal mutation object is not referentially stable,
but .mutate() is stable in TanStack Query v5. Remove from deps
to prevent unnecessary handleBadgeClick recreations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing byWorkflows invalidation to useUpdateTemplate

The onSettled handler was missing the byWorkflows() invalidation
that was dropped during the onSuccess→onSettled migration. Without
this, the deploy modal (useTemplateByWorkflow) would show stale data
after a template update.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add TanStack Query best practices to CLAUDE.md and cursor rules

Add comprehensive React Query best practices covering:
- Hierarchical query key factories with intermediate plural keys
- AbortSignal forwarding in all queryFn implementations
- Targeted cache invalidation over broad .all invalidation
- onSettled for optimistic mutation cache reconciliation
- keepPreviousData only on variable-key queries
- No manual fetch in components rule
- Stable mutation references in useCallback deps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback

- Fix syncedRef regression in use-webhook-management: only set
  syncedRef.current=true when webhook is found, so re-sync works
  after webhook creation (e.g., post-deploy)
- Remove redundant detail(id) invalidation from useUpdateTemplate
  onSettled since onSuccess already populates cache via setQueryData

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address second round of PR review feedback

- Reset syncedRef when blockId changes in use-webhook-management so
  component reuse with a different block syncs the new webhook
- Add response.ok check in postAttribution so non-2xx responses
  throw and trigger TanStack Query retry logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use lists() prefix invalidation in useCreateWorkspaceCredential

Use workspaceCredentialKeys.lists() instead of .list(workspaceId) so
filtered list queries are also invalidated on credential creation,
matching the pattern used by update and delete mutations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address third round of PR review feedback

- Add nullish coalescing fallback for bonusAmount in referral-code
  to prevent rendering "undefined" when server omits the field
- Reset syncedRef when queryEnabled becomes false so webhook data
  re-syncs when the query is re-enabled without component remount

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address fourth round of PR review feedback

- Add AbortSignal to testMcpServerConnection for consistency
- Wrap handleTestConnection in try/catch for mutateAsync error handling
- Replace broad subscriptionKeys.all with targeted users()/usage() invalidation
- Add intermediate users() key to subscription key factory for prefix matching
- Add comment documenting syncedRef null-webhook behavior
- Fix api-keys.ts silent error swallowing on non-ok responses
- Move deployments.ts cache invalidation from onSuccess to onSettled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: achieve full TanStack Query best practices compliance

- Add intermediate plural keys to api-keys, deployments, and schedules
  key factories for prefix-based invalidation support
- Change copilot-keys from refetchQueries to invalidateQueries
- Add signal parameter to organization.ts fetch functions (better-auth
  client does not support AbortSignal, documented accordingly)
- Move useCreateMcpServer invalidation from onSuccess to onSettled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ran lint

* Fix tables row count

* Update mothership to match copilot in logs

* improvement(resource): layout

* fix(knowledge): compute KB tokenCount from documents instead of stale column (#3463)

The knowledge_base.token_count column was initialized to 0 and never
updated. Replace with COALESCE(SUM(document.token_count), 0) in all
read queries, which already JOIN on documents with GROUP BY.

* improvement(resources): layout and items

* feat(knowledge): add v1 knowledge base API, Obsidian/Evernote connectors, and docs (#3465)

* feat(knowledge): add v1 knowledge base API, Obsidian/Evernote connectors, and docs

- Add v1 REST API for knowledge bases (CRUD, document management, vector search)
- Add Obsidian and Evernote knowledge base connectors
- Add file type validation to v1 file and document upload endpoints
- Update OpenAPI spec with knowledge base endpoints and schemas
- Add connectors documentation page
- Apply query hook formatting improvements

* fix(knowledge): address PR review feedback

- Remove validateFileType from v1/files route (general file upload, not document-only)
- Reject tag filters when searching multiple KBs (tag defs are KB-specific)
- Cache tag definitions to avoid duplicate getDocumentTagDefinitions call
- Fix Obsidian connector silent empty results when syncContext is undefined

* improvement(connectors): add syncContext to getDocument, clean up caching

- Update docs to say 20+ connectors
- Add syncContext param to ConnectorConfig.getDocument interface
- Use syncContext in Evernote getDocument to cache tag/notebook maps
- Replace index-based cache check with Map keyed by KB ID in search route

* fix(knowledge): address second round of PR review feedback

- Fix Zod .default('text') overriding tag definition's actual fieldType
- Fix encodeURIComponent breaking multi-level folder paths in Obsidian
- Use 413 instead of 400 for file-too-large in document upload
- Add knowledge-bases to API reference docs navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(knowledge): prevent cross-workspace KB access in search

Filter accessible KBs by matching workspaceId from the request,
preventing users from querying KBs in other workspaces they have
access to but didn't specify.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(knowledge): audit resourceId, SSRF protection, recursion depth limit

- Fix recordAudit using knowledgeBaseId instead of newDocument.id
- Add SSRF validation to Obsidian connector (reject private/loopback URLs)
- Add max recursion depth (20) to listVaultFiles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(obsidian): remove SSRF check that blocks localhost usage

The Obsidian connector is designed to connect to the Local REST API
plugin running on localhost (127.0.0.1:27124). The SSRF check was
incorrectly blocking this primary use case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* improvement(resources): segmented API

* fix(execution): ensure background tasks await post-execution DB status updates (#3466)

The fire-and-forget IIFE in execution-core.ts for post-execution logging could be abandoned when trigger.dev tasks exit, leaving executions permanently stuck in "running" status. Store the promise on LoggingSession so background tasks can optionally await it before returning.

* improvement(resource): sorting and icons

* fix(resource): sorting

* improvement(settings): fix mcp modal, add option to edit JSON and add Sim as an MCP client (#3467)

* improvement(settings): fix mcp modal, add option to edit JSON and add Sim as an MCP client

* added docs link in sidebar

* ack comments

* ack comments

* fixed error msg

* feat(mothership): billing (#3464)

* Billing update

* more billing improvements

* credits UI

* credit purchase safety

* progress

* ui improvements

* fix cancel sub

* fix types

* fix daily refresh for teams

* make max features differentiated

* address bugbot comments

* address greptile comments

* revert isHosted

* address more comments

* fix org refresh bar

* fix ui rounding

* fix minor rounding

* fix upgrade issue for legacy plans

* fix formatPlanName

* fix email dispay names

* fix legacy team reference bugs

* referral bonus in credits

* fix org upgrade bug

* improve logs

* respect toggle for paid users

* fix landing page pro features and usage limit checks

* fixed query and usage

* add unit test

* address more comments

* enterprise guard

* fix limits bug

* pass period start/end for overage

* fix(sidebar): restore drag-and-drop for workflows and folders (#3470)

* fix(sidebar): restore drag-and-drop for workflows and folders

Made-with: Cursor

* update docs, unrelated

* improvement(tables): consolidation

* feat(schedules): add schedule creator modal for standalone jobs

Add modal to create standalone scheduled jobs from the Schedules page.
Includes POST API endpoint, useCreateSchedule mutation hook, and full
modal with schedule type selection, timezone, lifecycle, and live preview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(schedules): add edit support with context menu for standalone jobs

* style(schedules): apply linter formatting

* improvement: tables, favicon

* feat(files): inline file viewer with text editing (#3475)

* feat(files): add inline file viewer with text editing and create file modal

Add file preview/edit functionality to the workspace files page. Text files
(md, json, txt, yaml, etc.) open in an editable textarea with Cmd/Ctrl+S save.
PDFs render in an iframe. New file button creates empty .md files via a modal.
Uses ResourceHeader breadcrumbs and ResourceOptionsBar for save/download/delete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improvement(files): add UX polish, PR review fixes, and context menu

- Add unsaved changes guard modal (matching credentials manager pattern)
- Add delete confirmation modal for both viewer and context menu
- Add save status feedback (Save → Saving... → Saved)
- Add right-click context menu with Open, Download, Delete actions
- Add 50MB file size limit on content update API
- Add storage quota check before content updates
- Add response.ok guard on download to prevent corrupt files
- Add skeleton loading for pending file selection (prevents flicker)
- Fix updateContent in handleSave dependency array

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): propagate save errors and remove redundant sizeDiff

- Remove try/catch in TextEditor.handleSave so errors propagate to
  parent, which correctly shows save failure status
- Remove redundant inner sizeDiff declaration that shadowed outer scope

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): remove unused textareaRef

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): move Cmd+S to parent, add save error feedback, hide save for non-text files

- Move Cmd+S keyboard handler from TextEditor to Files so it goes
  through the parent handleSave with proper status management
- Add 'error' save status with red "Save failed" label that auto-resets
- Only show Save button for text-editable file types (md, txt, json, etc.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improvement(files): add save tooltip, deduplicate text-editable extensions

- Add Tooltip on Save button showing Cmd+S / Ctrl+S shortcut
- Export TEXT_EDITABLE_EXTENSIONS from file-viewer and reuse in files.tsx
  instead of duplicating the list inline

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract isMacPlatform to shared utility

Move isMacPlatform() from global-commands-provider.tsx to
lib/core/utils/platform.ts so it can be reused by files.tsx tooltip
without duplication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(files): deduplicate delete modal, use shared formatFileSize

- Extract DeleteConfirmModal component to eliminate duplicate modal
  markup between viewer and list modes
- Replace local formatFileSize with shared utility from file-utils.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): fix a11y label lint error and remove mutation object from useCallback deps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): add isDirty guard on handleSave, return proper HTTP status codes

Prevents "Saving → Saved" flash when pressing Cmd+S with no changes.
Returns 404 for file-not-found and 402 for quota-exceeded instead of 500.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): reset isDirty/saveStatus on delete and discard, remove deprecated navigator.platform

- Clear isDirty and saveStatus when deleting the currently-viewed file to
  prevent spurious beforeunload prompts
- Reset saveStatus on discard to prevent stale "Save failed" when opening
  another file
- Remove deprecated navigator.platform, userAgent fallback covers all cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): prevent concurrent saves on rapid Cmd+S, add YAML MIME types

- Add saveStatus === 'saving' guard to handleSave to prevent duplicate
  concurrent PUT requests from rapid keyboard shortcuts
- Add yaml/yml MIME type mappings to getMimeTypeFromExtension

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(files): reuse shared extension constants, parallelize cancelQueries

- Replace hand-rolled SUPPORTED_EXTENSIONS with composition from existing
  SUPPORTED_DOCUMENT/AUDIO/VIDEO_EXTENSIONS in validation.ts
- Parallelize sequential cancelQueries calls in delete mutation onMutate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): guard handleCreate against duplicate calls while pending

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): show upload progress on the Upload button, not New file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(files): use ref-based guard for create pending state to avoid stale closure

The uploadFile.isPending check was stale because the mutation object
is excluded from useCallback deps (per codebase convention). Using a
ref ensures the guard works correctly across rapid Enter key presses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* cleanup(files): use shared icon import, remove no-op props, wrap handler in useCallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* improvement: tables, dropdown

* improvement(docs): align sidebar method badges and polish API reference styling (#3484)

* improvement(docs): align sidebar method badges and polish API reference styling

* fix(docs): revert className prop on DocsPage for CI compatibility

* fix(docs): restore oneOf schema for delete rows and use rem units in CSS

* fix(docs): replace :has() selectors with direct className for reliable prod layout

The API docs layout was intermittently narrow in production because CSS
:has(.api-page-header) selectors are unreliable in Tailwind v4 production
builds. Apply className="openapi-page" directly to DocsPage and replace
all 64 :has() selectors with .openapi-page class targeting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(docs): bypass TypeScript check for className prop on DocsPage

Use spread with type assertion to pass className to DocsPage, working
around a CI type resolution issue where the prop exists at runtime but
is not recognized by TypeScript in the Vercel build environment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(docs): use inline style tag for grid layout, revert CSS to :has() selectors

The className prop on DocsPage doesn't exist in the fumadocs-ui version
resolved on Vercel, so .openapi-page was never applied and all 64 CSS
rules broke. Revert to :has(.api-page-header) selectors for styling and
use an inline <style> tag for the critical grid-column layout override,
which is SSR'd and doesn't depend on any CSS selector matching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(docs): add pill styling to footer navigation method badges

The footer nav badges (POST, GET, etc.) had color from data-method rules
but lacked the structural pill styling (padding, border-radius, font-size).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(docs): use named grid lines instead of numeric column indices (#3487)

Root cause: the fumadocs grid template has 3 columns in production but
5 columns in local dev. Our CSS used `grid-column: 3 / span 2` which
targeted the wrong column in the 3-column grid, placing content in
the near-zero-width TOC column instead of the main content column.

Fix: use `grid-column: main-start / toc-end` which uses CSS named grid
lines from grid-template-areas, working regardless of column count.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* improvement(resource): layout

* improvement: icon, resource header options

* improvement: icons

* fix(files): icon

* feat(tables): column operations, row ordering, V1 API (#3488)

* feat(tables): add column operations, row ordering, V1 columns API, and OpenAPI spec

Adds column rename/delete/type change/constraint updates to the tables module,
row ordering via position column, UI metadata schema, V1 public API for column
operations with rate limiting and audit logging, and OpenAPI documentation.

Key changes:
- Service-layer column operations with validation (name pattern, type compatibility, unique/required constraints)
- Position column on user_table_rows with composite index for efficient ordering
- V1 /api/v1/tables/{tableId}/columns endpoint (POST/PATCH/DELETE) with rate limiting and audit
- Shared Zod schemas extracted to table/utils.ts using COLUMN_TYPES constant
- Targeted React Query invalidation (row vs schema mutations) with consistent onSettled usage
- OpenAPI 3.1.0 spec for columns endpoint with code samples
- Position field added to all row response mappings for consistency
- Sort fallback to position ordering when buildSortClause returns null

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tables): use specific error prefixes instead of broad "Cannot" match

Prevents internal TypeErrors (e.g. "Cannot read properties of undefined")
from leaking as 400 responses. Now matches only domain-specific errors:
"Cannot delete the last column" and "Cannot set column".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tables): reject Infinity and NaN in number type compatibility check

Number.isFinite rejects Infinity, -Infinity, and NaN, preventing
non-finite values from passing column type validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tables): invalidate table list on row create/delete for stale rowCount

Row create and delete mutations now invalidate the table list cache since
it includes a computed rowCount. Row updates (which don't change count)
continue to only invalidate row queries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tables): add column name length check, deduplicate name gen, reset pagination on clear

- Add MAX_COLUMN_NAME_LENGTH validation to addTableColumn (was missing,
  renameColumn already had it)
- Extract generateColumnName helper to eliminate triplicated logic across
  handleAddColumn, handleInsertColumnLeft, handleInsertColumnRight
- Reset pagination to page 0 when clearing sort/filter to prevent showing
  empty pages after narrowing filters are removed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: hoist tableId above try block in V1 columns route, add detail invalidation to invalidateRowCount

- V1 columns route: `tableId` was declared inside `try` but referenced in
  `catch` logger.error, causing undefined in error logs. Hoisted `await params`
  above try in all three handlers (POST, PATCH, DELETE).
- invalidateRowCount: added `tableKeys.detail(tableId)` invalidation since the
  single-table GET response includes `rowCount`, which becomes stale after
  row create/delete without this.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add position to all row mutation responses, remove dead filter code

- Add `position` field to POST (single + batch) and PATCH row responses
  across both internal and V1 routes, matching GET responses and OpenAPI spec.
- Remove unused `filterConfig`, `handleFilterToggle`, `handleFilterClear`,
  and `activeFilters` — dead code left over from merge conflict resolution.
  `handleFilterApply` (the one actually wired to JSX) is preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: invalidateTableSchema now also invalidates table list cache

Column add/rename/delete/update mutations now invalidate tableKeys.list()
since the list endpoint returns schema.columns for each table. Without this,
the sidebar table list would show stale column schemas until staleTime expires.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: replace window.prompt/confirm with emcn Modal dialogs

Replace non-standard browser dialogs with proper emcn Modal components
to match the existing codebase pattern (e.g. delete table confirmation).

- Column rename: Modal with Input field + Enter key support
- Column delete: Modal with destructive confirmation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* update schedule creation ui and run lint

* improvement: logs

* improvement(tables): multi-select and efficiencies

* Table tools

* improvement(folder-selection): folder deselection + selection order should match visual

* fix(selections): more nested folder inaccuracies

* Tool updates

* Store tool call results

* fix(landing): wire agent input to mothership

* feat(mothership): resource viewer

* fix tests

* fix(streaming): smoother streaming with throttled rendering, ResizeObserver scroll, and batched updates (#3471)

* fix(streaming): smoother streaming with throttled rendering, ResizeObserver scroll, and batched updates

- Add useThrottledValue hook (100ms trailing-edge throttle) to gate DOM re-renders during streaming across all chat surfaces
- Replace 100ms setInterval scroll polling with ResizeObserver-based auto-scroll, programmatic scroll timestamp tracking, and nested [data-scrollable] region handling
- Extract processContentBuffer from inline content handler for cleaner code organization in copilot SSE handlers
- Add RAF-based update batching (50ms max interval) to floating chat and home chat streaming paths
- Add useProgressiveList hook for progressive rendering of long conversation histories via requestAnimationFrame

Made-with: Cursor

* ack PR comments

* fix search modal

* more comments

* ack comments

* count

* ack comments

* ack comment

* improvement(mothership): worklfow resource

* Fix tool call persistence in chat

* Tool results

* Fix error status

* File uploads to mothership

* feat(templates): landing page templates workflow states

* improvement(mothership): chat stability

* improvement(mothership): chat history and stability

* improvement(tables): click-to-select navigation, inline rename, column resize (#3496)

* improvement(tables): click-to-select navigation, inline rename, column resize

* fix(tables): address PR review comments

- Add doneRef guard to useInlineRename preventing Enter+blur double-fire
- Fix PATCH error handler: return 500 for non-validation errors, fix unreachable logger.error
- Stop click propagation on breadcrumb rename input

* fix(tables): add rows-affected check in renameTable service

Prevents silent no-op when tableId doesn't match any record.

* fix(tables): useMemo deps + placeholder memo initialCharacter check

- Use primitive editingId/editValue in useMemo deps instead of whole
  useInlineRename object (which creates a new ref every render)
- Add initialCharacter comparison to placeholderPropsAreEqual, matching
  the existing pattern in dataRowPropsAreEqual

* fix(tables): address round 2 review comments

- Mirror name validation (regex + max length) in PatchTableSchema so
  validateTableName failures return 400 instead of 500
- Add .returning() + rows-affected check to renameWorkspaceFile,
  matching the renameTable pattern
- Check response.ok before parsing JSON in useRenameWorkspaceFile,
  matching the useRenameTable pattern

* refactor(tables): reuse InlineRenameInput in BreadcrumbSegment

Replace duplicated inline input markup with the shared component.
Eliminates redundant useRef, useEffect, and input boilerplate.

* fix(tables): set doneRef in cancelRename to prevent blur-triggered save

Escape → cancelRename → input unmounts → blur → submitRename would
save instead of canceling. Now cancelRename sets doneRef like
submitRename does, blocking the subsequent blur handler.

* fix(tables): pointercancel cleanup + typed FileConflictError

- Add pointercancel handler to column resize to prevent listener leaks
  when system interrupts the pointer (touch-action override, etc.)
- Replace stringly-typed error.message.includes('already exists') with
  FileConflictError class for refactor-safe 409 status detection

* fix(tables): stable useCallback dep + rename shadowed variable

- Use listRename.startRename (stable ref) instead of whole listRename
  object in handleContextMenuRename deps
- Rename inner 'target' to 'origin' in arrow-key handler to avoid
  shadowing the outer HTMLElement 'target'

* fix(tables): move class below imports, stable submitRename, clear editingCell

- Move FileConflictError below import statements (import-first convention)
- Make submitRename a stable useCallback([]) by reading editingId and
  editValue through refs (matches existing onSaveRef pattern)
- Add setEditingCell(null) to handleEmptyRowClick for symmetry with
  handleCellClick

* feat(tables): persist column widths in table metadata

Column widths now survive navigation and page reloads. On resize-end,
widths are debounced (500ms) and saved to the table's metadata field
via a new PUT /api/table/[tableId]/metadata endpoint. On load, widths
are seeded from the server once via React Query.

* fix type checking for file viewer

* fix(tables): address review feedback — 4 fixes

1. headerRename.onSave now uses the fileId parameter directly instead
   of the selectedFile closure, preventing rename-wrong-file race
2. updateMetadataMutation uses ref pattern matching mutateRef/createRef
3. Type-to-enter filters non-numeric chars for number columns, non-date
   chars for date columns
4. renameValue only passed to actively-renaming ColumnHeaderMenu,
   preserving React.memo for other columns

* fix(tables): position-based gap rows, insert above/below, consistency fixes

- Fix gap row insert shifting: only shift rows when target position is
  occupied, preventing unnecessary displacement of rows below
- Switch to position-based indexing throughout (positionMap, maxPosition)
  instead of array-index for correct sparse position handling
- Add insert row above/below to context menu
- Use CellContent for pending values in PositionGapRows (matching PlaceholderRows)
- Add belowHeader selection overlay logic to PositionGapRows
- Remove unnecessary 500ms debounce on column width persistence

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix cells nav w keyboard

* added preview panel for html, markdown rendering, completed table

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tables): one small tables ting (#3497)

* feat(exa-hosted-key): Restore exa hosted key (#3499)

Co-authored-by: Theodore Li <theo@sim.ai>

* improvement(ui): consistent styling

* styling alignment

* improvements(tables): styling improvements

* improve resizer for file preview for html files

* updated document icon

* fix(credentials): exclude regular login methods from credential sync

* update docs

* upgrade turbo

* improvement: tables, chat

* Fix table column delete

* small table rename bug, files updates not persisting

* Table batch ops

* fix(credentials): block usage at execution layer without perms + fix invites

* feat(hosted-key-services) Add hosted key for multiple services (#3461)

* feat(hosted keys): Implement serper hosted key

* Handle required fields correctly for hosted keys

* Add rate limiting (3 tries, exponential backoff)

* Add custom pricing, switch to exa as first hosted key

* Add telemetry

* Consolidate byok type definitions

* Add warning comment if default calculation is used

* Record usage to user stats table

* Fix unit tests, use cost property

* Include more metadata in cost output

* Fix disabled tests

* Fix spacing

* Fix lint

* Move knowledge cost restructuring away from generic block handler

* Migrate knowledge unit tests

* Lint

* Fix broken tests

* Add user based hosted key throttling

* Refactor hosted key handling. Add optimistic handling of throttling for custom throttle rules.

* Remove research as hosted key. Recommend BYOK if throtttling occurs

* Make adding api keys adjustable via env vars

* Remove vestigial fields from research

* Make billing actor id required for throttling

* Switch to round robin for api key distribution

* Add helper method for adding hosted key cost

* Strip leading double underscores to avoid breaking change

* Lint fix

* Remove falsy check in favor for explicit null check

* Add more detailed metrics for different throttling types

* Fix _costDollars field

* Handle hosted agent tool calls

* Fail loudly if cost field isn't found

* Remove any type

* Fix type error

* Fix lint

* Fix usage log double logging data

* Fix test

* Add browseruse hosted key

* Add firecrawl and serper hosted keys

* feat(hosted key): Add exa hosted key (#3221)

* feat(hosted keys): Implement serper hosted key

* Handle required fields correctly for hosted keys

* Add rate limiting (3 tries, exponential backoff)

* Add custom pricing, switch to exa as first hosted key

* Add telemetry

* Consolidate byok type definitions

* Add warning comment if default calculation is used

* Record usage to user stats table

* Fix unit tests, use cost property

* Include more metadata in cost output

* Fix disabled tests

* Fix spacing

* Fix lint

* Move knowledge cost restructuring away from generic block handler

* Migrate knowledge unit tests

* Lint

* Fix broken tests

* Add user based hosted key throttling

* Refactor hosted key handling. Add optimistic handling of throttling for custom throttle rules.

* Remove research as hosted key. Recommend BYOK if throtttling occurs

* Make adding api keys adjustable via env vars

* Remove vestigial fields from research

* Make billing actor id required for throttling

* Switch to round robin for api key distribution

* Add helper method for adding hosted key cost

* Strip leading double underscores to avoid breaking change

* Lint fix

* Remove falsy check in favor for explicit null check

* Add more detailed metrics for different throttling types

* Fix _costDollars field

* Handle hosted agent tool calls

* Fail loudly if cost field isn't found

* Remove any type

* Fix type error

* Fix lint

* Fix usage log double logging data

* Fix test

---------

Co-authored-by: Theodore Li <teddy@zenobiapay.com>

* Fail fast on cost data not being found

* Add hosted key for google services

* Add hosting configuration and pricing logic for ElevenLabs TTS tools

* Add linkup hosted key

* Add jina hosted key

* Add hugging face hosted key

* Add perplexity hosting

* Add broader metrics for throttling

* Add skill for adding hosted key

* Lint, remove vestigial hosted keys not implemented

* Revert agent changes

* fail fast

* Fix build issue

* Fix build issues

* Fix type error

* Remove byok types that aren't implemented

* Address feedback

* Use default model when model id isn't provided

* Fix cost default issues

* Remove firecrawl error suppression

* Restore original behavior for hugging face

* Add mistral hosted key

* Remove hugging face hosted key

* Fix pricing mismatch is mistral and perplexity

* Add hosted keys for parallel and brand fetch

* Add brandfetch hosted key

* Update types

* Change byok name to parallel_ai

* Add telemetry on unknown models

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* improvement(settings): SSR prefetch, code splitting, dedicated skeletons

* fix: bust browser cache for workspace file downloads

The downloadFile function was using a plain fetch() that honored the
aggressive cache headers, causing newly created files to download empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(settings): use emcn Skeleton in extracted skeleton files

* fix(settings): extract shared response mappers to prevent server/client shape drift

Addresses PR review feedback — prefetch.ts duplicated response mapping logic from client hooks. Extracted mapGeneralSettingsResponse and mapUserProfileResponse as shared functions used by both client fetch and server prefetch.

* update byok page

* fix(settings): include theme sync in client-side prefetch queryFn

Hover-based prefetchGeneralSettings now calls syncThemeToNextThemes, matching the useGeneralSettings hook behavior so theme updates aren't missed when prefetch refreshes stale cache.

* fix(byok): use EMCN Input for search field instead of ui Input

Replace @/components/ui Input with the already-imported EmcnInput for design-system consistency.

* fix(byok): use ui Input for search bar to match other settings pages

* fix(settings): use emcn Input for file input in general settings

* improvement(settings): add search bar to skeleton loading states

Skeletons now include the search bar (and action button where applicable) so the layout matches the final component 1:1. Eliminates layout shift when the dynamic chunk loads — search bar area is already reserved by the skeleton.

* fix(settings): align skeleton layouts with actual component structures

- Fix list item gap from 12px to 8px across all skeletons (API keys, custom tools, credentials, MCP)
- Add OAuth icon placeholder to credential skeleton
- Fix credential button group gap from 8px to 4px
- Remove incorrect gap-[4px] from credential-sets text column
- Rebuild debug skeleton to match real layout (description + input/button row)
- Add scrollable wrapper to BYOK skeleton with more representative item count

* chore: lint fixes

* improvement(sidebar): match workspace switcher popover width to sidebar

Use Radix UI's built-in --radix-popover-trigger-width CSS variable
instead of hardcoded 160px so the popover matches the trigger width
and responds to sidebar resizing.

* revert hardcoded ff

* fix: copilot, improvement: tables, mothership

* feat: inline chunk editor and table batch ops with undo/redo (#3504)

* feat: inline chunk editor and table batch operations with undo/redo

Replace modal-based chunk editing/creation with inline editor following
the files tab pattern (state-based view toggle with ResourceHeader).
Add batch update API endpoint, undo/redo support, and Popover-based
context menus for tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove icons from table context menu PopoverItems

Icons were incorrectly carried over from the DropdownMenu migration.
PopoverItems in this codebase use text-only labels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore DropdownMenu for table context menu

The table-level context menu was incorrectly migrated to Popover during
conflict resolution. Only the row-level context menu uses Popover; the
table context menu should remain DropdownMenu with icons, matching the
base branch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: bound cross-page chunk navigation polling to max 50 retries

Prevent indefinite polling if page data never loads during
chunk navigation across page boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: navigate to last page after chunk creation for multi-page documents

After creating a chunk, navigate to the last page (where new chunks
append) before selecting it. This prevents the editor from showing
"Loading chunk..." when the new chunk is not on the current page.
The loading state breadcrumb remains as an escape hatch for edge cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add duplicate rowId validation to BatchUpdateByIdsSchema

Adds a .refine() check to reject duplicate rowIds in batch update
requests, consistent with the positions uniqueness check on batch insert.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review comments

- Fix disableEdit logic: use || instead of && so connector doc chunks
  cannot be edited from context menu (row click still opens viewer)
- Add uniqueness validation for rowIds in BatchUpdateByIdsSchema
- Fix inconsistent bg token: bg-background → bg-[var(--bg)] in Pagination

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove duplicate rowId uniqueness refine on BatchUpdateByIdsSchema

The refine was applied both on the inner updates array and the outer
object. Keep only the inner array refine which is cleaner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address additional PR review comments

- Fix stale rowId after create-row redo: patch undo stack with new row
  ID using patchUndoRowId so subsequent undo targets the correct row
- Fix text color tokens in Pagination: use CSS variable references
  (text-[var(--text-body)], text-[var(--text-secondary)]) instead of
  Tailwind semantic tokens for consistency with the rest of the file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove dead code and fix type errors in table context menu

Remove unused `onAddData` prop and `isEmptyCell` variable from row context
menu (introduced in PR but never wired to JSX). Fix type errors in
optimistic update spreads by removing unnecessary `as Record<string, unknown>`
casts that lost the RowData type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent false "Saved" status on invalid content and mark fire-and-forget goToPage calls

ChunkEditor.handleSave now throws on empty/oversized content instead of
silently returning, so the parent's catch block correctly sets saveStatus
to 'error'. Also added explicit `void` to unawaited goToPage(1) calls
in filter handlers to signal intentional fire-and-forget.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle stale totalPages in handleChunkCreated for new-page edge case

When creating a chunk that spills onto a new page, totalPages in the
closure is stale. Now polls displayChunksRef for the new chunk, and if
not found, checks totalPagesRef for an updated page count and navigates
to the new last page before continuing to poll.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Streaming fix -- need to test more

* Make mothership block use long input instead of prompt input

* improvement(billing): isAnnual metadata + docs updates (#3506)

* improvement(billing): on demand toggling and infinite limits

* store stripe metadata to distinguish annual vs monthly

* udpate docs

* address bugbot

* Add piping

* feat(clean-hosted-keys) Remove eleven labs, browseruse. Tweak firecrawl and mistral key impl (#3503)

* Remove eleven labs, browseruse, and firecrawl

* Remove creditsUsed output

* Add back mistral hosting for mistral blocks

* Add back firecrawl since they queue up concurrent requests

* Fix price calculation, remove agent since its super long running and will clog up queue

* Define hosting per tool

* Remove redundant token finding

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* Update vfs to handle hosted keys

* improvement(tables): fix cell editing flash, batch API docs, and UI polish (#3507)

* fix: show text cursor in chunk editor and ensure textarea fills container

Add cursor-text to the editor wrapper so the whole area shows a text
cursor. Click on empty space focuses the textarea. Changed textarea from
h-full/w-full to flex-1/min-h-0 so it properly fills the flex container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improvement(tables): fix cell editing flash, add batch API docs, and UI polish

Fix stale-data flash when saving inline cell edits by using TanStack Query's
isPending+variables pattern instead of manual cache writes. Also adds OpenAPI
docs for batch table endpoints, DatePicker support in row modal, duplicate row
in context menu, and styling improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove dead resolveColumnFromEvent callback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: unify paste undo into single create-rows action

Batch-created rows from paste now push one `create-rows` undo entry
instead of N individual `create-row` entries, so a single Ctrl+Z
reverses the entire paste operation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: validate dates in inline editor and displayToStorage

InlineDateEditor now validates computed values via Date.parse before
saving, preventing invalid strings like "hello" from being sent to the
server. displayToStorage now rejects out-of-range month/day values
(e.g. 13/32) instead of producing invalid YYYY-MM-DD strings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: accept ISO date format in inline date editor

Fall back to raw draft input when displayToStorage returns null, so
valid ISO dates like "2024-03-15" pasted or typed directly are
accepted instead of silently discarded. Date.parse still validates
the final value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add ISO date support to displayToStorage and fix picker Escape

displayToStorage now recognizes YYYY-MM-DD input directly, so ISO
dates typed or pasted work correctly for both saving and picker sync.

DatePicker Escape now refocuses the input instead of saving, so the
user can press Escape again to cancel or Enter to confirm — matching
the expected cancel behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove dead paste boundary check

The totalR guard in handlePaste could never trigger since totalR
included pasteRows.length, making targetRow always < totalR.
Remove the unused variable and simplify the selection focus calc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* update openapi

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix dysfunctional unique operation in tables

* feat(autosave): files and chunk editor autosave with debounce + refetch  (#3508)

* feat(files): debounced autosave while editing

* address review comments

* more comments

* fix: unique constraint check crash and copilot table initial rows

- Fix TypeError in updateColumnConstraints: db.execute() returns a
  plain array with postgres-js, not { rows: [...] }. The .rows.length
  access always crashed, making "Set unique" completely broken.

- Add initialRowCount: 20 to copilot table creation so tables created
  via chat have the same empty rows as tables created from the UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix signaling

* revert: remove initialRowCount from copilot table creation

Copilot populates its own data after creating a table, so pre-creating
20 empty rows causes data to start at position 21 with empty rows above.
initialRowCount only makes sense for the manual UI creation flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improvement: chat, workspace header

* chat metadata

* Fix schema mismatch (#3510)

Co-authored-by: Theodore Li <theo@sim.ai>

* Fixes

* fix: manual table creation starts with 1 row, 1 column

Manual tables now create with a single 'name' column and 1 row instead
of 2 columns and 20 rows. Copilot tables remain at 0 rows, 0 columns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: horizontal scroll in embedded table by replacing overflow-hidden with overflow-clip

Cell content spans used Tailwind's `truncate` (overflow: hidden), creating
scroll containers that consumed trackpad wheel events on macOS without
propagating to the actual scroll ancestor. Replaced with overflow-clip
which clips identically but doesn't create a scroll container. Also moved
focus target from outer container to the scroll div for correctness.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix tool call ordering

* Fix tests

* feat: add task multi-select, context menu, and subscription UI updates

Add shift-click range selection, cmd/ctrl-click toggle, and right-click
context menu for tasks in sidebar matching workflow/folder patterns.
Update subscription settings tab UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(credentials): autosync behaviour cross workspace (#3511)

* fix(credentials): autosync behaviour cross workspace

* address comments

* fix(api-key-reminder) Add reminder on hosted keys that api key isnt needed (#3512)

* Add reminder on hosted keys that api key isnt needed

* Fix test case

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* improvement: sidebar, chat

* Usage limit

* Plan prompt

* fix(sidebar): workspace header collapse

* fix(sidebar): task navigation

* Subagent tool call persistence

* Don't drop suabgent text

* improvement(ux): streaming

* improvement: thinking

* fix(random): optimized kb connector sync engine, rerenders in tables, files, editors, chat (#3513)

* optimized kb connector sync engine, rerenders in tables, files, editors, chat

* refactor(sidebar): rename onTaskClick to onMultiSelectClick for clarity

Made-with: Cursor

* ack comments, add docsFailed

* feat(email-footer) Add "sent with sim ai" for free users (#3515)

* Add "sent with sim ai" for free users

* Only add prompt injection on free tier

* Add try catch around billing info fetch

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* improvement: modals

* ran migrations

* fix(mothership): fix hardcoded workflow color, tables drag line overflowing

* feat(mothership): file attachment indicators, persistence, and chat input improvements

- Show image thumbnails and file-icon cards above user messages in mothership chat
- Persist file attachment metadata (key, filename, media_type, size) in DB with user messages
- Restore attachments from history via /api/files/serve/ URLs so they survive refresh/navigation
- Unify all chat file inputs to use shared CHAT_ACCEPT_ATTRIBUTE constant
- Fix file thumbnail overflow: use flex-wrap instead of hidden horizontal scroll
- Compact attachment cards in floating workflow chat messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improvement: search modal

* improvement(usage): free plan to 1000 credits  (#3516)

* improvement(billing): free plan to five dollars

* fix comment

* remove per month terminology from marketing

* generate migration

* remove migration

* add migration back

* feat(workspace): add workspace color changing, consolidate update hooks, fix popover dismiss

- Add workspace color change via context menu, reusing workflow ColorGrid UI
- Consolidate useUpdateWorkspaceName + useUpdateWorkspaceColor into useUpdateWorkspace
- Fix popover hover submenu dismiss by using DismissableLayerBranch with pointerEvents
- Remove passthrough wrapper for export, reuse Workspace type for capturedWorkspaceRef
- Reorder log columns: workflow first, merge date+time into single column

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update oauth cred tool

* fix(diff-controls): fixed positioning for copilot diff controls

* fix(font): added back old font for emcn code editor

* improvement: panel, special tags

* improvement: chat

* improvement: loading and file dropping

* feat(templates): create home templates

* fix(uploads): resolve .md file upload rejection and deduplicate file type utilities

Browsers report empty or application/octet-stream MIME types for .md files,
causing copilot uploads to be rejected. Added resolveFileType() utility that
falls back to extension-based MIME resolution at both client and server
boundaries. Consolidated duplicate MIME mappings into module-level constants,
removed duplicate isImageFileType from copilot module, and replaced hardcoded
ALLOWED_EXTENSIONS with composition from shared validation constants. Also
switched file attachment previews to use shared getDocumentIcon utility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(home): prevent initial view from being scrollable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* autofill fixes

* added back integrations page, reverted secrets page back to old UI

* Fix workspace dropdown getting cut off when sidebar is collapsed

* fix(mothership): lint (#3517)

* fix(mothership): lint

* fix typing

* fix tests

* fix stale query

* fix plan display name

* Feat/add mothership manual workflow runs (#3520)

* Add run and open workflow buttons in workflow preview

* Send log request message after manual workflow run

* Make edges in embedded workflow non-editable

* Change chat to pass in log as additional context

* Revert "Change chat to pass in log as additional context"

This reverts commit e957dffb2f.

* Revert "Send log request message after manual workflow run"

This reverts commit 0fb92751f0.

* Move run and workflow icons to tab bar

* Simplify boolean condition

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* feat(resource-tab-scroll): Allow vertical scrolling to scroll resource tab

* fix(remove-speed-hosted-key) Remove maps speed limit hosted key, it's deprecated (#3521)

Co-authored-by: Theodore Li <theo@sim.ai>

* improvement: home, sidebar

* fix(download-file): render correct file download link for mothership (#3522)

* fix(download-file): render correct file download link for mothership

* Fix uunecessary call

* Use simple strip instead of db lookup and moving behavior

* Make regex strip more strict

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* improvement: schedules, auto-scroll

* fix(settings): navigate back to origin page instead of always going home

Use sessionStorage to store the return URL when entering settings, and
use router.replace for tab switches so history doesn't accumulate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(schedules): release lastQueuedAt lock on all exit paths to prevent stuck schedules

Multiple error/early-return paths in executeScheduleJob and executeJobInline
were exiting without clearing lastQueuedAt, causing the dueFilter to permanently
skip those schedules — resulting in stale "X hours ago" display for nextRunAt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(mothership): inline rename for resource tabs + workspace_file rename tool

- Add double-click inline rename on file and table resource tabs
- Wire useInlineRename + useRenameWorkspaceFile/useRenameTable mutations
- Add rename operation to workspace_file copilot tool (schema, server, router)
- Add knowledge base resource support (type, extraction, rendering, actions)
- Accept optional className on InlineRenameInput for context-specific sizing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* revert: remove inline rename UI from resource tabs

Keep the workspace_file rename tool for the mothership agent.
Only the UI-side inline rename (double-click tabs) is removed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(mothership): knowledge base resource extraction + Resource/ResourceTable refactor

- Extract KB resources from knowledge subagent respond format (knowledge_bases array)
- Add knowledge_base tool to RESOURCE_TOOL_NAMES and TOOL_UI_METADATA
- Extract ResourceTable as independently composable memoized component
- Move contentOverride/overlay to Resource shell level (not table primitive)
- Remove redundant disableHeaderSort and loadingRows props
- Rename internal sort state for clarity (sort → internalSort, sortOverride → externalSort)
- Export ResourceTable and ResourceTableProps from barrel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(logs) Run workflows client side in mothership to transmit logs (#3529)

* Run workflows client side in mothership to transmit logs

* Initialize set as constant, prevent duplicate execution

* Fix lint

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(import) fix missing file

* fix(resource): Hide resources that have been deleted (#3528)

* Hide resources that have been deleted

* Handle table, workflow not found

* Add animation to prevent flash when previous resource was deleted

* Fix animation playing on every switch

* Run workflows client side in mothership to transmit logs

* Fix race condition for animation

* Use shared workflow tool util file

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* fix: chat scrollbar on sidebar collapse/open

* edit existing workflow should bring up artifact

* fix(agent) subagent and main agent text being merged without spacing

* feat(mothership): remove resource-level delete tools from copilot

Remove delete operations for workflows, folders, tables, and files
from the mothership copilot to prevent destructive actions via AI.
Row-level and column-level deletes are preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: stop sidebar from auto-collapsing when resource panel appears (#3540)

The sidebar was forcibly collapsed whenever a resource (e.g. workflow)
first appeared in the resource panel during a task. This was disruptive
on larger screens where users want to keep both the sidebar and resource
panel visible simultaneously.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(mothership): insert copilot-created workflows at top of list (#3537)

* feat(mothership): remove resource-level delete tools from copilot

Remove delete operations for workflows, folders, tables, and files
from the mothership copilot to prevent destructive actions via AI.
Row-level and column-level deletes are preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(mothership): insert copilot-created workflows at top of list

* fix(mothership): server-side top-insertion sort order and deduplicate registry logic

* fix(mothership): include folder sort orders when computing top-insertion position

* fix(mothership): use getNextWorkflowColor instead of hardcoded color

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stop) Add stop of motehership ran workflows, persist stop messages (#3538)

* Connect play stop workflow in embedded view to workflow

* Fix stop not actually stoping workflow

* Fix ui not showing stopped by user

* Lint fix

* Plumb cancellation through system

* Stopping mothership chat stops workflow

* Remove extra fluff

* Persist blocks on cancellation

* Add root level stopped by user

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(autolayout): targetted autolayout heuristic restored (#3536)

* fix(autolayout): targetted autolayout heuristic restored

* fix autolayout boundary cases

* more fixes

* address comments

* on conflict updates

* address more comments

* fix relative position scope

* fix tye omission

* address bugbot comment

* Credential tags

* Credential id field

* feat(mothership): server-persisted unread task indicators via SSE (#3549)

* feat(mothership): server-persisted unread task indicators via SSE

Replace fragile client-side polling + timer-based green flash with
server-persisted lastSeenAt semantics, real-time SSE push via Redis
pub/sub, and dot overlay UI on the Blimp icon.

- Add lastSeenAt column to copilotChats for server-persisted read state
- Add Redis/local pub/sub singleton for task status events (started,
  completed, created, deleted, renamed)
- Add SSE endpoint (GET /api/mothership/events) with heartbeat and
  workspace-scoped filtering
- Add mark-read endpoint (POST /api/mothership/chats/read)
- Publish SSE events from chat, rename, delete, and auto-title handlers
- Add useTaskEvents hook for client-side SSE subscription
- Add useMarkTaskRead mutation with optimistic update
- Replace timer logic in sidebar with TaskStatus state machine
  (running/unread/idle) and dot overlay using brand color variables
- Mark tasks read on mount and stream completion in home page
- Fix security: add userId check to delete WHERE clause
- Fix: bump updatedAt on stream completion
- Fix: set lastSeenAt on rename to prevent false-positive unread

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback

- Return 404 when delete finds no matching chat (was silent no-op)
- Move log after ownership check so it only fires on actual deletion
- Publish completed SSE event from stop route so sidebar dot clears on abort

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: backfill last_seen_at in migration to prevent false unread dots

Existing rows would have last_seen_at = NULL after migration, causing
all past completed tasks to show as unread. Backfill sets last_seen_at
to updated_at for all existing rows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: timestamp mismatch on task creation + wasSendingRef leak across navigation

- Pass updatedAt explicitly alongside lastSeenAt on chat creation so
  both use the same JS timestamp (DB defaultNow() ran later, causing
  updatedAt > lastSeenAt → false unread)
- Reset wasSendingRef when chatId changes to prevent a stale true
  from task A triggering a redundant markRead on task B

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: mark-read fires for inline-created chats + encode workspaceId in SSE URL

Expose resolvedChatId from useChat so home.tsx can mark-read even when
chatId prop stays undefined after replaceState URL update. Also
URL-encode workspaceId in EventSource URL as a defensive measure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: auto-focus home input on initial view + fix sidebar task click handling

Auto-focus the textarea when the initial home view renders. Also fix
sidebar task click to always call onMultiSelectClick so selection state
stays consistent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: auto-title sets lastSeenAt + move started event inside DB guard

Auto-title now sets both updatedAt and lastSeenAt (matching the rename
route pattern) to prevent false-positive unread dots. Also move the
'started' SSE event inside the if(updated) guard so it only fires when
the DB update actually matched a row.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* modified tasks multi select to be just like workflows

* fix

* refactor: extract generic pub/sub and SSE factories + fixes

- Extract createPubSubChannel factory (lib/events/pubsub.ts) to eliminate
  duplicated Redis/EventEmitter boilerplate between task and MCP pub/sub
- Extract createWorkspaceSSE factory (lib/events/sse-endpoint.ts) to share
  auth, heartbeat, and cleanup logic across SSE endpoints
- Fix auto-title race suppressing unread status by removing updatedAt/lastSeenAt
  from title-only DB update
- Fix wheel event listener leak in ResourceTabs (RefCallback cleanup was silently
  discarded)
- Fix getFullSelection() missing taskIds (inconsistent with hasAnySelection)
- Deduplicate SSE_RESPONSE_HEADERS to spread from shared SSE_HEADERS
- Hoist isSttAvailable to module-level constant to avoid per-render IIFE

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(logs): add workflow trigger type for sub-workflow executions (#3554)

* feat(logs): add workflow trigger type for sub-workflow executions

* fix(logs): align workflow filter color with blue-secondary badge variant

* feat(tab) allow user to control resource tabs

* Make resources persist to backend

* Use colored squares for workflows

* Add click and drag functionality to resource

* Fix expanding panel logic

* Reduce duplication, reading resource also opens up resource panel

* Move resource dropdown to own file

* Handle renamed resources

* Clicking already open tab should just switch to tab

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* Fix new resource tab button not appearing on tasks

* improvement(ui): dropdown menus, icons, globals

* improvement: notifications, terminal, globals

* reverted task logic

* feat(context) pass resource tab as context (#3555)

* feat(context) add currenttly open resource file to context for agent

* Simplify resource resolution

* Skip initialize vfs

* Restore ff

* Add back try catch

* Remove redundant code

* Remove json serialization/deserialization loop

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* Feat(references) add at to reference sim resources(#3560)


* feat(chat) add at sign

* Address bugbot issues

* Remove extra chatcontext defs

* Add table and file to schema

* Add icon to chip for files

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* improvement(refactor): move to soft deletion of resources + reliability improvements (#3561)

* improvement(deletion): migrate to soft deletion of resources

* progress

* scoping fixes

* round of fixes

* deduplicated name on workflow import

* fix tests

* add migration

* cleanup dead code

* address bugbot comments

* optimize query

* feat(sim-mailer): email inbox for mothership with chat history and plan gating (#3558)

* feat(sim-mailer): email inbox for mothership with chat history and plan gating

* revert hardcoded ff

* fix(inbox): address PR review comments - plan enforcement, idempotency, webhook auth

- Enforce Max plan at API layer: hasInboxAccess() now checks subscription tier (>= 25k credits or enterprise)
- Add idempotency guard to executeInboxTask() to prevent duplicate emails on Trigger.dev retries
- Add AGENTMAIL_WEBHOOK_SECRET env var for webhook signature verification (Bearer token)

* improvement(inbox): harden security and efficiency from code audit

- Use crypto.timingSafeEqual for webhook secret comparison (prevents timing attacks)
- Atomic claim in executor: WHERE status='received' prevents duplicate processing on retries
- Parallelize hasInboxAccess + getUserEntityPermissions in all API routes (reduces latency)
- Truncate email body at webhook insertion (50k char limit, prevents unbounded DB storage)
- Harden escapeAttr with angle bracket and single quote escaping
- Rename use-inbox.ts to inbox.ts (matches hooks/queries/ naming convention)

* fix(inbox): replace Bearer token auth with proper Svix HMAC-SHA256 webhook verification

- Use per-workspace webhook secret from DB instead of global env var
- Verify AgentMail/Svix signatures: HMAC-SHA256 over svix-id.timestamp.body
- Timing-safe comparison via crypto.timingSafeEqual
- Replay protection via timestamp tolerance (5 min window)
- Join mothershipInboxWebhook in workspace lookup (zero additional DB calls)
- Remove dead AGENTMAIL_WEBHOOK_SECRET env var
- Select only needed workspace columns in webhook handler

* fix(inbox): require webhook secret — reject requests when secret is missing

Previously, if the webhook secret was missing from the DB (corrupted state),
the handler would skip verification entirely and process the request
unauthenticated. Now all three conditions are hard requirements: secret must
exist in DB, Svix headers must be present, and signature must verify.

* fix(inbox): address second round of PR review comments

- Exclude rejected tasks from rate limit count to prevent DoS via spam
- Strip raw HTML from LLM output before marked.parse to prevent XSS in emails
- Track responseSent flag to prevent duplicate emails when DB update fails after send

* fix(inbox): address third round of PR review comments

- Use dynamic isHosted from feature-flags instead of hardcoded true
- Atomic JSON append for chat message persistence (eliminates read-modify-write race)
- Handle cutIndex === 0 in stripQuotedReply (body starts with quote)
- Clean up orphan mothershipInboxWebhook row on enableInbox rollback
- Validate status query parameter against enum in tasks API

* fix(inbox): validate cursor param, preserve code blocks in HTML stripping

- Validate cursor date before using in query (return 400 for invalid)
- Split on fenced code blocks before stripping HTML tags to preserve
  code examples in email responses

* fix(inbox): return 500 on webhook server errors to enable Svix retries

* fix(inbox): remove isHosted guard from hasInboxAccess — feature flag is sufficient

* fix(inbox): prevent double-enable from deleting webhook secret row

* fix(inbox): null-safe stripThinkingTags, encode URL params, surface remove-sender errors

- Guard against null result.content in stripThinkingTags
- Use encodeURIComponent on all AgentMail API path parameters
- Surface handleRemoveSender errors to the user instead of swallowing

* improvement(inbox): remove unused types, narrow SELECT queries, fix optimistic ID collision

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(inbox): add keyboard accessibility to clickable task rows

* fix(inbox): use Svix library for webhook verification, fix responseSent flag, prevent inbox enumeration

- Replace manual HMAC-SHA256 verification with official Svix library per AgentMail docs
- Fix responseSent flag: only set true when email delivery actually succeeds
- Return consistent 401 for unknown inbox and bad signature to prevent enumeration
- Make AgentMailInbox.organization_id optional to match API docs

* chore(db): rebase inbox migration onto feat/mothership-copilot (0172 → 0173)

Sync schema with target branch and regenerate migration as 0173
to avoid conflicts with 0172_silky_magma on feat/mothership-copilot.

* fix(db): rebase inbox migration to 0173 after feat/mothership-copilot divergence

Target branch added 0172_silky_magma, so our inbox migration is now 0173_youthful_stryfe.

* fix(db): regenerate inbox migration after rebase on feat/mothership-copilot

* fix(inbox): case-insensitive email match and sanitize javascript: URIs in email HTML

- Use lower() in isSenderAllowed SQL to match workspace members regardless
  of email case stored by auth provider
- Strip javascript:, vbscript:, and data: URIs from marked HTML output to
  prevent XSS in outbound email responses

* fix(inbox): case-insensitive email match in resolveUserId

Consistent with the isSenderAllowed fix — uses lower() so mixed-case
stored emails match correctly, preventing silent fallback to workspace owner.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Kb args

* refactor(resource): remove logs-specific escape hatches from Resource abstraction

Logs now composes ResourceHeader + ResourceOptionsBar + ResourceTable directly
instead of using Resource with contentOverride/overlay escape hatches. Removes
contentOverride, onLoadMore, hasMore, isLoadingMore from ResourceProps. Adds
ColumnOption to barrel export and fixes table.tsx internal import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(sim-mailer): download email attachments and pass to LLM as multimodal content

Attachments were only passed as metadata text in the email body. Now downloads
actual file bytes from AgentMail, converts via createFileContent (same path as
interactive chat), and sends as fileAttachments to the orchestrator. Also
parallelizes attachment fetching with workspace context loading, and downloads
multiple attachments concurrently via Promise.allSettled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(connector): add Gmail knowledge base connector with thread-based sync and filtering

Syncs email threads from Gmail into knowledge bases with configurable filters:
label scoping, date range presets, promotions/social exclusion, Gmail search
syntax support, and max thread caps to keep KB size manageable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(connector): add Outlook knowledge base connector with conversation grouping and filtering

Syncs email conversations from Outlook/Office 365 via Microsoft Graph API.
Groups messages by conversationId into single documents. Configurable filters:
folder selection, date range presets, Focused Inbox, KQL search syntax, and
max conversation caps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* cleanup resource definition

* feat(connectors): add 8 knowledge base connectors — Zendesk, Intercom, ServiceNow, Google Sheets, Microsoft Teams, Discord, Google Calendar, Reddit

Each connector syncs documents into knowledge bases with configurable filtering:

- Zendesk: Help Center articles + support tickets with status/locale filters
- Intercom: Articles + conversations with state filtering
- ServiceNow: KB articles + incidents with state/priority/category filters
- Google Sheets: Spreadsheet tabs as LLM-friendly row-by-row documents
- Microsoft Teams: Channel messages (Slack-like pattern) via Graph API
- Discord: Channel messages with bot token auth
- Google Calendar: Events with date range presets and attendee metadata
- Reddit: Subreddit posts with top comments, sort/time filters

All connectors validated against official API docs with bug fixes applied.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(inbox): fetch real attachment binary from presigned URL and persist for chat display

The AgentMail attachment endpoint returns JSON metadata with a download_url,
not raw binary. We were base64-encoding the JSON text and sending it to the
LLM, causing provider rejection. Now we parse the metadata, fetch the actual
file from the presigned URL, upload it to copilot storage, and persist it on
the chat message so images render inline with previews.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* added agentmail domain for mailer

* added docs for sim mailer

* fix(resource) handle resource deletion  deletion (#3568)

* Add handle dragging tab to input chat

* Add back delete tools

* Handle deletions properly with resources view

* Fix lint

* Add permisssions checking

* Skip resource_added event when resource is deleted

* Pass workflow id as context

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* update docs styling, add delete confirmation on inbox

* Fix fast edit route

* updated docs styling, added FAQs, updated content

* upgrade turbo

* fix(knowledge) use consistent empty state for documents page

Replace the centered "No documents yet" text with the standard Resource
table empty state (column headers + create row), matching all other
resource pages. Move "Upload documents" from header action to table
create row as "New documents".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(notifications): polish modal styling, credential display, and trigger filters (#3571)

* fix(notifications): polish modal styling, credential display, and trigger filters

- Show credential display name instead of raw account ID in Slack account selector
- Fix label styling to use default Label component (text-primary) for consistency
- Fix modal body spacing with proper top padding after tab bar
- Replace list-card skeleton with form-field skeleton matching actual layout
- Replace custom "Select a Slack account first" box with disabled Combobox (dependsOn pattern)
- Use proper Label component in WorkflowSelector with consistent gap spacing
- Add overflow badge pattern (slice + +N) to level and trigger filter badges
- Use dynamic trigger options from getTriggerOptions() instead of hardcoded CORE_TRIGGER_TYPES
- Relax API validation to accept integration trigger types (z.string instead of z.enum)
- Deduplicate account rows from credential leftJoin in accounts API
- Extract getTriggerOptions() to module-level constants to avoid per-render calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(notifications): address PR review feedback

- Restore accountId in displayName fallback chain (credentialDisplayName || accountId || providerId)
- Add .default([]) to triggerFilter in create schema to preserve backward compatibility
- Treat empty triggerFilter as "match all" in notification matching logic
- Remove unreachable overflow badge for levelFilter (only 2 possible values)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(settings): add spacing to Sim Keys toggle and replace Sim Mailer icon with Send

Add 24px top margin to the "Allow personal Sim keys" toggle so it doesn't
sit right below the empty state. Replace the Mail envelope icon for Sim
Mailer with a new Send (paper plane) icon matching the emcn icon style.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* standardize back buttons in settings

* feat(restore) Add restore endpoints and ui (#3570)

* Add restore endpoints and ui

* Derive toast from notification

* Auth user if workspaceid not found

* Fix recently deleted ui

* Add restore error toast

* Fix deleted at timestamp mismatch

---------

Co-authored-by: Theodore Li <theo@sim.ai>

* fix type errors

* Lint

* improvements: ui/ux around mothership

* reactquery best practices, UI alignment in restore

* clamp logs panel

* subagent thinking text

* fix build, speedup tests by up to 40%

* Fix fast edit

* Add download file shortcut on mothership file view

* fix: SVG file support in mothership chat and file serving

- Send SVGs as document/text-xml to Claude instead of unsupported
  image/svg+xml, so the mothership can actually read SVG content
- Serve SVGs inline with proper content type and CSP sandbox so
  chat previews render correctly
- Add SVG preview support in file viewer (sandboxed iframe)
- Derive IMAGE_MIME_TYPES from MIME_TYPE_MAPPING to reduce duplication
- Add missing webp to contentTypeMap, SAFE_INLINE_TYPES, binaryExtensions
- Consolidate PREVIEWABLE_EXTENSIONS into preview-panel exports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: replace image/* wildcard with explicit supported types in file picker

The image/* accept attribute allowed users to select BMP, TIFF, HEIC,
and other image types that are rejected server-side. Replace with the
exact set of supported image MIME types and extensions to match the
copilot upload validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Context tags

* Fix lint

* improvement: chat and terminal

---------

Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
2026-03-13 21:02:08 -07:00
79bb4e5ad8 feat(docs): add API reference with OpenAPI spec and auto-generated endpoint pages (#3388)
* feat(docs): add API reference with OpenAPI spec and auto-generated endpoint pages

* multiline curl

* random improvements

* cleanup

* update docs copy

* fix build

* cast

* fix builg

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
2026-03-01 22:53:18 -08:00
Waleed 299cc26694 improvement(lint): fix react-doctor errors and warnings (#3232)
* improvement(lint): fix react-doctor errors and warnings

* remove separators
2026-02-17 11:40:47 -08:00
Waleed f718079593 improvement(docs): instant copy button + performance optimizations (#3076) 2026-01-29 23:24:58 -08:00
Emir Karabeg 9cba8eee48 improvement(preview): error paths, loops, workflow (#3010)
* improvement(switch): dark styling

* improvement(settings): change deployed MCPs to MCPs servers

* improvement(preview): added error paths, loop logic

* improvement(preview): nested workflows preview

* feat(preview): lightweight param

* improvement(preview): staging changes integrated
2026-01-26 17:57:48 -08:00
Waleed 3ccbee187d improvement(docs): updated logo, added lightbox to action media, fixed minor styling inconsistencies between themes (#3014)
* improvement(docs): updated logo, added lightbox to action media, fixed minor styling inconsistencies between themes

* updated og image

* ack comments
2026-01-26 15:49:23 -08:00
Waleed 1bf5ed4586 improvement(docs): add quick reference page and update SDK documentation (#2994)
* docs(sdk): update README to reflect new interface

* improvement(docs): add quick reference page and update SDK documentation

* docs(copilot): update copilot documentation with all features
2026-01-25 02:21:02 -08:00
Waleed 3c43779ba3 feat(search): added operations to search modal in main app, updated retrieval in docs to use RRF (#2889) 2026-01-19 13:57:56 -08:00
Waleed d79696beae feat(docs): added vector search (#2583)
* feat(docs): added vector search

* ack comments
2025-12-25 11:00:57 -08:00
Waleed 086982c7a3 improvement(docs): update og image (#2529) 2025-12-22 13:16:39 -08:00
Waleed a2f14cab54 feat(og): add opengraph images for templates, blogs, and updated existing opengraph image for all other pages (#2466)
* feat(og): add opengraph images for templates, blogs, and updated existing opengraph image for all other pages

* added to workspace templates page as well

* ack PR comments
2025-12-18 19:15:06 -08:00