Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 0 additions & 38 deletions apps/web/src/@types/flexsearch.d.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,4 @@
// Type declarations for flexsearch modules
declare module 'flexsearch/dist/module/document' {
export interface DocumentOptions {
id?: string
index?: Record<string, unknown>
store?: boolean | string[]
tag?: string
tokenize?: string | ((content: string) => string[])
cache?: boolean | number
async?: boolean
worker?: boolean | number
threshold?: number
resolution?: number
context?: boolean | object
}

export interface SearchOptions {
limit?: number
suggest?: boolean
where?: Record<string, unknown>
field?: string | string[]
boolean?: boolean
boost?: Record<string, number>
tag?: string | string[]
}

export interface SearchResult<T = Record<string, unknown>> {
field: string
result: T[]
}

export default class Document {
constructor(options: DocumentOptions)
add(doc: Record<string, unknown>): void
remove(id: string | number): void
search<T = unknown>(query: string, options?: SearchOptions): T[]
}
}

declare module 'flexsearch' {
export interface CreateOptions {
preset?: string
Expand Down
60 changes: 10 additions & 50 deletions apps/web/src/lib/search-browser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createBuildSafeLogger } from '@/lib/logging/build-safe-logger'
const logger = createBuildSafeLogger('search-browser') // Define search document structure
export interface SearchDocument {
interface SearchDocument {
id: string | number
title: string
content: string
Expand All @@ -9,26 +9,14 @@ export interface SearchDocument {
category?: string
}

// Define search result structure
export interface SearchResult {
id: string | number
title: string
content: string
url: string
tags?: string[]
category?: string
score?: number
match?: string[]
}

// Define minimal types for flexsearch
export interface IndexOptions {
interface IndexOptions {
tokenize?: string
cache?: number
context?: boolean
}

export interface SearchOptions {
interface SearchOptions {
limit?: number
suggest?: boolean
fuzzy?: number
Expand Down Expand Up @@ -237,42 +225,14 @@ export async function initBrowserSearch(
// Dynamic import with proper error handling
let Document: FlexSearchDocumentConstructor

try {
// Use dynamic import with module specifier pattern that Vite can analyze
// First attempt - standard import path for ESM
const flexsearchPath = 'flexsearch'
const flexsearch = (await import(flexsearchPath)) as {
default?: { Document?: FlexSearchDocumentConstructor }
Document?: FlexSearchDocumentConstructor
}
Document = (flexsearch.default?.Document ??
flexsearch.Document) as FlexSearchDocumentConstructor
} catch (err: unknown) {
logger.warn(
'Failed to load flexsearch directly, trying alternative path:',
err,
)
try {
// Second attempt - alternate path for CommonJS fallback
const documentModulePath = 'flexsearch/dist/module/document'
const documentModule = (await import(documentModulePath)) as {
default: FlexSearchDocumentConstructor
}
// Use default export as the Document class
Document = documentModule.default
} catch (docErr) {
logger.error(
'Failed to load flexsearch Document from alternate path:',
docErr,
)
const error = new Error('Cannot load flexsearch Document')
;(error as Error & { cause?: unknown }).cause = {
primary: err,
fallback: docErr,
}
throw error
}
// Use dynamic import with module specifier pattern that Vite can analyze
const flexsearchPath = 'flexsearch'
const flexsearch = (await import(flexsearchPath)) as {
default?: { Document?: FlexSearchDocumentConstructor }
Document?: FlexSearchDocumentConstructor
}
Document = (flexsearch.default?.Document ??
flexsearch.Document) as FlexSearchDocumentConstructor
Comment on lines +228 to +235

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.

Bug: Dynamic import using a variable await import(flexsearchPath) cannot be statically analyzed by bundlers, causing module resolution failure in the browser.
Severity: HIGH

Suggested Fix

Use a static string literal in the dynamic import, such as await import('flexsearch'), allowing bundlers to detect, bundle, and resolve the module properly for the browser.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: apps/web/src/lib/search-browser.ts#L228-L235

Potential issue: Dynamic import uses a variable specifier `await import(flexsearchPath)`
instead of a string literal. Because bundlers like Vite and rolldown cannot statically
analyze dynamic imports with variable specifiers, the bare module specifier is left
untouched in the client bundle. At runtime in the browser, resolving bare specifiers
fails without an import map, rejecting the import promise and falling back to a dummy
client that always returns empty search results.

Did we get this right? 👍 / 👎 to inform future reviews.


Comment on lines +241 to 248

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.

🟡 Medium · Search disappears on root import failure

When the flexsearch root entry fails to load while its document subpath is still available, initBrowserSearch now tries only the flexsearch entry point; when that entry cannot load but flexsearch/dist/module/document remains loadable, the catch returns an empty fallback client, so browser search produces no results. The error is logged, but the removed alternate import no longer recovers.

Retain a Vite-analyzable recovery path for the document subpath, or verify that all supported browser builds can load the root entry before removing the fallback.

Prompt for AI agents
In `apps/web/src/lib/search-browser.ts` at lines 241-248:

**Search disappears on root import failure**

When the `flexsearch` root entry fails to load while its document subpath is still available, `initBrowserSearch` now tries only the `flexsearch` entry point; when that entry cannot load but `flexsearch/dist/module/document` remains loadable, the catch returns an empty fallback client, so browser search produces no results. The error is logged, but the removed alternate import no longer recovers.

Retain a Vite-analyzable recovery path for the document subpath, or verify that all supported browser builds can load the root entry before removing the fallback.

Fix in Cursor

if (Document) {
return new BrowserSearchClient(Document, config)
Expand Down
11 changes: 10 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -274,16 +274,16 @@
{
name: 'fix-vite-ssr-input',
enforce: 'post',
resolveId(id) {
if (id === 'virtual:dummy-ssr-entry') {
return '\0virtual:dummy-ssr-entry'
}
},

Check warning on line 281 in astro.config.mjs

View workflow job for this annotation

GitHub Actions / build-test

typescript(consistent-return)

astro.config.mjs:277:9: Function 'resolveId' expected a return value.

Check warning on line 281 in astro.config.mjs

View workflow job for this annotation

GitHub Actions / build-test

typescript(consistent-return)

astro.config.mjs:277:9: Function 'resolveId' expected a return value.
load(id) {
if (id === '\0virtual:dummy-ssr-entry') {
return 'export default {}'
}
},

Check warning on line 286 in astro.config.mjs

View workflow job for this annotation

GitHub Actions / build-test

typescript(consistent-return)

astro.config.mjs:282:9: Function 'load' expected a return value.

Check warning on line 286 in astro.config.mjs

View workflow job for this annotation

GitHub Actions / build-test

typescript(consistent-return)

astro.config.mjs:282:9: Function 'load' expected a return value.
config(config) {
if (config.environments) {
const ASTRO_MANAGED_ENVS = new Set(['client', 'server', 'ssr'])
Expand Down Expand Up @@ -643,7 +643,16 @@
const MIN_DEV = process.env.MIN_DEV === '1'
const base = [
react({
include: ['**/react/*', '**/components/**/*'],
// Vite 8 (rolldown/oxc): limit include to JS/TS files. A broad glob
// like `**/components/**/*` also matches `.module.css`, which under
// Vite 8's OXC builtin transform leaks scoped CSS into the JS
// pipeline (esbuild in Vite 7 sniffed the loader and silently
// ignored it). `@astrojs/react` only transforms JSX/TSX, so the
// narrowed set is sufficient.
include: [
'**/react/*.{js,jsx,ts,tsx}',
'**/components/**/*.{js,jsx,ts,tsx}',
Comment on lines +652 to +654

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.

🟡 Medium · Docker builds fail on component CSS

When a Docker build selects infra/docker/astro.config.mjs and processes component CSS modules, the broad include in infra/docker/astro.config.mjs also matches component .module.css files, so Vite 8 sends the CSS through OXC's JavaScript transform and the Docker build fails.

Restrict the Docker config's include patterns to JavaScript and TypeScript files.

Prompt for AI agents
In `astro.config.mjs` at lines 652-654:

**Docker builds fail on component CSS**

When a Docker build selects `infra/docker/astro.config.mjs` and processes component CSS modules, the broad `include` in `infra/docker/astro.config.mjs` also matches component `.module.css` files, so Vite 8 sends the CSS through OXC's JavaScript transform and the Docker build fails.

Restrict the Docker config's `include` patterns to JavaScript and TypeScript files.

Fix in Cursor

],
experimentalReactChildren: true,
}),
]
Expand Down
Loading
Loading