nexus/ui/vite.config.ts
Nexus Dev 3d2117ee9f fix(nexus): auto-bootstrap invite and vite onnxruntime middleware
Zero-terminal first boot. Previously the bootstrap_ceo invite had to be
created via a CLI command (paperclipai auth bootstrap-ceo) and the UI
showed a code block instructing the user to run it. Nexus is meant to
be zero-terminal, so the server now auto-creates the invite on startup
when no instance admin exists and exposes its relative path through
/api/health. BootstrapPendingPage redirects straight to /invite/{token}.
The CLI command is left intact for headless/SSH-only setups.

Invite flow fixes that surfaced during testing:

  - InviteLanding's invite query had default React Query refetch
    behavior. After a successful bootstrap accept, the invite is marked
    accepted server-side, so the refetch returned "not available" and
    shadowed the success screen, making it look like the bootstrap had
    failed when it actually succeeded. Set staleTime: Infinity +
    refetchOnWindowFocus/Mount/Reconnect: false so the first fetch is a
    one-shot snapshot.

  - Reordered the render checks so result?.kind === "bootstrap" / "join"
    are evaluated before the invite-availability error check — defensive
    against any stray refetch that still leaks through.

  - On bootstrap success, window.location.replace("/") lands the new
    admin directly on the board; the "Bootstrap complete" confirmation
    screen is now an unreachable safety net.

Vite onnxruntime middleware replaces the earlier public/ dump. The
previous commit put ort-wasm-simd-threaded.{mjs,wasm} in ui/public/ so
VAD's onnxWASMBasePath: "/" would find them. That works at runtime but
trips vite's dep optimizer: it scans onnxruntime-web, resolves the
dynamic import string to the public asset, and errors with "files in
/public should not be imported from source code." Remove the files and
add a vite plugin (configureServer middleware) that serves the two URLs
straight from node_modules/.pnpm/onnxruntime-web@*/. Runtime keeps
working and the files never enter vite's module graph.

Production build caveat: the middleware only runs in dev. When building
a static dist for production, the wasm files will need a different
mechanism (e.g. generateBundle hook). Not addressed here.

Also bundled (load-bearing for LAN browser testing):

  - ui/src/lib/queryKeys.ts: add missing 'nexus' group. useNexusMode
    referenced queryKeys.nexus.settings since commit 7bb72a5a (Phase
    33-02) but the key was never added. Caused a blank screen crash on
    any page that mounts Sidebar.

  - ctl.sh: read PORT from .env instead of hardcoding 3100, and read it
    once at the top so every subcommand honors it. Fixes the Version /
    Mode showing '?' in status output after the port move to 6100.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:50:28 +00:00

112 lines
4.1 KiB
TypeScript

import fs from "node:fs";
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
// [nexus] Dev-only middleware that serves onnxruntime-web's wasm files at
// /ort-wasm-simd-threaded.{mjs,wasm}. @ricky0123/vad-react sets
// `onnxWASMBasePath: "/"`, which makes onnxruntime-web issue dynamic imports
// against the site root at runtime. Putting the files in ui/public/ works
// at runtime but trips vite's dep optimizer ("files in /public should not
// be imported from source code") because it scans onnxruntime-web's dynamic
// import and resolves the string to the public asset. This plugin bypasses
// public/ entirely — the files never enter vite's module graph, and the URL
// is served straight from node_modules.
function serveOnnxRuntimeWasm(): import("vite").Plugin {
// onnxruntime-web is a transitive dep of @ricky0123/vad-web and not hoisted
// to top-level node_modules under pnpm. Scan .pnpm/ for the version dir.
const pnpmRoot = path.resolve(__dirname, "../node_modules/.pnpm");
const ortEntry = fs
.readdirSync(pnpmRoot)
.find((name) => name.startsWith("onnxruntime-web@"));
const distDir = ortEntry
? path.join(pnpmRoot, ortEntry, "node_modules/onnxruntime-web/dist")
: null;
return {
name: "nexus-serve-onnxruntime-wasm",
configureServer(server) {
if (!distDir) {
server.config.logger.warn(
"[nexus-serve-onnxruntime-wasm] onnxruntime-web not found in .pnpm store — VAD voice input will 404",
);
return;
}
server.middlewares.use((req, res, next) => {
const url = (req.url ?? "").split("?")[0];
const match = url.match(/^\/ort-wasm-simd-threaded\.(mjs|wasm)$/);
if (!match) return next();
const filePath = path.join(distDir, `ort-wasm-simd-threaded.${match[1]}`);
fs.readFile(filePath, (err, content) => {
if (err) {
next(err);
return;
}
res.setHeader(
"Content-Type",
match[1] === "mjs" ? "text/javascript" : "application/wasm",
);
res.end(content);
});
});
},
};
}
// [nexus] Redirect OnboardingWizard → NexusOnboardingWizard at resolve time.
// A Vite plugin is used instead of resolve.alias because alias matches against
// the raw import specifier ("./components/OnboardingWizard") — not the resolved
// absolute path — so an absolute-path alias key never fires.
function nexusOnboardingRedirect(): import("vite").Plugin {
const target = path.resolve(__dirname, "./src/components/NexusOnboardingWizard.tsx");
return {
name: "nexus-onboarding-redirect",
enforce: "pre",
resolveId(source) {
if (source.endsWith("/components/OnboardingWizard") && !source.includes("Nexus")) {
return target;
}
},
};
}
export default defineConfig({
plugins: [nexusOnboardingRedirect(), serveOnnxRuntimeWasm(), react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
lexical: path.resolve(__dirname, "./node_modules/lexical/Lexical.mjs"),
},
},
build: {
rollupOptions: {
output: {
manualChunks: {
// Note: react/react-dom cannot be split via manualChunks — @vitejs/plugin-react
// injects the JSX runtime before Rollup processes chunks, so they stay in the entry.
// The 37 lazy-loaded page chunks are the primary performance lever.
"vendor-router": ["react-router-dom"],
"vendor-query": ["@tanstack/react-query"],
"vendor-markdown": ["react-markdown", "remark-gfm"],
},
},
},
},
server: {
port: 5173,
// COOP/COEP headers are only respected over HTTPS or localhost.
// Omitted so LAN access (plain HTTP + non-localhost IP) doesn't trigger
// browser warnings. Re-enable when serving behind TLS.
// headers: {
// "Cross-Origin-Opener-Policy": "same-origin",
// "Cross-Origin-Embedder-Policy": "require-corp",
// },
proxy: {
"/api": {
target: "http://localhost:6100",
ws: true,
},
},
},
});