Commit Graph
15 Commits
Author SHA1 Message Date
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 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
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
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
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 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 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 a45bb1bf3b fix(rce): add 'isolate' to list of trusted deps, fixed custom tools environment resolution (#2387)
* fix(rce): add isolate to list of trusted deps

* updated error enchancer in RCE

* fixed

* fix build

* fix failing test

* fix build

* fix build

* remove extraneous comment
2025-12-15 15:24:11 -08:00
Waleed 75a5b43252 feat(og): update og image (#2362) 2025-12-13 15:52:13 -08:00
Waleed f7d1b06d75 feat(docs): add opengraph to docs for dynamic link preview (#2360) 2025-12-13 13:51:43 -08:00
Waleed 5479461617 feat(docs): added page nav buttons, static search, TOC footer (#1787)
* feat(docs): added page nav buttons, static search, TOC footer

* back to dynamic search since indexes are too large, added caching hour over hour
2025-11-01 14:38:28 -07:00
Waleed Latif a92ee8bf46 feat(turbo): restructured repo to be a standard turborepo monorepo (#341)
* added turborepo

* finished turbo migration

* updated gitignore

* use dotenv & run format

* fixed error in docs

* remove standalone deployment in prod

* fix ts error, remove ignore ts errors during build

* added formatter to the end of the docs generator
2025-05-09 21:45:49 -07:00