@@ -16,7 +16,6 @@ import {
1616} from '@sim/utils/errors'
1717import { generateShortId } from '@sim/utils/id'
1818import { omit } from '@sim/utils/object'
19- import { escapeLikePattern } from '@sim/utils/string'
2019import { and , desc , eq , inArray , isNotNull , isNull , or , type SQL , sql } from 'drizzle-orm'
2120import type { ShareRecord } from '@/lib/api/contracts/public-shares'
2221import type { V2FileSortBy } from '@/lib/api/contracts/v2/files'
@@ -101,7 +100,7 @@ import {
101100import { getWorkspaceFileSize , MAX_WORKSPACE_FILE_SIZE } from '@/lib/uploads/shared/types'
102101import { isMarkdownFile } from '@/lib/uploads/utils/file-utils'
103102import type { ServableFile } from '@/lib/uploads/utils/file-utils.server'
104- import { normalizeDisplaySegment } from '@/lib/vfs/path'
103+ import { displaySegmentPattern } from '@/lib/vfs/path'
105104import { SIM_PAGE_CONTENT_TYPE } from '@/lib/workspace-files/page-compile'
106105import {
107106 MAX_SIM_PAGE_UPLOAD_SNIFF_BYTES ,
@@ -1580,36 +1579,33 @@ export function parseChatUploadReference(fileReference: string): string | null {
15801579 * Display names are unique per chat. Mothership supplies that namespace; callers
15811580 * without a chat scope retain the workspace-wide newest-name lookup.
15821581 *
1583- * `name` is in VFS display form ({ @link normalizeDisplaySegment}), since that is what the
1584- * upload notice's path encodes, while the stored name keeps the uploaded spelling (a macOS
1585- * screenshot carries U+202F before AM/PM) . The query narrows to names that, composed and
1586- * stripped of control characters, hold each of `name`'s words in order — a superset of the
1587- * exact match, which then compares the stored name in display form .
1582+ * `name` is decoded from the path the upload notice prints, which VFS encoding normalizes
1583+ * (NFC, control characters removed, whitespace runs collapsed and trimmed), while the stored
1584+ * name keeps the uploaded spelling: a macOS screenshot carries U+202F before AM/PM. The stored
1585+ * name is composed and stripped of control characters in SQL, and
1586+ * { @link displaySegmentPattern} matches its whitespace the way the encoding collapses it .
15881587 */
15891588async function getChatUploadByName (
15901589 workspaceId : string ,
15911590 name : string ,
15921591 chatId ?: string
15931592) : Promise < WorkspaceFileRecord | null > {
1594- const storedName = sql < string > `coalesce(${ workspaceFiles . displayName } , ${ workspaceFiles . originalName } )`
1595- const wordsInOrder = `%${ name . split ( ' ' ) . map ( escapeLikePattern ) . join ( '%' ) } %`
1596- const candidates = await db
1593+ const storedName = sql `coalesce(${ workspaceFiles . displayName } , ${ workspaceFiles . originalName } )`
1594+ const [ file ] = await db
15971595 . select ( )
15981596 . from ( workspaceFiles )
15991597 . where (
16001598 and (
16011599 eq ( workspaceFiles . workspaceId , workspaceId ) ,
16021600 eq ( workspaceFiles . context , 'mothership' ) ,
16031601 chatId === undefined ? undefined : eq ( workspaceFiles . chatId , chatId ) ,
1604- sql `regexp_replace(normalize(${ storedName } , NFC), '[\\x01-\\x1f\\x7f]', '', 'g') LIKE ${ wordsInOrder } ESCAPE '\\' ` ,
1602+ sql `regexp_replace(normalize(${ storedName } , NFC), '[\\x01-\\x1f\\x7f]', '', 'g') ~ ${ displaySegmentPattern ( name ) } ` ,
16051603 isNull ( workspaceFiles . deletedAt )
16061604 )
16071605 )
16081606 . orderBy ( desc ( workspaceFiles . uploadedAt ) )
1607+ . limit ( 1 )
16091608
1610- const file = candidates . find (
1611- ( candidate ) => normalizeDisplaySegment ( candidate . displayName ?? candidate . originalName ) === name
1612- )
16131609 return file ? mapChatUploadRecord ( file , workspaceId ) : null
16141610}
16151611
0 commit comments