Commit Graph
196 Commits
Author SHA1 Message Date
Theodore Li ea505f0388 improvement(tables): versioned CSV snapshot cache for table mounts + parallel multipart uploader (#5108)
* improvement(tables): versioned CSV snapshot cache for table mounts + parallel multipart uploader

* chore(db): drop colliding 0239 migration (renumber pending)

* chore(db): renumber rows_version migration to 0240 (off staging's 0239)

* improvement(tables): mount snapshots by presigned URL so the sandbox fetches directly (raise cap to 500MB)

* fix(tables): allow url sandbox entries in the function-execute contract; key snapshot by column shape so schema edits invalidate it

* chore(e2b): log sandbox inputs split by url-fetch vs inline write

* improvement(tables): order export + snapshot rows by order_key so the CSV matches the grid under fractional ordering
2026-06-17 15:26:05 -04:00
Waleed a82b44d36d perf(db): logs-list index, drop redundant indexes, replica routing, hot-path write cleanups (#5105)
* perf(db): logs-list index, drop redundant indexes, replica routing, hot-path write cleanups

* fix(logs): keep /api/v1/logs on primary db — its permissions join is the auth gate, not replica-safe
2026-06-16 19:10:25 -07:00
Vikhyath Mondreti 7b4626e547 improvement(perm-groups): allow workspace filter for permission groups (#5070)
* improvement(perm-groups): allow workspace filter for permission groups

* show errors correctly

* address comments

* address concurrent edit concern

* address locks

* address comments"

* index migration safety

* address at route level
2026-06-15 20:52:01 -07:00
Waleed 4ec26a0404 feat(scheduled-tasks): minute-granular calendar + user timezone preference (#5038)
* feat(scheduled-tasks): position week/day chips at their exact minute

Replace hour-bucketed event rendering with a per-day absolute overlay that
places each task chip at timeToOffset(start), so a 5:38 task sits at 5:38
instead of the top of the 5:00 cell. Hour cells become click/gridline-only;
the overlay is non-interactive so empty-space clicks still create. Removes the
now-obsolete eventsByHour/hourKey/bucketEventsByHour path (month view already
used eventsByDay).

* feat(settings): user timezone preference for scheduled tasks

Add a Timezone preference under Settings → General. Scheduled tasks now run
in the user's chosen IANA zone instead of whatever device created them.

- settings table gains a nullable `timezone` column (migration 0236); null
  means "use the browser-detected zone", so existing users are unchanged
- contract: validated IANA `timezone` on the settings get/update shapes
- useTimezone() resolves the saved zone or the browser fallback; the task
  modal captures it instead of recomputing the device zone
- General settings adds a searchable timezone combobox defaulting to the
  detected zone
- shared timezone util (getBrowserTimezone / getSupportedTimezones)

* fix(scheduled-tasks): interpret launch/end times in the account timezone

Address review: one-time runs and the end-of-day boundary were resolved in the
browser zone, so a task could fire at the wrong instant when the account zone
differed from the device. Resolve wall-clock launch/end through the account
zone (DST-correct), and evaluate the past-launch guard and the default seed in
that zone too — matching how the recurring cron is already evaluated.

- timezone util: zonedWallClockToUtc (DST-correct, no library), wallClockNow;
  getSupportedTimezones falls back to a common set and always includes UTC
- recurrenceToScheduleFields takes the timezone and resolves time/endsAt in it
- settings timezone Label drops its dangling htmlFor
- tests for the zone converter (UTC / +5:30 / DST) and the zoned mappings

* feat(scheduled-tasks): Google-Calendar-style side-by-side overlap layout

Tasks whose pills would collide now split the column into side-by-side lanes
(like Google Calendar) instead of stacking on top of each other; tasks that
don't overlap keep the full width. Adds a pure layoutColumn lane-assignment
helper (interval clustering + greedy lane packing) with tests.

* fix(scheduled-tasks): zone-consistent recurrence/edit + duplicate + loading

Address review (zone consistency):
- recurrenceToCron derives weekday/day-of-month from a UTC-parsed calendar date
  so the cron targets the right day regardless of device zone
- cronToRecurrence + editSeedFor recover the launch date/time and ends-on date
  read back in the schedule's zone (zonedWallClock), so editing shows the right
  values when the account zone differs from the device
- defaultLaunch no longer compares browser-local slot days against account-zone
  "today"

Features:
- right-click Duplicate: opens a pre-filled create modal from any task
  (TaskEditSeed now extends a shared TaskPrefill; modal gains a prefill prop)
- loading.tsx paints only the header chrome (the page is a calendar, not a
  table) so it no longer pops table -> calendar; the empty calendar loads tasks in
- task context menu drops "See details" (finished tasks open on click)

* fix(scheduled-tasks): edit/duplicate use the task's own timezone, not the account one

A task created in one zone but edited after the account zone changed (or
duplicated) seeded its launch in the task's stored zone while validating and
submitting in the current account zone, drifting unchanged run times. TaskPrefill
now carries the task's timezone; the modal seeds AND submits in it for
edit/duplicate, and only blank creates use the account zone.

* fix(scheduled-tasks): duplicating a past one-time task seeds a future launch

Audit follow-up: a duplicate of a one-time task whose launch already passed
opened with Schedule disabled. It now falls through to the next-hour default so
the new task is immediately schedulable. Also clarifies the DST spring-forward
note on zonedWallClockToUtc and drops a stray test comment.

* fix(scheduled-tasks): clear duplicate pre-fill when starting a fresh create

The create modal had two open-sources (isCreateOpen + duplicatePrefill) that
weren't coordinated. Starting a header/slot create now clears any duplicate
pre-fill (and duplicating closes any open create), so a stale duplicate draft
can never bleed into a blank or slot-seeded create.

* fix(scheduled-tasks): make create/duplicate/edit modals mutually exclusive

Opening any of the three modal flows (blank create, duplicate pre-fill, task
edit/record) now closes the other two, so the create modal can never co-exist
with the edit modal and no stale state survives a switch.

* feat(scheduled-tasks): render calendar in the effective timezone

Position each occurrence at its wall-clock time in the task's own
timezone, and draw the now-line / today highlight in the viewer's
effective zone, so the calendar always shows a task at the local time it
was scheduled for — matching the modal. Adds zonedClockDate as the single
zone boundary; the default case (account zone == browser zone) is
unchanged.

* fix(scheduled-tasks): re-sync calendar day frame when timezone resolves

When useTimezone() resolves from the browser fallback to the saved
account zone after mount, re-derive today (and the focused day, while it
is still on today) so the grid frame, now-line, and fetched range stay in
agreement. The focused day is preserved across the change once the user
has navigated away.

* fix(scheduled-tasks): pad view window for timezone slop; re-center scroll on zone change

visibleRange now expands the rendered span by a day on each side so an
occurrence whose own-zone display day is on screen is never filtered out
by the account-zone frame; bucketEventsByDay still places each on its
zoned day, dropping any off-screen. The week/day auto-scroll re-centers
when the effective timezone resolves.

* test(scheduled-tasks): pass timezone to cronToRecurrence ends-on case

The second cronToRecurrence call omitted the required timezone, so the
recovered end date depended on the test runner's system zone instead of
the schedule zone. Pin it to UTC for determinism.

* fix(scheduled-tasks): re-seed blank-create launch when timezone resolves

useTimezone() starts on the browser fallback, so a blank create's
next-top-of-the-hour default (and its past-launch guard) could be seeded
in the wrong zone and submitted in the resolved account zone. Re-seed the
default when the effective zone changes, unless the user has edited the
fields; slot/edit/duplicate seeds are zone-stable and untouched.

* fix(scheduled-tasks): DST fall-back resolve, today month-cell default, late-night pill bounds

Audit follow-ups:
- zonedWallClockToUtc resolves to the self-consistent instant, fixing
  one-time launches on the autumn fall-back day (were an hour early) while
  keeping the spring-forward gap rolling forward; adds DST regression tests.
- defaultLaunch: today's whole-day (month-cell) click defaults to the next
  top of the hour like the header action, not a past 9am that disables Save.
- DayEvents clips to the day bounds so a late-night pill never spills past
  the final hour row; now-line sits above event pills.
2026-06-13 20:49:18 -07:00
Vikhyath Mondreti 3a796f083d improvement(permissions): permission groups scoped to organization level (#5035)
* improvement(permissions): permission groups scoped to organization level

* chore(db): drop 0235 permission-groups migration to regenerate after staging merge

* merge latest staging
2026-06-13 17:56:10 -07:00
Emir Karabegandwaleed bcedadf1b1 feat(scheduled-tasks): calendar views + persisted, runnable tasks (#4979)
* 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>
2026-06-13 16:16:40 -07:00
51733b8d85 fix(db): correct misleading error message when DATABASE_REPLICA_URL is malformed (#5023)
* 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>
2026-06-13 09:18:51 -07:00
Theodore Li 2c75a4ac04 fix(tables): per-batch delete-job commits, real trigger.dev retries, post-index ANALYZE guard (#4997)
* fix(tables): per-batch delete-job commits, real trigger.dev retries, post-index ANALYZE guard

* fix(tables): resume job progress across retries, rethrow root cause for clean failure messages
2026-06-12 13:18:15 -04:00
Theodore LiandClaude Opus 4.8 53fdcab5d6 feat(tables): background jobs (delete/export/backfill on trigger.dev) + tenant-scoped query performance (#4915)
* 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>
2026-06-12 03:03:11 -04:00
Vikhyath Mondreti f7b40fe4a4 fix(db-part-1): eliminate pool self-deadlock from nested checkouts inside transactions (#4975)
* fix(db-part-1): eliminate pool self-deadlock from nested checkouts inside transactions

* update docs
2026-06-11 17:29:13 -07:00
Waleed 9efb7b45f4 feat(byok): support multiple keys per provider with round-robin rotation (#4963)
* feat(byok): support multiple keys per provider with round-robin rotation

* fix(byok): address review feedback — serialize cap check, defer encryption, explicit delete guard

* fix(byok): drop ON CONFLICT on removed unique index in legacy key migration script

* improvement(byok): guard double-submit, Enter-to-save on key field, cap hint in manage modal
2026-06-11 10:00:58 -07:00
Waleed 284edf0b85 improvement(db): opt-in read-replica client + migration runner hardening (#4955)
* improvement(db): add opt-in read-replica client and harden migration runner

* fix(db): detect wrapped lock-timeout errors, jittered retries, direct migration DSN support

* fix(db): audit fixes — unparameterized SET, primary reads for authz scoping, shared error helper

* fix(db): pin migration session — disable max_lifetime recycling, guard backend pid across retries

* fix(db): gate pid session guard to direct migration connections (pooler pids legitimately vary)

* improvement(billing): route display-only usage aggregations to the read replica via executor threading

* chore(db): drop call-site justification comments

* chore(db): tighten doc comments

* ci(migrations): map optional direct-connection DSN secrets

* fix(data-drains): add stability window to time cursors so late-visible rows are never skipped

* fix(billing): resolve pagination cursor on primary; replica for member ledger display

* fix(billing): usage-limits returns cost and limit from one computation
2026-06-10 18:33:58 -07:00
Vikhyath MondretiandCursor 6abcf82db2 feat(workflows): sim trigger, logs v2 block, toolbar renaming (#4941)
* feat(workflows): sim trigger, logs v2 block, toolbar renaming

* fix(review): bound rule queries, canonical logs params, watched-workflow SQL scoping

Code-review fixes: read the canonical workflowIds param in logs_v2 (the
serializer deletes the source pair ids), aggregate failure-rate in the DB
and switch rule windows to the indexed startedAt column, clamp rule config
to the legacy contract bounds, push no_activity watch scoping into SQL
before the LIMIT, fix the generated sim icon-map key, normalize docs
wording, and drop dead exports.

Co-authored-by: Cursor <cursoragent@cursor.com>

* address comments

* fix(review): integer rule rounding, success-gated workflow labels, display module hygiene

Second-pass review fixes: round integer rule fields so fractional input
never reaches SQL LIMIT, gate workflow-name readiness on a successful
non-placeholder load in both editor and preview (errored loads mislabeled
valid workflows as deleted), lazily read the variables store in preview
rows, move the filter-field JSON preview into the shared display module
and unexport its single-consumer helpers, and align >= boundary copy
(failure rate, error count, cooldown window) with implementation.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: sync lockfile after staging merge

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(workspace-events): keyset-paginate the no_activity subscription scan

A fixed LIMIT 500 with no ORDER BY silently starved subscriptions beyond
the cap once the global count exceeded it. The poll now pages by webhook
id so every subscription is visited each cycle; pagination bounds memory,
not total work.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(workspace-events): keyset-paginate the watched-workflow scan

The 500-row LIMIT silently and deterministically excluded high-id
workflows from no_activity coverage in watch-everything subscriptions on
large workspaces. The scan now pages by workflow id, mirroring the
subscription scan; per-workflow checks move into a helper so the
pagination loop stays flat.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(workspace-events): skip no_activity subscriptions on the execution-completion path

no_activity is poller-owned and can never fire from a completed execution,
but it passed into the rule branch and cost a pointless cooldown point-read
per subscription on the hottest path. Early-continue alongside the
workflow_deployed guard.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(sim-trigger): note failure-based alert conditions evaluate on failed runs

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(blocks): recategorize Data Enrichment as a core block

It's a Sim-native capability (registry enrichments over a managed provider
cascade, like Search), not a third-party integration. Moves it to Core
Blocks in the toolbar, out of the integrations catalog, and relocates its
docs page to blocks/ with the icon-map allowlist keeping the docs card icon.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(blocks): recategorize MySQL, PostgreSQL, SFTP, SMTP, SSH as integrations

External-system connectors with host/credential auth belong under
Integrations, not Core Blocks — consistent with MongoDB, Redis,
ClickHouse, and the other datastore integrations. They already carried
integrationType and /tools docsLinks; the regenerated docs pages turn
those previously-dangling links into real pages, and the blocks join the
integrations catalog and icon maps.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 11:04:12 -07:00
Waleed b2a485e164 fix(db): serialize concurrent migrations with a Postgres advisory lock (#4939)
* fix(db): serialize concurrent migrations with a Postgres advisory lock

Deployments start N app replicas at once, each with a migration sidecar.
drizzle migrate() has no cross-process lock, so all N read
__drizzle_migrations, all see the same migration pending, and all apply it
concurrently — one wins, the losers run the same DDL against already-mutated
state and exit 1 (e.g. DROP TABLE "form" -> table does not exist /
TaskFailedToStart). Wrap migrate() in a session-level pg_advisory_lock so
runners serialize: the winner migrates, the losers block, then re-read and
find nothing pending. Session locks auto-release on disconnect, so a crashed
runner never wedges the lock.

* fix(db): guard pg_advisory_unlock so it cannot mask a successful migration

If the explicit unlock throws (e.g. connection drops in the window after
migrate() commits), the exception bubbled to the outer catch and exited 1 —
falsely reporting a failed migration to the deploy orchestrator. The session
lock auto-releases on disconnect anyway, so swallow and log instead.

* refactor(db): move unlock-guard rationale to TSDoc helper
2026-06-09 21:54:27 -07:00
+1 39418f9615 improvement(mothership): v0.2 (#4923)
* CONTRACTS

* updates

* Prompt caching

* Fix regression

* updates to prompt caching

* Prompt caching trace

* VFS updates

* Changelog and plan

* VFS update

* Improvement/mothership (#4775)

* ff

* System role in cache

* Fixes

* Add dynamic user info

* Improve tool search

* improvement(platform): workspace UI/UX overhaul + integrations catalog

Rework the workspace around the AI-workspace model: a Mothership home, a
top-level Skills route, connected-credential and integration-detail pages,
and a polished sidebar/settings surface. Replace the notifications store
with a unified toast system (provider-level dismiss/pause, countdown ring).

Integrations & catalog:
- Add a BlockMeta layer (tags + catalog templates) scoped to catalog-visible
  integrations; every catalog integration carries >=7 grounded templates.
- Rework the taxonomy: each block declares category tools|blocks|triggers.
  3rd-party services are 'tools'; first-party primitives (postgres, mysql,
  knowledge, file, search, stt/tts, image/video generators, thinking, etc.)
  are 'blocks'. Versioned blocks follow the upgrade paradigm (old hidden,
  latest in toolbar/docs).
- Generate integrations.json + tool docs canonically from block configs.

Architecture & cleanup:
- Consolidate block data extraction behind a single latest-version strategy
  (getCanonicalBlocksByCategory; version-consistent getBlockMeta).
- Unify version-suffix handling in @sim/utils/string (stripVersionSuffix /
  isVersionedType, with tests); registry, generate-docs, tools/utils, and
  integrations all route through it.
- Repair latent broken barrels, remove dead code, fix BlockMeta-related type
  errors and 5 broken docs links.

Behavior-preserving for block execution and the toolbar's tool/block listing.

* refactor(platform): remove forms, templates, and creators features

Remove three standalone features and their supporting code:
- Forms: form-deployment pages, API routes, execution path, and docs.
- Templates: the template gallery (landing + workspace) and template APIs.
- Creators: creator-profile routes and contracts.

Add a super-user permissions module (lib/permissions/super-user) and an
organizations API contract; update the audit/db/testing packages, billing,
and the session/theme providers accordingly.

* New doc setup

* Fixes

* Fixes

* Logs tools

* test(workflows): update archiveWorkflow update count after forms removal

The forms feature was removed, dropping the form-table update from
archiveWorkflow. Update the stale assertion from 8 to 7 tx.update calls.

* Add finalization on error

* Fixes

* change dev CI to bun run db:push

* Updates

Please enter the commit message for your changes. Lines starting

* Contrat update

* improvement(nested): subagents

* updates

* upgrade

* improvement(knowledge): polish tag filter dropdowns (#4816)

* improvement(logs): object storage backed tracespans (#4787)

* improvement(logs): obj storage backed tracespans

* fix storage write context

* fix tests

* address comments

* address comments

* chore(db): remove migration 0219 to regenerate after staging merge

Drops the 0219_robust_shard SQL, its snapshot, and the journal entry so the
trace-spans/cost schema migration can be regenerated on top of the latest
staging migration chain (avoids a number collision with staging's migrations).

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(billing): accurate per-member usage via shared ledger helper

Per-member/per-user usage in the org-member routes now adds the usage_log
ledger to the currentPeriodCost baseline (which is no longer incremented),
via a shared getOrgMemberLedgerByUser helper to avoid repeating the
subscription→period→ledger lookup across the admin and member-facing routes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* regen migrations

* update migration

* address comments

* more code cleanup

* incorrect type cast

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(providers): harden OpenAI-compatible providers + add tests (#4796)

* improvement(providers): harden OpenAI-compatible providers + add tests

* fix(vllm): let tool-loop errors propagate instead of returning silent partial success

* fix(litellm): force tool_choice 'none' on final structured-output call

The deferred final call used tool_choice 'auto', so the model could emit
another tool_calls round instead of the structured answer, leaving content
stale. Use 'none' (matching vLLM/Fireworks) on both the streaming and
non-streaming final calls so the model must return the structured response.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(providers/ollama): drop tools from post-tool streaming call

Ollama ignores tool_choice (not in its supported fields), so vLLM/Fireworks'
tool_choice:'none' guard is a no-op here. Omit tools from the final streaming
payload instead so the summarization turn can't emit dropped tool calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(litellm): spread payload into deferred final call so reasoning_effort carries over

The non-streaming deferred finalPayload hand-picked fields and dropped
reasoning_effort (and any future payload field), diverging from the streaming
path which spreads ...payload. Spread payload here too for consistency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(providers/ollama): restore enrichment TSDoc block

Keeps parity with sibling Chat Completions providers (cerebras/mistral/xai).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(fireworks): restore TSDoc on utils helpers

Restore the TSDoc blocks on supportsNativeStructuredOutputs,
createReadableStreamFromOpenAIStream, and checkForForcedToolUsage —
TSDoc is the codebase documentation standard and should not have been
stripped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(litellm): remove inline rationale comments (codebase uses TSDoc)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(providers/ollama): drop orphaned enrichment TSDoc

The block documented a function that now lives in trace-enrichment.ts, so it
documents nothing in this file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* chore(copilot): deprecate mcp server (#4797)

* chore(copilot): deprecate mcp

* update error codes

* deprecate copilot api v1 route

* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza (#4777)

* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza

Add hosted-key support across all credit-consuming Findymail, Prospeo, and Wiza operations so Sim provides the key when a workspace has not brought its own. Register the three BYOK providers, consolidate Wiza's two-step reveal into a single polling wiza_individual_reveal op, and hide the API key field on hosted Sim for hosted operations.

* fix(integrations): harden Wiza reveal polling, soften enrichment getCost guards

Address Greptile + Cursor Bugbot review on #4777: return explicit failures from the Wiza individual_reveal poller instead of throwing (thrown errors were swallowed into a false queued success), short-circuit when the initial reveal is already terminal, tolerate transient 5xx/429 during polling, and return 0 (not throw) from Findymail getCost when the contacts/employees array is absent.

* chore(integrations): biome formatting after wiza merge resolution

* fix(wiza): type isTerminalReveal param structurally for next build typecheck

* feat(enrichments): add Findymail, Prospeo, Wiza to work-email waterfall

* feat(enrichments): add Wiza + Prospeo phone reveal to phone-number waterfall

* feat(enrichments): opportunistic identifiers + LinkedIn URL input across work-email & phone cascades

* fix(tables): reduce column header chevron size and fix sidebar shadow bleed (#4800)

* feat(slack): add install + privacy section to integration landing page (#4799)

* feat(slack): add install + privacy section to integration landing page

Adds a hand-authored, slug-keyed landing-content module (separate from the generated integrations.json so it survives regeneration) and renders an install walkthrough + privacy-policy link on integration pages when present. Also refreshes generated docs (data-enrichment entry, icon mappings, tool mdx).

* fix(landing): render privacy section independently, align CTA analytics label

* docs(landing): clarify the Slack install button is behind sign-in

* refactor(landing): bake integration landing content into generated json via docs-gen

Moves landing content (install walkthrough + privacy) out of a render-time augment and into the generation pipeline: generate-docs reads the pure-data content map and writes landingContent into integrations.json, so the page reads a single source (integration.landingContent). Canonical types live in integrations/data/types.ts.

* improvement(enrichments): align enrichments sidebar with design system (#4801)

* improvement(enrichments): align enrichments sidebar with design system

* fix(enrichments): consistent close button pattern and fix url link hover

* fix(misc): upgrade path change for new better-auth version, billing issue for workflow block agent usage (#4803)

* fix(misc): upgrade path change for new better-auth version, double-billing for workflow block agent usage

* fail loudly if stripe sub id missing

* fix(copilot): seq migration (#4804)

* chore(db): drop redundant idx_webhook_on_workflow_id_block_id index (#4809)

Removed because (workflow_id, block_id) is a left-prefix of idx_webhook_on_workflow_id_block_id_updated_at_desc, which fully covers it. The dropped index was non-unique and enforced no constraint.

* perf(copilot): read chat transcripts from copilot_messages (R+1 cutover) (#4808)

* perf(copilot): read chat transcripts from copilot_messages, not JSONB

Flip user-facing chat reads from the legacy copilot_chats.messages JSONB
array (5.7GB, 99% TOAST) to the normalized copilot_messages table via a
new loadCopilotChatMessages helper ordered by seq NULLS LAST, created_at,
id — the verified canonical order. Both chat-detail getters
(getAccessibleCopilotChat, getAccessibleCopilotChatWithMessages) now drop
the messages column from their metadata select (no more whole-array
detoast on every load) and assemble the transcript from the table after
authorization. This cascades to the copilot + mothership GET endpoints
and to resolveOrCreateChat's conversationHistory (the LLM payload).

The normalize/effective-transcript pipeline is source-agnostic
(copilot_messages.content == a JSONB array element), so transcripts are
byte-identical. Dual-write and the JSONB column stay in place as the
internal-logic source and fallback; removing JSONB writes is a later step.

Prod integrity verified before cutover: 0 messages missing, 0 NULL-seq,
0 dup keys/seq, 0 orphans, order-parity vs JSONB = 0 mismatches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(copilot): cover auth-deny on a found row skips the messages query

Address PR review: exercise the `if (!authorized) return null` contract —
when the chat row exists but authorization fails, the getter returns null
and never issues the copilot_messages read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(tables): right-align run/stop in embedded toolbar; workflow cells format like normal cells (#4806)

* fix(tables): right-align run/stop in the embedded table toolbar

Add a right-aligned `trailing` slot to ResourceOptionsBar and move the embedded
mothership table's run/stop control into it, so Filter + Sort stay left-aligned
and run/stop sits opposite on the right. No-op for the search-bearing consumers
(logs, resource list), which don't pass `trailing`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): workflow-output cells format values like normal cells

Workflow-output columns short-circuited in resolveCellRender and rendered their
value as plain text, so a sim-resource URL / external URL / JSON / date produced
by a workflow never got the chip, favicon link, or typed formatting a normal
cell gets. Factor value formatting into a shared `resolveValueKind` helper used
by both the workflow-value branch and the plain-cell branch; the workflow branch
keeps the typewriter reveal for plain streaming text via a `typewriter` flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): detect resource/URL links on workflow output regardless of column type

Workflow output columns default to `json` (columnTypeForLeaf), so routing their
values through the type-based formatter (a) gated chip/URL promotion behind
`column.type === 'string'` — a URL produced by a json-typed output never became
a chip — and (b) JSON.stringify'd plain string values, adding quotes and losing
the typewriter reveal. Detect links (sim-resource chip / favicon URL) on the
value string directly for workflow outputs, falling back to the plain `value`
kind; plain cells keep the type-based formatting. Addresses Greptile P2 on #4806.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(icons): repair broken integration icon rendering (#4810)

* fix(icons): repair broken integration icon rendering

Two distinct bugs left integration icons broken on the /integrations page
(visible at 32-40px, hidden at the toolbar's 16px):

1. Corrupted SVG paths (Notion, Greptile, Granola, Calendly, Grafana, Bedrock):
   over-minified data dropped elliptical-arc flag digits (e.g. `A1 1 0 5.9 7`
   instead of `A1 1 0 0 0 5.9 7`); Granola's cubic stream was truncated. Browsers
   abort path parsing at the first invalid arc flag, so each rendered as a fragment
   or blank. Replaced with correct path data from canonical sources, preserving each
   icon's existing fill/gradient and bgColor.

2. Invisible glyph (Bright Data): its icon uses fill='currentColor' but bgColor was
   '#FFFFFF', and every surface forces text-white on the glyph - white-on-white.
   Changed bgColor to Bright Data's brand blue (#3d7ffc) so the white glyph reads,
   matching the white-glyph-on-brand-chip convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): restore Calendly dual-tone brand colors

Addresses review feedback: the previous fix replaced the broken Calendly icon
with a monochrome #006BFF path, dropping the cyan #0ae8f0 accent from the
original dual-tone mark. Restored the two-tone logo (blue + cyan) using clean,
valid path data, cropped to a tight square viewBox so it fills the chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge icons, fix Zoom contrast and Quiver chip

- Zoom: glyph was blue-on-blue (#0B5CFF on #2D8CFF chip); switched to
  currentColor so it renders as a white glyph on the blue chip.
- Quiver: chip bgColor #000000 -> #FFFFFF to match the icon's near-white box,
  and enlarged the mark slightly (viewBox crop).
- Enlarged (tightened viewBox, verified no clipping): RevenueCat, Prospeo,
  Granola, Firecrawl, Enrich.so, and the AWS icons (RDS, DynamoDB, SQS,
  CloudFormation, Athena, CloudWatch, SES, Bedrock, S3).
- ZoomInfo left unchanged: it is a full red rounded-square logo that already
  fills its frame, so a crop would clip it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): use Bright Data wordmark on white chip; repair Circleback

- Bright Data: replaced the flame glyph with the official two-tone 'bright data'
  wordmark (provided asset), centered in a symmetric viewBox. Reverted the chip
  bgColor from #3d7ffc to #FFFFFF since the blue wordmark is invisible on a blue
  chip (the wordmark is designed for a light background).
- Circleback: a minifier had rounded the pattern's image scale to scale(0),
  collapsing the embedded logo to zero size (invisible). Restored the correct
  scale (1/280 = 0.00357142857) so the C. mark renders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(docs): sync Quiver block color card to white chip

Reflects the Quiver bgColor change (#000000 -> #FFFFFF) in the docs block info card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge AWS/Cloudflare/Dagster icons, fully white Zoom

- Enlarged (tighter viewBox, render-verified, no clipping): Cloudflare, Dagster,
  and the red AWS icons AWS IAM, Identity Center, Secrets Manager, SES, STS.
  Identity Center was anomalously small (filled ~32% of its frame); the group is
  now sized consistently (~80% fill).
- Zoom: the camera lens triangle was still #0B5CFF (blue-on-blue); switched it to
  currentColor so the whole camera renders white on the blue chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(wiza): consolidate individual reveal into a single operation

Merges the separate Start/Get Individual Reveal operations into one Individual
Reveal operation in the Wiza docs and integrations data (operationCount 5 -> 4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): size remaining AWS icons to match the set (~80% fill)

Bring RDS, DynamoDB, SQS, CloudFormation, Athena, CloudWatch and S3 up to the
same ~80% fill as the AWS IAM/Identity Center/Secrets Manager/SES/STS group, so
all AWS icons are visually consistent. Bedrock left as-is (already ~92% fill).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): use Bright Data flame mark, enlarge ZoomInfo

- Bright Data: the full 'bright data' wordmark was illegible at chip size.
  Replaced with just the flame-'i' brand mark (blue #4280f6 on the white chip),
  centered.
- ZoomInfo: cropped the viewBox toward the white 'Zi' so it's larger; the red
  rounded-square background still fills the chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge CrowdStrike icon

The falcon mark sat small in its chip because the icon used a wide 768x500
viewBox (letterboxed in the square chip). Switched to a square viewBox centered
on the mark so it fills ~80%, consistent with the other icons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(tables): serialize schema mutations to prevent parallel column clobber (#4812)

* Make workflow description nullable

* fix(tables): serialize schema mutations to prevent parallel column clobber

* fix(tables): load workflow outside schema lock; use DbOrTx for getTableById

* fix(tables): scale idle timeout in updateColumnType to avoid aborting large type changes

* fix(tables): skip stale remap types when workflowId changes concurrently

* fix(tables): scale idle timeout in updateColumnConstraints for large tables

* fix(wait): resume live/draft async waits and preserve cell context on chained waits (#4814)

* Make workflow description nullable

* fix(wait): resume live/draft async waits and preserve cell context on chained waits

* improvement(knowledge): polish tag filter dropdowns

* improvement(knowledge): soften filter section labels

* improvement(knowledge): soften list filter labels

* fix(security): harden SSO domain registration, webhook path isolation, and CSV export (#4813)

* fix(security): harden KB file access, SSO domain registration, webhook path isolation, env secrets, and CSV export

* fix(sso): scope domain conflict query with indexed lower(domain) filter

Address PR review: avoid a full-table scan on every SSO provider
registration by filtering candidate rows in SQL with
lower(domain) = <normalized>, keeping the in-memory ownership check.
Also tighten the normalizeSSODomain TSDoc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: condense env route security comments

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* icons update

* chore(security): tighten inline comments in CSV export and KB file authorization

Condense verbose comment blocks to concise TSDoc/single-line form; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): validate internal serve origin in KB file authorization

Replace the bypassable isInternalFileUrl substring check in resolveInternalKbKey
with an origin allow-list (base URL, internal API base URL, TRUSTED_ORIGINS).
A crafted external host whose path is /api/files/serve/<victim-key> no longer
resolves to the victim key. Relative same-origin URLs are unaffected.

* style(sso): use idiomatic sql lower() comparison for domain conflict query

Match the repo's prevailing `sql`lower(col) = value`` idiom for the
case-insensitive SSO domain conflict lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): align workspace env admin gate with hasWorkspaceAdminAccess

Use the same admin check the secrets UI uses (owner, admin permission, or
org-admin) so owners and org-admins are not wrongly denied their own decrypted
workspace secrets, while read-only members remain restricted to names only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(sso): rely on lower(domain) match for conflict detection, drop dead in-memory recheck

Address PR review: the SQL `lower(domain) = <normalized>` predicate already
excludes rows that the in-memory `normalizeSSODomain(...) === domain` recheck
claimed to catch, making that recheck dead/misleading code. Match on the
canonical lower-cased domain and filter purely by ownership. Malformed legacy
values (wildcards, schemes, ports) never match an email domain at sign-in, so
excluding them is not a gap. Test DB mock now applies the lower() predicate so
the casing-variant case is genuinely exercised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): scope webhook deploy path conflict to active webhooks

findConflictingWebhookPathOwner omitted the isActive filter that the
runtime dispatcher (findAllWebhooksForPath) applies, so an inactive but
non-archived webhook from another workflow (e.g. after undeploy or
failure auto-disable) would permanently block any new deployment on that
path even though it never receives deliveries. Align the guard with the
runtime isActive + archivedAt filter; the earliest-owner runtime check
remains the authoritative cross-tenant protection. Also trims verbose
TSDoc on the webhook path-isolation helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): exclude archived workflows from webhook deploy path conflict

findConflictingWebhookPathOwner now joins workflow and filters
isNull(workflow.archivedAt), matching the runtime dispatcher
(findAllWebhooksForPath). A webhook on an archived workflow can never
receive deliveries at runtime, so it must not block legitimate path reuse
with a 409.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): anchor KB file ownership to earliest document in any state

A KB file's owner is now the earliest document referencing its key regardless of
state (active/archived/deleted/excluded); access is granted only when that owning
document is still active. Closes the residual where an attacker could plant an
active document to claim a file whose original document was archived or deleted.

* updated greptile icon

* revert(security): drop KB file authorization changes

Reverts the knowledge-base file-access work (origin-pinning / owner-pinning /
origin allow-list in verifyKBFileAccess) and its test. The other hardening fixes
(SSO domain registration, webhook path isolation, workspace env secrets, CSV
export) are unchanged. apps/sim/app/api/files/authorization.ts is restored to its
origin/staging baseline.

* fix(sso): treat caller's own user-scoped provider as owned during conflict check

Self-hosters often register SSO user-scoped via the CLI script (no
SSO_ORGANIZATION_ID). If they later enable organizations and reconfigure the
same domain org-scoped through the UI, the conflict check previously treated
their own user-scoped row as another tenant's and returned a misleading 409.
Recognize the caller's own user-scoped provider as owned so that migration is
allowed, while still blocking another user's or another org's domain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* revert(security): remove workspace-env admin gate

Defer to a credential-based access model (separate change). Restores
GET /api/workspaces/[id]/environment to main behavior and removes the test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(security): consolidate webhook path-collision check into one helper

Extract findConflictingWebhookPathOwner to lib/webhooks/utils.server.ts as
the single source of truth for cross-tenant path-collision detection, used by
both webhook creation paths (deploy sync and the manual /api/webhooks route).

This also repairs two latent issues in the manual route's previous inline
check, which queried with limit(1) and only webhook.archivedAt:
- limit(1) inspected one arbitrary row, so a same-workflow row could mask a
  foreign collision (false negative). The shared helper scans all matching
  rows.
- It omitted isActive/workflow.archivedAt, so inactive or archived-workflow
  webhooks (which never receive deliveries) permanently blocked path reuse.
  The helper mirrors the runtime dispatcher's filter.

Same-workflow webhook reuse for upsert is now a separate, explicit lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF (#4818)

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF

* test(security): use real isPrivateOrReservedIP and cover IPv6 edge cases

* improvement(integrations): validate and expand devin, cursor, and greptile (#4820)

* improvement(integrations): validate and expand devin, cursor, and greptile

- devin: fix missing org_id path segment on all session endpoints, add 7 session sub-resource tools (list messages/attachments, get/append/replace tags, archive, terminate), pagination, and is_archived output
- cursor: add get_api_key_info, list_models, list_repositories tools
- greptile: align block and docs
- normalize array outputs to default [] and tighten types

* refactor(cursor): simplify list_repositories v2 array normalization

Collapse the redundant `?? []` + `Array.isArray` double-guard into a
single Array.isArray check, per PR review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(devin): scope session-tag mapping to tag ops and normalize array tag inputs

- Only map sessionTags into the tools tags param for append/replace operations,
  preventing stale sessionTags state from clobbering create_session tags
- Fall back to a wired tags value when sessionTags is empty for tag operations
- Normalize tag inputs (string or wired string[]) via normalizeTags so array
  values from other blocks no longer throw on .split

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cursor): restore base64 file data in legacy download_artifact metadata

The legacy CursorBlock exposes only content + metadata (no v2 file
output), so metadata.data was the only way legacy-block workflows could
access downloaded artifact bytes. Restore the base64 data field and
document it in the outputs/type instead of dropping it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(devin): coerce terminateArchive to archive flag for boolean-wired input

* docs(integrations): regenerate tool docs for new devin and cursor operations

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(search-replace): don't auto-navigate when content edits invalidate the active match (#4819)

* fix(search-replace): don't auto-navigate when content edits invalidate the active match

* fix(search-replace): clear afterReplaceIndexRef on apply failure and zero matches

* fix(search-replace): remove duplicate setActiveSearchTarget(null) on close

* fix(search-replace): move afterReplaceIndexRef write inside handleApply past the guard

* fix(search-replace): auto-navigate when hydration resolves with no prior active match

* chore(search-replace): remove inline comments

* fix(search-replace): revert !activeMatchId guard that caused immediate re-navigation after deselect

* improvement(enrichments): limit company-info to fields both providers return (#4817)

Hunter's company dataset returns null industry/foundedYear for many large companies (verified against the live API for Microsoft, Amazon, Google), so under the first-non-empty-wins cascade those columns appeared inconsistently across rows. Limit company-info outputs to employee count and description — the fields Hunter and PDL both reliably return — so every row is consistent. employeeCount is a string so Hunter's range bucket and PDL's exact count share the column.

* fix(files): don't reject external URLs containing '..' in file parse validation (#4821)

* fix(files): don't reject external URLs containing '..' in file parse validation

The file block's file_fetch operation rejected any external URL whose path
contained '..' (e.g. Slack files-pri slugs with a literal '...') with
'Access denied: path traversal detected'. Traversal checks only apply to
local paths — external http(s) URLs are fetched with SSRF protection
downstream and are never resolved against the filesystem, so they now
short-circuit as valid. Internal /api/files/serve/ URLs keep full traversal
protection.

* test(files): fix external-URL assertion to handle undefined error

* test(files): assert success explicitly in external-URL traversal test

* fix(files): keep traversal protection for https URLs matching internal serve paths

* feat(google-sheets): add row filtering to read with numeric operators (#4822)

* feat(google-sheets): add row filtering to read with numeric operators

Adds client-side row filtering to the Google Sheets read (v2) operation.
Filter the returned rows by a header column using text operators
(contains, not_contains, exact, not_equals, starts_with, ends_with) and
numeric/ordering operators (gt, gte, lt, lte). Filtering lives in a pure,
unit-tested helper (filterSheetRows) and runs over the fetched read range;
an optional `filter` output reports whether the column was found and how
many rows matched.

Also hardens the surrounding tools:
- trim spreadsheetId in write/update/append URL builders (matches read)
- URL-encode the v1 read default range
- expose valueInputOption for the update operation in the block

Backwards compatible: with no filter requested, read output is byte-
identical and the `filter` field is omitted. The filterMatchType union is
widened additively (4 -> 10 values).

* fix(google-sheets): correct filter metadata for missing column and header-only sheets

- matchedRows is now 0 (not totalRows) when the filter column is not found,
  so it no longer contradicts applied=false / columnFound=false
- columnFound now reflects an actual header lookup for empty/header-only
  sheets instead of being hardcoded true
- add tests covering header-only and empty sheets with present/absent columns

* fix(selectors): fetch all pages for paginated dropdown list routes (#4823)

* fix(selectors): fetch all pages for paginated dropdown list routes

Dropdown selectors fetched only the first page of paginated provider
APIs, silently hiding results past page one. Add bounded server-side
draining to the list routes across Microsoft Graph, Google, Notion,
Atlassian, Linear, AWS CloudWatch, and offset/token REST APIs, plus a
shared client-side drain cap in the selector hook. Response shapes,
stored values, and tool execution are unchanged; CloudWatch list tools
still honor a caller-supplied limit. Also fixes the Word file picker
that was searching for .xlsx files.

* fix(selectors): harden JSM and Monday pagination draining

- JSM service-desk/request-type drains advance `start` by the actual row
  count returned (not the fixed page size) and stop on an empty page, so a
  short non-final page can't skip items.
- Monday boards drain now checks `response.ok` per page, surfacing a
  mid-drain HTTP failure instead of treating it as an empty final page and
  returning a partial 200.

* docs(selectors): clarify JSM drain advances start by actual row count

The offset-advancement fix (advance `start` by the rows returned, not the
fixed page size) landed in 7b19788a8; update the TSDoc to match so it no
longer reads as advancing by `limit`.

* fix(selectors): drain fetchPage in direct fetchList callers

Making `fetchList` optional left three direct callers (outside the
useSelectorOptions hook) calling it unguarded, which broke the build's
type check. Route them through a shared `loadAllSelectorOptions` helper
that uses `fetchList` when present and otherwise drains `fetchPage`.
This also prevents a regression: `confluence.spaces` / `knowledge.documents`
now paginate via `fetchPage` only, and these callers (search/replace,
value resolution) would otherwise have silently returned no options.

* chore(selectors): rename MAX_PAGE_PAGES to MAX_NOTION_PAGES for readability

* fix(sso): re-check domain conflict before write and reject IP-address domains (#4825)

* improvement(copilot): make copilot_messages the sole transcript store, remove JSONB dual-write (#4826)

Stop writing/reading the legacy copilot_chats.messages JSONB column now that
reads are cut over to copilot_messages. Make appendCopilotChatMessages the
primary write (throws on failure instead of swallowing), repoint peripheral
readers (workspace VFS, chat cleanup, data drains, fork, superuser import) to
copilot_messages, and persist the assistant turn inside finalizeAssistantTurn's
transaction so it commits atomically with the stream-marker clear. The column
itself is dropped in a follow-up migration after this bakes.

* feat(tables): expand filter operators (not-contains, starts/ends-with, not-in, empty) (#4827)

Add does-not-contain ($ncontains), starts-with ($startsWith), ends-with
($endsWith), not-in-array ($nin, previously executed server-side but unexposed
in the UI), and is-empty/is-not-empty ($empty) filter operators end-to-end —
SQL builder, condition types, query-builder converters/constants, the filter
UI, the Table tools/block descriptions, and docs.

Also fix correctness bugs in the filter builder surfaced by the wider operator
set:
- Same-column AND rules (e.g. age > 18 AND age < 65, or name startsWith 'A'
  AND name endsWith 'Z') silently overwrote each other because the AND group
  was keyed by column name. They now merge into one operator object, which
  also makes Filter -> rules -> Filter round-trip losslessly for multi-operator
  columns.
- $nin values were not split into an array like $in, and textual-match values
  like "123" were numeric-coerced (breaking the ILIKE path).
- A non-boolean $empty operand from the raw API silently inverted the check; it
  now coerces 'true'/'false' strings and otherwise returns a 400.

* improvement(copilot): stop persisting tool-call result outputs in transcripts (#4829)

Opening a Mothership task could take many seconds because a single persisted
assistant message in copilot_messages.content can reach hundreds of MB, almost
entirely inside contentBlocks[].toolCall.result.output (e.g. a get_workflow_logs
or run_workflow result). The DB query is ~2ms; the cost is detoasting that
payload, shipping it to the browser, and parsing it.

These outputs are dead weight on the Sim side: they are never rendered (the
thread shows only tool name/title/status) and never replayed to the model (the
upstream copilot service owns conversation memory). So drop result.output before
it is persisted, keeping result.success/error plus the tool metadata.

- add stripToolResultOutput() in persisted-message.ts
- apply it in messages-store toRow (covers every write path) and in
  loadCopilotChatMessages (existing rows render fast on read)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers (#4830)

* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers

* fix(providers): guard Ollama streaming fast-path with hasActiveTools

Match Together/Baseten/Fireworks: when tools are supplied but all are
filtered out (usageControl 'none'), take the single streaming call instead
of an extra non-streaming round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(providers): filter non-chat model types from Together model list

* refactor(providers): dedupe Ollama Cloud upstream schema

ollamaCloudUpstreamResponseSchema was byte-for-byte identical to
ollamaUpstreamResponseSchema (both /api/tags endpoints return the same
{ models: [{ name }] } shape). Drop the duplicate and reuse the shared schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(knowledge): calendar view sync, deduplicate popover animation classes, type-safe filter cast

* cleanup(knowledge): remove TRIGGER_BORDER_CLASS duplication, inline displayLabel, drop enabledFilterParam alias

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>

* feat(blocks): add BlockMeta to Quiver and Linq; fix invalid block config fields; update skills

Block fixes:
- Add QuiverBlockMeta (tags + 3 templates: icon generator, diagram creator, vectorizer)
- Fix QuiverBlock: remove invalid tags field from BlockConfig, IntegrationType.Design →
  IntegrationType.AI (Design doesn't exist in the enum)
- Fix GreptileBlock: remove invalid tags field from BlockConfig,
  IntegrationType.DeveloperTools → IntegrationType.DevOps
- Fix LinqBlock: remove invalid tags field from BlockConfig (tags belong only in BlockMeta)

Skills:
- add-block: add dedicated BlockMeta section with structure, rules, and registration
  pattern; add BlockMeta checklist items
- add-integration: add BlockMeta to block structure template, add rules clarifying
  that tags must NOT appear on BlockConfig and integrationType must be a valid enum
  value; update registry snippet to include blocksMeta; add checklist items

* fix(integrations): fix category dropdown by defining missing LANDING_INTEGRATIONS_DATA_PATH and regenerating integrations.json

The staging merge introduced landing-content.ts but forgot to define
LANDING_INTEGRATIONS_DATA_PATH in generate-docs.ts, causing the script
to crash before writing integrations.json.

The stale JSON had integrationTypes (plural array) from an older script
version, while the Integration type and workspace UI both read
integrationType (singular string) — so ALL_CATEGORY_SECTIONS bucketed
to undefined and the category filters never appeared in the dropdown.

Fixed by adding the missing path constant and re-running the generator.
integrations.json now has 192 entries with the correct integrationType field.

* fix(sidebar): restore resize handle on all pages

commit 3109104582 wrapped the resize handle in {(isCollapsed ||
isOnWorkflowPage) && ...} and added a useEffect that resets sidebar
width to SIDEBAR_WIDTH.MIN whenever the user navigates away from a
workflow page. Together these made the sidebar non-resizable on Tasks,
Tables, Knowledge Base, and every other non-workflow page.

Restore the staging behavior: always render the resize handle and
remove the effect that forced the width reset on page transitions.

* fix(sidebar): match staging onKeyDown and tabIndex on resize handle

The resize handle was still conditionalizing onKeyDown and tabIndex
on isCollapsed, blocking keyboard accessibility of the separator role
when expanded. Staging always attaches both unconditionally.

onKeyDown={isCollapsed ? handleEdgeKeyDown : undefined} → onKeyDown={handleEdgeKeyDown}
tabIndex={isCollapsed ? 0 : undefined}                 → tabIndex={0}

* feat(integrations): show connected credentials on integration detail page

When navigating to /integrations/google-docs (or any integration), a
Connected section now appears above the templates listing all workspace
credentials tied to that provider. Each row links back to the credential
detail page (/integrations/connected/${id}) for management actions.

Pairs with the earlier change that routes connected items from the
integrations list to the provider detail page instead of directly to
the credential detail page.

* fix(integrations): rename Add in chat to Add to Sim

* fix(skills): rename Add button to Add to Sim

* fix(platform): restore M1/M2/M3 regressions and LazyMotion on landing page

M1 — Invitation guard: re-introduce usePermissionConfig().isInvitationsDisabled
alongside the workspace inviteDisabledReason check. The flag now also
respects NEXT_PUBLIC_DISABLE_INVITATIONS and EE permission-group
disableInvitations, not just billing policy.

M2 — Settings redirects: /settings/integrations and /settings/skills
now server-redirect to /integrations and /skills respectively so old
bookmarks and emails don't silently land on General.

M3 — Starter block search exclusion: restore block.type !== 'starter'
guard in the search store so users cannot add a duplicate Starter block
via the command palette.

LazyMotion: restore LazyMotion + domMax/domAnimation wrappers and m.*
components in landing-preview-panel and landing-preview-home. The
removal was accidental (the full motion bundle was left after an
import cleanup), which caused the entire framer-motion feature set to
load eagerly on the landing page.

* fix(integrations): revert connected list to credential detail; remove settings redirects

* feat(sidebar): restore workspace switcher search with updated styling

Shows a search input in the workspace dropdown when the user has more
than 3 workspaces (WORKSPACE_SEARCH_THRESHOLD). Keyboard navigation:
ArrowDown/Up to move through results, Enter to switch, resets on close.

Styled to match the current branch (border-1/surface-5 tokens, sm text,
11px Search icon) rather than the old staging styles. Highlight state
is wired through chipVariants active prop so it follows the same active
appearance as clicked/hovered items.

* fix(sidebar): clean up workspace search — layout, memo, and effect guard

* fix(sidebar): align workspace rename input selection style with workflow rename

* perf(sidebar): eliminate React re-renders during sidebar drag resize

Previously, every mousemove during resize called setSidebarWidth(), which
both updated the --sidebar-width CSS variable (sync) and set Zustand state
(async). This caused:
  1. A 1-frame transition flash on mousedown — isResizing state had to
     round-trip through React before the is-resizing CSS class was applied,
     so the width transition fired for the first pixel of movement.
  2. A React re-render per pixel dragged — components reading sidebarWidth
     from the store (avatars, usage-indicator) lagged one frame behind the
     container, making the + and ... buttons appear to jump ahead.

New approach:
  - handleMouseDown adds is-resizing directly to the sidebar DOM node before
    any React involvement (synchronous, no frame lag).
  - mousemove writes only to the CSS custom property (zero React renders).
  - mouseup persists the final width to Zustand exactly once.
  - isResizing / setIsResizing state removed from the store and hook — they
    are no longer needed since the class is managed via direct DOM mutation.

* perf(sidebar): add requestAnimationFrame throttle to resize mousemove handler

* fix(sidebar): fix drag-right lag caused by WorkspaceChrome overflow-hidden transition

The sidebar-container's is-resizing class correctly suppressed its own width
transition, but the two wrapper divs in WorkspaceChrome both have
transition-[width]/transition-transform with a 175ms ease. The outer wrapper
also has overflow-hidden, so while the sidebar content was at the correct width
instantly, it was visually clipped by the outer wrapper which was still
animating — causing the + and ... buttons to appear to lag behind the resize
line on drag-right (not on drag-left, since shrinking doesn't clip content).

Fix: add sidebar-shell-outer/sidebar-shell-inner class names to both chrome
wrappers, and suppress their transitions via html.sidebar-resizing rule when
a drag is active. The html.sidebar-resizing class is toggled directly in the
resize hook alongside is-resizing, so it takes effect synchronously on mousedown.

* fix(icons): redesign Download icon to match Upload style; fix Upload/Download confusion

Download icon was missing the tray/shelf line at the bottom that Upload has,
making it look like a plain arrow rather than a matched pair. Updated Download
to use the same viewBox, stroke weight, and three-path structure as Upload
(tray + stem + arrowhead), just pointing down.

Also fix 5 places where Upload (↑) was incorrectly used for download/export
actions:
  - files.tsx: two Download action rows in toolbar and context menu
  - tables/table.tsx: Export CSV toolbar button
  - table-context-menu.tsx: Export CSV context menu item
  - logs.tsx: Export toolbar button
  - landing-preview-logs.tsx: decorative Export button

Import CSV and actual upload actions correctly keep the Upload icon.

* fix(icons): replace Upload with Download on all remaining export/download actions

- panel.tsx: Export workflow dropdown item
- context-menu.tsx: Export in sidebar workflow context menu
- chat.tsx: Export chat button
- output-panel.tsx: Export console CSV button
- terminal.tsx: Export console CSV button
- resource-content.tsx: Export table as CSV + Download file buttons

* fix(icons): fix remaining Upload→Download on download actions in files and logs

- action-bar.tsx: download button in files toolbar
- file-row-context-menu.tsx: Download item in file context menu
- file-download.tsx: both download buttons in log details file viewer

* updated block skills, settings pages, modals, buttons -> chips, blocks missing metadata

* updated skill modal

* improvement(resource-header): refine breadcrumb truncation ux

* Fixes

* improvement(resource): add floating overflow text tooltips

* wire up credits counter

* improvement(resource-header): mute path dropdown title

* refactor(resource-header): share floating-tooltip engine, prune dead overlay tooltips (#4844)

Clean up the breadcrumb truncation feature for reuse and correctness:

- Extract useFloatingTooltip / useIsOverflowing / FloatingTooltip into a shared
  floating-tooltip module. BreadcrumbSegment and FloatingOverflowText now consume
  one implementation instead of duplicating ~150 lines of positioning, velocity,
  overflow-detection, and portal logic.
- Replace the hardcoded terminal-label regex in ResourceHeader with a typed
  `terminal` flag on BreadcrumbItem (set by the document chunk/loading crumbs),
  decoupling the generic header from knowledge-base copy.
- Clear the path-popover close timeout on unmount and reuse the shared
  POPOVER_ANIMATION_CLASSES constant.
- Drop the redundant manual overflow-state writes (fixes a sticky fade mask).
- Revert FloatingOverflowText inside Combobox `overlayContent` back to plain
  truncating spans across files/logs/tables/scheduled-tasks/document: the combobox
  overlay is pointer-events-none, so the tooltip handlers never fired there.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(emcn,resource-header): address PR #4844 review feedback

- useIsOverflowing now uses a callback ref so the ResizeObserver follows the
  element across mount/unmount/reassignment instead of capturing it once at mount.
  Safe for conditionally rendered consumers of the shared hook. (greptile P2)
- Move POPOVER_ANIMATION_CLASSES out of chip-date-picker implementation internals
  into emcn/components/popover/popover-animation.ts, exported from the
  @/components/emcn barrel. Consumers now import from the module boundary.
  (greptile P2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Prompt surface

* Regenerated contracts

* upgrade table and styling upgrade

* Literalness gaps

* Updates

* Remove prefix for running code

* Display names for reads

* Tool catalog

* fix schema to include integration

* fix(files): align delete icon with tables view (Trash → Trash2)

Co-Authored-By: waleed <waleed@simstudio.ai>

* fix(mothership): preserve blockType for integration contexts in sent messages

Integration mention chips were missing their provider icons in sent messages
because blockType was dropped when mapping ChatContext to messageContexts.
renderIntegrationTile returns null without blockType, silently hiding the icon.

* fix(mothership): allow 'integration' resource type in chat resources API

The VALID_RESOURCE_TYPES allowlist was missing 'integration', causing a
400 error when adding integrations to the Mothership resource tab — so
they never persisted and disappeared on refresh.

* fix(ui): Add "File" title next to file resource header

* fix(ui): fix resource header columns being bolded

* fix(resource): keep the floating tooltip from jumping on click

Gate the focus-driven show behind :focus-visible so a mouse click (which
focuses the trigger) no longer re-shows the tooltip anchored to the element's
bottom edge. On click the tooltip now hides cleanly instead of jumping down;
keyboard focus still shows it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(sidebar): eliminate unnecessary re-renders in workspace switcher for non-search users

- onMouseEnter: only set highlightedIndex when showSearch is true, preventing
  a state update + re-render on every workspace row hover for users with ≤ 3
  workspaces where the search is never shown
- onOpenChange: only reset workspaceSearch and highlightedIndex when showSearch
  is true, since both values are always already at their defaults for non-search
  users and setting them triggers a pointless re-render during dropdown close
- data-workspace-row-idx: only set when showSearch is true since the scroll
  effect that reads this attribute is already gated on showSearch

* feat(search): context-aware cmd-k results on the integrations page

When cmd-k is opened on the integrations page, show two new result
groups: connected accounts (visible even with empty input) and catalog
integrations (appear once the user types). Selecting an OAuth integration
deep-links to its detail page with ?connect=oauth so the connect modal
auto-opens. Non-OAuth integrations navigate to the plain detail page.

Both groups are gated to the integrations page only and respect the
hideIntegrationsTab permission. The credentials fetch shares the same
React Query cache key as the integrations page itself (no double fetch).

* refactor(emcn): make the floating tooltip the one canonical Tooltip

Replace the Radix-based emcn Tooltip with the cursor-following floating tooltip so
every tooltip in the app uses one consistent style. Built on the shared
floating-tooltip engine (relocated into emcn), not a parallel implementation.

- Move the floating-tooltip engine into emcn/components/tooltip and export it from
  the barrel; re-point its consumers (FloatingOverflowText, resource-header)
- Extend the FloatingTooltip bubble to render arbitrary children (+ role/id for
  a11y) so it can back general tooltips, not just overflow text
- Rebuild emcn Tooltip (Root/Trigger/Content/Provider/Shortcut/Preview) on
  useFloatingTooltip — compound API preserved, ~350 call sites unchanged, legacy
  side/align props accepted and ignored (the tooltip follows the cursor). Removes
  @radix-ui/react-tooltip usage (package kept for a later cleanup; react-slot
  retained for asChild)

Note: general tooltips now show instantly (no hover delay) and follow the cursor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style(emcn): put tooltip text on the design scale (text-caption)

Replace the tooltip's ad-hoc `text-xs` + `leading-[18px]` with the semantic
`text-caption` (12px) font-size token so the text styling is fully on the design
scale and self-documenting, matching how the rest of the system is set up. The
color already used the global `--text-body` token. No visual change (still 12px
with a ~18px line height).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Mship byok

* feat(sidebar): add empty task state and inline task creation

- Show "No tasks yet" in the Tasks section (expanded and collapsed) when the list is empty
- Clicking + now creates a task via the API and navigates directly to it, rather than navigating to home
- Add isCreatingTaskRef guard to prevent double-click from spawning multiple tasks
- Disable + button while creation is pending
- Fall back to home navigation on creation error

* invite, billing, home

* Sandbox warning

* improvement(seats): auto purchase seats on invitations into workspace (#4857)

* improvement(seats): auto purchase seats on invitations into workspace

* improve sampling for seat drift reconciler

* address comments

* feat(knowledge): align connector UI with integrations page styling

- ConnectorTypeCard now matches integration rows: brand-colored rounded-xl tile, ArrowRight, title/subtitle hierarchy
- ConnectorCard icon upgraded from flat surface-4 to branded tile (white icon on brand bg, graceful fallback)
- Connector header badges use chipVariants instead of custom Button classes
- Add-connector search input aligned to integrations style (h-[30px], rounded-lg, border-1)

* fix(icons): trim Folder SVG viewBox to remove right-side whitespace

The folder path only extends to x≈14.33 in a 15-unit viewBox, leaving
~0.5 units of empty space on the right. At 12px rendered size this
produces ~0.4px extra gap (visible as ~1px on retina displays) compared
to solid icons like the workflow color square. Trimming the viewBox to
14.5 units makes the folder fill its chip slot evenly.

* fix(user-input): restore draft text synchronously to preserve contexts on nav

The SSR-safe approach (empty useState + effect restore) created a timing
window where the sync effect in useContextManagement fired with message=''
before the value was set, clearing any restored contexts. Folder and workflow
contexts (not re-added by applyAutoMentions) were lost on every nav-back.

Revert to the staging approach: initialize value synchronously from the
draft store so message is already populated when effects run, matching
the behavior on staging.

* fix(queue): render context chips in queued messages

- Remove plainMentions from queued message rows so context chips render
  with icons, consistent with sent messages
- Fix computeMentionRanges to use '/' prefix for skill contexts (content
  has the slash trigger restored at submit time, not '@')

* fix(mothership): remove integrations from add-resource dropdown

* fix(mothership): comment out integrations from add-resource dropdown

* File serializer

* Skills

* chore(db): drop form, templates, template_creators, template_stars tables

These tables backed the Forms and Templates platform features which were
intentionally removed from this branch. Clean up the DB schema to match.

* chore(db): add migration metadata for 0224 drop tables

* block icons, sidebar, toolbar

* chore: remove remaining dead code for template-profile feature

- Remove 'template-profile' from SettingsSection union type
- Remove 'template-profile' entry from SECTION_TITLES
- Remove now-redundant template-profile guard in settings sidebar
- Remove commented-out template-profile nav item

* fix(multi-select): preserve anchor on range selection for tasks and folders

After a shift+click range, the anchor (lastSelectedTaskId / lastSelectedFolderId)
was being updated to the end of the range (toId). This caused subsequent
shift+clicks to extend from the wrong point instead of the original click.

Standard behavior: anchor stays at the initial click (fromId) so repeated
shift+clicks always expand/contract relative to where you started.

* Update skills

* Load skill tool

* Fix vfs dynamic context encoding

* Media subagent

* feat(emcn): add SearchInput component and unify search bars platform-wide

- Add SearchInput to emcn: 30px chip-family filled search input matching the
  integrations page pattern (border-1, surface-5, leading Search icon)
- Migrate all 22 search bars across settings, EE pages, and integrations to
  SearchInput (only layout classes allowed at callsites)
- Rename Sim Keys -> Sim API Keys in nav/title; page copy now says API key
- Remove components/ui input, label, and verified-badge; migrate consumers
  to emcn equivalents or raw inputs (table cell editor, wand prompt bar)
- Delete dead EE skeleton files (data-drains, data-retention)
- General settings: Home Page chip moves to header left as navigation

* fix(files,tables): restore new-file editor autofocus and CSV import error toasts

Both were dropped on the staging line and regressed vs production (main):

- Files: the new-file editor autofocus chain (files.tsx -> file-viewer ->
  text-editor) was stripped by the react-doctor dead-code pass in #4544,
  which misread the prop-drilled `autoFocus` (consumed by an imperative
  `editor.focus()` effect) as unused. Restored the prop through all three
  layers and the one-shot focus effect so creating a new file focuses the
  editor immediately.
- Tables: CSV import failures were silently logged with no user feedback.
  Restored the per-file and generic `toast.error` surfacing.

* Move superagnet back into superagent

* feat(home): score suggested actions by workspace signals

- Derive the suggestion pool from the curated block template catalog
  (1,343 prompts across 172 blocks) instead of 15 hardcoded entries
- Fix inverted relevance: prompts for connected providers are now boosted
  4x (instantly runnable) instead of excluded; unconnected discounted 0.4x
- Weight by featured (3x), popular category (1.5x), and resource gaps
  (no tables -> boost table starters; has KBs -> dampen KB-creation prompts)
- Weighted sampling without replacement, max one suggestion per block
- Connect rows weighted by catalog template count; 2 for fresh workspaces,
  1 once something is connected
- Key the catalog map by both versioned and base block types so gmail_v2
  templates resolve (gmail, github, notion, linear were silently dropped)
- Replace derive-in-effect state with a useMemo keyed by a shuffle nonce
- Add suggested_action_clicked / suggested_actions_shuffled /
  suggested_actions_toggled PostHog events

* billing, teammates

* improvement(credentials): credentials invites, secrets tab wiring up (#4874)

* improvement(credentials): move away from invite notion

* wire up secrets ui/ux

* address comments

* get consistent styling by removing emcninput + text area

* styling consistency

* remove fallback

* address comment:

* refactor(ui): migrate settings & workspace UI to chip design system

Migrate modals to ChipModal (showDivider, hint, resizable, size, leading,
ChipModalTabs), standardize Chip variants, add ChipCombobox wrapper, and apply
chip inputs/dropdowns across settings, knowledge, logs, tables, inbox, EE tabs.
Render ChipModalTabs as a ChipSwitch segmented control. Align /settings/secrets
detail with /integrations, refresh whitelabeling, and restore file-editor
autofocus and CSV import error toasts.

* Update media

* fix(mothership): restore integrations to useAvailableResources for @ mention

Integrations were fully removed from useAvailableResources which broke
the @ mention menu since user-input shares the same hook. Now integrations
are always included in the hook but excluded at the AddResourceDropdown
component level, keeping them out of the sidebar + menu while remaining
available for @ mention autocomplete.

* refactor(settings): chip design-system consistency pass across all tabs

Extract a shared chip-field shell (CHIP_FIELD_SHELL/CHIP_FIELD_INPUT) mirroring
Input variant='chip' and route secrets, credential detail, and integrations
credential detail through it (30px height, font-medium, focus ring). Add a
Discard action to the secrets header when dirty. Group BYOK providers into
Models/Search & web/Enrichment sections and align its tiles to the integrations
tile.

Normalize list-row typography to text-[14px]/text-[12px] and icon tiles to
rounded-xl + border across api-keys, copilot, custom-tools, mcp,
workflow-mcp-servers, credential-sets, and access-control. Tone the secrets
Details chip and per-row affordances to ghost. Fix token correctness: raw
tailwind colors to design tokens (data-drains), missing chip variant on the
Snowflake role input, ColorInput chip-field reuse (whitelabeling), error token
and icon sizes (workflow-mcp-servers), border token (mcp), no-results sizing
(secrets), row chrome (recently-deleted), hover token and Button to Chip
(access-control), and deduped textarea chrome (sso).

* feat(settings): unify filter dropdowns on ChipSelect (integrations style)

Add a ChipSelect emcn component — a filled chip trigger + chevron opening a
DropdownMenu, matching the integrations category filter — supporting single
select, multi-select (checkbox rows), grouped options, and optional in-menu
search. Migrate every settings/EE filter dropdown off ChipCombobox to it:
audit-logs (resource-type multi + time-range), data-retention, data-drains,
general, admin (grouped tool picker), inbox status filter, and the workflow
MCP-server pickers. The SSO provider-id field stays an editable combobox since
it accepts free-text slugs.

Also fix audit findings: chip the MCP client-secret input, normalize an MCP
error-text size, and drop now-dead destination-icon code in data-drains.

* fix(mentions): require explicit @ for integration mentions; decorate sent messages robustly

- Bare integration names in prose (Monday, Notion, Clay) are no longer
  auto-converted to mentions or chipped — mention treatment is strictly
  opt-in via a token-starting @ (fixes the scunthorpe problem)
- @-prefixed mentions still canonicalize casing (@slack -> @Slack) on both
  the keystroke fast-path and bulk paths (paste, template, draft, STT)
- Sent/queued messages now self-sufficiently decorate @IntegrationName
  tokens via a text scan, covering messages sent before the input pass
  ran or authored outside the chat input
- Integration contexts missing a resolvable blockType (messages persisted
  before blockType was saved) are backfilled by label lookup so their
  mention pills render the brand icon again

* refactor(settings): section the API keys page like secrets

Wrap Workspace, Personal, and the allow-personal-keys toggle in SettingsSection
(muted label + divider) instead of bare bold headers, matching the secrets and
BYOK pages.

* fix(settings): ChipSelect renders above modals + full-width form mode

Raise the ChipSelect menu to --z-popover so it layers above modal surfaces
(--z-modal) instead of opening behind them. Add a fullWidth prop that stretches
the trigger and right-aligns the chevron for form-field use, and apply it to the
workflow MCP-server pickers.

* fix(emcn): ChipSelect uses the emcn flat chevron, not lucide's square one

The lucide ChevronDown is square; rendering it at the chip's 9x7 footprint
stretched it. Switch to the custom emcn ChevronDown (built for that wide aspect),
matching the integrations filter and ChipDropdown.

* fix(emcn): ChipSelect trigger hugs its content (w-fit)

In a stacked form layout the trigger was stretched by align-items: stretch,
leaving an empty gap to the right of the value. Add w-fit so the chip sizes to
its content (a compact pill) everywhere; fullWidth form selects are unaffected.

* fix(emcn): ChipSelect uses a square lucide chevron

Revert to lucide's ChevronDown sized square (size-[14px]) so it renders crisp,
matching the standard select chevron used by Combobox.

* renamed tasks to chats

* rename and file change

* Wspace resource refs

* improvement(billing): wire up billing, org, teammates tabs + remove deprecated subscription tab (#4887)

* improvement(billing): wire up billing, org, teammates tabs + remove depr subscription tab

* pass exec timeout to tool routes

* reuse helper

* address comments

* address disable comment

* Vfs updates

* chore(db): remove migration 0224 to regenerate on top of staging

Co-authored-by: Cursor <cursoragent@cursor.com>

* VFS updates and linter

* fix type errors and regen migration?

* File block v5

* Remove docs

* Fix

* Doccer image

* Nuke the dag

* chore(db): drop branch migration 0226 ahead of staging merge; will regenerate

* chore(db): regenerate migration 0226 after staging merge

* Fix lint

* externalize before compaction in fallback'

* Run workflow improvements

* Loosen run options validation

* Lint

* fix save/discard chips to be consistent

* fix(ui): remove smodal tabs in favor of chip modal tabs

* update byok manager component

* fix(ui): skip auto-scrolling on mouse highlight of workspace

* Add creds to wspace context

* Wscontext

* fix(platform): restore settings redirects, forgot-password Enter submit, and tag tooltip visibility

- Re-add SETTINGS_REDIRECTS so /settings/integrations and /settings/skills
  deep links redirect to their top-level routes instead of rendering an
  empty settings panel (accidentally removed in 86da193cc3 one minute
  after cca5054cf6 added it)
- Add opt-in onSubmit to ChipModalField input/email variants and wire it
  in the forgot-password modal so Enter submits again (lost in the
  ChipModal conversion)
- Knowledge tag tooltip: drop the max-h/overflow-y-auto clamp that the
  pointer-events-none floating tooltip made unreachable; truncate each
  tag row instead so all tags stay visible with bounded height

* feat(usage-limits): org member specific limits (#4893)

* feat(usage-limits): org member specific limits

* revert feature flags

* fix type issues

* address comments

* address comments and address other billing gapes'

* fix

* improvements

* fix remaining credits display

* fix

* pass ws id to route validations

* chore(db): remove migration 0226_third_spot before staging merge

* chore(db): regenerate migration as 0227 after staging merge

* Block

* feat(telemetry): add posthog + audit coverage for new platform actions

Audit log (compliance/permission-relevant only):
- org_seat.provisioned — seat auto-purchased when an invite acceptance
  grows the org (actor = accepting user, includes seat delta)
- org_plan.converted — Pro→Team conversion triggered by invite acceptance
- org_seat.drift_reconciled — hourly cron healed a drifted seat count
- credential_member.added/removed/role_changed — credential sharing
  surface was previously fully unaudited
- table.created — parity with existing table.updated/deleted
- skill updates now record skill.updated instead of mislabeled
  skill.created

PostHog:
- seats_provisioned, credential_shared/unshared,
  environment_updated/deleted (key counts only, never names/values)
- table_import_started/completed — background CSV imports previously had
  zero failure observability
- table_exported, file_downloaded, skill_updated
- credential_connected now fires for OAuth completions (draft-hooks),
  credential_deleted for OAuth disconnects; previously only manual
  credentials were tracked
- table_workflow_run gains deployment_mode (live/deployed/mixed)

Also: logger.warn on all new credential-admin 403 denials (members +
environment routes); invite-created audit enriched with
enforcedFixedSeats/plan.

Deliberately excluded as noise: per-hour dead-letter audit rows (would
re-record the same stuck event every cron run) and a duplicate
system-actor org_plan.converted in the Stripe outbox handler.

* feat(home): fill textarea on suggested prompt click instead of sending

Clicking a prompt action in the Suggested Actions panel now populates
the Mothership user-input textarea (via applyAutoMentions) and focuses
it with the caret at end, rather than immediately submitting. The user
can review, edit, and send manually.

* fix(templates): name owning integration in featured block template prompts

Four featured prompts omitted their owning integration name, making them
unbranded and disconnected from their title. Each prompt now explicitly
names GitHub or Google Sheets so mentionifyIntegrations renders the chip
and the copy reads as a self-contained agent-building instruction.

* fix(templates): rewrite fragment prompts so each names its integration and reads as a complete instruction

Featured and non-featured block template prompts that either omitted
the owning integration's canonical name or were phrased as marketing
fragments rather than natural user instructions have been rewritten.
Each updated prompt now starts with an imperative verb ("Build a
workflow that…"), names the owning integration explicitly so the
@-mention chip renders correctly, and aligns with the entry's title.

Files changed: salesforce, hubspot (×2), github, slack, airtable,
firecrawl, iam.

* fix(templates): name integration in remaining block template prompts

- google_docs.ts: replace 'Google Doc' with 'Google Docs document' in 4 prompts; update title 'Meeting notes to Google Doc' to 'Meeting notes to Google Docs'
- google_sheets.ts: replace 'Google Sheet' with 'Google Sheets spreadsheet/Google Sheets' in 3 non-featured prompts
- slack.ts: replace 'Google Doc' with 'Google Docs document' in 'Daily standup summary'
- stripe.ts: replace 'Google Sheet'/'Slacks' with 'Google Sheets'/'Slack' in 'Weekly metrics report'
- reddit.ts: add 'Reddit' to 3 prompts that only referenced subreddits
- notion.ts: rewrite featured prompt to start with a verb and name Notion
- jira.ts: rewrite featured marketing-fragment prompt to start with a verb
- linear.ts: rewrite featured marketing-fragment prompt to start with a verb
- gmail.ts: rewrite featured marketing-fragment prompt to start with a verb and name Gmail

* fix(icons): convert monochrome dark brand icons to currentColor for dark mode

LinkupIcon, InfisicalIcon, IntercomIcon, LumaIcon, GranolaIcon, OnePasswordIcon,
and RailwayIcon were hardcoded to black/near-black fills/strokes, making them
invisible in bare DARK mode. Convert all to currentColor so they follow the
theme-aware text color.

Add iconColor: '#286efa' to IntercomBlock (Intercom brand blue is a confident
mid-tone, safe on both themes).

Two-tone icons (StagehandIcon, AgentPhoneIcon, QuiverIcon) are left unchanged
because their white details are structural — converting to single-tone would
destroy logo legibility.

* removed color, user input, sidebar, suggested actions, chips/emcn

* removed color migration

* Table bump

* improve(blocks): audit block catalog metadata for accuracy and fill gaps

- Fix template prompts that claimed capabilities blocks don't have
  (fabricated triggers, non-existent tools) across ~98 integrations
- Normalize integration tags to family conventions and valid union values
- Align alsoIntegrations and modules with template prompt content
- Add BlockMeta for circleback, imap, and rss trigger integrations
- Add templates to clickhouse and greptile metas
- Remove duplicate PageSpeed deploy-gate template

* chore(telemetry): drop org_seat.drift_reconciled audit

System self-heal bookkeeping doesn't belong in the user-facing audit
trail — membership and seat-purchase changes are already audited, and
the cron's logger output covers ops visibility.

* chore(db): consolidate branch migrations into single 0227

* fix(emcn): make ChipModal scroll internally when content exceeds viewport

The Modal→ChipModal migration dropped the old ModalBody scroll container:
ChipModal renders ModalContent bare (no overflow-hidden) and its wrappers
had no min-h-0 chain, so tall modals (e.g. New data drain with an S3
destination) overflowed max-h-[84vh] off-screen with no way to scroll.

Complete the flex min-h-0 chain through ChipModal's frame and give
ChipModalBody flex-1 min-h-0 overflow-y-auto — header/footer stay pinned,
body scrolls only when constrained. Short modals are unaffected (max-h
caps, it doesn't stretch), and dropdowns inside the body are Radix-portaled
so the new scroll container cannot clip them. Five modals that had locally
patched this with max-h-[Nvh] overrides keep working unchanged.

* fix(emcn): don't close modal when dismissing a dropdown via outside click

Radix dispatches pointer-down-outside to every open dismissable layer at
once, so clicking outside an open dropdown/select inside a modal closed
both the dropdown and the modal in one jarring step. ModalContent now
prevents its own dismissal while a portaled popper layer is open — the
first outside click closes just the popper, the next one closes the
modal.

* docs(skills): de-duplicate and correct agent docs; canonical styling tokens; mirror sim-sandbox rule

* docs(skills): broaden boundary-raw-fetch scope note, sync cursor rule mirrors

* fix(emcn): harden modal popper guard and exempt caret-anchored dropdowns from body scroll

Adversarial review follow-ups to the ChipModal scroll + dismissal fixes:

- Popper guard now requires data-state="open" inside the popper wrapper,
  so a dropdown that is merely animating closed no longer swallows the
  next outside click on the modal (DropdownMenu has an exit animation
  that keeps its wrapper mounted briefly)
- Port the same guard to SModalContent for consistency
- custom-tool-modal: opt its body out of the chrome scroll container
  (flex-none + overflow-visible); the caret-anchored EnvVar/Tag
  autocomplete dropdowns are absolute-positioned inside the body and
  must spill past its bounds rather than clip against a scroll boundary

* refactor(billing): drop unnecessary useCallback wrappers from event handlers

* fix(data-drains): complete chip migration of destination forms and fill missing placeholders

Finishes the in-flight FormField → ChipModalField conversion for all
destination form specs and adds the placeholders that several inputs
never had (S3 bucket/region/access keys, Azure account key, Datadog API
key, webhook signing secret/bearer token) — the cause of the New Data
Drain modal showing placeholder-less inputs inconsistently.

* fix(emcn): broaden modal popper guard to onInteractOutside

Covers the focusOutside dismissal path too: when a popper's focus scope
unwinds on close, the transient focus shift could still dismiss the
modal (and simultaneous body pointer-events lock teardown could freeze
the page). Same data-state="open" scoping as the pointer guard.

* fix

* fix(emcn): harden modal outside interactions

* Remove migration 0227 to regenerate after merge

Drop the 0227_org_member_usage_limit migration, its snapshot, and journal
entry so it can be regenerated cleanly after merging improvement/platform.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Regenerate migration as 0228 after merging improvement/platform

drizzle-kit generate re-emits the org_member_usage_limit table, document
uploaded_by, and table_run_dispatches triggered_by_user_id changes on top of
improvement/platform's 0227 snapshot. Also collapse the admin.tsx emcn import
left multi-line after merge conflict resolution.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix pre-existing type errors surfaced by post-merge type-check

These were present on dev before the merge; the post-merge type-check made
them visible.

- workspace-vfs.ts: materializeEnvironment now emits full oauth integration
  objects (id, providerId, displayName, role) so the summary matches
  WorkspaceMdData['oauthIntegrations'] (consumer reads credentialId from id)
- workflows/utils.ts: narrow resolved workflow rows to non-null workspaceId
  (the inArray filter already guarantees it) so the resolved result type holds

Co-authored-by: Cursor <cursoragent@cursor.com>

* Slack trigger

* fix tool mark complete bugs

* change thinking icon to just be blimp

* fix tests

* MCP fixes

* Fix files

* refactor use chat

* multiple fixes

* Fix deploys

* fix billing, tracing, retry lifecycle issues

* remove stale generated migration

Drop migration 0228 and its metadata so it can be regenerated after merging staging.

Co-authored-by: Cursor <cursoragent@cursor.com>

* merge staging in

* fix sockets err classification

* sockets invite flow fix

* remove stale generated migration 0229 before staging merge

Co-authored-by: Cursor <cursoragent@cursor.com>

* regen migration

* Update bun lock

* Fix schema issues

* Fix lint

* Fix build

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: andres <k62hc5kjst@privaterelay.appleid.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>
Co-authored-by: waleed <waleed@simstudio.ai>
2026-06-09 13:39:51 -07:00
Theodore Li f7811f8acc feat(tables): stable column ids for metadata-only rename (#4898)
* feat(tables): stable column ids for metadata-only rename

* fix(tables): address review — id-key exec clears + waiting labels, name in upsert error, un-gate group output ids

* fix(tables): id-correct column undo (rename/create/delete) with id reuse on re-add

* refactor(tables): mint column ids via generateId (uuid), drop collision-check plumbing

* fix(tables): match column id or name when removing column from optimistic delete cache

* fix(tables): resolve column storage id once for delete optimistic strips; biome-format 0228 snapshot

* fix(tables): make in-grid find id-native (scan/return JSONB keys as column ids)

* fix(tables): translate id→name in CSV/JSON export read path (+regression test)
2026-06-09 02:33:52 -04:00
Theodore Li ec256d244f fix(tables): compare order_key bytewise (COLLATE "C") to stop insert collation errors (#4908)
* fix(tables): compare order_key bytewise (COLLATE "C") to stop insert collation errors

* fix(tables): detect mis-keyed tables by position order, not order_key sort

* improvement(tables): make repair-script dry-run lock-free and clearly labeled
2026-06-08 17:35:29 -04:00
0075ab9cf6 improvement(platform): remove tour, simplify sidebar/header, drop loading skeletons (#4354)
* improvement(platform): workspace UI/UX overhaul + integrations catalog

Rework the workspace around the AI-workspace model: a Mothership home, a
top-level Skills route, connected-credential and integration-detail pages,
and a polished sidebar/settings surface. Replace the notifications store
with a unified toast system (provider-level dismiss/pause, countdown ring).

Integrations & catalog:
- Add a BlockMeta layer (tags + catalog templates) scoped to catalog-visible
  integrations; every catalog integration carries >=7 grounded templates.
- Rework the taxonomy: each block declares category tools|blocks|triggers.
  3rd-party services are 'tools'; first-party primitives (postgres, mysql,
  knowledge, file, search, stt/tts, image/video generators, thinking, etc.)
  are 'blocks'. Versioned blocks follow the upgrade paradigm (old hidden,
  latest in toolbar/docs).
- Generate integrations.json + tool docs canonically from block configs.

Architecture & cleanup:
- Consolidate block data extraction behind a single latest-version strategy
  (getCanonicalBlocksByCategory; version-consistent getBlockMeta).
- Unify version-suffix handling in @sim/utils/string (stripVersionSuffix /
  isVersionedType, with tests); registry, generate-docs, tools/utils, and
  integrations all route through it.
- Repair latent broken barrels, remove dead code, fix BlockMeta-related type
  errors and 5 broken docs links.

Behavior-preserving for block execution and the toolbar's tool/block listing.

* refactor(platform): remove forms, templates, and creators features

Remove three standalone features and their supporting code:
- Forms: form-deployment pages, API routes, execution path, and docs.
- Templates: the template gallery (landing + workspace) and template APIs.
- Creators: creator-profile routes and contracts.

Add a super-user permissions module (lib/permissions/super-user) and an
organizations API contract; update the audit/db/testing packages, billing,
and the session/theme providers accordingly.

* test(workflows): update archiveWorkflow update count after forms removal

The forms feature was removed, dropping the form-table update from
archiveWorkflow. Update the stale assertion from 8 to 7 tx.update calls.

* upgrade

* improvement(knowledge): polish tag filter dropdowns (#4816)

* improvement(logs): object storage backed tracespans (#4787)

* improvement(logs): obj storage backed tracespans

* fix storage write context

* fix tests

* address comments

* address comments

* chore(db): remove migration 0219 to regenerate after staging merge

Drops the 0219_robust_shard SQL, its snapshot, and the journal entry so the
trace-spans/cost schema migration can be regenerated on top of the latest
staging migration chain (avoids a number collision with staging's migrations).

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(billing): accurate per-member usage via shared ledger helper

Per-member/per-user usage in the org-member routes now adds the usage_log
ledger to the currentPeriodCost baseline (which is no longer incremented),
via a shared getOrgMemberLedgerByUser helper to avoid repeating the
subscription→period→ledger lookup across the admin and member-facing routes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* regen migrations

* update migration

* address comments

* more code cleanup

* incorrect type cast

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(providers): harden OpenAI-compatible providers + add tests (#4796)

* improvement(providers): harden OpenAI-compatible providers + add tests

* fix(vllm): let tool-loop errors propagate instead of returning silent partial success

* fix(litellm): force tool_choice 'none' on final structured-output call

The deferred final call used tool_choice 'auto', so the model could emit
another tool_calls round instead of the structured answer, leaving content
stale. Use 'none' (matching vLLM/Fireworks) on both the streaming and
non-streaming final calls so the model must return the structured response.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(providers/ollama): drop tools from post-tool streaming call

Ollama ignores tool_choice (not in its supported fields), so vLLM/Fireworks'
tool_choice:'none' guard is a no-op here. Omit tools from the final streaming
payload instead so the summarization turn can't emit dropped tool calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(litellm): spread payload into deferred final call so reasoning_effort carries over

The non-streaming deferred finalPayload hand-picked fields and dropped
reasoning_effort (and any future payload field), diverging from the streaming
path which spreads ...payload. Spread payload here too for consistency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(providers/ollama): restore enrichment TSDoc block

Keeps parity with sibling Chat Completions providers (cerebras/mistral/xai).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(fireworks): restore TSDoc on utils helpers

Restore the TSDoc blocks on supportsNativeStructuredOutputs,
createReadableStreamFromOpenAIStream, and checkForForcedToolUsage —
TSDoc is the codebase documentation standard and should not have been
stripped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(litellm): remove inline rationale comments (codebase uses TSDoc)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(providers/ollama): drop orphaned enrichment TSDoc

The block documented a function that now lives in trace-enrichment.ts, so it
documents nothing in this file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* chore(copilot): deprecate mcp server (#4797)

* chore(copilot): deprecate mcp

* update error codes

* deprecate copilot api v1 route

* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza (#4777)

* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza

Add hosted-key support across all credit-consuming Findymail, Prospeo, and Wiza operations so Sim provides the key when a workspace has not brought its own. Register the three BYOK providers, consolidate Wiza's two-step reveal into a single polling wiza_individual_reveal op, and hide the API key field on hosted Sim for hosted operations.

* fix(integrations): harden Wiza reveal polling, soften enrichment getCost guards

Address Greptile + Cursor Bugbot review on #4777: return explicit failures from the Wiza individual_reveal poller instead of throwing (thrown errors were swallowed into a false queued success), short-circuit when the initial reveal is already terminal, tolerate transient 5xx/429 during polling, and return 0 (not throw) from Findymail getCost when the contacts/employees array is absent.

* chore(integrations): biome formatting after wiza merge resolution

* fix(wiza): type isTerminalReveal param structurally for next build typecheck

* feat(enrichments): add Findymail, Prospeo, Wiza to work-email waterfall

* feat(enrichments): add Wiza + Prospeo phone reveal to phone-number waterfall

* feat(enrichments): opportunistic identifiers + LinkedIn URL input across work-email & phone cascades

* fix(tables): reduce column header chevron size and fix sidebar shadow bleed (#4800)

* feat(slack): add install + privacy section to integration landing page (#4799)

* feat(slack): add install + privacy section to integration landing page

Adds a hand-authored, slug-keyed landing-content module (separate from the generated integrations.json so it survives regeneration) and renders an install walkthrough + privacy-policy link on integration pages when present. Also refreshes generated docs (data-enrichment entry, icon mappings, tool mdx).

* fix(landing): render privacy section independently, align CTA analytics label

* docs(landing): clarify the Slack install button is behind sign-in

* refactor(landing): bake integration landing content into generated json via docs-gen

Moves landing content (install walkthrough + privacy) out of a render-time augment and into the generation pipeline: generate-docs reads the pure-data content map and writes landingContent into integrations.json, so the page reads a single source (integration.landingContent). Canonical types live in integrations/data/types.ts.

* improvement(enrichments): align enrichments sidebar with design system (#4801)

* improvement(enrichments): align enrichments sidebar with design system

* fix(enrichments): consistent close button pattern and fix url link hover

* fix(misc): upgrade path change for new better-auth version, billing issue for workflow block agent usage (#4803)

* fix(misc): upgrade path change for new better-auth version, double-billing for workflow block agent usage

* fail loudly if stripe sub id missing

* fix(copilot): seq migration (#4804)

* chore(db): drop redundant idx_webhook_on_workflow_id_block_id index (#4809)

Removed because (workflow_id, block_id) is a left-prefix of idx_webhook_on_workflow_id_block_id_updated_at_desc, which fully covers it. The dropped index was non-unique and enforced no constraint.

* perf(copilot): read chat transcripts from copilot_messages (R+1 cutover) (#4808)

* perf(copilot): read chat transcripts from copilot_messages, not JSONB

Flip user-facing chat reads from the legacy copilot_chats.messages JSONB
array (5.7GB, 99% TOAST) to the normalized copilot_messages table via a
new loadCopilotChatMessages helper ordered by seq NULLS LAST, created_at,
id — the verified canonical order. Both chat-detail getters
(getAccessibleCopilotChat, getAccessibleCopilotChatWithMessages) now drop
the messages column from their metadata select (no more whole-array
detoast on every load) and assemble the transcript from the table after
authorization. This cascades to the copilot + mothership GET endpoints
and to resolveOrCreateChat's conversationHistory (the LLM payload).

The normalize/effective-transcript pipeline is source-agnostic
(copilot_messages.content == a JSONB array element), so transcripts are
byte-identical. Dual-write and the JSONB column stay in place as the
internal-logic source and fallback; removing JSONB writes is a later step.

Prod integrity verified before cutover: 0 messages missing, 0 NULL-seq,
0 dup keys/seq, 0 orphans, order-parity vs JSONB = 0 mismatches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(copilot): cover auth-deny on a found row skips the messages query

Address PR review: exercise the `if (!authorized) return null` contract —
when the chat row exists but authorization fails, the getter returns null
and never issues the copilot_messages read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(tables): right-align run/stop in embedded toolbar; workflow cells format like normal cells (#4806)

* fix(tables): right-align run/stop in the embedded table toolbar

Add a right-aligned `trailing` slot to ResourceOptionsBar and move the embedded
mothership table's run/stop control into it, so Filter + Sort stay left-aligned
and run/stop sits opposite on the right. No-op for the search-bearing consumers
(logs, resource list), which don't pass `trailing`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): workflow-output cells format values like normal cells

Workflow-output columns short-circuited in resolveCellRender and rendered their
value as plain text, so a sim-resource URL / external URL / JSON / date produced
by a workflow never got the chip, favicon link, or typed formatting a normal
cell gets. Factor value formatting into a shared `resolveValueKind` helper used
by both the workflow-value branch and the plain-cell branch; the workflow branch
keeps the typewriter reveal for plain streaming text via a `typewriter` flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): detect resource/URL links on workflow output regardless of column type

Workflow output columns default to `json` (columnTypeForLeaf), so routing their
values through the type-based formatter (a) gated chip/URL promotion behind
`column.type === 'string'` — a URL produced by a json-typed output never became
a chip — and (b) JSON.stringify'd plain string values, adding quotes and losing
the typewriter reveal. Detect links (sim-resource chip / favicon URL) on the
value string directly for workflow outputs, falling back to the plain `value`
kind; plain cells keep the type-based formatting. Addresses Greptile P2 on #4806.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(icons): repair broken integration icon rendering (#4810)

* fix(icons): repair broken integration icon rendering

Two distinct bugs left integration icons broken on the /integrations page
(visible at 32-40px, hidden at the toolbar's 16px):

1. Corrupted SVG paths (Notion, Greptile, Granola, Calendly, Grafana, Bedrock):
   over-minified data dropped elliptical-arc flag digits (e.g. `A1 1 0 5.9 7`
   instead of `A1 1 0 0 0 5.9 7`); Granola's cubic stream was truncated. Browsers
   abort path parsing at the first invalid arc flag, so each rendered as a fragment
   or blank. Replaced with correct path data from canonical sources, preserving each
   icon's existing fill/gradient and bgColor.

2. Invisible glyph (Bright Data): its icon uses fill='currentColor' but bgColor was
   '#FFFFFF', and every surface forces text-white on the glyph - white-on-white.
   Changed bgColor to Bright Data's brand blue (#3d7ffc) so the white glyph reads,
   matching the white-glyph-on-brand-chip convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): restore Calendly dual-tone brand colors

Addresses review feedback: the previous fix replaced the broken Calendly icon
with a monochrome #006BFF path, dropping the cyan #0ae8f0 accent from the
original dual-tone mark. Restored the two-tone logo (blue + cyan) using clean,
valid path data, cropped to a tight square viewBox so it fills the chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge icons, fix Zoom contrast and Quiver chip

- Zoom: glyph was blue-on-blue (#0B5CFF on #2D8CFF chip); switched to
  currentColor so it renders as a white glyph on the blue chip.
- Quiver: chip bgColor #000000 -> #FFFFFF to match the icon's near-white box,
  and enlarged the mark slightly (viewBox crop).
- Enlarged (tightened viewBox, verified no clipping): RevenueCat, Prospeo,
  Granola, Firecrawl, Enrich.so, and the AWS icons (RDS, DynamoDB, SQS,
  CloudFormation, Athena, CloudWatch, SES, Bedrock, S3).
- ZoomInfo left unchanged: it is a full red rounded-square logo that already
  fills its frame, so a crop would clip it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): use Bright Data wordmark on white chip; repair Circleback

- Bright Data: replaced the flame glyph with the official two-tone 'bright data'
  wordmark (provided asset), centered in a symmetric viewBox. Reverted the chip
  bgColor from #3d7ffc to #FFFFFF since the blue wordmark is invisible on a blue
  chip (the wordmark is designed for a light background).
- Circleback: a minifier had rounded the pattern's image scale to scale(0),
  collapsing the embedded logo to zero size (invisible). Restored the correct
  scale (1/280 = 0.00357142857) so the C. mark renders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(docs): sync Quiver block color card to white chip

Reflects the Quiver bgColor change (#000000 -> #FFFFFF) in the docs block info card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge AWS/Cloudflare/Dagster icons, fully white Zoom

- Enlarged (tighter viewBox, render-verified, no clipping): Cloudflare, Dagster,
  and the red AWS icons AWS IAM, Identity Center, Secrets Manager, SES, STS.
  Identity Center was anomalously small (filled ~32% of its frame); the group is
  now sized consistently (~80% fill).
- Zoom: the camera lens triangle was still #0B5CFF (blue-on-blue); switched it to
  currentColor so the whole camera renders white on the blue chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(wiza): consolidate individual reveal into a single operation

Merges the separate Start/Get Individual Reveal operations into one Individual
Reveal operation in the Wiza docs and integrations data (operationCount 5 -> 4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): size remaining AWS icons to match the set (~80% fill)

Bring RDS, DynamoDB, SQS, CloudFormation, Athena, CloudWatch and S3 up to the
same ~80% fill as the AWS IAM/Identity Center/Secrets Manager/SES/STS group, so
all AWS icons are visually consistent. Bedrock left as-is (already ~92% fill).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): use Bright Data flame mark, enlarge ZoomInfo

- Bright Data: the full 'bright data' wordmark was illegible at chip size.
  Replaced with just the flame-'i' brand mark (blue #4280f6 on the white chip),
  centered.
- ZoomInfo: cropped the viewBox toward the white 'Zi' so it's larger; the red
  rounded-square background still fills the chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(icons): enlarge CrowdStrike icon

The falcon mark sat small in its chip because the icon used a wide 768x500
viewBox (letterboxed in the square chip). Switched to a square viewBox centered
on the mark so it fills ~80%, consistent with the other icons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(tables): serialize schema mutations to prevent parallel column clobber (#4812)

* Make workflow description nullable

* fix(tables): serialize schema mutations to prevent parallel column clobber

* fix(tables): load workflow outside schema lock; use DbOrTx for getTableById

* fix(tables): scale idle timeout in updateColumnType to avoid aborting large type changes

* fix(tables): skip stale remap types when workflowId changes concurrently

* fix(tables): scale idle timeout in updateColumnConstraints for large tables

* fix(wait): resume live/draft async waits and preserve cell context on chained waits (#4814)

* Make workflow description nullable

* fix(wait): resume live/draft async waits and preserve cell context on chained waits

* improvement(knowledge): polish tag filter dropdowns

* improvement(knowledge): soften filter section labels

* improvement(knowledge): soften list filter labels

* fix(security): harden SSO domain registration, webhook path isolation, and CSV export (#4813)

* fix(security): harden KB file access, SSO domain registration, webhook path isolation, env secrets, and CSV export

* fix(sso): scope domain conflict query with indexed lower(domain) filter

Address PR review: avoid a full-table scan on every SSO provider
registration by filtering candidate rows in SQL with
lower(domain) = <normalized>, keeping the in-memory ownership check.
Also tighten the normalizeSSODomain TSDoc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: condense env route security comments

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* icons update

* chore(security): tighten inline comments in CSV export and KB file authorization

Condense verbose comment blocks to concise TSDoc/single-line form; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): validate internal serve origin in KB file authorization

Replace the bypassable isInternalFileUrl substring check in resolveInternalKbKey
with an origin allow-list (base URL, internal API base URL, TRUSTED_ORIGINS).
A crafted external host whose path is /api/files/serve/<victim-key> no longer
resolves to the victim key. Relative same-origin URLs are unaffected.

* style(sso): use idiomatic sql lower() comparison for domain conflict query

Match the repo's prevailing `sql`lower(col) = value`` idiom for the
case-insensitive SSO domain conflict lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): align workspace env admin gate with hasWorkspaceAdminAccess

Use the same admin check the secrets UI uses (owner, admin permission, or
org-admin) so owners and org-admins are not wrongly denied their own decrypted
workspace secrets, while read-only members remain restricted to names only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(sso): rely on lower(domain) match for conflict detection, drop dead in-memory recheck

Address PR review: the SQL `lower(domain) = <normalized>` predicate already
excludes rows that the in-memory `normalizeSSODomain(...) === domain` recheck
claimed to catch, making that recheck dead/misleading code. Match on the
canonical lower-cased domain and filter purely by ownership. Malformed legacy
values (wildcards, schemes, ports) never match an email domain at sign-in, so
excluding them is not a gap. Test DB mock now applies the lower() predicate so
the casing-variant case is genuinely exercised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): scope webhook deploy path conflict to active webhooks

findConflictingWebhookPathOwner omitted the isActive filter that the
runtime dispatcher (findAllWebhooksForPath) applies, so an inactive but
non-archived webhook from another workflow (e.g. after undeploy or
failure auto-disable) would permanently block any new deployment on that
path even though it never receives deliveries. Align the guard with the
runtime isActive + archivedAt filter; the earliest-owner runtime check
remains the authoritative cross-tenant protection. Also trims verbose
TSDoc on the webhook path-isolation helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): exclude archived workflows from webhook deploy path conflict

findConflictingWebhookPathOwner now joins workflow and filters
isNull(workflow.archivedAt), matching the runtime dispatcher
(findAllWebhooksForPath). A webhook on an archived workflow can never
receive deliveries at runtime, so it must not block legitimate path reuse
with a 409.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): anchor KB file ownership to earliest document in any state

A KB file's owner is now the earliest document referencing its key regardless of
state (active/archived/deleted/excluded); access is granted only when that owning
document is still active. Closes the residual where an attacker could plant an
active document to claim a file whose original document was archived or deleted.

* updated greptile icon

* revert(security): drop KB file authorization changes

Reverts the knowledge-base file-access work (origin-pinning / owner-pinning /
origin allow-list in verifyKBFileAccess) and its test. The other hardening fixes
(SSO domain registration, webhook path isolation, workspace env secrets, CSV
export) are unchanged. apps/sim/app/api/files/authorization.ts is restored to its
origin/staging baseline.

* fix(sso): treat caller's own user-scoped provider as owned during conflict check

Self-hosters often register SSO user-scoped via the CLI script (no
SSO_ORGANIZATION_ID). If they later enable organizations and reconfigure the
same domain org-scoped through the UI, the conflict check previously treated
their own user-scoped row as another tenant's and returned a misleading 409.
Recognize the caller's own user-scoped provider as owned so that migration is
allowed, while still blocking another user's or another org's domain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* revert(security): remove workspace-env admin gate

Defer to a credential-based access model (separate change). Restores
GET /api/workspaces/[id]/environment to main behavior and removes the test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(security): consolidate webhook path-collision check into one helper

Extract findConflictingWebhookPathOwner to lib/webhooks/utils.server.ts as
the single source of truth for cross-tenant path-collision detection, used by
both webhook creation paths (deploy sync and the manual /api/webhooks route).

This also repairs two latent issues in the manual route's previous inline
check, which queried with limit(1) and only webhook.archivedAt:
- limit(1) inspected one arbitrary row, so a same-workflow row could mask a
  foreign collision (false negative). The shared helper scans all matching
  rows.
- It omitted isActive/workflow.archivedAt, so inactive or archived-workflow
  webhooks (which never receive deliveries) permanently blocked path reuse.
  The helper mirrors the runtime dispatcher's filter.

Same-workflow webhook reuse for upsert is now a separate, explicit lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF (#4818)

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF

* test(security): use real isPrivateOrReservedIP and cover IPv6 edge cases

* improvement(integrations): validate and expand devin, cursor, and greptile (#4820)

* improvement(integrations): validate and expand devin, cursor, and greptile

- devin: fix missing org_id path segment on all session endpoints, add 7 session sub-resource tools (list messages/attachments, get/append/replace tags, archive, terminate), pagination, and is_archived output
- cursor: add get_api_key_info, list_models, list_repositories tools
- greptile: align block and docs
- normalize array outputs to default [] and tighten types

* refactor(cursor): simplify list_repositories v2 array normalization

Collapse the redundant `?? []` + `Array.isArray` double-guard into a
single Array.isArray check, per PR review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(devin): scope session-tag mapping to tag ops and normalize array tag inputs

- Only map sessionTags into the tools tags param for append/replace operations,
  preventing stale sessionTags state from clobbering create_session tags
- Fall back to a wired tags value when sessionTags is empty for tag operations
- Normalize tag inputs (string or wired string[]) via normalizeTags so array
  values from other blocks no longer throw on .split

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cursor): restore base64 file data in legacy download_artifact metadata

The legacy CursorBlock exposes only content + metadata (no v2 file
output), so metadata.data was the only way legacy-block workflows could
access downloaded artifact bytes. Restore the base64 data field and
document it in the outputs/type instead of dropping it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(devin): coerce terminateArchive to archive flag for boolean-wired input

* docs(integrations): regenerate tool docs for new devin and cursor operations

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(search-replace): don't auto-navigate when content edits invalidate the active match (#4819)

* fix(search-replace): don't auto-navigate when content edits invalidate the active match

* fix(search-replace): clear afterReplaceIndexRef on apply failure and zero matches

* fix(search-replace): remove duplicate setActiveSearchTarget(null) on close

* fix(search-replace): move afterReplaceIndexRef write inside handleApply past the guard

* fix(search-replace): auto-navigate when hydration resolves with no prior active match

* chore(search-replace): remove inline comments

* fix(search-replace): revert !activeMatchId guard that caused immediate re-navigation after deselect

* improvement(enrichments): limit company-info to fields both providers return (#4817)

Hunter's company dataset returns null industry/foundedYear for many large companies (verified against the live API for Microsoft, Amazon, Google), so under the first-non-empty-wins cascade those columns appeared inconsistently across rows. Limit company-info outputs to employee count and description — the fields Hunter and PDL both reliably return — so every row is consistent. employeeCount is a string so Hunter's range bucket and PDL's exact count share the column.

* fix(files): don't reject external URLs containing '..' in file parse validation (#4821)

* fix(files): don't reject external URLs containing '..' in file parse validation

The file block's file_fetch operation rejected any external URL whose path
contained '..' (e.g. Slack files-pri slugs with a literal '...') with
'Access denied: path traversal detected'. Traversal checks only apply to
local paths — external http(s) URLs are fetched with SSRF protection
downstream and are never resolved against the filesystem, so they now
short-circuit as valid. Internal /api/files/serve/ URLs keep full traversal
protection.

* test(files): fix external-URL assertion to handle undefined error

* test(files): assert success explicitly in external-URL traversal test

* fix(files): keep traversal protection for https URLs matching internal serve paths

* feat(google-sheets): add row filtering to read with numeric operators (#4822)

* feat(google-sheets): add row filtering to read with numeric operators

Adds client-side row filtering to the Google Sheets read (v2) operation.
Filter the returned rows by a header column using text operators
(contains, not_contains, exact, not_equals, starts_with, ends_with) and
numeric/ordering operators (gt, gte, lt, lte). Filtering lives in a pure,
unit-tested helper (filterSheetRows) and runs over the fetched read range;
an optional `filter` output reports whether the column was found and how
many rows matched.

Also hardens the surrounding tools:
- trim spreadsheetId in write/update/append URL builders (matches read)
- URL-encode the v1 read default range
- expose valueInputOption for the update operation in the block

Backwards compatible: with no filter requested, read output is byte-
identical and the `filter` field is omitted. The filterMatchType union is
widened additively (4 -> 10 values).

* fix(google-sheets): correct filter metadata for missing column and header-only sheets

- matchedRows is now 0 (not totalRows) when the filter column is not found,
  so it no longer contradicts applied=false / columnFound=false
- columnFound now reflects an actual header lookup for empty/header-only
  sheets instead of being hardcoded true
- add tests covering header-only and empty sheets with present/absent columns

* fix(selectors): fetch all pages for paginated dropdown list routes (#4823)

* fix(selectors): fetch all pages for paginated dropdown list routes

Dropdown selectors fetched only the first page of paginated provider
APIs, silently hiding results past page one. Add bounded server-side
draining to the list routes across Microsoft Graph, Google, Notion,
Atlassian, Linear, AWS CloudWatch, and offset/token REST APIs, plus a
shared client-side drain cap in the selector hook. Response shapes,
stored values, and tool execution are unchanged; CloudWatch list tools
still honor a caller-supplied limit. Also fixes the Word file picker
that was searching for .xlsx files.

* fix(selectors): harden JSM and Monday pagination draining

- JSM service-desk/request-type drains advance `start` by the actual row
  count returned (not the fixed page size) and stop on an empty page, so a
  short non-final page can't skip items.
- Monday boards drain now checks `response.ok` per page, surfacing a
  mid-drain HTTP failure instead of treating it as an empty final page and
  returning a partial 200.

* docs(selectors): clarify JSM drain advances start by actual row count

The offset-advancement fix (advance `start` by the rows returned, not the
fixed page size) landed in 7b19788a8; update the TSDoc to match so it no
longer reads as advancing by `limit`.

* fix(selectors): drain fetchPage in direct fetchList callers

Making `fetchList` optional left three direct callers (outside the
useSelectorOptions hook) calling it unguarded, which broke the build's
type check. Route them through a shared `loadAllSelectorOptions` helper
that uses `fetchList` when present and otherwise drains `fetchPage`.
This also prevents a regression: `confluence.spaces` / `knowledge.documents`
now paginate via `fetchPage` only, and these callers (search/replace,
value resolution) would otherwise have silently returned no options.

* chore(selectors): rename MAX_PAGE_PAGES to MAX_NOTION_PAGES for readability

* fix(sso): re-check domain conflict before write and reject IP-address domains (#4825)

* improvement(copilot): make copilot_messages the sole transcript store, remove JSONB dual-write (#4826)

Stop writing/reading the legacy copilot_chats.messages JSONB column now that
reads are cut over to copilot_messages. Make appendCopilotChatMessages the
primary write (throws on failure instead of swallowing), repoint peripheral
readers (workspace VFS, chat cleanup, data drains, fork, superuser import) to
copilot_messages, and persist the assistant turn inside finalizeAssistantTurn's
transaction so it commits atomically with the stream-marker clear. The column
itself is dropped in a follow-up migration after this bakes.

* feat(tables): expand filter operators (not-contains, starts/ends-with, not-in, empty) (#4827)

Add does-not-contain ($ncontains), starts-with ($startsWith), ends-with
($endsWith), not-in-array ($nin, previously executed server-side but unexposed
in the UI), and is-empty/is-not-empty ($empty) filter operators end-to-end —
SQL builder, condition types, query-builder converters/constants, the filter
UI, the Table tools/block descriptions, and docs.

Also fix correctness bugs in the filter builder surfaced by the wider operator
set:
- Same-column AND rules (e.g. age > 18 AND age < 65, or name startsWith 'A'
  AND name endsWith 'Z') silently overwrote each other because the AND group
  was keyed by column name. They now merge into one operator object, which
  also makes Filter -> rules -> Filter round-trip losslessly for multi-operator
  columns.
- $nin values were not split into an array like $in, and textual-match values
  like "123" were numeric-coerced (breaking the ILIKE path).
- A non-boolean $empty operand from the raw API silently inverted the check; it
  now coerces 'true'/'false' strings and otherwise returns a 400.

* improvement(copilot): stop persisting tool-call result outputs in transcripts (#4829)

Opening a Mothership task could take many seconds because a single persisted
assistant message in copilot_messages.content can reach hundreds of MB, almost
entirely inside contentBlocks[].toolCall.result.output (e.g. a get_workflow_logs
or run_workflow result). The DB query is ~2ms; the cost is detoasting that
payload, shipping it to the browser, and parsing it.

These outputs are dead weight on the Sim side: they are never rendered (the
thread shows only tool name/title/status) and never replayed to the model (the
upstream copilot service owns conversation memory). So drop result.output before
it is persisted, keeping result.success/error plus the tool metadata.

- add stripToolResultOutput() in persisted-message.ts
- apply it in messages-store toRow (covers every write path) and in
  loadCopilotChatMessages (existing rows render fast on read)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers (#4830)

* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers

* fix(providers): guard Ollama streaming fast-path with hasActiveTools

Match Together/Baseten/Fireworks: when tools are supplied but all are
filtered out (usageControl 'none'), take the single streaming call instead
of an extra non-streaming round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(providers): filter non-chat model types from Together model list

* refactor(providers): dedupe Ollama Cloud upstream schema

ollamaCloudUpstreamResponseSchema was byte-for-byte identical to
ollamaUpstreamResponseSchema (both /api/tags endpoints return the same
{ models: [{ name }] } shape). Drop the duplicate and reuse the shared schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(knowledge): calendar view sync, deduplicate popover animation classes, type-safe filter cast

* cleanup(knowledge): remove TRIGGER_BORDER_CLASS duplication, inline displayLabel, drop enabledFilterParam alias

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>

* feat(blocks): add BlockMeta to Quiver and Linq; fix invalid block config fields; update skills

Block fixes:
- Add QuiverBlockMeta (tags + 3 templates: icon generator, diagram creator, vectorizer)
- Fix QuiverBlock: remove invalid tags field from BlockConfig, IntegrationType.Design →
  IntegrationType.AI (Design doesn't exist in the enum)
- Fix GreptileBlock: remove invalid tags field from BlockConfig,
  IntegrationType.DeveloperTools → IntegrationType.DevOps
- Fix LinqBlock: remove invalid tags field from BlockConfig (tags belong only in BlockMeta)

Skills:
- add-block: add dedicated BlockMeta section with structure, rules, and registration
  pattern; add BlockMeta checklist items
- add-integration: add BlockMeta to block structure template, add rules clarifying
  that tags must NOT appear on BlockConfig and integrationType must be a valid enum
  value; update registry snippet to include blocksMeta; add checklist items

* fix(integrations): fix category dropdown by defining missing LANDING_INTEGRATIONS_DATA_PATH and regenerating integrations.json

The staging merge introduced landing-content.ts but forgot to define
LANDING_INTEGRATIONS_DATA_PATH in generate-docs.ts, causing the script
to crash before writing integrations.json.

The stale JSON had integrationTypes (plural array) from an older script
version, while the Integration type and workspace UI both read
integrationType (singular string) — so ALL_CATEGORY_SECTIONS bucketed
to undefined and the category filters never appeared in the dropdown.

Fixed by adding the missing path constant and re-running the generator.
integrations.json now has 192 entries with the correct integrationType field.

* fix(sidebar): restore resize handle on all pages

commit 3109104582 wrapped the resize handle in {(isCollapsed ||
isOnWorkflowPage) && ...} and added a useEffect that resets sidebar
width to SIDEBAR_WIDTH.MIN whenever the user navigates away from a
workflow page. Together these made the sidebar non-resizable on Tasks,
Tables, Knowledge Base, and every other non-workflow page.

Restore the staging behavior: always render the resize handle and
remove the effect that forced the width reset on page transitions.

* fix(sidebar): match staging onKeyDown and tabIndex on resize handle

The resize handle was still conditionalizing onKeyDown and tabIndex
on isCollapsed, blocking keyboard accessibility of the separator role
when expanded. Staging always attaches both unconditionally.

onKeyDown={isCollapsed ? handleEdgeKeyDown : undefined} → onKeyDown={handleEdgeKeyDown}
tabIndex={isCollapsed ? 0 : undefined}                 → tabIndex={0}

* feat(integrations): show connected credentials on integration detail page

When navigating to /integrations/google-docs (or any integration), a
Connected section now appears above the templates listing all workspace
credentials tied to that provider. Each row links back to the credential
detail page (/integrations/connected/${id}) for management actions.

Pairs with the earlier change that routes connected items from the
integrations list to the provider detail page instead of directly to
the credential detail page.

* fix(integrations): rename Add in chat to Add to Sim

* fix(skills): rename Add button to Add to Sim

* fix(platform): restore M1/M2/M3 regressions and LazyMotion on landing page

M1 — Invitation guard: re-introduce usePermissionConfig().isInvitationsDisabled
alongside the workspace inviteDisabledReason check. The flag now also
respects NEXT_PUBLIC_DISABLE_INVITATIONS and EE permission-group
disableInvitations, not just billing policy.

M2 — Settings redirects: /settings/integrations and /settings/skills
now server-redirect to /integrations and /skills respectively so old
bookmarks and emails don't silently land on General.

M3 — Starter block search exclusion: restore block.type !== 'starter'
guard in the search store so users cannot add a duplicate Starter block
via the command palette.

LazyMotion: restore LazyMotion + domMax/domAnimation wrappers and m.*
components in landing-preview-panel and landing-preview-home. The
removal was accidental (the full motion bundle was left after an
import cleanup), which caused the entire framer-motion feature set to
load eagerly on the landing page.

* fix(integrations): revert connected list to credential detail; remove settings redirects

* feat(sidebar): restore workspace switcher search with updated styling

Shows a search input in the workspace dropdown when the user has more
than 3 workspaces (WORKSPACE_SEARCH_THRESHOLD). Keyboard navigation:
ArrowDown/Up to move through results, Enter to switch, resets on close.

Styled to match the current branch (border-1/surface-5 tokens, sm text,
11px Search icon) rather than the old staging styles. Highlight state
is wired through chipVariants active prop so it follows the same active
appearance as clicked/hovered items.

* fix(sidebar): clean up workspace search — layout, memo, and effect guard

* fix(sidebar): align workspace rename input selection style with workflow rename

* perf(sidebar): eliminate React re-renders during sidebar drag resize

Previously, every mousemove during resize called setSidebarWidth(), which
both updated the --sidebar-width CSS variable (sync) and set Zustand state
(async). This caused:
  1. A 1-frame transition flash on mousedown — isResizing state had to
     round-trip through React before the is-resizing CSS class was applied,
     so the width transition fired for the first pixel of movement.
  2. A React re-render per pixel dragged — components reading sidebarWidth
     from the store (avatars, usage-indicator) lagged one frame behind the
     container, making the + and ... buttons appear to jump ahead.

New approach:
  - handleMouseDown adds is-resizing directly to the sidebar DOM node before
    any React involvement (synchronous, no frame lag).
  - mousemove writes only to the CSS custom property (zero React renders).
  - mouseup persists the final width to Zustand exactly once.
  - isResizing / setIsResizing state removed from the store and hook — they
    are no longer needed since the class is managed via direct DOM mutation.

* perf(sidebar): add requestAnimationFrame throttle to resize mousemove handler

* fix(sidebar): fix drag-right lag caused by WorkspaceChrome overflow-hidden transition

The sidebar-container's is-resizing class correctly suppressed its own width
transition, but the two wrapper divs in WorkspaceChrome both have
transition-[width]/transition-transform with a 175ms ease. The outer wrapper
also has overflow-hidden, so while the sidebar content was at the correct width
instantly, it was visually clipped by the outer wrapper which was still
animating — causing the + and ... buttons to appear to lag behind the resize
line on drag-right (not on drag-left, since shrinking doesn't clip content).

Fix: add sidebar-shell-outer/sidebar-shell-inner class names to both chrome
wrappers, and suppress their transitions via html.sidebar-resizing rule when
a drag is active. The html.sidebar-resizing class is toggled directly in the
resize hook alongside is-resizing, so it takes effect synchronously on mousedown.

* fix(icons): redesign Download icon to match Upload style; fix Upload/Download confusion

Download icon was missing the tray/shelf line at the bottom that Upload has,
making it look like a plain arrow rather than a matched pair. Updated Download
to use the same viewBox, stroke weight, and three-path structure as Upload
(tray + stem + arrowhead), just pointing down.

Also fix 5 places where Upload (↑) was incorrectly used for download/export
actions:
  - files.tsx: two Download action rows in toolbar and context menu
  - tables/table.tsx: Export CSV toolbar button
  - table-context-menu.tsx: Export CSV context menu item
  - logs.tsx: Export toolbar button
  - landing-preview-logs.tsx: decorative Export button

Import CSV and actual upload actions correctly keep the Upload icon.

* fix(icons): replace Upload with Download on all remaining export/download actions

- panel.tsx: Export workflow dropdown item
- context-menu.tsx: Export in sidebar workflow context menu
- chat.tsx: Export chat button
- output-panel.tsx: Export console CSV button
- terminal.tsx: Export console CSV button
- resource-content.tsx: Export table as CSV + Download file buttons

* fix(icons): fix remaining Upload→Download on download actions in files and logs

- action-bar.tsx: download button in files toolbar
- file-row-context-menu.tsx: Download item in file context menu
- file-download.tsx: both download buttons in log details file viewer

* updated block skills, settings pages, modals, buttons -> chips, blocks missing metadata

* updated skill modal

* improvement(resource-header): refine breadcrumb truncation ux

* improvement(resource): add floating overflow text tooltips

* wire up credits counter

* improvement(resource-header): mute path dropdown title

* refactor(resource-header): share floating-tooltip engine, prune dead overlay tooltips (#4844)

Clean up the breadcrumb truncation feature for reuse and correctness:

- Extract useFloatingTooltip / useIsOverflowing / FloatingTooltip into a shared
  floating-tooltip module. BreadcrumbSegment and FloatingOverflowText now consume
  one implementation instead of duplicating ~150 lines of positioning, velocity,
  overflow-detection, and portal logic.
- Replace the hardcoded terminal-label regex in ResourceHeader with a typed
  `terminal` flag on BreadcrumbItem (set by the document chunk/loading crumbs),
  decoupling the generic header from knowledge-base copy.
- Clear the path-popover close timeout on unmount and reuse the shared
  POPOVER_ANIMATION_CLASSES constant.
- Drop the redundant manual overflow-state writes (fixes a sticky fade mask).
- Revert FloatingOverflowText inside Combobox `overlayContent` back to plain
  truncating spans across files/logs/tables/scheduled-tasks/document: the combobox
  overlay is pointer-events-none, so the tooltip handlers never fired there.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(emcn,resource-header): address PR #4844 review feedback

- useIsOverflowing now uses a callback ref so the ResizeObserver follows the
  element across mount/unmount/reassignment instead of capturing it once at mount.
  Safe for conditionally rendered consumers of the shared hook. (greptile P2)
- Move POPOVER_ANIMATION_CLASSES out of chip-date-picker implementation internals
  into emcn/components/popover/popover-animation.ts, exported from the
  @/components/emcn barrel. Consumers now import from the module boundary.
  (greptile P2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* upgrade table and styling upgrade

* fix schema to include integration

* fix(files): align delete icon with tables view (Trash → Trash2)

Co-Authored-By: waleed <waleed@simstudio.ai>

* fix(mothership): preserve blockType for integration contexts in sent messages

Integration mention chips were missing their provider icons in sent messages
because blockType was dropped when mapping ChatContext to messageContexts.
renderIntegrationTile returns null without blockType, silently hiding the icon.

* fix(mothership): allow 'integration' resource type in chat resources API

The VALID_RESOURCE_TYPES allowlist was missing 'integration', causing a
400 error when adding integrations to the Mothership resource tab — so
they never persisted and disappeared on refresh.

* fix(ui): Add "File" title next to file resource header

* fix(ui): fix resource header columns being bolded

* fix(resource): keep the floating tooltip from jumping on click

Gate the focus-driven show behind :focus-visible so a mouse click (which
focuses the trigger) no longer re-shows the tooltip anchored to the element's
bottom edge. On click the tooltip now hides cleanly instead of jumping down;
keyboard focus still shows it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(sidebar): eliminate unnecessary re-renders in workspace switcher for non-search users

- onMouseEnter: only set highlightedIndex when showSearch is true, preventing
  a state update + re-render on every workspace row hover for users with ≤ 3
  workspaces where the search is never shown
- onOpenChange: only reset workspaceSearch and highlightedIndex when showSearch
  is true, since both values are always already at their defaults for non-search
  users and setting them triggers a pointless re-render during dropdown close
- data-workspace-row-idx: only set when showSearch is true since the scroll
  effect that reads this attribute is already gated on showSearch

* feat(search): context-aware cmd-k results on the integrations page

When cmd-k is opened on the integrations page, show two new result
groups: connected accounts (visible even with empty input) and catalog
integrations (appear once the user types). Selecting an OAuth integration
deep-links to its detail page with ?connect=oauth so the connect modal
auto-opens. Non-OAuth integrations navigate to the plain detail page.

Both groups are gated to the integrations page only and respect the
hideIntegrationsTab permission. The credentials fetch shares the same
React Query cache key as the integrations page itself (no double fetch).

* refactor(emcn): make the floating tooltip the one canonical Tooltip

Replace the Radix-based emcn Tooltip with the cursor-following floating tooltip so
every tooltip in the app uses one consistent style. Built on the shared
floating-tooltip engine (relocated into emcn), not a parallel implementation.

- Move the floating-tooltip engine into emcn/components/tooltip and export it from
  the barrel; re-point its consumers (FloatingOverflowText, resource-header)
- Extend the FloatingTooltip bubble to render arbitrary children (+ role/id for
  a11y) so it can back general tooltips, not just overflow text
- Rebuild emcn Tooltip (Root/Trigger/Content/Provider/Shortcut/Preview) on
  useFloatingTooltip — compound API preserved, ~350 call sites unchanged, legacy
  side/align props accepted and ignored (the tooltip follows the cursor). Removes
  @radix-ui/react-tooltip usage (package kept for a later cleanup; react-slot
  retained for asChild)

Note: general tooltips now show instantly (no hover delay) and follow the cursor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style(emcn): put tooltip text on the design scale (text-caption)

Replace the tooltip's ad-hoc `text-xs` + `leading-[18px]` with the semantic
`text-caption` (12px) font-size token so the text styling is fully on the design
scale and self-documenting, matching how the rest of the system is set up. The
color already used the global `--text-body` token. No visual change (still 12px
with a ~18px line height).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(sidebar): add empty task state and inline task creation

- Show "No tasks yet" in the Tasks section (expanded and collapsed) when the list is empty
- Clicking + now creates a task via the API and navigates directly to it, rather than navigating to home
- Add isCreatingTaskRef guard to prevent double-click from spawning multiple tasks
- Disable + button while creation is pending
- Fall back to home navigation on creation error

* invite, billing, home

* improvement(seats): auto purchase seats on invitations into workspace (#4857)

* improvement(seats): auto purchase seats on invitations into workspace

* improve sampling for seat drift reconciler

* address comments

* feat(knowledge): align connector UI with integrations page styling

- ConnectorTypeCard now matches integration rows: brand-colored rounded-xl tile, ArrowRight, title/subtitle hierarchy
- ConnectorCard icon upgraded from flat surface-4 to branded tile (white icon on brand bg, graceful fallback)
- Connector header badges use chipVariants instead of custom Button classes
- Add-connector search input aligned to integrations style (h-[30px], rounded-lg, border-1)

* fix(icons): trim Folder SVG viewBox to remove right-side whitespace

The folder path only extends to x≈14.33 in a 15-unit viewBox, leaving
~0.5 units of empty space on the right. At 12px rendered size this
produces ~0.4px extra gap (visible as ~1px on retina displays) compared
to solid icons like the workflow color square. Trimming the viewBox to
14.5 units makes the folder fill its chip slot evenly.

* fix(user-input): restore draft text synchronously to preserve contexts on nav

The SSR-safe approach (empty useState + effect restore) created a timing
window where the sync effect in useContextManagement fired with message=''
before the value was set, clearing any restored contexts. Folder and workflow
contexts (not re-added by applyAutoMentions) were lost on every nav-back.

Revert to the staging approach: initialize value synchronously from the
draft store so message is already populated when effects run, matching
the behavior on staging.

* fix(queue): render context chips in queued messages

- Remove plainMentions from queued message rows so context chips render
  with icons, consistent with sent messages
- Fix computeMentionRanges to use '/' prefix for skill contexts (content
  has the slash trigger restored at submit time, not '@')

* fix(mothership): remove integrations from add-resource dropdown

* fix(mothership): comment out integrations from add-resource dropdown

* chore(db): drop form, templates, template_creators, template_stars tables

These tables backed the Forms and Templates platform features which were
intentionally removed from this branch. Clean up the DB schema to match.

* chore(db): add migration metadata for 0224 drop tables

* block icons, sidebar, toolbar

* chore: remove remaining dead code for template-profile feature

- Remove 'template-profile' from SettingsSection union type
- Remove 'template-profile' entry from SECTION_TITLES
- Remove now-redundant template-profile guard in settings sidebar
- Remove commented-out template-profile nav item

* fix(multi-select): preserve anchor on range selection for tasks and folders

After a shift+click range, the anchor (lastSelectedTaskId / lastSelectedFolderId)
was being updated to the end of the range (toId). This caused subsequent
shift+clicks to extend from the wrong point instead of the original click.

Standard behavior: anchor stays at the initial click (fromId) so repeated
shift+clicks always expand/contract relative to where you started.

* feat(emcn): add SearchInput component and unify search bars platform-wide

- Add SearchInput to emcn: 30px chip-family filled search input matching the
  integrations page pattern (border-1, surface-5, leading Search icon)
- Migrate all 22 search bars across settings, EE pages, and integrations to
  SearchInput (only layout classes allowed at callsites)
- Rename Sim Keys -> Sim API Keys in nav/title; page copy now says API key
- Remove components/ui input, label, and verified-badge; migrate consumers
  to emcn equivalents or raw inputs (table cell editor, wand prompt bar)
- Delete dead EE skeleton files (data-drains, data-retention)
- General settings: Home Page chip moves to header left as navigation

* fix(files,tables): restore new-file editor autofocus and CSV import error toasts

Both were dropped on the staging line and regressed vs production (main):

- Files: the new-file editor autofocus chain (files.tsx -> file-viewer ->
  text-editor) was stripped by the react-doctor dead-code pass in #4544,
  which misread the prop-drilled `autoFocus` (consumed by an imperative
  `editor.focus()` effect) as unused. Restored the prop through all three
  layers and the one-shot focus effect so creating a new file focuses the
  editor immediately.
- Tables: CSV import failures were silently logged with no user feedback.
  Restored the per-file and generic `toast.error` surfacing.

* feat(home): score suggested actions by workspace signals

- Derive the suggestion pool from the curated block template catalog
  (1,343 prompts across 172 blocks) instead of 15 hardcoded entries
- Fix inverted relevance: prompts for connected providers are now boosted
  4x (instantly runnable) instead of excluded; unconnected discounted 0.4x
- Weight by featured (3x), popular category (1.5x), and resource gaps
  (no tables -> boost table starters; has KBs -> dampen KB-creation prompts)
- Weighted sampling without replacement, max one suggestion per block
- Connect rows weighted by catalog template count; 2 for fresh workspaces,
  1 once something is connected
- Key the catalog map by both versioned and base block types so gmail_v2
  templates resolve (gmail, github, notion, linear were silently dropped)
- Replace derive-in-effect state with a useMemo keyed by a shuffle nonce
- Add suggested_action_clicked / suggested_actions_shuffled /
  suggested_actions_toggled PostHog events

* billing, teammates

* improvement(credentials): credentials invites, secrets tab wiring up (#4874)

* improvement(credentials): move away from invite notion

* wire up secrets ui/ux

* address comments

* get consistent styling by removing emcninput + text area

* styling consistency

* remove fallback

* address comment:

* refactor(ui): migrate settings & workspace UI to chip design system

Migrate modals to ChipModal (showDivider, hint, resizable, size, leading,
ChipModalTabs), standardize Chip variants, add ChipCombobox wrapper, and apply
chip inputs/dropdowns across settings, knowledge, logs, tables, inbox, EE tabs.
Render ChipModalTabs as a ChipSwitch segmented control. Align /settings/secrets
detail with /integrations, refresh whitelabeling, and restore file-editor
autofocus and CSV import error toasts.

* fix(mothership): restore integrations to useAvailableResources for @ mention

Integrations were fully removed from useAvailableResources which broke
the @ mention menu since user-input shares the same hook. Now integrations
are always included in the hook but excluded at the AddResourceDropdown
component level, keeping them out of the sidebar + menu while remaining
available for @ mention autocomplete.

* refactor(settings): chip design-system consistency pass across all tabs

Extract a shared chip-field shell (CHIP_FIELD_SHELL/CHIP_FIELD_INPUT) mirroring
Input variant='chip' and route secrets, credential detail, and integrations
credential detail through it (30px height, font-medium, focus ring). Add a
Discard action to the secrets header when dirty. Group BYOK providers into
Models/Search & web/Enrichment sections and align its tiles to the integrations
tile.

Normalize list-row typography to text-[14px]/text-[12px] and icon tiles to
rounded-xl + border across api-keys, copilot, custom-tools, mcp,
workflow-mcp-servers, credential-sets, and access-control. Tone the secrets
Details chip and per-row affordances to ghost. Fix token correctness: raw
tailwind colors to design tokens (data-drains), missing chip variant on the
Snowflake role input, ColorInput chip-field reuse (whitelabeling), error token
and icon sizes (workflow-mcp-servers), border token (mcp), no-results sizing
(secrets), row chrome (recently-deleted), hover token and Button to Chip
(access-control), and deduped textarea chrome (sso).

* feat(settings): unify filter dropdowns on ChipSelect (integrations style)

Add a ChipSelect emcn component — a filled chip trigger + chevron opening a
DropdownMenu, matching the integrations category filter — supporting single
select, multi-select (checkbox rows), grouped options, and optional in-menu
search. Migrate every settings/EE filter dropdown off ChipCombobox to it:
audit-logs (resource-type multi + time-range), data-retention, data-drains,
general, admin (grouped tool picker), inbox status filter, and the workflow
MCP-server pickers. The SSO provider-id field stays an editable combobox since
it accepts free-text slugs.

Also fix audit findings: chip the MCP client-secret input, normalize an MCP
error-text size, and drop now-dead destination-icon code in data-drains.

* fix(mentions): require explicit @ for integration mentions; decorate sent messages robustly

- Bare integration names in prose (Monday, Notion, Clay) are no longer
  auto-converted to mentions or chipped — mention treatment is strictly
  opt-in via a token-starting @ (fixes the scunthorpe problem)
- @-prefixed mentions still canonicalize casing (@slack -> @Slack) on both
  the keystroke fast-path and bulk paths (paste, template, draft, STT)
- Sent/queued messages now self-sufficiently decorate @IntegrationName
  tokens via a text scan, covering messages sent before the input pass
  ran or authored outside the chat input
- Integration contexts missing a resolvable blockType (messages persisted
  before blockType was saved) are backfilled by label lookup so their
  mention pills render the brand icon again

* refactor(settings): section the API keys page like secrets

Wrap Workspace, Personal, and the allow-personal-keys toggle in SettingsSection
(muted label + divider) instead of bare bold headers, matching the secrets and
BYOK pages.

* fix(settings): ChipSelect renders above modals + full-width form mode

Raise the ChipSelect menu to --z-popover so it layers above modal surfaces
(--z-modal) instead of opening behind them. Add a fullWidth prop that stretches
the trigger and right-aligns the chevron for form-field use, and apply it to the
workflow MCP-server pickers.

* fix(emcn): ChipSelect uses the emcn flat chevron, not lucide's square one

The lucide ChevronDown is square; rendering it at the chip's 9x7 footprint
stretched it. Switch to the custom emcn ChevronDown (built for that wide aspect),
matching the integrations filter and ChipDropdown.

* fix(emcn): ChipSelect trigger hugs its content (w-fit)

In a stacked form layout the trigger was stretched by align-items: stretch,
leaving an empty gap to the right of the value. Add w-fit so the chip sizes to
its content (a compact pill) everywhere; fullWidth form selects are unaffected.

* fix(emcn): ChipSelect uses a square lucide chevron

Revert to lucide's ChevronDown sized square (size-[14px]) so it renders crisp,
matching the standard select chevron used by Combobox.

* renamed tasks to chats

* rename and file change

* improvement(billing): wire up billing, org, teammates tabs + remove deprecated subscription tab (#4887)

* improvement(billing): wire up billing, org, teammates tabs + remove depr subscription tab

* pass exec timeout to tool routes

* reuse helper

* address comments

* address disable comment

* chore(db): remove migration 0224 to regenerate on top of staging

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix type errors and regen migration?

* chore(db): drop branch migration 0226 ahead of staging merge; will regenerate

* chore(db): regenerate migration 0226 after staging merge

* externalize before compaction in fallback'

* fix save/discard chips to be consistent

* fix(ui): remove smodal tabs in favor of chip modal tabs

* fix(ui): skip auto-scrolling on mouse highlight of workspace

* fix(platform): restore settings redirects, forgot-password Enter submit, and tag tooltip visibility

- Re-add SETTINGS_REDIRECTS so /settings/integrations and /settings/skills
  deep links redirect to their top-level routes instead of rendering an
  empty settings panel (accidentally removed in 86da193cc3 one minute
  after cca5054cf6 added it)
- Add opt-in onSubmit to ChipModalField input/email variants and wire it
  in the forgot-password modal so Enter submits again (lost in the
  ChipModal conversion)
- Knowledge tag tooltip: drop the max-h/overflow-y-auto clamp that the
  pointer-events-none floating tooltip made unreachable; truncate each
  tag row instead so all tags stay visible with bounded height

* chore(db): remove migration 0226_third_spot before staging merge

* chore(db): regenerate migration as 0227 after staging merge

* feat(telemetry): add posthog + audit coverage for new platform actions

Audit log (compliance/permission-relevant only):
- org_seat.provisioned — seat auto-purchased when an invite acceptance
  grows the org (actor = accepting user, includes seat delta)
- org_plan.converted — Pro→Team conversion triggered by invite acceptance
- org_seat.drift_reconciled — hourly cron healed a drifted seat count
- credential_member.added/removed/role_changed — credential sharing
  surface was previously fully unaudited
- table.created — parity with existing table.updated/deleted
- skill updates now record skill.updated instead of mislabeled
  skill.created

PostHog:
- seats_provisioned, credential_shared/unshared,
  environment_updated/deleted (key counts only, never names/values)
- table_import_started/completed — background CSV imports previously had
  zero failure observability
- table_exported, file_downloaded, skill_updated
- credential_connected now fires for OAuth completions (draft-hooks),
  credential_deleted for OAuth disconnects; previously only manual
  credentials were tracked
- table_workflow_run gains deployment_mode (live/deployed/mixed)

Also: logger.warn on all new credential-admin 403 denials (members +
environment routes); invite-created audit enriched with
enforcedFixedSeats/plan.

Deliberately excluded as noise: per-hour dead-letter audit rows (would
re-record the same stuck event every cron run) and a duplicate
system-actor org_plan.converted in the Stripe outbox handler.

* feat(home): fill textarea on suggested prompt click instead of sending

Clicking a prompt action in the Suggested Actions panel now populates
the Mothership user-input textarea (via applyAutoMentions) and focuses
it with the caret at end, rather than immediately submitting. The user
can review, edit, and send manually.

* fix(templates): name owning integration in featured block template prompts

Four featured prompts omitted their owning integration name, making them
unbranded and disconnected from their title. Each prompt now explicitly
names GitHub or Google Sheets so mentionifyIntegrations renders the chip
and the copy reads as a self-contained agent-building instruction.

* fix(templates): rewrite fragment prompts so each names its integration and reads as a complete instruction

Featured and non-featured block template prompts that either omitted
the owning integration's canonical name or were phrased as marketing
fragments rather than natural user instructions have been rewritten.
Each updated prompt now starts with an imperative verb ("Build a
workflow that…"), names the owning integration explicitly so the
@-mention chip renders correctly, and aligns with the entry's title.

Files changed: salesforce, hubspot (×2), github, slack, airtable,
firecrawl, iam.

* fix(templates): name integration in remaining block template prompts

- google_docs.ts: replace 'Google Doc' with 'Google Docs document' in 4 prompts; update title 'Meeting notes to Google Doc' to 'Meeting notes to Google Docs'
- google_sheets.ts: replace 'Google Sheet' with 'Google Sheets spreadsheet/Google Sheets' in 3 non-featured prompts
- slack.ts: replace 'Google Doc' with 'Google Docs document' in 'Daily standup summary'
- stripe.ts: replace 'Google Sheet'/'Slacks' with 'Google Sheets'/'Slack' in 'Weekly metrics report'
- reddit.ts: add 'Reddit' to 3 prompts that only referenced subreddits
- notion.ts: rewrite featured prompt to start with a verb and name Notion
- jira.ts: rewrite featured marketing-fragment prompt to start with a verb
- linear.ts: rewrite featured marketing-fragment prompt to start with a verb
- gmail.ts: rewrite featured marketing-fragment prompt to start with a verb and name Gmail

* fix(icons): convert monochrome dark brand icons to currentColor for dark mode

LinkupIcon, InfisicalIcon, IntercomIcon, LumaIcon, GranolaIcon, OnePasswordIcon,
and RailwayIcon were hardcoded to black/near-black fills/strokes, making them
invisible in bare DARK mode. Convert all to currentColor so they follow the
theme-aware text color.

Add iconColor: '#286efa' to IntercomBlock (Intercom brand blue is a confident
mid-tone, safe on both themes).

Two-tone icons (StagehandIcon, AgentPhoneIcon, QuiverIcon) are left unchanged
because their white details are structural — converting to single-tone would
destroy logo legibility.

* removed color, user input, sidebar, suggested actions, chips/emcn

* removed color migration

* improve(blocks): audit block catalog metadata for accuracy and fill gaps

- Fix template prompts that claimed capabilities blocks don't have
  (fabricated triggers, non-existent tools) across ~98 integrations
- Normalize integration tags to family conventions and valid union values
- Align alsoIntegrations and modules with template prompt content
- Add BlockMeta for circleback, imap, and rss trigger integrations
- Add templates to clickhouse and greptile metas
- Remove duplicate PageSpeed deploy-gate template

* chore(telemetry): drop org_seat.drift_reconciled audit

System self-heal bookkeeping doesn't belong in the user-facing audit
trail — membership and seat-purchase changes are already audited, and
the cron's logger output covers ops visibility.

* chore(db): consolidate branch migrations into single 0227

* fix(emcn): make ChipModal scroll internally when content exceeds viewport

The Modal→ChipModal migration dropped the old ModalBody scroll container:
ChipModal renders ModalContent bare (no overflow-hidden) and its wrappers
had no min-h-0 chain, so tall modals (e.g. New data drain with an S3
destination) overflowed max-h-[84vh] off-screen with no way to scroll.

Complete the flex min-h-0 chain through ChipModal's frame and give
ChipModalBody flex-1 min-h-0 overflow-y-auto — header/footer stay pinned,
body scrolls only when constrained. Short modals are unaffected (max-h
caps, it doesn't stretch), and dropdowns inside the body are Radix-portaled
so the new scroll container cannot clip them. Five modals that had locally
patched this with max-h-[Nvh] overrides keep working unchanged.

* fix(emcn): don't close modal when dismissing a dropdown via outside click

Radix dispatches pointer-down-outside to every open dismissable layer at
once, so clicking outside an open dropdown/select inside a modal closed
both the dropdown and the modal in one jarring step. ModalContent now
prevents its own dismissal while a portaled popper layer is open — the
first outside click closes just the popper, the next one closes the
modal.

* docs(skills): de-duplicate and correct agent docs; canonical styling tokens; mirror sim-sandbox rule

* docs(skills): broaden boundary-raw-fetch scope note, sync cursor rule mirrors

* fix(emcn): harden modal popper guard and exempt caret-anchored dropdowns from body scroll

Adversarial review follow-ups to the ChipModal scroll + dismissal fixes:

- Popper guard now requires data-state="open" inside the popper wrapper,
  so a dropdown that is merely animating closed no longer swallows the
  next outside click on the modal (DropdownMenu has an exit animation
  that keeps its wrapper mounted briefly)
- Port the same guard to SModalContent for consistency
- custom-tool-modal: opt its body out of the chrome scroll container
  (flex-none + overflow-visible); the caret-anchored EnvVar/Tag
  autocomplete dropdowns are absolute-positioned inside the body and
  must spill past its bounds rather than clip against a scroll boundary

* refactor(billing): drop unnecessary useCallback wrappers from event handlers

* fix(data-drains): complete chip migration of destination forms and fill missing placeholders

Finishes the in-flight FormField → ChipModalField conversion for all
destination form specs and adds the placeholders that several inputs
never had (S3 bucket/region/access keys, Azure account key, Datadog API
key, webhook signing secret/bearer token) — the cause of the New Data
Drain modal showing placeholder-less inputs inconsistently.

* fix(emcn): broaden modal popper guard to onInteractOutside

Covers the focusOutside dismissal path too: when a popper's focus scope
unwinds on close, the transient focus shift could still dismiss the
modal (and simultaneous body pointer-events lock teardown could freeze
the page). Same data-state="open" scoping as the pointer guard.

* fix(emcn): harden modal outside interactions

* update audit mock

---------

Co-authored-by: andres <k62hc5kjst@privaterelay.appleid.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: waleed <waleed@simstudio.ai>
2026-06-06 11:39:48 -07:00
Theodore Li 24a6086d95 feat(tables): fractional order keys for O(log n) row insert/delete (flag-gated, default off) (#4890)
* feat(tables): add order_key column, fractional-indexing util, and ordering flag (off)

* feat(tables): write order_key on insert, flag-gate delete reindex + query ordering, add backfill

Flag off (default) = identical behavior. Single-insert assigns a fractional
order_key; queryRows orders by order_key when the flag is on; deletes skip the
O(N) reindex when on. Per-table-atomic backfill script populates existing rows.

* feat(tables): write order_key on all insert paths (batch, upsert, replace, import, create, copilot)

Completes the always-write-keys prerequisite: every row insert now assigns a
fractional order_key consistent with position order, so the flag can be flipped
safely after backfill. Flag off (default) still = identical behavior.

* feat(tables): insert-by-neighbor-id + orderKey on wire + client order-by-key

Inserts express intent as afterRowId/beforeRowId (O(1) key mint via the
(table_id,order_key,id) index); orderKey is returned on every row; client
reconcile/undo place by orderKey (no neighbor bump) with position fallback.
Flag off = unchanged. 205 table tests pass.

* feat(tables): resolve position-based inserts by key ordinal under the flag

Position-based callers (mothership tool, v1 API, undo fallback, transient old
clients) resolve their insert neighbor by order_key ordinal (OFFSET) when the
flag is on — positions are gappy then, so WHERE position=N would miss. Flag off
keeps the indexed position lookup. The mothership tool itself is unchanged.

* test(tables): flag-on coverage — delete skips reindex, insert mints key + no shift

* fix lint

* chore(db): regenerate order_key migration with default drizzle name

* fix(tables): address review — guard neighbor insert + mutual-exclusion + safe reconcile

- resolveInsertByNeighbor throws when the anchor row is missing (was silently
  inserting at the front) and when its order_key is null under the flag.
- insert contract: afterRowId/beforeRowId are mutually exclusive (refine).
- reconcileCreatedRow only key-sorts when every cached row is keyed, so mid-
  backfill un-keyed rows aren't yanked to the front.

* fix(kb): restore non-null guard in storage-key filter (unsafe-lint regression)

* refactor(tables): extract maxOrderKey + thread import append key

- Extract maxOrderKey(executor, tableId) helper; replaces three identical
  max(order_key) selects (single/batch insert append + import).
- Import: read the append anchor once up front and thread each batch's last
  key forward (nextImportStartOrderKey + afterOrderKey) instead of re-scanning
  max(order_key) per batch over a growing table — one scan per import, not
  one per 1k-row batch.

* fix(tables): keep insert body base omittable for v1 contract

The afterRowId/beforeRowId mutual-exclusion .refine() turned the schema into a
ZodEffects, which Zod forbids .omit() on — v1's insertTableRowBodySchema.omit({
position }) threw at module load (runtime-only; tsc misses it). Split the plain
object base out, apply the shared refine on top, and have v1 omit from the base
then re-apply it.

* fix(tables): chunk backfill order-key writes

A single UPDATE … FROM (VALUES …) over a whole large table overflows the JS call
stack while drizzle assembles the VALUES list (and would blow past Postgres's
65535 bound-param limit at ~32k rows) — large tables failed with 'Maximum call
stack size exceeded'. Write in 1000-row chunks inside the same per-table
transaction so keying stays atomic.

* fix(tables): emit orderKey in insert responses

The single-row and batch insert handlers dropped orderKey from the JSON
response even though the service returns it, so reconcileCreatedRow always fell
back to position-sorting and could place neighbor inserts wrong under the
fractional-ordering flag. Serialize orderKey alongside position.

* fix(tables): restore by orderKey, not position, under fractional flag

A saved position is the gappy column value, but under the flag insert reads
position as a visual rank (OFFSET) — so position-based restore misplaces rows.

- create-row redo now goes through the batch path carrying the saved orderKey
  (the single-insert API has no orderKey field); drop the now-unused single
  create mutation.
- resolveBatchInsertOrderKeys appends under the flag instead of feeding gappy
  positions to resolveInsertOrderKey; positions remain the flag-off path.

* perf(tables): backfill writes 5000 rows/chunk (was 1000)

5x fewer round-trips per table; ~10k bound params stays well under Postgres's
65535 ceiling and far below the single-statement size that overflows the stack.

* fix(tables): drop rowNumber from table trigger payload

position is gappy under the fractional-ordering flag, so rowNumber (= row.position)
no longer reflects a contiguous visual rank. Rather than compute-on-read, remove
it from the trigger payload, output schema, and column-execution input.

Also pin isTablesFractionalOrderingEnabled=false in update-row.test.ts so its
flag-off position-shift assertions are deterministic regardless of local env.

* chore(db): format generated 0226 migration metadata

biome check . flagged the drizzle-generated _journal.json and 0226_snapshot.json;
apply the formatter so packages/db lint:check passes in CI.

* docs(triggers): drop rowNumber from table trigger outputs

rowNumber was removed from the table trigger payload; remove it from the
documented output fields to match.

* test(tables): remove flag-on fractional-ordering unit suite

Flag-on behavior is covered by manual large-table verification; the heavily-
mocked DB-chain suite added little signal.
2026-06-05 17:18:33 -04:00
Waleed 5505076222 chore(db): drop legacy copilot_chats.messages JSONB column (#4886)
Reads and writes are fully cut over to the normalized copilot_messages table
(verified in production: no writes to the column in 24h, recently-active chats
have empty JSONB while copilot_messages holds the transcript). Drop the dead
column via drizzle migration 0225 and re-type CopilotChatDetailRow.messages as
an assembled (non-column) field.

Deploy notes: reconcile any chats where the JSONB still leads copilot_messages
before applying, and pg_repack copilot_chats afterward to reclaim the ~5.7GB
TOAST storage (DROP COLUMN is metadata-only).
2026-06-04 14:39:15 -07:00
Theodore LiandClaude Opus 4.8 3518b999af feat(tables): background import for large CSVs with live progress (#4861)
* feat(tables): background import for large CSVs with live progress

* fix(tables): address review — import heartbeat, overlap guard, column/empty validation

* fix(tables): guard sync import overlap, scope fileKey to workspace, delete-on-replace after download

* fix(tables): stream large CSV imports from storage instead of buffering the whole file

* test(tables): fix async-import route tests for workspace-scoped fileKey + name uniquification

* fix(tables): append imports start after existing rows; reconcile missed import failures in the tray

* fix(tables): delete the uploaded CSV from storage after the import finishes

* fix(tables): validate replace before deleting rows; ignore stale replayed import events by importId

* fix(tables): bind import worker to its importId (no stale-worker clobber/overlap) and destroy storage stream on failure

* feat(tables): byte-based import progress, cancel support, and a start toast that opens the import view

* fix(tables): don't emit ready after cancel; honor cancel during the upload phase

* improvement(tables): use a stop (square) icon for canceling an active import

* fix(tables): make markTableImporting an atomic claim to close the concurrent-import TOCTOU race

* improvement(tables): preview CSV import from a slice, drop client row-count warning

The import dialog parsed the entire file in the browser to show an exact row
count and a row-limit warning. That holds the whole file in memory, blocks the
main thread, and hits V8's ~512MB string ceiling — so the dialog capped the
effective import size well below what the streaming importer handles.

Parse only the first 512KB (headers + sample for the mapping); drop the exact
count and the "would exceed the row limit by N" gate. The DB row-count trigger
already enforces max_rows server-side, so an over-limit import fails fast during
the run with a clear message instead of being blocked by an expensive parse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): gate import ownership every batch and stop canceled imports reappearing

- Worker checked run ownership only at the progress cadence (~every 5k rows), so
  a canceled/superseded import could insert several more batches (incl. the final
  partial batch) before stopping. Move the updateImportProgress ownership gate to
  the top of every flush — a run that lost the table stops within one batch.
- A list/dialog import canceled mid-upload left the server row `importing` until
  the in-flight server cancel landed; hydration re-seeded it from useTablesList,
  so the dismissed import flickered back. Flag the real table id canceled on the
  mid-upload cancel path, skip re-seeding flagged tables in hydration, and clear
  the flag once the server import is terminal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(tables): drive import tray by polling derived from server, not SSE

Import progress no longer holds an SSE connection per importing table. The tray
now derives its importing rows live from the table list (React Query), polled
only while an import is in flight; the table detail page keeps its own
cell-state SSE for grid refresh.

- store holds only client-only state now: optimistic uploads, which terminal
  completions to surface this session, canceled ids, menu open — no copied
  importStatus/rowsProcessed.
- useWorkspaceImports is the single source: polls via a data-predicate
  refetchInterval, derives rows, and fires completion toasts on the
  importing -> terminal transition.
- kickoff handlers use startUpload/setUploadPercent/endUpload; the invalidated
  list refetch surfaces the server row and polling takes over.
- removes use-hydrate-import-tray + use-import-progress-tracker (folded in).
- trims over-verbose comments across the import paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): ignore superseded-run import events in the detail SSE cache

applyImport applied every replayed import payload to the detail cache. The SSE
buffer can replay a prior import's terminal event for the same table, stomping a
newer in-flight import's UI. Lock to the active run's importId (and ignore a
replayed terminal before the id is known), matching the guard the header tracker
used to have.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): close sync-import TOCTOU by claiming the atomic import gate

The sync import route checked importStatus from a checkAccess snapshot, then
parsed/validated/wrote seconds later without taking the atomic claim. A
concurrent async kickoff (markTableImporting) could slip into that window and
both writers would run together — for replace mode, two delete+insert passes
leave the table indeterminate.

Claim the same atomic gate (markTableImporting) right before the write and
release it in the finally (before the response returns, so a client refetch
never sees the transient status). A row-level FOR UPDATE was avoided on purpose:
it would invert lock order against the position advisory lock / row-count
trigger and risk a deadlock — markTableImporting is the established gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(multipart): keep abort wired after resolve so a mid-upload disconnect tears down the stream

readMultipart resolves on the file-part header and hands the caller an un-drained
stream, but settle() ran cleanup() and detached the abort listener on that path
too. A client disconnect mid-upload then destroyed nothing — busboy never saw EOF,
the file stream stalled, and the route's `for await` held a request slot until
maxDuration (300s). Re-arm an abort handler scoped to the file stream on resolve,
detached when the stream closes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 21:26:06 -04:00
Waleed 3f3efc98c3 chore(auth): remove deprecated OAuth MCP provider plugin and backing tables (#4847) 2026-06-02 10:46:20 -07:00
Vikhyath Mondreti 9bed841173 improvement(kbs): ownership bindings (#4833)
* improvement(kbs): ownership association

* address comments

* more comments
2026-06-01 15:46:41 -07:00
Waleed 75d98739eb chore(db): drop redundant idx_webhook_on_workflow_id_block_id index (#4809)
Removed because (workflow_id, block_id) is a left-prefix of idx_webhook_on_workflow_id_block_id_updated_at_desc, which fully covers it. The dropped index was non-unique and enforced no constraint.
2026-05-29 20:31:37 -07:00
Vikhyath Mondreti 15ca66f112 fix(copilot): seq migration (#4804) 2026-05-29 18:50:51 -07:00
Vikhyath Mondreti 4b0dab4682 chore(copilot): deprecate mcp server (#4797)
* chore(copilot): deprecate mcp

* update error codes

* deprecate copilot api v1 route
2026-05-29 14:34:15 -07:00
Vikhyath MondretiandCursor 49673055be improvement(logs): object storage backed tracespans (#4787)
* improvement(logs): obj storage backed tracespans

* fix storage write context

* fix tests

* address comments

* address comments

* chore(db): remove migration 0219 to regenerate after staging merge

Drops the 0219_robust_shard SQL, its snapshot, and the journal entry so the
trace-spans/cost schema migration can be regenerated on top of the latest
staging migration chain (avoids a number collision with staging's migrations).

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(billing): accurate per-member usage via shared ledger helper

Per-member/per-user usage in the org-member routes now adds the usage_log
ledger to the currentPeriodCost baseline (which is no longer incremented),
via a shared getOrgMemberLedgerByUser helper to avoid repeating the
subscription→period→ledger lookup across the admin and member-facing routes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* regen migrations

* update migration

* address comments

* more code cleanup

* incorrect type cast

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 13:47:32 -07:00
WaleedandClaude Opus 4.8 fd77bb4069 feat(copilot): add seq ordinal to copilot_messages for order-preserving reads (#4791)
copilot_messages had no column preserving message order: created_at (set
from each message's timestamp) ties at millisecond granularity in 58% of
chats, and some chats have out-of-order timestamps within their array. The
only other tiebreaker, id, is a random UUID — so ORDER BY created_at, id
renders same-timestamp user/assistant pairs swapped. This blocks the R+1
read cutover.

Add an integer seq = the message's 0-based index within the chat's JSONB
array (ground-truth order), backfilled inline in migration 0219 (no script
for self-hosters or us). Reads will use ORDER BY seq NULLS LAST, created_at,
id at cutover; reads still come from JSONB after this PR.

Design:
- seq is a tiebreaker, not the sole sort key (concurrent-append/NULL safety).
- Nullable now; defer NOT NULL so rolling-deploy old pods don't fail inserts.
- replace (update-messages snapshot) overwrites seq = array index
  (re-densifies after a mid-conversation delete); append preserves existing
  seq via COALESCE and assigns base+idx from a single MAX(seq) read (never
  MAX+i in SQL — multi-row batches would collide). The non-atomic
  read-then-insert window is documented and bounded by the read tiebreak +
  snapshot re-densify.
- Dedupe message ids before insert (87 prod chats carry dup ids; a repeated
  id in one INSERT...ON CONFLICT would otherwise throw).
- Backfill picks first-occurrence per (chat,id), gap-free via ROW_NUMBER;
  validated on staging data (0-based, contiguous, 0 bad ranges).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 11:50:44 -07:00
Vikhyath MondretiandCursor 53eaa600af improvement(billing): migrate hot path writes away from user_stats (#4768)
* improvement(billing): migrate hot path writes away from user_stats

* fix period start and end sot

* address comments

* Remove stale billing migration

Co-authored-by: Cursor <cursoragent@cursor.com>

* regen migrations

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 13:54:27 -07:00
WaleedandClaude Opus 4.7 ddc47eb221 feat(copilot): add copilot_messages table with dual-write rollout (#4726)
Splits copilot chat messages out of the copilot_chats.messages JSONB
column into a dedicated copilot_messages table. JSONB stays canonical
during R+0 — every write path dual-writes to the new table best-effort
(try/catch + log warn, never throws).

Migration 0217 creates the table + indexes and inline-backfills history
from JSONB so OSS self-hosters don't need to run a separate script.

Write paths covered:
- post.ts (user message append)
- terminal-state.ts (assistant turn finalize)
- update-messages/route.ts (snapshot replace)
- inbox/executor.ts (background turn)
- fork/route.ts (chat clone)
- superuser/import-workflow/route.ts (chat import)

Each call threads chatModel + streamId where relevant; ON CONFLICT DO
UPDATE preserves existing stream_id / model via COALESCE.

For pre-R+1 reconciliation, run:
  bun apps/sim/scripts/copilot-messages-reconcile.ts [--since='7 days']

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 12:16:25 -07:00
Waleed 34c47f7e93 chore(auth): upgrade better-auth 1.3.12 → 1.6.11 (#4766)
* chore(auth): upgrade better-auth 1.3.12 → 1.6.11

* chore(auth): address Greptile review — broaden change-email type + migration newline

* fix(auth): correct oneTimeToken expiresIn unit (minutes, not seconds)

Better-auth's oneTimeToken expiresIn is in minutes (multiplied by 60_000ms
internally). Sim's existing 24*60*60 evaluated to ~60 days of token
lifetime instead of the intended 24 hours. Tokens are one-time-use and
typically consumed within seconds of generation (Socket.IO handshake),
so this tightens an unused security window without affecting UX.
2026-05-28 11:25:28 -07:00
Vikhyath MondretiandCursor 81bcdf255a improvement(schedules): retries, concurrency limits (#4755)
* improvement(schedu
les): retries, concurrency limits

* improvement(schedules): remove stale generated migration

Drop the pre-merge generated 0213 migration so it can be regenerated after syncing with staging.

Co-authored-by: Cursor <cursoragent@cursor.com>

* address comments

* fix pinned version for lru cache

* retryable errs cleanup

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 11:31:49 -07:00
Theodore LiandClaude Opus 4.7 92fd17cd15 fix(tables): workflow-column run fixes + bounded run-N-rows (#4754)
* fix(db): disable statement_timeout for migrations

* fix(ci): route migration workflow through guarded migrate.ts

* feat(tables): workflow-column run fixes + bounded "run N rows"

- Pass group.autoRun as the add-group dispatch flag so an autoRun=false
  column no longer opens a no-op dispatch that flashes the run-count badge.
- Scope the context-menu re-run to the right-clicked workflow cell's group
  (cascading to dependents) instead of every group on the row.
- Add an extensible per-dispatch row cap (DispatchLimit { type:'rows', max })
  surfaced as "Run 10 / 1,000 empty rows" in the group header; dispatcher
  stops after N eligible rows. New limit/processed_count columns on
  table_run_dispatches.
- Fix stranded "Queued" cells: the cascade owner now treats a queued marker
  (orphan pre-stamp) as a manual run so autoRun=false requested groups are
  picked up, and drains late markers before releasing the row lock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(db): regenerate dispatch limit migration on staging chain (0214)

Re-numbers the table_run_dispatches limit/processed_count columns from the
collided 0212 to 0214 after merging staging (which added its own 0212/0213).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(tables): lint formatting

* fix(tables): address PR review on dispatch cap + cascade drain

- Don't consume the row cap when batchEnqueueAndWait fails; a transient
  failure no longer completes a capped dispatch with zero rows started.
- Outer cascade-drain loop only re-drives a genuine queued marker, not any
  eligible group, so an empty-output group can't re-run forever.
- completeDispatch forwards limit on the terminal SSE event.
- Extract shared LIMITED_RUN_PRESETS for the Run-N-rows menu items.

* chore(lint): format generated tool-schemas-v1

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 04:39:37 -04:00
Theodore LiandClaude Opus 4.7 65e2fe81c1 feat(tables): Add enrichment table column type (#4752)
* feat(tables): native enrichments sidebar + workflow input mapping

Add a Clay-style enrichments catalog to the table view and wire per-row
input mapping into workflow-backed columns.

- New "Enrichments" entry in the New-column dropdown opens a sliding panel
  listing curated enrichment templates; picking one swaps to the workflow
  config in-place (no cross-slide) with a back button.
- Type the workflow sidebar as manual | enrichment; enrichment hides the
  launch + add-column-inputs affordances.
- Add a "Workflow inputs" advanced panel mapping Start-block input fields to
  table columns (left-of-workflow columns only), with name-match auto-fill
  and collapsible input-mapping-style rows.
- Persist type + inputMappings on the workflow group (types, contract, route,
  service, hook) — jsonb, no migration.
- Consume inputMappings at run time: when present, feed Start-block fields
  from the mapped columns; otherwise fall back to name-match spread.
- Clean up inputMappings on column rename/delete (stripGroupDeps + renameColumn).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(emcn): extract CollapsibleCard and reuse for input mapping

Pull the collapsible field-card markup (surface-4 header + surface-2 body,
click/keyboard toggle, truncated title + optional badge) into a shared
`CollapsibleCard` emcn component, and use it in the workflow-builder input
mapping rows and the table sidebar's input-mapping panel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(tables): code-defined enrichment registry run directly per row

Enrichments are now TS configs in apps/sim/enrichments/ (registry, like
connectors) that run directly per table row via the existing run/dispatch/
cell-write rails — no workflow execution.

- enrichments/{types,registry} + work-email (heuristic) and phone-number (stub).
- WorkflowGroup gains enrichmentId; WorkflowGroupOutput gains outputId
  (workflowId/blockId/path kept required, '' for enrichment groups).
- Executor branches on group.type === 'enrichment' → maps inputMappings →
  enrich() → outputs by outputId → cell-write. Missing required inputs skip
  (blank cell) instead of erroring.
- Sidebar lists the registry; enrichment-config panel maps inputs to columns
  and creates the enrichment group (no workflow UI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(enrichments): provider fallback cascade + hosted-key usage source

Replace each enrichment's single enrich() with an ordered providers[]
fallback cascade. Providers are plain data ({ id, label, toolId,
buildParams, mapOutput }) so the catalog stays client-safe; the
server-only runner (run.ts) calls executeTool per provider, first
non-empty result wins, misses/errors fall through, all-miss = blank cell.

Wire four enrichments on the hosted-safe providers (Hunter, PDL):
- Work Email (fullName, companyDomain): Hunter -> PDL
- Phone Number (fullName, companyDomain): PDL
- Company Domain (companyName): PDL
- Company Info (domain): PDL -> Hunter

Person enrichments take a single canonical fullName (Clay-style); Hunter
gets first/last via splitName(), PDL takes name directly.

Add 'enrichment' to usage_log_source enum (+ migration) so hosted-key
tool cost from these per-row calls can be billed to the table owner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(enrichments): bill hosted-key cost; surface provider errors; abort safety

- runEnrichment now returns { result, cost, error }: accumulates hosted-key
  cost across the cascade, and sets `error` only when every provider that ran
  errored (auth/rate-limit/outage) vs a clean miss.
- Executor records the cost to the table owner (createdBy) via recordUsage
  (source 'enrichment'); billing failures are logged, never error the cell.
- F1: all-providers-errored now writes status 'error' instead of a blank
  'completed' cell that looked like "no data found".
- F2: re-check the abort signal after the cascade so a cancel mid-tool-call
  isn't recorded as a completed empty cell.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(tables): present enrichment columns as first-class in the grid

- Meta-header shows the enrichment's name + icon (Mail/Phone/Globe/Building2)
  instead of "Workflow" + a color chip.
- Per-column header icon uses the enrichment's icon (via columnSourceInfo)
  instead of the generic play icon.
- Hide "View execution" for enrichment cells in both the row context menu and
  the action bar (no workflow execution exists to open); also hide the
  meta-menu "View workflow" item for enrichment groups.
- Clicking an enrichment column header now opens the enrichments sidebar in
  edit mode (pre-filled input mappings, Update via useUpdateWorkflowGroup)
  instead of the workflow "Configure workflow" sidebar.
- Enrichment config lets the user name each output column (editable per-output,
  deduped defaults) since enrichments can produce multiple columns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(tables): enrichment columns use type icon; output names editable

- Drop the per-column enrichment icon (it duplicated the meta-header icon).
  Enrichment output columns now render the standard column-type icon (Text,
  etc.) — the enrichment's icon stays only on the group meta-header.
- Make output column names editable in the enrichment config edit mode too;
  changed names rename their columns via useUpdateColumn (the rename cascades
  into the group's output refs server-side). Validation excludes the output's
  own current name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(tables): wrap enrichment catalog descriptions instead of truncating

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(tables): edit enrichment output columns via the plain column editor

Edit column on an enrichment output now opens the normal column-config sidebar
(rename / type / unique) instead of the workflow 'Configure output column'
panel, which showed workflow-only fields and blocked a simple rename.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(copilot): list_enrichments + add_enrichment table tools

Let the copilot enumerate the code-defined enrichment registry and add an
enrichment column to a table (validating required input mappings against the
table's columns), backed by the same workflow-group machinery the UI uses.

* fix(enrichments): address PR review feedback

- Guard the enrichment cell path on `enrichmentId` so a group typed
  'enrichment' without a registry id falls through to the workflow path
  instead of erroring.
- Clear stale output values when skipping a row for missing required inputs,
  so the auto cascade re-enriches once inputs return (was left completed+filled).
- Write a terminal state on abort in the enrichment path (matches the workflow
  path) so a cancel between run and terminal-write can't leave the cell running.
- Edit mode: apply the group update (mappings/deps/auto-run) before column
  renames so the primary edit lands even if a rename fails.
- Disable Save once validation has surfaced a missing required input.
- Use the workflowGroupById map instead of O(n) find in the context-menu and
  action-bar hot paths.

* chore(commands): add /add-enrichment command

Guides adding a code-defined table enrichment to the registry, with a required
step to verify each provider tool has hosted-key support and chain to
/add-hosted-key when it doesn't.

* fix(enrichments): address second-pass PR review

- updateWorkflowGroup output diff now keys on outputId (falling back to
  blockId::path) so enrichment outputs — which share empty blockId/path —
  no longer collapse to one key and drop sibling columns.
- Enrichment terminal write now clears output columns absent from the result,
  so a partial/empty re-run doesn't leave stale values.
- Editing a group whose enrichment was removed from the registry shows an
  explanatory panel instead of silently falling through to the new-enrichment
  catalog.

* feat(tables): show "Not found" badge for empty completed enrichment cells

An enrichment that runs to completion but matches nothing now renders a gray
"Not found" badge (like the Queued/Waiting cell states) instead of a blank
cell, so a real miss is distinguishable from an unrun cell. Scoped to
enrichment output columns; an empty string no longer counts as a value.

* fix(enrichments): don't re-run completed no-match enrichments on auto cascade

A completed enrichment with empty outputs is a real no-match result, not an
unfinished run. Eligibility now treats an enrichment's completed status as
terminal (regardless of output fill), so the auto cascade stops re-invoking
billable provider calls on every no-match row each dispatch. Input changes
still clear the exec entry, so genuine re-runs are unaffected; manual Run all
still re-runs.

* fix(enrichments): treat provider 404 as no-match, not a cell error

Providers like People Data Labs signal 'no record found' with HTTP 404, which
executeTool surfaces as a failed ToolResponse (status on output.status). The
cascade now treats a 404 as a clean miss — falls through to the next provider
and lets the cell render 'Not found' — instead of marking the cell errored.
Auth/rate-limit/5xx still propagate as real errors.

* fix(tools): surface HTTP status on error ToolResponse output

executeTool's catch handled Error instances in its first branch and only
extracted status/statusText/data for non-Error object throws — so HTTP errors
(thrown as Error instances carrying .status) lost their status on the returned
output. Surface it for Error instances too, so callers can branch on the
status (e.g. the enrichment cascade treating a provider 404 as a no-match).

* fix lint

* Revert ff

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 03:26:46 -04:00
Vikhyath Mondreti 209ca5f121 fix(large-refs): cleanup based on table read (#4716)
* fix(large-refs): cleanup based on table read

* address comments

* address comments

* bubble up storage ref errors

* cleanup code

* do not attempt blob deletion for infra outage

* cleanup dup helper
2026-05-22 12:08:22 -07:00
Theodore Li 786c6f0607 fix(db): disable statement_timeout for migrations (#4714)
* fix(db): disable statement_timeout for migrations

* fix(ci): route migration workflow through guarded migrate.ts
2026-05-22 14:23:06 -04:00
Waleed 3f7698c66b perf(db): reduce read/write fanout across hot paths (#4704)
* perf(db): reduce read/write fanout across hot paths

* fix(templates): include name/workflowId/status/tags in DELETE projection

* fix(db): address review feedback (joinedAt backfill, mcp delete error, chatDeploy projection, sql newline)

* fix(webhooks): include NULL failedCount in markWebhookSuccess guard
2026-05-21 15:25:31 -07:00
WaleedandClaude Opus 4.7 46db40620f feat(mcp): OAuth 2.1 + PKCE for outbound MCP servers (#4441)
* feat(mcp): OAuth 2.1 support for outbound MCP servers

* fix(mcp): tighten OAuth refresh race and session-error detection

Re-load the OAuth row inside withMcpOauthRefreshLock so concurrent
callers observe predecessor-written tokens instead of a stale snapshot
loaded before lock acquisition. Without this, the second caller's
provider held a rotated-out refresh token and the SDK tripped
invalid_grant, forcing reauthorization.

Switch isSessionError to match the SDK's typed StreamableHTTPError
(code 404/400) instead of substring-checking arbitrary error messages,
removing false positives on URLs that happen to contain those digits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(mcp): tighten OAuth callback contract and registration metadata

- Validate callback query params via mcpOauthCallbackContract instead of
  raw searchParams.get, matching the rest of the MCP route surface.
- Drop non-RFC-7591 application_type field from dynamic client registration
  to avoid rejection by strict authorization servers.
- Collapse the pre-lock OAuth row load in createClient — the row is now
  loaded exclusively inside withMcpOauthRefreshLock, removing a redundant
  query and a stale-snapshot path.

* fix(mcp): narrow workspaceId before async closure in OAuth createClient

* fix(mcp): return authType from create-server endpoint

The POST /api/mcp/servers handler omitted authType from the success
response, so useCreateMcpServer always saw data.data.authType as
undefined and never triggered the OAuth popup after creating an
OAuth-protected server. Thread authType through performCreateMcpServer
into the response so the client can decide whether to auto-start OAuth.

* fix(mcp): mirror server null normalization in optimistic oauthClientId update

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): revert optimistic oauthClientId to undefined to match McpServer type

The response contract preprocesses null → undefined, so McpServer.oauthClientId
is string | undefined. Using null broke type checking.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): tighten OAuth probe signal and clear stale popup interval

- probe: only classify as OAuth on resource_metadata or scope params.
  Bare `Bearer error="invalid_token"` is generic and used by API-key servers,
  so it must not auto-flip the auth type to OAuth.
- popup hook: clear any existing close-watcher interval before overwriting
  when startOauthForServer is invoked twice for the same serverId.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): normalize empty-string oauthClientId at route boundary

Orchestration already converts falsy → null via `|| null` (server-lifecycle.ts),
so the DB was never receiving an empty string. Tightening the route layer to
match the same convention keeps the boundary contract consistent and avoids
relying on downstream normalization.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(canvas): expand MCP tool params into per-row labels on block tile

The MCP Tool block on the workflow canvas previously crammed every selected-
tool parameter into a stringified blob under the `Tool` row. Now, when a tool
is selected, the tile reads the cached `_toolSchema` and emits one labeled
SubBlockRow per parameter (matching the Exa block's per-param layout). Labels
reuse `formatParameterLabel` for parity with the editor panel; values pass
through the existing `getDisplayValue` so booleans/numbers/arrays render
identically to other blocks. Deterministic tile height counts expanded rows
so the tile sizes correctly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(logs): show MCP icon and strip prefix in trace tool spans

Tool spans for MCP calls were rendering the raw id (e.g.
`mcp-f908f259-planetscale_list_organizations`) with the default blank-
square icon. Now they read just the tool name and render the MCP block's
icon and bgColor, matching how workflow-execute tools render.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): lift near-black trace icon backgrounds for dark-mode contrast

Block bgColors below a small luminance threshold (e.g. the MCP block's
#181C1E) rendered nearly invisible against the dark-mode surface
(--bg: #1b1b1b). Adds a tiny adjustBgForContrast helper that floors each
RGB channel at 0x33 only when luminance is below 30,000, leaving every
branded color above that band untouched. Applied to both the trace tree
row and the detail pane.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): fall back to neutral gray for near-black trace icon bgs

#333333 was still too close to the dark-mode surface to read. For bgs
below the luminance threshold (e.g. the MCP block's #181C1E) we now fall
back to DEFAULT_BLOCK_COLOR (#6b7280) — the same neutral the renderer
uses for blocks with no distinct identity. Clearly visible in both
themes; brighter brand colors still pass through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(db): drop 0209_mcp_oauth migration ahead of staging merge

Staging shipped 0209_smiling_fixer; the MCP OAuth migration will be
regenerated on top of staging as 0210.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(db): regenerate MCP OAuth migration as 0210

Re-runs drizzle-kit generate on top of staging's 0209_smiling_fixer.
Same schema (mcp_server_oauth table + mcp_servers.auth_type / oauth_*
columns) as the dropped 0209_mcp_oauth.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(audit): bump route baseline 748 → 749 after staging merge

The post-merge route count is 749 (this branch's OAuth start/callback
plus staging's new route). I had set the baseline to 748 in the merge
conflict resolution — bumping to match reality so the strict audit
passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: remove source-command skill files committed by accident

These were untracked-then-accidentally-staged in 05c4bc19e via a wide
`git add -A`. They aren't part of this PR's scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 12:10:04 -07:00
Theodore LiandClaude Opus 4.7 f0311a6f5e feat(table): chunked dispatcher + workflow cascade (#4672)
* feat(table): chunked dispatcher for workflow-column runs

Replaces the all-rows-at-once runWorkflowColumn with a row-window dispatcher
backed by a new table_run_dispatches row. Each user click inserts a dispatch
row and triggers a trigger.dev task that crawls the table 20 rows at a time,
re-enqueueing itself between windows. The HTTP/Mothership entrypoints return
{ dispatchId } immediately instead of holding the request open for minutes
on multi-thousand-row dispatches.

- Per-row cancel stamps cancelledAt; the dispatcher skips cells whose
  cancelledAt > dispatch.requestedAt so a mid-cascade cancel sticks even
  under isManualRun.
- Table-wide cancel marks active dispatches cancelled atomically so the
  dispatcher bails on its next iteration.
- New 'dispatch' SSE event variant plumbed; client ignores for v1.

* fix(table): eager bulk clear on column run so cells flip immediately

Run-column with run-mode 'all' wasn't visually flipping rows that already
had data — the cell renderer's "value wins" branch kept showing the prior
output behind the queued/running state. The dispatcher only cleared one
window of rows at a time, so most of the column stayed stale until the
cursor walked to it.

Now:
- Dispatcher's `pending → dispatching` transition runs a single SQL UPDATE
  that wipes targeted `data` output columns and `executions[gid]` across
  every targeted row (mode-aware: 'incomplete' skips fully-filled rows).
- Per-window clear in `dispatcherStep` is gone — rows are pre-cleared,
  the loop only filters cancel tombstones / unmet deps and enqueues.
- Optimistic patch in `useRunColumn` mirrors the bulk clear by nulling
  output values in the cached row, so the UI flips queued/running
  instantly without waiting for the SSE catch-up.

* fix(table): bulk clear honors in-flight execs under mode: 'incomplete'

The eager bulk clear for mode: 'incomplete' only skipped rows that were
already fully filled, so two overlapping dispatches could race — dispatch B
would nuke executions[gid] on a row dispatch A had just stamped 'queued',
flickering the cell and potentially confusing the worker.

Skip any row whose targeted group is currently queued/running/pending — an
'incomplete' run shouldn't touch what another dispatch is actively working
on. The per-walk 'in-flight' eligibility skip already handles rows that
flip in-flight between the clear and the cursor reaching them.

* refactor(table): dispatcher uses batchTriggerAndWait + tag-based cancel

Switch the per-window cell fan-out from fire-and-forget tasks.trigger to
tasks.batchTriggerAndWait. The dispatcher is now a single long-lived
trigger.dev task that loops dispatcherStep until the table is exhausted;
trigger.dev CRIU-checkpoints the parent during each wait so we don't pay
compute while cells execute. Queue depth is bounded at WINDOW_SIZE per
dispatch — no more flooding trigger.dev with a million queued runs.

- dispatcher.ts builds payloads via the new shared buildPendingRuns helper
  and calls tasks.batchTriggerAndWait directly. Pre-stamps each cell to
  `queued` (jobId=null) so the UI flips instantly.
- table-run-dispatcher.ts is now a plain while-true loop. No
  RUN_BUDGET_MS, no self-re-enqueue, no cold-start tax per window.

Cancel:
- New cancelCellRunsByTags(tags) paginates runs.list + runs.cancel(id).
- cancelWorkflowGroupRuns fires the tag-sweep alongside the per-jobId
  queue.cancelJob path (preserved for auto-fire cells that have real
  jobIds from single tasks.trigger calls).
- Trigger.dev acks the cancel → batchTriggerAndWait resumes → dispatcher
  observes the dispatch-row cancel flag → exits.

Side fixes:
- getAsyncBackendType returns 'trigger-dev' whenever taskContext.isInsideTask
  is true, regardless of TRIGGER_DEV_ENABLED env. The preview/dev-sim
  worker silently routing cell jobs to DatabaseJobQueue (no poller) is
  fixed without any env config change.
- runWorkflowColumn skips the dispatcher entirely when trigger.dev is
  disabled, running cells inline via DatabaseJobQueue.runInline. HTTP
  response returns dispatchId: null in that mode.
- runColumnContract response schema updated to dispatchId.nullable().

* fix(table): show Stop button on optimistic-pending row cells

isExecInFlight required a jobId for `pending` status, gating it as "real
backend pending" vs "optimistic flag only." The row-gutter Stop button
keyed on this — so a freshly clicked Play sat as `pending` (no jobId) and
the user couldn't cancel it until the server-side `queued` stamp arrived
via SSE. With the dispatcher pre-batch stamping cells as `queued` (not
`pending`) and no per-cell jobIds under batchTriggerAndWait, the gap was
worse.

Drop the jobId requirement. `pending` now counts as in-flight everywhere.
Cancel writes `cancelled` to the cell exec authoritatively whether or not
a real trigger.dev run exists yet — cancelling an optimistic cell means
"don't run this," which is correct.

Also collapse isOptimisticInFlight into isExecInFlight since the two
helpers are now identical.

* refactor(table): loop-in-cell cascade + dispatcher-everywhere routing

Two coupled changes:

1. Cell-task runs the row's full cascade in-process. executeWorkflowGroupCellJob
   acquires a Redis lock per (tableId, rowId) with heartbeat (10s/30s TTL),
   then loops through eligible workflow groups for the row. One cell-task =
   one row's full cascade, not N. Resume worker holds the same lock and
   continues the cascade after a HITL resume. Shared withCascadeLock helper
   in lib/table/cascade-lock.ts.

2. Every cell-enqueue goes through the dispatcher. The implicit
   scheduleRunsForRows reactor in service.ts is removed — 8 callsites
   (insertRow, batchInsertRows, upsertRow, updateRowsByFilter,
   batchUpdateRows, addWorkflowGroup, updateWorkflowGroup) now fire
   runWorkflowColumn with mode: 'incomplete', isManualRun: false. HTTP
   routes that call updateRow directly also fire runWorkflowColumn
   afterwards. scheduleRunsForTable / scheduleRunsForRowIds deleted;
   scheduleRunsForRows demoted to private (only the TRIGGER_DEV_ENABLED=false
   fallback uses it). skipScheduler flag dropped from UpdateRowData /
   BatchUpdateByIdData — no longer meaningful since there's nothing implicit
   to suppress.

Plumbed isManualRun through the dispatch row (new is_manual_run column,
default true) so auto-fire callers honor autoRun: false and don't re-run
completed cells.

Stamp 'pending' (not 'queued', executionId: null) before
batchTriggerAndWait — cell-task writes its own 'queued' on lock acquire.

Small UI polish: row gutter Play button spacing, "Delete workflow" →
"Delete column" label, optimistic-pending cells now show Stop button
(isExecInFlight no longer requires jobId).

* fix(table): SQL cancellation guard allows worker to claim a null-execId cell

The dispatcher's pre-batch `pending` stamp leaves executionId unset so any
cell-task that wins the cascade lock can claim the cell. The cancellation-
guard SQL clause was rejecting these claims because it tested
`executions->gid IS NULL` (whole exec missing) but the pre-stamp leaves
the exec present with executionId=null.

Add a third carve-out: `executions->gid->>'executionId' IS NULL`. Now the
guard reads "write allowed if no exec exists, OR no executionId is set
yet, OR the executionId matches ours."

Symptom: every cell-task's first markWorkflowGroupPickedUp call would log
"SQL guard saw cancelled" and skip, leaving cells stuck at the dispatcher's
pending stamp.

* fix(table): dispatcher cursor starts at -1 so position 0 is included

The dispatcher's row-window SELECT is `position > cursor` for exclusive
lower-bound semantics. With cursor initialized to 0, position-0 rows were
never picked up — every dispatch silently skipped the table's first row.

Start cursor at -1 instead. First window's filter `position > -1` matches
position 0; subsequent iterations advance to `lastPosition` which then
correctly excludes already-processed rows.

* refactor(table): align optimistic UI with new dispatcher; sticky cancel via 'new' mode

Fix 0: new `DispatchMode = 'new'` for auto-fire callsites. Eligibility skips
rows with any prior `executions[gid]` entry — cancelled / errored / completed
cells stay sticky until a manual run. Dispatcher's windowed SELECT pushes
`NOT jsonb_exists_any(...)` to SQL so CSV imports into mostly-attempted
tables don't pay a per-window load+JS-filter. `batchInsertRows` drops its
`rowIds` payload (keeps dispatch scope tiny on big imports).

Fix A/B/D: client optimistic patches now mirror the backend's actual
invariants. `useCreateTableRow.onSuccess` stamps eligible groups via
`optimisticallyScheduleNewlyEligibleGroups` so newly-inserted rows show
`Queued` instantly. `useCancelTableRuns.onMutate` distinguishes optimistic-
only pending (`executionId == null` — strip silently) from real worker
claims (stamp cancelled; SSE will reconcile). Drop `onSettled` invalidation
on `useUpdateTableRow` / `useBatchUpdateTableRows` to kill the
delete-cell flicker.

Fix C: active-dispatches overlay. New `listActiveDispatches` helper,
contract, and `GET /api/table/[tableId]/dispatches` route. `kind:'dispatch'`
SSE events carry scope+cursor+mode on every transition. New
`useActiveDispatches` hook + `resolveCellExec` synthesize a virtual
`pending` exec for cells in an active dispatch's scope ahead of cursor —
queued indicators now survive page refresh during long Run-all dispatches.
`cancelWorkflowGroupRuns` emits `kind:'dispatch',status:'cancelled'`
events so the overlay clears without a refetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(table): unify trigger.dev and inline dispatcher paths

`runWorkflowColumn` now always inserts a `table_run_dispatches` row and
drives the dispatcher state machine. The trigger.dev / in-process branch
narrows to a single line: trigger.dev fires `tableRunDispatcherTask` (which
calls the new `runDispatcherToCompletion`), the inline path calls the same
helper fire-and-forget. Deletes `scheduleRunsForRows` and
`stampQueuedOrCancel` — the inline-fallback no longer duplicates window
walking, SSE emission, or cancel.

The dispatcher's window-execute call goes through `JobQueueBackend`:
- New `batchEnqueueAndWait` interface method.
- Trigger.dev impl wraps `tasks.batchTriggerAndWait` behind a
  `taskContext.isInsideTask` guard (clear error if called from outside a
  task).
- Database impl skips `async_jobs` entirely — `Promise.all` over
  `options.runner(payload, signal)` per item, with per-cell AbortControllers
  tracked by `cancelKey` for cancel.

`cancelInlineRun` moves to the interface as `cancelByKey` so
`cancelWorkflowGroupRuns` no longer reaches into the database backend.

Fix `mode: 'new'` SQL filter:
- `${array}::text[]` interpolated as a tuple-cast which Postgres rejected
  ("cannot cast type record to text[]") and every inline dispatch silently
  failed. Switched to `ARRAY[${sql.join(...)}]::text[]`.
- Predicate was `jsonb_exists_any` ("any one targeted group present"),
  which excluded rows that needed at least one group re-run after a
  downstream output was deleted. Switched to `jsonb_exists_all` — per-group
  JS eligibility handles the rest.

Cascade-loop workflowId bug: `runRowCascadeLoop` was not threading the new
group's `workflowId` when advancing across groups. The cell-task ran the
previous group's workflow against the next group's cell, terminating
`completed` with empty `accumulatedData`. Fixed by tracking
`currentWorkflowId` alongside `currentGroupId` / `currentExecutionId`.

Client optimistic-patch tightening:
- `useRunColumn.onMutate` mirrors server eligibility — skip cells with
  unmet deps so unmet rows don't flash Queued and get stuck (no SSE will
  arrive for cells the server skipped).
- `resolveCellExec` overlay synthesizes a virtual `pending` only when
  `areGroupDepsSatisfied` is true. Rows with unmet deps render Waiting,
  matching the dispatcher's actual behavior.

Cleanup from /simplify pass:
- Use `generateShortId(20)` instead of
  `generateId().replace(/-/g, '').slice(0, 20)`.
- Inline `batchEnqueueAndWait` no longer allocates synthetic ids
  (returned `string[]` is unused).
- Flattened the per-cell `tracked` array — only push entries that
  registered controllers, drop the null placeholders.
- Extracted `runDispatcherToCompletion` to share the loop between the
  trigger.dev wrapper and the in-process path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(table): backend running counter, dep-aware retrigger, sidebar polish

Counter (Fix 1): top-right "X running" + per-row badge are now
backend-bootstrapped via a count on `user_table_rows.executions ->> 'status'
= 'running'` returned alongside active dispatches. SSE `kind: 'cell'` events
compute a delta from `prev → next` status to keep the cache live; cell
events for rows outside the loaded page slice trigger a run-state refetch.
On `pruned` we invalidate the cache. Counts only worker-claimed `running`
cells — optimistic queued/pending no longer inflate the badge, and rows
outside the loaded page slice are counted too.

Sidebar (Fix 2 + 3a): `Run after` no longer ticks every column by default
for new groups (empty list). Save is disabled with an inline error when
auto-run is on with zero deps. `edit-group` mode anchors the left-of-current
filter to the group's leftmost column, so a workflow can only depend on
columns to its left.

Reorder scrub (Fix 3b): `updateTableMetadata` walks the schema's workflow
groups when `columnOrder` is in the patch and drops any dep whose new
position lands at or after the group's leftmost column (uses the existing
`stripGroupDeps` helper). Metadata + schema updates land atomically.

Server returns ordered columns (Fix 3b cont'd): `getTableById` /
`listTables` now sort `schema.columns` by `metadata.columnOrder` before
returning, via a new `applyColumnOrderToSchema` helper. Every consumer
(grid, sidebar, copilot, mothership) gets one ordered list — the sidebar's
leftmost-group-column anchor now points at the right index.

Dep-aware retrigger (Fix 4): editing a value that a downstream workflow
depends on now re-runs that workflow.
- `deriveExecClearsForDataPatch` returns
  `{ executionsPatch, inFlightDownstreamGroups }`. Walks
  `schema.workflowGroups[].dependencies.columns` for every column in the
  patch, clears terminal-state downstream entries, and reports in-flight
  entries.
- `updateRow` calls `cancelWorkflowGroupRuns` + `runWorkflowColumn`
  (`mode: 'incomplete' + isManualRun: true`) for in-flight downstream
  groups, then always fires `runWorkflowColumn({ mode: 'new' })` for the
  cleared groups. Skips both when `executionsPatch` is provided by the
  caller — those are cell-task / cancel writes that would otherwise spawn
  a recursive flood of dispatches per partial-write.
- `cancelWorkflowGroupRuns(tableId, rowId, { groupIds? })` accepts a
  per-group filter so the cancel only touches the affected groups, not
  every in-flight cell on the row.
- `pickNextEligibleGroupForRow` now treats a dispatcher pre-stamp
  (`pending` + `executionId: null`) as claimable — the cascade-loop is the
  real owner. Without this, the dispatcher's pre-stamp of downstream
  groups made the cascade-loop see them as "in-flight" and skip them,
  stranding `pending` cells forever.
- `optimisticallyScheduleNewlyEligibleGroups` extends the cache patch to
  flip dep-touched groups to `pending` regardless of their current status,
  matching the server's cancel-then-rerun behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(table): paused workflow cells route through executeResumeJob; render Pending + viewable

Three connected issues with workflows that pause mid-cell (e.g. wait blocks):

1. `/api/resume/poll` (the time-pause auto-resumer) called
   `PauseResumeManager.startResumeExecution` directly, bypassing
   `executeResumeJob` from `background/resume-execution.ts`. The wrapper is
   where the cell-context restoration + cascade-loop continuation lives —
   without it, the resumed workflow ran to completion but never wrote the
   terminal state back to the table cell. Cell stays `pending` forever
   even though the underlying execution finished.

   Fix: dynamically import `executeResumeJob` and use it for the
   `'starting'` branch. Same primitive the trigger.dev `resumeExecutionTask`
   wraps — calling it directly handles both trigger.dev-disabled local dev
   and trigger.dev-enabled prod identically.

2. The cell renderer mapped `status: 'pending'` to `kind: 'queued'` (gray
   "Queued" badge) regardless of whether the run had started. A HITL-paused
   run has `status: 'pending'` + `jobId` prefixed `paused-` + a real
   `executionId` — semantically very different from "queued, hasn't run."
   Now renders as `pending-upstream` (the existing Pending pill) for
   paused-jobId rows.

3. Right-click "View execution" was disabled for `pending` cells (gated to
   `completed | error | running`), so users couldn't open the trace for a
   paused execution. Paused runs have a viewable trace (the executionId is
   real and the log row exists). Both the per-row context menu and the
   action-bar derivation now recognize `pending` + `paused-` jobId as a
   started run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(table): typewriter reveal for SSE-driven workflow cell values

Workflow-output cells now reveal their text character-by-character when an
SSE update lands, while page reloads and virtualization remounts still paint
the value instantly. A first-render guard inside the new useTypewriter hook
distinguishes hydration from live updates with no plumbing through the cell
tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(table): address bugbot/greptile review feedback

Two P1 issues + one cleanup from the bot reviewers:

1. **Double-dispatch + completed-output wipe.** Both PATCH row routes
   (`app/api/table/[tableId]/rows/[rowId]` and
   `app/api/v1/tables/[tableId]/rows/[rowId]`) were firing a second
   `runWorkflowColumn({ mode: 'incomplete' })` after `updateRow` returns.
   `updateRow` already fires `mode: 'new'` internally for user edits, so
   the second call created a concurrent dispatch. Worse, the
   `mode: 'incomplete'` path's `bulkClearWorkflowGroupCells` wipes ALL
   targeted output columns on any row where any one column is empty —
   meaning sibling-group completed outputs could be erased. Removed both
   route-level calls; auto-dispatch lives entirely in `updateRow`.

2. **`runWorkflowColumn` log-spamming on plain tables.**
   `if (targetGroups.length === 0) throw new Error(...)` fired on every
   row insert/update for tables without any workflow groups (the
   majority). Every caller wraps with `.catch(logger.error)`, so each
   PATCH produced an error-level log. Return `{ dispatchId: null }`
   silently — manual `runWorkflowColumn` callers pass `groupIds`
   explicitly so they can't reach this branch.

3. **`isManualRun` plumbed through dispatch SSE events.** Late-arriving
   `kind: 'dispatch'` events for dispatches not in the initial fetch
   were hardcoding `isManualRun: false`. Added the field to the event
   shape, emit it from `dispatcherStep` (pending → complete, dispatching
   transitions) and `markActiveDispatchesCancelled`, and consume it in
   the SSE handler with a sensible fallback for legacy emits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(table): row executions sidecar + left-to-right dep retrigger + cancel counter refresh

Split per-row workflow-group execution state out of the user_table_rows.executions
JSONB column into a new table_row_executions sidecar keyed by (row_id, group_id).
Dispatcher filters, "X running" counter, bulk clears, and the cancellation guard
all hit indexed columns instead of walking JSONB. Wire shape unchanged — server
merges sidecar rows back into row.executions on the way out.

Also:
- deriveExecClearsForDataPatch now walks workflowGroups left-to-right with a
  propagating dirtied-column set so transitive dep chains (edit col A → group 1
  re-runs → group 2 depends on group 1's output → group 2 re-runs) collapse to
  a single forward pass.
- useCancelTableRuns.onSettled invalidates the activeDispatches query so the
  top-right counter and row gutter Stop button refetch from the server after
  any Stop (per-cell, row, or table-wide). countRunningCells is the source of
  truth; client no longer needs duplicate state.

Three migrations on this branch (0209 + 0210 + new sidecar) collapsed into one
since the feature is unreleased.

* fix(table): address remaining cursor/greptile review feedback

- Mothership update_row no longer double-dispatches. updateRow already fires
  the auto-cascade internally; the second `mode: 'incomplete'` call here
  raced with it and could bulk-clear sibling-group outputs.
- SSE dispatch events no longer dropped when the activeDispatches cache is
  cold. Seed an empty TableRunState if the initial fetch hasn't landed yet
  so the queued overlay doesn't lose the first dispatch event.
- batchUpdateRows now runs cancel+rerun for per-row in-flight downstream
  groups, mirroring updateRow. Without this, dep edits in a batch left
  running workflows reading stale upstream values.

* fix(table): cancel prior runs, scope batch insert dispatch, recover orphan pre-stamps

Addresses cursor + greptile review feedback on table dispatcher edge cases:

- Manual table-wide Run-all / Run-column now cancels prior active dispatches
  AND in-flight cell workers before bulk-clearing. Without this, mode:'all'
  deleted running sidecar rows out from under their workers (which kept
  writing into the wiped state) and a second Run-all could enqueue overlapping
  cells racing on the same rows. Row-scoped manual calls (dep-edit cascade)
  are excluded — those already cancel their own scope.
- batchInsertRowsWithTx now scopes its auto-dispatch to the newly-inserted
  row ids. Without this, after the sidecar migration the NOT EXISTS filter
  matches every existing row (zero sidecar entries), so a CSV import would
  walk the entire table dispatching workflow runs on every pre-existing row.
- classifyEligibility carve-out: pending + executionId=null is an orphan
  pre-stamp (cascade-lock contention, batchEnqueueAndWait failure, etc.),
  treated as claimable so future dispatchers can re-stamp instead of skipping
  it as 'in-flight' forever. Matches pickNextEligibleGroupForRow's logic.
- On batchEnqueueAndWait failure, dispatcherStep now sweeps the orphan
  pre-stamps it wrote for the failed batch so the cells don't render Queued
  forever; the next user action picks them up cleanly.

* fix(table): row-scoped Refresh cancels in-flight; counter includes queued/pending

- runWorkflowColumn now cancels prior in-flight cells for row-scoped manual
  runs too (context-menu Refresh on a row subset, action-bar Refresh on
  selected rows). Previously only the table-wide path cancelled, so a
  row-scoped Refresh would bulk-clear running sidecar rows without aborting
  workers. Per-row cancel skips markActiveDispatchesCancelled so unrelated
  dispatches keep running.
- countRunningCells now counts all in-flight statuses (queued / running /
  pending) instead of just running. The row gutter Run/Stop button reads
  this map — with the old behavior, clicking Play during the queued window
  would re-enqueue an already-queued cell. SSE applyCell handler updated
  to use isExecInFlight so client deltas track the same semantics.

* fix(table): per-row Stop tombstones ahead-of-cursor rows during Run-all

Per-row Stop only cancelled sidecar rows already in flight. A row the
dispatcher hadn't reached yet had no exec record, so Stop was a no-op there
— the dispatcher would later walk to it, classify the group eligible, and
re-fire workflows the user thought they stopped.

cancelWorkflowGroupRuns now, for a per-row cancel, checks active dispatches
whose scope covers the row and writes `cancelled` tombstones (cancelledAt =
now) for the at-risk groups that don't already have a sidecar entry. The
dispatcher's existing `cancelledAt > dispatch.requestedAt` filter then skips
them when the cursor arrives. onConflictDoNothing guards against clobbering
a concurrently-written entry; the active-dispatch check avoids stamping
spurious cancels on idle rows.

* fix(table): seed dispatch overlay on Run; surface batch-enqueue failures as error

- useRunColumn.onSuccess invalidates the activeDispatches query so the
  resolveCellExec queued overlay populates immediately for ahead-of-cursor
  rows (scrolled-in / refetched), instead of waiting for the first dispatch
  SSE. Targeted at activeDispatches only — the rows cache stays owned by
  useTableEventStream.
- On batchEnqueueAndWait failure, dispatcherStep now flips the orphan
  pre-stamps to a terminal `error` state and emits a cell SSE event, rather
  than deleting them. The cursor still advances past the window, but the
  dropped cells are now visible (Error pill) instead of silently empty, stay
  out of the in-flight set, and re-run on the next manual run.

* fix(table): seed dispatch overlay on Run; surface batch-enqueue failures as error

- useRunColumn.onSuccess invalidates activeDispatches so the resolveCellExec
  queued overlay populates immediately for ahead-of-cursor rows instead of
  waiting for the first dispatch SSE. Rows cache stays owned by SSE.
- On batchEnqueueAndWait failure, dispatcherStep flips orphan pre-stamps to a
  terminal error state (+ cell SSE) instead of deleting them, so the dropped
  window is visible (Error pill) rather than silently empty and re-runs on the
  next manual run.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:44:09 -04:00
Siddharth Ganesan 67e18f747d fix(migrations): remove duplicate column add (#4632) 2026-05-16 10:46:02 -07:00
Waleed 8d7bbbc670 chore(utils): migrate to shared random/ID utilities and add enforcement linting (#4623)
* chore(utils): migrate to shared random/ID utilities and add enforcement linting

- Replace all Math.random(), crypto.randomUUID(), crypto.randomBytes(), nanoid, and uuid usages with shared @sim/utils/random and @sim/utils/id helpers across 72 files
- Add new @sim/utils exports: deepClone, omit, filterUndefined (object), truncate (string), backoffWithJitter, parseRetryAfter (retry), getErrorMessage (errors)
- Sweep all getErrorMessage, sleep, deepClone callsites across 500+ files to use shared utilities
- Add Biome noRestrictedImports rule to catch nanoid, uuid, and crypto named imports at lint time
- Add scripts/check-utils-enforcement.ts to catch Math.random and crypto.* global property access
- Add check:utils script to package.json

* chore(utils): replace deepClone wrapper with structuredClone built-in

deepClone() was a one-line wrapper around structuredClone(), which is
universally available in Node 17+ and all modern browsers. Removing the
abstraction reduces indirection and means contributors don't need to
learn a project-specific name for a well-known built-in.

- Remove deepClone from packages/utils/src/object.ts and index.ts
- Replace all 17 call sites with structuredClone() directly
- Update check:utils script suggestion text
- Update CLAUDE.md and global.md docs

* fix(utils): add missing biome noRestrictedImports rule and correct truncate docs

- Add noRestrictedImports to biome.json under style — bans nanoid and uuid
  package imports at lint time (crypto.randomUUID/randomBytes are caught by
  the check:utils grep script which handles global property access)
- Correct truncate() TSDoc and parameter name: sliceLength makes it clear
  that total output length is sliceLength + suffix.length, matching the
  behavior all callers were already written to expect

* fix(utils): add missing getErrorMessage imports at 4 call sites

The sweep agents added getErrorMessage calls without the corresponding
import in 4 files, causing test failures. Added the missing imports.

* fix(utils): fix build errors from getErrorMessage sweep and retry.ts Turbopack issue

- Fix retry.ts cross-file import: Turbopack cannot resolve './random.js' for
  internal package imports; inline the jitter crypto call directly
- Add missing getErrorMessage imports to 32 files where the sweep added calls
  without the corresponding import (caught by type-check and test runs)
- Remove accidental getErrorMessage import from crowdstrike/query/route.ts
  which has its own domain-specific getErrorMessage for parsing CrowdStrike's
  JSON error format
- Fix use-sub-block-value.ts type error from structuredClone narrowing:
  add 'as T' cast at emitValue callsite (safe — valueCopy is always a
  structural copy of newValue)

* fix(tools): use toError in crowdstrike catch block instead of local getErrorMessage

The catch block was calling the local getErrorMessage function which
parses CrowdStrike API JSON responses, not JavaScript Error objects.
Use toError(error).message to correctly extract the message from a
caught value in this context.
2026-05-15 17:31:27 -07:00
c9118e775b feat(files): folders, multiselect, vfs update (#4572)
* 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(files): folders + vfs update

* address comments

* address comments

* cleanup unnused code

* address comments

* perf improvements

* address next set

* cycle detect

* error handling

* path improvements

* cleanup, best practices

* react query best practices: targeted invalidation, optimistic updates, key factory hierarchy

- Add workspaceLists(workspaceId) intermediate key level to both workspaceFilesKeys
  and workspaceFileFolderKeys so invalidation targets only the affected workspace
  instead of all workspaces
- Replace all lists() invalidation calls with workspaceLists(workspaceId) across
  every mutation (upload, rename, delete, restore, update content, folder mutations)
- Add optimistic updates to useRenameWorkspaceFile and useUpdateWorkspaceFileFolder
  with onMutate snapshot, onError rollback, onSettled reconciliation
- Move storage key into the content() factory as optional param so query keys are
  always built through the factory (useWorkspaceFileContent, useWorkspaceFileBinary)
- Fix AnimatePresence wrapping in FilesActionBar so exit animation fires on deselect
- Fix ResourceColGroup to use percentage weights instead of pixel widths to prevent
  horizontal scroll on narrow viewports

* add shift-click range selection and selection-aware context menu for files

- Extend SelectableConfig.onSelectRow with optional shiftKey param; DataRow captures shiftKey before onCheckedChange fires via a ref so the Radix Checkbox interaction chain stays intact
- Implement shift-click range selection in files.tsx using lastSelectedIndexRef; tracks last-selected index in visibleRowIds to compute the range
- Reset lastSelectedIndexRef on deselect and select-all
- Add selectedCount prop to FileRowContextMenu; hide Open and Rename when multiple items are selected, show "Delete N items" / "Download N items" labels in multi-select mode

* add Move submenu to file context menu and fix shift-click anchor update

- Add nested Move submenu to FileRowContextMenu using DropdownMenuSub/SubTrigger/SubContent; shows available folders filtered by selection, converts '__root__' -> null for moving to the root level
- Add handleContextMenuMove in files.tsx that calls moveItems.mutateAsync directly (no modal) and clears selection on success
- Fix shift-click range selection: update lastSelectedIndexRef after range select so chained shift-clicks extend from the new anchor point correctly

* fix move submenu: use folder names with tree-ordered indentation instead of stale paths

- Compute folder depth from parentId chain client-side (avoids stale server-computed path field)
- Tree-order folders so parents appear before their children, sorted by sortOrder then name
- Show folder.name instead of folder.path so optimistic renames are reflected immediately
- Indent each folder by depth * 12px in the submenu so po/shit renders as 'shit' indented under 'po'
- MoveOption gains optional depth field; contextMenuMoveOptions is a separate memo from moveFolderOptions (modal keeps its existing path-label behavior)

* fix shift-click anchor drift and remove dead stopPropagation constant

- Remove dead stopPropagation const in resource.tsx (replaced by handleSelectRowClick)
- Reset lastSelectedIndexRef when visibleRowIds changes so search/filter/folder navigation doesn't leave a stale anchor that produces wrong ranges on the next shift-click
- Update lastSelectedIndexRef in handleRowContextMenu when right-clicking resets selection to a single item, so the anchor matches the newly-selected row
- Add visibleRowIds to handleRowContextMenu deps (now reads it to compute anchor index)
- Remove moveItems.mutateAsync from handleContextMenuMove deps per project convention (.mutateAsync is stable in TanStack v5)

* complete workspace files feature: audit logs, posthog events, folder restore, empty state, keyboard shortcuts, storage indicator, breadcrumb rename

- Audit + PostHog: wire file_renamed, file_deleted, file_moved, file_bulk_deleted, folder_created, folder_renamed, folder_deleted, folder_moved events to all file/folder API routes
- Add AuditAction.FOLDER_UPDATED, FILE_MOVED, FOLDER_MOVED to audit types
- Folder restore: server function, contract, API route (POST /files/folders/[folderId]/restore), hook (useRestoreWorkspaceFileFolder), Recently Deleted integration with new File Folders tab
- Empty state: contextual emptyMessage passed to <Resource> based on search/filters/folder context
- Keyboard shortcuts: Delete/Backspace deletes selection, Escape deselects, Cmd+A selects all (list view only, input-aware guard)
- Storage indicator: useStorageInfo drives compact "used / limit" display in file list header via leadingActions
- Breadcrumb rename: current folder breadcrumb gains Rename dropdown + inline editing via breadcrumbRename (useInlineRename)
- Resource: thread leadingActions prop from ResourceProps to ResourceHeader

* cleanup: accessibility, emcn design tokens, react best practices across workspace UI

- Add sr-only ModalDescription to dialogs/modals for accessibility
- Replace hardcoded colors and z-indices with design token CSS variables
- Apply emcn design review fixes across tables, knowledge, logs, settings, workflows

* fix audit and posthog: FOLDER_RESTORED action on restore, fire folder_moved event separately from file_moved

* sidebar: add Files section with nested folder tree; polish move UX and cleanup

- Files section in sidebar shows folder/file tree with expand/collapse,
  matching Workflows section structure; collapsed sidebar shows flyout menu
- Move action bar now uses nested DropdownMenuSub tree instead of flat modal
- Context menu and action bar share renderMoveOption from move-options.tsx
- FolderInput added to emcn icons barrel; all FolderInput imports migrated
- Drag ghost uses CSS vars (--border, --shadow-medium, --z-toast)
- Selection pruning converted from useEffect to render-time comparison
- Keyboard listener stabilized with handleBulkDeleteRef pattern
- toError() used consistently in restore and move route handlers

* remove Files section from sidebar

* restore Files nav item in sidebar workspace section

* fix infinite re-render on files page - revert selection pruning to useEffect

* add filefolder resource type for ingesting workspace file folders

* export filefolder tree types; add toast feedback for file/folder mutations

* regenerate migration as 0208 after rebase onto staging

* add workspaceFileFolder to schema mock

* add FILE_MOVED, FOLDER_MOVED, FOLDER_UPDATED to audit mock

* add filefolder ChatContext kind and wire through schema and resolver

* add filefolder to AgentContextType

* add filefolder to chat context kind registry; fix resolver to use workspaceFiles table

* add .deepsec to gitignore

* cleanup: effect, emcn tokens, mutation error handling

- Replace selection-pruning useEffect with inline state adjustment during render
- Fix drag overlay using invalid --accent HSL token → --brand-secondary; z-50 → z-[var(--z-dropdown)]
- Move static inline styles on context menu trigger div to className
- Add missing onError toast to useUpdateWorkspaceFileFolder, useRestoreWorkspaceFileFolder, useRestoreWorkspaceFile

* lint

* fix: remove duplicate handleCopilotStopGeneration from rebase

* feat(copilot): folder-aware file context in WORKSPACE.md

* feat(copilot): add move operation to file manage API

* fix(files): make targetFolder optional in move file contract

* perf(files): parallelize buffer fetches, fix N+1 folder queries, stabilize drag useMemo

- download route: fan out all fetchWorkspaceFileBuffer calls with Promise.all
  before zip assembly so 100 files resolve in one round-trip instead of sequentially
- getWorkspaceFileFolder: replace per-ancestor SELECTs with a single workspace-wide
  folder load + buildWorkspaceFileFolderPathMap, making depth irrelevant to query count
- ensureWorkspaceFileFolderPath: pre-load all workspace folders in one SELECT before
  the segment loop; resolve existing segments from an in-memory map; only hit the DB
  to CREATE missing segments; conflict retry path preserved and also updates the map
- files.tsx rowDragDropConfig: move activeDropTargetId into a ref so the useMemo
  does not recompute on every drag-over event

* fix(files): remove files/ path stripping, fix stale path in optimistic update

- splitWorkspaceFilePath: remove the unconditional .replace(/^files\//, '')
  that clobbered paths for files inside a folder literally named "files"
- useUpdateWorkspaceFileFolder: when a name update is in flight, recompute
  the path field for the renamed folder (replace last segment) and propagate
  the new prefix to all descendant folders so breadcrumbs stay correct
  during the optimistic window

* fix(files): revert broken ref opt, clean 409 on restore, null parentId on orphaned restore

- files.tsx: revert the activeDropTargetId ref optimization — the ref doesn't
  trigger re-renders so the drop-target highlight never updated during drag;
  activeDropTargetId is back in state and in the rowDragDropConfig deps
- restore/route.ts: catch Postgres 23505 unique-constraint violation and
  return a clean 409 instead of leaking the raw error as 400
- restoreWorkspaceFileFolder: check if the parent folder is still archived
  before restoring; if it is, restore to root (parentId: null) so the folder
  is never orphaned under an archived parent

* feat(search): show folder path for files in cmd-k modal, strip extraneous comments

- FileItem interface with folderPath?: string[] added to search modal utils
- MemoizedFileItem component renders folder breadcrumb identically to
  MemoizedWorkflowItem — truncated path segments on the right with / separators
- FilesGroup rewritten as a dedicated memo component (was createIconGroup factory)
  so it accepts FileItem[] and includes folderPath segments in the search value
- searchModalFiles in sidebar splits f.folderPath string into string[] segments
- search-modal.tsx typed to FileItem and includes folderPath in filterAndSort
- Remove self-explanatory "Phase 1" section label from download route
- Remove redundant TSDoc on the unique index in db schema

* fix(workspace-files): audit fixes — transaction, status codes, contract refinements, guards

* fix(vfs): pass folderPath separately so buildWorkspaceMd groups files correctly

* fix(types): narrow unknown fileInput with Record cast after object guard

* fix(routes): replace instanceof Error with toError() across new workspace file routes

* improvement(files): cleanup pass — remove unnecessary useCallbacks, consolidate emcn icon imports

- Remove useCallback from 5 drag-event handlers in DataRow (passed to native <tr> elements, no observer)
- Remove stable useCallback fns from 3 useMemo deps arrays in files.tsx (editingId/editValue remain)
- Merge all @/components/emcn/icons subpath imports into barrel (files.tsx, action-bar, file-row-context-menu)

* fix(files): apply activeSort to folders, reject drop onto current parent folder

- visibleFolders now respects activeSort column (name/updated/created) and direction
  so folder ordering stays consistent with file ordering
- isInvalidDropTarget now returns true when all dragged items are already direct children
  of the target folder, preventing a no-op move mutation

* fix breadcrumb

* add new tools to rename, create, delete folders

* move more ui actions into orchestration dir

* address comments

* fix params

* fix tests

* address comments

* improve error codes

* address comments

* address more nits

* fix mcp server error code

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: waleed <walif6@gmail.com>
2026-05-15 12:05:03 -07:00
Theodore Li b1a87d531c Revert "improvement(db): add session statement/lock timeouts; simplify KB doc tx (#4593)" (#4599)
This reverts commit 4295a5c855.
2026-05-14 13:42:15 -04:00
4295a5c855 improvement(db): add session statement/lock timeouts; simplify KB doc tx (#4593)
* 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

* improvement(db): add session statement/lock timeouts; simplify KB doc tx

* fix(knowledge): close soft-delete TOCTOU on KB document insert

Fix the race the bots flagged: KB delete is soft (`deletedAt = now`) so
the FK can't catch a concurrent KB delete between the existence check
and the document insert.

- Add `insertDocumentsIfKbAlive` helper that gates the insert on
  `EXISTS(SELECT 1 FROM knowledge_base WHERE id=$kb AND deleted_at IS NULL)`
  in the same statement via INSERT...SELECT...WHERE EXISTS. Atomic at the
  MVCC snapshot — no transaction, no row lock.
- Use jsonb_to_recordset to declare column types once, avoiding per-param
  casts for nullable columns.
- Wire into both `createDocumentRecords` (bulk) and `createSingleDocument`.
- Keep the upfront KB existence check as a fast-path early-out for the
  common case; the atomic insert is the race guard.

---------

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>
2026-05-14 12:54:05 -04:00
Waleed b5dba82ac9 improvement(db): reduce connection saturation and egress hotspots (#4594)
* improvement(db): reduce connection saturation and egress hotspots

* fix(vfs): preserve native content type in copilot SQL projection

* fix(vfs): guard jsonb_array_elements against non-array contentBlocks
2026-05-13 23:39:59 -07:00
Waleed 1ed3a4ec6d feat(mothership): pin tasks to keep them at the top of the sidebar (#4582)
* feat(mothership): pin tasks to keep them at the top of the sidebar

* fix(sidebar): address PR review feedback for pin tasks

* fix(posthog): register task_pinned and task_unpinned events

* fix(tasks): insert new optimistic tasks below pinned partition
2026-05-13 14:46:04 -07:00
Waleed 39f74aa353 feat(data-drains): add GCS, Azure Blob, BigQuery, Snowflake, and Datadog destinations (#4552)
* feat(data-drains): add GCS, Azure Blob, BigQuery, Snowflake, and Datadog destinations

* fix(data-drains): address PR review comments

* fix(data-drains): extract sleepUntilAborted, honor abort across all destinations

* fix(data-drains): widen BigQuery projectId max and dedupe parseServiceAccount

* fix(data-drains): tighten GCS bucket contract and expose Azure endpointSuffix

* improvement(data-drains): extract normalizePrefix and buildObjectKey to shared utils

* fix(data-drains): retry BigQuery network errors; tighten Azure accountKey contract

- BigQuery insertAll now wraps the fetch in try/catch inside the retry loop so DNS failures, socket resets, and timeouts are retried with backoff instead of propagating immediately.
- Align azureBlobCredentialsBodySchema with the runtime schema (min 64 / max 120 / base64 regex) so obviously invalid keys are rejected at the API boundary rather than at drain-run time.

* improvement(data-drains): consolidate parseRetryAfter; add Datadog NDJSON line context

- Extract a single parseRetryAfter helper (capped at 30s, returns number | null) into lib/data-drains/destinations/utils.ts and remove the five local copies in bigquery, datadog, gcs, snowflake, and webhook.
- Datadog parseNdjson now wraps JSON.parse in try/catch and surfaces the failing line index, matching BigQuery's parser.

* fix(data-drains): correct Datadog size guard and Snowflake VARIANT limit

- Datadog payload guard now checks the uncompressed size against the 5 MB limit and the wire size against the 6 MB compressed limit, so gzip cannot smuggle an oversized body past the client-side check.
- Snowflake VARIANT limit is 16 MiB (16,777,216 bytes), not 16,000,000 bytes — small payloads between 16 MB and 16 MiB were being rejected unnecessarily.
- Drop the unused apiKey field on Datadog PostInput; the key is already embedded in the prepared request headers.

* improvement(data-drains): consolidate backoffWithJitter into shared utils

Datadog, GCS, and webhook each had byte-identical backoff helpers (BASE 500ms, MAX 30s, jitter ±20%, Retry-After floor). Lift the helper into lib/data-drains/destinations/utils.ts alongside parseRetryAfter and sleepUntilAborted, and drop the per-file copies and their BASE_BACKOFF_MS/MAX_BACKOFF_MS constants.

* fix(data-drains): align destinations with live provider specs

Audited every destination against live AWS/GCS/Azure/BigQuery/Snowflake/
Datadog/webhook docs and applied spec-correctness fixes:

- S3: reserved bucket prefix amzn-s3-demo-, suffixes --x-s3/--table-s3;
  metadata byte formula excludes x-amz-meta- prefix per AWS spec
- GCS: reject -./.- adjacency; UTF-8 prefix cap; forbid .well-known/
  acme-challenge/ prefix; ASCII-only x-goog-meta-* enforcement
- BigQuery: insertId is 128 chars (not bytes); split DATASET_RE (ASCII)
  and TABLE_RE (Unicode L/M/N + connectors); UTF-8 byte cap on tableId
- Snowflake: disambiguate org-account vs legacy locator account formats;
  requestId+retry=true for idempotent retries; server-side timeout=600;
  default column DATA uppercase to match unquoted canonical form
- Azure: endpoint suffix allowlist (4 sovereign clouds); accountKey
  length(88) base64
- Webhook: url max(2048); CRLF/NUL rejection on bearer/secret/sig header

* fix(data-drains): address PR review on snowflake poll + shared NDJSON parsing

- snowflake pollStatement: per-attempt timeout via AbortSignal.any, retry on 429/5xx with Retry-After + jitter
- bigquery parseNdjson error messages now 1-indexed
- consolidate parseNdjson variants into shared parseNdjsonLines/parseNdjsonObjects in utils

* fix(data-drains): per-attempt fetch timeouts in gcs/bigquery, snowflake poll double-sleep

- gcs.fetchWithRetry + bigquery.postInsertAll now use AbortSignal.any with a per-attempt timeout so a hung TCP connection cannot stall the drain
- snowflake.pollStatement skips the next interval sleep when it just slept for retry backoff

* fix(data-drains): bigquery probe timeout + jittered retries, align Snowflake column default UI/docs

- bigquery test() probe now uses AbortSignal.any + per-attempt timeout
- bigquery insertAll retry switches to backoffWithJitter for thundering-herd avoidance
- Snowflake column placeholder + docs say DATA (uppercase) to match the code default

* fix(data-drains): mirror webhook signingSecret min length in form gate

isComplete now requires signingSecret >= 32 to match the contract/runtime
schema so the Save button can't enable on a value that will fail server-side.

* fix(data-drains): validate JSON client-side for Snowflake before binding

Switch Snowflake to parseNdjsonObjects so malformed rows are caught locally
with 1-indexed line numbers instead of failing the whole INSERT server-side.
Re-stringify each parsed object before binding to PARSE_JSON(?).
Drop the now-unused parseNdjsonLines helper.

* fix(data-drains): cross-cutting audit pass against live provider docs

- Azure: bound retryOptions on BlobServiceClient (SDK default tryTimeoutInMs is per-try unbounded; cap at 30s x 5 tries)
- Webhook contract: mirror runtime — signingSecret.max(512), bearerToken.max(4096) + CRLF/NUL refine, signatureHeader charset + CRLF/NUL refine
- S3 (lib + contract): reject bucket names with dash adjacent to dot; require https:// endpoint at the schema layer
- Snowflake: bind original NDJSON line bytes (re-stringifying a JSON.parse'd value loses bigint precision beyond 2^53-1); check pollStatement 200 body for the SQL error envelope (sqlState/code)
- Datadog: entry builder writes defaults first then user attrs then forced ddtags/message so user rows can't clobber routing fields; validate config.tags as comma-separated key:value pairs
- registry.tsx: tighten isComplete predicates to mirror contract minimums (GCS bucket >= 3, Azure containerName >= 3 / accountKey === 88, BigQuery projectId >= 6, Snowflake account >= 3)

* fix(data-drains): force ddsource/service overrides on Datadog entries

Previous fix placed ddsource/service before ...attrs, leaving them clobberable
by a user row field. Per Datadog docs, service + ddsource pick the processing
pipeline, so a drain's routing config must not be overridable per-row. Spread
attrs first, then force all four reserved fields (ddsource, service, ddtags,
message).

* fix(data-drains): preserve row-distinguishing index when BigQuery insertId overflows

Truncating from the left dropped the index suffix, so any overflow would
collapse all rows in a chunk to the same insertId and BigQuery would silently
dedupe them. Path is unreachable today (UUIDs keep raw ~85 chars), but the
overflow branch is now correct: hash the prefix, keep the index intact.

* fix(data-drains): refresh GCS token per retry, tighten Azure key regex

- gcs: rebuild Authorization header per attempt via buildHeaders so token
  refresh from google-auth-library kicks in if a 5xx retry crosses the
  hour-long token lifetime
- azure_blob: pin account-key regex to {0,2} trailing '=' (base64 of 64
  bytes = exactly 88 chars with up to two '=' pad chars)

* fix(data-drains): address bugbot review of 6336948f6

- gcs: allow 1-char dot-separated bucket components (e.g. "a.bucket")
  to match GCS naming rules — overall name is 3-63 (or up to 222 with
  dots), but per-component minimum is 1 per Google's spec
- bigquery: drain the 401 response body before re-issuing the request
  with a refreshed token so undici can return the socket to the
  keep-alive pool
- snowflake: hoist getJwt() above the perAttempt timer in
  executeStatement so JWT signing doesn't eat the network budget
  (matches the order already used in pollStatement)

* fix(data-drains): allow org-account Snowflake identifier with region suffix

The account validation rejected `<orgname>-<acctname>.<region>.<cloud>`
because `ACCOUNT_LOCATOR_RE`'s first segment forbade hyphens, while
`ACCOUNT_ORG_RE` forbade dots. `normalizeAccountForJwt` already handles
this composite form. Widen the first segment of `ACCOUNT_LOCATOR_RE` to
allow hyphens so the boundary contract and the runtime schema accept
what the JWT layer was already designed to process.

* fix(data-drains): drain retryable response bodies in datadog/gcs loops

Mirrors the bigquery 401 fix. Without consuming the body before
sleeping, undici can't return the socket to the keep-alive pool, so
each retry leaks a TCP connection instead of reusing it.

* fix(data-drains): drain snowflake poll bodies on 202 and retryable status

Mirrors the bigquery/datadog/gcs drains. Long async statements can poll
many times against the same connection; without consuming the body
undici can't return the socket to the keep-alive pool, so each iteration
leaks a connection until GC.

* fix(data-drains): consume success bodies; check Snowflake sqlState on 200

- gcs: drain the body on success paths so undici can return the socket
  to the keep-alive pool
- snowflake: drain the body on synchronous 200 OK and run the same
  sqlState envelope check pollStatement already does — otherwise a
  statement-level failure that completes synchronously would silently
  return success

* fix(data-drains): drain datadog and bigquery probe success bodies

Same undici keep-alive issue as the prior fixes: postWithRetries
returned the Response on success without draining (callers only read
headers); the BigQuery `test()` probe returned without consuming the
body. Both now drain before returning.

* chore(data-drains): regenerate enum migration as 0206 after staging rebase

* fix(data-drains): cap snowflake poll retries; tighten datadog tags min length
2026-05-11 17:37:10 -07:00
Siddharth GanesanandTheodore Li 0b2cfaf7f7 feat(mothership): add superuser env selection (#4558)
* feat(table): live cell updates via SSE + per-table event buffer

Replaces the polling-based row refetch with a push-based SSE stream that
patches the React Query cache directly as cell-state events arrive.

Architecture:
- New per-table event buffer in apps/sim/lib/table/events.ts. Redis sorted-set
  with monotonic eventId, 1h TTL, 5000-event cap, in-memory fallback. Modeled
  after apps/sim/lib/execution/event-buffer.ts but stripped of complexity
  tables don't need (no per-execution lifecycle, no id-batching, no write
  queue serialization). ~150 lines instead of 700.
- writeWorkflowGroupState appends a fat event after each successful 'wrote'.
  Status transitions carry executionId + jobId; terminal/partial transitions
  also include the new output values inline so the client can patch row data
  without a follow-up refetch.
- New SSE route at /api/table/[tableId]/events/stream?from=<lastEventId>.
  Replays from buffer on connect, polls at 500ms (mirrors workflow execution
  stream), heartbeat every 15s, signals 'pruned' if the caller fell off the
  back of the buffer.
- Client hook useTableEventStream subscribes via EventSource. Reconnect-resume
  with last-seen eventId. On 'pruned', invalidates the rows query and resumes
  from the new earliest. Cache patches walk every cached query under
  rowsRoot(tableId) so filter/sort variants all stay live.
- Removes refetchInterval from useTableRows and the per-page polling effect
  from useInfiniteTableRows. React Query's refetchOnWindowFocus +
  refetchOnReconnect cover the durability gap if any push is dropped.

Out of scope:
- Bulk-cancel events (cancellation path is being redesigned separately).
- Generalizing the workflow event-buffer module to a shared primitive (defer
  until a third use case appears; for now the table buffer is the simpler
  cousin of the workflow one).

* fix(table): drop run-mutation refetch so SSE patches aren't overwritten

useRunColumn.onSettled was canceling in-flight queries and invalidating the
rows query — leftover behavior from the polling era. With the SSE stream
now keeping the cache live via incremental patches, this refetch races the
stream and snaps the cache back to whatever DB shows at the refetch moment,
which can lag the just-arrived queued/running events. Cells appeared stuck
on the optimistic 'pending' even though the SSE was delivering the real
transitions.

* chore(table): simplify SSE plumbing — reuse helpers, drop dead polling code

- Reuse snapshotAndMutateRows for SSE cache patches instead of reimplementing
  the page-walk + cache-shape detection. Adds a {cancelInFlight: false} opt
  for the SSE caller (mutations still cancel as before).
- Drop client-side type duplication in use-table-event-stream — import
  TableEvent and TableEventEntry from lib/table/events directly.
- Drop the now-dead mergePagePreservingIdentity + rowEqual from tables.ts;
  their only caller was the polling effect that was removed earlier.
- Drop the defensive try/catch around appendTableEvent in cell-write — the
  function is documented as never-throwing (returns null on failure).
- Combine INCR + ZADD into one Lua eval in events.ts. Halves Redis RTT per
  cell-write. Lua returns the new eventId; the script splices it into the
  pre-built entry JSON.
- Trim refs to plain let bindings inside the effect; trim stale
  comments referencing the old polling implementation.

* fix(table): address PR review on SSE buffer

- TTL-expiry silent miss: when all keys expire, hgetall(meta) returns empty
  so earliestEventId is undefined and the prune branch was skipped. Reconnect
  with non-zero afterEventId now checks the seq counter — its absence (TTL
  expired) signals pruned so the client refetches. Memory fallback mirrors.
- Unbounded ZRANGEBYSCORE: cap reads at TABLE_EVENT_READ_CHUNK = 500 events
  per call. The route's 500ms poll loop drains chunks across ticks instead of
  flushing 5000 entries (multi-MB) in one tick after a long disconnect.
- Pruned handler closes EventSource client-side: server-side close was firing
  onerror and routing through the 500ms backoff path. Now we close
  proactively, reset the reconnect attempt counter, and reconnect immediately
  from the new earliest.

* Cross env copilot

* Force deploy

* Run migration

* Updates

* Fix migration

* Redeploy

* Make dev db push

* restore old migs

* Cross env copilot

* Add custom tools, skills, mcps to mothership

* Update migration

* Fix migs

* UPdate

* Fix types

* Fix

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-05-11 16:58:48 -07:00
WaleedandCursor 3ed8615b5d chore(cleanup): react-doctor dead code elimination, landing + docs overhaul, component modernization (#4544)
* fix(react-doctor): remove unused export types, useEffect clearTimeout missing, a11y fixes

* fix(react-doctor): strip unused export types from contracts, copilot, stores, and components

Remove export keyword from type/interface declarations confirmed to have zero importers
across lib/api/contracts/tools/aws/, lib/api/contracts/*.ts, lib/copilot/generated/,
stores/workflows/workflow/types.ts, ee/access-control, ee/data-retention, lib/logs/types.ts,
and app/workspace component files. TypeScript and API validation both pass clean.

Reduces unused-types count from 394 → 181 and fully eliminates the ✗ critical
dead-code categories (exports, types, files now show as ⚠ warnings not ✗ errors).

* docs improvements

* fix(react-doctor): delete 50 unused files (dead barrels, unreachable components, stale utilities)

Remove confirmed-unused barrel index.ts files across stores/, connectors/, executor/,
lib/, and app/workspace/ that had zero importers. Also delete unreachable components
(chat-history-skeleton, trace-spans, logs-list, template-profile, enterprise landing
sections), stale utilities (buffered-stream, blob-to-data-url, queued-workflow-execution,
compute-edit-sequence), and obsolete generated/contract files. TypeScript passes clean.

* remove dead code

* cleanup

* more

* fix(blog): restore DiffControlsDemo for v0-5 blog post

* fix: restore ContactButton for enterprise blog post, export WIKIPEDIA_PAGE_CONTENT_OUTPUT_PROPERTIES

* fix(react-doc): restore stripped exports and remove server-only dependency

* chore(deps): update lockfile after removing server-only

* added back some exports

* docs

* more

* fix type issues

* tc

* fix docs search route

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(tag-dropdown): add missing isEqual import from es-toolkit

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 12:00:05 -07:00
WaleedandClaude Opus 4.7 d721dc3358 feat(enterprise): add data drains for continuous export to S3 / webhook (#4440)
* feat(enterprise): add data drains for continuous export to S3 / webhook

* chore(data-drains): regenerate migration on top of staging + bump route baseline

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(data-drains): clarify retention pairing is user-coupled, not enforced

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(data-drains): preserve explicit forcePathStyle=false + reserve x-sim-signature

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(data-drains): drift guard ensures every webhook header is reserved

Asserts that any header buildHeaders writes is rejected when reused as a
custom signatureHeader. Adding a new metadata header without mirroring it
into RESERVED_SIGNATURE_HEADER_NAMES now fails CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 18:04:02 -07:00