v0.7.30: md editor improvements, og image updates, spacexai hosted keys, cmd-k speedups, pagespeed optimizations, security hardening#5611
Conversation
* 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Security & API hardening re-checks chat Workflow canvas dragging a connection handle onto empty canvas opens Cmd-K with 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. |
|
Greptile SummaryThis PR updates landing pages, editor behavior, API guards, and copilot tooling. The main changes are:
Confidence Score: 5/5This looks safe to merge after tightening custom-block icon URL validation.
apps/sim/lib/copilot/tools/handlers/deployment/custom-block.ts
|
| 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
… 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).
Uh oh!
There was an error while loading. Please reload this page.