A text-to-flowchart editor. Users type in a custom DSL and see a live graph visualization. Supports local (sandbox) and hosted (cloud) charts, with a pro subscription tier (~500 paying customers).
Live site: flowchart.fun (dev site: dev.flowchart.fun) GitHub: tone-row/flowchart-fun Single maintainer: Rob Gordon (~1400+ commits)
pnpm workspaces monorepo with 4 packages:
flowchart-fun/
├── app/ → React frontend (CRA / react-scripts 5.0.1)
├── api/ → Vercel serverless functions (Node.js)
├── shared/ → Shared TypeScript types & constants (compiled with tsc)
├── formulaic/ → React form-building library (compiled with microbundle)
Package manager: pnpm (v10)
Node version: 22 (see .node-version)
Maintainer note: The monorepo structure with all these packages partly exists because of CRA/webpack limitations that needed workarounds. Some of this sprawl is historical.
pnpm install
# IMPORTANT: shared and formulaic must be built before the app can run
pnpm -F shared build
pnpm -F formulaic build
# Start the dev server (use this for local development)
pnpm startUse pnpm start (runs vercel dev) for local development. This starts the React app AND exposes the /api/* serverless functions. Required for features like feedback submission, AI prompts, auth flows, and Stripe — and required for E2E tests to pass.
pnpm dev (runs pnpm -F '*' --parallel dev) only starts the React app + workspace watchers without API routes. Use this only if you're working on purely frontend changes and don't need API access.
Both serve on port 3000 by default.
The app requires env vars in app/.env.local. Pull from Vercel with:
pnpm env:pull # runs: vercel env pull app/.envKey variables (see app/.env.example):
REACT_APP_SUPABASE_URL/REACT_APP_SUPABASE_ANON_KEY— Supabase auth & DBREACT_APP_STRIPE_KEY/STRIPE_KEY— Stripe paymentsREACT_APP_SENTRY_ENVIRONMENT— Sentry error trackingSENDGRID_API_KEY— Email via SendGridNOTION_ACCESS_TOKEN— Notion integration (blog/roadmap)
Strict dependency order:
formulaic → shared → app
Root build script: pnpm -F formulaic build && pnpm -F shared build && pnpm -F app build
If you modify shared/ or formulaic/, rebuild them before the app will pick up changes (or use pnpm dev which runs watch mode for all).
- Create feature branch off
dev(e.g.,robgordon/ff-485-add-folders), merge back intodevvia PR - On
dev, bump version inapp/package.jsonand commit aschore: version {type}(where type isfeature,fix,patch, orhotfix) - Create PR from
dev→mainwith title being just the version number (e.g.,v1.63.1) - Merge triggers
.github/workflows/release.ymlwhich usesjustincy/github-action-npm-releaseto auto-create a GitHub Release + tag fromapp/package.jsonversion - Vercel auto-deploys from
main
Multiple feature PRs may accumulate on dev before a single version bump + release to main, or a single feature may get its own release. All merges to main come exclusively from dev — never direct feature branch → main.
The version in app/package.json is the source of truth. Tags are created automatically.
The core feature is a custom text DSL parsed by the graph-selector npm package (v0.13.0). This is the most fundamental dependency in the project — it's what makes flowchart.fun work.
Key pipeline: Text → parse() → toCytoscapeElements() → Cytoscape renders
Main parsing entry point: app/src/lib/getElements.ts
Maintainer note:
graph-selectorlives in a separate repository (also maintained by Rob). It has extensive unit tests. The workflow for parser changes is: make changes in that repo, publish to npm, bump version here. It was made separate thinking others might use it, but in practice nobody else does. Moving it into this monorepo is an option if it would reduce friction — the tests would come with it.
Charts are stored as text with embedded metadata:
Node A
Node B
Node C
=====
{"themeEditor": {...}, "cytoscapeStyle": "..."}
=====
=====— Current delimiter for JSON metadata (newDelimitersinapp/src/lib/constants.ts)~~~— Legacy delimiter (YAML frontmatter via gray-matter)¼▓╬— Legacy hidden graph options divider
The parsing logic in app/src/lib/prepareChart/prepareChart.ts handles all three formats and merges them, with migration logic for old layouts.
DO NOT REMOVE the legacy delimiter handling. There are ~500 paying customers with existing charts that may use older formats. Removing backward compatibility could break their projects.
Primary: Zustand stores
| Store | File | Purpose |
|---|---|---|
useDoc |
lib/useDoc.ts |
Document text, metadata, and chart details |
useEditorStore |
lib/useEditorStore.ts |
Monaco editor instance & state |
useGraphStore |
lib/useGraphStore.ts |
Cytoscape layout, elements, zoom, pan, selected nodes |
useMobileStore |
lib/useMobileStore.ts |
Mobile UI tab/menu state |
usePaywallModalStore |
lib/usePaywallModalStore.ts |
Paywall dialog state |
usePromptStore |
lib/usePromptStore.ts |
AI prompt/diff state |
useParseErrorStore |
lib/useDoc.ts |
Parser error tracking |
Secondary: React Context (AppContextProvider.tsx) provides auth session, theme, language, and Stripe customer info.
Routes use short paths (defined in app/src/components/Router.tsx):
| Path | Page | Purpose |
|---|---|---|
/ |
Sandbox | Local storage editor (home) |
/u/:id |
EditHosted | Edit a hosted chart |
/p/:public_id |
Public | Public read-only view (permalink) |
/r/:graphText |
ReadOnly | Encoded chart in URL |
/c/:graphText |
ReadOnly | Compressed chart in URL |
/f/:graphText |
Fullscreen | Fullscreen read-only view |
/charts |
MyCharts | User's saved charts (auth required) |
/new |
New | Create hosted chart (auth required) |
/a |
Account | Account settings |
/l |
LogIn | Login page |
/s |
Settings | App settings |
/o |
Feedback | Feedback form |
/d |
DesignSystem | Internal design system page |
/pricing |
Pricing2 | Pricing page |
Maintainer note: The single-letter routes (
/a,/l,/s,/o,/d) were originally chosen to avoid conflicting with user project names, but that reasoning no longer holds up. They're kept for backward compatibility.
Free (Sandbox):
- 1 temporary chart stored in localStorage (
flowcharts.fun.sandbox) - Charts expire after 24 hours (client-side check in
Sandbox.tsxviameta.expires) - Exports watermarked (base64 PNG watermark in constants.ts, 15% width, bottom-left, 80% opacity)
- Lower export resolution (1.5x scale)
- PNG/JPG export only
- Theme editor available
- AI convert: 2 requests per 30 days
Pro ($4/month):
- Unlimited permanent charts stored in Supabase DB
- No watermark, higher resolution exports (3x scale)
- SVG export
- Import from Visio, Lucidchart, CSV
- AI editing: 3 requests per minute
- Custom sharing options
- Local file support
- Priority support
Pro access is determined by Stripe subscription status (active or trialing) via useHasProAccess() in lib/hooks.ts.
The sandbox warning modal (SandboxWarning.tsx) appears after 3 minutes of editing to encourage upgrade.
13 templates defined in shared/src/templates.ts (single source of truth, as const array). Each template is a file at app/src/lib/templates/{name}-template.ts exporting three things:
content: string— starter DSL text for the editortheme: FFTheme— layout + visual config object (26 properties)cytoscapeStyle: string— Cytoscape CSS with variables, color/shape class definitions, and advanced selectors
Template names: code-flow, default, process-flow, flowchart, org-chart, network-diagram-dark, decision-flow, pert-light, knowledge-graph, network-diagram-icons, mindmap, playful-mindmap, mindmap-dark.
- Type definition at
app/src/lib/FFTheme.ts - If you change FFTheme, run
pnpm -F app theme:schema:generateto regenerateFFTheme.schema.json - 26 properties in 4 groups:
- Global: fontFamily, background, lineHeight
- Layout: layoutName (10 options), direction (RIGHT/LEFT/DOWN/UP), spacingFactor
- Node: shape, textMaxWidth, padding, borderWidth, borderColor, nodeBackground, nodeForeground, textMarginY, curveStyle, useFixedHeight, fixedHeight
- Edge: edgeWidth, edgeColor, sourceArrowShape, targetArrowShape, sourceDistanceFromNode, targetDistanceFromNode, arrowScale, edgeTextSize, rotateEdgeLabel
- Edited via ThemeTab UI (
app/src/components/Tabs/ThemeTab.tsx) using the formulaic library - Stored in document metadata as
meta.themeEditor - Conversion:
toTheme(themeEditor)inapp/src/lib/toTheme.ts→ returns{ layout, style, postStyle }
10 layout algorithms (from LayoutName type in FFTheme.ts):
| Layout | Engine | Best For |
|---|---|---|
| dagre | dagre | Hierarchical flowcharts |
| klay | klayjs | Hierarchical (alternative) |
| layered | ELK | Hierarchical with balanced alignment |
| mrtree | ELK | Tree structures |
| stress | ELK | Force-directed, interactive |
| radial | ELK | Radial/spoke layouts |
| cose | fcose | Force-directed with animation |
| breadthfirst | cytoscape | Breadth-first tree |
| concentric | cytoscape | Concentric circles |
| circle | cytoscape | Simple circle |
Direction property maps to layout-specific direction configs (dagre: rankDir TB/LR/RL/BT; klay: klay.direction; ELK: elk.direction). Reference: https://js.cytoscape.org/ for Cytoscape layout/CSS docs.
All CSS in cytoscapeStyle is Cytoscape CSS — different property names, selectors, and values from browser CSS. Reference: https://js.cytoscape.org/#style
Key features:
- Variables:
$varname: value;— preprocessed byapp/src/lib/preprocessStyle.ts, substituted throughout - Font imports:
@import url(...)— extracted and loaded via FontFace API - Dynamic class detection: classes matching
type_namepattern (e.g.,color_blue,shape_diamond) auto-detected and made available in the right-click context menu on nodes/edges (GraphContextMenu.tsx) - Selectors:
:childless(leaf nodes),:parent(group nodes),edge,[in_degree < 1](data attributes) - Three-stage pipeline: themeStyle (generated from FFTheme) → customCss (user's cytoscapeStyle) → postStyle (utility classes). All concatenated unless
customCssOnly: truein metadata.
Example from flowchart-template.ts cytoscapeStyle:
$blue: #e3f2fd;
:childless.color_blue {
background-color: $blue;
color: $color;
}
:childless[in_degree > 0][out_degree > 1] {
shape: diamond;
height: $width;
}From app/src/lib/graphUtilityClasses.ts (generated as postStyle, always available):
- Shape classes (
.shape_*): All Cytoscape shapes (rectangle, roundrectangle, ellipse, triangle, diamond, star, hexagon, etc.) + "smart shapes" (circle, square, roundsquare) that enforce 1:1 aspect ratio - Border classes (
.border_*): none, solid, dashed, dotted, double — works on both nodes and edges - Color classes (
.color_*): Defined per-template in cytoscapeStyle, NOT global. Common set across most templates: red, orange, yellow, green, blue, pink, grey, white, black. Each template defines its own color values. - Text size classes (from
getSize.ts): text-sm (0.75x), text-base (1x), text-lg (1.5x), text-xl (2x)
Applied in the DSL with dot notation: Node Name .color_blue .shape_diamond
- Create
app/src/lib/templates/{name}-template.tsexportingcontent,theme(FFTheme), andcytoscapeStyle - Add the template name string to the array in
shared/src/templates.ts - Rebuild shared:
pnpm -F shared build - Generate screenshots (requires dev server running on port 3000 + ImageMagick installed):
# Terminal 1: pnpm start # Terminal 2: pnpm -F app screenshot-templates {name}
- Verify:
app/public/template-screenshots/thumb_{name}.png(275x275) and{name}.png(full) exist - The template will automatically appear in LoadTemplateDialog, New chart page, and AI template chooser
- Modify the template file in
app/src/lib/templates/ - If visual changes were made, regenerate its screenshot:
pnpm -F app screenshot-templates {name} - If FFTheme type was changed:
pnpm -F app theme:schema:generate
- Delete the template file from
app/src/lib/templates/ - Remove the name from
shared/src/templates.ts - Rebuild shared:
pnpm -F shared build - Delete the screenshot files from
app/public/template-screenshots/({name}.png and thumb_{name}.png)
- Script:
app/scripts/screenshot-templates.mjs - Command:
pnpm -F app screenshot-templates [optional-filter] - Prerequisites: Dev server on port 3000 (
pnpm start), ImageMagick installed (convertcommand) - Process:
- Playwright launches Chromium (1000x1000 viewport)
- Navigates to localhost:3000
- Calls
window.__load_template__(name, true)to load each template - Waits 3s for render, gets fullscreen link via
window.__get_screenshot_link__() - Navigates to fullscreen URL, screenshots the
[data-flowchart-fun-canvas="true"]element - ImageMagick resizes to 275x275 centered thumbnails
- Output:
app/public/template-screenshots/{name}.png+thumb_{name}.png - Filter: Pass a string to only screenshot matching templates (e.g.,
pnpm -F app screenshot-templates mindmapscreenshots all mindmap variants) - Window globals set up by
app/src/lib/loadTemplate.ts(useEnsureLoadTemplatehook) andapp/src/lib/useEnsureGetScreenshotLink.ts
- When user runs AI "prompt" or "convert",
runAi()(app/src/lib/runAi.ts) first calls/api/prompt/choose-templateto auto-select the best template for the user's prompt - The choose-template endpoint uses a short system prompt to pick from the 13 template names (avoids "default"); the specific model is documented in the AI Integration table
- Selected template's
themeandcytoscapeStyleare applied to the document before the AI streams generated text — so the graph looks right as text arrives - Edit mode does NOT use template selection (it edits existing text in-place, preserving the current theme)
- LoadTemplateDialog (
app/src/components/LoadTemplateDialog.tsx): "Examples" button in toolbar; shows grid of 13 thumbnails; user can independently toggle "Load layout and styles" and "Load default content" checkboxes - New hosted chart (
app/src/pages/New.tsx): template selector during creation; template content+theme baked into the chart at insert time - loadTemplate() (
app/src/lib/loadTemplate.ts): dynamically imports template module, merges theme into doc metadata, unfreezes nodePositions, unmounts/remounts graph for clean re-layout
| Component | File |
|---|---|
| Template names (source of truth) | shared/src/templates.ts |
| Template data files | app/src/lib/templates/{name}-template.ts |
| FFTheme type definition | app/src/lib/FFTheme.ts |
| Theme → Cytoscape conversion | app/src/lib/toTheme.ts |
| CSS preprocessing (variables, fonts, dynamic classes) | app/src/lib/preprocessStyle.ts |
| Utility classes (shapes, borders) | app/src/lib/graphUtilityClasses.ts |
| Node sizing + text size classes | app/src/lib/getSize.ts |
| Template loading function | app/src/lib/loadTemplate.ts |
| Screenshot script | app/scripts/screenshot-templates.mjs |
| Screenshot assets | app/public/template-screenshots/ |
| ThemeTab UI | app/src/components/Tabs/ThemeTab.tsx |
| LoadTemplateDialog UI | app/src/components/LoadTemplateDialog.tsx |
| AI template chooser endpoint | api/prompt/choose-template.ts |
| AI runner (template integration) | app/src/lib/runAi.ts |
- React 18.2 with CRA (react-scripts 5.0.1) — no eject, no craco
- TypeScript 5.5.3
- Cytoscape.js 3.31 with layout plugins: dagre, klay, elk, fcose, cose-bilkent
- Monaco Editor (pinned v0.33.0) — code editor with custom syntax highlighting from graph-selector
- Radix UI — headless component library (dialogs, dropdowns, tabs, toasts, etc.)
- Tailwind CSS 3.2.6 — utility classes, dark mode via
dark:prefix - @tone-row/slang — legacy CSS-in-JS design token system (see Slang section below)
- CSS Modules — scoped styles via
*.module.cssfiles - Zustand — state management
- React Query v3 — server state & caching
- Lingui — i18n (8 languages: en, fr, zh, de, hi, ko, pt-br, es)
- Framer Motion — animations
- Stripe (react-stripe-js) — payment UI
- Supabase (supabase-js v2) — auth & database
- Sentry — error tracking
- PostHog — product analytics
- React Router v6 — routing
- Vercel serverless functions
- Supabase — auth verification, database queries
- Stripe — payment processing, subscription management
- OpenAI (via Vercel AI SDK) — AI flowchart generation/editing (see AI section)
- SendGrid — transactional email
- Notion API — blog content, roadmap (still renders but no new posts in a while)
- Upstash — rate limiting via Redis
- Zod — request validation
- TypeScript-only, no runtime deps
- Exports: template names, ImportDataFormType, blog utilities
- Built with
tscto CommonJS
- Generic
createControls<T>()form builder framework - Built with microbundle (CJS/ESM/UMD outputs)
- Uses Vitest for tests
Maintainer note: Formulaic was an experiment in simplifying form markup generation. In practice it may have made things more complex rather than simpler. But the theme tab (
app/src/components/Tabs/ThemeTab.tsx) is deeply built on it — it renders all 27 FFTheme properties across 5 sections using 7 custom controls (select, range, text, color, checkbox, fontpicker, customCss). We're stuck with it for now.
Three overlapping systems exist for historical reasons:
- Tailwind CSS — preferred for new code, utility-first classes
- CSS Modules —
*.module.cssfiles with scoped class names, used by many existing components - @tone-row/slang — legacy auto-generated design tokens and typed
Box/Typecomponents fromsrc/slang/config.ts
Dark mode: controlled by AppContext.theme, adds dark class to <body>. Slang's colors and darkTheme from src/slang/config.ts are used in AppContextProvider.tsx.
@tone-row/slang is a CSS-in-JS library also written by the maintainer. It generates code in src/slang/ — those files say "Do not edit this file directly". Running pnpm -F app theme regenerates them.
Currently 15 files still import from slang, mostly using the Box component: App.tsx, Graph.tsx, Layout.tsx, Settings.tsx, Loading.tsx, GraphWrapper.tsx, ShareDialog.tsx, Account.tsx, Blog.tsx, and several others.
Maintainer note: The Box and Type components from slang are disliked and ideally would be replaced with plain Tailwind. There was an attempt to remove slang entirely but it wasn't fully successful. The
themeandtheme:watchscripts are rarely touched. New code should use Tailwind, not slang components.
Note: app/package.json still has a start script referencing yarn theme:watch — this is legacy/dead and the actual dev command is dev.
The app uses Lingui for internationalization, supporting 8 languages: English, French, Chinese, German, Hindi, Korean, Brazilian Portuguese, and Spanish.
All user-facing text must be wrapped in Lingui macros:
- JSX content:
<Trans>Hello world</Trans>(from@lingui/macro) - JS strings (e.g. props, variables):
t`Hello world`(from@lingui/macro)
If you change, add, or remove any user-facing copy, you must run the translation pipeline before committing.
After any copy changes, run these commands in order:
pnpm -F app extract # Extract new/changed strings from source
pnpm -F app autotranslations # Auto-translate to all supported languages
pnpm -F app compile # Compile translation catalogs for runtimeThen commit the updated locale files (in app/src/locales/) alongside your code changes.
Important: Skipping this workflow means new or changed copy will only appear in English for non-English users. Always run the full extract → autotranslations → compile pipeline when copy changes.
The AI features use OpenAI via Vercel AI SDK. All endpoints are in api/prompt/.
| Feature | Endpoint | Model | Access |
|---|---|---|---|
| Convert to Flowchart | /api/prompt/convert |
gpt-4-turbo | Free: 2/30 days, Pro: 3/min |
| Edit with AI | /api/prompt/edit |
gpt-4-turbo-2024-04-09 | Pro only: 3/min |
| Generate Flowchart | /api/prompt/prompt |
gpt-4-turbo | Free: 2/30 days, Pro: 3/min |
| Choose Template | /api/prompt/choose-template |
gpt-3.5-turbo | All: 3/min |
| Speech-to-Text | /api/prompt/speech-to-text |
whisper-1 | Pro only |
Rate limiting uses Upstash Redis (@upstash/ratelimit). Default model is set in api/prompt/_shared.ts.
Maintainer note: The AI integration uses outdated models (gpt-4-turbo, gpt-3.5-turbo) from early 2024. This is one of the big things that needs updating — the models should be migrated to newer options (gpt-4o, gpt-4o-mini, etc.). The prompts teach the AI the graph-selector syntax so it can generate valid flowchart text. The
openaiv3.2.1 package in app/ may be dead code (api/ uses v4.24.2).
@ai-sdk/openai: app v0.0.37 vs api v0.0.45ai: app v3.2.32 vs api v3.3.6openai: app v3.2.1 vs api v4.24.2 (major version gap)
Run the full verification suite after making changes:
# 1. Type check (fast, catches type errors)
pnpm -F api check # API types (~2s)
pnpm -F app check # App types (~10s)
# 2. Unit tests (8 suites, 32 tests, ~28s)
pnpm -F app test -- --watchAll=false
# 3. E2E tests (~37s, requires `pnpm start` running on port 3000)
pnpm -F app e2eE2E tests require pnpm start (vercel dev), not pnpm dev. The tests hit /api/* routes that only exist via vercel dev.
Command: pnpm -F app test -- --watchAll=false
Framework: Jest via react-scripts + React Testing Library
Status: 8 suites, 32 passed, 5 todo, 0 failures
Duration: ~28 seconds
Test files are in app/src/ alongside source code (e.g., Graph.test.tsx, AppContextProvider.test.tsx, toVisio.test.ts).
Test utilities in app/src/test-utils.tsx wrap render with all providers (AppContext, Router, QueryClient). Helpers: flushMicrotasks(), nextFrame(), sleep().
Prerequisites: shared and formulaic must be built first. No env vars needed.
Jest config is in app/package.json under "jest" key. Key settings:
transformIgnorePatternsexcludesreact-use-localstorage,monaco-editor, andmonaco-editor-corefrom ignore (forces Babel transform for ESM compat)- If a new ESM-only dependency causes "Cannot use import statement outside a module" errors, add it to the negation pattern in transformIgnorePatterns
Command: pnpm -F api check
What it does: tsc --noEmit — type checking only, no tests
Command: pnpm -F app e2e
Framework: Playwright 1.45.2
Config: app/playwright.config.ts
Prerequisites:
vercel devrunning on port 3000 (pnpm start) — NOTpnpm dev. E2E tests hit/api/*routes (e.g., feedback form →/api/mail) which only exist via vercel dev.- Playwright browsers installed:
npx playwright install - Environment file:
app/.env.e2e(contains test account credentials)
Run against a different port:
E2E_START_URL="http://localhost:8080" pnpm -F app e2eDebug mode (Chromium only, single worker, UI):
pnpm -F app e2e:debugTest files in app/e2e/:
| File | Tests | Auth Required | Notes |
|---|---|---|---|
not-logged-in.spec.ts |
Theme editor, exports, paywalls, settings, language | No | Most reliable suite |
logged-in.spec.ts |
Login, chart creation, account access, logout | Yes (basic) | Chromium only |
pro.spec.ts |
Create/rename/publish charts, SVG export, import CSV | Yes (pro) | Chromium only, serial |
sign-up.spec.ts |
Full Stripe signup flow | No | SKIPPED (test.skip()) |
E2E Config Details:
- 12 parallel workers, 120s timeout, 1200x1080 viewport
- Runs Chromium + Firefox (normal mode) or Chromium only (debug)
- Max 3 failures before stopping
- Tests add
?isE2E=trueto URLs for special E2E handling window.__set_text()is used to programmatically set editor content
Current E2E Status (with pnpm start / vercel dev):
not-logged-in.spec.ts: 5/6 pass — 1 flaky: Mermaid Live popup URL check has a timing issue (test reads URL before redirect completes)logged-in.spec.ts: 7/7 pass (1 Firefox skipped by design)pro.spec.ts: all pass (Chromium only, serial execution)sign-up.spec.ts: entirely skipped (test.skip())
Known E2E Issues:
- Pro tests depend on the
.env.e2epro account (rob.gordon+111@tone-row.com) having an active Stripe subscription — if it lapses, all pro tests fail - The Mermaid Live test in
not-logged-in.spec.ts:45is flaky due to popup redirect timing sign-up.spec.tsis entirely skipped- Must use
pnpm start(vercel dev), notpnpm dev— tests that hit API routes will fail without the serverless functions
Command: pnpm -F formulaic test -- --run
Status: All test cases are commented out — effectively no tests exist.
No test files exist in the shared package.
Maintainer note: E2E test coverage is between "bad and moderate" — roughly 50% of happy paths, with no edge case or error coverage. They've been problematic over the years. Unit tests are well-maintained and passing. If we ever migrate off CRA, E2E tests will become much more important and we'll need to write more.
- GitHub Actions:
test.yml— runs on all pushes (build shared, run app tests + api type check)e2e.yml— runs on PRs (waits for Vercel preview, runs Playwright)release.yml— runs on push to main (auto GitHub release from app/package.json version)
- Vercel: auto-deploys on push, preview deployments on PRs
- Husky pre-commit: lint-staged (eslint + prettier) + TypeScript check + unstage vercel.json
The app uses react-scripts which is effectively unmaintained. This means:
- No easy webpack customization (no craco/rewired)
- Stuck with CRA's webpack 5 config
- Buffer polyfill is manually imported in
index.tsx - Some transform ignores needed in jest config for monaco-editor
- Webpack dev server deprecation warnings (onAfterSetupMiddleware/onBeforeSetupMiddleware) — not actionable without ejecting
Maintainer note: Would love to migrate off CRA (to Vite or similar), but it's been too risky and too large to attempt without dedicated bandwidth. Some branches may have attempted ejecting but never merged. The CRA limitations are partly why the monorepo has so much structural complexity.
The committed version contains a "rewrites": [{ "source": "/(.*)", "destination": "/" }] rule needed for SPA client-side routing in production. But this rewrite breaks vercel dev locally, so the local working copy has it removed. The pre-commit hook (git reset HEAD vercel.json) prevents accidentally committing the local version without the rewrites. Do not commit changes to vercel.json — the hook handles this automatically.
monaco-editor is pinned to exactly 0.33.0 (not ^0.33.0). Intentional — newer versions may break the integration.
The DSL parser is a separate npm package. Changes to parsing require publishing a new version of graph-selector and bumping here. See architecture section for more detail.
The flow from user typing → parsing → updating the Cytoscape instance has known gotchas. There have been weird bugs related to timing, re-rendering, and state synchronization between the editor, parser, and graph view. Be careful when modifying anything in this pipeline.
Document parsing handles three formats for backward compatibility. ~500 paying customers may have charts using any format. Do not remove legacy format handling.
Many routes use single-letter paths (/a, /l, /s, /o, /d). See the routing table for what they map to.
The deleteDevUsers cron job (daily at 4 AM) prevents people from signing up on dev.flowchart.fun with Stripe test credentials and getting free access. It's a safety hatch, not just for test cleanup.
flowcharts.fun.sandbox— sandbox chart data (withmeta.expiresfor 24-hour TTL)flowcharts.fun.user.settings— user preferences (theme, language)
Origin unknown. Possibly from formulaic's dev build. Should probably be gitignored.
There is significant technical debt that is acknowledged but not yet prioritized:
- OpenAI version mismatch between app (v3) and api (v4) — app v3 may be dead code
- React Query v3 — could upgrade to TanStack Query v5
- Supabase SDK — likely outdated, still functional
- Slang components — 15 files still depend on Box/Type, ideally replaced with Tailwind
- Legacy
yarnreference in app'sstartscript - AI models — using gpt-4-turbo and gpt-3.5-turbo, need migration to newer models
- Browserslist — data is 13+ months old (
npx update-browserslist-db@latest)
Maintainer note: There are a lot of open questions around dead code. Eventually we might tackle these, but not ready yet. Worth tracking as potential future work but don't attempt cleanup without explicit direction.
Key serverless functions in api/:
| Endpoint | Method | Purpose |
|---|---|---|
/api/prompt/* |
POST | AI flowchart generation/editing |
/api/customer-info |
POST | Get Stripe customer data |
/api/create-checkout-session |
POST | Start Stripe checkout |
/api/cancel-subscription |
POST | Cancel subscription |
/api/resume-subscription |
POST | Resume subscription |
/api/customer-portal-session |
POST | Stripe customer portal |
/api/public |
POST | Get public chart data |
/api/generate-public-id |
POST | Generate shareable chart ID |
/api/mail |
POST | Send email via SendGrid |
/api/update-email |
POST | Update user email |
/api/version |
GET | Get app version |
/api/changelog |
GET | Get changelog (from GitHub releases) |
/api/roadmap |
GET | Get roadmap (from Notion) |
/api/cron/deleteDevUsers |
CRON | Daily dev site user cleanup (4 AM) |
Charts can be exported as: PNG, JPG, SVG (pro only), PDF, JSON Canvas, Excalidraw, Visio XML, Mermaid, CSV.