@vistagenic/vista 0.3.6 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/bin/flashpack-agent-playground.mjs +134 -0
  2. package/bin/flashpack-boot.mjs +506 -0
  3. package/bin/flashpack-ssr-loader.mjs +42 -0
  4. package/bin/flashpack-ssr.mjs +202 -0
  5. package/bin/flashpack-vendor.mjs +97 -0
  6. package/bin/vista.js +56 -42
  7. package/dist/ai/providers/base.js +7 -1
  8. package/dist/bin/build-rsc.js +16 -0
  9. package/dist/bin/build.js +17 -11
  10. package/dist/bin/deploy.js +7 -0
  11. package/dist/bin/dev-client-runtime.d.ts +6 -0
  12. package/dist/bin/dev-client-runtime.js +99 -0
  13. package/dist/bin/dev-error-overlay-snippet.js +79 -21
  14. package/dist/bin/dev-error-overlay-snippet.ts +840 -0
  15. package/dist/bin/devtools-indicator-snippet.js +29 -13
  16. package/dist/bin/file-scanner.js +7 -24
  17. package/dist/bin/generate.js +94 -2
  18. package/dist/bin/webpack.config.js +3 -1
  19. package/dist/build/rsc/compiler.d.ts +14 -0
  20. package/dist/build/rsc/compiler.js +66 -6
  21. package/dist/build/rsc/server-manifest.js +5 -4
  22. package/dist/config.d.ts +5 -0
  23. package/dist/config.js +15 -1
  24. package/dist/deploy/adapters/render.js +2 -2
  25. package/dist/deploy/index.d.ts +1 -0
  26. package/dist/deploy/index.js +3 -0
  27. package/dist/deploy/runtime-pack.js +22 -1
  28. package/dist/deploy/utils.js +16 -6
  29. package/dist/flashpack/command.d.ts +1 -0
  30. package/dist/flashpack/command.js +42 -76
  31. package/dist/flashpack/runtime.d.ts +12 -0
  32. package/dist/flashpack/runtime.js +124 -22
  33. package/dist/metadata/generate.d.ts +1 -0
  34. package/dist/metadata/generate.js +117 -21
  35. package/dist/metadata/index.d.ts +5 -0
  36. package/dist/metadata/index.js +15 -1
  37. package/dist/metadata/json-ld.d.ts +15 -0
  38. package/dist/metadata/json-ld.js +19 -0
  39. package/dist/metadata/merge.d.ts +17 -0
  40. package/dist/metadata/merge.js +161 -0
  41. package/dist/metadata/routes.d.ts +66 -0
  42. package/dist/metadata/routes.js +143 -0
  43. package/dist/metadata/types.d.ts +1 -1
  44. package/dist/metadata/types.js +3 -1
  45. package/dist/og/image-response.d.ts +38 -0
  46. package/dist/og/image-response.js +88 -0
  47. package/dist/og/index.d.ts +2 -0
  48. package/dist/og/index.js +5 -0
  49. package/dist/rust/index.d.ts +13 -0
  50. package/dist/rust/index.js +132 -0
  51. package/dist/server/engine.js +13 -3
  52. package/dist/server/logger.js +2 -1
  53. package/dist/server/project-alias-resolver.d.ts +7 -0
  54. package/dist/server/project-alias-resolver.js +70 -0
  55. package/dist/server/rsc-engine.js +93 -16
  56. package/dist/server/static-generator.js +4 -3
  57. package/dist/server/typed-api-runtime.js +92 -0
  58. package/docs/ai.md +69 -0
  59. package/docs/app.md +30 -0
  60. package/docs/cli.md +29 -0
  61. package/docs/index.md +18 -0
  62. package/docs/rag.md +54 -0
  63. package/native/win32-x64/flashpack-cli.exe +0 -0
  64. package/package.json +196 -178
  65. package/skills/README.md +9 -0
  66. package/skills/vista-agent/SKILL.md +28 -0
  67. package/skills/vista-dev-loop/SKILL.md +25 -0
  68. package/skills/vista-rag/SKILL.md +26 -0
@@ -616,13 +616,29 @@ async function createRouteElement(route, context, isDev, rootLayout, runtimeRoot
616
616
  clearProjectRequireCache(runtimeRoot);
617
617
  }
618
618
  const PageModule = require(route.pagePath);
