Skip to content

v0.7.30: md editor improvements, og image updates, spacexai hosted keys, cmd-k speedups, pagespeed optimizations, security hardening#5611

Merged
waleedlatif1 merged 23 commits into
mainfrom
staging
Jul 12, 2026
Merged

v0.7.30: md editor improvements, og image updates, spacexai hosted keys, cmd-k speedups, pagespeed optimizations, security hardening#5611
waleedlatif1 merged 23 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

waleedlatif1 and others added 18 commits July 10, 2026 21:58
* fix(canvas): replace native title tooltip with styled overflow tooltip on block params

Hovering a truncated subblock value or block name on the canvas popped
the browser's raw native tooltip with the full untruncated content
(including raw code). Replace the `title` attribute with the
cursor-following styled Tooltip, shown only when the text is actually
clipped.

* fix(canvas): drop unused ResizeObserver in OverflowSpan

useFloatingTooltip's canShow already receives the hovered element, so
measuring overflow via useIsOverflowing was redundant — every canvas
row was paying for a ResizeObserver and resize listener it never used.
…aste 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.
…e 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.
…uto-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
* 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
…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.
…de 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.
…#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.
* 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.
… 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
…utes (#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.
…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.
#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.
…uffle (#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.
…ads (#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.
…ce 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.
* 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 (3b2d35c) only staged the two deleted public/llms.txt
files - these two modified route.ts files (the Mothership/link-format
fixes they were meant to accompany) were left unstaged. No new changes,
just completing that commit's intent.
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 12, 2026 3:21am

Request Review

@waleedlatif1 waleedlatif1 changed the title v0.7.30: md editor improvements, og image updates, spacexai hosted keys, cmd-k speedups, pagespeed optimizations v0.7.30: md editor improvements, og image updates, spacexai hosted keys, cmd-k speedups, pagespeed optimizations, security hardening Jul 12, 2026
@cursor

cursor Bot commented Jul 12, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches authentication (OTP), file upload authorization/quota, and several request-body parsers, but changes are mostly additive guards and UX fixes rather than broad refactors.

Overview
Docs & marketing refresh OG generation to match the reference cover (Söhne typography, CJK-aware title wrapping, palette tweaks) and improve llms.txt / llms-full.txt as markdown with links. Landing CTAs centralize on shared signup/demo hrefs; pricing sends enterprise Talk to sales to /demo; hero and content images get fetchPriority and tighter sizes.

Security & API hardening re-checks chat authType before OTP verify; mothership uploads require workspace write/admin plus storage quota (batch-sized); help/speech/knowledge routes bound body reads; DOCX previews sanitize link hrefs; custom-block iconUrl limited to https or internal serve paths; MCP server list tolerates bad consecutiveFailures.

Workflow canvas dragging a connection handle onto empty canvas opens Cmd-K with pendingConnect, placing the chosen block at the drop and wiring from that handle (container-aware).

File editor & viewer post-agent-stream reconcile polls file content until the server advances past baseline; Backspace on empty bullets no longer NodeSelects adjacent images; markdown editor gains link-on-URL-paste, quieter autolink round-trips, reactive editability for images/code, and mention chips that inherit ambient color.

Copilot / providers headless mothership execute and chat payloads include workspace entitlements; xAI joins workspace BYOK and hosted key paths; BYOK settings UI adds xAI.

Perf & UX Cmd-K caps and ranks results (name over secondary text), optional section filters; chat subagent/tool rows use shimmer labels; Slack selector combobox supports multi-select channels.

Reviewed by Cursor Bugbot for commit 0cc6ed6. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions github-actions Bot added the requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep label Jul 12, 2026
@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

⚠️ Cross-repo companion check

One or more companion PRs aren't merged into main yet (aggregated across the feature PRs in this release). Merging this without them will leave copilot and sim out of sync — merge them in lockstep.

  • ⚠️ simstudioai/mothership#346 — merged into staging (this PR targets main) — feat(deploy): add deploy_custom_block tool, skill, and prompts
  • ⚠️ simstudioai/mothership#350 — merged into staging (this PR targets main) — improvement(deploy-custom-block): document https-only external icon URLs

@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates landing pages, editor behavior, API guards, and copilot tooling. The main changes are:

  • Marketing CTA, image priority, OG image, and llms text updates.
  • Bounded body parsing for selected API routes.
  • Workspace permission and quota checks for mothership uploads.
  • Docx hyperlink sanitization and markdown editor paste/selection fixes.
  • Custom-block deployment support through copilot.
  • xAI hosted key and provider updates.

Confidence Score: 5/5

This looks safe to merge after tightening custom-block icon URL validation.

  • No blocking issues found in the changed code.
  • The remaining issue is a contained validation gap in newly stored icon metadata.

apps/sim/lib/copilot/tools/handlers/deployment/custom-block.ts

Security Review

One hardening issue was found in the new copilot custom-block deployment path: external icon URLs are accepted without scheme validation before being stored in shared custom-block metadata.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/tools/handlers/deployment/custom-block.ts Adds copilot publish, update, undeploy, icon ingestion, and audit handling for custom blocks.
apps/sim/app/api/files/upload/route.ts Adds mothership workspace permission checks and batch quota enforcement before upload.
apps/sim/app/api/speech/token/route.ts Uses a bounded optional JSON body parser before speech token auth and billing work.
apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts Moves chunk POST body parsing to the shared bounded JSON helper and validates workflowId shape.
apps/sim/lib/core/security/url-safety.ts Adds shared URL safety helpers for document-rendered hyperlinks.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/docx-preview.tsx Sanitizes docx-rendered hyperlinks after preview rendering.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-paste.ts Adds selected-text URL paste linkification with href normalization.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/keymap.ts Changes Backspace selection placement around leaf nodes and gap cursors.

Reviews (1): Last reviewed commit: "fix(landing): repair Lighthouse-flagged ..." | Re-trigger Greptile

Comment thread apps/sim/lib/copilot/tools/handlers/deployment/custom-block.ts Outdated
… 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
* 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
…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.
…e 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).
@waleedlatif1 waleedlatif1 merged commit 207785c into main Jul 12, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants