Skip to content

v0.8.48: search improvements, file versions - #8008

Closed
waleedlatif1 wants to merge 34 commits into
mainfrom
staging
Closed

waleedlatif1 wants to merge 34 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

icecrasher321 and others added 7 commits September 18, 2026 22:25
* docs(blog): update enterprise

* Pi Babysit: address PR #8002 feedback

* Change author in enterprise blog index

Updated author name from 'andrew' to 'vik'.

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
Co-authored-by: Waleed <walif6@gmail.com>
… from task failures (#8004)

* fix(file-search): skip base64-dominated files and redact query errors from task failures

* fix(file-search): count wrapped base64 blocks and verify trigram estimate against pg_trgm
… MCP (#7997)

* feat(files): workspace file version history over the v2 API, CLI, and MCP

* fix(files): address review feedback on file version history

* chore(db): regenerate file version migration after staging's 0364

* improvement(files): scope version rows to the write's workspace and narrow stored provenance status

* fix(files): release purged file history atomically with restore

* fix(files): release file history through the outbox after the current object is gone

* fix(files): release purged history inside the file-row purge transaction

* fix(files): resolve metadata version from the record's own storage key

* fix(files): never pair a stale file record with a newer version number

* improvement(files): simplify file version history internals

- read metadata and its current version in one statement; drop the retry and 409
- move provenance policy branching into the provenance module
- project stored provenance out of list, head, and get reads; revert reads it on demand
- chunk storage-cleanup enqueues to the outbox bulk limit in one place
- drop the write-only content_updated_at version column (unreleased 0365)
- reuse findCause, the shared cleanup batch constants, and the version-number primitives
- share the v2 text presenter between the file and version routes
…8006)

* improvement(knowledge): remember a caller's saturated search reach

A caller whose tokens reach more documents than the permitted-set limit
paid the reach count on every search only to learn again that the set is
unbounded. That answer is now remembered per bases and token set for five
minutes. The probe now reports saturation apart from a timeout, and only
saturation is remembered; an unbounded set only means the legs apply the
full access predicate per candidate, so a stale answer costs speed, never
access.

* improvement(knowledge): keep the permitted-set resolver's TSDoc on its function
)

* fix(knowledge): keep Slack searchable while its member crawl runs

Members-mode search shows a document only while its member observation is
younger than a day, but a full Slack listing re-fetched every thread and so
took far longer than a day on a large workspace, leaving most of Slack
invisible. Access is now renewed per channel the member can still read, and
listings re-read only threads whose root changed, are active, or are due in a
rolling 28-day refresh.

* fix(knowledge): keep scope renewal due when a channel listing is cut off

A Slack conversation listing that stops at its page cap now reports itself
incomplete, and the member's renewal watermark only advances once every
reachable channel has been listed and renewed.

* fix(knowledge): restart listings for Full resync and reset renewal on identity change

A Full resync now starts a new full-sync listing even when the connector
does not rehydrate, so Slack rereads every thread instead of resuming an
ordinary cursor. A member whose identity changes or whose token is
rejected also loses its scope-renewal watermark, so renewal runs for the
new identity right away.

* fix(knowledge): resume member scope renewal across runs

Accessible scopes are now listed page by page, and a renewal that does not
finish within its budget stores its channel-listing cursor and start time,
so the next run continues from there instead of re-reading the first pages
and never reaching channels past them. The watermark records when the whole
pass began, and an expired cursor restarts the pass once.

* fix(knowledge): batch scope renewal and keep refreshed metadata

Scope renewal now gathers container pages into batches before scanning the
member's stale observations, so the scan runs once per batch instead of once
per source page, and an unfinished batch resumes from where it was read.
Content that hydrates unchanged under a new hash also refreshes its source
URL, modified time and tags, and the member sync log records how many
observations renewal kept fresh.
)

