The settings page imported `auth` from `@/lib/firebase/config` and called
`auth.currentUser` inside an unguarded `useEffect`. Since the app runs on
PostgreSQL + NextAuth (Firebase isn't configured), `auth` was `undefined`
and the uncaught TypeError crashed React's commit, leaving the page blank
behind the Next.js dev error overlay. The WorkspaceKeysPanel never got a
chance to mount even though `/api/workspaces` was returning fine.
Swap to `useSession()` from `next-auth/react` to read display name + email
from the existing NextAuth session. Drop the dead fetch to
`/api/workspace/{slug}/settings`, which was never implemented.
Made-with: Cursor
The panel was fetching /api/workspaces/{urlSlug} where {urlSlug}
is whatever is in the `[workspace]` dynamic segment (e.g.
"mark-account"). That slug has nothing to do with vibn_workspaces.slug,
which is derived from the user's email — so the fetch 404'd, the
component showed "Loading workspace…" forever, and minting/revoking
would target a non-existent workspace.
Now:
- GET /api/workspaces lazy-creates a workspace row if the signed-in
user has none (migration path for accounts created before the
signIn hook was added).
- WorkspaceKeysPanel discovers the user's actual workspace from that
list and uses *its* slug for all subsequent calls (details, keys,
provisioning, revocation).
- Empty / error states render a proper card with a retry button
instead of a bare "Workspace not found." line.
Made-with: Cursor
Adds logical multi-tenancy on top of Coolify + Gitea so every Vibn
account gets its own isolated tenant boundary, and exposes that
boundary to AI agents (Cursor, Claude Code, scripts) through
per-workspace bearer tokens.
Schema (additive, idempotent — run /api/admin/migrate once after deploy)
- vibn_workspaces: slug, name, owner, coolify_project_uuid,
coolify_team_id (reserved for when Coolify ships POST /teams),
gitea_org, provision_status
- vibn_workspace_members: room for multi-user workspaces later
- vibn_workspace_api_keys: sha256-hashed bearer tokens
- fs_projects.vibn_workspace_id: nullable FK linking projects
to their workspace
Provisioning
- On first sign-in, ensureWorkspaceForUser() inserts the row
(no network calls — keeps signin fast).
- On first project create, ensureWorkspaceProvisioned() lazily
creates a Coolify Project (vibn-ws-{slug}) and a Gitea org
(vibn-{slug}). Failures are recorded on the row, not thrown,
and POST /api/workspaces/{slug}/provision retries.
Auth surface
- lib/auth/workspace-auth.ts: requireWorkspacePrincipal() accepts
either a NextAuth session or "Authorization: Bearer vibn_sk_...".
The bearer key is hard-pinned to one workspace — it cannot
reach any other tenant.
- mintWorkspaceApiKey / listWorkspaceApiKeys / revokeWorkspaceApiKey
Routes
- GET /api/workspaces list
- GET /api/workspaces/[slug] details
- POST /api/workspaces/[slug]/provision retry provisioning
- GET /api/workspaces/[slug]/keys list keys
- POST /api/workspaces/[slug]/keys mint key (token shown once)
- DELETE /api/workspaces/[slug]/keys/[keyId] revoke
UI
- components/workspace/WorkspaceKeysPanel.tsx: identity card,
keys CRUD with one-time secret reveal, and a "Connect Cursor"
block with copy/download for:
.cursor/rules/vibn-workspace.mdc — rule telling the agent
about the API + workspace IDs + house rules
~/.cursor/mcp.json — MCP server registration with key
embedded (server URL is /api/mcp; HTTP MCP route lands next)
.env.local — VIBN_API_KEY + smoke-test curl
- Slotted into existing /[workspace]/settings between Workspace
and Notifications cards (no other layout changes).
projects/create
- Resolves the user's workspace (creating + provisioning lazily).
- Repos go under workspace.gitea_org (falls back to GITEA_ADMIN_USER
for backwards compat).
- Coolify services are created inside workspace.coolify_project_uuid
(renamed {slug}-{appName} to stay unique within the namespace) —
no more per-Vibn-project Coolify Project sprawl.
- Stamps vibn_workspace_id on fs_projects.
lib/gitea
- createOrg, getOrg, addOrgOwner, getUser
- createRepo now routes /orgs/{owner}/repos when owner != admin
Also includes prior-turn auth hardening that was already in
authOptions.ts (CredentialsProvider for dev-local, isLocalNextAuth
cookie config) bundled in to keep the auth layer in one consistent
state.
.env.example
- Documents GITEA_API_URL / GITEA_API_TOKEN / GITEA_ADMIN_USER /
GITEA_WEBHOOK_SECRET and COOLIFY_URL / COOLIFY_API_TOKEN /
COOLIFY_SERVER_UUID, with the canonical hostnames
(git.vibnai.com, coolify.vibnai.com).
Post-deploy
- Run once: curl -X POST https://vibnai.com/api/admin/migrate \\
-H "x-admin-secret: \$ADMIN_MIGRATE_SECRET"
- Existing users get a workspace row on next sign-in.
- Existing fs_projects keep working (legacy gitea owner + their
own per-project Coolify Projects); new projects use the
workspace-scoped path.
Not in this commit (follow-ups)
- Wiring requireWorkspacePrincipal into the rest of /api/projects/*
so API keys can drive existing routes
- HTTP MCP server at /api/mcp (the mcp.json snippet already
points at the right URL — no client re-setup when it lands)
- Backfill script to assign legacy fs_projects to a workspace
Made-with: Cursor
- Add app/styles/justine/01-homepage.css: rules from 01_homepage.html scoped to [data-justine]
- Replace app/(marketing) with app/(justine): layout wraps data-justine + Plus Jakarta
- JustineHomePage/Nav/Footer: original class names (btn-ink, hero-grid, …) + inline styles from HTML
- Remove app/justine-marketing.css; move /features /pricing /privacy /terms under (justine)
Made-with: Cursor
- Replace blue/purple gradients with ink gradient text and cream/parch CTA surface
- Step badges and transformation icons use primary (ink) fills
- /features page icons unified to text-primary; Lora section titles
- Tree view status colors use semantic tokens instead of blue/green
Made-with: Cursor
- Map Justine tokens to shadcn CSS variables (--vibn-* aliases)
- Switch fonts to Inter + Lora via next/font (IBM Plex Mono for code)
- Base typography: body Inter, h1–h3 Lora; marketing hero + wordmark serif
- Project shell and global chrome use semantic colors
- Replace Outfit/Newsreader references across TSX inline styles
Made-with: Cursor
New users were being sent to /marks-account/projects. Now derives
workspace from the signed-in user's email so everyone lands on
their own workspace after Google OAuth.
Made-with: Cursor
- PRD page now has a tabbed view: PRD | Architecture
Architecture tab renders apps, packages, infrastructure, integrations,
and risk notes as structured cards. Only shown when arch doc exists.
- Advisor route now includes the architecture summary and key fields
in the COO's knowledge context so the orchestrator knows what's
been planned technically
Made-with: Cursor
The advisor route now proxies to /orchestrator/chat on agents.vibnai.com
instead of calling Gemini directly. The Orchestrator (Claude Sonnet 4.6)
has full tool access — Gitea, Coolify, web search, memory, agent spawning.
- Build project knowledge_context from DB (name, vision, repo, PRD,
phases, apps, recent sessions) and inject as COO persona + data
- Convert frontend history format (model→assistant) for the orchestrator
- Return orchestrator reply as streaming text response
- Session scoped per project for in-memory context persistence
Made-with: Cursor
MigrateSetup now sends the PAT field to the API; create route
forwards it as github_token so the agent runner can clone private repos.
Made-with: Cursor
- Top bar left section (320px) = logo + project name, aligns with chat panel
- Top bar right section = Build|Market|Assist pills + tool icons (Preview, Tasks, Code, Design, Backend) + avatar
- Read GOOGLE_API_KEY inside POST handler (not top-level) to ensure env is resolved at request time
Made-with: Cursor
- New top navbar in ProjectShell: logo + project name | Build | Market |
Assist tabs | user avatar — replaces the left icon sidebar for project pages
- CooChat extracted to components/layout/coo-chat.tsx and moved into the
shell so it persists across Build/Market/Assist route changes
- Build page inner layout simplified: inner nav (200px) + file viewer,
no longer owns the chat column
- Layout: [top nav 48px] / [Assist chat 320px | content flex]
Made-with: Cursor
- New CooChat component: streaming Gemini-backed advisor chat, message
bubbles, typing cursor animation, Shift+Enter for newlines
- New /api/projects/[projectId]/advisor streaming endpoint: builds a
COO system prompt from project context (name, description, vision,
repo), proxies Gemini SSE stream back to the client
- Restructured BuildHubInner layout:
Left (340px): CooChat — persistent across all Build sections
Inner nav (200px): Build pills + contextual items (apps, tree, surfaces)
Main area: File viewer for Code, Layouts content, Infra content
- AgentMode removed from main view — execution surfaces via COO delegation
Made-with: Cursor
- B (left sidebar, 260px): project header, Build pills (Code/Layouts/Infra),
app list, file tree embedded below active app
- D (center): AgentMode as primary content; sessions shown as a horizontal
chip strip at the top instead of a 220px left sidebar
- Right (460px): FileViewer — shows file selected in B's tree / code changes
- F (bottom): Terminal collapsible strip unchanged
- Split CodeContent into FileTree + FileViewer components; lifted file
selection state to BuildHubInner so B and Right share it
Made-with: Cursor
Removes the Browse/Agent/Terminal tab switcher from the code section.
Browse (file tree + viewer) is now the left pane, Agent chat is a
fixed 420px right pane, and Terminal is a collapsible strip at the
bottom — all visible simultaneously.
Made-with: Cursor
- Fall back to CODEBASE_MAP.md parsing when no apps/ dir exists
- Further fallback: scan top-level dirs for deployable app signals
(package.json, Dockerfile, requirements.txt, next.config.*, etc.)
- Skips docs, scripts, keys, and other non-app directories
- Returns isImport flag to frontend for context
Made-with: Cursor
- sessions POST: look up coolifyServiceUuid, pass autoApprove:true to runner
- sessions PATCH: approved added to terminal statuses (sets completed_at)
- build/page.tsx: approved status, STATUS_COLORS/LABELS for "Shipped",
auto-committed UI in changed files panel, bottom bar for approved state
- Architecture doc: fully updated with current state
Made-with: Cursor
- Running/pending: input locked with "agent is working" message
- Done: shows "+ Follow up" and "New task" buttons instead of open input
- No session: normal new-task input (unchanged UX)
- On mount: auto-selects the most recent running/pending session,
falls back to latest session — so navigating away and back doesn't
lose context and doesn't require manual re-selection
Made-with: Cursor
- retry/route.ts: reset failed/stopped session and re-fire agent runner
with optional continueTask follow-up text
- build/page.tsx: Retry button and Follow up input appear on failed/stopped
sessions so users can continue without losing context or creating a
duplicate session; task input hint clarifies each Run = new session
Made-with: Cursor
PostgreSQL can't implicitly coerce text params to UUID columns.
Add explicit ::uuid casts on id and project_id in all agent session
routes (list, get, patch, stop, approve).
Made-with: Cursor
- Wire Approve & commit button: shows commit message input, calls
POST /api/.../sessions/[id]/approve which asks agent runner to
git commit + push, then marks session as approved in DB
- Adaptive polling: 500ms while session running, 5s when idle —
output feels near-real-time without hammering the API
- Auto-refresh session list when a session completes
- Open in Theia links to theia.vibnai.com (escape hatch for manual edits)
Made-with: Cursor
- Sessions route now reads giteaRepo from project.data and forwards it
to /agent/execute so the runner can clone/update the correct repo
- PATCH route now validates x-agent-runner-secret header to prevent
unauthorized session output injection
Made-with: Cursor
- Sidebar Infrastructure replaced with 6 named rows linking to /infrastructure?tab=
- New /infrastructure page with left sub-nav and per-tab content panels:
Builds — lists deployed Coolify apps with live status
Databases — coming soon placeholder
Services — coming soon placeholder
Environment — variable table with masked values (scaffold)
Domains — lists configured domains with SSL status
Logs — dark terminal panel, ready to stream
- Dim state on rows reflects whether data exists (e.g. no domains = dim)
Made-with: Cursor
- Sidebar Layouts items now link to /design?surface=<surfaceId>
- Design page reads ?surface= param and opens that surface directly
- DesignPage split into DesignPageInner + Suspense wrapper so
useSearchParams works in the Next.js static build
Made-with: Cursor
- Sidebar "Apps" section renamed to "Build"
- Each app now links to /build?app=<name>&root=<path> so the browser
opens scoped to that app's subdirectory only
- Build page shows an empty-state prompt when no app is selected
- File tree header shows the selected app name, breadcrumb shows
relative path within the app (strips the root prefix)
- Wraps useSearchParams in Suspense for Next.js static rendering
Made-with: Cursor
- Build page: full file tree (lazy-load dirs) + code preview panel
with line numbers and token-level syntax colouring (VS Code dark theme)
- New API route /api/projects/[id]/file proxies Gitea contents API
returning directory listings or decoded file content
- Sidebar Apps section now links to /build instead of raw Gitea URL
- Status indicator replaced with a proper coloured dot (amber/blue/green)
alongside the status label text
Made-with: Cursor
Add ::text cast to all $1/$2 parameters so PostgreSQL never needs
to infer types. Split SELECT and UPDATE into separate try/catch blocks
with distinct error labels so logs show exactly which query fails.
Made-with: Cursor
PostgreSQL could not determine the type of $2 in 'WHERE id = $2'
when id column type is UUID. Casting the column (id::text = $1)
sidesteps the extended-protocol type inference issue. Also moves
projectId to $1 to match the proven working pattern in other routes.
Made-with: Cursor
- Add themeMode?: 'dark'|'light' to ThemeColor (unset = any mode)
- Tag all DaisyUI themes: 11 dark (synthwave, aqua, luxury, night, etc.)
and 6 light (light, cupcake, valentine, cyberpunk, retro, winter)
- Tag HeroUI Marketing themes: purple/blue/teal/modern=light, dark=dark
- Aceternity accent palettes stay untagged (work with either mode)
- Filter availableColorThemes in SurfaceSection by designConfig.mode
- Auto-reset active palette when mode switches makes previously
selected palette incompatible
Made-with: Cursor