mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
d64ad856f191f3f775e626dfaff7d71bea6dc893
5258
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d64ad856f1 |
improvement(microsoft-ad): add pagination support and fill BlockMeta gaps (#5442)
* improvement(microsoft-ad): add pagination support and fill BlockMeta gaps Full validate-integration pass against live Microsoft Graph API docs. No critical bugs found (endpoints, methods, params, and OAuth scopes were already correct). Fixed the real gaps: - List Users/Groups/Group Members had no way to page past the default Graph page size (100, max 999 via $top), which silently truncated results for the block's own audit/sweep templates. Added a nextLink input/output following the same convention as microsoft_dataverse. - Create Group's visibility dropdown was missing HiddenMembership, a valid Microsoft 365-only value that can only be set at creation time. - Rounded out BlockMeta skills (3 -> 5) with two more real, tool-grounded use cases: directory search and ad hoc group membership changes. * fix(microsoft-ad): correct $search syntax and create-user response fields Independent 4-agent re-audit against live Graph docs surfaced two real bugs predating this PR: - list_users/list_groups sent $search="<term>" with no property prefix. Graph requires the "property:value" form for directory-object search (e.g. "displayName:term" OR "mail:term") and 400s on a bare string. - create_user's POST had no $select, so Graph's default create response omits department/accountEnabled even when submitted, making transformResponse report them back as null. Added the same $select used by list/get so the response reflects what was actually set. Also tightened create_group's visibility description: only HiddenMembership is create-only: Private/Public can still be changed after creation via Update Group. * fix(microsoft-ad): validate nextLink origin, allow nextLink-only pagination Greptile and Cursor both flagged the same real issue: nextLink was passed straight to fetch() as the request URL with no origin check, while the OAuth bearer token was always attached. A crafted or prompt-injected nextLink pointing outside graph.microsoft.com would exfiltrate the token. Fix: reuse the existing assertGraphNextPageUrl/getGraphNextPageUrl helpers from tools/sharepoint/utils (already the shared pattern for SharePoint, OneDrive, Teams, Planner, Outlook, Excel Graph pagination) instead of a bespoke unvalidated pass-through. Cursor also caught that list_group_members required groupId even when only nextLink was supplied for a later page. Relaxed groupId to optional at the tool level, matching how sharepoint_get_list treats its analogous listId param — the URL builder still throws a clear error if neither groupId nor nextLink is given. * fix(microsoft-ad): allow $search+$filter combo, escape backslashes, fix pagination UX Second independent 4-agent re-audit of the final state (post security fix) surfaced 3 more real issues: - list_users/list_groups threw an error whenever $search and $filter were both supplied, claiming Graph doesn't support combining them. It does (AND semantics, documented) — the check was blocking valid, documented usage for no reason. Removed it. - The $search term escaping only handled embedded double quotes, not backslashes, which Graph's own escaping rule also requires. Fixed the replace order (backslashes first, then quotes). - list_group_members's Group ID field was still hard-required in the block UI for every operation including list_group_members, undermining the nextLink-only pagination path added earlier (the tool itself no longer requires it). Dropped list_group_members from the UI-required list, matching the tool's own conditional requirement — the runtime "Group ID is required" check still catches a genuinely empty call. |
||
|
|
6d5ac583bc |
fix(cloudflare): align integration with live API, fix type-coercion bug (#5444)
* fix(cloudflare): align integration with live API, fix type-coercion bug
- fix update_zone_setting body builder: was blindly JSON.parse-ing every
value, silently coercing scalar settings (e.g. min_tls_version "1.2")
to the wrong type; now only parses object/array literals
- fix list_dns_records name/content/tag filters to use Cloudflare's
exact-match dotted param names (name.exact/content.exact/tag.exact)
- remove auto_minify (never a real setting ID) and minify (deprecated
and removed from the live zone-settings API in Aug 2024)
- remove dead jump_start param from create_zone (not part of the
current POST /zones schema)
- fix block bgColor from #F5F6FA to Cloudflare's actual brand orange
#F38020
- add missing secondary zone type option and plan.id sort option
- expand BlockMeta skills/templates
* fix(cloudflare): reject empty browser_cache_ttl value instead of coercing to 0
* fix(cloudflare): address Greptile review feedback
- use trimmed value consistently in update_zone_setting fallback paths
- document that tag_match has no effect with a single exact-match tag
filter (Cloudflare's API only combines multiple tag conditions)
* fix(cloudflare): coerce non-string setting values before trim
Wand-generated or block-referenced values can arrive as a number at
runtime despite the declared string param type, which crashed the
body builder's .trim() call.
* fix(cloudflare): harden null/undefined value handling, fix stale tag description
- coerce null/undefined value to empty string instead of literal
"null"/"undefined" strings before trimming
- fix stale block-level 'tag' input description left over from the
comma-separated-tags -> exact-match-tag filter fix
* fix(cloudflare): pass through structured array/object values as-is
A block-referenced array/object value was being blindly stringified
before the JSON-shape check, so String([...]) comma-joined arrays and
String({...}) produced the literal "[object Object]" instead of the
JSON shape Cloudflare expects (e.g. for ciphers). Already-structured
values now pass straight through.
* revert(cloudflare): keep original block bgColor (#F5F6FA)
|
||
|
|
0916b193d9 |
improvement(instantly): validate integration against live API, add lead/campaign lifecycle tools (#5448)
* improvement(instantly): validate integration against live API, add lead/campaign lifecycle tools - Verify all existing Instantly tools against the live API v2 spec - Add instantly_patch_lead, instantly_pause_campaign, instantly_delete_campaign - Fix activate_campaign response mapping to surface the full campaign object - Restore status/ai_sales_agent_id list_campaigns filters * fix(instantly): guard against empty patch_lead request body Follows the same pattern used in tools/langsmith/update_run.ts. |
||
|
|
0363451539 |
improvement(new-relic): validate integration against NerdGraph docs, surface more entity fields (#5443)
* improvement(new-relic): validate integration against NerdGraph docs, surface more entity fields
- Audited all 4 New Relic tools (nrql_query, search_entities, get_entity,
create_deployment_event) plus the block against live NerdGraph API docs;
request/response shapes, auth, region endpoints, and GraphQL injection
handling all confirmed correct.
- Added domain, reporting, alertSeverity, and tags to get_entity and
search_entities outputs — stable Entity schema fields we weren't
surfacing. Purely additive, no existing fields changed.
- Skipped NerdGraph's aiIssues/incidents API; New Relic marks it "unsafe
experimental" and requires an opt-in header, not worth the fragility.
* fix(new-relic): normalize search_entities output, share entity normalization
search_entities passed raw NerdGraph entities straight through, so tags/
domain/reporting/alertSeverity could arrive as null/undefined at runtime
despite the non-nullable NewRelicEntity type contract — a .map() on a
null tags array would throw. get_entity already normalized these fields
correctly.
Extracted the normalization into a shared normalizeNewRelicEntity() in
utils.ts and reuse it from both tools, so the two can't drift again.
* fix(new-relic): gate alertSeverity behind required GraphQL interface fragments
alertSeverity isn't a direct field on New Relic's Entity/EntityOutline
types — it only exists on the AlertableEntity/AlertableEntityOutline
sub-interfaces, and NerdGraph rejects the query outright without the
inline fragment. get_entity.ts (entity(guid), returns Entity) now uses
`... on AlertableEntity { alertSeverity }`; search_entities.ts
(entitySearch, returns EntityOutline) now uses
`... on AlertableEntityOutline { alertSeverity }`. Confirmed against
New Relic's live NerdGraph entities API docs.
|
||
|
|
a342424ffe |
fix(mcp): scope tools loading state to each server's own query (#5441)
Server rows derived "Loading..." from a workspace-wide isLoading/isFetching aggregate instead of that server's own per-server query, so a row could sit stuck on "Loading..." (or flicker back to it) whenever any other server's tool query was fetching, only clearing after a full page reload. |
||
|
|
018283b29e |
fix(rich-md-editor): raw-HTML-block fragmentation + styling follow-up (#5440)
* fix(rich-md-editor): fix raw-HTML-block fragmentation and styling - RawHtmlBlock previously delegated to marked's built-in block-HTML tokenizer, which (per CommonMark's HTML-block-type-6 rule) stops at the first blank line. A real <details> with a paragraph inside — the common case — fragmented into a raw chip, an ordinary rendered paragraph, and a second raw chip, stranding real content in between. - Fixed with a custom block-level tokenizer that scans to the tag's matching close (reusing the balanced open/close depth-tracking already built for nested inline HTML), spanning blank lines, restricted to CommonMark's own block-tag whitelist (details, div, table, section, etc.) so tags that can legitimately start a paragraph (em, a, span, code, kbd) are left untouched. - Dropped the warning-colored tint on raw HTML/footnote blocks (color-mix with --warning read as an error state) in favor of the same neutral surface as existing code blocks — the hover badge alone now signals "this is raw, unrendered text". * fix(rich-md-editor): fix indented HTML, quoted attributes, and code-mention edge cases - CommonMark allows up to 3 leading spaces before a block-HTML opening line; the new block tokenizer required column 0, so an indented <details>/<div> still fragmented across blank lines. Fixed by splitting off the leading indent, matching against the rest, and stitching the indent back onto raw. - The open-tag and balanced-close regexes stopped at the first `>`, even inside a quoted attribute value (e.g. data-example="a > b"), producing wrong match lengths and miscounting a same-tag mention inside the quoted value as a real nested tag. Replaced with an attribute-aware pattern that treats a full quoted value (including any interior >) as one unit. - The balanced-tag scan couldn't distinguish real markup from a tag name mentioned inside inline code or a fenced code block. Now masks code regions (same-length filler, positions preserved) before scanning, so a properly-escaped mention (backticks or a fenced example) is never miscounted. A genuinely bare, unescaped mention outside code remains a known, inherent limitation of regex-based tag matching (shared by real HTML parsers given the same ambiguous input) — verified this can never lose data or hang, only reflow to a stable fixpoint on save. * fix(rich-md-editor): fix blockquoted-fence masking and void-tag balance scan - maskCodeRegions's fenced-code regex required fence markers at column 0, so a fence quoted inside a markdown blockquote (each line prefixed with `> `) wasn't recognized, leaving tag mentions inside it visible to the balance scanner. Extended the fence pattern to tolerate an optional blockquote prefix on both the opening and closing fence line. - BLOCK_HTML_TAG_NAMES includes several void elements (link, meta, base, hr, ...) that have no closing tag at all. The block tokenizer only treated an explicit self-closing `/>` as complete, so a void tag without one would scan the rest of the document for a `</meta>` that will never appear, risking a false match on a later same-name mention. Now reuses the existing VOID_TAGS set (already used by the inline tokenizer) to treat these as complete right after the open tag. Also restored `hr` to the whitelist, which was accidentally dropped when transcribing marked's tag list. * fix(rich-md-editor): tolerate an indented (non-blockquoted) fence in code masking maskCodeRegions's fence pattern handled a blockquoted fence (`> \`\`\``) but still required the fence marker at column 0 otherwise, missing CommonMark's independent up-to-3-space fence indent tolerance this codebase already relies on elsewhere (FENCE_OPEN/FENCE_CLOSE in markdown-parse.ts). An indented fence's tag-name mention could still end a whitelisted block early. Fixed by combining both allowances in one prefix pattern (zero-or-more blockquote levels, each independently followed by up to 3 more spaces of indent). * fix(rich-md-editor): mask HTML comments in balanced-tag scan A tag-name mention inside an HTML comment (e.g. `<!-- see </div> below -->`) inside a whitelisted raw HTML block could still be matched by the balance scan and end the block early, fragmenting it. maskCodeRegions already masked fenced/inline code the same way; extend it to mask comments too. |
||
|
|
dee814b6fa |
fix(db): bound knowledge connector and document fan-out concurrency (#5432)
Claude-Session: https://claude.ai/code/session_011Lmib23o5aQtBr7ZPhgpgf Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
34b2abadcb |
perf(pii): mask offloaded refs + parallelize + raise redaction concurrency (#5436)
* perf(pii): mask offloaded refs in block outputs/input + parallelize + raise concurrency - Block-output and input stages now hydrate → mask → re-store large-value refs (function/tool outputs are offloaded to refs before reaching the redactor, which treats refs as opaque) — fixes big block outputs coming back unredacted - Parallelize large-value ref hydrate/mask/re-store: collect refs across the whole payload and process them with bounded concurrency instead of one sequential pass per key (PII_REF_CONCURRENCY, default 4) - Raise mask-batch chunk concurrency default 4 -> 64 to saturate the load-balanced Presidio fleet behind the internal ALB (PII_MASK_CHUNK_CONCURRENCY, env-tunable) * fix(pii): keep resolveReplacements mapper total (respect mapWithConcurrency contract) - Catch per-ref errors inside the mapWithConcurrency mapper and rethrow the first after the pool drains, instead of letting a throwing mapper reject the pool (the util documents fn MUST NOT reject). Preserves fail-fast abort in throw mode. - Add multi-ref throw-mode test: one of several refs failing aborts the redaction * feat(pii): make route->Presidio chunk concurrency env-tunable (PII_SERVICE_CHUNK_CONCURRENCY) Was hardcoded to 4; now env-tunable (default 4) so the inner route->Presidio fan-out can scale with the fleet alongside the outer PII_MASK_CHUNK_CONCURRENCY. * feat(pii): combined /redact + /redact_batch endpoint (one round-trip) - Presidio: add /redact and /redact_batch (analyze + anonymize server-side, feeding analyzer results straight to the anonymizer, no dict round-trip). All existing endpoints kept, so old clients keep working during rollout. - App: maskPIIBatch now uses /redact_batch (halves app<->service round-trips, no shipping text back up for anonymize). Falls back to the legacy analyze_batch + anonymize_batch pair on a 404 (older Presidio image), so the app is safe to deploy before or after the service in either order. Same length-check fail-closed guarantee. |
||
|
|
9538beb1b8 |
improvement(settings): align design-system text-scale tokens across settings pages (#5439)
* improvement(inbox): align Sim Mailer settings page with design-system tokens Replace literal pixel text-size classes (text-[12px]/[13px]/[14px]/[16px]) with the named Tailwind scale tokens across inbox.tsx, inbox-enable-toggle, inbox-settings-tab, and inbox-task-list — pixel-identical, no visual change. Swap the hand-rolled toggle-row span pair for the emcn Label component, matching the toggle-row convention used elsewhere in settings. * improvement(settings): extend design-system token migration across all settings pages Replace literal pixel text-size classes with named Tailwind scale tokens (text-caption/small/sm/base/md/lg) across every other settings page and shared component (member-list, api-keys, mcp, billing, credential-sets, workflow-mcp-servers, mothership, admin, byok, copilot, custom-tools, settings-resource-row, and the ee/ access-control, data-retention, whitelabeling, and shared setting-row components) — pixel-identical, no visual change. Document the row title/subtitle token pairing and toggle-row Label convention in sim-settings-pages.md and the add-settings-page skill's audit checklist so future pages default to it. |
||
|
|
846e0f5562 |
fix(attachments): cross tenant hardening (#5310)
* v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li <theo@sim.ai> * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha * fix(attachments): cross tenant security hardening * address comments * fix * fix * remove dead code --------- Co-authored-by: Waleed <walif6@gmail.com> Co-authored-by: Theodore Li <theodoreqili@gmail.com> Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com> Co-authored-by: Theodore Li <theo@sim.ai> |
||
|
|
5acdaeea02 |
feat(rich-md-editor): table row/col toolbar, raw HTML/footnote support, paste fidelity (#5438)
* feat(rich-md-editor): table row/col toolbar, raw HTML/footnote support, paste fidelity - Add a table toolbar (add/delete row, add/delete column, toggle header, delete table) wired to stock @tiptap/extension-table commands - Add verbatim snippet nodes for raw HTML blocks, HTML comments, footnotes (def + ref), and inline raw HTML tags — these no longer force the whole document read-only, and the raw source is directly editable in place - Fix an upstream @tiptap/markdown footgun found while building this: a custom tokenizer with no explicit `start` callback corrupts the shared lexer and silently drops unrelated content elsewhere in the document - Prefer the markdown parser over generic HTML→DOM paste mapping when the plaintext clipboard side looks like markdown, even if an HTML sibling is present - Fix a stale select-all selection surviving a stream-settle content replace, which permanently highlighted every divider/image after a file regeneration * fix(rich-md-editor): address review findings from initial PR round - Collapse the stream-settle selection unconditionally, not only when setContent re-runs — the last streaming tick already syncs lastSyncedBodyRef to the final body, so the fix was previously skipped in the common streamed-content case (Cursor Bugbot) - Support GFM footnote definition continuation lines (>=4-space indented, with blank lines between paragraphs) instead of truncating to just the opening line (Greptile P1) - Fix the inline raw-HTML tokenizer to find the balanced closing tag by tracking nesting depth, instead of matching the first same-name closing tag — fixes corruption on nested same-tag elements like <span>outer <span>inner</span></span> (Greptile P1) - Stop caching the table toolbar's anchor rect by selection key — the same cell can move on screen from scrolling alone with no selection change, and the cached rect went stale (Greptile P2) * fix(rich-md-editor): fix CI type errors in raw-markdown-snippet.tsx - parseMarkdown callbacks returned null on no-match, which @tiptap/core's MarkdownParseResult type doesn't permit — switch to returning [] (empty array), matching the same no-match convention MarkdownCodeBlock already uses, with identical runtime behavior - Pin NodeViewContent's generic to 'span' (NodeViewContent<'span'> as='span'), since it defaults to 'div' and rejects other `as` values without an explicit type argument Verified with a full `tsc --noEmit` (NODE_OPTIONS=--max-old-space-size=8192, matching CI) — 0 errors, where it previously failed the Next.js build's type-check step. |
||
|
|
89e5a142e2 |
fix(analytics): GTM/GA4 missing on /demo and other landing pages (#5437)
* fix(analytics): apply runtime CSP to all landing pages, not just / GTM/GA4 script-src and connect-src allowlist entries are gated behind isHosted, which next.config.ts's build-time CSP bakes in from whatever NEXT_PUBLIC_APP_URL was resolved at build/boot time. Only the homepage route was overridden with the request-time CSP in proxy.ts, so every other landing page (including /demo) silently lost the googletagmanager.com/google-analytics.com allowlist and GTM never fired. * style: drop inline comment from proxy CSP fix * fix(analytics): apply runtime CSP to authenticated /invite pages too handleInvitationRedirects returns NextResponse.next() for authenticated users, bypassing the catch-all block entirely, so it never picked up the runtime CSP fix in the same way. Same class of gap flagged by review. |
||
|
|
8bc0d9717c |
fix(mailer): correct AgentMail webhook message schema field requirements (#5434)
cc, attachments, and html are omitted from AgentMail's webhook payload when empty rather than sent as empty arrays/null, but the zod schema marked them required. Every inbound email without a CC recipient or attachment (the common case) failed schema validation before a task row was ever created, silently breaking Sim Mailer inbox ingestion since the feature shipped. Renames from_ to from to match AgentMail's documented field name. |
||
|
|
4fa1e045bd |
fix(sidebar): prefetch folders and workspace permissions on cold load (#5426)
* fix(sidebar): prefetch folders and workspace permissions on cold load * refactor(sidebar): dedupe folder query and permission lookups from /simplify pass * docs(workspaces): trim getWorkspacePermissionsForViewer tsdoc to match repo convention * fix(folders): reference FOLDER_LIST_STALE_TIME constant instead of duplicating literal |
||
|
|
65435f89aa |
improvement(sidebar): memoize workflow/folder rows for faster tab navigation (#5428)
* improvement(sidebar): memoize workflow/folder rows for faster tab navigation Switching between workspace tabs re-rendered every workflow and folder row in the sidebar because the rows (and the shared export hooks they call) subscribed to useParams, which re-renders on every navigation. - Wrap WorkflowItem and FolderItem in React.memo (the only un-memoized leaf rows; every sibling row was already memoized). - Decouple the rows from useParams: thread workspaceId as a stable prop from WorkflowList (matching the FileList convention), and expose the live active workflowId through a stable activeWorkflowIdRef on SidebarListContext, read only in delete callbacks — never during render. - Refactor the three shared export hooks (used only by these two rows) to take workspaceId as a param instead of calling useParams internally. - Stabilize handleWorkflowClick's identity via refs so the shared list context no longer changes identity on navigation. - Lazy-init the drag-drop siblings Map ref. On a tab switch only the two rows whose active state flips now re-render. * fix(sidebar): add workspaceId to render-callback deps renderWorkflowItem/renderFolderSection now pass workspaceId into the rows, so they must list it as a dependency — otherwise a workspace switch that doesn't also change workflowId would leave the callbacks closing over a stale workspaceId (wrong-workspace deletes/exports). |
||
|
|
487166f414 |
refactor(react-query): hoist every staleTime into a named exported constant (#5427)
* refactor(react-query): hoist every staleTime into a named exported constant Adds a repo-wide convention (documented in .claude/rules/sim-queries.md, CLAUDE.md, and the react-query-best-practices skill/command) that every staleTime value must come from a named exported constant instead of an inline numeric literal. This prevents a server-side prefetch and its client hook from independently duplicating the same duration and drifting out of sync, which Greptile caught on PR #5426. Applies the convention across all of hooks/queries/** and their prefetch.ts consumers. * refactor(react-query): use FOLDER_LIST_STALE_TIME in folders.ts hooks useFolders and useFolderMap still used inline 60 * 1000 despite folders.ts already exporting FOLDER_LIST_STALE_TIME — the same prefetch-drift gap this refactor closes everywhere else. Same value, no behavior change. |
||
|
|
20e86543fb |
refactor(permissions): make hasWorkspaceAdminAccess and getUserEntityPermissions thin wrappers of checkWorkspaceAccess (#5430)
hasWorkspaceAdminAccess and getUserEntityPermissions('workspace', ...)
each independently re-implemented the same getWorkspaceWithOwner +
getEffectiveWorkspacePermission fetch that checkWorkspaceAccess already
does — this is exactly the duplication that let the custom-blocks POST
handler drift into two separate DB round-trips for one permission
resolution (fixed separately). Centralizing all three onto one
implementation means a future logic change or bug fix only has to
happen once, and any future caller that (accidentally) calls two of
these functions together is now at least drawing from one consistent
definition of "effective permission" instead of two that could diverge.
Adds a `permission: PermissionType | null` field to the WorkspaceAccess
return shape so getUserEntityPermissions doesn't need a second
independent getEffectiveWorkspacePermission call to get the raw value.
No behavior change: verified analytically (canAdmin is exactly
permission === 'admin' per PERMISSION_RANK) and confirmed by an
independent security review focused on argument-order and
privilege-escalation risks in this kind of refactor. 616 existing
tests across lib/workspaces, lib/credentials, lib/invitations,
lib/copilot/vfs, and the affected API routes pass unmodified (plus 2
test assertions updated for the new additive field).
|
||
|
|
732e4ec975 |
fix(custom-blocks): dedupe redundant workspace lookup in POST admin check (#5429)
hasWorkspaceAdminAccess + a separate getWorkspaceWithOwner call each independently re-fetched the workspace row for the same (userId, workspaceId) pair. Consolidated into a single checkWorkspaceAccess call, matching the pattern the GET handler in this same file already uses. access.canAdmin is logically identical to hasWorkspaceAdminAccess's result (admin is the top PERMISSION_RANK, nothing else satisfies it) — no behavior change, one fewer DB round-trip per publish request. |
||
|
|
85f80fa213 |
fix(compare): render two already-populated fact rows that were missing from the table (#5425)
dataTables and richTextEditor are required fact fields, already researched and filled in for Sim and every competitor, but neither was ever added to the row list the table actually renders. |
||
|
|
82eb6dce42 |
fix(404): use ChipLink for return-home CTA on root not-found page (#5424)
Matches the emcn ChipLink pattern already used by sibling landing not-found pages instead of a hand-rolled Link with raw Tailwind classes. |
||
|
|
ee1824caef |
improvement(compare): accuracy and consistency pass on comparison pages (#5422)
* improvement(compare): accuracy and consistency pass on comparison pages Cross-checked facts across all comparison pages against live sources, corrected a few internal inconsistencies, and added one new fact category. No schema or rendering changes beyond a single new row. * fix(compare): align a parity check and a stale source citation * improvement(compare): cross-reference Sim's permission-groups system from rbac * improvement(compare): sharper bottom-line reasoning, minor emphasis fixes Reworded a few competitor headline claims to reflect genuine product positioning rather than a narrow feature, and gave a couple of Sim's own already-documented capabilities (model reach, integration count, live collaboration) more prominent placement. * improvement(compare): verify every cited source is live, fix a few dead links * fix(compare): correct mcpSupport boolean-icon misparse in power-automate.ts |
||
|
|
3edaae31fe |
fix(mcp): correct fetchFn fallback order in mcpAuthGuarded (#5423)
Spread order previously let an explicit fetchFn (including fetchFn: undefined) in options silently disable the SSRF-guarded default. Fallback is now applied after the spread so the guard always wins unless a real override is passed. fix(tools): handle non-numeric Drive file size in early size check Guard the pre-download size check against a malformed metadata.size string so it's skipped explicitly instead of relying on an incidental NaN no-op; the streaming cap on the actual download still enforces the limit either way. |
||
|
|
f456ed9a0b |
improvement(chat): smooth streaming — eased stick-to-bottom glide and time-based reveal pacing (#5417)
* improvement(chat): eased stick-to-bottom glide for streaming chat * improvement(chat): time-based word-at-a-time reveal pacing * improvement(chat): harden smooth-chase against reentrant kick/cancel during a step |
||
|
|
4b133984c6 |
fix(mcp): make SSRF-guarded fetch structurally mandatory in OAuth auth() calls (#5419)
PR #5399 fixed the callback route forgetting to pass fetchFn into the SDK's auth(), but every call site (start + callback routes) still imported the raw SDK auth() directly and had to remember to pass fetchFn: createSsrfGuardedMcpFetch() by hand — the same omission was possible again at any future call site. Add mcpAuthGuarded() in lib/mcp/oauth/auth.ts, a thin wrapper around the SDK's auth() that always defaults fetchFn to the SSRF-guarded fetch (still overridable for tests). Both routes now import mcpAuthGuarded from @/lib/mcp/oauth instead of the raw SDK auth, so omitting the guard is no longer possible by omission. |
||
|
|
0d6994d3cc |
refactor(uploads): hoist execution permission check out of upload loop, dedupe permission-gate tests (#5420)
/simplify pass on PR #5404: resolve the execution-context workspace permission check once per request instead of once per uploaded file, and collapse repeated request-construction boilerplate in the new permission-gate tests into a shared helper. |
||
|
|
41fb86355d |
fix(webhooks): validate and pin EmailBison apiBaseUrl before outbound requests (#5415)
* fix(webhooks): validate and pin EmailBison apiBaseUrl before outbound requests Route Email Bison webhook create/delete requests through the shared DNS-validated, IP-pinned fetch used by the Teams and Slack webhook providers instead of a raw fetch to the user-configured instance URL. * fix(webhooks): restore NEXT_PUBLIC_APP_URL in emailbison tests, dedupe strict-delete warning log Test env var mutation was never restored, risking cross-file leakage in single-threaded vitest runs. Strict-mode deleteSubscription failures were logged twice (once at the throw site with context, once generically by the outer catch); the outer catch now skips its own log for errors already logged at the throw site. |
||
|
|
a1fbb5743d |
fix(stt): bound audio download response size (#5412)
* fix(stt): bound audio download response size Cap the audioUrl download in the STT proxy route at the platform's standard 100MB file-size ceiling, matching the pattern already used by sharepoint/download-file and other external download routes. Classify size-limit rejections as a clean 413 instead of an unhandled 500. * fix(stt): avoid double isPayloadSizeLimitError call in error handling |
||
|
|
1a371e51e9 |
fix(uploads): bound multipart body read in workspace-file and knowledge-document upload routes (#5413)
* fix(uploads): bound multipart body read in workspace-file and knowledge-document upload routes * fix(uploads): avoid FormData-body stream race in bounded-read tests * fix(uploads): share MAX_MULTIPART_OVERHEAD_BYTES constant across upload routes |
||
|
|
b98c7c4136 |
fix(tools): bound download response size for drive/slack/onedrive (#5414)
Google Drive, Slack, and OneDrive download routes fetched provider file content without a response size cap, unlike the SharePoint download route. Add maxResponseBytes to each content fetch, reject Google Drive files whose metadata size already exceeds the cap before starting the download, and map the resulting size-limit error to a clean 413 response. |
||
|
|
04432b64a8 |
fix(copilot): validate credential-link URL scheme before rendering (#5416)
* fix(copilot): validate credential-link URL scheme before rendering Only render the credential connect link as a clickable anchor when its value resolves to an http(s) URL, reusing the isSafeHttpUrl helper already used for chat file links. * refactor(copilot): move isSafeHttpUrl to shared lib/core/utils/urls Per Greptile's convention feedback: isSafeHttpUrl was consumed by both chat and workspace/home but defined inside a feature-specific 'use client' component. Move it alongside getBrowserOrigin (which it already depends on) in lib/core/utils/urls.ts, matching this repo's shared-utility rule. |
||
|
|
acf4afb1b5 | fix(mothership): block subflow re-parenting in embedded workflow view (#5418) | ||
|
|
82eff5435d |
feat(custom-block): deploy a workflow as a reusable org-scoped block (#5407)
* feat(custom-block): deploy a workflow as a reusable org-scoped block * fix(custom-block): reseed deploy form, guard duplicate publish, run child deployed * test(custom-block): isolate custom-block rows fetch in execution-core test * fix(custom-block): allow cross-workspace exec, org-scope authority, keep field ids, hide disabled * feat(custom-block): run child under source owner's identity, workspace, and env * fix(custom-block): bind publish authz to the source workflow's workspace * fix(custom-block): gate edit/delete on source-workspace admin, not org admin * chore(custom-block): rebaseline route count to 887 after staging merge * fix(custom-block): sanitize failure output so it can't leak source workflow internals * fix(custom-block): derive inputs and curated outputs from deployed state, not draft * fix(custom-block): hide disabled blocks from the toolbar palette too * fix(custom-block): bill nested + failed-run hosted cost; expose real inputs to the agent * fix(custom-block): enforce enterprise + flag gate at every consumption path |
||
|
|
818fa00133 |
fix(mothership): stop chat perf decay from permanently-animated streamed messages (#5411)
* fix(mothership): stop chat perf decay from permanently-animated streamed messages * fix(mothership): reset animation latches when a reused ChatContent gets replaced content * fix(mothership): keep streaming parser on settled messages to kill the drain-swap flash * fix(mothership): unify plain/special render branches to stop whole-message re-fade when options arrive * improvement(mothership): hold render-phase animation latches in useState per updated hook rules * fix(styling): translucent text-selection in form controls so field text stays readable * improvement(styling): one lighter translucent text-selection color everywhere, no forced text color * chore(styling): selection-muted tokens as 8-digit hex to match color token convention |
||
|
|
0249516cab |
fix(compare): swap LangChain logo, fix comparison-table horizontal scroll (#5409)
* fix(compare): swap LangChain logo, fix comparison-table horizontal scroll
Replace LangChainIcon's path with the official brand mark (light-blue
link icon) instead of the prior monochrome recreation.
The comparison table's grid cells were missing min-w-0, so a grid item
without it sizes to its content's max-content width instead of
respecting its column's fr track, pushing the whole table wider than
its container and forcing a horizontal scrollbar. Add min-w-0 to every
grid cell/header, and size the row-label column to minmax(140px,
max-content) instead of a guessed fr ratio, so it's exactly as wide as
its longest label ("Vetted first-party integrations") needs and no
wider, leaving the Sim/competitor value columns their full share.
* fix(compare): stacked mobile layout for the comparison table
Below sm (640px) a 3-column table has no room to be legible even with
the sticky label column, so switch to the standard responsive-table
pattern instead: each fact stacks as label -> Sim's value -> the
competitor's value, each value tagged with its product name since the
column headers are no longer directly above. Pure CSS (max-sm:/sm:
variants), no JS, keeping this a zero-hydration server component.
* fix(compare): fix SourceLink inline-anchor overflow, align table breakpoint
Root cause of the persistent table-overflow/mobile-scroll issues: SourceLink
renders a plain <a> with no explicit display, so it defaults to
'display: inline'. min-width and truncate are no-ops on inline elements, so
any fact with a source (nearly all of them) ignored its flex/grid parent's
width constraint and could force the whole row (and thus the whole table)
wider than intended, regardless of any container-level min-w-0/max-content
fix. Give the anchor 'block min-w-0' so width constraints and truncation
actually cascade down to the wrapped value.
Also aligns the table's mobile-stack breakpoint from an ad hoc sm (640px) to
lg (1024px), matching this route group's own tablet-and-below convention
(.claude/rules for the (landing) group), and fixes the stacked mobile cells
to override the cell's base items-center with items-stretch so the name tag
and value get a real full-width box to truncate within instead of shrinking
to their own content size with no boundary.
* fix(compare): add min-w-0 to ColumnHeader per Greptile review
ColumnHeader (the Sim/competitor logo header cells in the two fr columns)
still had default min-width: auto, so an unusually long competitor name
could size the header to its min-content width and push the grid wider
than its column allows, same root cause as the rows already fixed.
|
||
|
|
759dddbf4c |
feat(billing): dedicated Credit usage page with date-range filter and CSV export (#5405)
* fix(billing): apportion per-row credit costs so they sum to the page total Cursor Bugbot (medium): each row rounded its own dollar cost to credits independently while the header total rounded the summed dollars once — over enough rows those two roundings can visibly disagree, the exact "line items don't add up to the total" class of bug apportionCredits was already built to prevent (used by the trace view / cost breakdown). Route now apportions each page's row credits against that page's dollar sum instead of rounding rows independently. Added a test with three sub-cent rows that would each independently round to 0 credits (but sum to 1) to prove the reconciliation holds. * fix(billing): dim stale credit usage rows while a new period loads Cursor Bugbot (medium): keepPreviousData kept the prior period's rows and total on screen while a newly selected period fetched, but the dropdown label updated immediately — so during the transition the displayed numbers were labeled under a period they didn't belong to. Now reads isPlaceholderData (the standard TanStack Query signal for "this data is a stale placeholder, not a fresh fetch for the current key") and dims the list while it's true, matching the same flag already used for this exact purpose in integration-skills-section.tsx. * fix(billing): show "<1 credit" for rows apportioned to 0 Cursor Bugbot (low): with apportioned per-row credits, a row with a real but sub-credit dollarCost can legitimately apportion to 0 credits once a sibling row absorbs the shared rounding remainder — rendering a flat "0 credits" reads as if nothing was charged, inconsistent with formatCreditCost's "<1 credit" wording used elsewhere in billing. Added dollarCost to the wire response (needed to distinguish a genuinely free row from a rounded-to-zero one) and a small formatRowCredits helper that only changes the label, not the underlying creditCost number, so the page-total reconciliation from the prior fix is unaffected. * fix(audit-logs): fix broken Custom range picker, trim time-range presets Custom range silently did nothing: the time-range trigger was a ChipSelect (Radix DropdownMenu, modal by default), and selecting "Custom range" opened the Calendar popover in the same tick the modal menu began its close/focus-lock cleanup, trapping the popover non-interactive. Swapped to ChipCombobox (Radix Popover, non-modal), mirroring the already-working pattern in the main Logs page exactly. Also trimmed the preset list from 11 to 8 entries (dropped Past 30 minutes/12 hours/14 days) so the menu fits without scrolling. * feat(billing): dedicated Credit usage page with date-range filter and CSV export Follow-up to #5391 per team feedback in Slack: move the credit usage list out of the inline Billing section into its own page, redesign rows to show source ("Chat", "Workflow: <name>") instead of a raw model description + badge, and add real date-range filtering and export. - Billing settings now shows a compact glance (30-day total + a "View usage logs" link) instead of the full inline list. - New /settings/billing/credit-usage page (sibling of [section], mirrors the secrets/[credentialId] detail-route pattern) with day presets (Today/7d/30d/All time) plus a working Custom range picker — the same ChipCombobox+Popover+Calendar wiring the audit-logs fix in this branch uses, not the broken ChipSelect pattern. - Rows show the humanized source label, or "Workflow: <name>" for workflow-sourced events (new server-side workflow-name lookup, batched per page). Dropped the redundant badge and raw model description. - CSV export of the currently-filtered logs via a new GET .../usage-logs/export route (mode: 'text' contract, synchronous single-response CSV — the dataset is a bounded per-user ledger, not a workspace-wide export, so no async job queue needed). Query-filter logic (date-range resolution, workflow-name lookup) is shared with the list route via shared.ts rather than duplicated. - period/startDate/endDate live in the URL via a co-located search-params.ts; the list query keeps keepPreviousData + isPlaceholderData dimming during filter transitions, matching the behavior already shipped in #5391. Verified live end-to-end: back link navigation, custom range picker opens and applies, day presets, CSV export downloads and matches the on-screen rows exactly (credits reconcile with the total), compact Billing summary + link. * refactor(billing): move workflow-name enrichment into getUserUsageLogs, dedup helpers /simplify pass over the credit-usage-page branch (4 parallel review angles: reuse, simplification, efficiency, altitude): - getUserUsageLogs now LEFT JOINs workflow and returns workflowName directly (matching lib/logs/list-logs.ts's established pattern), eliminating the route-layer resolveWorkflowNames query that both the list and export routes previously ran independently. - Added includeSummary (default true) to getUserUsageLogs so the export route's cursor loop can skip the cursor-independent SUM/GROUP BY aggregate it never reads — that aggregate was being recomputed on every page of a paginated export for no reason. - Fixed an off-by-one in the export's pagination loop: `<= MAX_EXPORT_ROWS` let it fetch one more full page past the cap only to discard it; `< MAX_EXPORT_ROWS` with a shrinking per-page limit never overshoots. - Deduplicated the SOURCE_LABELS map (was defined identically in both the page and the export route) into a shared, DB-free source-labels.ts both can import. - Export route now builds CSV rows via lib/table/export-format.ts's toCsvRow/formatCsvValue instead of a hand-rolled escaper. - Added formatApportionedCreditCost to conversion.ts so the page's row rendering shares its zero/sub-credit wording with formatCreditCost instead of re-deriving the same three-way branch. - Replaced the generic requireStartDateForCustomPeriod<Schema> contract helper (nontrivial generic bound for a single four-line refine used at two call sites) with a plain shared error-options object. - Removed the credit-usage page's dateRangeAppliedRef guard — a controlled Radix Popover never re-invokes onOpenChange in response to the parent's own setState call, so the guard was defending against a re-entrant close that can't happen. - Added a modal prop to ChipSelect (forwarded to the underlying DropdownMenu, which already supported it) so a future call site that hits the same "modal select traps a same-tick Popover" bug the audit-logs Custom range fix worked around has a real fix available instead of having to swap components again. Re-verified live end-to-end after the refactor: workflow-name resolution, credit reconciliation, and CSV export all still correct. * fix(billing): drop Dollar cost from the CSV export, strip inline comments We only surface credits to the user, not the underlying dollar figure — "Dollar cost" was the one place the export literally displayed a dollar amount (the rest of the codebase uses dollarCost purely as an internal signal to distinguish a sub-credit charge from a genuinely free event, never rendered as a "$" value). * fix(billing): export honors partial custom date range, surfaces truncation Greptile (P1) and Cursor Bugbot independently caught the same bug: handleExport only forwarded startDate/endDate when BOTH were truthy, but the list query and both API contracts treat endDate as optional for a custom period (defaults to now). A user landing on a bookmarked ?period=custom&startDate=... URL would see populated rows and an enabled Export button, then get a 400 on click since the export omitted the required startDate too. Fixed by forwarding each date independently, matching the list query's existing behavior. Also addressed Greptile's other two findings: - The export route now sets X-Export-Truncated so a 5,000-row-capped download is visible to the user (a toast), not just a server log. Reading that header meant switching the trigger from a plain anchor navigation to fetch+blob — an anchor can't inspect the response before the browser commits to the download. - resolveDateRange now throws explicitly when a custom period is missing startDate instead of silencing the null check with `as string`, which would have produced a silent Invalid Date if ever called without prior contract validation. * fix(billing): remove the export's arbitrary row cap, fix a cursor pagination bug it exposed A personal credit ledger doesn't have the same unbounded-growth problem a workspace table does — capping the export at 5,000 rows just meant long-tenured or high-usage accounts (exactly the ones most likely to need a full export to reconcile a billing question) got silently truncated. Replaced the cap with a 50,000-row circuit breaker that should never fire in normal use (logged as an error, not a warning, if it ever does) and bumped the page size from 500 to 1,000 to cut round trips. Removing the cap surfaced a real, pre-existing bug in getUserUsageLogs's cursor pagination: a raw `sql` template embedded a JS Date object directly as a bound parameter, which the postgres driver can't serialize (unlike drizzle's typed gte/lte operators, which already handle Date correctly elsewhere in the same function). It only ever manifested past the first page, which nothing before this export route's tight multi-page loop reliably exercised. Replaced the raw sql template with drizzle's typed lt/eq/or/and operators, matching the pattern already proven correct in this file. Verified live: seeded 6,000 rows (past the old cap) and confirmed the export downloads all of them in one request with credits reconciling exactly against the total. * perf(billing): skip the redundant cursor lookup when the caller already has it The export loop holds the previous page's rows in memory, so its next cursor's createdAt is already known — getUserUsageLogs was still re-resolving it via an extra DB round trip every page regardless. Added an optional cursorCreatedAt to skip that lookup when provided; the list route's existing callers are unaffected since they don't pass it. Verified live: zero cursor-lookup queries fired across a 3,500-row / 4-page export that previously issued one per page. * fix(billing): apportion credits over the whole filtered set, not per page/call Cursor Bugbot caught this: the list route apportioned each page's rows against only that page's own dollar total, while the export apportioned every exported row against the complete set's total. Since apportionment depends on the full set, the same log could show a different creditCost between the list and the export, or even between two pages of the same "Load more" list — and the sum of every loaded row could visibly drift from the "Total" header shown above them once more than one page had loaded. Extracted getUsageCreditsByLogId — a single, shared, whole-filter apportionment lookup both routes now call instead of each computing their own subset locally. The list route calls it once per page request (same cost profile as the summary aggregate it already pays for every page); the export calls it once before its pagination loop, not per page, keeping the round-trip count this session's earlier fix already reduced. Also extracted the condition-building shared by the main query, the summary aggregate, and this new lookup into one buildUsageLogConditions helper, removing a third copy of that logic. Verified live: summed every row across 4 "Load more" pages and confirmed it now matches the reported total exactly (previously could drift), and confirmed the list and the export produce byte-identical credit sequences for the same rows. * fix(billing): make custom-range startDate/endDate nullable, not '' defaulted startDate/endDate had no sensible static default (they're only ever meaningful mid-custom-range), so defaulting them to '' via .withDefault('') meant switching back to a preset left the URL carrying startDate=&endDate= instead of dropping the params entirely. Made them nullable (no .withDefault) instead, matching the identical fields in the main Logs page's own search-params.ts. Verified live — switching from a custom range back to a preset now clears both params from the URL completely. * feat(audit-logs): add CSV export, matching the Credit usage page pattern Adds an Export chip to the top-right of the Audit Logs page (via SettingsPanel's actions slot — the same header mechanism the Credit usage page uses), downloading every audit log matching the current search/type/date filters as CSV. - New GET /api/audit-logs/export route: same session + enterprise admin/owner gating as the existing list route, reuses the shared buildFilterConditions/buildOrgScopeCondition/queryAuditLogs helpers (already using drizzle's typed operators for cursor pagination, not the raw-sql-with-embedded-Date pattern fixed elsewhere this session), and the same fetch+blob+X-Export-Truncated pattern the Credit usage export already established. - Capped at 10,000 rows (not the 50,000 used for a personal credit ledger) — an org's audit trail can genuinely grow much larger than one user's usage history, so this is sized for "a reasonable audit review window," with truncation surfaced via a toast rather than silently dropped. - Bumped the API-validation-contract audit's route-count baseline for the new route. Verified live against a real enterprise org: switched to "All time," exported ~750 real audit log rows, confirmed formatting (quoted descriptions, actor email fallback) and correct filter scoping. * fix(billing): skip wasted credit apportionment on the summary fetch, block export during stale data Cursor Bugbot caught two real issues: 1. The compact Billing summary glance (limit=1) only ever reads summary.totalCredits, but the list route unconditionally ran getUsageCreditsByLogId's whole-filter scan on every call including this one — pure wasted work for a caller that discards the result. Added an includeCredits query flag (default true, using the shared booleanQueryFlagSchema) so useUsageSummary can opt out; the main paginated view keeps it on since it genuinely needs per-row values. 2. Export stayed enabled while useUsageLogs held stale rows via keepPreviousData mid-filter-transition — a user could change the period/range and click Export before the new data loaded, exporting against the new filter while the table still showed the old one. Export is now also disabled while isPlaceholderData is true. * fix(billing): deterministic apportionment order, block audit export during stale data Cursor Bugbot caught two more real issues on the latest push: 1. Same stale-export bug as the earlier Credit usage fix, this time in Audit Logs: Export stayed enabled while useAuditLogs held prior rows via keepPreviousData, so it could export against a just-changed filter while the table still showed the old one. Now also disabled while isPlaceholderData is true. 2. getUsageCreditsByLogId had no ORDER BY before apportionCredits's largest-remainder tie-break, so which row absorbed a tied remainder credit depended on undefined Postgres row order — the same event's displayed credit could flip between calls (list vs. export, or even two successive requests). Added the same `orderBy(desc(createdAt), desc(id))` the main list query already uses, making the tie-break reproducible. Verified live: 3 identically-costed rows produced the same tie-break winner across 3 repeated requests (previously order-dependent). * fix(billing): distinguish a failed summary fetch from zero usage The compact Billing glance only branched on isPending, so once useUsageSummary settled into an error state, totalCredits stayed undefined and formatCreditsLabel(0) rendered "0 credits" — visually identical to genuinely having no usage this period. Now shows the same neutral "—" placeholder for isError as it already does for isPending. * fix(billing): gate the credit-usage page server-side for enterprise accounts Greptile (P1) caught this: hiding the "View usage logs" link on the Billing page for enterprise accounts doesn't stop direct navigation — anyone with the URL (bookmark, shared link, browser history) could still reach the full page and its CSV export, which enterprise accounts were never supposed to see at all (billing is managed out-of-band for them). Added a server-side check in page.tsx before anything renders: resolve the session, look up the highest-priority subscription, and redirect to /settings/billing if it's enterprise — matching how getHighestPrioritySubscription is already used elsewhere for server-side plan checks, rather than relying on a client-side-only conditional the way the Billing page's inline section does. Also fixes loading.tsx: it was a Server Component (no directive) passing a raw icon function reference into the client Chip component, which fails RSC serialization. Added 'use client'. Verified live in a real browser against both an enterprise account (redirects to Billing before any credit-usage content renders) and a non-enterprise account (reaches the page normally). |
||
|
|
d5082013a2 |
fix(ui): surface silent validation and rejection errors across editors and modals (#5394)
* fix(ui): surface silent validation and rejection errors across editors and modals * improvement(knowledge): toast chunk validation errors instead of inline footer text * fix(tables): reject invalid date/number in expanded cell editor, dedupe invalid-input toasts * fix(knowledge): toast every failed chunk validation attempt, replacing the previous toast * fix(knowledge): dismiss stale validation toast once content validates * revert(ui): drop chat identifier error rendering and profile-name toast |
||
|
|
88330b43bf |
fix(uploads): gate execution-context uploads behind write/admin permission (#5404)
Fallback multipart upload route (/api/files/upload) had no workspace permission check for execution-context uploads, unlike the primary presigned-upload route which requires write/admin. Mirror that gate so both paths enforce the same access control. |
||
|
|
afe3d32032 |
fix(mcp): pass SSRF-guarded fetch into OAuth callback token exchange (#5399)
Mirrors the same wiring already used by probe.ts and revoke.ts, so the callback's token-exchange request goes through the same guarded fetch as the rest of the OAuth flow. |
||
|
|
1574c20806 |
fix(mcp): pass SSRF-guarded fetch into OAuth start flow, matching probe/revoke (#5398)
Discovery and registration during the MCP OAuth start flow were using the default global fetch. probe.ts and revoke.ts already route these calls through createSsrfGuardedMcpFetch(); this brings the start route in line with the same pattern. |
||
|
|
ff8844c7aa |
fix(guardrails): authorize vertexCredential before use in hallucination validation (#5400)
* fix(guardrails): authorize vertexCredential before use in hallucination validation The guardrails validate route now checks credential access via authorizeCredentialUse before passing a caller-supplied vertexCredential into hallucination validation, matching the existing pattern already used in the providers route for the same field. * fix(guardrails): gate vertexCredential authorization on the resolved provider Only run the credential check when the model actually resolves to vertex, matching the providers route's gating exactly, and drop a redundant fallback now that workflowId is already guaranteed present at that point. |
||
|
|
6bc70cb149 |
fix(tables): verify workflow belongs to table's workspace before binding (#5397)
* fix(tables): verify workflow belongs to table's workspace before binding Add a check that a table workflow group's workflowId resolves to an active workflow in the table's own workspace, in both the create and update handlers, before it is persisted. * fix(tables): reorder JSDoc for mapWorkflowGroupError Greptile flagged the JSDoc for mapWorkflowGroupError as orphaned after validateWorkflowInWorkspace was inserted between the comment and the function it documented. Move the comment back above its function. |
||
|
|
fd98218e58 |
fix(gmail): strip CR/LF from header values before MIME assembly (#5395)
* fix(gmail): strip CR/LF from header values before MIME assembly Adds sanitizeHeaderValue and applies it to to/cc/bcc/subject/ inReplyTo/references and the attachment filename in buildSimpleEmailMessage and buildMimeMessage before they're placed into MIME header lines. * fix(gmail): sanitize attachment mimeType in Content-Type header attachment.mimeType was written verbatim into the Content-Type header, unlike the other header fields this PR sanitizes. Route it through the same sanitizeHeaderValue helper for consistency. |
||
|
|
2e3574407b |
fix(chat): fail-safe to noindex if the deployment lookup errors (#5396)
generateMetadata queried the DB with no error handling, unlike the identical query in api/chat/[identifier]/route.ts (which already wraps it in try/catch). There's no error.tsx under app/(interfaces)/chat/ — metadata resolution errors aren't caught by route-segment error boundaries at all, only the root global-error.tsx — so a DB hiccup during this lookup would take the whole page down to a generic error page instead of just failing to determine indexability. Catches the error, logs it, and defaults to noindex: if we can't confirm the deployment is safely public, that's the correct SEO default anyway, not a reason to crash the request. Flagged by Cursor Bugbot on #5388 (already merged); this ships the fix as a standalone follow-up since the underlying code is already live. |
||
|
|
ca2a52ccf7 |
feat(billing): expose credit usage log in Billing settings (#5391)
* feat(billing): expose credit usage log in Billing settings
Add a "Credit usage" section under Billing, below Invoices, showing a
paginated, period-filterable list of individual credit-consuming
events (model, tool, and fixed charges) for every plan except
Enterprise. Wires the existing (previously unused) usage-logs backend
to a proper contract, React Query hook, and emcn-styled UI:
- getUsageLogsContract in contracts/user.ts, broadened the source enum
to match the real usage_log schema
- Rewrote the route to use parseRequest per the API boundary rules
- useUsageLogs infinite-query hook, keyset-paginated
- CreditUsageSection: period dropdown, total-credits summary, row
list with source badges, "Load more"
- Extracted formatCreditsLabel in conversion.ts as the shared
formatter for already-converted integer credits
* fix(billing): move usage-log key factory out of the 'use client' boundary
Greptile P2: usageLogKeys lived in the 'use client' usage-logs.ts hook
file — importing it from a server component (e.g. a future prefetch)
would resolve to a client-reference stub and crash at build/SSR, the
same class of bug that hit tables' key factory before. Extracted to
hooks/queries/utils/usage-log-keys.ts, matching table-keys.ts and
folder-keys.ts.
Also renamed a shadowed `source` map param in the route to `sourceKey`
for clarity.
* chore(billing): dedupe the usage-log period literal type
The '1d' | '7d' | '30d' | 'all' union was hand-typed in three places
across usage-logs.ts and credit-usage-section.tsx. Extracted
usageLogPeriodSchema in the contract and derived UsageLogPeriod from
it, so the hook, the component, and the key factory all share one
definition instead of risking drift.
* improvement(billing): move credit usage period filter into the URL
/cleanup pass (nuqs rule, react-query-best-practices, emcn review):
- period was a plain useState, but it's exactly the kind of shareable
list filter sim-url-state.md calls out for nuqs — migrated to
billingParsers/useQueryStates so the selection deep-links, survives
reload, and matches every sibling settings section (recently-deleted,
inbox, teammates). Derives its literal values from
usageLogPeriodSchema instead of a fourth copy of the same union.
- Removed an unjustified showSelectedCheck={false} on the period
ChipDropdown — the one other usage in the codebase is a one-shot
action menu with no persistent selection; this is a real filter and
should show the check like the default intends.
- Added placeholderData: keepPreviousData to useUsageLogs — period is
a variable query key, so without it switching periods flashed the
loading empty-state instead of smoothly transitioning.
Verified live: deep-link with ?period=7d pre-selects and loads
correctly, switching periods updates the URL, and the selection
survives a hard reload.
|
||
|
|
4fe372b583 |
improvement(seo): extract shared withFilteredNoindex helper (#5392)
The `{ ...base, ...(isFiltered && { robots: { index: false, follow: true } }) }`
faceted-navigation noindex pattern was duplicated verbatim across five
catalog pages (integrations, models, blog, careers, pricing). Extracted to
lib/landing/seo.ts alongside buildLandingMetadata, giving the pattern a
name and a single point of change. Pure refactor — verified byte-identical
rendered output (robots meta + canonical) on baseline and filtered variants
of all five pages before and after.
|
||
|
|
8937005076 |
improvement(compare): tighten comparison-page copy, cut self-referential hedging (#5390)
* improvement(compare): tighten comparison-page copy, cut self-referential hedging Across sim.ts and all 20 competitor profiles, remove phrasing that describes a claim's own provenance/reliability (e.g. 'marketing claim', 'marketing copy states', 'as of this check') in favor of stating the fact directly. Also trims overlong run-on sentences into shorter, more direct ones. No numbers, dates, comparisons, confidence levels, or sources changed. * fix(compare): avoid accidental boolean-icon misparse from copy tightening Three facts whose tightened wording started with a bare 'No ' now matched the Yes/No boolean-icon convention (parseFactValue), collapsing a nuanced 'Partial' or plain descriptive sentence into a bare X icon. Rephrase with 'Not'/'not' so these render as full text again, matching pre-edit behavior. |
||
|
|
03e0e075e1 |
improvement(styling): consistent branded text-selection color app-wide (#5389)
* improvement(styling): use a consistent branded text-selection color app-wide Previously no ::selection rule was defined anywhere, so text selection fell back to the browser/OS default, which dims when a window loses focus and made pages look inconsistent side by side. Adds one global rule using the existing --selection token. * fix(styling): resolve markdown-selection conflict, use --white token Remove the local link/strikethrough ::selection color overrides in rich-markdown-editor.css now that the global ::selection rule already forces uniform white text on every selection. Also swap the literal #ffffff for the existing --white design token. |
||
|
|
7ff4f17eb2 |
fix(seo): fix GSC indexing issues, remove unused academy/partners pages (#5388)
* fix(seo): fix GSC indexing issues, remove unused academy/partners pages - robots.ts: unblock /chat/ (page-level noindex now gates gated/inactive deployments instead), drop the now-vestigial blog-tag/link-preview carve-out - next.config.ts: add missing redirects for renamed integration slugs (sap-s-4hana, calcom), removed /partners, and removed /academy - fix missing canonical/noindex on filtered catalog pages (integrations, models, blog, careers, pricing) causing GSC "duplicate, Google chose different canonical" - standardize page titles to "Page | Sim, the AI Workspace" across the board - remove the academy marketing pages and partner program page (content consolidated into docs.sim.ai/academy); drop the unused academy_certificate table via migration and strip the sandbox-mode plumbing from the workflow editor that only academy ever used * fix(migrations): defer academy_certificate table drop to a follow-up PR CI's expand/contract migration safety check correctly flagged this: the academy_certificate DROP TABLE was bundled in the same PR as removing the code that reads/writes it (the certificates API route had no feature-flag guard of its own, so it was reachable independent of the marketing pages being disabled). Dropping the table in the same deploy risks breaking any pod still running the old code during a rolling deploy. Restores the table/enum in schema.ts and the test mock, and removes the 0254 migration. The table drop should ship in its own PR once this one's code removal is confirmed live. * fix(seo): drop dead revalidate exports on searchParams-driven pages Any Server Component in the route tree reading searchParams forces the whole route to fully dynamic per-request rendering, which overrides ISR — revalidate is a silent no-op once that happens. True on pricing/careers because generateMetadata now parses searchParams directly, and was already true on blog before this PR (its page body already read searchParams). Flagged by Greptile on pricing; same root cause applies to all three. |
||
|
|
e1b8200660 |
fix(sso): support skipping the OIDC UserInfo endpoint at registration (#5386)
* fix(sso): support skipping the OIDC UserInfo endpoint at registration * fix(sso): cap OIDC discovery fetch at 10s to avoid stalling registration * test(sso): default-mock discovery fetch so intent is explicit * fix(sso): prefer client_secret_post and surface discovery failure reasons * fix(sso): always resolve token auth method and skip SSRF-checking a discarded userInfoEndpoint |
||
|
|
331875b7fe |
fix(billing): stop showing "View all" when there are no more invoices (#5387)
* fix(billing): stop showing "View all" when there are no more invoices Show fewer invoices (10) and derive hasMore from the finalized-invoice count instead of Stripe's raw has_more, which counted drafts we filter out client-side and could report more invoices than actually exist. * chore(billing): move invoice pagination explanation into TSDoc Extract collectFinalizedInvoices with a TSDoc comment explaining the Stripe has_more/draft-filtering rationale, instead of an inline comment block. * fix(billing): don't hide View all when the page-cap is hit inconclusively Greptile P1: if MAX_STRIPE_PAGES is exhausted while the finalized count sits exactly at MAX_INVOICES and Stripe still has_more, hasMore was silently returned as false. collectFinalizedInvoices now also returns whether Stripe's cursor was still open at exit, and the route ORs that into hasMore so the safety cap can never suppress "View all". Also asserts starting_after cursor propagation across pages and adds a test for the safety-cap-hit case. |