-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathvite.config.ts
More file actions
699 lines (686 loc) · 30.8 KB
/
Copy pathvite.config.ts
File metadata and controls
699 lines (686 loc) · 30.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
import { defineConfig, type Plugin } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import fs from "fs";
import { visualizer } from "rollup-plugin-visualizer";
import { imagetools } from "vite-imagetools";
import { VitePWA } from "vite-plugin-pwa";
import Critters from "critters";
/// <reference types="vitest" />
// Critical CSS plugin using Critters
function criticalCSSPlugin(): Plugin {
return {
name: 'vite-plugin-critical-css',
apply: 'build',
enforce: 'post',
async closeBundle() {
try {
const distDir = path.resolve(__dirname, "dist");
const indexPath = path.join(distDir, "index.html");
if (!fs.existsSync(indexPath)) return;
const html = await fs.promises.readFile(indexPath, "utf8");
const critters = new Critters({
path: distDir,
publicPath: "/",
preload: "swap",
noscriptFallback: true,
inlineFonts: true,
preloadFonts: true,
compress: true,
pruneSource: false,
});
const result = await critters.process(html);
await fs.promises.writeFile(indexPath, result, "utf8");
} catch (error) {
console.warn("[Critical CSS] Failed to process:", error);
}
},
};
}
/**
* Strip <link rel="stylesheet"> tags for heavy lazy-loaded CSS from index.html.
*
* Vite adds <link> tags for ALL extracted CSS chunks to index.html, even those
* that belong to lazy-loaded routes. This plugin removes them post-build so
* they only load on-demand when their parent JS chunk is fetched.
*
* The CSS files remain in dist/assets/ — they're referenced by their JS chunks
* via __vite__mapDeps and loaded automatically when the chunk executes.
*/
function deferHeavyCSSPlugin(): Plugin {
// CSS files matching any of these prefixes are removed from index.html.
// They'll still be loaded on-demand by their JS chunk.
const deferredCssPrefixes = ['katex-vendor'];
return {
name: 'vite-plugin-defer-heavy-css',
apply: 'build',
enforce: 'post',
async closeBundle() {
try {
const distDir = path.resolve(__dirname, "dist");
const indexPath = path.join(distDir, "index.html");
if (!fs.existsSync(indexPath)) return;
let html = await fs.promises.readFile(indexPath, "utf8");
for (const prefix of deferredCssPrefixes) {
// Match <link rel="stylesheet" ... href=".../{prefix}-{hash}.css">
const re = new RegExp(
`\\s*<link[^>]*href="[^"]*/${prefix}-[^"]*\\.css"[^>]*>`,
'g'
);
html = html.replace(re, '');
}
await fs.promises.writeFile(indexPath, html, "utf8");
} catch (error) {
console.warn("[Defer Heavy CSS] Failed to process:", error);
}
},
};
}
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
// Root cause: terser minification can OOM on 2-core / 8GB builders (Vercel + local).
// Default to esbuild minify for reliability; opt into terser via NODEBENCH_MINIFY=terser.
//
// Example:
// NODEBENCH_MINIFY=terser npm run build
// NODEBENCH_MINIFY=esbuild npm run build
//
// "esbuild" is the Vite default and is far more memory efficient.
// Terser is retained for optional maximum compression.
const minify = (process.env.NODEBENCH_MINIFY ?? "esbuild") === "terser" ? "terser" : "esbuild";
const useTerser = minify === "terser";
const enableCriticalCss = mode === "production" && process.env.NODEBENCH_CRITICAL_CSS === "1";
const nodebenchServerTarget =
process.env.VITE_NODEBENCH_SERVER_TARGET ??
process.env.NODEBENCH_SERVER_TARGET ??
"http://localhost:3100";
return ({
server: {
host: true, // Bind to all interfaces so Chrome can reach the dev server
proxy: {
"/api/search": {
target: "http://localhost:8020",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/search/, "/v1/search"),
},
"/api/search-upload": {
target: "http://localhost:8020",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/search-upload/, "/search/upload"),
},
"/api/search-health": {
target: "http://localhost:8020",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/search-health/, "/health"),
},
"/api/search-history": {
target: nodebenchServerTarget,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/search-history/, "/search/history"),
},
"/api/search-sync-status": {
target: nodebenchServerTarget,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/search-sync-status/, "/search/sync-status"),
},
"/api/sync-bridge": {
target: nodebenchServerTarget,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/sync-bridge/, "/api/sync-bridge"),
},
"/api/hyperloop": {
target: nodebenchServerTarget,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/hyperloop/, "/api/hyperloop"),
},
"/api/pipeline": {
target: nodebenchServerTarget,
changeOrigin: true,
},
"/api/improvements": {
target: nodebenchServerTarget,
changeOrigin: true,
},
"/api/retention": {
target: nodebenchServerTarget,
changeOrigin: true,
},
"/api/search/stream": {
target: nodebenchServerTarget,
changeOrigin: true,
},
"/api/product": {
target: nodebenchServerTarget,
changeOrigin: true,
},
"/voice": {
target: nodebenchServerTarget,
changeOrigin: true,
},
},
},
plugins: [
react(),
// Service Worker + PWA for aggressive caching
VitePWA({
registerType: 'autoUpdate',
includeAssets: [
'favicon.svg',
'favicon.ico',
'robots.txt',
'apple-touch-icon.png',
'pwa-192.png',
'pwa-512.png',
'mask-icon.svg',
],
manifest: {
name: 'NodeBench AI',
short_name: 'NodeBench',
description: 'AI-powered research and analytics platform',
theme_color: '#09090B',
background_color: '#09090B',
display: 'standalone',
start_url: '/?surface=chat',
scope: '/',
icons: [
{
src: '/pwa-192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/pwa-512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: '/pwa-512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
},
workbox: {
skipWaiting: true,
clientsClaim: true,
// CRITICAL: navigateFallbackDenylist with NetworkFirst on navigation
// requests means HTML responses are NEVER cache-served. This is the
// single biggest cause of "users see stale bundle after deploy".
// With NetworkFirst, every navigation goes to the network first,
// and only falls back to cache when offline. New deploys reach
// users on the next page load, not after manual cache clear.
//
// The hashed JS/CSS chunks below stay on StaleWhileRevalidate —
// they're content-addressed (index-COn3r21_.js) so cache is safe.
// It's the entry HTML that points to those chunks that must NOT
// be cached, otherwise users get the OLD HTML pointing at OLD chunks.
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'],
// Dogfood generation archives are QA evidence, not runtime assets.
// Keep them fetchable under /dogfood, but do not precache every
// archived screenshot into the service worker for normal users.
globIgnores: [
'**/dogfood/generations/**',
'**/node_modules/**',
'**/proto/**/node_modules/**',
],
navigateFallback: '/index.html',
navigateFallbackDenylist: [/^\/api\//, /^\/voice\//, /^\/install\.sh/],
runtimeCaching: [
// Navigation requests — NetworkFirst with short timeout.
// Users see new deploys on the next page load, not after cache clear.
{
urlPattern: ({ request }) => request.mode === 'navigate',
handler: 'NetworkFirst',
options: {
cacheName: 'navigation-cache',
networkTimeoutSeconds: 3,
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24, // 1 day max for HTML
},
cacheableResponse: {
statuses: [200],
},
},
},
// Cache Google Fonts
{
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'gstatic-fonts-cache',
expiration: {
maxEntries: 20,
maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
// Cache JS/CSS with stale-while-revalidate
{
urlPattern: /\.(?:js|css)$/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'static-resources',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
},
},
},
// Cache images
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/,
handler: 'CacheFirst',
options: {
cacheName: 'images-cache',
expiration: {
maxEntries: 60,
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
},
},
},
],
},
}),
// Image optimization - converts PNG/JPG to WebP with quality 80
imagetools({
defaultDirectives: (url) => {
// Only optimize images in public/assets
if (url.searchParams.has('url') && url.searchParams.get('url')?.includes('/assets/')) {
return new URLSearchParams({
format: 'webp',
quality: '80',
});
}
return new URLSearchParams();
},
}),
// Critical CSS inlining can significantly increase build time on constrained builders.
// Keep it opt-in (NODEBENCH_CRITICAL_CSS=1) and rely on `index.html` baseline styles to prevent FOUC.
enableCriticalCss ? criticalCSSPlugin() : null,
// Remove katex-vendor CSS from index.html — it's only needed in the agents surface
deferHeavyCSSPlugin(),
// Bundle analyzer - run with: ANALYZE=true npm run build
process.env.ANALYZE === 'true' ? visualizer({
filename: './dist/stats.html',
open: true,
gzipSize: true,
brotliSize: true,
}) : null,
// The code below enables dev tools like taking screenshots of your site
// while it is being developed on chef.convex.dev.
// Feel free to remove this code if you're no longer developing your app with Chef.
mode === "development"
? {
name: "inject-chef-dev",
transform(code: string, id: string) {
if (id.includes("main.tsx")) {
return {
code: `${code}
/* Added by Vite plugin inject-chef-dev */
window.addEventListener('message', async (message) => {
if (message.source !== window.parent) return;
if (message.data.type !== 'chefPreviewRequest') return;
const worker = await import('https://chef.convex.dev/scripts/worker.bundled.mjs');
await worker.respondToMessage(message);
});
`,
map: null,
};
}
return null;
},
}
: null,
// End of code for taking screenshots on chef.convex.dev.
].filter(Boolean),
resolve: {
// Order matters: keep specific aliases above the generic "@/" alias.
alias: [
{ find: "@features", replacement: path.resolve(__dirname, "./src/features").replace(/\\/g, "/") },
{ find: "@shared", replacement: path.resolve(__dirname, "./src/shared").replace(/\\/g, "/") },
{ find: "shared", replacement: path.resolve(__dirname, "./shared").replace(/\\/g, "/") },
{ find: /^@\//, replacement: `${path.resolve(__dirname, "./src").replace(/\\/g, "/")}/` },
],
// Prevent duplicate React instances (common cause of "Invalid hook call" in Vite/monorepo setups).
dedupe: ["react", "react-dom"],
},
optimizeDeps: {
include: ["react", "react-dom", "rehype-raw", "rehype-sanitize", "rehype-parse", "hast-util-raw"],
// Exclude heavy libraries that are lazy-loaded to speed up dev server start
exclude: ["@pdfme/generator"],
},
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./src/test/setup.ts"],
},
build: {
// The heaviest routes/editors are lazy-loaded; keep this warning slightly higher
// so it flags meaningful regressions without noise.
chunkSizeWarningLimit: 2200,
minify,
target: "es2020",
cssMinify: true,
// Modern browsers don't need module preload polyfill.
// Custom resolveDependencies limits preload to essential first-paint chunks
// (vendor + small shared code). Heavy lazy-loaded chunks (agent-fast-panel,
// editor-vendor, recharts-vendor, etc.) are excluded — they load on demand
// when the user navigates to their surface.
modulePreload: {
polyfill: false,
resolveDependencies(filename, deps) {
// Only preload deps for the entry file
if (!filename.includes('index')) return [];
// Filter out heavy chunks that are lazy-loaded surfaces, not first-paint
const heavyChunkPrefixes = [
'agent-fast-panel', 'agent-autonomous', 'agent-oracle', 'agent-topic',
'agent-sidebar', 'agent-approval', 'agent-notifications',
'route-agents', 'route-research', 'route-documents', 'route-calendar',
'route-analytics', 'route-spreadsheets',
'editor-vendor', 'syntax-vendor', 'katex-vendor', 'katex-lazy', 'recharts-vendor',
'spreadsheet-vendor',
'Workbench', 'Benchmark',
'doc-', 'editor-',
'trajectory', 'DevDashboard', 'SettingsModal',
];
return deps.filter(dep => {
const basename = dep.split('/').pop() ?? dep;
return !heavyChunkPrefixes.some(prefix => basename.startsWith(prefix));
});
},
},
terserOptions: useTerser ? {
compress: {
// Keep `console.error`/`console.warn` for diagnostics; strip noisy logs only.
drop_console: false,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info', 'console.debug'], // Remove specific console methods
passes: 2, // Multiple compression passes for better results
},
mangle: {
safari10: true, // Safari 10 compatibility
},
format: {
comments: false, // Remove all comments
},
} : undefined,
rolldownOptions: {
checks: {
pluginTimings: false,
},
// Tree shaking - be careful with external deps that have side effects
treeshake: {
moduleSideEffects: true, // Don't assume side-effect-free (fixes lucide-react init)
propertyReadSideEffects: false,
},
output: {
chunkFileNames: "assets/[name]-[hash].js",
entryFileNames: "assets/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash][extname]",
// Route-based code splitting for optimal loading
manualChunks(id) {
// KaTeX lazy-loader wrapper — force into its own tiny chunk so Rollup
// does NOT inline it into agent-fast-panel. If inlined, Rollup hoists
// katex-vendor as a static dep of agent-fast-panel (and transitively
// the main bundle). As a separate chunk, the dynamic import boundary
// is preserved: agent-fast-panel -> (dynamic) -> katex-lazy -> (static) -> katex-vendor.
if (id.includes('lazyRehypeKatex')) {
return 'katex-lazy';
}
// Convex generated API — MUST be in its own chunk.
// convex/_generated/api.ts creates 513 circular dependency chains
// with convex/ action/tool files. When Rollup inlines it into the
// main bundle, ESM execution order causes TDZ crash in headless
// Chrome: "Cannot access 'o' before initialization".
// Isolating it breaks the circular init chain.
if (id.includes('convex/_generated/')) {
return 'convex-runtime';
}
// Convex domain files (actions, tools, operations) — keep together
// to avoid splitting circular deps across chunks.
if (id.match(/convex\/(actions|domains|tools|crons|workflows)\//)) {
return 'convex-runtime';
}
// Vendor chunks - only split large/important libraries
if (id.includes('/node_modules/')) {
// React core (keep small and cacheable)
if (id.match(/\/node_modules\/(react\/|react-dom\/|scheduler\/)/)) {
return 'react-vendor';
}
if (id.includes('/node_modules/react-router-dom/')) {
return 'router-vendor';
}
// Convex (API client)
if (id.includes('/node_modules/convex/')) {
return 'convex-runtime';
}
// Editor ecosystem — ALL related packages MUST land in one chunk.
// @blocknote/@tiptap/prosemirror have circular init deps; splitting
// them across chunks causes "Cannot access X before initialization".
// Including the full transitive set (unified/rehype/remark, yjs,
// emoji-mart, floating-ui) prevents Rollup from splitting them.
if (
id.includes('/node_modules/@tiptap/') ||
id.includes('/node_modules/@blocknote/') ||
id.includes('/node_modules/prosemirror-') ||
id.includes('/node_modules/@prosemirror-adapter/') ||
id.includes('/node_modules/@handlewithcare/') ||
id.includes('/node_modules/y-prosemirror') ||
id.includes('/node_modules/y-protocols') ||
id.includes('/node_modules/yjs/') ||
id.includes('/node_modules/lib0/') ||
id.includes('/node_modules/unified/') ||
id.includes('/node_modules/rehype-') ||
id.includes('/node_modules/remark-') ||
id.includes('/node_modules/hast-') ||
id.includes('/node_modules/mdast-') ||
id.includes('/node_modules/unist-') ||
id.includes('/node_modules/emoji-mart') ||
id.includes('/node_modules/@emoji-mart/') ||
id.includes('/node_modules/@floating-ui/') ||
id.includes('/node_modules/@shikijs/')
) {
return 'editor-vendor';
}
// Spreadsheet engine (very heavy)
if (id.includes('/node_modules/xlsx')) {
return 'spreadsheet-vendor';
}
// Date utilities (commonly used, stable)
if (id.includes('/node_modules/date-fns/')) {
return 'date-vendor';
}
// Animation library (Framer Motion - heavy)
if (id.includes('/node_modules/framer-motion/')) {
return 'motion-vendor';
}
// Markdown/syntax highlighting (heavy)
if (id.includes('/node_modules/prismjs/') || id.includes('/node_modules/highlight.js/')) {
return 'syntax-vendor';
}
// KaTeX math rendering (heavy, only used in FastAgentPanel message bubbles)
// rehype-katex depends on katex — bundle them together so the entire
// katex dependency tree lives in one deferred chunk.
if (id.includes('/node_modules/katex/') || id.includes('/node_modules/rehype-katex/')) {
return 'katex-vendor';
}
// Recharts (heavy charting, used in TelemetryInspector + ModelEvalDashboard)
if (id.includes('/node_modules/recharts/')) {
return 'recharts-vendor';
}
// Zod (validation, commonly used)
if (id.includes('/node_modules/zod/')) {
return 'zod-vendor';
}
// Let everything else be handled by Vite's default splitting
// This avoids creating one massive vendor chunk
}
// Route-based splitting for application features
if (id.includes('/features/analytics/')) {
return 'route-analytics';
}
// Benchmarks: let Rollup decide chunking. Manual splitting caused TDZ
// crashes in headless Chrome — bench-* chunks had circular init deps
// with agent-fast-panel and convex-api that Rollup couldn't resolve
// when forced into separate chunks.
// Trajectory: lazy-loaded from WorkbenchView and other views
if (id.includes('/features/trajectory/')) {
return 'trajectory';
}
// Documents feature: per-boundary chunking so only the active
// mode/overlay loads. Each chunk matches a React.lazy() boundary.
if (id.includes('/features/documents/')) {
// Per-mode view surfaces (lazy-loaded by DocumentsTabContent)
if (id.includes('/viewModes/DocumentsCardsView')) return 'doc-view-cards';
if (id.includes('/viewModes/DocumentsListView')) return 'doc-view-list';
if (id.includes('/viewModes/DocumentsSegmentedView')) return 'doc-view-segmented';
if (id.includes('/viewModes/index')) return undefined; // barrel — let Vite decide
// VisualGlimpse preview renderer (lazy-loaded by DocumentCard)
if (id.includes('/cards/VisualGlimpse')) return 'doc-visual-glimpse';
// Preview family modules (lazy-loadable by family)
if (id.includes('/previews/SpreadsheetPreview')) return 'doc-preview-spreadsheet';
// Media cinema viewer (lazy-loaded by PlannerOverlays)
if (id.includes('/MediaCinemaViewer')) return 'doc-media-viewer';
// Sidebar panel (lazy-loaded by WorkspaceSurface)
if (id.includes('/DocumentSidebarPanel')) return 'doc-sidebar';
// Planner surface island (lazy-loaded by DocumentsHomeHub)
if (id.includes('/DocumentsPlannerSurface')) return 'doc-planner-surface';
// Planner overlays (lazy-loaded by PlannerSurface)
if (id.includes('/DocumentsPlannerOverlays')) return 'doc-planner-overlays';
// Planner hooks (loaded by PlannerSurface, not entry)
if (id.includes('/hooks/usePlannerController')) return 'doc-planner-surface';
if (id.includes('/hooks/usePlannerDateNav')) return 'doc-planner-surface';
if (id.includes('/hooks/usePlannerAgendaData')) return 'doc-planner-surface';
if (id.includes('/hooks/usePlannerViewPrefs')) return 'doc-planner-surface';
if (id.includes('/hooks/usePlannerEditor')) return 'doc-planner-surface';
if (id.includes('/hooks/usePlannerMutations')) return 'doc-planner-surface';
// Planner provider (loaded by PlannerSurface)
if (id.includes('/context/DocumentsPlannerProvider')) return 'doc-planner-surface';
// Secondary views — NOT on documents entry path, loaded by
// ActiveSurfaceHost or other lazy boundaries
if (id.includes('/views/SpreadsheetView')) return 'doc-view-spreadsheet';
if (id.includes('/views/FileViewer')) return 'doc-view-file';
if (id.includes('/views/DocumentView')) return 'doc-view-document';
if (id.includes('/views/PublicDocuments')) return undefined; // tiny, let Vite decide
// Components only used by secondary views (not entry path)
if (id.includes('/DocumentHeader')) return 'doc-view-document';
if (id.includes('/CodeViewer')) return 'doc-view-file';
if (id.includes('/DocumentGrid')) return 'doc-legacy-grid'; // dead code, legacy only
// Spreadsheet surfaces — lazy-loaded from ActiveSurfaceHost
if (id.includes('/surfaces/spreadsheets/')) return 'doc-spreadsheet-surface';
// Editors — lazy-loaded from MiniEditorPopover in @shared/
if (id.includes('/editors/SpreadsheetMiniEditor')) return 'doc-editor-spreadsheet';
if (id.includes('/editors/DossierMiniEditor')) return 'doc-editor-dossier';
if (id.includes('/editors/DocumentMiniEditor')) return 'doc-editor-document';
if (id.includes('/editors/DualEditMiniPanel')) return 'doc-editor-dual';
if (id.includes('/editors/DualCreateMiniPanel')) return 'doc-editor-dual';
if (id.includes('/editors/PopoverMiniEditor')) return 'doc-editor-popover';
// Everything else stays in route-documents
return 'route-documents';
}
// Agents feature: carve out lazy-loaded panels so React.lazy() produces separate chunks
if (id.includes('/features/agents/')) {
// Heavy panels lazy-loaded from AgentsHub
if (id.includes('/AutonomousOperationsPanel')) return 'agent-autonomous-ops';
if (id.includes('/OracleControlTowerPanel')) return 'agent-oracle-tower';
if (id.includes('/TopicCanvasPanel')) return 'agent-topic-canvas';
if (id.includes('/AgentSidebar')) return 'agent-sidebar';
if (id.includes('/HumanApprovalQueue')) return 'agent-approval-queue';
if (id.includes('/NotificationActivityPanel')) return 'agent-notifications';
// Lazy sub-surfaces used by AgentsHub or FastAgentPanel — let Vite split
if (id.includes('/SwarmLanesView') || id.includes('/FreeModelRankingsPanel') || id.includes('/TaskManager/')) return undefined;
// FastAgentPanel: tab-gated sub-components are lazy-loaded.
// Exclude them from the catch-all so they get their own chunks.
if (id.includes('/FastAgentPanel/')) {
// Tab-gated panels (lazy-loaded on tab switch, not on panel open)
if (id.includes('.Settings')) return 'agent-panel-settings';
if (id.includes('.DisclosureTrace')) return 'agent-panel-trace';
if (id.includes('.TraceAuditPanel')) return 'agent-panel-trace';
if (id.includes('.BriefTab')) return 'agent-panel-brief';
if (id.includes('.AgentTasksTab')) return 'agent-panel-tasks';
if (id.includes('.EditsTab')) return 'agent-panel-edits';
if (id.includes('.ParallelTaskTimeline')) return 'agent-panel-timeline';
if (id.includes('.DecisionTreeKanban')) return 'agent-panel-kanban';
if (id.includes('.PromptEnhancer')) return 'agent-panel-enhancer';
if (id.includes('.SkillsPanel')) return 'agent-panel-skills';
if (id.includes('.AgentHierarchy')) return 'agent-panel-hierarchy';
if (id.includes('JarvisHUD')) return 'agent-panel-hud';
if (id.includes('MediaGallery')) return 'agent-panel-media';
if (id.includes('FileViewer')) return 'agent-panel-fileviewer';
// Core chat shell — stays in agent-fast-panel
return 'agent-fast-panel';
}
// Agent contexts — shared across entry + lazy chunks, let Vite split
if (id.includes('/features/agents/context/')) return undefined;
// AgentsHub is the landing view — only this stays in route-agents
if (id.includes('/views/AgentsHub')) return 'route-agents';
// Non-landing views: used as embedded sub-surfaces by AgentsHub or
// FastAgentPanel. Let Vite split naturally so they land in the chunk
// of their actual consumer, not forced into the landing entry.
if (id.includes('/views/LiveAgentLanes')) return undefined;
if (id.includes('/views/TaskPlanPanel')) return undefined;
if (id.includes('/views/WorkflowMetricsBar')) return undefined;
if (id.includes('/views/DeepAgentProgress')) return undefined;
if (id.includes('/views/PublicActivityView')) return undefined;
// Everything else (shared hooks, types, utils) — let Vite decide
return undefined;
}
// Research feature: Split into core hub vs lazy-loaded sections
// This allows React.lazy() components to become separate chunks
if (id.includes('/features/research/views/ResearchHub')) {
return 'route-research-hub';
}
if (id.includes('/features/research/views/CinematicHome')) {
return 'route-research-home';
}
// Research sections: default-tab sections stay with hub (eagerly imported).
// Tab-gated sections (BriefingSection, FeedSection) are lazy-loaded by
// ResearchHub — let Vite split them into their consumer's chunk naturally.
if (id.includes('/features/research/sections/BriefingSection')) return 'research-briefing';
if (id.includes('/features/research/sections/FeedSection')) return 'research-feed';
if (id.includes('/features/research/sections/DealListSection')) return 'research-deals';
// DigestSection and DashboardSection are default-tab — stay in hub
if (id.includes('/features/research/sections/')) return 'route-research-hub';
if (id.includes('/features/research/components/')) {
// Let components chunk naturally via lazy loading
return undefined;
}
if (id.includes('/features/spreadsheets/')) {
return 'route-spreadsheets';
}
if (id.includes('/features/calendar/')) {
return 'route-calendar';
}
// Heavy editors from src
if (id.includes('/components/Editor/') || id.includes('UnifiedEditor')) {
return 'editor';
}
// Default: shared application code
return undefined;
},
},
},
// Enable source maps for production debugging (can disable for smaller bundles)
sourcemap: false, // Disable source maps to reduce bundle size
},
});
});