Skip to content

vldsvrk/henro

Repository files navigation

Henro

Brainstorming canvas that expands your ideas with AI.

Type a seed. Branch it. Steer each branch with a lens — scrappy indie, VC pitch, pessimist, whatever. Merge two ideas into one. Compose the whole board into a summary.

henro.space · No accounts · Your OpenRouter key · Everything stays in your browser.

Why

AI chats end up as linear walls of text. You scroll forever, half of it is fluff, and the idea you wanted is gone.

Ideas aren't linear. Chats are. That's the whole problem.

Henro puts brainstorming on a canvas where branches fan out, ideas merge, and context stays visible.

How it works

  • Seed — type an idea
  • Branch — AI expands a node into N sub-ideas (configurable, 1–10)
  • Steer — every expansion takes an optional lens so branches go where you want
  • Merge — drag two nodes together; AI writes the combined idea
  • Compose — synthesize everything active into a summary
  • Context-aware prompts — AI sees what's directly connected to each node, not just a flat bag of text

Stack

  • Vite + React 19 + TypeScript
  • Zustand for state (persist middleware, per-project localStorage keys)
  • Tailwind 4
  • Framer Motion
  • OpenRouter for inference (BYOK)

No backend. No accounts. No database. Everything is localStorage.

Run locally

pnpm install
pnpm dev

Open the app, paste your OpenRouter key into Settings, and start branching. Get a key at openrouter.ai/keys.

Env flags

Copy .env.example to .env.local if you want any of these.

Flag Effect
VITE_OPENROUTER_API_KEY=sk-or-… Sets the BYOK key for local dev — skips the welcome screen and is used as a fallback when nothing is saved in localStorage. Never set this in a hosted build: Vite inlines VITE_* vars into the public JS bundle, exposing the key to anyone who opens DevTools.
VITE_HENRO_DEBUG=true Logs every AI prompt + response to the browser console (collapsed groups)

In production, the app is BYOK-only — every visitor pastes their own key, which lives in their localStorage and never leaves the browser.

Deploy

Static build, any host. Includes config for Cloudflare Pages:

pnpm build
  • dist/ is the deploy output
  • public/_headers → security headers (HSTS, nosniff, frame-ancestors, referrer-policy)
  • public/robots.txt + public/sitemap.xml → search-engine basics

Architecture

src/
├── App.tsx              – tri-state gate: WelcomeScreen → SeedInput → Canvas
├── main.tsx             – ErrorBoundary wrapper
├── store.ts             – single Zustand store, persist middleware, project slice
├── components/
│   ├── Canvas.tsx       – pan/zoom, marquee select
│   ├── BubbleNode.tsx   – draggable node, merge animation, expand
│   ├── Connections.tsx  – SVG edges
│   ├── SeedInput.tsx    – initial seed prompt (pre-canvas)
│   ├── NodeInput.tsx    – inline rename / per-node prompt
│   ├── SidePanel.tsx    – node detail (on selection)
│   ├── ComposeButton.tsx– summary modal
│   ├── Settings.tsx     – API key + model + branch count + system prompt (presets + editor)
│   ├── ProjectSwitcher.tsx
│   ├── HenroMenu.tsx    – top-left brand menu
│   ├── HelpButton.tsx   – bottom-left shortcuts overlay
│   ├── SelectionCount.tsx – selection count chip
│   ├── WelcomeScreen.tsx
│   ├── Toaster.tsx / ErrorBoundary.tsx
│   └── icons.tsx
├── lib/
│   ├── ai.ts            – OpenRouter chat + retry + debug logging
│   ├── prompts.ts       – system-prompt presets
│   ├── errors.ts        – AiError class + classifier
│   ├── toast.ts         – tiny toast store
│   ├── persistence.ts   – custom Zustand StateStorage adapter (split per-project keys)
│   ├── config.ts        – BYOK config read/write + useHasApiKey
│   ├── tokens.ts        – design tokens (z-index, etc.)
│   ├── motion.ts        – framer-motion duration / easing presets
│   ├── uid.ts           – RFC4122 v4 UUID helper (works in non-secure contexts)
│   ├── layout.ts        – child-node position math
│   ├── physics.ts       – overlap resolution
│   └── usePhysics.ts    – settle-after-drag hook
└── index.css            – Tailwind theme + prose-compose + scrollbar-soft

Persistence schema:

  • henro:projects:index — project metadata list + current ID
  • henro:project:<id> — one key per project (nodes, connections, viewport, seed, composeResult)
  • openrouter-config — BYOK config (apiKey, model, branchCount, systemPrompt)

Contributing

PRs welcome. See CONTRIBUTING.md.

Small fixes and UI polish: open a PR.

Bigger changes (new features, architectural shifts): open an issue first so we can align before you spend time.

License

MIT — see LICENSE.

Author

Built by @vldsvrk. Follow along for updates.

About

Infinite canvas for brainstorming with AI

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors