IdeaMesh is a multi-agent AI ideation platform that turns a raw software idea into a structured plan. It runs a staged pipeline:
- Seed
- Mesh
- Probe
- Scout
- Compare
- Blueprint
- Pitch Deck
The platform has two core differentiators on top of the linear flow:
- Copy-on-write branching. Any pipeline can fork at a completed stage. The branch deep-copies upstream state, resets downstream stages to
idle, and preserves the original timeline. - In-context refinement with queued notes. Any generated block can be made editable. Notes are queued across a stage and then applied in one batch refinement call, producing at most one fork.
Users provide a software idea as either personal or business. The app then runs brainstorming, clarification, critique, competitor research, comparison, technical synthesis, and optionally a pitch deck.
- Framework: Next.js 16.2.6 with App Router
- Runtime: Node.js server components and route handlers
- Language: TypeScript 5
- UI: React 19.2.4
- Styling: Tailwind CSS 3.4.x
- Motion: framer-motion 12.x
- Icons: lucide-react 1.x
- AI SDK: openai 6.x
- State: zustand 5.x with persist middleware
- Auth and DB:
@supabase/ssr0.10.x and@supabase/supabase-js2.x - Utilities: clsx and tailwind-merge via
cn() - Fonts: Geist
Notes:
npm run devusesnext dev --webpack.npm run buildusesnext buildand succeeds with the current Linux Vercel build setup.- Do not add platform-specific SWC packages to the root dependencies. Next already resolves the correct optional binary for the host platform.
src/
app/
(workspace)/
layout.tsx
mesh/page.tsx
probe/page.tsx
scout/page.tsx
compare/page.tsx
blueprint/page.tsx
pitchdeck/page.tsx
api/agents/
brainstorm/route.ts
qa/route.ts
probe/route.ts
scout/route.ts
compare/route.ts
blueprint/route.ts
pitchdeck/route.ts
refine/route.ts
refine-batch/route.ts
auth/
login/page.tsx
signup/page.tsx
forgot-password/page.tsx
reset-password/page.tsx
callback/route.ts
profile/page.tsx
layout.tsx
page.tsx
components/
pipeline/StageRail.tsx
stages/*.tsx
ui/AgentCard.tsx
ui/EditableBlock.tsx
ui/EditPopover.tsx
ui/RefinementQueueBar.tsx
ui/UserMenu.tsx
lib/
ai/client.ts
ai/prompts.ts
ai/stream.ts
jsonPath.ts
search/
index.ts
duckduckgo.ts
tavily.ts
http.ts
types.ts
supabase/
client.ts
server.ts
config.ts
utils.ts
store/
pipelineStore.ts
editModeStore.ts
types/pipeline.ts
proxy.ts
| # | Stage ID | Label | Purpose | Agents |
|---|---|---|---|---|
| 1 | seed |
Seed | Capture the initial idea | Input |
| 2 | mesh |
Mesh | Expand and question the idea | Brainstorm, Q&A |
| 3 | probe |
Probe | Evaluate weaknesses and risks | Pros/Cons, Critique |
| 4 | scout |
Scout | Research competitors and similar tools | Web Search |
| 5 | compare |
Compare | Identify overlap and gaps | Comparison |
| 6 | blueprint |
Blueprint | Produce the build plan and coding guidance | Synthesis |
| 7 | pitchdeck |
Pitch Deck | Generate a short investor-style deck for business ideas | Pitch Deck |
Pitch Deck is hidden for personal ideas in the StageRail and on stage navigation.
- The landing page collects title, description, and idea type.
initPipeline()creates the initialPipelineState, marks Seed as done, and stores it in Zustand.- Stage pages auto-run on mount when their status is
idle. - Each stage calls its route handler, parses JSON, updates
PipelineContext, and marks the stage done or error. setStageStatus('done' | 'error')triggers persistence.- The workspace layout and StageRail let the user move through stages or swap branches.
Every pipeline belongs to a tree:
PipelineState.id: unique pipeline idPipelineState.rootId: topmost ancestor id, equal toidfor a root pipelinePipelineState.parentId: direct parent id, orundefinedfor rootsPipelineState.branchName: label shown in the StageRailPipelineState.forkedAtStage: stage where the branch diverged
Branching behavior in pipelineStore.ts:
- The current pipeline is saved first so the parent row exists before the branch is inserted.
- The new branch deep-clones the pipeline state.
- Downstream stages after the fork point are reset to
idle. - Downstream context keys are trimmed so reruns regenerate them instead of reusing stale output.
- The new branch is persisted and becomes active.
Saved pipeline browsing:
fetchSavedPipelines()returns only root pipelines for the main saved list.fetchBranches(rootId)returns all pipelines sharing a root, used by the tree view.- Deleting a pipeline cascades to descendants in Supabase because the foreign key is
ON DELETE CASCADE.
State lives in src/store/editModeStore.ts and supports:
isEditModeactiveEditqueueenqueue()removeFromQueue()clearQueueFor()rekeyQueueFor()
Flow:
- User enables Edit mode.
- Editable blocks become clickable.
- Clicking a block opens
EditPopover. - The note is queued, keyed by pipeline id, stage, and JSON path.
RefinementQueueBarappears when there are queued items for the current stage.- Proceeding applies all notes in one batch refine call.
- If downstream output already exists, the app forks once before applying refinements.
/api/agents/refine-batch:
- Receives
fullContext,stage, and an array of{ targetPath, label, instruction } - Looks up each path with
getAtPath - Asks the model to rewrite all queued targets coherently
- Validates that the returned values keep the same JSON shape
- Rejects output if keys are added, removed, renamed, or if primitive types change
Limits:
- Max 50 refinements per batch
- Max 2000 characters per instruction
For authenticated users, pipeline state is stored in public.pipelines and profile data in public.profiles.
Pipeline columns:
iduser_idtitleidea_typecurrent_stagestagescontextparent_idroot_idbranch_nameforked_at_stagecreated_atupdated_at
If Supabase is not configured, the app runs in local mode:
- Pipeline state falls back to localStorage
- Saved ideas still work locally
- Auth pages and profile page degrade gracefully instead of throwing during build or render
src/lib/supabase/config.ts is the single source of truth for whether Supabase is configured. It checks:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEY
The browser and server Supabase clients are only created when configuration is present.
- Email/password and Google OAuth use Supabase Auth
src/proxy.tsreplaces the deprecatedmiddleware.tsstyle and handles session refresh plus route protection- Protected routes:
/mesh,/probe,/scout,/compare,/blueprint,/pitchdeck,/profile - Auth pages:
/auth/login,/auth/signup,/auth/forgot-password,/auth/reset-password,/auth/callback - When Supabase is configured, unauthenticated users are redirected to login for protected routes
- When Supabase is not configured, the app stays in local mode and skips auth redirects
Scout uses a pluggable search layer in src/lib/search/.
Search providers:
tavilyduckduckgoauto
Resolution rules:
SEARCH_PROVIDER=tavilyforces TavilySEARCH_PROVIDER=duckduckgoforces DuckDuckGoSEARCH_PROVIDER=autoprefers Tavily ifTAVILY_API_KEYis present, otherwise DuckDuckGo
DuckDuckGo behavior:
- First attempt uses
duck-duck-scrape - If that provider throws or returns no results, the code falls back to DuckDuckGo HTML or Lite endpoints
- The HTML fallback uses browser-like headers and parses result links/snippets from returned markup
Enrichment:
SEARCH_ENRICH_PAGES=truefetches the top results and appends extracted page text- Page fetches are time-limited and filtered to avoid obviously unsafe local/private URLs
Scout query generation:
- Uses the idea title and description
- Adds a query from
brainstorm.coreValuePropositionwhen available
src/lib/ai/client.ts uses a lazy singleton pattern. This matters because OpenAI SDK v6 validates apiKey eagerly, so the client must not be constructed until a route handler actually needs it.
Env vars:
LLM_API_KEYLLM_BASE_URLLLM_MODEL
src/lib/ai/prompts.ts defines the shared context block and every stage prompt.
Key behaviors:
- Prompts return JSON only
- Personal ideas are treated differently in Q&A and pitch deck generation
selectedExpansionsfrom the Mesh stage are marked as primary in downstream prompts- Scout summaries consume formatted search hits and produce structured competitor results plus a narrative summary
src/lib/utils.ts strips fences, then parses JSON. If parsing fails, it tries to extract the outermost object or array from mixed content responses.
src/lib/jsonPath.ts supports dotted keys and numeric array indices only. It is used by the refinement flow to read and write nested values safely.
Each stage page follows the same pattern:
- Read pipeline state from the store.
- Auto-run when the stage is idle.
- Set stage status to running.
- Call the matching API route.
- Update context and stage status on success.
- Show retry controls on error.
The StageRail is the left-side tree visualizer:
- Renders the active pipeline spine
- Shows branch chips for sibling pipelines forked at each stage
- Allows switching between branches
- Includes an "Original timeline" action for returning to the root
- The app uses a dark, utility-first UI
- Accent colors are purple, teal, coral, blue, amber, and green
- Components use small radii and compact spacing
- Page sections are framed by bands rather than nested cards
# LLM
LLM_API_KEY=
LLM_BASE_URL=
LLM_MODEL=
# Web search
SEARCH_PROVIDER=auto
TAVILY_API_KEY=
SEARCH_ENRICH_PAGES=true
# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
# App
NEXT_PUBLIC_APP_URL=http://localhost:3000- Next.js 16 docs should be checked in
node_modules/next/dist/docs/before changing route handlers or route segment config. serverExternalPackagesis available innext.config.ts, but platform-specific SWC binaries should not be added as root dependencies.- OpenAI client construction must stay lazy because SDK v6 validates envs immediately.
- The app must keep working when Supabase is absent.
- Search can fail transiently, so Scout has a provider fallback path.
- Refinement output must preserve JSON shape.
- The store backfills
rootIdfor older pipelines. loadPipeline()andfetchSavedPipelines()intentionally distinguish roots from branches.
npm run build
npm run lint
npx tsc --noEmitCurrent expectation:
npm run buildmust passnpm run lintmay still report existing warnings in unrelated files, but there should be no errorsnpx tsc --noEmitmust pass