619
- let metadata = { ...(rootLayout.metadata || {}) };
619
+ const { deepMergeMetadata, mergeMetadataChain } = require('../metadata/merge');
620
+ const layoutMetadata = [];
621
+ for (const layoutPath of route.layoutPaths || []) {
622
+ try {
623
+ const layoutModule = require(layoutPath);
624
+ if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
625
+ layoutMetadata.push(layoutModule.metadata);
626
+ }
627
+ }
628
+ catch {
629
+ // Ignore layout metadata load failures
630
+ }
631
+ }
632
+ if (layoutMetadata.length === 0 && rootLayout.metadata) {
633
+ layoutMetadata.push(rootLayout.metadata);
634
+ }
635
+ let metadata = mergeMetadataChain(layoutMetadata);
620
636
  if (PageModule.metadata) {
621
- metadata = { ...metadata, ...PageModule.metadata };
637
+ metadata = deepMergeMetadata(metadata, PageModule.metadata);
622
638
  }
623
639
  if (typeof PageModule.generateMetadata === 'function') {
624
640
  const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
625
- metadata = { ...metadata, ...dynamicMeta };
641
+ metadata = deepMergeMetadata(metadata, dynamicMeta);
626
642
  }
627
643
  const element = await renderAppSubtreeElement({
628
644
  subtreeRootDir: (0, app_dir_1.resolveAppDir)(runtimeRoot),
@@ -877,6 +893,18 @@ function wrapInDocumentShell(bodyContent, metadataHtml, chunkFiles, rootMode) {
877
893
  </body>
878
894
  </html>`;
879
895
  }
896
+ function logWebpackBuildErrors(errors) {
897
+ const count = errors.length;
898
+ console.error('');
899
+ (0, logger_1.logError)(`Build failed with ${count} error${count === 1 ? '' : 's'}`);
900
+ for (const message of errors) {
901
+ const text = message.replace(/\u001b\[[0-9;]*m/g, '').trim();
902
+ if (!text)
903
+ continue;
904
+ console.error(text);
905
+ console.error('');
906
+ }
907
+ }
880
908
  function normalizeWebpackErrors(stats) {
881
909
  const errors = stats.toJson().errors || [];
882
910
  const normalizedErrors = errors
@@ -1137,8 +1165,26 @@ function startRSCServer(options = {}) {
1137
1165
  // - Pushes compile errors/success from webpack client build
1138
1166
  // ========================================================================
1139
1167
  const sseReloadClients = new Set();
1168
+ // Cold start only: block until the first successful client compile.
1169
+ // After that, rebuilds must not flash a full-page "compiling" interstitial
1170
+ // (Next does not either — HMR/reload waits for the rebuild to finish).
1140
1171
  let clientCompileState = isDev && options.compiler ? 'compiling' : 'ready';
1141
1172
  let clientCompileErrors = [];
1173
+ let clientBundleReadyOnce = !(isDev && options.compiler);
1174
+ let clientRebuildInFlight = false;
1175
+ let pendingLiveReload = false;
1176
+ const pushSSE = (payload) => {
1177
+ sseReloadClients.forEach((c) => c.write(`data: ${payload}\n\n`));
1178
+ };
1179
+ const flushPendingLiveReload = () => {
1180
+ if (!pendingLiveReload)
1181
+ return;
1182
+ pendingLiveReload = false;
1183
+ (0, logger_1.logEvent)('Source changed, reloading...');
1184
+ pushSSE('reload');
1185
+ };
1186
+ /** True only while we still have no usable client/SSR bundle to serve. */
1187
+ const shouldBlockForClientCompile = () => Boolean(isDev && options.compiler && !clientBundleReadyOnce && clientCompileState === 'compiling');
1142
1188
  if (isDev) {
1143
1189
  app.get(constants_1.SSE_ENDPOINT, (req, res) => {
1144
1190
  res.setHeader('Content-Type', 'text/event-stream');
@@ -1151,9 +1197,6 @@ function startRSCServer(options = {}) {
1151
1197
  sseReloadClients.delete(res);
1152
1198
  });
1153
1199
  });
1154
- const pushSSE = (payload) => {
1155
- sseReloadClients.forEach((c) => c.write(`data: ${payload}\n\n`));
1156
- };
1157
1200
  const watchExtPattern = /\.(?:[cm]?[jt]sx?|css|md|mdx|json)$/i;
1158
1201
  const watchRoots = [
1159
1202
  'app',
@@ -1171,6 +1214,12 @@ function startRSCServer(options = {}) {
1171
1214
  .filter((entry) => fs_1.default.existsSync(entry));
1172
1215
  let reloadTimer = null;
1173
1216
  const scheduleReload = () => {
1217
+ // If webpack is mid-rebuild, wait for `done` so the browser does not
1218
+ // reload into a compiling gate (the intermittent Next-unlike flash).
1219
+ if (options.compiler && (clientRebuildInFlight || !clientBundleReadyOnce)) {
1220
+ pendingLiveReload = true;
1221
+ return;
1222
+ }
1174
1223
  if (reloadTimer)
1175
1224
  clearTimeout(reloadTimer);
1176
1225
  reloadTimer = setTimeout(() => {
@@ -1229,28 +1278,36 @@ function startRSCServer(options = {}) {
1229
1278
  }));
1230
1279
  // No webpack-hot-middleware — Vista uses SSE live-reload for RSC
1231
1280
  options.compiler.hooks.invalid.tap('VistaRSCCompileStateInvalid', () => {
1232
- clientCompileState = 'compiling';
1281
+ clientRebuildInFlight = true;
1233
1282
  clientCompileErrors = [];
1283
+ // Only gate requests on the cold first compile — not every HMR rebuild.
1284
+ if (!clientBundleReadyOnce) {
1285
+ clientCompileState = 'compiling';
1286
+ }
1234
1287
  });
1235
1288
  // Push compile errors/success to SSE clients
1236
1289
  options.compiler.hooks.done.tap('VistaRSCLiveReload', (stats) => {
1290
+ clientRebuildInFlight = false;
1237
1291
  if (stats.hasErrors()) {
1238
1292
  const normalizedErrors = normalizeWebpackErrors(stats);
1239
1293
  const fallback = normalizedErrors.join('\n\n');
1240
1294
  clientCompileState = 'error';
1241
1295
  clientCompileErrors = normalizedErrors;
1296
+ logWebpackBuildErrors(normalizedErrors);
1242
1297
  const payload = JSON.stringify({
1243
1298
  type: 'error',
1244
1299
  message: fallback,
1245
1300
  errors: normalizedErrors,
1246
1301
  });
1247
- sseReloadClients.forEach((c) => c.write(`data: ${payload}\n\n`));
1302
+ pushSSE(payload);
1248
1303
  }
1249
1304
  else {
1250
1305
  clientCompileState = 'ready';
1251
1306
  clientCompileErrors = [];
1307
+ clientBundleReadyOnce = true;
1252
1308
  const payload = JSON.stringify({ type: 'ok' });
1253
- sseReloadClients.forEach((c) => c.write(`data: ${payload}\n\n`));
1309
+ pushSSE(payload);
1310
+ flushPendingLiveReload();
1254
1311
  }
1255
1312
  });
1256
1313
  options.compiler.hooks.afterEmit.tap('VistaRSCServerManifestReload', () => {
@@ -1341,13 +1398,17 @@ function startRSCServer(options = {}) {
1341
1398
  const imageHandler = (0, image_optimizer_1.createImageHandler)(runtimeRoot, isDev);
1342
1399
  app.get(constants_1.IMAGE_ENDPOINT, imageHandler);
1343
1400
  app.use(express_1.default.static(path_1.default.join(runtimeRoot, 'public')));
1401
+ // Also serve the project public/ (dev + apps whose runtimeRoot is a standalone copy).
1402
+ if (path_1.default.resolve(runtimeRoot) !== path_1.default.resolve(cwd)) {
1403
+ app.use(express_1.default.static(path_1.default.join(cwd, 'public')));
1404
+ }
1344
1405
  app.use(`${constants_1.URL_PREFIX}/static`, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR, 'static')));
1345
1406
  app.use(constants_1.URL_PREFIX, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
1346
1407
  app.use(express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
1347
1408
  const getUpstreamUnavailableMessage = () => `RSC upstream unavailable (${upstreamOrigin}/rsc): ${upstreamUnavailableReason || 'upstream process is not running'}`;
1348
1409
  const proxyRSCRequest = async (req, res) => {
1349
1410
  if (isDev && options.compiler) {
1350
- if (clientCompileState === 'compiling') {
1411
+ if (shouldBlockForClientCompile()) {
1351
1412
  res.status(503).type('text/plain').send('[vista] Client bundle is compiling. Retry shortly.');
1352
1413
  return;
1353
1414
  }
@@ -1457,7 +1518,7 @@ function startRSCServer(options = {}) {
1457
1518
  return;
1458
1519
  }
1459
1520
  if (isDev && options.compiler) {
1460
- if (clientCompileState === 'compiling') {
1521
+ if (shouldBlockForClientCompile()) {
1461
1522
  res.status(503).type('text/html').send(renderCompilePendingHTML());
1462
1523
  return;
1463
1524
  }
@@ -1556,8 +1617,8 @@ function startRSCServer(options = {}) {
1556
1617
  }
1557
1618
  }
1558
1619
  if (!useFlightSSR || !flightSSRClient || !isSSRManifestReady(ssrManifest)) {
1559
- if (isDev && options.compiler) {
1560
- // Wait for a non-empty SSR manifest after the first webpack emit.
1620
+ if (isDev && options.compiler && !clientBundleReadyOnce) {
1621
+ // Cold start only — wait for the first webpack emit / SSR manifest.
1561
1622
  res.status(503).type('text/html').send(renderCompilePendingHTML());
1562
1623
  return;
1563
1624
  }
@@ -1589,15 +1650,31 @@ function startRSCServer(options = {}) {
1589
1650
  clearProjectRequireCache(runtimeRoot);
1590
1651
  }
1591
1652
  const PageModule = require(route.pagePath);
1592
- let metadata = { ...(rootLayout.metadata || {}) };
1653
+ const { deepMergeMetadata, mergeMetadataChain } = require('../metadata/merge');
1654
+ const layoutMetadata = [];
1655
+ for (const layoutPath of route.layoutPaths || []) {
1656
+ try {
1657
+ const layoutModule = require(layoutPath);
1658
+ if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
1659
+ layoutMetadata.push(layoutModule.metadata);
1660
+ }
1661
+ }
1662
+ catch {
1663
+ // Ignore layout metadata load failures
1664
+ }
1665
+ }
1666
+ if (layoutMetadata.length === 0 && rootLayout.metadata) {
1667
+ layoutMetadata.push(rootLayout.metadata);
1668
+ }
1669
+ let metadata = mergeMetadataChain(layoutMetadata);
1593
1670
  if (PageModule.metadata) {
1594
- metadata = { ...metadata, ...PageModule.metadata };
1671
+ metadata = deepMergeMetadata(metadata, PageModule.metadata);
1595
1672
  }
1596
1673
  if (typeof PageModule.generateMetadata === 'function') {
1597
1674
  const params = extractParams(req.path, route);
1598
1675
  const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
1599
1676
  const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
1600
- metadata = { ...metadata, ...dynamicMeta };
1677
+ metadata = deepMergeMetadata(metadata, dynamicMeta);
1601
1678
  }
1602
1679
  const { generateMetadataHtml } = require('../metadata/generate');
1603
1680
  metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
@@ -322,11 +322,12 @@ async function prerenderPage(urlPath, route, params, cwd, vistaDirRoot, appPprEn
322
322
  }
323
323
  let metadata = {};
324
324
  const searchParams = {};
325
+ const { deepMergeMetadata } = require('../metadata/merge');
325
326
  for (const layoutPath of route.layoutPaths) {
326
327
  try {
327
328
  const layoutModule = require(layoutPath);
328
329
  if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
329
- metadata = { ...metadata, ...layoutModule.metadata };
330
+ metadata = deepMergeMetadata(metadata, layoutModule.metadata);
330
331
  }
331
332
  }
332
333
  catch {
@@ -334,13 +335,13 @@ async function prerenderPage(urlPath, route, params, cwd, vistaDirRoot, appPprEn
334
335
  }
335
336
  }
336
337
  if (pageModule.metadata && typeof pageModule.metadata === 'object') {
337
- metadata = { ...metadata, ...pageModule.metadata };
338
+ metadata = deepMergeMetadata(metadata, pageModule.metadata);
338
339
  }
339
340
  if (typeof pageModule.generateMetadata === 'function') {
340
341
  try {
341
342
  const dynamicMeta = await pageModule.generateMetadata({ params: params || {}, searchParams }, metadata);
342
343
  if (dynamicMeta && typeof dynamicMeta === 'object') {
343
- metadata = { ...metadata, ...dynamicMeta };
344
+ metadata = deepMergeMetadata(metadata, dynamicMeta);
344
345
  }
345
346
  }
346
347
  catch (metadataError) {
@@ -33,7 +33,9 @@ const METADATA_ROUTE_MAPPINGS = [
33
33
  { requestPath: '/sitemap.xml', stem: 'sitemap' },
34
34
  { requestPath: '/manifest.webmanifest', stem: 'manifest' },
35
35
  ];
36
+ const IMAGE_ROUTE_STEMS = new Set(['opengraph-image', 'twitter-image']);
36
37
  const ROUTE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
38
+ const STATIC_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp'];
37
39
  class BodyLimitError extends Error {
38
40
  status = 413;
39
41
  constructor(limitBytes) {
@@ -136,6 +138,42 @@ function resolveMetadataRoutePath(cwd, stem) {
136
138
  };
137
139
  return searchGroupDirectories(appDir);
138
140
  }
141
+ /**
142
+ * Resolve `opengraph-image` / `twitter-image` conventions (Next parity).
143
+ * `/docs/opengraph-image` → `app/docs/opengraph-image.tsx` (or static `.png`).
144
+ */
145
+ function resolveImageMetadataRoutePath(cwd, requestPath) {
146
+ const pathname = String(requestPath || '').split('?')[0];
147
+ const match = pathname.match(/^(.*)\/(opengraph-image|twitter-image)(?:\.[a-z0-9]+)?$/i);
148
+ if (!match)
149
+ return null;
150
+ const prefix = (match[1] || '').replace(/\/+$/, '');
151
+ const stem = match[2].toLowerCase();
152
+ if (!IMAGE_ROUTE_STEMS.has(stem))
153
+ return null;
154
+ const appDir = (0, app_dir_1.resolveAppDir)(cwd);
155
+ const dir = prefix ? path_1.default.join(appDir, prefix.replace(/^\//, '')) : appDir;
156
+ for (const extension of ROUTE_FILE_EXTENSIONS) {
157
+ const candidate = path_1.default.join(dir, `${stem}${extension}`);
158
+ if (fs_1.default.existsSync(candidate))
159
+ return candidate;
160
+ }
161
+ for (const extension of STATIC_IMAGE_EXTENSIONS) {
162
+ const candidate = path_1.default.join(dir, `${stem}${extension}`);
163
+ if (fs_1.default.existsSync(candidate))
164
+ return candidate;
165
+ }
166
+ // Also allow route-group nesting: app/(marketing)/opengraph-image.tsx for /
167
+ if (!prefix) {
168
+ return resolveMetadataRoutePath(cwd, stem);
169
+ }
170
+ return null;
171
+ }
172
+ function getMetadataStemForRequest(requestPath) {
173
+ const pathname = String(requestPath || '').split('?')[0];
174
+ const mapping = METADATA_ROUTE_MAPPINGS.find((entry) => entry.requestPath === pathname);
175
+ return mapping?.stem ?? null;
176
+ }
139
177
  function hasMethodMatch(router, pathname, method) {
140
178
  const normalized = method.toLowerCase();
141
179
  return router.resolve(pathname, normalized) !== null;
@@ -456,6 +494,10 @@ function resolveLegacyRouteHandlerPath(cwd, requestPath) {
456
494
  routeCandidates.push(resolvedMetadataPath);
457
495
  }
458
496
  }
497
+ const imageRoutePath = resolveImageMetadataRoutePath(cwd, requestPath);
498
+ if (imageRoutePath) {
499
+ routeCandidates.push(imageRoutePath);
500
+ }
459
501
  if (normalized.startsWith('api/')) {
460
502
  const apiRoute = normalized.slice('api/'.length);
461
503
  routeCandidates.push(path_1.default.join(appDir, 'api', apiRoute, 'route.ts'), path_1.default.join(appDir, 'api', apiRoute, 'route.tsx'), path_1.default.join(appDir, 'api', apiRoute, 'route.js'), path_1.default.join(appDir, 'api', apiRoute, 'route.jsx'), path_1.default.join(appDir, 'api', `${apiRoute}.ts`), path_1.default.join(appDir, 'api', `${apiRoute}.tsx`), path_1.default.join(appDir, 'api', `${apiRoute}.js`), path_1.default.join(appDir, 'api', `${apiRoute}.jsx`));
@@ -475,6 +517,23 @@ function getExportedRouteMethods(apiModule) {
475
517
  async function runLegacyApiRoute(options) {
476
518
  const { req, res, apiPath, isDev } = options;
477
519
  const params = options.params || {};
520
+ const requestPath = String(req.path || req.url || '').split('?')[0];
521
+ const fileExt = path_1.default.extname(apiPath).toLowerCase();
522
+ // Static opengraph-image.png / twitter-image.jpg convention files.
523
+ if (STATIC_IMAGE_EXTENSIONS.includes(fileExt)) {
524
+ const contentTypes = {
525
+ '.png': 'image/png',
526
+ '.jpg': 'image/jpeg',
527
+ '.jpeg': 'image/jpeg',
528
+ '.gif': 'image/gif',
529
+ '.webp': 'image/webp',
530
+ };
531
+ res.status(200);
532
+ res.setHeader('Content-Type', contentTypes[fileExt] || 'application/octet-stream');
533
+ res.setHeader('Cache-Control', 'public, max-age=0, s-maxage=3600');
534
+ fs_1.default.createReadStream(apiPath).pipe(res);
535
+ return;
536
+ }
478
537
  if (isDev) {
479
538
  delete require.cache[require.resolve(apiPath)];
480
539
  }
@@ -506,6 +565,16 @@ async function runLegacyApiRoute(options) {
506
565
  await sendFetchResponse(res, result);
507
566
  return;
508
567
  }
568
+ const metadataStem = getMetadataStemForRequest(requestPath);
569
+ if (metadataStem && result !== undefined) {
570
+ const { metadataRouteToResponse } = require('../metadata/routes');
571
+ const converted = metadataRouteToResponse(metadataStem, result);
572
+ if (converted) {
573
+ res.req = res.req || req;
574
+ await sendFetchResponse(res, converted);
575
+ return;
576
+ }
577
+ }
509
578
  if (result !== undefined) {
510
579
  res.status(200).json(result);
511
580
  return;
@@ -521,6 +590,29 @@ async function runLegacyApiRoute(options) {
521
590
  throw error;
522
591
  }
523
592
  }
593
+ // MetadataRoute + OG image conventions: default export returning data or Response.
594
+ const metadataStem = getMetadataStemForRequest(requestPath);
595
+ const isImageMetadataRoute = /(?:^|\/)(opengraph-image|twitter-image)(?:\.[a-z0-9]+)?$/i.test(requestPath);
596
+ if ((metadataStem || isImageMetadataRoute) &&
597
+ typeof apiModule.default === 'function' &&
598
+ (method === 'GET' || method === 'HEAD')) {
599
+ try {
600
+ const result = await apiModule.default({ params });
601
+ let response = result instanceof Response ? result : null;
602
+ if (!response && metadataStem) {
603
+ const { metadataRouteToResponse } = require('../metadata/routes');
604
+ response = metadataRouteToResponse(metadataStem, result);
605
+ }
606
+ if (response) {
607
+ res.req = res.req || req;
608
+ await sendFetchResponse(res, response);
609
+ return;
610
+ }
611
+ }
612
+ catch (error) {
613
+ throw error;
614
+ }
615
+ }
524
616
  if (isEdgeRuntime(runtime) && typeof apiModule.default === 'function') {
525
617
  res.status(500).json({
526
618
  error: 'Edge runtime route handlers must export HTTP method functions instead of a default Express handler.',
package/docs/ai.md ADDED
@@ -0,0 +1,69 @@
1
+ # AI
2
+
3
+ Import app code from `vista/ai` and React hooks from `vista/ai/react`. Do not add a third-party agent SDK for these primitives.
4
+
5
+ ## Model strings
6
+
7
+ `provider:model`:
8
+
9
+ - `openai:gpt-4o` (`OPENAI_API_KEY`)
10
+ - `anthropic:claude-3-5-sonnet` (`ANTHROPIC_API_KEY`)
11
+ - `gemini:gemini-1.5-flash` (`GEMINI_API_KEY`)
12
+ - `ollama:llama3`
13
+ - `groq:llama-3.1-8b-instant` (`GROQ_API_KEY`)
14
+ - `nvidia:meta/llama-3.1-8b-instruct` (`NVIDIA_API_KEY` or `NIM_API_KEY`)
15
+ - `mock:echo` for tests with no key
16
+
17
+ Do not commit API keys or `.env` files.
18
+
19
+ ## Agent
20
+
21
+ `vista g agent support` writes the files. The generated module looks like this:
22
+
23
+ ```ts
24
+ import { agent, tool } from 'vista/ai';
25
+
26
+ export const supportAgent = agent({
27
+ name: 'support',
28
+ model: process.env.VISTA_AI_MODEL || 'openai:gpt-4o',
29
+ systemPrompt: 'You are a helpful AI assistant for support.',
30
+ tools: [
31
+ tool({
32
+ name: 'ping',
33
+ description: 'Check agent connectivity',
34
+ execute: async () => ({ status: 'ok', time: new Date().toISOString() }),
35
+ }),
36
+ ],
37
+ memory: true,
38
+ });
39
+ ```
40
+
41
+ Route:
42
+
43
+ ```ts
44
+ import { supportAgent } from '../../../agents/support/agent';
45
+
46
+ export async function POST(req: Request) {
47
+ const { prompt, messages, sessionId } = await req.json();
48
+ const stream = supportAgent.stream({ prompt, messages, sessionId });
49
+ return stream.toDataStreamResponse();
50
+ }
51
+ ```
52
+
53
+ Client:
54
+
55
+ ```tsx
56
+ 'use client';
57
+ import { useAgent } from 'vista/ai/react';
58
+
59
+ export default function ChatPage() {
60
+ const { messages, input, setInput, handleSubmit, isLoading } = useAgent({
61
+ api: '/api/agents/support',
62
+ });
63
+ // render messages and a form that calls handleSubmit
64
+ }
65
+ ```
66
+
67
+ `agent()` options used by the framework: `name`, `model`, `systemPrompt`, `tools`, `memory`, `maxSteps` (default 5), `temperature`, `observability`. Turn another agent into a tool with `.asTool({ description })`.
68
+
69
+ `tool()` takes `name`, `description`, optional JSON-schema `parameters`, and `execute`.
package/docs/app.md ADDED
@@ -0,0 +1,30 @@
1
+ # App conventions
2
+
3
+ Vista apps are React. Server Components are the default under `app/`. Add `'use client'` only on modules that use state, effects, or browser events.
4
+
5
+ ## Routes
6
+
7
+ - `app/index.tsx` is the home page (`/`). There is no `app/page.tsx` convention.
8
+ - `app/root.tsx` is the root layout. There is no `app/layout.tsx` convention.
9
+ - A route segment is `app/<segment>/page.tsx` (for example `app/about/page.tsx` → `/about`).
10
+ - Dynamic segments use brackets: `app/docs/[...slug]/page.tsx`.
11
+ - HTTP handlers are `app/**/route.ts` exporting `GET`, `POST`, and the other methods.
12
+ - Shared UI is not scaffolded. Import theme pieces from `vista/theme` (`ThemeProvider`, `ThemeScript`, `ThemeToggle`). Add `components/` only when UI is reused.
13
+
14
+ ## Client boundaries
15
+
16
+ `'use client'` may live in `app/`, `components/`, `utils/`, `lib/`, or `src/`. The client manifest scans those roots.
17
+
18
+ ## Config and output
19
+
20
+ - `vista.config.ts` selects the engine with `engine.variant`: `'default'` or `'flashpack'`.
21
+ - `package.json` `vista.engine` is the same switch. Scripts stay `vista dev`, `vista build`, `vista start`.
22
+ - Production output is `.vista/` (the `.next` equivalent). Do not commit it.
23
+ - Flashpack dev output is `.flash/`. It is a compiler cache, not a second copy of `.vista/`.
24
+ - Dev server port is `3003`.
25
+
26
+ ## SEO files already in a new app
27
+
28
+ - `app/robots.ts`, `app/sitemap.ts`, `app/opengraph-image.tsx`
29
+ - Metadata helpers come from `vista` / `vista/metadata`. OG images use `vista/og`.
30
+ - `vista g seo` adds `app/manifest.ts` when it is missing.
package/docs/cli.md ADDED
@@ -0,0 +1,29 @@
1
+ # CLI
2
+
3
+ ## Create an app
4
+
5
+ ```bash
6
+ npx create-vista-app@latest my-app
7
+ npx create-vista-app@latest my-app --engine flashpack
8
+ npx create-vista-app@latest my-app --no-agents-md
9
+ ```
10
+
11
+ The scaffold writes `AGENTS.md` and `CLAUDE.md` unless `--no-agents-md` is set. `CLAUDE.md` is `@AGENTS.md`.
12
+
13
+ ## Generators
14
+
15
+ ```bash
16
+ vista g api-init
17
+ vista g router <name>
18
+ vista g procedure <name> [get|post]
19
+ vista g agent <name>
20
+ vista g auth
21
+ vista g seo
22
+ ```
23
+
24
+ - `vista g agent <name>` writes `app/agents/<name>/agent.ts`, `app/api/agents/<name>/route.ts`, and `app/AGENTS.md` if that file is missing.
25
+ - `vista g auth` writes `auth.ts`, `app/api/auth/[...vista]/route.ts`, `/signin`, `/account`, fail-closed `middleware.ts`, a `SessionProvider` wrapper, and `.env.example`. Set `AUTH_SECRET` before signing in. Import the server helper from `vista/auth` and the client helper from `vista/auth/react`.
26
+ - `vista g api-init` starts the typed API. Call procedures from a Server Component with `v.createCaller(router, { ctx, env })`.
27
+ - `vista g seo` writes `app/robots.ts`, `app/sitemap.ts`, and `app/manifest.ts`.
28
+
29
+ `vista deploy` writes platform files. The scaffold does not copy Docker, Render, Netlify, Vercel, or Cloudflare configs.
package/docs/index.md ADDED
@@ -0,0 +1,18 @@
1
+ # Vista docs for agents
2
+
3
+ These files ship with the installed `vista` package and match that version. Read the guide that fits the task before writing code. Do not invent Next.js App Router APIs, `app/page.tsx` as the home route, or `app/layout.tsx`.
4
+
5
+ | Guide | Use it for |
6
+ | --- | --- |
7
+ | [app.md](app.md) | Routes, layouts, client components, `vista.config.ts`, build output |
8
+ | [cli.md](cli.md) | `create-vista-app` and `vista g` generators |
9
+ | [ai.md](ai.md) | Agents, tools, streaming, model strings |
10
+ | [rag.md](rag.md) | `InMemoryVectorStore`, `createRetrieverTool`, `embedText` |
11
+
12
+ Workflow skills (read the matching `SKILL.md` before a multi-step task):
13
+
14
+ - `../skills/vista-dev-loop/SKILL.md`
15
+ - `../skills/vista-agent/SKILL.md`
16
+ - `../skills/vista-rag/SKILL.md`
17
+
18
+ Imports in an app are `vista/...`. The published package name is `@vistagenic/vista`.
package/docs/rag.md ADDED
@@ -0,0 +1,54 @@
1
+ # RAG
2
+
3
+ Index documents, retrieve matches, and pass them to an agent as a tool. Imports:
4
+
5
+ ```ts
6
+ import { agent, InMemoryVectorStore, createRetrieverTool, embedText } from 'vista/ai';
7
+ ```
8
+
9
+ ## Keyword search
10
+
11
+ Omit `embed`. The retriever uses keyword search and needs no API key. `mock:echo` is enough for a smoke test.
12
+
13
+ ```ts
14
+ const store = new InMemoryVectorStore();
15
+ store.addDocuments([
16
+ { id: 'rsc', text: 'Vista uses React Server Components by default under app/.' },
17
+ ]);
18
+
19
+ const search = createRetrieverTool({
20
+ name: 'search_knowledge_base',
21
+ description: 'Search the product knowledge base',
22
+ store,
23
+ topK: 3,
24
+ });
25
+
26
+ export const ragAgent = agent({
27
+ name: 'rag',
28
+ model: 'mock:echo',
29
+ systemPrompt: 'Answer using search_knowledge_base results only.',
30
+ tools: [search],
31
+ memory: true,
32
+ });
33
+ ```
34
+
35
+ ## Embeddings
36
+
37
+ `embedText` calls an OpenAI-compatible `/embeddings` endpoint. Set the provider key. Pass the same embedder to the store and the tool.
38
+
39
+ ```ts
40
+ const text = 'Vista builds into .vista/, similar to Next.js .next/.';
41
+ store.addDocument({
42
+ id: 'build',
43
+ text,
44
+ vector: await embedText(text, { model: 'openai:text-embedding-3-small' }),
45
+ });
46
+
47
+ const search = createRetrieverTool({
48
+ store,
49
+ embed: (query) => embedText(query, { model: 'openai:text-embedding-3-small' }),
50
+ topK: 3,
51
+ });
52
+ ```
53
+
54
+ Stream the agent from `app/api/agents/<name>/route.ts` and render it with `useAgent` from `vista/ai/react`. A runnable demo lives in the Vista repo at `apps/vista-rag-demo`.