* improvement(permissions): permission groups scoped to organization level
* chore(db): drop 0235 permission-groups migration to regenerate after staging merge
* merge latest staging
* feat(hubspot): add notes, emails, properties & associations tools
- Add 11 tools: get_properties (read property/enum options), notes
(create/get/list/search), email engagements (create/get/list/search),
and v4 associations (list/create)
- Add scopes: crm.objects.notes.read/write, crm.objects.emails.read/write,
sales-email-read (required for email engagement content)
- Wire new operations into the HubSpot block (subBlocks, conditions,
tool mapping, outputs, BlockMeta templates/skills)
- Fix pre-existing bugs found in validation: list_marketing_events list
URL (/marketing/marketing-events/v3), appointment property names
(hs_appointment_*), get_users output shape (CRM envelope), create_list
response unwrap, and stringified-associations parsing in create tools
- Regenerate integration docs
* fix(hubspot): drop non-grantable notes/emails scopes; remove inline comments
- crm.objects.notes.*/crm.objects.emails.* are not grantable HubSpot
scopes (would break the OAuth authorize flow). Notes/emails engagement
and association endpoints are authorized by crm.objects.contacts.*;
sales-email-read remains for email-engagement content
- Remove non-TSDoc inline comments from new tool files
* fix(hubspot): address review comments
- Forward the properties param for the Get Users operation (block param
mapping + Properties-to-Return field now include get_users)
- Use Record<string, unknown> for create_note/create_email request bodies
- Only send Content-Type on create_association when a body is sent
(default-association PUT has no body)
- Remove stray duplicate JSDoc opener in types.ts
* improvement(resource): simplify table shell, toasts, and loading breadcrumbs
- Resource.Table: remove internal sorting (defaultSort/sortValues) and the
emptyMessage state — rows render in the order given, chrome always paints
- Resource: root is now the positioning context for overlays; consumers
(files, tables, knowledge, document) wrap detail views in <Resource>
instead of hand-rolled divs
- ResourceHeader: root titles no longer truncate during initial layout;
LocationFocusVeil gates the portal on mount to fix a hydration mismatch
- Toasts: drop the StackDismiss ring and stack countdown — each toast runs
its own timer; remove the Mod+E clear-notifications command; align toast
typography and icons with chip chrome
- Breadcrumbs: use the canonical '…' placeholder while names load
- incident.io: fix display name and catalog slug (with redirect)
- Add dev:capped / dev:full:capped scripts with a 4GB heap cap
* feat(scheduled-tasks): calendar views; rename Mothership to Sim/Chat
Add month/time calendar views for scheduled tasks with toolbar, event
chips, and a create-task modal, backed by calendar-grid and
schedule-events utils (with tests) and a use-calendar hook. Replace the
old schedule-modal/context-menu flow.
Rename the "Mothership" agent to "Sim" and the chat surface to "Chat"
across landing copy, constitution, block metadata, API error messages,
and copilot/data-drain internals. Drop unused workspace route layouts.
* fix(emcn): force dropdown menus modal inside dialogs so they scroll
A non-modal DropdownMenu portals outside an open dialog's
react-remove-scroll subtree, so its content cannot be wheel-scrolled
(e.g. the time picker in the scheduled-task create modal). ModalContent
now marks its subtree via an InsideModal context, and the emcn
DropdownMenu root upgrades itself to modal inside dialogs so it mounts
its own scroll lock and focus scope; page-level menus keep their
consumer-chosen modality.
Also stretch the create-task modal's date/time chip controls to full
width and drop the dead EDGE_GUTTER constant left behind by the
equal-tracks calendar layout.
* fix(scheduled-tasks): address review — midnight rollover, stub feedback, smooth Today scroll
- useCalendar: today was frozen at mount, so after midnight the isToday
column highlight and the current-time indicator stayed on the previous
day. today is now state refreshed by a sleep-resilient minute poll
that only re-renders when the calendar day actually changes
- CreateTaskModal: the stub submit closed silently, reading as false
success; it now shows an info toast that the task was not created
- ScheduleCalendar: Today presses scroll smoothly as an orientation
cue; mount and scope switches keep instant positioning
* feat(emcn): view-only field primitives + scheduled-task modals
- ChipCopyInput (canonical view-only copy field), ChipTimePicker,
ChipModalField type='copy', ChipTextarea viewOnly; new border chip
variant and shared chipPrimaryFillTokens
- migrate ~40 consumers off disabled inputs and the deleted
CopyableValueField; ChipConfirmModal description->text and
secondaryActions[] API sweep
- scheduled-tasks: rename create-task-modal to task-modal, add
task-details-modal + task-context-menu, useScheduledTasks hook
- home: extract prompt-editor (usePromptEditor) out of user-input
* feat(scheduled-tasks): persist + run tasks via the job-schedule backend
Wire the calendar UI to the existing sourceType='job' workflow_schedule
backend instead of local component state, so tasks persist and actually
run as Sim agent invocations.
- schema: add contexts (@-mentions resolved into the run), excludedDates
(per-occurrence deletes), and endsAt (recurrence end) to workflow_schedule
(migration 0235)
- contracts/schedules: expose one-time `time`, contexts, endsAt on create;
add the exclude_occurrence action; nullable cron in the create response
- orchestration: persist the new fields, honor exclusions + end boundary via
a shared computeNextRunAt, add performExcludeOccurrence
- execution: forward contexts to /api/mothership/execute and recompute the
next run through computeNextRunAt
- mothership/execute: accept + resolve contexts like the interactive chat path
- frontend: replace the local hook with React Query (create/update/delete +
exclude-occurrence), expand recurrences into calendar occurrences, add the
recurrence control (frequency + end) and the recurring this/all delete dialog
* chore(scheduled-tasks): satisfy biome line-width on user-input imports
* fix(scheduled-tasks): log agent-context resolution failures in execute route
* fix(scheduled-tasks): keep context double-cast adjacent to its boundary annotation
* fix(scheduled-tasks): preserve @-mention contexts on edit; sync editor valueRef on input
* fix(scheduled-tasks): footer-wrap modal controls; audit fixes; cleanup
- chip-modal: footer secondary cluster now wraps (min-w-0 flex-wrap) with a
non-shrinking action cluster, so scheduling controls can never clip Cancel/
primary; recurrence labels compacted so the common case stays one row
- schedule-execution: failure path now completes a recurring job when maxRuns/
endsAt/exclusions are exhausted (and a one-time/maxRuns job), mirroring the
success path instead of leaving it active with a stale nextRunAt
- prompt-editor: commitValue keeps valueRef in lockstep with state on the
mention-hook setter paths, completing the stale-ref fix
- task-modal: preserve @-mention contexts on edit (seed editor.setContexts);
single emptiness source of truth
- recurrence-control: preserve prior count when toggling end type; drop a
needless useMemo
- contracts: reuse scheduleContextSchema for the execute contexts shape
* feat(scheduled-tasks): valid future default launch time; test scheduleToTasks mapping
* chore(scheduled-tasks): convert added inline comments to TSDoc
* simplify(scheduled-tasks): reuse date-fns for launch/end math; drop dead 'running' status + single-use helper
---------
Co-authored-by: waleed <walif6@gmail.com>
* feat(blocks): add external-service url to block metadata
- Add optional `url` field to BlockMeta for the integration's own homepage (e.g. exa.ai, salesforce.com), distinct from docsLink which points at Sim's docs
- Populate `url` on all 209 integration blocks with verified homepages (protocol/internal blocks without a single vendor site are left without one)
- Document the field in the add-block skill and command, with rule + checklist entries
- Backfill missing docsLink on dspy and add a few accurate tag entries
* fix(blocks): drop tag backfills to avoid catalog drift
Revert the agiloft/exa/tailscale tag additions; integrations.json (used by landing/SEO) is not regenerated here, so the expanded tags would diverge from getAllBlockMeta(). Keep the url additions, which are not projected into integrations.json.
* fix(blocks): correct inaccurate tool URLs flagged in review
- spotify: open.spotify.com (web player) -> www.spotify.com brand homepage
- sts: point to the STS API reference (aws.amazon.com/sts 404s; STS has no standalone product page) instead of the shared IAM page
- microsoft: drop locale-specific /en-us/ segments across Excel, Teams, OneDrive, SharePoint, Outlook, Dataverse, Planner, Entra ID, and shorten OneDrive/Outlook to stable product roots
* fix(mothership): keep isAnimating latched so completed messages don't flash
The streamed-text reveal latches `mode` and `animated` via `keepStreamingTree`
to avoid the streaming→static handoff flash, but `isAnimating` was still wired
to `isRevealing`, which flips false the instant the reveal catches up. Streamdown
treats `isAnimating: false` as "streaming over" and rebuilds the whole message
without the per-word animation spans — that DOM rebuild is a visible flash when a
message finishes.
Wire `isAnimating` to the same `keepStreamingTree` latch so all three Streamdown
props stay constant across completion. Content is stable once revealed, so a
permanently-true `isAnimating` has no new tokens to fade and never re-animates.
* feat(tavily): official brand icon and white block background
Replace the Tavily block icon with the official brand mark and set the block
bgColor to white. Ran generate-docs so the docs app icon, the Tavily docs page,
and the integrations catalog pick up the new icon/color.
* fix(skills): reuse shared upload field in skill import modal; logo-only Quartr icon
- Replace the hand-rolled drop zone in the skill import modal with the shared
ChipModalField type='file' control (same component the Knowledge Base and
Help & Support modals use), so the upload zone is visually consistent.
- Migrate the GitHub-URL and paste-content rows to ChipModalField so every
field shares the canonical px-4 gutter and error rendering, and align the
'or' dividers to match.
- Drop the monospace font on the paste textarea so its text matches the rest
of the modal.
- Quartr icon now renders the logo mark only (no wordmark) as a black mark on
a white rounded tile.
* fix(emcn): restore upload spinner via loading prop on ChipModalField file control
Addresses review feedback — the shared file drop zone now accepts an optional
loading prop that renders an animated spinner and blocks further picks while an
async import is in flight, restoring the feedback the skill import modal lost
when it migrated off its bespoke drop zone.
* 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(db): correct misleading error message when DATABASE_REPLICA_URL is malformed
The error message said reads fall back to the primary when unset, but the
code throws a fatal error instead. The misleading parenthetical contradicted
actual behavior and could waste time during incident response when an operator
sees this message and expects graceful degradation.
---------
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: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
* perf(mothership): virtualize chat transcript and isolate input from stream re-renders
Long chats rendered every message into the DOM with no windowing — a custom
rAF "progressive list" only smeared the mount cost across frames without
capping it. At ~1000 messages this was 52k DOM nodes and a 21s main-thread
block on open, and the input toolbar re-rendered on every streamed token.
- Virtualize the message list with @tanstack/react-virtual using dynamic
measureElement, stable per-row keys, and a tuned size estimate. Only the
visible window mounts, so load cost is now flat regardless of transcript
length. Remove the now-redundant useProgressiveList hook.
- Memoize UserInput and stabilize its callbacks (useCallback in MothershipChat
and home) so streaming ticks no longer re-render the entire input toolbar.
- Keep the existing useAutoScroll for streaming stick-to-bottom (it reads the
virtualizer's real scrollHeight) and add a per-chat scrollToIndex for initial
positioning before paint.
Measured on a cloned 1032-message chat: time-to-rendered 26.3s -> 1.7s,
main-thread blocked 21.4s -> 0.8s, DOM nodes 52k -> 1.4k, typing-while-
streaming p-max 104ms -> 26ms. Adds scripts/perf/ harness used to validate.
* address review: pending-chat scroll, flash on tail unmount, empty-row gap, per-role estimate
- Pending-chat initial scroll (Cursor, high): seed the scrolled-chat guard with
a unique sentinel so a not-yet-persisted chat (undefined chatId) with messages
still scrolls to bottom instead of being treated as already-scrolled.
- Streaming-row flash (review of virtualization): pin the last row in the
rendered window via rangeExtractor so scrolling it out of the overscan window
and back mid-stream can't unmount/remount it and re-fire the reveal fade.
- Empty assistant row gap (Greptile): move the row gap from the virtual-item
wrapper into the row content so a null-rendering (finalised, empty) assistant
turn collapses to zero height instead of leaving a pb-6 blank slot.
- Per-role row-height estimate instead of a single blended constant, so the
scrollbar drifts less as off-screen rows resolve.
- Drop the scripts/perf harness from the PR.
* fix(mothership): preserve user-row top spacing in virtualized layout
Restoring pt-3/pt-2 on the user row keeps the exact inter-row rhythm from the
old space-y-6 layout: assistant→user gaps stay 24+12px and user→assistant stay
24px, instead of becoming uniform when the gap moved to per-row pb.
* fix(mothership): don't re-scroll when a pending chat persists its id
The per-chat initial-scroll guard treated undefined→persisted-id as a chat
switch and scrolled to the bottom again, yanking the viewport down if the user
had scrolled up mid-stream. Treat that transition as the same conversation:
adopt the id without re-scrolling. Genuine chat switches (id→different id) still
re-scroll.
* 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(mothership): tenant-check outputTable writes and route them through replaceTableRows
maybeWriteOutputToTable / maybeWriteReadCsvToTable accepted any table id
without verifying it belongs to the caller's workspace, so a foreign
table's rows could be wiped and replaced cross-tenant. They also wrote
rows with raw drizzle keyed by column *name*, bypassing the service
layer's job-slot lock, validation, plan row limits, rowCount
maintenance, and the stable column-id keying every other writer uses.
Both handlers now reject tables outside the caller's workspace and
delegate to replaceTableRows with name→id remapped rows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(mothership): fail outputTable writes per-row when any row matches no columns
Review feedback: the all-rows-empty guard let mixed batches slip
unmatched rows through as empty objects. Reject on the first row that
maps to zero columns, naming the row. Adds the missing CSV-suite parity
tests (no-matching-headers, service-error surfacing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(tables): heartbeat export job before upload so the stale janitor can't kill a live finalize
* chore(tables): rename stale-janitor counters to cover all table job types
* refactor(deployments): consolidate version-list reads onto listWorkflowVersions
The UI deployments list route and the mothership get_deployment_log handler
each had their own inline version query; both now consume the shared
persistence helper, so every deployment surface (UI, v1, admin, tools,
mothership) reads versions through one code path.
* refactor(deployments): shared status mapper, single-version fetch, and v1 workflow resolver
- statusForOrchestrationError maps orchestration error codes to HTTP statuses
in one place (was an identical ternary in six routes: UI deploy/activate,
v1 deploy/rollback, tool deploy/promote)
- getWorkflowDeploymentVersion consolidates the single-version fetch used by
the UI version GET and the deployments tool version route
- resolveV1DeploymentWorkflow extracts the v1 mutation prologue (active-record
load, admin permission check, 404 masking) shared by deploy, undeploy, and
rollback
* feat(deployments): add v1 deployment endpoints and Deployments block
* fix(deployments): require deployed workflow for rollback, normalize warnings, guard orphaned workspaceId
* fix(deployments): workspace-bound tool routes, optional-body parsing, version bounds, and 404 masking
- Tool routes now require the executing workspace ID and reject cross-workspace targets
- v1 deploy/rollback read optional bodies via parseOptionalJsonBody (size-capped, 400 on malformed JSON)
- Version numbers bounded to the Postgres integer range
- v1 mutation routes mask access failures as 404, matching the v1 detail route
- listWorkflowVersions returns description and normalizes admin-api deployedByName (parity with mothership get_deployment_log)
- Workflow selector no longer auto-selects the first workflow (new autoSelectFirstOption opt-out)
- Shared deployment version metadata field schemas across UI/v1/tool contracts
* chore(api-validation): bump route baseline for rebased staging (826)
* fix(docs): use real ID formats in OpenAPI examples
Workflow, workspace, folder, knowledge-base, document, and execution IDs are
plain UUIDv4; workspace file IDs are wf_<shortId>; table and row IDs are
tbl_/row_ + de-dashed UUID. Replaces all fake prefixed example IDs (wf_abc123,
ws_xyz789, exec_..., kb_..., etc.) accordingly and marks the deploy body
description as nullable to match the shared schema.
* feat(deployments): resolve workflow names in block UI, add workflow_undeployed Sim trigger event
- Deployments block now uses the workflow-selector subblock (same as the
Workflow block), so the canvas tile shows the workflow name instead of the
raw ID; reverts the now-unneeded dropdown autoSelectFirstOption prop
- Adds workflow_undeployed to the Sim workspace-event trigger, emitted by
performFullUndeploy through a shared lifecycle-event dispatch loop
* ci(migrations): fail dev schema push with an actionable error on rename/drop prompt
`drizzle-kit push --force` only suppresses the data-loss confirm, not the
rename-vs-drop disambiguation prompt. That prompt fires whenever a diff both
adds and drops tables/columns at once (e.g. migration 0231 created
sim_trigger_state while dropping the workspace_notification_* tables), and in
CI it crashes with a bare "Interactive prompts require a TTY" stack trace.
Catch that specific failure in the dev push step and emit a GitHub error
annotation explaining the cause and the fix (drop the stale objects on the dev
DB to match schema.ts — the same DROPs the versioned migration already applied
to staging/prod), instead of leaving an opaque trace. Exit status is preserved
either way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* improvement(auth): layer disposable-email-domains into signup email validation
Compose the disposable-email-domains list (exact + wildcard) into better-auth-harmony's validator alongside its bundled Mailchecker list, so signup rejects an email if either flags it. Server-only module to keep the dataset out of the client bundle.
* improvement(auth): defer disposable-domains dataset behind lazy dynamic import
Address review: load the ~120K-entry dataset on first use instead of at module import, so deployments with SIGNUP_EMAIL_VALIDATION_ENABLED off never pay the cost. Add a bare wildcard-base test case.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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
* feat(credentials): add Atlassian service account credentials
* improvement(credentials): tighten Atlassian service account plumbing
- Collapse fetchOAuthTokenBundle into fetchOAuthToken (returns the bundle)
- Reuse serviceAccountJsonSchema in the JSON form instead of hand-rolled checks
- Use parseAtlassianErrorMessage for log details; drop one-line bearer helper
- Extract ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID/_SECRET_TYPE constants
- Use Drizzle .returning() instead of post-insert SELECT
- Helper for the duplicated 401/403 + non-OK pattern in the validator
* docs(credentials): add Atlassian service account setup guide
- New /integrations/atlassian-service-account doc covers token creation,
scope selection, and adding the credential to Sim
- Form's "View setup guide" link now points at the doc
- Fix the existing Google form link that pointed to the wrong path
Screenshot TODOs left inline as MDX comments for the docs team.
* docs(credentials): add Atlassian service account screenshots
- Auth type picker, Sim add-credential modal, Jira block credential dropdown
- Scope-picker screenshot still TODO
* docs(credentials): add Atlassian scope picker screenshot
* fix(credentials): address greptile feedback on Atlassian SA
- Drop stale 'email and API token' copy from the service description
(we only collect a token + domain, no email field)
- Move duplicate display-name check inside the create transaction so
concurrent POSTs can't both pass the check and insert duplicates
* fix(docs): move Atlassian screenshots to docs/public
Docs site serves /static/* from apps/docs/public, not apps/sim/public —
matches the existing google-service-account screenshot convention.
* fix(credentials): address review feedback on Atlassian SA
- SSRF: only accept *.atlassian.net / *.jira-dev.com hosts before fetching
tenant_info, blocking probes against localhost/internal IPs
- Confluence spaces selector: pull cloudId from the SA secret instead of
calling accessible-resources, which 401s for scoped service-account tokens
- Case-insensitive https?:// strip so HTTPS://team.atlassian.net normalizes
correctly
* chore: merge staging and bump API validation route baseline to 727
* perf(credentials): single-resolve in confluence spaces selector
Atlassian SAs were hitting resolveOAuthAccountId twice (once via
refreshAccessTokenIfNeeded, once directly to read cloudId) and
decrypting the secret twice (via getAtlassianServiceAccountToken
inside refresh, then again via getAtlassianServiceAccountSecret).
Resolve once up front and branch the whole flow on the result —
SA path skips refresh entirely and pulls token+cloudId from a
single secret read.
* refactor(credentials): consolidate Atlassian SA creation into /api/credentials
Atlassian service-account creation lived in its own route, contract, and
mutation hook, copy-pasting ~140 lines of insert/membership/audit/posthog
boilerplate from /api/credentials. Two endpoints means two authz paths,
two audit shapes, two TOCTOU stories — they will drift.
Fold Atlassian into the existing service_account branch of /api/credentials,
dispatching by providerId. The Atlassian validator (tenant_info + Bearer
/myself, SSRF host allowlist, typed error codes) lives in
lib/credentials/atlassian-service-account.ts and is the only Atlassian-
specific piece left. AtlassianValidationError maps to a {code, error} 400
in the existing catch block; the rest of the flow (transaction, members,
audit, posthog, dup-check) is now shared with Google SA + env credentials.
Delete:
- /api/auth/atlassian-service-account route
- contracts/atlassian-service-account.ts + barrel export
- useCreateAtlassianServiceAccount hook
- API audit baseline 727 → 726
Both forms (Google JSON-key, Atlassian token+domain) now call
useCreateWorkspaceCredential with the appropriate body shape.
* fix(credentials): close TOCTOU and restore typed errors after consolidation
- Add inner duplicate-guard inside the create transaction (DuplicateCredentialError)
to close the race that the outer findExistingCredentialBySource leaves open.
service_account rows have no DB-level unique index on (workspaceId, providerId,
displayName), so this is the actual safety net. Tx-internal check applies to
Google + env_workspace too — race-safety win for all credential types.
- Re-emit {code: 'duplicate_display_name', error: ...} on conflict so the form's
ERROR_MESSAGES.duplicate_display_name mapping is reachable again.
- Thread Atlassian-specific audit metadata (atlassianDomain, atlassianCloudId)
back into recordAudit; consolidation had dropped them.
- Use ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID constant in contract superRefine.
- Drop `error: any` in catch in favor of `error: unknown` + getPostgresErrorCode.
* chore(credentials): drop dead createWorkspaceCredentialBodySchema + updateWorkspaceCredentialBodySchema
Both shadowed the actually-used schemas (createCredentialBodySchema /
updateCredentialByIdBodySchema) and were missing the apiToken/domain
Atlassian fields. A future change could pick the wrong one and silently
drop those fields. Confirmed zero non-definition references in the repo
(grep across apps/, packages/, scripts/ minus build artifacts).
* fix(credentials): scope inner duplicate re-check to service_account
OAuth dedupes by accountId, env_* by envKey — both have DB-level partial
unique indexes that surface as 23505. The previous inner re-check fired
for all types and always threw DuplicateCredentialError, which mapped to
'duplicate_display_name' in the UI even when the real conflict was a
duplicate OAuth account or env key. Restrict the in-tx re-check to
service_account (the only type without a DB-level index) and let the
23505 handler emit a generic message for everything else.
* feat(integrations): add Documentation link to service-account connect modals
* fix(integrations): point service-account modal docs links at Sim guides
* fix(integrations): rename service-account modal docs link to Setup guide
---------
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
A filtered select-all Stop only cancels matching rows server-side, but
the optimistic update flipped in-flight cells across every cached rows
query — stale unfiltered views showed workflows as cancelled until the
refetch. snapshotAndMutateRows gains an onlyKey option; the cancel
mutation passes the active view's exact cache key (filter + sort) when a
filter is present, and onSettled's invalidation reconciles other views.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* improvement(files): fit-width previews and chip-chrome viewer controls
- PDF and DOCX previews now treat 100% zoom as fit-to-width instead of
capping at the page's natural print size, removing the dead gutters in
wide panels (pdf.js re-renders the canvas at the target width and DOCX
uses CSS zoom, so both stay crisp)
- PreviewToolbar page/zoom controls move from 24px ghost Buttons with
off-token labels to canonical emcn chips (icon-only Chip pills, text-sm
--text-body value labels)
- XLSX sheet tabs move from underline-style ghost Buttons to a chip
cluster using the active pill state
- Audio preview swaps the music emoji for the lucide Music icon on
design-system tokens
* improvement(files): debounce PDF panel-resize re-rasterisation
With fit-to-width every pageWidth change re-rasterises all page canvases,
so per-tick updates during a panel-divider drag re-rendered the document
continuously. First measurement still applies immediately.
* fix(files): don't let a zero-width first measurement consume the immediate resize slot
A hidden container reports zero width from the ResizeObserver; treating
that as the initial measurement pushed the real first width onto the
debounce path and delayed initial render.
* improvement(files): module cleanup — dedupe media previews, debounce docx refits
- Merge the near-identical AudioPreview/VideoPreview into one MediaPreview
(shared fetch/blob-URL/error/loading path; only the player differs)
- Debounce docx resize refits the same way the PDF preview debounces width
measurements (the initial fit comes from the render path, not the observer)
- Document the load-bearing buffer copy in pdf-viewer (pdf.js transfers and
detaches the ArrayBuffer it receives)
* fix(integrations): resolve OAuth connect UI by service id instead of display name
* test(integrations): pin OAuth service resolution for all catalog integrations; fix credential branding reverse lookup
* fix(docs-gen): blank string literals and comments before brace scanning in extractOAuthServiceId
* feat(tables): paginated background row-delete jobs via table_jobs
* fix(tables): address review on async row-delete (filtered count, scoped optimistic clear, Cmd+A select-all, hide delete from tray)
* improvement(tables): filter-aware select-all runs, delete-job read mask, keyset index + autovacuum tuning
* feat(tables): run import/delete/export/backfill jobs on trigger.dev with in-process fallback
* improvement(tables): raise delete page to 10k and export batch to 5k
* improvement(tables): raise CSV import batch to 5k rows (param-cap bounded)
* feat(tables): surface export jobs in the header tray with progress, cancel, and download
* improvement(tables): surface exports as derived tables-scoped toasts instead of the import tray
* Revert "improvement(tables): surface exports as derived tables-scoped toasts instead of the import tray"
This reverts commit 1ea5871610.
* fix(tables): preserve export storage key (NoSuchKey) and unify jobs in one spinner tray
* improvement(tables): jobs tray icon reflects aggregate state (spinner/check/alert)
* fix(tables): restore jobs tray on the tables list (dropped in staging merge)
* improvement(tables): keyset-paginate export row reads (offset paging was O(n^2) over large tables)
* perf(tables): keyset pagination for grid infinite scroll
Default-order row pages now cursor on (order_key, id) instead of OFFSET —
each page is an index seek on tableOrderKeyIdx, where OFFSET re-scans and
discards every prior row (O(N²) across deep scrolls and full drains like
select-all/export-to-clipboard). Sorted views keep offset paging; the
contract refines after+sort as mutually exclusive. v1 public rows API is
unchanged (extends the unrefined base, omits after).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): show export in job tray immediately on kickoff
The export-jobs query's poll only self-sustains once a running job is
already in the cache, so a freshly kicked export stayed invisible until
an SSE event or page refresh. Invalidate the tray query on kickoff
success so the icon appears right away.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): surface real row/column write errors in toasts
Drizzle wraps DB errors in DrizzleQueryError whose message is the failed
SQL — the real cause (e.g. the row-limit trigger's RAISE) sits on .cause,
so the routes' substring classification never matched and everything fell
through to generic 500s ("Failed to insert row"). Add rootErrorMessage
(cause-chain unwrap) and a shared rowWriteErrorResponse classifier that
consolidates the per-route pattern lists and rewrites the trigger message
into a friendly "Row limit exceeded — capped at N rows". Applied across
the app and v1 row-write routes and the columns route.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(tables): tenant-bound filtered row counts (12.7s -> 0.6s)
JSONB filter predicates (->> ILIKE / range casts) are opaque to the
planner: it estimates a handful of matches and picks a parallel seq scan
over the entire shared user_table_rows relation — every tenant's rows —
for the page-0 COUNT(*), so any non-equality filter on a large table cost
10s+ regardless of how few rows matched. Run filtered counts in a
transaction with SET LOCAL enable_seqscan = off, forcing the
tenant-bounded bitmap plan. Unfiltered counts keep their index-only scan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(tables): tenant-bound Cmd+F search and stream its window (75s -> 2s)
Same planner trap as the filtered count, compounded: the lateral
jsonb_each_text ILIKE is unestimatable, so findRowMatches on a 1M-row
table seq-scanned the whole 12M-row shared relation and disk-sorted
~120MB of window input (75s measured). SET LOCAL enable_seqscan=off
bounds the scan to the tenant; on the default order, additionally
penalizing bitmap/sort/parallel steers the planner onto the already-
sorted (table_id, order_key, id) index walk so row_number() streams
with no sort at all (2s measured). Flags only penalize plan shapes —
a custom sort still sorts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(tables): tenant-bound sorted pages and filtered write selections
Extends the seqscan fix to every remaining jsonb-predicate path, all
measured on a 1M-row table in a 12M-row shared relation:
- sorted page query (ORDER BY data->>'col'): 9.7s/page -> 0.76s, and deep
pages stop spilling ~130MB sorts to disk
- updateRowsByFilter / deleteRowsByFilter row selection: 14.4s -> bounded
- delete-job worker selectRowIdPage with a filter: 12.6s/page -> bounded
- dispatcher filtered-scope window walk: same shape, same fix
Shared withSeqscanOff helper moves to lib/table/planner.ts (service +
dispatcher both consume it; dispatcher can't import service).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(tables): tenant-scoped containment index (migration 0232)
The plain GIN on user_table_rows.data matched @> candidates across every
tenant sharing the relation — a hot value in someone else's table
inflated everyone's equality filters (1.07M candidates fetched for a
33k-row match, lossy bitmap, 1.1s). Replace it with btree_gin
(table_id, data jsonb_path_ops): the tenant intersection happens inside
the index and paths are single hashed entries. Rare-equality probe
326ms -> 17ms with zero wasted candidates; unique-constraint checks and
upsert conflict lookups ride the same index. The new index is smaller
than the one it replaces (529MB vs 694MB on the 12M-row dev relation).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(tables): tenant-bound unique-constraint checks (3.5s -> <1s per write)
The unique check runs lower(data->>'col') = $1 LIMIT 1 on every insert
and cell edit touching a unique column. The predicate is unestimatable
and a unique (non-conflicting) value never exits early, so the planner
seq-scanned all 12.3M shared-relation rows per check — 3.5s measured.
Tenant-bound both the single and batch variants; the batch path sets the
flag on the caller's transaction when one is supplied (SET LOCAL dies at
its commit, and the statements that follow are tenant-scoped writes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(tables): tenant-bound upsert conflict lookup
Same unestimatable data->>key predicate as the unique checks; an
insert-path upsert has no existing match so the lookup can't exit early
and seq-scans the whole shared relation. The upsert already runs in a
transaction — set the planner flag on it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(tables): consolidate executor types onto planner exports
service.ts kept a private DbTransaction alias and two inline
typeof db | DbTransaction unions after planner.ts began exporting the
canonical DbTransaction/DbExecutor — import those instead. From the
/simplify review of the perf series; no behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tests): drop narrow schema mock override in process-contents test
The local vi.mock('@sim/db/schema') stubbed only document/knowledgeBase,
but the file's import graph reaches lib/table/service whose module scope
now references tableJobs. The global schema mock already covers all of
it — rely on it per the testing rules instead of re-mocking.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): scope cancels and counts to the filtered selection (review)
Addresses the open Bugbot/Greptile findings on filtered select-all:
- Filtered runs no longer cancel the whole table: cancelWorkflowGroupRuns
takes a filter — it stops only dispatches with that exact filter scope
and only in-flight cells on matching rows (semi-join); whole-table and
differently-scoped dispatches keep running, their cancelled cells
skipped via cancelledAt > requestedAt.
- Stop on a filtered select-all sends the filter through cancel-runs
(contract + route + mutation) instead of a table-wide cancel.
- runColumnBodySchema rejects rowIds + filter together (mirrors
deleteTableRowsBodySchema).
- Select-all delete clears the selection in onSuccess, not at click, so
a failed kickoff restores both rows and selection.
- Clipboard copy/cut estimates use the filter-aware total (rowTotal)
instead of the whole-table rowCount.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: retrigger CI (Actions dropped the previous push events)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: bump api-validation route baseline to 807 (staging route + merge)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): release job claim when trigger.dev dispatch fails
If tasks.trigger (or its dynamic imports) throws after
markTableJobRunning, the ghost running row held the table's
one-write-job slot until the stale-job janitor fired (~15-20 min of
409s). All four kickoff routes now release the claim and rethrow; the
backfill runner releases and warns (a failed backfill never fails the
schema change). Greptile P1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(db): squash 0232 into 0231 (one migration for the PR)
Both are branch-only — no environment has applied them through the
migration ledger yet — so the tenant-scoped GIN (btree_gin extension,
index swap) folds into 0231_table_jobs_and_keyset. Snapshot chain
re-pointed; drizzle-kit generate confirms zero drift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): context-menu delete label shows the true select-all count
Under select-all the context menu counted only the loaded page ("Delete
1000 rows" on a 999k-row table) while the action correctly deletes every
matching row via the background job. Delete now gets its own count from
the filter-aware total minus deselections; the run-action labels keep the
loaded-row count since those actions act on loaded rows only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): context-menu bulk actions act on the full select-all scope
Follow-up to the label fix: under select-all the context menu's Run /
Re-run / Stop only acted on the loaded page of rows. They now route
through the same scopes as the action bar — runs dispatch by filter
(whole table when unfiltered), Stop uses the filter-scoped cancel — and
all labels share one true count (filter-aware total minus deselections,
locale-formatted). Like the action bar, filter-scoped runs ignore
deselections (the run API has no exclusion set).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(tables): exclusion set for select-all runs and stops
Select-all minus deselected rows now means exactly that for every bulk
action, not just delete. runColumnBodySchema and cancelTableRunsBodySchema
accept excludeRowIds (bounded by MAX_EXCLUDE_ROW_IDS, select-all scope
only); the dispatch scope persists it and the dispatcher window walk,
eager bulk-clear, pre-run cancel, and filter/table-scoped cancel all skip
excluded rows. Client threads exclusions from the selection through the
action bar and the grid context menu, including the optimistic stamps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): spare excluded-row dispatches on Stop; no orphan placeholder table
Two Bugbot findings on the exclusion work:
- Select-all-minus-deselections Stop (no filter) cancelled every active
dispatch table-wide, killing row-scoped runs on deselected rows.
markActiveDispatchesCancelled now spares dispatches whose scope.rowIds
are fully contained in the exclusion set (coalesce(false) keeps
table-wide dispatches cancellable).
- Create-mode import: a failed trigger.dev dispatch released the job
claim but left the just-created placeholder table in the workspace.
Archive it on the failure path (no hard-delete surface exists).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): row counts reflect a running delete everywhere
A mid-delete refresh resurrected the old counts: the optimistic update
stripped cached rows but left page-0 totalCount (footer / select-all
label) at the old total, and list/detail counts reported raw row_count
including doomed-but-not-yet-deleted rows.
- onMutate now sets the active view's totalCount to the kept rows and
decrements the cached detail rowCount by the doomed estimate
- the kickoff persists that estimate on the job (payload.doomedCount,
clamped server-side); getTableById/listTables subtract the
not-yet-deleted remainder (doomedCount - rows_processed) while the
delete runs, so refetched counts match the read path's delete mask
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* copy(tables): drop background mention from delete confirm
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): clear select-all immediately when a delete kicks off
The header checkbox lingered as a minus over the optimistically-emptied
grid: rowSelectionCoversAll treats zero rows as not-covered, and the
selection clear waited for the kickoff's onSuccess. Clear at click
(failed kickoffs visibly restore rows + toast; re-selecting is cheap)
and render an empty grid's header checkbox unchecked regardless — a
selection over zero rows is vacuous.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): export takes the async path while a delete job runs
The sync/async export choice reads rowCount, which is a doomed-estimate-
adjusted number during a running delete (and the estimate is client-
supplied) — an overstated estimate could route a still-large masked set
through the synchronous stream. Mid-delete exports now always run as a
job: safe at any size, and exports bypass the one-job-per-table gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(build): stop uploads setup from sweeping the project into route graphs
next build (Turbopack) failed with "Two or more assets with different
content were emitted to the same output path" on the server-root chunk.
Root cause: setup.server.ts's unscoped path.resolve(process.cwd()) made
node-file-tracing sweep the entire project — next.config.ts included —
into every route graph reaching lib/uploads (the files/upload route and,
since the export job, the export-async path). Two producers emitted the
swept config into same-named chunks; staging's latest commits made their
contents diverge and the names collided. Annotate the path derivation
with turbopackIgnore per the NFT warning's own remediation — the build
passes and all ~390 "unexpected file in NFT list" warnings disappear.
Also inline the releaseJobClaim dynamic imports in the kickoff routes to
plain static imports — service is already statically imported there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(files): support Safari < 17.4 in PDF preview
pdf.js 5.x calls Promise.withResolvers (Safari >= 17.4) and URL.parse
(Safari >= 18) at module-evaluation time, so on older engines importing
react-pdf threw an uncaught TypeError that unwound to the workspace
error boundary — every PDF preview (chat and Files tab) rendered as
"Something went wrong" for those users.
- Polyfill both APIs in a side-effect module imported before react-pdf
- Serve the legacy pdf.js worker build, which self-polyfills (the worker
context is unreachable from main-thread polyfills)
- Wrap the PDF preview in an error boundary so a viewer crash degrades
to the standard preview fallback instead of replacing the workspace
* fix(files): reset preview error boundary on content change, log component stack
Key the boundary (not the child) by file id + data version so a tripped
boundary remounts and retries when the preview content updates, and
include React's componentStack in crash logs.
* fix(files): apply preview mode once deep-linked file record loads
On a hard load of /files/<id> the preview-mode initializer ran before the
files list arrived, fell back to the code editor, and the route-change
effect never corrected it (the route id never changed). Previewable
files (html, markdown, csv, svg, mermaid) opened as source instead of
the rendered preview. Defer recording the applied route target until the
file record exists so the mode is applied as soon as the list loads,
without clobbering manual mode toggles afterwards.
* improvement(files): derive routed-file presence from existing selectedFile memo
Local review follow-ups: reuse the memoized selectedFile/selectedFileRef
instead of a second O(n) scan per render, and collapse the applied-mode
ref to string | null — initializing at null (the list view) preserves
the pre-existing fresh-mount behavior while still deferring deep-link
mode application until the file record loads.
* fix(files): settle preview mode when a deep-linked file id is missing
Gate the mode effect on the files query having resolved (record found or
initial load finished) rather than on the record existing, so an invalid
or deleted deep-link id decides 'editor' once instead of deferring
indefinitely.
* fix(providers): correct pricing, deprecations, and capabilities across model catalog
* fix(providers): apply full re-validation pass across model catalog with per-provider justification docs
* chore(providers): keep model validation logs local, not in the repo
* fix(providers): default azure-openai to gpt-5.4 instead of deprecated gpt-4o
* feat(integrations): add Daytona integration with sandbox lifecycle, code execution, and file tools
* fix(daytona): address review feedback and harden edge cases
- Pre-check file size via userFile.size before downloading from storage in the upload route
- Tolerate empty response bodies in delete/start/stop sandbox tools
- Preserve explicit timeout 0 for run_code and execute_command
- Default missing exitCode to -1 so unknown state is distinguishable from success
- Reject blank sandbox IDs, clamp list limit to 1-200, trim destination path
- Clarify that toolbox operations require the sandbox ID (not name)
- Bump contract route baseline to 812 for the new daytona upload route
* fix(daytona): cap download size at 100MB and preserve sandbox identity on empty lifecycle responses
* improvement(notion): black icon on white background to match brand
* fix(daytona): reject oversized base64 uploads before decoding
* improvement(landing): move integration last-updated below CTAs and de-emphasize it
* fix(daytona): forward explicit zero cpu/memory/disk values in create sandbox
* improvement(integrations): overhaul landing FAQs for SEO/GEO and fix dynamic OG images
* improvement(integrations): trim comments and fold catalog updatedAt into integrations.json
* fix(integrations): correct FAQ copy for zero-capability and single-tool integrations
* feat(integrations): add Convex integration with function execution and data export tools
* fix(convex): separate List Documents page cursor from deltas cursor and surface HTTP errors in transforms
* fix(convex): rename List Documents pagination cursor to pageCursor end-to-end for unambiguous chaining
* fix(convex): validate deployment URL with shared SSRF guard
* improvement(convex): polish from final validation pass — reject query strings in deployment URL, validate object args, fix sync skill wording
* docs(convex): note streaming export plan requirement on data-export tools (verified via live E2E)
* feat(integrations): add Brex integration with expenses, receipts, transactions, team, budgets, and payments tools
* fix(brex): reject whitespace-only expense IDs in receipt upload instead of silently falling back to receipt match
* fix(brex): trim receipt name in contract so whitespace-only overrides are rejected
* fix(brex): align spend limit balance shape, enum descriptions, and pagination metadata with Brex API specs
* improvement(brex): validate pre-signed upload URL with DNS pinning and harden API key input
* fix(brex): correct shared limit placeholder to reflect the 100-item cap on list expenses
* fix(brex): normalize timezone-suffixed timestamps for transactions date filters (Brex rejects offsets)
* improvement(sockets): make offline mode recoverable and stop transient races tripping it
* data persistence issues should trigger offline mode and force refresh
* code cleanup
* fix(deps): dedupe radix focus-scope/dismissable-layer so in-modal dropdowns open
@radix-ui/react-dropdown-menu was the only Radix package pinned exactly
(2.1.16), so the v0.7.0 lockfile refresh left its react-menu on
focus-scope@1.1.7 + dismissable-layer@1.1.11 while react-dialog@1.1.16
moved to focus-scope@1.1.9 + dismissable-layer@1.1.12. These packages
coordinate modal/popper interplay through module-scoped singletons
(focusScopesStack, layersWithOutsidePointerEventsDisabled); with two
copies in the bundle, a dialog's focus trap never pauses for a menu
portaled outside it and yanks focus back as the menu opens, so the
menu's dismiss layer unmounts it in the same tick. Symptom: ChipDropdown
menus inside ChipModal (e.g. the credential Add People role dropdown)
never appear in fresh-install/production builds while stale local
installs with a single shared copy keep working.
Bump dropdown-menu to 2.1.17, whose react-menu pins the same internals
batch as dialog 1.1.16 — every coordination package (dismissable-layer,
focus-scope, focus-guards, portal, popper, presence) now resolves to a
single version tree-wide.
* fix(deps): use caret range for dropdown-menu so radix internals track the same batch
Address review: the exact pin was the original divergence mechanism, and
the rest of the radix popper/dialog family floats on caret ranges that
jump to the same internals batch together on lockfile refreshes.