Vikhyath Mondreti 2e111f615c fix(search): answer a Note match on the canvas card (#6901)
* fix(search): answer a Note match on the canvas card

A workflow search match inside a Note counted towards the result total and
then highlighted nowhere: the editor panel renders nothing for a Note, and
`clearCurrentBlock` — which the panel calls to refuse one — also cleared the
shared `activeSearchTarget`, destroying the very target the card was about to
paint. Searching a 15k-character note reported "1 of 6" and moved nothing.

The card's read view is now the surface that answers:

- A rehype plugin marks every rendered occurrence; the current one is picked by
  an ordinal counted with the same scan the indexer uses, and travels by context
  so cycling matches does not re-parse the document.
- `<Streamdown>` is keyed on the query. Its memo comparator ignores
  `rehypePlugins`/`components`, so a plugin change alone cannot re-render it —
  marks appeared only when something else remounted the card, and then outlived
  the query that produced them.
- The canvas selects, centres and expands the Note, because a compact card
  resets its scroll region to the top and cannot hold a position deep in its
  own body. Scrolling to the mark is `scrollTop` arithmetic, never
  `scrollIntoView`, which would drag ReactFlow's transformed viewport off-frame.
- Title matches mark the name too.

`activeSearchTarget` is re-published with a fresh identity on most of the search
panel's renders, so subscribers take primitives. Holding the object in
`WorkflowContent` — the panel's own ancestor — closed an unbounded update loop.

Separately, the serializer escaped every underscore, writing
`SB\_ACTION\_ROUTER\_SECRET` into the document. CommonMark's intraword rule
means that backslash carries no meaning, and search matches the stored markdown,
so it made anything with an underscore unfindable in a note that plainly showed
it. Dropped outside code regions, where the serializer emits verbatim and a
backslash is the author's own character.

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

* fix(search): honour fences and folded whitespace in Note highlighting

Two review findings, both real.

The intraword-underscore cleanup guarded code with a pattern that recognised
only the shortest delimiter forms — a bare ``` pair and a single-backtick span.
A ````-fenced block, a tilde fence, or a ``multi-backtick`` span ended the
region early and handed the rest of the author's code to the rewrite, turning
`a\_b` into `a_b` inside their code sample. Fenced blocks are now walked a line
at a time, tracking the opening delimiter exactly the way stripEmptyListItemLines
already does (three or more, closed only by a run at least as long), and the
inline branch matches a backtick RUN closed by one of equal length.

The note scanner claimed to be the same scan as the indexer's `findTextRanges`
but did not fold whitespace, which staging added since this branch was written.
The indexer folds every `\s` to a space, so a phrase matches across a soft line
break — which `remark-breaks` renders as a `<br>`, splitting the phrase over two
text nodes that a per-node scan could never see. The hit counted in the panel
and highlighted nowhere, the exact bug this branch exists to fix.

The plugin now scans runs of continuously-readable text rather than single
nodes, so a match spanning an inline boundary (a soft break, a bold word) is
wrapped as several marks sharing one ordinal. Runs end at any non-inline
element, so two paragraphs are never joined into a phrase the reader cannot see.

`foldSearchWhitespace` moved to `@sim/utils/string`: the canvas card renders
from a package, which cannot import from `apps/*`, and two copies of that rule
silently disagreeing is precisely what produced the second finding.

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

* fix(markdown): close a fence only on a bare delimiter run

A closing fence carries nothing but its delimiter run; a line that merely
starts with one is content. The guard matched the prefix alone, so an interior
line like ` ````example ` inside a same-length fence ended the block, and every
cleanup below then processed the author's remaining code as prose — dropping
the backslashes from their `a\_b`.

Both fence walks in this file shared that flaw, so both now go through one
`closesFence`, which requires the run to be followed by nothing but whitespace.
Strictly more conservative: a fence stays open longer, so more content is left
verbatim.

Scope, stated plainly: the serializer always opens a block with one more
delimiter than the longest run inside it, so its own output cannot reach this
shape today, and `postProcessSerializedMarkdown` only ever sees serializer
output. This is a correctness fix that removes an unstated coupling to that
choice, not a live corruption path. The tests therefore exercise
`postProcessSerializedMarkdown` directly — a round-trip test of the same input
would pass either way, which is exactly the vacuous check worth avoiding.

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

* fix(markdown): skip quoted fences, and stop joining runs across inline tags

Two review findings, both real, both the same shape: a rule that looked at the
rendered form and forgot what the source actually says.

QUOTED FENCES. The fence walk only recognised a bare delimiter run, but the
serializer writes a fence inside a blockquote or a `[!NOTE]` callout with a `>`
on every line. Code state was therefore never entered there and the block's
interior was cleaned up as prose: `> x = a\_b` round-tripped to `> x = a_b`,
losing the author's backslash. Unlike the fence-length cases this one is
reachable today — verified against the real serializer before and after. Both
fence walks now unquote the line first.

INLINE JOINS. Runs concatenated the visible text of every inline tag, so
`a<strong>b</strong>c` read as `abc` — a hit that cannot exist in the markdown
the indexer scans, where `**` sits between the words. That is worse than a
spurious mark: `occurrenceIndex` counts SOURCE occurrences, so a fabricated hit
earlier in the document steals the current ordinal and paints the mark on text
the search never matched. Only `<br>` continues a run now, because it alone
stands for a character the source really has (a `\n`, folded to a space).
Everything else stands for syntax the render drops.

Nothing real is lost: a match spanning `a**b**c` would have to contain the
asterisks to exist at all, and a match wholly inside an element is still found —
the element simply starts its own run.

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

* fix(search): match a Note body as it renders, instead of rewriting the file

Replaces the serializer change with one that writes nothing.

The editor backslash-escapes every markdown-significant character in prose, so a
Note the reader sees as `{{TE_SERET}}` is stored as `{{TE\_SERET}}` and search —
which matches the stored value — could not find it. The previous approach undid
that escape in `postProcessSerializedMarkdown`, which meant re-deriving markdown
structure from the serialized string with regexes so it knew what was code. That
is a losing game: three review rounds, each finding another construct it did not
model (longer fences, then delimiter-prefixed lines, then quoted fences), and
each miss REWROTE somebody's code. `markdown-fidelity.ts` is back to staging,
byte for byte.

The escape is now undone on the matching side only. A field declares
`searchTextFormat: 'markdown'` (the Note body is the only one), and the indexer
matches it against `projectEscapedMarkdownForSearch(value)` — a total,
structure-free function that returns the rendered text plus an index back into
the source. Ranges stay in source coordinates, so replace still rewrites the
whole `\_` and never strands a backslash.

The asymmetry is the whole point: a matcher that de-escapes something a fence
would have kept literal changes only which text highlights, and no caller writes
it back. A rewriter making the identical mistake corrupts the file. So there is
nothing here that needs to know about fences at all.

Two consequences worth having: existing notes are searchable immediately rather
than after their next edit, and no stored byte changes, so no document the
editor has ever written can be affected.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 15:29:25 -07:00

Sim.ai Documentation Slack X

Ask DeepWiki Set Up with Cursor

Sim — Integrate, Context, Build, and Monitor AI agents

A workspace to build, deploy and manage AI agents and workflows.

Quickstart

Cloud-hosted: sim.ai

Open sim.ai

Self-hosted

npx sim-setup

Open http://localhost:3000

The Sim platform — chat on the left, the visual workflow builder on the right

Capabilities

  • Connect 1,000+ integrations and every major LLM
  • Add Slack, Notion, HubSpot, Salesforce, databases, and more
  • Build agents visually, conversationally, or with code
  • Ingest files, knowledge bases, and structured table data
  • Monitor runs, logs, schedules, and workflow activity

One workspace, every surface

Chat and workflows are just the start — tables, files, and knowledge all live in the same workspace.

Tables in Sim — structured data your agents can query

Tables — a database, built in

Files in Sim — documents for your team and every agent

Files — one store for your team and every agent

Knowledge bases in Sim — synced docs your agents can search

Knowledge — your agents' memory

Self-hosting

Requirements: Node.js 20+ and Docker.

npx sim-setup is an interactive wizard that creates a small sim/ deployment directory, provisions the database, generates secrets, writes .env, connects a Chat API key, and starts the published Sim images with Docker Compose. It does not clone the repository.

When it finishes, open http://localhost:3000.

Inside a cloned Sim repository, run bun run sim-setup to unlock the source-only local development and Kubernetes modes.

Reconfigure an optional capability without rerunning the full wizard:

npx sim-setup config
npx sim-setup add email
npx sim-setup add storage
npx sim-setup add sandbox
npx sim-setup add jobs
npx sim-setup add cache
npx sim-setup add knowledge
npx sim-setup add llm
npx sim-setup add integration slack

npx sim-setup config detects the effective local-dev, Docker Compose, or current-context Helm configuration and reports configured, missing, or invalid capabilities and OAuth integrations without printing credential values. This is separate from npx sim-setup status, which reports whether installed services are running and healthy.

Manage your install from its directory:

npx sim-setup start | stop | restart   # bring your install up / down / cycle
npx sim-setup update                   # pull and apply Compose images
npx sim-setup status                   # what's installed and healthy
npx sim-setup logs                     # follow logs
npx sim-setup doctor                   # diagnose configuration problems
npx sim-setup down                     # remove containers (data kept)
npx sim-setup reset                    # archive .env and wipe managed data

The setup package detects how you're running and acts accordingly. Use --dir <path> to create or manage a deployment somewhere other than ./sim.

Sim also supports local models via Ollama and vLLM. See the self-hosting docs for details.

Chat API Keys

Chat is a Sim-managed service. npx sim-setup connects a Chat API key for you — sign in when it opens your browser and the key is stored automatically. To view, create, or revoke keys later, go to sim.ai/selfhost/settings/chat-keys.

Environment Variables

See the environment variables reference for the full list, or apps/sim/.env.example for defaults.

Tech Stack

Next.js · Bun · PostgreSQL · Drizzle · Better Auth · Tailwind — and the rest of the stack

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Built by the Sim team in San Francisco

Languages
TypeScript 77%
MDX 20.8%
JavaScript 1.9%
CSS 0.1%