A member who reaches more of an organization search index than an exact
ranking can afford searched keywords through the GIN projection, which
scores every chunk matching the term before access is checked, so a common
word cost seconds. Where the database provides the tin extension, a BM25
projection of search-index chunks is kept by embedding and knowledge-base
triggers, and the keyword leg ranks with Tin first and checks access only on
the top of that ranking, widening the window while too few ranked chunks are
readable and leaving a page to GIN if the widest window cannot fill it.

The query is analyzed by the same websearch_to_tsquery as the GIN path and
translated to TINQL; shapes TINQL cannot express keep GIN. The path is gated
by the knowledge-tin-keyword flag, a valid Tin index (built only after the
backfill completes), and every base being a search index. Script migration
0019 installs the projection only where tin is available and creatable, so
self-hosted databases keep an empty table and the GIN path.
@vercel

vercel Bot commented Sep 19, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
docs Ready Ready Preview Sep 20, 2026 2:11am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 1/5

The PR is not safe to merge until Slack reply deletions stop remaining searchable for weeks and database-query redaction removes the raw parameter-bearing cause from Trigger task failures.

Findings

  1. P1 Security Slack replies remain searchable
  2. P1 Security Redaction preserves sensitive cause
  3. P2 Indented base64 bypasses detection

Summary

This PR adds workspace-file version history across storage, v2 APIs, CLI/MCP, retention, and audit surfaces; revises Slack synchronization and member-access renewal; introduces Tin-backed organization keyword ranking; and hardens workspace-file search indexing.

  • Adds version creation, listing, reading, downloading, reverting, deleting, and retention cleanup.
  • Changes Slack thread hashing and scheduled hydration to reduce redundant source reads.
  • Adds Tin keyword projections, migration/backfill support, and search fallback logic.
  • Excludes base64-dominated files and redacts database failures from indexing tasks.
  • Includes substantial API, migration, integration-test, documentation, and generated-client updates.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Workspace file write] --> B[Version transaction]
  B --> C[(workspace_file_version)]
  B --> D[Current file object]
  C --> E[v2 API / CLI / MCP]
  C --> F[Retention cleanup]
  F --> G[Storage cleanup outbox]

  H[Slack listing] --> I{Root changed, active,<br/>or rolling refresh?}
  I -->|Yes| J[Hydrate thread]
  I -->|No| K[Reuse indexed document]
  J --> L[Knowledge index]
  K --> L

  M[Organization keyword query] --> N{Tin ready?}
  N -->|Yes| O[Tin candidate ranking]
  N -->|No| P[GIN fallback]
  O --> Q[Access filtering and hydration]
  P --> Q
Loading

Reviews (1) · Last reviewed commit: "improvement(knowledge): rank organizatio..."

Comment on lines +816 to +819
const reread =
syncContext?.fullSync === true ||
now / 1000 - lastRootActivity(message) < ACTIVE_THREAD_REFRESH_SECONDS ||
dueForRollingRefresh(externalId, now)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Slack replies remain searchable

For quiet threads, this code skips hydration whenever the root-derived version is unchanged. That version includes only the root edit timestamp, reply count, and latest-reply timestamp, so editing or deleting an existing reply does not change it. Once the seven-day active window passes, stale or deleted reply text can remain indexed and searchable until the thread's one-in-28-day refresh.

How this was verified: The listing hash excludes reply edit and deletion state, and a matching root version is classified as current without hydrating the thread.

Knowledge Base Used:

