mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
2d41360807c98db1433d39b683083ebe5ae0883f
3895
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2d41360807 |
improvement(concurrency): limits configurable, docs updates (#5640)
* improvement(concurrency): limits configurable, docs updates * remove dead tests * limits self hosted vars |
||
|
|
e94e514c0a |
feat(flint): add Flint integration with agent task tools, block, and docs (#5641)
* feat(flint): add Flint integration with agent task tools, block, and docs * fix(flint): forward explicit publish=false, guard missing taskId, align default params branch with tool fallback * docs(flint): add manual intro section to integration docs page * fix(flint): fail get_task on OK responses without a task ID * fix(flint): drop json-object generation type so the wand emits the pages array |
||
|
|
04535ee33b |
feat(buffer): add Buffer integration with posts, channels, and ideas (#5637)
* feat(buffer): add Buffer integration with posts, channels, and ideas * fix(buffer): return clear tool error when account lookup yields no account * fix(buffer): default schedulingType server-side so basic-mode blocks never fail validation * fix(buffer): probe Content-Type for extensionless media URLs so videos are not sent as images * feat(buffer): add get_ideas and get_idea_groups tools, harden media URL classification * fix(buffer): guard missing post on PostActionSuccess responses |
||
|
|
836ceda3a7 |
fix(landing): complete Organization schema, add CollectionPage/BlogPosting JSON-LD, fix TechArticle rich-result eligibility (#5638)
* fix(landing): complete Organization schema, add CollectionPage/BlogPosting JSON-LD, fix TechArticle rich-result eligibility - Organization schema (site-structured-data.tsx): add brand and contactPoint.url (verified against the real /contact route); all other fields were already correct and verified against the Footer's sameAs links. foundingDate/legalName/address omitted — not verifiable from anything in this repo. - CollectionPage (blog + library index): buildCollectionPageJsonLd now takes the real posts list (same getAllPostMeta() the index page already renders from) and emits a mainEntity ItemList of BlogPosting stubs instead of omitting mainEntity entirely. - BlogPosting + TechArticle (post detail template): Google's Article rich-result eligibility only recognizes Article/NewsArticle/BlogPosting — a bare TechArticle type isn't in that allowlist. buildArticleJsonLd now emits a multi-type @type array (["BlogPosting","TechArticle"]) for genuinely technical posts, and "BlogPosting" alone for posts that are general announcements, via a new `technical` frontmatter flag (defaults true; set to false on the series-a funding-announcement post, the one post with no technical content). Also fixed a real markup/schema mismatch: the article's `speakable.cssSelector` referenced `[itemprop="description"]`, but no element carried that itemProp — added it to the description paragraph. TechArticle/BlogPosting image URLs are now made absolute (previously relative paths, invalid for crawlers). - FAQPage: audited every LandingFAQ usage (/models, /models/[provider], /models/[provider]/[model], /integrations, /integrations/[slug], /comparison, /comparison/[provider]) — all already emit matching FAQPage JSON-LD sourced from the same data passed to LandingFAQ; no gaps found, no changes needed. * fix(landing): match microdata itemType and scope collection JSON-LD to visible posts Address Greptile/Cursor review on PR #5638: - itemType now always resolves to BlogPosting (matching Greptile's exact suggested fix), since the JSON-LD graph already carries the richer TechArticle type via a multi-type array and the microdata path doesn't need to duplicate that distinction - buildCollectionPageJsonLd now receives the same tag-filtered/paginated post subset ContentIndexPage actually renders, instead of the full unfiltered catalog, via a new shared selectVisiblePosts/paginateContentPosts helper in lib/content/index-list.ts (also de-duplicates the pagination logic that previously lived only in ContentIndexPage) * fix(landing): match CollectionPage ItemList order and url to the visible filtered/paginated variant Address round-2 Cursor Bugbot findings on PR #5638: - buildCollectionPageJsonLd no longer re-sorts the given posts by date - ordering is now solely owned by the caller (selectVisiblePosts), so featured-row-first render order matches ItemList position order - buildCollectionPageJsonLd now takes an optional {tag, page} filter descriptor and reflects it in the emitted url, instead of always pointing at the bare section index regardless of which filtered/ paginated variant's posts are actually listed in mainEntity |
||
|
|
0640c0bbac | improvement(hubspot): align tools with API docs and expand coverage with delete, list membership, and search tools (#5635) | ||
|
|
b1bd2b5cab |
feat(gong): align tools with official API spec + 4 new tools (#5632)
* feat(gong): align tools with official API spec, add ask-anything, brief, unassign, and logs tools
- fix gong_list_flows: query param was flowEmailOwner (typo copied from Gong's endpoint prose); the API requires flowOwnerEmail, so every call failed
- create_call: drop phantom url output (API returns only requestId/callId) and make downloadMediaUrl optional per spec
- list_scorecards: refresh to current spec (numeric IDs, questionType/answerGuide/minRange/maxRange/answerOptions, reviewMethod)
- answered_scorecards: map selectedOptions on answers, correct score range description (1-50)
- surface requestId uniformly across all read tools; totalRecords no longer fabricated from page size
- normalize includeAvatars to a strict boolean param, uppercase aggregationPeriod, encode userId path param
- validate email/phone format before irreversible GDPR purge calls
- new tools: gong_ask_anything, gong_get_brief (AI entity Q&A/briefs), gong_unassign_flow_prospects, gong_get_logs
* fix(gong): require custom-range dates and gate param remapping by operation
- ask_anything/get_brief: entityFromDateTime/entityToDateTime now conditionally required (UI) and validated tool-side when timePeriod is CUSTOM_RANGE
- block params mapper: remaps are gated by the selected operation so stale values from previously configured operations can no longer overwrite fromDateTime/fromDate/workspaceId
* fix(gong): final spec-alignment pass, review fixes, and regenerated docs
- ask_anything/get_brief: send fromDateTime/toDateTime only for CUSTOM_RANGE; declare mcpResult brief section field
- unassign: dedicated optional unassignFlowId subblock so a stale assign-flow ID can never silently narrow an unassign to one flow
- get_logs: logType is a closed enum (AccessLog, UserActivityLog, UserCallPlay, ExternallySharedCallAccess, ExternallySharedCallPlay) - dropdown in block, enumerated in tool description
- get_folder_content: folderId optional per spec; get_call: encode callId path param
- list_trackers: drop saidInCallParts (absent from the spec's KeywordTracker schema)
- get_extensive_calls: correct declared interactionStats item shape to {name, value}
- block inputs: list all remapped subblock params; optional flags on nullable outputs; absolute types re-export
- regenerate Gong integration docs and integrations.json entries (29 operations)
|
||
|
|
0d9f8607c1 |
feat(library): add n8n alternatives, LangGraph alternatives, and open-source AI agent platform listings (#5633)
* feat(library): add n8n alternatives, LangGraph alternatives, and open-source AI agent platform listings * fix(content): declare actual OG image dimensions instead of hardcoded 1200x630 |
||
|
|
8ae353828c |
fix(landing): fix oversized HTML and severe LCP on integration/comparison pages (#5634)
* fix(landing): fix oversized HTML and severe LCP on integration/comparison pages Google Search Console flagged /integrations, /integrations/slack, and several others for exceeding Googlebot's 2MB uncompressed-HTML crawl limit, plus severe LCP on /integrations, /integrations/slack, /integrations/hubspot, /comparison/flowise, and /integrations/hugging-face. Root cause 1 - /integrations/slack was 5.2MB (measured on production). The "Agent templates" section renders every template matching getTemplatesForBlock(type) with no cap - Slack is referenced as alsoIntegrations in 445 templates (vs 52-126 for Salesforce/Gmail/ HubSpot), so its detail page embedded hundreds of full template cards in the initial HTML/RSC payload. Capped to 12, consistent with the same file's existing related-integrations cap of 4. Root cause 2 - /integrations was 1.83MB, right at the limit. The client IntegrationGrid component receives the full Integration[] as props (needed for instant client-side search), which serializes every integration's complete operations/triggers arrays - including full per-operation description sentences - into the initial payload purely to build a search index. Added IntegrationSummary + toIntegrationSummary (lib/integrations): the same searchable surface (name, description, operation names, trigger names) precomputed server-side into one lowercased string, dropping the full operations/triggers data the client never actually renders. Measured: 627KB -> 142KB of embedded integration data (77% reduction). Verified via a real production build + curl: - /integrations: 1.83MB -> 1.31MB - /integrations/slack: 5.2MB -> 355KB (93% reduction) - /integrations/hubspot: 901KB -> 452KB Verified via Lighthouse (mobile, devtools throttling, matching what real users on a typical device experience) against both live production and the fixed local build: - /integrations: 47 -> 96 (LCP unmeasured->2.1s, TBT 2,770ms->110ms) - /integrations/slack: 47 -> 96 (LCP 9.7s -> 1.9s) - /integrations/hubspot: 72 -> 97 (LCP 9.1s -> 1.9s) - /integrations/hugging-face: 72 -> 95 (LCP 9.2s -> 2.3s, reproduced twice on production before fixing - not a fluke) - /comparison/flowise: 71 -> 95 (LCP 9.8s -> 2.1s) The last two aren't Slack-style template-count outliers (4 and 0 alsoIntegrations references respectively) - shrinking the shared IntegrationGrid client bundle appears to have reduced a shared chunk loaded broadly across landing pages, benefiting pages beyond the ones directly touched. Also checked and confirmed already healthy, no action needed: /integrations/salesforce, /integrations/gmail, /integrations/amazon-dynamodb, /comparison/tines, /models/xai/grok-4-20-multi-agent-0309. * fix(landing): restore full-fidelity search and template priority Two real regressions from the previous commit, both confirmed by Greptile and Cursor Bugbot independently: - lib/integrations: IntegrationSummary.searchText joined every field into one string, so (a) it dropped operation/trigger descriptions entirely (a search for API-specific terms that only appear in an operation's description, not its name, silently stopped matching), and (b) a single joined string lets a query span a field boundary (e.g. matching across the tail of a name and the head of the next field) that the original per-field search never allowed. Reverted to a searchFields array - same content as the original per-field index (name, description, every operation's name+description, every trigger's name), just precomputed server-side instead of shipping the full Integration objects. Grid filtering goes back to `.some(field => field.includes(q))`, matching the exact original matching semantics. - blocks/registry.ts + integrations/[slug]/page.tsx: capping getTemplatesForBlock's result with a plain .slice(0, 12) kept whatever 12 templates happened to iterate first in registry insertion order - for a high-connectivity integration like Slack, that can be entirely alsoIntegrations matches from unrelated blocks, silently dropping the integration's own owned templates and any marked featured. Added an explicit isOwner flag to ScopedBlockTemplate (the registry function already computes this internally, just wasn't surfacing it) and sort owner-first, featured-second before slicing. * fix(landing): sort featured templates ahead of owned, not just as a tiebreaker The previous sort (owner-first, featured as a tiebreaker within each owner tier) meant an integration with 12+ owned templates filled the entire cap with non-featured owned templates before any featured related template (reached via alsoIntegrations) was ever considered - Slack hits this case. Swapped the sort so featured (owned or related) ranks first, then owned-but-not-featured, so a curated featured template can no longer be sliced away by a pile of ordinary owned ones. |
||
|
|
4b3c688fec |
fix(landing): fix 404ing OG images for every model and integration page (#5636)
Google Search Console flagged every /models/{provider}/{model}/opengraph-image
and /integrations/{slug}/opengraph-image URL as 404 (~230 pages total: 5
sample model pages plus ~130 more models, plus every integration).
Root cause: the sibling page.tsx for each of these routes sets
`dynamicParams = false`, a segment-level restriction that also blocks
the metadata route (opengraph-image.tsx) from rendering any param
combination it wasn't statically told about - but Next does not share
generateStaticParams between a page and its sibling metadata routes.
Since none of the three opengraph-image.tsx files exported their own
generateStaticParams, every param was "unknown" to that restriction and
404d, for every single model and integration.
Added a matching generateStaticParams to all three files, mirroring
each route's own page.tsx.
Note: the exact URLs in the audit report (bare /opengraph-image, no
suffix) aren't the real ones - Next serves these at a build-generated
hash suffix (e.g. /opengraph-image-15dal5?<hash>), which is what's
actually embedded in each page's <meta property="og:image"> tag. The
underlying bug the report surfaced is real regardless; verified by
requesting the actual hash-suffixed URL each page embeds (previously
404, now 200) for both a model and an integration page, on a real
production build.
|
||
|
|
f9cc73f534 | fix(slack): stop requesting unapproved OAuth scopes that break connect (#5631) | ||
|
|
fc25cfb3c8 |
fix(docs): fix Core Web Vitals regressions on docs.sim.ai (#5630)
* fix(docs): fix Core Web Vitals regressions on docs.sim.ai Empirically measured under real trace-based (devtools) CPU/network throttling against the live site: mobile Performance 59, LCP 9.2s (TTFB 745ms + 8.4s element render delay). - sidebar-components.tsx / [lang]/layout.tsx: the docs sidebar renders every page in the doc tree as a link at once. Next's default viewport-prefetch fired an RSC payload fetch for every one of them on initial load - dozens of concurrent requests competing with the page's own content for bandwidth. Wired fumadocs' documented `sidebar.prefetch` option through to the custom SidebarItem/SidebarFolder components (which were bypassing it entirely, using next/link directly with no prefetch prop) via the `useSidebar()` context hook. - video.tsx: `autoPlay` forces browsers to fetch the full video file immediately on mount regardless of `preload`. Gated actual src loading behind an IntersectionObserver so a page with several of these doesn't pull down every video up front (5MB across 3 requests, in this case). Single shared component - fixes every doc page that embeds one. - proxy.ts: the i18n middleware matcher excluded favicon/robots.txt/etc but not `icon.svg`, so every request for it got routed through i18n negotiation instead of served as a static file, 404ing in production. - next.config.ts: enable productionBrowserSourceMaps - safe since this repo's source is already fully public, real debuggability benefit, zero performance cost. - shiki 4.0.0 -> 4.3.1 (verified: syntax highlighting still renders correctly). Attempted a coordinated fumadocs-core/ui/mdx/openapi upgrade to latest; fumadocs-openapi's v11 factory function became client-only (breaking change beyond its declared peer deps, requiring a component-boundary restructure), so only the safe, verified, docs-exclusive bumps (fumadocs-core/ui/mdx, shiki) are included here - the openapi major bump needs its own dedicated migration PR. Verified via a real production build (dummy env, all 3974 pages including API reference render/build cleanly) and a clean (non-stale) local server: Performance 59 -> 71 measured under real devtools throttling, RSC prefetch requests 63 -> 11, video requests/bytes 3/5MB -> 0. A pre-existing React hydration warning (#418) was found and confirmed present on live production before any of these changes, unrelated to this diff - documented, not blocking. * fix(docs): fall back to eager video loading without IntersectionObserver The lazy-load gate from the previous commit threw before isInView could ever become true in environments lacking IntersectionObserver (older browsers, some embedded webviews), leaving videos permanently source-less instead of falling back to eager loading. * chore(docs): drop non-TSDoc inline comments Repo convention is TSDoc-only, no plain // comments. * fix(docs): accessibility and SEO defects across the docs app Audited with parallel subagents against the accessibility and SEO skill checklists, each fix verified by reading the actual code (not assumed): Accessibility: - lightbox.tsx: focus was never captured/restored on close, and Tab escaped the modal to the page behind it (no focus trap on the single focusable element) - heading.tsx: the per-heading copy-link icon only appeared on hover, invisible to keyboard-only navigation (added peer-focus-visible) - navbar.tsx: active nav tab had no aria-current - response-section.tsx: the status-code dropdown had no aria-haspopup/aria-expanded/role, and no Escape-to-close - workflow-preview.tsx: same focus-trap gap as lightbox.tsx on the expanded-canvas modal SEO: - page.tsx: generateMetadata's hreflang/canonical URLs used a naive String.replace to strip the locale prefix, which also matched "/en" inside unrelated slugs (platform/enterprise, integrations/enrich, platform/self-hosting/environment-variables), corrupting those pages' canonical and alternate-language URLs. Replaced with a prefix-only strip. - structured-data.tsx: the SoftwareApplication JSON-LD block compared url === baseUrl (no trailing slash) against the homepage's actual url (always has a trailing slash), so the condition was always false and this structured data never rendered anywhere, including the homepage. - structured-data.tsx: "Mothership" in the indexed featureList violated the constitution's required language (the agent is "Sim", the surface is "Chat") - this ships in JSON-LD search engines parse. * fix(docs): defer the Ask Sim chat widget's heavy deps until opened The chat panel (useChat from @ai-sdk/react, Streamdown + its CSS) was mounted unconditionally in the root layout on every single page, so its full weight loaded and executed even though the widget starts closed on every page view. Traced via the LCP breakdown insight under real devtools CPU/network throttling: the LCP text element (the intro paragraph) had a ~8s element render delay despite a ~13ms TTFB, and bootup-time attributed ~4.3s of scripting time to a single chunk containing React/ReactDOM's own runtime plus this widget's eagerly-bundled dependencies. Split into a lightweight ask-ai.tsx (just the toggle button + open state) and ask-ai-panel.tsx (the actual chat UI, useChat, Streamdown), loaded via next/dynamic(..., { ssr: false }) only when the user opens the widget. Verified: the panel's chunk now has zero network requests on initial page load. Measured (mobile, devtools throttling, /introduction): - Performance: 69 -> 75 - LCP: 8.0s -> 6.4s - TBT: 260ms -> 130ms The remaining ~6.4s LCP delay traces to the same shared chunk, now identified as core React/ReactDOM hydration cost for this page's sidebar/TOC/breadcrumb tree rather than an isolated bug - a real, larger initiative (hydration architecture, not a surgical fix), documented here rather than rushed. * fix(docs): preserve Ask Sim chat state across close/reopen The panel split unmounted AskAIPanel entirely on close, discarding useChat's message state - reopening always started an empty conversation, unlike the original single-component layout where useChat lived in a component that never unmounted. Fixed by keeping the panel mounted (via a hasOpened flag that never resets) once first opened, and having the panel itself return null when closed rather than being conditionally removed from the tree by its parent - hooks still run every render, so useChat's state persists across visibility toggles. The dynamic import still only fires on the first open, so the initial-load win is unchanged. Verified via a real click-through (open, type, close, reopen): input persists correctly, and the panel chunk still has zero network requests on initial page load. Performance unchanged at 75. * chore(docs): lint fixes (import order, formatting) * fix(docs): fill the Ask Sim UI gap while the panel chunk loads handleOpen set open=true synchronously, hiding the trigger button before the dynamically imported panel had a chance to render anything (next/dynamic renders null by default with no loading option) - on a slow connection neither the button nor the panel was visible. Added a loading fallback in the same fixed position so there's no gap between the button disappearing and the real panel appearing. |
||
|
|
ef7c8e24b2 |
feat(platform): settings permissions, admin, billing attribution (#5545)
* fix(invites): preserve active organization for external access Keep organization activation server-owned so failed membership checks cannot clear a valid session context. * feat(admin, billing, settings): cleanup settings visibility, billing actor resolution, new admin routes * address comments * chore(db): reset pending migrations before staging merge Remove locally generated migrations so they can be regenerated against the latest staging schema without preserving stale snapshots or numbering. * regen migrations * address comments * chore(db): reset generated migrations before staging merge Remove this branch's generated migrations so they can be regenerated against the latest staging schema with fresh numbering. * upgrade global work * fix lint * address comments * legacy callbacks correctness * address comments * update * guardrail attribution |
||
|
|
5e891da819 | fix(editor): allow references in boolean variable values and sync tag selections to canvas preview (#5628) | ||
|
|
29cf3e7d9f |
fix(ashby): fail loudly instead of silently dropping malformed socialLinks (#5624)
* fix(ashby): fail loudly instead of silently dropping malformed socialLinks parseSocialLinksInput returned [] for any non-JSON-parseable input, and tools.config.params only sets result.socialLinks when the parsed array is non-empty — so a malformed socialLinks string (user typo, or a wand response that didn't follow the JSON-array prompt) silently omitted the field entirely. The Ashby candidate.update call then succeeded without applying the requested links, with no error surfaced to the workflow author. Throw a clear error instead, matching the existing throw-on-invalid-JSON pattern used elsewhere (e.g. blocks/airtable.ts). * fix(ashby): use getErrorMessage instead of inline error-message extraction check:utils bans the e instanceof Error ? e.message : fallback pattern in favor of getErrorMessage(e, fallback?) from @sim/utils/errors. |
||
|
|
b20bbdc111 |
fix(global-commands): use isContentEditable for the editable guard (#5623)
* fix(global-commands): use isContentEditable for the editable guard * chore(lint): keep focusable span in editable-guard test with biome-ignore |
||
|
|
5dec4f3168 |
fix(ashby): parse alternateEmailAddresses and socialLinks into arrays before dispatch (#5621)
* fix(ashby): parse alternateEmailAddresses and socialLinks into arrays before dispatch
The Ashby create_candidate and update_candidate tools require
alternateEmailAddresses (string[]) and socialLinks ({type,url}[]) as
JSON arrays in the request body, guarded by Array.isArray checks. The
block collected both through long-input text fields but forwarded the
raw string straight through to tools.config.params, so Array.isArray
was always false and both fields were silently dropped on every
create/update call.
Parse them in tools.config.params (execution-time, after variable
resolution) using the same comma-separated-or-JSON-array pattern used
elsewhere in the codebase (see blocks/findymail.ts), and add wandConfig
to both fields so the AI wand can generate well-formed input for them.
* fix(ashby): drop json-object generationType from array-shaped wandConfig fields
generationType: 'json-object' makes the wand API append an instruction
that the response must start with { and end with }, but
alternateEmailAddresses/socialLinks parse a raw JSON array or
comma-separated string, not an object. A wand-generated
{"emails":[...]}-shaped response would get comma-split into invalid
email fragments by parseStringListInput, and an object-wrapped
socialLinks response would get silently dropped by parseSocialLinksInput
returning []. Matches the existing array-field wandConfig pattern in
blocks/findymail.ts, which never sets generationType and relies on the
prompt text alone.
* fix(ashby): remove the non-functional candidateId filter from list_applications
Live-verified against Ashby's application.list endpoint: passing
candidateId (including a nonexistent UUID) returns identical, unfiltered
results either way — Ashby's API silently ignores this body field
entirely. Sending it gave users the false impression of filtering by
candidate while actually returning every application. Removed the param,
the tool type, and the block's filterCandidateId subBlock/wiring/input.
The correct path for a candidate's applications is ashby_get_candidate's
applicationIds field.
* fix(ashby): add subblock-id migration for the removed filterCandidateId field
The subblock ID stability CI check correctly caught that removing
filterCandidateId without a migration entry would silently drop the
value on already-deployed workflows. Added the standard _removed_ mapping,
following the same pattern already used for this block's prior removals
(emailType, phoneType, expandApplicationFormDefinition,
expandSurveyFormDefinitions).
|
||
|
|
f9b09ecfd4 |
fix(webhooks): resolve env var references before deploy-triggered subscription creation (#5619)
* fix(webhooks): resolve env var references before deploy-triggered subscription creation
Provider config fields like an API key can reference an environment
variable via {{VAR_NAME}}. The interactive trigger-save route already
resolved these before calling a provider's createSubscription, but the
async deployment-outbox path (workflow deploy -> saveTriggerWebhooksForDeploy
-> createExternalWebhookSubscription) did not, so the literal unresolved
{{VAR_NAME}} string was sent to the provider as the credential and
rejected. Resolve env vars in createExternalWebhookSubscription itself so
both callers behave the same; the persisted providerConfig keeps storing
the unresolved template, only the outbound call gets the resolved value.
* fix(webhooks): guard against a non-string workspaceId when resolving env vars
workflow.workspaceId as string | undefined was an unchecked cast on a
Record<string, unknown> — if a caller ever passed a workflow-like object
where workspaceId isn't actually a string, workspace-scoped {{VAR}}
references would silently stay unresolved and the provider would receive
the literal template as the credential, reproducing the exact class of bug
this change exists to fix. Replaced with a runtime typeof check that falls
back to undefined (personal-env-only resolution) instead of forwarding an
unvalidated value.
|
||
|
|
1fe94d3c7a |
feat(sidebar): add Cmd+B shortcut to toggle sidebar collapse (#5618)
* feat(sidebar): add Cmd+B shortcut to toggle sidebar collapse * fix(sidebar): skip Cmd+B sidebar toggle inside editable fields * fix(sidebar): don't dead-zone Cmd+B on read-only editors; drop stale Mod+B ownership * chore(lint): apply biome fixes |
||
|
|
f477faab4b |
fix(rich-markdown-editor): make drag-reorder of an image actually move it, on real browser payloads (#5617)
* fix(rich-markdown-editor): make drag-reorder of an image actually move it, on real browser payloads Reported on latest staging (deploy verified via CodePipeline): dragging an image duplicates it instead of moving it, and a click with a few px of hand jitter — which the draggable <img> turns into a native drag+drop-on-self — destroys the selection and duplicates too, reading as "I can't select this image anymore". Reproduced in real Chromium with real mouse input (micro-drag becomes dragstart, never click) and with the real drag payload shape. Two compounding root causes, both empirically pinned: - TipTap's node-view dragstart bypasses ProseMirror's drag serialization entirely (verified in @tiptap/core source: onDragStart only sets a drag image and NodeSelects the node — no PM text/html, no view.dragging). What the drop actually carries is the BROWSER's native enrichment: an image File plus text/html whose <img src> is the ABSOLUTE rendered URL. - Both hosted-image recognizers (extractEmbeddedFileRef and isInlineRouteSrc) reject absolute URLs, so the #5573 skip-check never matched on real drags: the drop fell into the upload branch (duplicate; original never moves). Falling through to PM instead would be no better: with view.dragging unset its default drop PARSES the html into a copy — persisting the display-layer src that share/export tracking don't recognize — and never deletes the original. Fix, at the mechanism level: - Normalize clipboard/dataTransfer srcs origin-relative before comparing (toSameOriginPath), keyed off window.location.origin deliberately rather than getBaseUrl(): the browser serializes against the origin the page is ACTUALLY viewed on, which legitimately diverges from the configured NEXT_PUBLIC_APP_URL (localhost dev, previews, apex-vs-www). Cross-origin srcs are never treated as ours. Applied to isInlineRouteSrc, hasHostedImageHtml, and findHostedImageAttrs (the paste-clone path had the same absolute-URL gap for browser-native "Copy Image"). - handleDrop performs the internal move itself when the drop's html references the currently-selected image node (htmlReferencesSrc — TipTap's dragstart guarantees that selection): same delete → map → insert shape as ProseMirror's own move, ending NodeSelected. Drop-on-self is a no-op that keeps the ring — which is what a jittery click now resolves to. Empirical before/after (real-Chromium harness driving the real editor + engine): pre-fix the drag leaves the original in place and uploads a duplicate; post-fix the node moves exactly once, nothing uploads, and the moved image stays selected. Paste-clone verified for both relative (PM copy) and absolute (native Copy Image) payloads. 604 unit tests pass including 11 new ones for the origin-aware helpers. * fix(rich-markdown-editor): no-op invalid drop points, match external-image identity by absolute URL Greptile round 1, both real: - dropPoint can return null (no valid insertion point); the raw coords.pos fallback could make tr.insert throw — PM's own null-fallback is only safe because it uses the forgiving replaceRangeWith. A null drop point is now a handled no-op: the node stays put, still selected. - A doc image with a cross-origin src (README badge, CDN image) failed the same-origin identity check, so drag-reordering IT still fell into the duplicate path. htmlReferencesSrc now compares full ABSOLUTE URLs — identity is the question there, not hosted-by-us membership — while the hosted-recognition helpers stay same-origin-scoped. New regression test fails pre-fix. Also folded the remaining inline comments into the handleDrop TSDoc and gave IMG_SRC_RE / INLINE_ROUTE_QUERY_KEYS proper TSDoc (production diff is now TSDoc-only). |
||
|
|
a2f868c397 |
fix(demo): preload the Cal.com booking embed while the visitor fills the form (#5616)
* fix(demo): preload the Cal.com booking embed while the visitor fills the form The embed script, booker iframe, and its assets only started downloading after the visitor pressed Continue, so the calendar took several seconds to appear. Warm the whole path on first form focus via the embed's documented preload instruction (hidden ?preload=true iframe caches the booker assets) plus a preconnect to app.cal.com. Nothing Cal.com-related loads at initial page load, so Lighthouse/LCP are untouched. * fix(demo): retry embed warm-up on failure, preconnect only on first focus Reset the preload guard when embed.js fails to load so a later focus can retry, and move the app.cal.com preconnect from render into the focus-triggered preload path so initial page load makes zero Cal.com connections. |
||
|
|
0cc6ed61d3 |
improvement(emcn): multi-select selectors + Wizard on ChipModal (#5614)
* feat(sub-block): support multi-select in channel/user selector fields * improvement(emcn): migrate Wizard primitive to ChipModal * fix(emcn): wizard height sizes whole dialog; restore dialog description |
||
|
|
1b82b9763c | fix(custom-blocks): restrict iconUrl to https or internal serve paths (#5613) | ||
|
|
b629292d5b |
improvement(chat): shimmer active subagent and tool labels instead of spinners (#5612)
* improvement(chat): shimmer active subagent and tool labels instead of spinners * improvement(chat): address review — focus-visible chevron, single shimmer source, reduced-motion rest color * improvement(chat): pulse shimmer text under reduced motion so running state stays visible * improvement(chat): reset background-clip in reduced-motion shimmer fallback * fix(chat): apply reduced-motion shimmer fallback in dark mode too |
||
|
|
fcf4e02930 |
fix(landing): repair Lighthouse-flagged CWV audits on production (#5605)
* fix(landing): repair Lighthouse-flagged CWV audits on production
Empirically verified against a live full Lighthouse run of www.sim.ai
(production, pre-fix) plus a local build of the exact deployed commit with
source maps temporarily enabled for root-causing. Distinguished genuinely
failing audits from passing ones already misread as broken.
- fetchPriority missing on every LCP hero image: `priority` generates a
preload <link> but Next does not auto-add fetchpriority=high to it -
confirmed via raw deployed HTML diff. Added explicit fetchPriority='high'
to all 5 priority Image usages (hero, enterprise, blog/library post +
index cards).
- valid-source-maps failing: production ships no source maps at all
(productionBrowserSourceMaps defaults false). Enabled it - safe here since
this repo's frontend is already fully open source, so no incremental
exposure versus Next's default.
- image-delivery-insight (55.8KB wasted): feature-integrate-ui.png's `sizes`
hint was a flat 1050px regardless of viewport, so mobile fetched the
1920w variant for a ~423px real render. Replaced with a responsive sizes
expression derived from the sibling backdrop image's own (already
correct) hint, scaled by the callout's documented 125% overhang.
- cache-insight (best-fixable portion): _next/static/* filenames are
content-hashed and immutable per deploy, but shared one cache rule with
unhashed /public assets, capping both at 1-day max-age. Split into two
rules - hashed assets now get 1-year immutable, unhashed assets keep the
shorter revalidating TTL. Verified via a real build + server that both
paths now return the correct distinct header.
Investigated and NOT changed (documented, not assumed):
- legacy-javascript-insight (14KB): traced via sourcemap to
next/dist/build/polyfills/polyfill-module.js - Next's own built-in
polyfill bundle, not our code or a dependency, and not exposed via any
next.config.ts option. No browserslist misconfiguration on our end (none
exists; Next already defaults to its modern target).
- forced-reflow-insight: even with source maps present locally, the
dominant cost (335-417ms) stayed [unattributed] by Chrome's own profiler,
and the small attributed slice was non-deterministic between our own
chunk and a third-party script (HubSpot analytics) across runs - not a
confident single root cause worth a targeted fix.
- render-blocking-insight / network-dependency-tree / bf-cache: bf-cache's
actual failure reason is Cache-Control: no-store on the main document -
the exact root cause already fixed on staging (PR #5522/#5528, the
PublicEnvScript/unstable_noStore fix) but not yet promoted to main/prod.
Resolves once that ships, not additional work here.
* fix(landing): convert mothership cover from PNG to JPEG (/blog LCP 6.6s -> 2.8s)
Ran a full Lighthouse sweep across every public page as requested. /blog
scored 73 (LCP 6.6s) while every other page scored 95+ - reproduced
consistently across 3 runs, not noise. Traced via lcp-breakdown-insight:
the LCP image (mothership/cover.png, 241KB even after the earlier palette
compression pass) took 6+ seconds to download on simulated mobile
throttling, well beyond what its size should cost.
PNG is a poor fit for this illustration's subtle gradients versus JPEG's
lossy compression. Verified empirically before converting: same 1920x1080
resolution, visually identical (spot-checked), 241KB -> 65KB (73% smaller).
No other cover in the content set uses PNG and benefits the same way
(checked copilot/cover.png, the only other PNG cover - already optimal at
64KB, converting it yielded no improvement, left unchanged).
Verified fix: /blog score 73->93, LCP 6.6s->2.8s, reproduced across 3 runs.
* fix(landing): correct mobile sizes tier, drop non-functional cache rule
- integrations-callout: account for FeatureCard's max-lg:grid-cols-1 mobile
stack in the sizes hint, verified against Lighthouse's measured mobile
render width.
- next.config: remove a custom _next/static cache-control rule that never
actually fired (confirmed via header-marker test) - Next's own built-in
default already applies the correct immutable 1yr cache to that path.
* fix(landing): correct sizes underestimate + fix dead .map header rule
- integrations-callout: derive sizes from the section's actual grid math
(fixed 386px copy column, 40px gap, section gutters) instead of an
approximated vw fraction. Verified against a static reproduction of the
layout rendered at each Tailwind breakpoint - the old 110vw mobile tier
underestimated real render width by ~3% right at the 1023px stack
boundary, which could cause the browser to pick a too-small srcset
candidate and upscale.
- next.config: the .map header rule's trailing `$` was read as a literal
character by Next's path-to-regexp source matcher, not a regex anchor,
so the rule never matched a real .map URL (confirmed via routes-manifest
regex + a live header check). Removed the dead anchor and added a
bounded Cache-Control so a future decision to stop shipping source maps
isn't undermined by a 1yr immutable cache on already-fetched maps.
* fix(llms): serve well-formed llms.txt, remove Mothership + dead static files
Both the marketing site and docs site's llms.txt validator errors ("does
not appear to contain any links") traced to the same root cause: a static
public/llms.txt shadowed a better-written, already-existing dynamic
app/llms.txt route, and every "link" in the static files (and in the
docs app's auto-generated route) was bare `label: url` text, not Markdown
link syntax - so a strict Markdown-link parser found zero matches even
though URLs were visibly present.
- apps/sim: delete public/llms.txt (dead code, shadowing the properly
Markdown-linked app/llms.txt route.ts, confirmed via production headers
showing the static file was what actually served). Fix llms-full.txt's
Links/Support/Legal sections to use [label](url) syntax, correct a
stale "Next.js 15" reference, and replace "Mothership" with "Chat" per
the constitution's language rules.
- apps/docs: same shadowing issue - delete the orphaned public/llms.txt
(also still said "Mothership"). Fix the auto-generated per-page link
list in app/llms.txt/route.ts to emit [title](url) instead of
"title: url" for every documentation page.
* fix(llms): actually include the route.ts fixes from the prior commit
The prior commit (
|
||
|
|
83c532ce36 | improvement(files): show loading spinner in file preview content area (#5610) | ||
|
|
0aa23090e9 |
fix(files): unwedge post-stream read-only editor; stop bullet Backspace from destroying the image below (#5608)
* fix(rich-markdown-editor): stop Backspace on an empty bullet from destroying the image below it Reported: clearing an empty bullet with an image after it "nuked the bullet point and the image". Reproduced: when the emptied bullet is the doc's first block, removeEmptyWrappedBlock's Selection.near(resolve(start), -1) finds no text position behind it and silently lands a NodeSelection on the FOLLOWING image — so the user's next keystroke is destructive (a second Backspace while clearing deletes the image; typing replaces it). Only-first-block explains why it wouldn't re-repro. The selection left behind is now always a caret: end of the previous textblock first, else a gap cursor at the deletion point when the neighbour is a leaf (typing there inserts a new block instead of replacing the image), else the next textblock. The regression test surfaced two adjacent gap-cursor crashes on Backspace, both reachable on current staging whenever a gap cursor exists (e.g. between two dividers/images, the data-gap-between-leaves state): - our own handler threw RangeError from $from.before(0) on a depth-0 (doc-start) gap cursor - with that guarded by falling through, TipTap's blockquote Backspace handler crashes on the same resolution ($from.node(-1) is undefined) — so a doc-start gap cursor consumes the key instead (there is nothing before it for Backspace to act on) * fix(files): poll the content query while the post-stream reconcile waits, so the editor can't wedge read-only Reported: images "don't get selected sometimes" (can't grab/drag/resize, doc uneditable) until a full refresh. Reproduced in a real-Chromium harness driving the actual RichMarkdownEditor + engine: after an agent stream settles, the reconcile phase exits only when a fetch shows the server content advanced past the pre-stream baseline — but that exit had no retry. A single refetch racing the agent's write (or the mothership invalidation never reaching this surface — it's the only place that invalidates this query) left the editor locked read-only indefinitely: contenteditable=false, images not grabbable, until refetchOnWindowFocus or a reload happened to run. Fix: while (and only while) the reducer is in `reconciling`, the content query polls via react-query's refetchInterval — the same pattern this module already uses for the generated-doc 409 polling, and like it, bounded (45s window; past that the write has almost certainly failed and refetchOnWindowFocus remains the recovery). The interval is the function form reading the phase through a ref, re-evaluated by react-query after every fetch, so polling stops the moment a fetch advances without needing an extra render. Harness (real Chromium, real editor + engine, in-memory server): pre-fix the editor stays editable=false with fetches frozen at 1 indefinitely while the server holds the new content; post-fix it unlocks within one poll (~1.5s), polling stops immediately after finalize, and the streamed image click-selects. Unit tests drive stream -> settle -> advance through the real engine and assert the interval flips on/off with the phase (2 of 3 fail pre-fix), plus the bounded window and a no-polling guard for plain at-rest editing. * test(files): cover the refetchInterval passthrough against real react-query Both consumers' test setups (the reconcile unit tests and the browser harness) replace @/hooks/queries/workspace-files, so the real hook's two changed lines were exercised by nothing but the type-checker. These render the real useWorkspaceFileContent under a real QueryClientProvider with a stubbed fetch: no polling by default, polling with a numeric interval, and the function form re-evaluated so flipping its condition stops the polling — the exact mechanism the reconcile fix depends on. The two polling tests fail against the pre-fix hook. * fix(files): degrade reconcile polling to a slow cadence instead of stopping; prove findFrom textOnly never leaf-selects Greptile round 1: - Real gap in my bounded window: past 45s the poll stopped outright, leaving the reducer wedged in reconciling with only focus-refetch/reload as recovery — the exact failure shape this PR exists to remove, just later. Polling now degrades to a 15s cadence instead of stopping, so a write landing late (slow job, replica catch-up) is still picked up automatically; react-query pauses interval refetches in background tabs by default, so an abandoned doc doesn't poll unattended. - Refuted with source + an executable test: Selection.findFrom($gap, -1, true) cannot return a NodeSelection — prosemirror-state's findSelectionIn skips atoms entirely under textOnly (`!text && isSelectable`). New regression test pins the exact scenario (image directly BEFORE the emptied bullet): backward search returns null, the gap-cursor/forward-caret branches take over, and the image is never silently selected. |
||
|
|
d165712d54 |
fix(files-upload): enforce workspace authorization on mothership uploads (#5604)
* fix(files-upload): enforce workspace authorization on mothership uploads The mothership context in POST /api/files/upload skipped the workspace permission and storage quota checks that every sibling context enforces, letting a caller write files into a workspace they have no access to. * fix(files-upload): check mothership quota once against the full batch Resolve the mothership permission and quota check once per request (mirroring the existing execution-context pattern) instead of per file: a per-file quota check let a multi-file batch exceed the caller's quota since each file's own size fit even when the combined total did not. Also corrects the missing-workspaceId error message, which named the chat context instead of mothership. |
||
|
|
67a2f00266 |
perf(search): cap Cmd-K result groups so typing isn't blocked by reshuffle (#5597)
* perf(search): cap Cmd-K result groups so typing isn't blocked by reshuffle Every result group re-rendered its full match set on each keystroke — the catalog alone is 1,000+ tool operations, plus all workflows/files in large workspaces — so the deferred re-render that reshuffles results stalls the input and drops the next character. Add a per-group cap (filterAndCap, MAX_RESULTS_PER_GROUP=50) applied to every variable-size group. Results are already score-sorted, so the cap only trims the low-relevance tail while keeping the DOM and per-keystroke reconciliation bounded. No UX changes. * perf(search): scope the result cap to active queries, never the browse list Keep the empty state byte-for-byte identical to before — capping applies only to the top-ranked matches of an active query (the reshuffling per-keystroke render that stalls input), never to the full browsable list. No browsable result a user could otherwise see is hidden. * fix(search): rank blocks/tools by name so exact name matches win Blocks and tools were ranked against their full searchValue (name + type + every command-searchable option label), so an exact name match couldn't earn the exact-match bonus and paid a length penalty inflated by option text — e.g. "Agent" lost to "Pi Coding Agent" for the query "agent". Rank by name first via a new optional secondary accessor on filterAndSort/filterAndCap, falling back to searchValue only when the name doesn't match, so an exact name match always wins while a block stays findable by an option label. * fix(search): treat whitespace-only queries as browse A whitespace-only query (e.g. a single space) was truthy, so it both filtered (a space matches the spaces in multi-word labels) and capped large groups to 50 while the palette looked empty. Trim the query at the source in filterAndSort so every caller treats whitespace-only as browse, and decide the cap on the trimmed query — whitespace-only input now returns the full, unfiltered browse state. * fix(search): keep integrations catalog hidden on whitespace-only input The filteredIntegrations guard used the raw deferredSearch, so a whitespace-only value passed it while filterAndCap trimmed the same value to browse and returned the full catalog. Guard on deferredSearch.trim() to match the trimmed-emptiness semantics — the catalog stays hidden until the user types something meaningful. |
||
|
|
eb12333032 |
fix(chat-otp): re-check authType before minting deployment auth cookie (#5600)
PUT verify no longer trusts a stale authType at cookie-mint time — it now re-checks the chat is still email-auth before issuing the cookie, matching the existing POST guard and the public-file OTP route. |
||
|
|
b73116226a |
fix(file-viewer): sanitize docx hyperlink hrefs to block javascript: XSS (#5599)
Sanitize anchor hrefs rendered by docx-preview after render, stripping any scheme outside http/https/mailto (same allowlist already used by the PPTX renderer). Covers both the workspace file viewer and the unauthenticated public share page, which reuse the same component. |
||
|
|
e2e29eed30 |
fix(api): bound request-body reads on speech/knowledge-chunks/help routes (#5601)
* fix(api): bound request-body reads on speech/knowledge-chunks/help routes Replace unbounded request.json()/formData() reads with the existing size-limited helpers, and move auth ahead of the body read on the knowledge chunks route so unauthenticated callers can't force a large allocation before being rejected. * fix(knowledge): reject non-string workflowId instead of silently skipping authorization A truthy non-string workflowId previously fell through the type guard and skipped the workflow-scoped write authorization entirely. Validate the type explicitly and fail closed with a 400 instead. |
||
|
|
b486aba07e |
fix(pricing): route Talk to sales CTA to demo request form instead of signup (#5602)
- pricing page's enterprise card was labeled "Talk to sales" but linked to /signup for every card, sending visitors to self-serve signup instead of the demo-request flow every other "Contact sales" CTA on the site uses - resolveCta now keys off the CTA's sales intent to pick the right href - extracted the /signup and /demo route literals (previously hardcoded independently in 6 files) into a single shared apps/sim/app/(landing)/constants.ts so no CTA can drift to the wrong destination again - hoisted the static per-column comparison sections out of render and deduped the annual-discount price math in pricing-plans.tsx |
||
|
|
7c2de1d426 |
feat(providers): add xAI to hosted key rotation pool (#5574)
* feat(providers): add xAI to hosted key rotation pool Wires xai into the same hosted-key mechanism as OpenAI, Anthropic, and Z.ai so Sim can serve Grok models without users bringing their own key. * fix(pi): include xai in Pi cloud-mode workspace BYOK read-back xai was fully wired as a Pi-supported provider but missing from WORKSPACE_BYOK_PROVIDERS, so a stored workspace xAI key was never read back for cloud-mode Pi runs. * fix(byok): add xai settings UI row xai is both hosted (Pi block hides its inline API key field for hosted models) and Pi-supported (cloud mode requires a user key), so without a Settings > BYOK row users had no way to supply an xai key for Pi cloud runs. |
||
|
|
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. |
||
|
|
591516a9b9 |
fix(rich-markdown-editor): fix mention chip losing ambient color inside links/h6 (#5594)
* fix(rich-markdown-editor): fix mention chip losing ambient color (same class as #5573) Auditing the whole "an element's own explicit color always wins over an inherited one" bug class (previously fixed for strong/em/code/del/s vs. links and h6 in #5573) turned up one more instance: the @-mention chip's label hardcoded text-[var(--text-primary)], which is redundant with the prose default anyway (matching the strong/em/code precedent) and silently overrides any ambient color a mention's container legitimately sets — a link's blue, or h6's dimmer --text-secondary — since a mention is inline content that can appear inside either (e.g. "###### see @some-file"). Removed the hardcoded color entirely so the label inherits correctly in every context, same fix as strong/em/code. The icon's own monochrome --text-icon fallback is untouched (icons intentionally don't follow ambient text color). New test renders MentionChipView directly and asserts the wrapper carries no explicit text-color utility class; verified it fails against the pre-fix className. * fix(rich-markdown-editor): broaden mention-chip color-regression guard beyond the exact old class Greptile: the test only matched the literal old text-[var(--text-primary)] string — a future edit swapping it for e.g. text-[var(--text-secondary)] or text-blue-500 would still silently reintroduce the ambient-color bug and pass this test. Now checks every non-descendant-scoped (excludes the [&>svg]: icon rule) text-* utility on the wrapper against a color-shaped pattern (arbitrary value, color-shade pairs, or a named color keyword), so any bare text color slipping back in fails. Verified against a text-blue-500 regression. * fix(rich-markdown-editor): close the semantic-Tailwind-color gap in the mention-chip test Greptile: the color-shaped regex still missed semantic theme tokens (text-primary, text-muted-foreground, text-chart-1, etc.) since they don't match a shade-suffix or bracket pattern. Rather than keep enumerating Tailwind's color-naming schemes, flag ANY unscoped text-* utility on the wrapper — none is legitimate on this chip today, so this can only be a color slipping back in. Verified against text-primary/text-muted-foreground/text-chart-1 regressions. * fix(rich-markdown-editor): catch Tailwind's self-targeting [&]:text-* variant too Greptile: the previous filter excluded ANY class starting with `[&`, which also dropped Tailwind's self-targeting arbitrary variant (`[&]:text-primary` applies to the element itself, same as a bare `text-primary`) — only descendant variants like `[&>svg]:text-*` should be excluded. Now explicitly catches both the bare and `[&]:` forms. Verified against a `[&]:text-primary` regression. |
||
|
|
d14c304787 |
fix(og-image): match sim.ai OG image colors to live landing tokens (#5592)
The wordmark ink and background were slightly off from the actual site: #1a1a1a on #f8f8f8 in the static OG asset vs var(--text-body) (#3b3b3b) on var(--bg) (#fefefe) as rendered on the live landing page. Recolored the same wordmark artwork in place to match exactly - alpha reconstructed from the existing two-color image and recomposited onto the new colors, so the glyph geometry/anti-aliasing is unchanged. |
||
|
|
3a2f4e5c8f |
feat(custom-blocks): add deploy_custom_block copilot tool (#5532)
* feat(custom-blocks): add deploy_custom_block copilot tool * feat(copilot): send workspace entitlements to the mothership * chore(copilot): sync tool catalog — plan-neutral deploy trigger text * refactor(copilot): extract entitlements registry with add-an-entitlement recipe * fix(custom-blocks): review fixes — undeploy without enterprise, array bounds, whitespace name * fix(custom-blocks): enforce per-item field limits from the REST contract * fix(custom-blocks): enterprise gate applies to first publish only, matching REST * chore(copilot): sync tool catalog — deploy_custom_block requires name |
||
|
|
fca5f10f86 |
feat(workflow-editor): open block palette on edge drag-release with auto-connect (#5586)
* feat(workflow-editor): open block palette on edge drag-release with auto-connect * fix(workflow-editor): correct drag-release drop coords, scoping, and container placement * fix(workflow-editor): correlate drag-release palette selection with a token * fix(workflow-editor): preserve tool operation preset on in-container drag-release * fix(workflow-editor): wire drag-release edge from the actual source handle via handleToolbarDrop * refactor(workflow-editor): collapse drag-release correlation into one store field |
||
|
|
3d02bbbe62 |
fix(mcp): coerce corrupted consecutiveFailures instead of crashing the whole server list (#5593)
Root cause: updateServerStatus() only fell back to the default status
config when the whole statusConfig column was null/undefined, not when
it was a real object missing consecutiveFailures (e.g. the column's
'{}' default on server creation). currentConfig.consecutiveFailures
was then undefined, undefined + 1 evaluated to NaN, and
JSON.stringify(NaN) persisted as a literal `null` into the DB the
first time a freshly-created server had a connection failure.
That corrupted value then failed listMcpServersContract's Zod parse
client-side (consecutiveFailures: z.number() rejects null), and since
the response is a single array, one bad server blanked the entire MCP
servers list with "Response failed contract validation" for the whole
workspace — currently affecting 81 servers across 69 production
workspaces.
Two fixes:
- service.ts: normalize the read-back statusConfig so
consecutiveFailures is always a real number, never NaN, going
forward.
- contracts/mcp.ts: coerce any non-number consecutiveFailures
(including the already-corrupted `null` rows) to the schema's
default of 0 instead of failing validation, so every
already-affected workspace self-heals on next load with no DB
migration needed.
|
||
|
|
2ba0b5837d |
fix(rich-markdown-editor): reliable image selection + serialization/paste polish (#5590)
* fix(rich-markdown-editor): reliable image selection + resize and broken-image polish - Reactive editability. The editor runs with shouldRerenderOnTransaction:false, so a node view that read editor.isEditable once at render kept a stale value after setEditable() toggled (e.g. an agent stream settling into the doc), leaving a pasted image showing read-only affordances and code blocks stuck on their read-only label until a full refresh. A shared useEditorEditable hook subscribes to the editor's update/transaction events so both node views track editability reactively. - Deterministic click-to-select. A handleClickOn plugin sets the image's NodeSelection on a plain click so selecting never depends on ProseMirror's click-vs-drag arbitration; grab-anywhere drag-reorder is kept, and modified clicks (Cmd/Ctrl to follow a linked badge) fall through. - Resize commits once. The width previews in local state during the drag and commits to the node once on pointer-up (or pointer-cancel), so a resize is a single undo step and an interrupted drag isn't lost. - Broken-image placeholder. A src that fails to load renders as a visible box with its alt text and stays selectable, instead of collapsing to a bare broken-icon. * fix(rich-markdown-editor): keep bare URLs and autolinks bare on serialize The normalizing serializer rewrote a bare URL or <url>/<email> autolink to [url](url) / [a@b.com](mailto:a@b.com) on every save, churning every README's links. postProcessSerializedMarkdown now collapses a link back to its bare form when the visible text already equals the destination (a plain http(s) URL, or an email behind mailto:) — GFM re-autolinks it, so the round-trip is identical with a far quieter diff. Titled links, explicit links, and any link inside a fenced/inline code region are left untouched. Idempotent. * feat(rich-markdown-editor): linkify a selection when a URL is pasted over it Pasting a single URL (or a bare www. host / email) over a non-empty text selection within one block now wraps the selection in a link, keeping the visible text. www. gets https://, an email gets mailto:, and the href is scheme-sanitized (javascript:/data: rejected; mailto: requires a real user@host address). Collapsed carets, cross-block selections, multi-word pastes, node selections, and code contexts fall through to normal paste. * chore(rich-markdown-editor): drop useless String.raw in highlight.ts biome 2.0's noUselessStringRaw flags HIGHLIGHT_BODY — its pattern has no escape sequences, so String.raw is equivalent to a plain template literal (byte-identical value; interpolated into the other String.raw regexes unchanged). Pre-existing on staging; the repo-wide lint gate blocks CI on it. |
||
|
|
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. |
||
|
|
1c604152b3 | fix(landing): contain sr-only logos heading to stop phantom root scrollbar (#5585) | ||
|
|
da2371adf8 | fix(models): restore Anthropic models in landing page compare chart (#5584) | ||
|
|
8525ba5a2b |
feat(landing): add Share chip to integration and model pages (#5582)
- add a Share chip (copy link / X / LinkedIn) to integration and
model detail pages, matching the bordered secondary-pill chip
already used for View docs / All {provider} models
- rework ShareButton to render as a Chip everywhere (blog, library,
integrations, models) instead of a bespoke muted-text trigger, and
switch its copy-link state to the shared useCopyToClipboard hook
|
||
|
|
7962236719 |
improvement(custom-blocks): hardened delete with usage count + per-input required option (#5575)
* improvement(custom-blocks): usage visibility + type-to-confirm delete * feat(custom-blocks): per-input required option * improvement(custom-blocks): replace usage tab with delete-confirmation usage count * fix(custom-blocks): escape LIKE wildcards in usage scan + fresh count on delete modal * fix(custom-blocks): explicit ESCAPE clause on usage-scan LIKE prefilter |
||
|
|
8e7e2db35e |
feat(docs): update favicon, fix icon contrast, add integration intros (#5581)
* feat(docs): update favicon, fix icon contrast, add integration intros - replace docs favicon/icon assets with new sim logo - fix light-tile icon contrast in BlockInfoCard so icons like Daytona no longer render invisible (white-on-white); matches sim toolbar's brightness-based contrast logic - add missing MANUAL-CONTENT-START:intro sections to 30 integration docs pages that lacked context/links * chore(docs): normalize spacing from generate-docs pass Ran the docs generator to verify our new intro sections survive regeneration cleanly. It reformats the blank line before "## Usage Instructions" to match every other manually-annotated page. * fix(context-dev): remove prefetch and simplified-brand tools - remove context_dev_prefetch_domain, context_dev_prefetch_by_email, and context_dev_get_brand_simplified tools and their block operation entries; these aren't meant for general use - regenerate docs to drop their sections from context_dev.mdx |
||
|
|
f4d47ed826 |
feat(slack): reusable custom bot credentials, slack_v2 block (preview), redesigned trigger (#5323)
* feat(slack): enable assistant-agent tools via assistant:write scope Add assistant:write, app_mentions:read, and im:history to the Slack bot OAuth scopes so the Set Assistant Status / Title / Suggested Prompts tools (assistant.threads.*) work with users' existing Slack credentials — no new app or credentials required. Restore the action_assistant trigger capability (scope assistant:write) in the manifest generator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WpeT8J5yVCrrNQB9Hzm9uS * Add slack trigger * fix channel picker in slack trigger * improvement(slack-trigger): reorder app type, gate account to sim mode, add channel-id input * fix(slack-trigger): drop unmapped events from filter, resolve oauth token for reaction text + file downloads * fix(slack-trigger): empty operation selection fires nothing; resolve token via credential owner not execution actor * fix(slack-trigger): ignore message edit/delete/system subtypes; prefer channel picker over stale manual ids * feat(slack-trigger): single-event model with contextual filters and full event catalog * fix(slack-trigger): apply event/channel/bot filters on custom-app path too * fix(slack-trigger): don't drop edit/delete events when channel_type is absent * feat(slack): reusable custom bot credentials, slack_v2 block, interactivity triggers - Custom bot as a workspace service-account credential (set up once, shared ingest URL /api/webhooks/slack/custom/{credentialId}, reused across triggers and actions) - slack_v2 action block: credential-based Custom Bot auth alongside Sim OAuth; v1 hidden from toolbar - Interaction triggers (block_actions / view_submission) with optional action/callback id filter; settings.interactivity in generated manifests - Setup wizard: name + description, full permissions by default with ChipDropdown customization; reconnect mode rotates secrets in place - Centralized service-account token resolution (unknown provider fails loudly) - Shared Slack webhook fan-out dispatcher for native + custom ingest routes * chore(api-validation): bump route baseline to 924 after staging merge * feat(slack): preview-gate slack_v2 and the custom-bot credential surfaces slack_v2 (block + hosted slack_oauth trigger) ships preview: true — hidden from all discovery until revealed via block-visibility AppConfig or PREVIEW_BLOCKS. v1 stays toolbar-visible with the legacy slack_webhook trigger until v2 GAs. The integrations-page custom-bot setup surface rides the same flag via isHiddenUnder(slack_v2); placed instances, existing credentials, and ingest/execution paths are never gated. * fix(slack): v1 keeps slack_webhook trigger subblocks; handle object-form event channels - v1 spread had been swapped to slack_oauth's trigger subblocks (shared with v2), leaving its slack_webhook deploy path without signing-secret config (Bugbot high). v1 now carries the legacy trigger set again; v2 swaps them for slack_oauth's. - resolveSlackEventChannel reads channel.id for channel_created/channel_rename payloads, so channel filters no longer drop every rename event. * fix(slack): default absent appType to custom at deploy; deactivate custom-bot webhooks on credential delete - appType is hidden and seeded 'custom' by value(), which only covers editor-created blocks; defaultValue now persists it via buildProviderConfig and the deploy fallback flips to custom (the only exposed mode this ship) - deleting a slack-custom-bot credential now also deactivates provider='slack' webhooks routed by that credential id, not just native slack_app rows * fix(slack): resolve credential owner for deploy-time team_id lookup A teammate deploying a trigger wired to a shared Slack credential isn't the credential owner; refreshAccessTokenIfNeeded only loads tokens for the owning user. Resolve the account owner first, mirroring the runtime formatInput path. * chore(slack): reconcile staging merge - nullable webhook.path coalesced at correlation/payload/tiktok boundaries - slack dispatch delegates to staging's dispatchResolvedWebhookTarget (shared preprocess/deployment/filter/enqueue lifecycle), keeping the skip-reason diagnostics; route tests reworked around that seam - api-validation route baseline 924 -> 926 * fix(slack): workspace-scope bot credentials at deploy; recreate webhooks on routing transitions - a bot credential id is semi-public (embedded in Slack Request URLs), so the custom deploy branch now rejects credentials outside the workflow's workspace - needsRecreation also compares path/routingKey, so a row from an older routing model can't survive redeploy as a stale delivery surface * test(slack): pin fail-closed behavior for empty/missing event selection * fix(slack): 409 on custom-bot name collision instead of silently returning the existing credential The service-account dedupe matches on displayName, which defaults to the Slack team name — shared by every bot in that workspace. A second unnamed bot create returned the first credential as success, orphaning the new id already pasted into the Slack Request URL. Same-id replays stay idempotent; different-id collisions now fail loudly so the wizard prompts for a distinct name. * fix(slack): reconnect surfaces Atlassian error codes and persists name/description edits - PUT credential route now returns the Atlassian provider code (providerErrorCode -> code) so reconnect failures map to specific token/domain messages, matching create - Google/Atlassian reconnect send + seed displayName/description (parity with Slack); edits are no longer silently discarded, and empty fields don't clobber existing values * fix(slack): require bot name; propagate rotated bot_user_id to webhooks on reconnect - the setup wizard now requires a bot name (canAdvance), so the credential name, manifest app name, and uniqueness key all use the user's choice instead of the shared Slack team-name fallback that collided for a second bot in one workspace - reconnect that changes the bot user id (recreated Slack app) now updates the bot_user_id cached in each bound webhook's providerConfig, so reaction self-drop keeps working instead of letting the bot's own reactions re-enter --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f60d41af40 |
feat(landing): enterprise page redesign with platform-loop hero and feature graphics (#5535)
* feat(landing): add enterprise link to navbar and footer * feat(landing): redesign enterprise page with platform-loop hero and feature graphics - New enterprise hero with animated platform loop (sidebar + home stage) and hero background - Nine redesigned feature tiles under enterprise/components/feature-graphics with a shared monochrome design vocabulary, per-tile tones, and CSS-module animations - Shared hero-header component and landing-layout constants; hero/platform/solutions pages aligned to the same layout system Co-authored-by: Cursor <cursoragent@cursor.com> * chore(dev): allow 127.0.0.1 dev origins and skip root redirect in dev Co-authored-by: Cursor <cursoragent@cursor.com> * chore(skills): install make-interfaces-feel-better skill Co-authored-by: Cursor <cursoragent@cursor.com> * cleanup(landing): enterprise redesign polish + asset compression - Fix ChipLink chrome overrides in navbar/mobile-nav to use variant='border' - Dedup elapsed-time reveal effects in enterprise-home-stage into one hook - Remove unused FeatureGraphicNode component and barrel export - Convert enterprise-hero-background.png (8.9MB lossless) to WebP q90 (1.07MB, near-lossless) - Fix team-avatar-1/2/3.png mislabeling (actual JPEG bytes) to correct .jpg extension * fix(landing): keep feature-tile graphics uncropped at small breakpoints Feature tiles shrank their min-height on small screens while the tallest vignettes (audit ledger, staging panel) still needed ~300px of visual slot, cropping their tops against the slot's overflow-hidden. Tiles now hold one 440px min-height everywhere. The 3-up grid also collapses to two columns below lg instead of md, and the fixed-width canvases (access graph, standards seal, ops router) scale down on the narrow two-column band and the 3-up row just past lg so outer labels and chips are never cut off. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(landing): regroup enterprise feature cards into 4/4/2/2 at two-column band Merge the four enterprise feature sections into one EnterpriseFeatureGrid so the 640-1023px two-column layout fills 4/4/2/2 with no orphan empty cell; lg+ and <sm layouts are unchanged via sm:max-lg order utilities. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
65b1ca125d |
feat(context-dev): add Context.dev to hosted key rotation pool (#5576)
Wires all 22 context_dev tools into the same hosted-key mechanism as Exa: CONTEXT_DEV_API_KEY_COUNT/1..N rotation, BYOK provider registration, and hideWhenHosted on the block's API key field. Cost is read directly from each response's reported credits_consumed rather than estimated per endpoint. |
||
|
|
5de66db678 |
fix(rich-markdown-editor): stop image dupe-uploads on drag/paste; fix link color under bold/italic/strikethrough/code (#5573)
* fix(rich-markdown-editor): stop drag/paste of an existing image from re-uploading a duplicate
Dragging an image block to reorder it, or copy-pasting an already-hosted image within the
editor, both re-uploaded the image as a brand-new file instead of reusing/moving the existing
one:
- Dragging an <img> to reorder it is a native HTML5 drag; browsers synthesize an image File
into event.dataTransfer for it (the same mechanism that lets you drag a web image to your
desktop), indistinguishable from a real external drop by dataTransfer contents alone. Our
handleDrop treated that File as a genuinely new image, uploaded it, and inserted a duplicate
node — while ProseMirror's own default move logic never got to run, so the original was left
behind too. Fixed by checking `view.dragging` (ProseMirror's own signal that a drop follows a
dragstart within this same view) and bailing out to let its default move logic run.
- The same browser behavior applies to copy-paste: selecting a rendered <img> already on the
page and pressing Cmd+C puts BOTH `text/html` (the real node, with its real hosted src) AND a
synthesized image File onto the clipboard. Our handlePaste preferred the File, re-uploading and
inserting a new node rather than cloning the original (silently dropping width/href/title in
the process). Fixed by preferring the HTML sibling — via the existing extractEmbeddedFileRef
helper — whenever it already names one of our own hosted files.
* fix(rich-markdown-editor): fix link color lost under bold/italic/strikethrough/code
strong/em/del/s/code each set their own explicit `color` for the plain (no-link) case. Nested
inside a link, that explicit rule on the mark itself always wins over the color inherited from
the ancestor <a> — an inherited value never beats an element's own explicit rule, regardless of
how specific the ancestor's selector is. So an italic (or bold/struck-through/inline-code) link
rendered in the mark's plain-text color instead of the link's blue.
Adds an explicit `.rich-markdown-prose a <mark>` / `.rich-markdown-prose <mark> a` override,
covering both DOM nesting orders since ProseMirror's mark order (and so which nests outside the
other) depends on which was toggled first, not a fixed schema order. Only `color` is touched —
each mark's own font-weight/font-style/text-decoration/background composes normally underneath.
24 new tests load the real, shipped CSS into jsdom and assert against getComputedStyle for every
mark x both nesting directions x multi-mark stacks, plus regression guards that a mark with no
link keeps its own color and a link elsewhere in the doc doesn't bleed color into unrelated text.
Verified all 11 color-assertion tests fail against the pre-fix CSS.
* fix(rich-markdown-editor): fix real-world gaps in the image dupe-upload fix
Found while re-verifying the drag/paste dupe-upload fix against the actual rendered DOM before
trusting it:
- hasHostedImageHtml's predicate only recognized the *persisted* src shape
(extractEmbeddedFileRef, e.g. /api/files/view/...). The DOM (and so a same-page copy's
clipboard html) always contains resolveImageSrc's REWRITTEN inline-route URL instead
(/api/workspaces/{id}/files/inline?key=.../?fileId=..., or the public-share equivalent) — a shape
the fix never recognized, so it silently never engaged for a real browser copy. Added
isInlineRouteSrc to also recognize it, verified end-to-end against the real resolveImageSrc
output (not a hand-typed guess).
- The img-src regex only matched quoted attribute values; an unquoted src (valid HTML) fell
through to the old re-upload path instead of being recognized as hosted.
- The paste bypass fired on ANY hosted image found in the html, even when the clipboard also
offered additional image files — a genuinely mixed paste (the hosted image plus a separate new
one) would have the new file silently dropped instead of uploaded. Narrowed to only bypass when
exactly one image file is offered.
- The drop bypass checked view.dragging unconditionally, including for the plain-file swallow
branch below it — a stale view.dragging (ProseMirror clears it up to ~50ms late via dragend when
a prior internal drag was dropped outside the view) could suppress swallowing an unrelated
non-image file drop (e.g. a PDF) in that window, letting it fall through to the browser default.
Gated on images.length > 0 so staleness can only ever affect the image-specific path it exists
for.
Extracted the paste/drop bypass decisions into shouldSkipPasteUpload/shouldSkipDropUpload so
they're unit-testable without mounting the full editor component.
* fix(rich-markdown-editor): fix the entire class of mark-color-vs-ambient-color bugs, not just links
Auditing every explicit `color` in this file for the same failure mode (an element's own explicit
color always wins over an inherited one, regardless of ancestor specificity) surfaced a second,
previously-unfixed instance: bold/italic text inside an h6 heading showed the brighter
--text-primary instead of h6's own intentionally dimmer --text-secondary, since strong/em hardcoded
--text-primary as their default.
strong/em's color was always redundant with the prose root's own default anyway — removing it
entirely (matching the highlight/`mark` rule's existing `color: inherit` convention in this same
file) lets normal CSS inheritance carry the correct color through from ANY ambient context, not
just links: a link's blue, h6's dimmer tone, or any future colored container this file doesn't
know about yet. `code` has the same redundant color, also removed.
del/s genuinely need their own dimmer default (distinct from the prose default), so they keep an
explicit color plus the link-color override — now the only mark that needs one, since strong/em/
code no longer set a competing color to override in the first place.
10 new tests cover every heading level x strong/em/code/del/s x link, including 2 that fail against
the pre-fix CSS (bold/italic and inline-code inside h6 both incorrectly showed --text-primary).
* fix(rich-markdown-editor): stop paste-clone from persisting the display-layer image URL
Cursor caught a real correctness bug in the paste/drop dupe-upload fix: bypassing to the editor's
DEFAULT html-based paste for an already-hosted image made it re-parse the clipboard html's <img
src>, which is resolveImageSrc's REWRITTEN *display* URL, not the real persisted one — baking that
display-only URL into the document. Public share, export, and referenced-by-doc tracking only
recognize the persisted shape, so the pasted image would silently vanish from all three.
Fixed by no longer letting default paste construct the node at all: findHostedImageAttrs walks the
CURRENT doc for an existing image node whose *resolved* src matches the clipboard html's, and
returns that node's real, persisted attrs (src, width, href, title — everything) to clone
ourselves. Falls through to a normal upload (always correct, just occasionally redundant) if no
match is found, rather than ever trusting the html's src directly.
Also merged the paste- and drop-specific skip checks into one shouldSkipFileUpload, and switched
the drop side off `view.dragging` entirely (Greptile: it can go briefly stale, up to ~50ms, when a
prior internal drag was dropped outside the view, which could suppress upload of an unrelated new
file dropped in that window) — now purely a function of what the current event's html/images
actually contain, which the drop's own default move logic (relocating the real node, never
re-parsing html) was never at risk from in the first place.
|