Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/actions/ci-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ inputs:
default: "24.x"

bun-version:
description: "Bun version to install (default: 1.3.9)"
description: "Bun version to install in CI (default: 1.3.9; may differ from Vercel's resolved 1.x patch)"
required: false
default: "1.3.9"

Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,34 @@ jobs:

- name: Build
run: bun run build

build-bun-strict:
name: Build (Next.js, Bun Strict Canary)
runs-on: ${{ fromJSON(vars.ACTIONS_RUNNER_LABELS || '["ubuntu-latest"]') }}
needs: ["lint", "typecheck", "test-merge"]
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
fetch-depth: 1

- name: Setup
uses: ./.github/actions/ci-setup

- name: Ensure clean build output
run: rm -rf .next

- name: Build (strict Bun)
run: |
set -o pipefail
mkdir -p .artifacts
bun run build:strict-bun 2>&1 | tee .artifacts/build-bun-strict.log

- name: Upload strict Bun build logs
if: ${{ always() && !cancelled() }}
uses: actions/upload-artifact@v6.0.0
with:
name: build-bun-strict-log
path: .artifacts/build-bun-strict.log
retention-days: 7
Comment thread
BjornMelin marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ This repo is optimized for agent-driven development (Codex + local automation).
```bash
bun install # Install dependencies
bun run dev # Start development server
bun run build # Build for production
bun run build # Build for production (Bun-first, Node fallback on known transient Bun/Turbopack failures)
bun run build:strict-bun # Bun-only production build (canary signal)
bun run format # Format code with Biome
bun run lint # Run Biome and ESLint
bun run typecheck # Run TypeScript compiler checks
Expand Down
539 changes: 248 additions & 291 deletions bun.lock

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions docs/ops/build-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Build Runtime Policy

This project uses a Bun-only build strategy for local, CI, and deployment
builds.

## Goals

- Keep Bun as the only supported runtime for installs and scripts.
- Maintain consistent behavior across local, CI, and deployment builds.

## Build entrypoints

- `bun run build`
- Runs `bun --bun next build --webpack`.
- `bun run build:strict-bun`
- Runs `bun --bun next build --webpack`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

`bun run build` is the default local/general entrypoint. `bun run build:strict-bun`
is the CI/strict alias used for canary visibility and future divergence; it is
intentionally equivalent today for reproducibility, but reserved for stricter
environment enforcement or flags without changing developer defaults.

## CI policy

- Primary CI build job runs `bun run build`.
- A separate non-blocking canary job runs `bun run build:strict-bun` and uploads
logs as an artifact.

## Vercel policy

- `vercel.json` uses:
- `buildCommand: bun run build:vercel`
- `installCommand: bun install --frozen-lockfile`
- `bunVersion: 1.x`

This keeps dependency installs reproducible while preserving Bun runtime support
for the deployed project.
2 changes: 0 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const js = require("@eslint/js");
const globals = require("globals");
const jsdoc = require("eslint-plugin-jsdoc");
const tsdoc = require("eslint-plugin-tsdoc");
const tseslint = require("@typescript-eslint/eslint-plugin");
const tsParser = require("@typescript-eslint/parser");
const nextCoreWebVitals = require("eslint-config-next/core-web-vitals");
const drizzle = require("eslint-plugin-drizzle");
Expand Down Expand Up @@ -53,7 +52,6 @@ module.exports = [
},
},
plugins: {
"@typescript-eslint": tseslint,
drizzle,
jsdoc,
tsdoc,
Expand Down
35 changes: 35 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import path from "node:path";
import type { NextConfig } from "next";
import { withWorkflow } from "workflow/next";

const workflowExcludeNodeModulesRule = {
condition: { all: [{ not: { path: /[/\\]node_modules[/\\]/ } }] },
loaders: [],
} satisfies NonNullable<NonNullable<NextConfig["turbopack"]>["rules"]>[string];

/** Base Next.js configuration for app runtime, images, and build behavior. */
const nextConfig: NextConfig = {
cacheComponents: true,
experimental: {
optimizePackageImports: ["radix-ui", "lucide-react"],
// Keep build filesystem cache explicitly disabled until we gather stable
// CI/Vercel benchmark data for this project.
turbopackFileSystemCacheForBuild: false,
},
images: {
contentDispositionType: "attachment",
Expand Down Expand Up @@ -34,6 +42,33 @@ const nextConfig: NextConfig = {
reactCompiler: true,
turbopack: {
root: path.resolve(__dirname),
rules: {
"*.cjs": workflowExcludeNodeModulesRule,
"*.cts": workflowExcludeNodeModulesRule,
"*.js": workflowExcludeNodeModulesRule,
"*.jsx": workflowExcludeNodeModulesRule,
"*.mjs": workflowExcludeNodeModulesRule,
"*.mts": workflowExcludeNodeModulesRule,
"*.ts": workflowExcludeNodeModulesRule,
"*.tsx": workflowExcludeNodeModulesRule,
},
},
webpack: (config) => {
if (config.module?.rules) {
config.module.rules = config.module.rules.filter((rule: unknown) => {
if (
typeof rule === "object" &&
rule !== null &&
"loader" in rule &&
typeof rule.loader === "string" &&
rule.loader.includes("@workflow/next/dist/loader")
) {
return false;
}
return true;
});
}
return config;
},
};

Expand Down
66 changes: 39 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"auth:create:local": "bun scripts/neon-auth-local.ts create",
"auth:repair:local": "bun scripts/neon-auth-local.ts repair",
"auth:smoke:local": "bun scripts/neon-auth-local.ts smoke",
"build": "bun --bun next build",
"build": "bun --bun next build --webpack",
"build:strict-bun": "bun --bun next build --webpack",
Comment on lines +14 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Scripts build and build:strict-bun are identical.

Both scripts run the exact same command: bun --bun next build --webpack. If they're intended to serve different purposes (e.g., CI canary vs. standard build), consider differentiating them or adding a comment in the documentation explaining the semantic distinction. If they're intentionally identical for now, this is acceptable but worth noting for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 14 - 15, The package.json defines two scripts,
"build" and "build:strict-bun", that currently run the identical command (`bun
--bun next build --webpack`); either make their intent explicit or differentiate
them: update the "build:strict-bun" or "build" script to the intended different
command (e.g., add stricter flags or CI-specific env vars) or add a clarifying
comment in docs/README describing why both exist and are identical for now;
reference the "build" and "build:strict-bun" script names when making the change
so future maintainers understand the semantic distinction.

"build:vercel": "bun run db:migrate && bun run build",
"ci": "bun run lint && bun run typecheck && bun run test:coverage && bun run build",
"db:generate": "drizzle-kit generate",
Expand All @@ -35,30 +36,30 @@
"typegen": "bun -e \"import { rmSync } from 'node:fs'; rmSync('.next/types', { recursive: true, force: true }); rmSync('.next/dev/types', { recursive: true, force: true });\" && bun --bun next typegen"
},
"dependencies": {
"@ai-sdk/mcp": "^1.0.19",
"@ai-sdk/react": "^3.0.79",
"@mendable/firecrawl-js": "^4.12.0",
"@ai-sdk/mcp": "^1.0.21",
"@ai-sdk/react": "^3.0.88",
"@mendable/firecrawl-js": "^4.12.1",
"@neondatabase/auth": "0.2.0-beta.1",
"@octokit/rest": "^22.0.1",
"@radix-ui/react-use-controllable-state": "^1.2.2",
"@rive-app/react-webgl2": "^4.26.2",
"@streamdown/cjk": "^1.0.1",
"@streamdown/code": "^1.0.1",
"@streamdown/math": "^1.0.1",
"@streamdown/mermaid": "^1.0.1",
"@rive-app/react-webgl2": "^4.27.0",
"@streamdown/cjk": "^1.0.2",
"@streamdown/code": "^1.0.2",
"@streamdown/math": "^1.0.2",
"@streamdown/mermaid": "^1.0.2",
"@tanstack/react-virtual": "^3.13.18",
"@upstash/qstash": "~2.9.0",
"@upstash/ratelimit": "~2.0.8",
"@upstash/redis": "~1.36.2",
"@upstash/vector": "~1.2.2",
"@vercel/analytics": "^1.6.1",
"@vercel/blob": "~2.2.0",
"@vercel/functions": "^3.4.1",
"@vercel/sandbox": "^1.4.1",
"@vercel/functions": "^3.4.2",
"@vercel/sandbox": "^1.5.0",
"@vercel/sdk": "^1.19.1",
"@workflow/ai": "^4.0.1-beta.52",
"@xyflow/react": "^12.10.0",
"ai": "^6.0.77",
"ai": "^6.0.86",
"ai-sdk-tool-code-execution": "^0.0.2",
"ansi-to-react": "^6.2.6",
"bash-tool": "^1.3.14",
Expand All @@ -72,7 +73,7 @@
"jszip": "^3.10.1",
"lucide-react": "^0.563.0",
"media-chrome": "^4.17.2",
"motion": "^12.33.0",
"motion": "^12.34.0",
"nanoid": "^5.1.6",
"next": "~16.1.6",
"next-themes": "^0.4.6",
Expand All @@ -84,44 +85,55 @@
"server-only": "^0.0.1",
"shiki": "^3.22.0",
"sonner": "^2.0.7",
"streamdown": "^2.1.0",
"tailwind-merge": "^3.4.0",
"streamdown": "^2.2.0",
"tailwind-merge": "^3.4.1",
"tokenlens": "^1.3.1",
"use-stick-to-bottom": "^1.1.3",
"vaul": "^1.1.2",
"workflow": "^4.1.0-beta.52",
"workflow": "^4.1.0-beta.57",
"zod": "~4.3.6"
},
"devDependencies": {
"@biomejs/biome": "^2.3.14",
"@biomejs/biome": "^2.4.1",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.37.0",
"@tailwindcss/postcss": "^4.1.18",
"@types/node": "^24.10.12",
"@types/node": "^24.10.13",
"@types/pg": "^8.16.0",
"@types/react": "^19.2.13",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"@vitejs/plugin-react": "^5.1.3",
"@typescript-eslint/eslint-plugin": "^8.56.0",
"@typescript-eslint/parser": "^8.56.0",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18",
"babel-plugin-react-compiler": "^1.0.0",
"dotenv": "^17.2.4",
"drizzle-kit": "^0.31.8",
"dotenv": "^17.3.1",
"drizzle-kit": "^0.31.9",
"eslint": "^9.37.0",
"eslint-config-next": "^16.1.6",
"eslint-plugin-drizzle": "^0.2.3",
"eslint-plugin-jsdoc": "^62.5.4",
"eslint-plugin-jsdoc": "^62.5.5",
"eslint-plugin-tsdoc": "^0.5.0",
"globals": "^17.3.0",
"jsdom": "^28.0.0",
"jsdom": "^28.1.0",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"typescript": "~5.9.3",
"vite-tsconfig-paths": "^6.1.0",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.18"
},
"overrides": {
"@esbuild-kit/core-utils/esbuild": "^0.27.2",
"@workflow/web/next": "^16.1.6",
"@workflow/world-local/undici": "^6.23.0",
"axios": "^1.13.5",
"devalue": "^5.6.2",
"esbuild": "^0.27.2",
"lodash-es": "^4.17.23",
"next": "^16.1.6",
"undici": "^7.21.0"
},
"trustedDependencies": [
"sharp",
"unrs-resolver"
Expand Down
10 changes: 8 additions & 2 deletions src/app/(app)/projects/[projectId]/chat/chat-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ const CHAT_MAX_ATTACHMENT_FILES =
: defaultUploadMaxFiles;

type UserMessageMarker = Readonly<{
type: "user-message";
id: string;
content: string;
domain?: "chat";
files?: readonly FileUIPart[] | undefined;
id: string;
timestamp: number;
type: "user-message";
version?: 2;
}>;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

type AppUIMessage = UIMessage<unknown, UIDataTypes, UITools>;
Expand Down Expand Up @@ -145,8 +147,12 @@ function isUserMessageMarker(data: unknown): data is UserMessageMarker {
if (!data || typeof data !== "object") return false;
const value = data as Partial<UserMessageMarker>;
const hasValidFiles = value.files === undefined || Array.isArray(value.files);
const hasValidDomain = value.domain === "chat" || value.domain === undefined;
const hasValidVersion = value.version === 2 || value.version === undefined;
return (
hasValidDomain &&
value.type === "user-message" &&
hasValidVersion &&
typeof value.id === "string" &&
typeof value.content === "string" &&
hasValidFiles &&
Expand Down
Loading
Loading