Comment on lines +22 to +24
return new Error(`${operation} failed (${reason ? `${code}, ${reason}` : code})`, {
cause: error,
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Redaction preserves sensitive cause

The sanitized error still attaches the original Drizzle error as its cause, even though that error carries the SQL and bound file text this helper is meant to redact. The indexing task throws this wrapper to Trigger.dev, so cause-chain serialization can retain confidential file content. Equivalent background database-error paths avoid this by throwing a cause-free diagnostic.

How this was verified: The wrapper directly retains the original parameter-bearing Drizzle error as its cause before the indexing task throws it.

Comment on lines +46 to +53
if (run === 0) runFillsLines = lineChars === 0
run++
lineChars++
upper ||= isUpper
lower ||= isLower
digit ||= isDigit
} else if (code === 10) {
if (!(run > 0 && runFillsLines && lineChars >= FILE_SEARCH_ENCODED_WRAP_MIN_CHARS)) endRun()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Indented base64 bypasses detection

Wrapped base64 is joined across lines only when the run starts at column zero. Common indented 64- or 76-column payloads in YAML, generated source, and nested text are therefore split into runs shorter than 256 characters and report no encoded bytes. These base64-dominated files proceed into the expensive trigram index instead of being skipped. Please track wrapped runs independently of leading indentation and add coverage for an indented payload.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 137 files

Confidence score: 2/5

  • apps/sim/lib/core/errors/database-query-error.ts can expose SQL and bound parameters through the DrizzleQueryError cause chain, allowing indexed file content to bypass redaction; omit the original query error as cause.
  • apps/sim/lib/knowledge/search/tin-keyword.ts may retain a true cache value for ten minutes after Tin rows are deleted, causing selectTinPage to treat empty pages as successful and return no matches; invalidate or refresh this state when an index is demoted.
  • apps/sim/connectors/slack/slack.ts can treat a quiet thread as current from rootVersion even after an existing reply is edited or deleted, returning stale thread data; track reply edit/deletion state or force hydration before accepting the listing.
  • apps/sim/lib/workspace-files/search/index-plan.ts splits indented wrapped payloads at every newline when runFillsLines is false, so base64-heavy files may evade exclusion; track payload line width independently of indentation.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/lib/knowledge/search/tin-keyword.ts">

<violation number="1" location="apps/sim/lib/knowledge/search/tin-keyword.ts:39">
P1: When a search index is demoted, this cache can keep `true` for ten minutes after the migration trigger deletes its Tin rows. `selectTinPage` treats `ranked = 0` as a successful page, so searches return no matches instead of falling back to GIN. Invalidate membership on `isSearchIndex` changes or avoid caching positive entries.</violation>
</file>

<file name="apps/sim/lib/core/errors/database-query-error.ts">

<violation number="1" location="apps/sim/lib/core/errors/database-query-error.ts:23">
P1: Do not attach the original query error as `cause` here. `DrizzleQueryError` retains SQL and bound parameters, so the indexing task can still expose file content through the supposedly redacted cause chain.</violation>
</file>

<file name="apps/sim/lib/workspace-files/search/index-plan.ts">

<violation number="1" location="apps/sim/lib/workspace-files/search/index-plan.ts:46">
P2: Indented wrapped payloads are split at every newline because `runFillsLines` is false after leading whitespace. Track payload line width independently of indentation so base64-dominated files are excluded from the expensive trigram index.</violation>
</file>

<file name="apps/sim/connectors/slack/slack.ts">

<violation number="1" location="apps/sim/connectors/slack/slack.ts:545">
P1: A quiet thread is marked current from `rootVersion` alone, but Slack does not change that value when an existing reply is edited or deleted. Track reply edit/deletion state or force hydration before accepting this listed hash, otherwise stale reply text remains searchable.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/sim/app/api/v2/files/[fileId]/metadata/route.ts
/** Only organization search indexes are projected; `is_search_index` is fixed at creation. */
const searchIndexBases = new LRUCache<string, boolean>({
max: 10_000,
ttl: SEARCH_INDEX_TTL_MS,

@cubic-dev-ai cubic-dev-ai Bot Sep 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a search index is demoted, this cache can keep true for ten minutes after the migration trigger deletes its Tin rows. selectTinPage treats ranked = 0 as a successful page, so searches return no matches instead of falling back to GIN. Invalidate membership on isSearchIndex changes or avoid caching positive entries.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/knowledge/search/tin-keyword.ts, line 39:

<comment>When a search index is demoted, this cache can keep `true` for ten minutes after the migration trigger deletes its Tin rows. `selectTinPage` treats `ranked = 0` as a successful page, so searches return no matches instead of falling back to GIN. Invalidate membership on `isSearchIndex` changes or avoid caching positive entries.</comment>

<file context>
@@ -0,0 +1,95 @@
+/** Only organization search indexes are projected; `is_search_index` is fixed at creation. */
+const searchIndexBases = new LRUCache<string, boolean>({
+  max: 10_000,
+  ttl: SEARCH_INDEX_TTL_MS,
+})
+
</file context>
Fix with cubic

const code = getPostgresErrorCode(queryError) ?? 'no error code'
const reason = getPostgresCancellationReason(queryError)
return new Error(`${operation} failed (${reason ? `${code}, ${reason}` : code})`, {
cause: error,

@cubic-dev-ai cubic-dev-ai Bot Sep 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Do not attach the original query error as cause here. DrizzleQueryError retains SQL and bound parameters, so the indexing task can still expose file content through the supposedly redacted cause chain.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/core/errors/database-query-error.ts, line 23:

<comment>Do not attach the original query error as `cause` here. `DrizzleQueryError` retains SQL and bound parameters, so the indexing task can still expose file content through the supposedly redacted cause chain.</comment>

<file context>
@@ -8,3 +8,18 @@ import { DrizzleQueryError } from 'drizzle-orm/errors'
+  const code = getPostgresErrorCode(queryError) ?? 'no error code'
+  const reason = getPostgresCancellationReason(queryError)
+  return new Error(`${operation} failed (${reason ? `${code}, ${reason}` : code})`, {
+    cause: error,
+  })
+}
</file context>
Fix with cubic

const next = parseContentHash(candidate)
const previous = parseContentHash(stored)
if (!next || previous?.kind !== 'hydrated') return 'stale'
if (next.kind === 'listed') return previous.version === next.version ? 'current' : 'stale'

@cubic-dev-ai cubic-dev-ai Bot Sep 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: A quiet thread is marked current from rootVersion alone, but Slack does not change that value when an existing reply is edited or deleted. Track reply edit/deletion state or force hydration before accepting this listed hash, otherwise stale reply text remains searchable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/connectors/slack/slack.ts, line 545:

<comment>A quiet thread is marked current from `rootVersion` alone, but Slack does not change that value when an existing reply is edited or deleted. Track reply edit/deletion state or force hydration before accepting this listed hash, otherwise stale reply text remains searchable.</comment>

<file context>
@@ -403,7 +448,142 @@ function listingToken(syncContext?: Record<string, unknown>): string {
+  const next = parseContentHash(candidate)
+  const previous = parseContentHash(stored)
+  if (!next || previous?.kind !== 'hydrated') return 'stale'
+  if (next.kind === 'listed') return previous.version === next.version ? 'current' : 'stale'
+  return previous.text === next.text ? 'equivalent' : 'stale'
+}
</file context>
Fix with cubic

Comment thread apps/sim/background/cleanup-file-versions.ts Outdated
Comment thread apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-cleanup-outbox.ts Outdated
Comment thread apps/sim/lib/uploads/contexts/workspace/workspace-file-versions.ts
const isLower = code >= 97 && code <= 122
const isDigit = code >= 48 && code <= 57
if (isUpper || isLower || isDigit || code === 43 || code === 47) {
if (run === 0) runFillsLines = lineChars === 0

@cubic-dev-ai cubic-dev-ai Bot Sep 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Indented wrapped payloads are split at every newline because runFillsLines is false after leading whitespace. Track payload line width independently of indentation so base64-dominated files are excluded from the expensive trigram index.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/workspace-files/search/index-plan.ts, line 46:

<comment>Indented wrapped payloads are split at every newline because `runFillsLines` is false after leading whitespace. Track payload line width independently of indentation so base64-dominated files are excluded from the expensive trigram index.</comment>

<file context>
@@ -2,11 +2,79 @@ import { Buffer } from 'node:buffer'
+    const isLower = code >= 97 && code <= 122
+    const isDigit = code >= 48 && code <= 57
+    if (isUpper || isLower || isDigit || code === 43 || code === 47) {
+      if (run === 0) runFillsLines = lineChars === 0
+      run++
+      lineChars++
</file context>
Fix with cubic

Comment thread packages/testing/src/mocks/schema.mock.ts Outdated
* fix(files): bound file version retention per run and document both coalescing cutoffs

* fix(files): agree on the current version when a write skipped recording, and tidy CLI version output

* fix(files): size each retention batch to the remaining run allowance

* fix(files): read file versions against one snapshot of the file
* fix(slack-search): reuse manifest installation in setup

* fix(slack-search): register token connection in operation test
BillLeoutsakosvl346 and others added 3 commits September 19, 2026 11:55
* refactor(ui): reuse EMCN icons for simple product glyphs

* docs(emcn): add CircleStop usage example

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
…8019)

* feat(google-calendar): add RSVP operation to respond to invitations

* fix(google-calendar): honor cancellation on RSVP write and tighten outputs

* fix(google-calendar): verify the RSVP note was saved
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
* fix(realtime): validate cursor and selection presence payloads

The workflow cursor-update and selection-update handlers stored whatever
object a client sent into the shared room presence hash and rebroadcast it
to every peer, with no shape or size check. An authenticated user with read
access to any workflow could park a multi-megabyte blob in shared Redis on
every socket they opened and have the server fan it out on each presence
broadcast.

Both payloads are now rebuilt from a fixed field set before they reach room
state or any broadcast, so unexpected keys cannot ride along - mirroring
normalizeCellSelection in the table presence handler. Adds a defensive
per-field length cap in updateUserActivity so future presence-bearing events
inherit the bound, and marks UserPresence.cursor nullable to match the
cleared-cursor value the client already sends.

* fix(realtime): measure presence field cap in utf-8 bytes

The cap compared UTF-16 code units against a byte budget, so a multi-byte
payload could pass the check and still land several times larger in the
room hash. It now measures the UTF-8 bytes Redis actually stores.

Raises the ceiling to 16384. A table cell selection carries four ids capped
at 200 characters each, and multi-byte characters plus JSON escaping can
expand a legitimate worst case to roughly 5 KB - above the previous 4096,
so the old bound could have dropped real presence.
…d tool route (#8042)

Tool results reach the model over two lanes, but the model-facing projection was
applied on only one. The resume lane runs results through getToolCallTerminalData,
which reduces generate_api_key to its status message. The in-band route
(POST /api/copilot/tools/execute) returned the handler output verbatim, so the
freshly minted plaintext workspace API key crossed to the model and into the turn
transcript.

The egress projection cannot cover this: its registry is a catalog of pre-existing
environment and credential secrets, built once per turn, so a key minted mid-turn is
invisible to it.

Apply toolResultForModel at the route so both lanes return the same model-facing
projection. It is an identity for every other tool.
* feat(memory): preserve durable Agent tool history

* docs(agent): explain durable tool history and context limits

* feat(memory): bound durable context and retrieve retained tool detail

* fix(memory): validate checkpoint recovery and bounded summary coverage

* chore(db): format durable memory migration metadata

* docs(memory): remove redundant internal README

* fix(memory): preserve tool loops and harden durable history

* fix(memory): preserve stream usage and bound portable history

* fix(memory): surface bounded history and validate replay inputs

* fix(memory): retain legacy function call exchanges

* fix(memory): admit only complete stored tool exchanges
Comment on lines +56 to +87
JSON.stringify({
providerId,
model: request.model,
endpoint:
request.azureEndpoint ??
(providerId === 'azure-openai' ? env.AZURE_OPENAI_ENDPOINT : undefined) ??
(providerId === 'vllm'
? env.VLLM_BASE_URL
: providerId === 'litellm'
? env.LITELLM_BASE_URL
: providerId === 'ollama'
? getOllamaUrl()
: undefined),
apiVersion:
request.azureApiVersion ??
(providerId === 'azure-openai' ? env.AZURE_OPENAI_API_VERSION : undefined),
systemPrompt: request.systemPrompt,
systemMessages: request.messages?.filter((message) => message.role === 'system'),
context: request.context,
account: {
apiKey: request.apiKey,
accessKey: request.bedrockAccessKeyId,
secretKey: request.bedrockSecretKey,
},
project: request.vertexProject,
location: request.vertexLocation,
region: request.bedrockRegion,
tools: request.tools?.map(getConfiguredConversationToolBinding),
responseFormat: request.responseFormat,
reasoningEffort: request.reasoningEffort,
thinkingLevel: request.thinkingLevel,
})
…#8044)

* improvement(copilot): refuse approval-gated tools on the in-band lane

Copilot's approval gate is scaffolding today: COPILOT_TOOL_PERMISSIONS_ENABLED is off
by default, so nothing is gated on any lane. It is built only on the dispatch lane,
which holds a call against a streaming context and a decision row and then declines
to dispatch anything the mothership marks in-band. Those calls run via
POST /api/copilot/tools/execute, which has no context and no waiter, so turning the
flag on would gate the foreground and leave background lanes ungated — a gate that
looks enforced but is not.

Add toolRequiresApprovalLane next to toolCallNeedsApproval so the covered tool set is
defined once, and refuse a gated tool at the in-band route before it runs. Refuse
rather than block: a background lane must never hang on a prompt with no row behind
it. The check deliberately ignores the stored auto-allow list — an auto-allowed tool
sent to the checkpoint lane is admitted there without prompting anyone, so reading it
here would only add a database read to reach the same place.

Inert while the flag is off, which is the state this ships in; a test pins that.
Also record on the flag itself that the gate is a property of the lane, since that is
what the next person reads before enabling it.

* improvement(copilot): move the approval-lane predicate beside the tool router

Importing the dispatch gate module for a one-line predicate pulled the permission
persistence layer in with it, whose module body opens a pub/sub channel — two Redis
clients and a channel subscription — in every process that loads the in-band route.

Move toolRequiresApprovalLane to tool-executor/router.ts, which imports only the
catalog. The route already imported @/lib/copilot/tool-executor for
ensureHandlersRegistered, so the guard now costs no new import edge at all. The
dispatch gate keeps a pointer to it.

Its flag-and-catalog behavior is covered in the router tests against the real flag and
the real catalog; the route tests keep to what the route does with the answer.
Extend the Confluence attachment allowlist so .pptx and .xlsx files on
synced pages and blog posts are listed and handed to the shared parser
pipeline the same way PDF and Word attachments already are. Macro-enabled,
template, legacy binary and OpenDocument variants stay excluded.

Add listing, hydration, genuine-bytes roundtrip and renamed-to-unsupported
coverage, and update the connector guides to name the new formats.
waleedlatif1 and others added 2 commits September 19, 2026 17:54
* fix(ui): align fallback model subblock styling

* fix(ui): keep editor pickers on shared combobox styling
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
* improvement(ui): reuse chip inputs for remaining settings fields

* improvement(ui): reuse the shared branding upload drop zone (#8031)

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
…ke (#8052)

* improvement(utils): add escapeRegExp and compareStrings to @sim/utils/string

* improvement(utils): replace nine local escapeRegExp copies with the shared helper

* improvement(utils): adopt shared compareStrings and isRecordLike over local copies

* docs: document the shared escapeRegExp, compareStrings, and isRecordLike helpers

* improvement(utils): finish the escapeRegExp sweep and share the metacharacter class

Replaces the nine inline copies of the escape body the name-based sweep missed,
folds the catalog cursor comparator into compareStrings, and gives linear-regex
its metacharacter test from the same source the escaper uses.
…ord coercions (#8053)

* improvement(utils): adopt toRecord and toRecordOrNull over inline record coercions

* improvement(utils): route the remaining record-coercion helpers through toRecord

The first pass matched one operand order, so six exact equivalents written
`x !== null && typeof x === 'object'` survived — two of them beside a sibling
the pass had already deleted. Domain-named wrappers keep their names and
delegate, matching the microsoft-teams client that already did.
* refactor(ui): share log and enrichment details panels

* fix(ui): prevent focus in closed details panels

* refactor(ui): use CSS-variable sizing for details panels

* style(ui): make resize calculation spacing explicit

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants