refactor(search): one definition of what a search occurrence is - #6905
Conversation
Follow-up to #6901, closing two places where the workflow search index and the Note card that mirrors it could drift apart. Neither is a live bug; both are the shape that produced one — the card silently disagreeing with the panel about which hit is which, counted in one place and painted in another. THE SCAN. #6901 shared `foldSearchWhitespace` but left the scan around it duplicated: normalize, then non-overlapping `indexOf` stepping by `max(len, 1)`, written out once in the indexer and once in the renderer package. They agree today. They would stop agreeing the moment either grew whole-word matching, diacritic folding, or a regex mode, and the failure is silent. Both now call one `forEachSearchOccurrence` in `@sim/utils/string` — the only place either package can share, since the card renders from a package that cannot import from `apps/*`. THE DECLARATION. The indexer projects markdown escapes only for a field declaring `searchTextFormat: 'markdown'`; the card projects unconditionally, because it cannot read the block registry. Dropping that one line from the Note config would leave them disagreeing with nothing to catch it, so a test now pins it and explains why. Net negative in lines: this deletes a duplicated loop rather than adding a layer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryLow Risk Overview The non-overlapping A test pins Reviewed by Cursor Bugbot for commit 44fe5f4. Configure here. |
Greptile SummaryThe PR centralizes workflow-search occurrence scanning in
Confidence Score: 5/5The PR appears safe to merge with no actionable correctness, security, or quality issues identified. The extracted scanner is behaviorally equivalent for all current callers, remains reachable through existing package exports and dependencies, and preserves the indexer-to-renderer occurrence and coordinate contracts.
|
| Filename | Overview |
|---|---|
| packages/utils/src/string.ts | Adds the shared occurrence scanner while preserving the duplicated algorithms’ whitespace folding, case handling, and non-overlapping iteration. |
| apps/sim/lib/workflows/search-replace/indexer.ts | Delegates range scanning to the shared utility without changing markdown projection or projected-to-source coordinate mapping. |
| packages/workflow-renderer/src/note/note-search-highlight.ts | Removes the renderer-specific scanner and uses the shared primitive for source occurrence counting and rendered highlights. |
| apps/sim/lib/workflows/search-replace/indexer.test.ts | Pins the Note content configuration to markdown search projection using the same NoteBlock object registered in production. |
| packages/workflow-renderer/src/note/note-search-highlight.test.tsx | Updates scanner tests to exercise the shared utility directly while retaining case-insensitive and non-overlapping behavior coverage. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Query[Search query] --> Scanner[forEachSearchOccurrence]
Indexer[Workflow indexer] --> Projection[Optional markdown projection]
Projection --> Scanner
Note[Note renderer] --> NoteProjection[Markdown projection]
NoteProjection --> Scanner
Scanner --> Ranges[Index ranges]
Scanner --> Highlights[Note highlights]
Reviews (1): Last reviewed commit: "refactor(search): one definition of what..." | Re-trigger Greptile
Follow-up to #6901. Two places where the workflow search index and the Note card that mirrors it could drift apart. Neither is a live bug — both are the shape that produced one during that PR's review, where the card silently disagreed with the panel about which hit was which.
1. The scan was duplicated
#6901 shared
foldSearchWhitespacebut left the loop around it written out twice — normalize, then non-overlappingindexOfstepping bymax(len, 1)— once infindTextRangesand once in the renderer package.They agree today. They stop agreeing the moment either grows whole-word matching, diacritic folding, or a regex mode, and the failure mode is silent: the panel counts a hit the card marks somewhere else. That is exactly the bug Bugbot caught in #6901, in its whitespace-folding form; sharing only the fold fixed the instance and left the mechanism.
Both now call one
forEachSearchOccurrencein@sim/utils/string. That is the only place they can share it: the card renders from@sim/workflow-renderer, and packages cannot import fromapps/*.2. The markdown declaration was unpinned
The indexer projects markdown escapes only when a field declares
searchTextFormat: 'markdown'. The card projects unconditionally — it has no access to the block registry. Removing that one line from the Note config would leave the two disagreeing about what an occurrence is, with nothing to catch it.A test now pins the declaration and explains the coupling. Verified it fails when the line is removed.
Notes for review
forEachNoteSearchOccurrenceis gone from the renderer package — it was a wrapper around the duplicate. Callers use the shared primitive directly.Testing
@sim/utils160,@sim/workflow-renderer104,apps/simsearch-replace + panel 132 — all pass. Both type-checks,check:api-validation, monorepo boundaries and biome clean.The new parity test was checked for vacuousness: deleting
searchTextFormatfromblocks/blocks/note.tsmakes it fail.🤖 Generated with Claude Code