@thierrynakoa/fire-flow 12.2.1 → 13.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/CREDITS.md +25 -0
  2. package/DOMINION-FLOW-OVERVIEW.md +182 -38
  3. package/README.md +399 -455
  4. package/TROUBLESHOOTING.md +264 -264
  5. package/agents/fire-debugger.md +54 -0
  6. package/agents/fire-executor.md +1610 -1033
  7. package/agents/fire-fact-checker.md +1 -1
  8. package/agents/fire-planner.md +85 -17
  9. package/agents/fire-project-researcher.md +1 -1
  10. package/agents/fire-researcher.md +4 -22
  11. package/agents/{fire-phoenix-analyst.md → fire-resurrection-analyst.md} +394 -394
  12. package/agents/fire-reviewer.md +552 -499
  13. package/agents/fire-verifier.md +114 -19
  14. package/bin/cli.js +18 -101
  15. package/commands/fire-0-orient.md +2 -2
  16. package/commands/fire-1a-new.md +50 -15
  17. package/commands/fire-1c-setup.md +33 -5
  18. package/commands/fire-1d-discuss.md +87 -1
  19. package/commands/fire-2-plan.md +556 -527
  20. package/commands/fire-3-execute.md +2046 -1356
  21. package/commands/fire-4-verify.md +975 -906
  22. package/commands/fire-5-handoff.md +46 -5
  23. package/commands/fire-6-resume.md +2 -31
  24. package/commands/fire-add-new-skill.md +138 -19
  25. package/commands/fire-autonomous.md +14 -2
  26. package/commands/fire-complete-milestone.md +1 -1
  27. package/commands/fire-cost.md +179 -183
  28. package/commands/fire-debug.md +1 -6
  29. package/commands/fire-loop-resume.md +2 -2
  30. package/commands/fire-loop-stop.md +1 -1
  31. package/commands/fire-loop.md +2 -15
  32. package/commands/fire-map-codebase.md +1 -1
  33. package/commands/fire-migrate-database.md +548 -0
  34. package/commands/fire-new-milestone.md +1 -1
  35. package/commands/fire-reflect.md +1 -2
  36. package/commands/fire-research.md +142 -21
  37. package/commands/{fire-phoenix.md → fire-resurrect.md} +859 -603
  38. package/commands/fire-scaffold.md +297 -0
  39. package/commands/fire-search.md +1 -2
  40. package/commands/fire-security-scan.md +483 -484
  41. package/commands/fire-setup.md +359 -0
  42. package/commands/fire-skill.md +770 -0
  43. package/commands/fire-skills-diff.md +506 -506
  44. package/commands/fire-skills-history.md +388 -388
  45. package/commands/fire-skills-rollback.md +7 -7
  46. package/commands/fire-skills-sync.md +470 -470
  47. package/commands/fire-test.md +5 -5
  48. package/commands/fire-todos.md +1 -1
  49. package/commands/fire-update.md +5 -5
  50. package/commands/fire-validate-skills.md +282 -0
  51. package/commands/fire-vuln-scan.md +492 -493
  52. package/hooks/run-hook.sh +8 -8
  53. package/hooks/run-session-end.sh +7 -7
  54. package/hooks/session-end.sh +90 -90
  55. package/hooks/session-start.sh +1 -1
  56. package/package.json +4 -24
  57. package/plugin.json +7 -7
  58. package/references/autonomy-levels.md +235 -0
  59. package/references/behavioral-directives.md +95 -3
  60. package/references/blocker-tracking.md +1 -1
  61. package/references/circuit-breaker.md +93 -2
  62. package/references/context-engineering.md +227 -9
  63. package/references/honesty-protocols.md +70 -1
  64. package/references/issue-to-pr-pipeline.md +149 -150
  65. package/references/metrics-and-trends.md +1 -2
  66. package/references/research-improvements.md +4 -108
  67. package/references/sdlc-mapping.md +73 -0
  68. package/references/state-machine.md +151 -0
  69. package/skills-library/AVAILABLE_TOOLS_REFERENCE.md +333 -0
  70. package/skills-library/SKILLS-INDEX.md +57 -558
  71. package/skills-library/SKILLS_LIBRARY_INDEX.md +532 -0
  72. package/skills-library/_general/api-patterns/api-field-name-mismatch.md +107 -0
  73. package/skills-library/_general/api-patterns/streaming-command-timeout.md +122 -0
  74. package/skills-library/_general/api-patterns/streaming-proxy-cors-bypass.md +102 -0
  75. package/skills-library/_general/automation/settings-gui-generator.md +172 -0
  76. package/skills-library/_general/database-solutions/data-type-mapping-reference.md +181 -0
  77. package/skills-library/_general/database-solutions/mysql-limit-offset-string-coercion.md +102 -0
  78. package/skills-library/_general/database-solutions/mysql-to-pg-migration.md +195 -0
  79. package/skills-library/_general/database-solutions/orm-schema-portability.md +193 -0
  80. package/skills-library/_general/database-solutions/persistent-analysis-storage.md +207 -0
  81. package/skills-library/_general/database-solutions/pg-to-mysql-schema-migration-methodology.md +190 -0
  82. package/skills-library/_general/database-solutions/sql-dialect-compatibility-matrix.md +306 -0
  83. package/skills-library/_general/database-solutions/sqlite-to-pg-migration.md +219 -0
  84. package/skills-library/_general/frontend/canvas-bubble-animation-grouping.md +270 -0
  85. package/skills-library/_general/frontend/color-token-migration.md +112 -0
  86. package/skills-library/_general/frontend/framer-motion-layoutid-grouping.md +150 -0
  87. package/skills-library/_general/frontend/pyqt6-settings-dialog.md +191 -0
  88. package/skills-library/_general/frontend/react-flow-animated-layout-switching.md +101 -0
  89. package/skills-library/_general/frontend/react-hooks-order-debugging.md +141 -0
  90. package/skills-library/_general/frontend/redux-localstorage-auth-desync.md +126 -0
  91. package/skills-library/_general/frontend/safari-csp-theme-color-debugging.md +124 -0
  92. package/skills-library/_general/frontend/safari-sw-cache-poisoning.md +138 -0
  93. package/skills-library/_general/frontend/svg-sparkline-no-charting-library.md +131 -0
  94. package/skills-library/_general/growth-marketing/oss-daily-growth-intelligence.md +224 -0
  95. package/skills-library/_general/integrations/claude-code-local-mcp-integration.md +250 -0
  96. package/skills-library/_general/integrations/mcp-composite-tool-orchestration.md +200 -0
  97. package/skills-library/_general/methodology/AGENT_SDK_STANDALONE_TOOLING.md +181 -0
  98. package/skills-library/_general/methodology/AGENT_TEAMS_GUIDE.md +169 -0
  99. package/skills-library/_general/methodology/ALAS_STATEFUL_EXECUTION.md +207 -0
  100. package/skills-library/_general/methodology/AUTO_REVIEWER_SUBAGENT.md +211 -0
  101. package/skills-library/_general/methodology/CONSISTENCY_CHECK_AMBIGUITY_GATE.md +96 -0
  102. package/skills-library/_general/methodology/DEAD_ENDS_SHELF.md +4 -4
  103. package/skills-library/_general/methodology/DISTILL_NOT_DUMP.md +108 -0
  104. package/skills-library/_general/methodology/EXECUTION_PROGRESS_MONITOR.md +157 -0
  105. package/skills-library/_general/methodology/HIERARCHICAL_REVIEW_MARS.md +122 -0
  106. package/skills-library/_general/methodology/MCP_INTER_AGENT_BRIDGE.md +207 -0
  107. package/skills-library/_general/methodology/MERMAID_WIZARD_DIAGRAMS.md +77 -0
  108. package/skills-library/_general/methodology/MISSING_DIMENSION_DETECTOR.md +89 -0
  109. package/skills-library/_general/methodology/MULTI_AGENT_COORDINATION.md +397 -0
  110. package/skills-library/_general/methodology/OBSERVATION_MASKING.md +100 -0
  111. package/skills-library/_general/methodology/PHOENIX_REBUILD_METHODOLOGY.md +82 -11
  112. package/skills-library/_general/methodology/REVIEW_BACKTRACK_PANEL.md +140 -0
  113. package/skills-library/_general/methodology/REVIEW_FIX_LOOP.md +117 -0
  114. package/skills-library/_general/methodology/VOTING_VERDICT_ARBITRATION.md +155 -0
  115. package/skills-library/_general/methodology/ZERO_FRICTION_CLI_SETUP.md +2 -2
  116. package/skills-library/_general/methodology/dead-code-activation.md +123 -0
  117. package/skills-library/_general/methodology/debug-swarm-researcher-escape-hatch.md +240 -240
  118. package/skills-library/_general/methodology/shell-autonomous-loop-fixplan.md +1 -1
  119. package/skills-library/_general/patterns-standards/GOF_DESIGN_PATTERNS_FOR_AI_AGENTS.md +5 -5
  120. package/skills-library/_general/patterns-standards/cascading-failure-diagnosis.md +119 -0
  121. package/skills-library/_general/patterns-standards/domain-specific-layout-algorithms.md +209 -0
  122. package/skills-library/_general/patterns-standards/python-desktop-app-architecture.md +399 -0
  123. package/skills-library/_general/patterns-standards/realtime-monitoring-dashboard.md +457 -0
  124. package/skills-library/_general/patterns-standards/togglable-processing-pipeline.md +169 -0
  125. package/skills-library/_general/performance/liveclock-extraction.md +112 -0
  126. package/skills-library/_general/performance/ref-based-canvas-animation.md +117 -0
  127. package/skills-library/_general/performance/use-visible-interval.md +131 -0
  128. package/skills-library/_general/testing/playwright-firefox-withcredentials-auth-issue.md +104 -0
  129. package/skills-library/_quarantine/README.md +30 -0
  130. package/skills-library/api-patterns/BROADCAST_SCHEDULER_SHARED_EXECUTE_FUNCTION.md +150 -0
  131. package/skills-library/api-patterns/ERROR_RESPONSE_STANDARDS.md +145 -0
  132. package/skills-library/api-patterns/EXPRESS_ROUTE_ORDERING_MIDDLEWARE_INTERCEPTION.md +326 -0
  133. package/skills-library/api-patterns/PAGINATION_PATTERNS.md +137 -0
  134. package/skills-library/api-patterns/PODCAST_PROGRESS_TRACKING_THREE_ROOT_CAUSES.md +277 -0
  135. package/skills-library/api-patterns/RATE_LIMITING_TOGGLE.md +155 -0
  136. package/skills-library/api-patterns/graphql-content-queries.md +708 -0
  137. package/skills-library/appointment-scheduler-design.md +423 -0
  138. package/skills-library/automation/AUTO_POPULATE_COMPLETE_GUIDE.md +631 -0
  139. package/skills-library/automation/CC_WORKFLOW_STUDIO.md +83 -0
  140. package/skills-library/automation/CLAUDE_CODE_SWARM_MODE.md +95 -0
  141. package/skills-library/automation/DAEMON_TRIGGER_FILE_IPC.md +195 -0
  142. package/skills-library/automation/scheduled-content-publishing.md +608 -0
  143. package/skills-library/awesome-workflows/Blogging-Platform-Instructions/view_commands.md +25 -0
  144. package/skills-library/awesome-workflows/CREDENTIAL-SECURITY-WORKFLOW.md +109 -0
  145. package/skills-library/awesome-workflows/DEBUGGING-WORKFLOW.md +124 -0
  146. package/skills-library/awesome-workflows/Design-Review-Workflow/README.md +31 -0
  147. package/skills-library/awesome-workflows/Design-Review-Workflow/design-principles-example.md +129 -0
  148. package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-agent.md +107 -0
  149. package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-claude-md-snippet.md +24 -0
  150. package/skills-library/awesome-workflows/Design-Review-Workflow/design-review-slash-command.md +38 -0
  151. package/skills-library/awesome-workflows/PARALLEL-RESEARCH-WORKFLOW.md +89 -0
  152. package/skills-library/awesome-workflows/PHASE-EXECUTION-WORKFLOW.md +97 -0
  153. package/skills-library/awesome-workflows/SESSION-HANDOFF-WORKFLOW.md +116 -0
  154. package/skills-library/cms-patterns/content-branch-preview.md +515 -0
  155. package/skills-library/cms-patterns/inline-visual-editing.md +666 -0
  156. package/skills-library/cms-patterns/mdx-component-content.md +649 -0
  157. package/skills-library/cms-patterns/media-manager-abstraction.md +827 -0
  158. package/skills-library/cms-patterns/schema-driven-form-generator.md +838 -0
  159. package/skills-library/complexity-metrics/complexity-divider.md +707 -0
  160. package/skills-library/complexity-metrics/work-with-complexity.md +193 -0
  161. package/skills-library/creative-multimedia/animation-stack-guide.md +577 -0
  162. package/skills-library/creative-multimedia/audio-enhancement-pipeline.md +625 -0
  163. package/skills-library/creative-multimedia/content-repurposing-pipeline.md +1146 -0
  164. package/skills-library/creative-multimedia/data-visualization-generator.md +862 -0
  165. package/skills-library/creative-multimedia/doc-to-podcast-pipeline.md +2184 -0
  166. package/skills-library/creative-multimedia/ffmpeg-command-generator.md +405 -0
  167. package/skills-library/creative-multimedia/image-optimization-pipeline.md +605 -0
  168. package/skills-library/creative-multimedia/multi-format-content-generator.md +1759 -0
  169. package/skills-library/creative-multimedia/og-image-generator.md +635 -0
  170. package/skills-library/creative-multimedia/podcast-audio-composition.md +1355 -0
  171. package/skills-library/creative-multimedia/podcast-quality-evaluation.md +1452 -0
  172. package/skills-library/creative-multimedia/podcast-script-generation.md +1841 -0
  173. package/skills-library/creative-multimedia/svg-generation.md +750 -0
  174. package/skills-library/creative-multimedia/text-to-speech-provider-selector.md +1414 -0
  175. package/skills-library/creative-multimedia/transcription-pipeline-selector.md +677 -0
  176. package/skills-library/creative-multimedia/video-streaming-setup.md +559 -0
  177. package/skills-library/database-solutions/AI_RESPONSE_DATABASE_CACHING.md +520 -0
  178. package/skills-library/database-solutions/CONDITIONAL_SQL_MIGRATION_PATTERN.md +119 -0
  179. package/skills-library/database-solutions/DATABASE_COLUMN_NAME_MISMATCH.md +393 -0
  180. package/skills-library/database-solutions/DATABASE_SCHEMA.md +394 -0
  181. package/skills-library/database-solutions/DATABASE_SCHEMA_VERIFICATION_GUIDE.md +348 -0
  182. package/skills-library/database-solutions/DATABASE_STRATEGY.md +71 -0
  183. package/skills-library/database-solutions/ES_MODULE_SEED_SCRIPT_PATTERN.md +52 -0
  184. package/skills-library/database-solutions/MIGRATION_GUIDE.md +3 -0
  185. package/skills-library/database-solutions/PLPGSQL_VARIABLE_CONFLICT_FIX.md +208 -0
  186. package/skills-library/database-solutions/POSTGRESQL_JSONB_DOUBLE_STRINGIFY_FIX.md +245 -0
  187. package/skills-library/database-solutions/POSTGRESQL_LICENSE_TABLE_DESIGN.md +393 -0
  188. package/skills-library/database-solutions/POSTGRESQL_UUID_DOCUMENT_RAG_DUAL_SCOPE.md +732 -0
  189. package/skills-library/database-solutions/POSTGRES_SQL_TEMPLATE_BINDING_ERROR.md +240 -0
  190. package/skills-library/database-solutions/PRISMA_DB_PUSH_DATA_LOSS_PREVENTION.md +141 -0
  191. package/skills-library/database-solutions/PRODUCTION_QUERY_OPTIMIZATION_RESTART_FIX.md +389 -0
  192. package/skills-library/database-solutions/RLS_SECURITY_GUIDE.md +107 -0
  193. package/skills-library/database-solutions/SCHEMA_ENHANCEMENTS_GUIDE.md +373 -0
  194. package/skills-library/database-solutions/SCHEMA_MIGRATION_GUIDE.md +368 -0
  195. package/skills-library/database-solutions/SCHEMA_VERIFICATION_QUICK_REFERENCE.md +104 -0
  196. package/skills-library/database-solutions/ai-erd-generator.md +1213 -0
  197. package/skills-library/database-solutions/content-publishing-states.md +631 -0
  198. package/skills-library/database-solutions/database-schema-designer.md +522 -0
  199. package/skills-library/database-solutions/er-diagram-components.md +569 -0
  200. package/skills-library/database-solutions/er-to-ddl-mapping.md +1405 -0
  201. package/skills-library/database-solutions/erd-creator-textbook-research.md +433 -0
  202. package/skills-library/database-solutions/erd-react-flow-architecture.md +1965 -0
  203. package/skills-library/database-solutions/mariadb-aggregate-function-replacement.md +145 -0
  204. package/skills-library/database-solutions/normalization-validator.md +778 -0
  205. package/skills-library/database-solutions/postgres-full-text-search-content.md +494 -0
  206. package/skills-library/database-solutions/postgresql-to-mysql-runtime-translation.md +286 -0
  207. package/skills-library/database-solutions/regex-alternation-ordering-sql-types.md +92 -0
  208. package/skills-library/database-solutions/reserved-word-context-aware-quoting.md +142 -0
  209. package/skills-library/database-solutions/sql-ddl-generator.md +756 -0
  210. package/skills-library/database-solutions/supabase-connection-pooler-fix.md +102 -0
  211. package/skills-library/deployment-security/CPANEL_NODE_DEPLOYMENT.md +166 -0
  212. package/skills-library/deployment-security/DEPLOYMENT.md +275 -0
  213. package/skills-library/deployment-security/DEPLOYMENT_CHECKLIST.md +363 -0
  214. package/skills-library/deployment-security/DEPLOYMENT_PLAN.md +669 -0
  215. package/skills-library/deployment-security/KNEX_DATABASE_ABSTRACTION.md +444 -0
  216. package/skills-library/deployment-security/LICENSE_KEY_SYSTEM.md +206 -0
  217. package/skills-library/deployment-security/NODE18_DEPENDENCY_COMPATIBILITY.md +284 -0
  218. package/skills-library/deployment-security/PHP_INSTALLER_WIZARD_GUIDE.md +315 -0
  219. package/skills-library/deployment-security/PM2_ENVIRONMENT_VARIABLE_CACHING.md +256 -0
  220. package/skills-library/deployment-security/PM2_MEMORY_EXHAUSTION_FIX.md +370 -0
  221. package/skills-library/deployment-security/PRODUCTION_DEPLOYMENT_GUIDE.md +592 -0
  222. package/skills-library/deployment-security/PRODUCTION_HARDENING_DOCUMENTATION.md +307 -0
  223. package/skills-library/deployment-security/PRODUCTION_RECOVERY_CHERRY_PICK_PATTERN.md +202 -0
  224. package/skills-library/deployment-security/PYINSTALLER_CUDA_WHISPER_BUNDLING.md +236 -0
  225. package/skills-library/deployment-security/SECURITY.md +41 -0
  226. package/skills-library/deployment-security/SMTP_SSL_HOSTNAME_MISMATCH_SHARED_HOSTING.md +220 -0
  227. package/skills-library/deployment-security/SPA_SEO_OPTIMIZATION_CPANEL.md +200 -0
  228. package/skills-library/deployment-security/SUPABASE_EDGE_FUNCTIONS.md +338 -0
  229. package/skills-library/deployment-security/VERCEL_GITHUB_DEPLOYMENT_GUIDE.md +858 -0
  230. package/skills-library/deployment-security/VPS_DEPLOYMENT_READINESS.md +356 -0
  231. package/skills-library/deployment-security/deployment-changes-not-applying.md +241 -0
  232. package/skills-library/deployment-security/env-file-management-production-local.md +203 -0
  233. package/skills-library/deployment-security/express-secure-file-downloads.md +413 -0
  234. package/skills-library/deployment-security/react-production-deployment-desktop-guide.md +2011 -0
  235. package/skills-library/deployment-security/self-hosted-supabase-coolify-guide.md +1684 -0
  236. package/skills-library/deployment-security/unique-features-ai-strategy-plaid-security.md +1613 -0
  237. package/skills-library/deployment-security/vps-deployment.md +135 -0
  238. package/skills-library/document-processing/WORD_EXPORT_MARKDOWN_FORMATTING.md +482 -0
  239. package/skills-library/document-processing/document-ai-landingai-integration.md +677 -0
  240. package/skills-library/document-processing/express-secure-file-downloads-mern.md +413 -0
  241. package/skills-library/document-processing/express-secure-file-downloads.md +413 -0
  242. package/skills-library/document-processing/md-to-word-converter.md +318 -0
  243. package/skills-library/document-processing/pdf-forms-integration/README.md +101 -0
  244. package/skills-library/document-processing/pdf-forms-integration/SKILL.md +662 -0
  245. package/skills-library/ecommerce/ADMIN_PRODUCTS_GUIDE.md +428 -0
  246. package/skills-library/ecommerce/ECOMMERCE_API_REFERENCE.md +776 -0
  247. package/skills-library/ecommerce/ECOMMERCE_COMPLETION_SUMMARY.md +673 -0
  248. package/skills-library/ecommerce/ECOMMERCE_IMPLEMENTATION_GUIDE.md +729 -0
  249. package/skills-library/ecommerce/ECOMMERCE_QUICK_REFERENCE.md +521 -0
  250. package/skills-library/ecommerce/ECOMMERCE_TESTING_CHECKLIST.md +565 -0
  251. package/skills-library/ecommerce/ECOMMERCE_WORKFLOW_GUIDE.md +1059 -0
  252. package/skills-library/ecommerce/PRODUCT_CREATION_EXPANDED.md +522 -0
  253. package/skills-library/ecommerce/agentic-commerce-protocol.md +203 -0
  254. package/skills-library/ecommerce/cart-abandonment-recovery.md +236 -0
  255. package/skills-library/ecommerce/cart-architecture-patterns.md +300 -0
  256. package/skills-library/ecommerce/cart-item-count-indicator.md +264 -0
  257. package/skills-library/ecommerce/checkout-ux-conversion.md +227 -0
  258. package/skills-library/ecommerce/composable-commerce-selection.md +166 -0
  259. package/skills-library/ecommerce/ecommerce-analytics-patterns.md +167 -0
  260. package/skills-library/ecommerce/fraud-detection-patterns.md +179 -0
  261. package/skills-library/ecommerce/inventory-stock-management.md +270 -0
  262. package/skills-library/ecommerce/order-saga-state-machine.md +336 -0
  263. package/skills-library/ecommerce/payment-provider-abstraction.md +245 -0
  264. package/skills-library/ecommerce/pci-compliance-checklist.md +192 -0
  265. package/skills-library/ecommerce/refund-chargeback-handling.md +177 -0
  266. package/skills-library/ecommerce/shipping-carrier-integration.md +218 -0
  267. package/skills-library/ecommerce/webhook-idempotency-patterns.md +253 -0
  268. package/skills-library/excalidraw-diagrams/.github/workflows/ci.yml +558 -0
  269. package/skills-library/excalidraw-diagrams/.github/workflows/prompt-gallery.yml +448 -0
  270. package/skills-library/excalidraw-diagrams/.github/workflows/release.yml +42 -0
  271. package/skills-library/excalidraw-diagrams/.github/workflows/test-reusable-ci.yml +25 -0
  272. package/skills-library/excalidraw-diagrams/CLAUDE.md +57 -0
  273. package/skills-library/excalidraw-diagrams/LICENSE +21 -0
  274. package/skills-library/excalidraw-diagrams/README.md +178 -0
  275. package/skills-library/excalidraw-diagrams/SKILL.md +715 -0
  276. package/skills-library/form-solutions/BUTTON_TYPE_FORM_SUBMISSION.md +336 -0
  277. package/skills-library/form-solutions/FILLABLE_PDF_IMPLEMENTATION.md +226 -0
  278. package/skills-library/form-solutions/SURVEYJS_QUESTIONNAIRE_SYSTEM.md +367 -0
  279. package/skills-library/form-solutions/tiptap-minimal-setup.md +690 -0
  280. package/skills-library/frontend/scholarly-classification-bubble-map.md +149 -0
  281. package/skills-library/infrastructure/ci-cd-pipeline-builder.md +517 -0
  282. package/skills-library/infrastructure/observability-designer.md +264 -0
  283. package/skills-library/infrastructure/performance-profiler.md +621 -0
  284. package/skills-library/installer-wizard-patterns.md +249 -0
  285. package/skills-library/integrations/CLAUDE_CODE_TOKEN_ANALYTICS.md +160 -0
  286. package/skills-library/integrations/CONFIGURABLE_AI_PROVIDER_SELECTION.md +728 -0
  287. package/skills-library/integrations/SOCKET_IO_BROADCAST_ALL_VS_ROOM.md +141 -0
  288. package/skills-library/integrations/VIRTUAL_MEETINGS_IMPLEMENTATION.md +374 -0
  289. package/skills-library/integrations/WORDPRESS_LEARNDASH_DATA_RECOVERY.md +53 -0
  290. package/skills-library/integrations/YOUTUBE_API_SETUP.md +141 -0
  291. package/skills-library/integrations/YOUTUBE_BOOKMARKING_EXPLANATION.md +252 -0
  292. package/skills-library/integrations/YOUTUBE_BOOKMARKING_SOLUTION.md +268 -0
  293. package/skills-library/integrations/YOUTUBE_OAUTH_SETUP_GUIDE.md +200 -0
  294. package/skills-library/integrations/YOUTUBE_VIDEO_FIX_COMPLETE.md +192 -0
  295. package/skills-library/integrations/ai-ml/GEMINI_AI_RAG_PIPELINE_COMPLETE_GUIDE.md +195 -0
  296. package/skills-library/integrations/ai-ml/GEMINI_IMAGE_GENERATION_SETUP.md +64 -0
  297. package/skills-library/integrations/cloudflare/cloudflare-turnstile-debugging.md +202 -0
  298. package/skills-library/integrations/cloudflare/cloudflare-turnstile-implementation.md +476 -0
  299. package/skills-library/integrations/cloudflare-turnstile-debugging.md +202 -0
  300. package/skills-library/integrations/cloudflare-turnstile-implementation.md +476 -0
  301. package/skills-library/integrations/ghost-creator-monetization-pattern.md +454 -0
  302. package/skills-library/integrations/headless-cms-architecture.md +484 -0
  303. package/skills-library/integrations/headless-cms-stack-selection.md +183 -0
  304. package/skills-library/integrations/payload-cms-patterns.md +674 -0
  305. package/skills-library/integrations/realtimestt-openwakeword-cuda-windows.md +229 -0
  306. package/skills-library/integrations/rss-podcast-integration.md +300 -0
  307. package/skills-library/integrations/wordpress/WORDPRESS_LEARNDASH_DATA_RECOVERY.md +53 -0
  308. package/skills-library/integrations/youtube/YOUTUBE_API_SETUP.md +141 -0
  309. package/skills-library/integrations/youtube/YOUTUBE_BOOKMARKING_EXPLANATION.md +252 -0
  310. package/skills-library/integrations/youtube/YOUTUBE_BOOKMARKING_SOLUTION.md +268 -0
  311. package/skills-library/integrations/youtube/YOUTUBE_OAUTH_SETUP_GUIDE.md +200 -0
  312. package/skills-library/integrations/youtube/YOUTUBE_VIDEO_FIX_COMPLETE.md +192 -0
  313. package/skills-library/marketing/campaign-analytics.md +97 -0
  314. package/skills-library/marketing/content-creator.md +105 -0
  315. package/skills-library/marketing/marketing-strategy-pmm.md +94 -0
  316. package/skills-library/marketing/social-media-analyzer.md +81 -0
  317. package/skills-library/methodology/ADVANCED_ORCHESTRATION_PATTERNS.md +401 -0
  318. package/skills-library/methodology/AGENT_SELF_IMPROVEMENT_LOOP.md +179 -0
  319. package/skills-library/methodology/BREATH_BASED_PARALLEL_EXECUTION.md +1 -1
  320. package/skills-library/methodology/CLEANSING_CYCLE.md +358 -0
  321. package/skills-library/methodology/CONFIDENCE_ANNOTATION_PATTERN.md +143 -0
  322. package/skills-library/methodology/CRITICAL_PATTERNS_DOCUMENTATION_COMPLETE.md +204 -0
  323. package/skills-library/methodology/DELIVERABLES_SUMMARY.md +341 -0
  324. package/skills-library/methodology/DIFFICULTY_AWARE_AGENT_ROUTING.md +252 -0
  325. package/skills-library/methodology/EVOLUTIONARY_SKILL_SYNTHESIS.md +219 -0
  326. package/skills-library/methodology/GLOMERULUS_DECISION_GATE.md +223 -0
  327. package/skills-library/methodology/HIBERNATION_SYSTEM.md +231 -0
  328. package/skills-library/methodology/INSTRUMENTATION_OVER_RESTRICTION.md +192 -0
  329. package/skills-library/methodology/MASTER_COMPLETION_SUMMARY.md +444 -0
  330. package/skills-library/methodology/MASTER_SESSION_COMPLETION.md +743 -0
  331. package/skills-library/methodology/MERN_QUICK_REFERENCE.md +358 -0
  332. package/skills-library/methodology/ORGAN_AGENT_MAPPING.md +177 -0
  333. package/skills-library/methodology/PARALLEL_WAVE_BASED_REFACTORING.md +440 -0
  334. package/skills-library/methodology/QUICK_REFERENCE.md +358 -0
  335. package/skills-library/methodology/SDFT_ONPOLICY_SELF_DISTILLATION.md +186 -0
  336. package/skills-library/methodology/SELF_QUESTIONING_TASK_GENERATION.md +270 -0
  337. package/skills-library/methodology/SESSION_COMPLETION_SUMMARY.md +304 -0
  338. package/skills-library/methodology/SESSION_SUMMARY.md +432 -0
  339. package/skills-library/methodology/WARRIOR_WORKFLOW_DEBUGGING_PROTOCOL.md +252 -0
  340. package/skills-library/methodology/tech-debt-tracker.md +570 -0
  341. package/skills-library/parallel-debug/SKILL.md +60 -0
  342. package/skills-library/patterns-standards/API_PATTERN_FIX_SUMMARY.md +236 -0
  343. package/skills-library/patterns-standards/BATCH_OPERATIONS_WITH_PROGRESS_MODAL.md +362 -0
  344. package/skills-library/patterns-standards/CRITICAL_CODING_PATTERNS.md +639 -0
  345. package/skills-library/patterns-standards/DARK_MODE_MODAL_VISIBILITY.md +258 -0
  346. package/skills-library/patterns-standards/ERROR_RESILIENCE_IMPLEMENTATION.md +375 -0
  347. package/skills-library/patterns-standards/ES_MODULE_IMPORT_HOISTING_DOTENV.md +298 -0
  348. package/skills-library/patterns-standards/NESTED_BACKDROP_FILTER_CSS_ARTIFACT_FIX.md +76 -0
  349. package/skills-library/patterns-standards/ORDERED_DETECTOR_PIPELINE_GRACEFUL_FALLBACK.md +333 -0
  350. package/skills-library/patterns-standards/PHASE_IMPORT_ERROR_DEBUGGING.md +271 -0
  351. package/skills-library/patterns-standards/PYNPUT_GLOBAL_HOTKEY_VK_MATCHING.md +252 -0
  352. package/skills-library/patterns-standards/REACT_USEEFFECT_CASCADE_RESET_FIX.md +132 -0
  353. package/skills-library/patterns-standards/SUBMENU_HOVER_DROPDOWN_PATTERN.md +225 -0
  354. package/skills-library/patterns-standards/TAILWIND_TEXT_VISIBILITY_OVERRIDE.md +322 -0
  355. package/skills-library/patterns-standards/THEME_AWARE_CSS_VARIABLES_PATTERN.md +209 -0
  356. package/skills-library/patterns-standards/THEME_USER_OBJECT_PROPERTY_NAMING.md +194 -0
  357. package/skills-library/patterns-standards/TOOLTIP_BLOCKING_CLICKS_FIX.md +267 -0
  358. package/skills-library/patterns-standards/claude-code-plugin-structure.md +235 -0
  359. package/skills-library/patterns-standards/react-i18next-setup.md +429 -0
  360. package/skills-library/patterns-standards/thesys-c1-generative-ui-integration.md +967 -0
  361. package/skills-library/plugin-development/CLAUDE_CODE_COMMAND_REGISTRATION_SILENT_FAILURE.md +315 -0
  362. package/skills-library/plugin-development/plugin-command-namespace-vs-global.md +390 -0
  363. package/skills-library/plugin-development/plugin-doc-auto-generation.md +172 -0
  364. package/skills-library/security/GITHUB_REPO_SECURITY_AUDIT.md +115 -0
  365. package/skills-library/security/admin-deletion-safety.md +396 -0
  366. package/skills-library/security/application-vuln-patterns.md +477 -0
  367. package/skills-library/security/env-secrets-manager.md +686 -0
  368. package/skills-library/security/secure-ai-application-templates.md +347 -0
  369. package/skills-library/security/sql-injection-prevention-postgresjs.md +151 -0
  370. package/skills-library/supabase-connection-pooler-fix.md +102 -0
  371. package/skills-library/system-context/POWERSHELL_BASH_INTEROP.md +82 -0
  372. package/skills-library/system-context/SERVICE_LIFECYCLE_MANAGEMENT.md +119 -0
  373. package/skills-library/system-context/SKILL.md +40 -0
  374. package/skills-library/system-context/WINDOWS_DEV_ENVIRONMENT.md +73 -0
  375. package/skills-library/testing/E2E_PLAYWRIGHT_PATTERNS.md +99 -0
  376. package/skills-library/testing/INTEGRATION_TEST_STRATEGY.md +82 -0
  377. package/skills-library/testing/RED_GREEN_BUGFIX_GATE.md +203 -0
  378. package/skills-library/testing/TEST_DATA_MANAGEMENT.md +69 -0
  379. package/skills-library/testing/VITEST_UNIT_TEST_PATTERNS.md +75 -0
  380. package/skills-library/testing/playwright-api-security-tests.md +202 -0
  381. package/skills-library/toolbox/SKILL.md +84 -0
  382. package/skills-library/toolbox/code-graph-and-web-scraping-mcps.md +237 -0
  383. package/skills-library/ui-ux-pro-max/ACCESSIBILITY_ESSENTIALS.md +115 -0
  384. package/skills-library/ui-ux-pro-max/DESIGN_SYSTEM_SCAFFOLDING.md +133 -0
  385. package/skills-library/ui-ux-pro-max/RESPONSIVE_LAYOUT_PATTERNS.md +119 -0
  386. package/skills-library/ui-ux-pro-max/SKILL.md +386 -0
  387. package/skills-library/ui-ux-pro-max/data/charts.csv +26 -0
  388. package/skills-library/ui-ux-pro-max/data/colors.csv +97 -0
  389. package/skills-library/ui-ux-pro-max/data/icons.csv +101 -0
  390. package/skills-library/ui-ux-pro-max/data/landing.csv +31 -0
  391. package/skills-library/ui-ux-pro-max/data/products.csv +97 -0
  392. package/skills-library/ui-ux-pro-max/data/react-performance.csv +45 -0
  393. package/skills-library/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  394. package/skills-library/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  395. package/skills-library/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  396. package/skills-library/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  397. package/skills-library/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  398. package/skills-library/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  399. package/skills-library/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  400. package/skills-library/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  401. package/skills-library/ui-ux-pro-max/data/stacks/react.csv +54 -0
  402. package/skills-library/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  403. package/skills-library/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  404. package/skills-library/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  405. package/skills-library/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  406. package/skills-library/ui-ux-pro-max/data/styles.csv +68 -0
  407. package/skills-library/ui-ux-pro-max/data/typography.csv +58 -0
  408. package/skills-library/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  409. package/skills-library/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  410. package/skills-library/ui-ux-pro-max/data/web-interface.csv +31 -0
  411. package/skills-library/wordpress-style-theme-components.md +1526 -0
  412. package/templates/ASSUMPTIONS.md +1 -1
  413. package/templates/DECISION_LOG.md +0 -1
  414. package/templates/phase-prompt.md +1 -1
  415. package/templates/phoenix-comparison.md +6 -6
  416. package/templates/skill-api-integration.md +106 -0
  417. package/templates/skill-architecture-pattern.md +92 -0
  418. package/templates/skill-debug-pattern.md +98 -0
  419. package/templates/skill-devops-recipe.md +107 -0
  420. package/templates/skill-general.md +65 -0
  421. package/templates/skill-ui-component.md +113 -0
  422. package/tools/uat-runner.py +179 -0
  423. package/version.json +7 -3
  424. package/workflows/handoff-session.md +2 -2
  425. package/workflows/new-project.md +2 -2
  426. package/workflows/plan-phase.md +1 -1
  427. package/.claude-plugin/plugin.json +0 -64
  428. package/skills-library/_general/methodology/LIVE_BREADCRUMB_PROTOCOL.md +0 -242
  429. package/skills-library/_general/methodology/llm-judge-memory-crud.md +0 -241
  430. package/skills-library/methodology/REFLEXION_MEMORY_PATTERN.md +0 -183
  431. package/skills-library/methodology/RESEARCH_BACKED_WORKFLOW_UPGRADE.md +0 -263
  432. package/skills-library/methodology/SABBATH_REST_PATTERN.md +0 -267
  433. package/skills-library/methodology/STONE_AND_SCAFFOLD.md +0 -220
  434. package/skills-library/specialists/api-architecture/api-designer.md +0 -49
  435. package/skills-library/specialists/api-architecture/graphql-architect.md +0 -49
  436. package/skills-library/specialists/api-architecture/mcp-developer.md +0 -51
  437. package/skills-library/specialists/api-architecture/microservices-architect.md +0 -50
  438. package/skills-library/specialists/api-architecture/websocket-engineer.md +0 -48
  439. package/skills-library/specialists/backend/django-expert.md +0 -52
  440. package/skills-library/specialists/backend/fastapi-expert.md +0 -52
  441. package/skills-library/specialists/backend/laravel-specialist.md +0 -52
  442. package/skills-library/specialists/backend/nestjs-expert.md +0 -51
  443. package/skills-library/specialists/backend/rails-expert.md +0 -53
  444. package/skills-library/specialists/backend/spring-boot-engineer.md +0 -56
  445. package/skills-library/specialists/data-ml/fine-tuning-expert.md +0 -48
  446. package/skills-library/specialists/data-ml/ml-pipeline.md +0 -47
  447. package/skills-library/specialists/data-ml/pandas-pro.md +0 -47
  448. package/skills-library/specialists/data-ml/rag-architect.md +0 -51
  449. package/skills-library/specialists/data-ml/spark-engineer.md +0 -47
  450. package/skills-library/specialists/frontend/angular-architect.md +0 -52
  451. package/skills-library/specialists/frontend/flutter-expert.md +0 -51
  452. package/skills-library/specialists/frontend/nextjs-developer.md +0 -54
  453. package/skills-library/specialists/frontend/react-native-expert.md +0 -50
  454. package/skills-library/specialists/frontend/vue-expert.md +0 -51
  455. package/skills-library/specialists/infrastructure/chaos-engineer.md +0 -74
  456. package/skills-library/specialists/infrastructure/cloud-architect.md +0 -70
  457. package/skills-library/specialists/infrastructure/database-optimizer.md +0 -64
  458. package/skills-library/specialists/infrastructure/devops-engineer.md +0 -70
  459. package/skills-library/specialists/infrastructure/kubernetes-specialist.md +0 -52
  460. package/skills-library/specialists/infrastructure/monitoring-expert.md +0 -70
  461. package/skills-library/specialists/infrastructure/sre-engineer.md +0 -70
  462. package/skills-library/specialists/infrastructure/terraform-engineer.md +0 -51
  463. package/skills-library/specialists/languages/cpp-pro.md +0 -74
  464. package/skills-library/specialists/languages/csharp-developer.md +0 -69
  465. package/skills-library/specialists/languages/dotnet-core-expert.md +0 -54
  466. package/skills-library/specialists/languages/golang-pro.md +0 -51
  467. package/skills-library/specialists/languages/java-architect.md +0 -49
  468. package/skills-library/specialists/languages/javascript-pro.md +0 -68
  469. package/skills-library/specialists/languages/kotlin-specialist.md +0 -68
  470. package/skills-library/specialists/languages/php-pro.md +0 -49
  471. package/skills-library/specialists/languages/python-pro.md +0 -52
  472. package/skills-library/specialists/languages/react-expert.md +0 -51
  473. package/skills-library/specialists/languages/rust-engineer.md +0 -50
  474. package/skills-library/specialists/languages/sql-pro.md +0 -56
  475. package/skills-library/specialists/languages/swift-expert.md +0 -69
  476. package/skills-library/specialists/languages/typescript-pro.md +0 -51
  477. package/skills-library/specialists/platform/atlassian-mcp.md +0 -52
  478. package/skills-library/specialists/platform/embedded-systems.md +0 -53
  479. package/skills-library/specialists/platform/game-developer.md +0 -53
  480. package/skills-library/specialists/platform/salesforce-developer.md +0 -53
  481. package/skills-library/specialists/platform/shopify-expert.md +0 -49
  482. package/skills-library/specialists/platform/wordpress-pro.md +0 -49
  483. package/skills-library/specialists/quality/code-documenter.md +0 -51
  484. package/skills-library/specialists/quality/code-reviewer.md +0 -67
  485. package/skills-library/specialists/quality/debugging-wizard.md +0 -51
  486. package/skills-library/specialists/quality/fullstack-guardian.md +0 -51
  487. package/skills-library/specialists/quality/legacy-modernizer.md +0 -50
  488. package/skills-library/specialists/quality/playwright-expert.md +0 -65
  489. package/skills-library/specialists/quality/spec-miner.md +0 -56
  490. package/skills-library/specialists/quality/test-master.md +0 -65
  491. package/skills-library/specialists/security/secure-code-guardian.md +0 -55
  492. package/skills-library/specialists/security/security-reviewer.md +0 -53
  493. package/skills-library/specialists/workflow/architecture-designer.md +0 -53
  494. package/skills-library/specialists/workflow/cli-developer.md +0 -70
  495. package/skills-library/specialists/workflow/feature-forge.md +0 -65
  496. package/skills-library/specialists/workflow/prompt-engineer.md +0 -54
  497. package/skills-library/specialists/workflow/the-fool.md +0 -62
  498. /package/skills-library/{performance → _general/performance}/cache-augmented-generation.md +0 -0
  499. /package/skills-library/{debugging → parallel-debug}/FAILURE_TAXONOMY_CLASSIFICATION.md +0 -0
  500. /package/skills-library/{debugging → parallel-debug}/THREE_AGENT_HYPOTHESIS_DEBUGGING.md +0 -0
@@ -0,0 +1,1965 @@
1
+ ---
2
+ name: erd-react-flow-architecture
3
+ category: database-solutions
4
+ version: 1.0.0
5
+ contributed: 2026-03-09
6
+ contributor: fire-research
7
+ last_updated: 2026-03-09
8
+ tags: [react-flow, erd-editor, architecture, tauri, shadcn, typescript, chartdb, drawdb]
9
+ difficulty: hard
10
+ ---
11
+
12
+ # ERD React Flow Architecture
13
+
14
+
15
+ ## Problem
16
+
17
+ Building a production-quality visual ERD editor with React Flow requires synthesizing patterns from multiple proven implementations. No single OSS project covers the full surface area: ChartDB has the best stack match but lacks multi-dialect DDL generation; DrawDB has gold-standard parsers but uses a custom canvas; React Flow's official DatabaseSchemaNode handles per-column handles but not Crow's Foot notation. A developer starting from scratch will spend weeks discovering patterns that are already solved across these projects.
18
+
19
+ ## Solution Pattern
20
+
21
+ Combine the best of each reference implementation into a layered architecture:
22
+
23
+ 1. **Data Model** (DrawDB pattern) — Dialect-neutral JSON as the single source of truth
24
+ 2. **React Flow Visualization** (ChartDB + official DatabaseSchemaNode) — Custom node/edge types with per-column handles
25
+ 3. **Parser/Generator** (DrawDB pattern) — Separate parser and generator per SQL dialect, all round-tripping through the JSON model
26
+ 4. **Persistence** (Tauri + Dexie.js fallback) — File system for desktop, IndexedDB for web
27
+ 5. **MCP Integration** (ERFlow pattern) — Expose schema operations as MCP tools for NL editing
28
+
29
+ ---
30
+
31
+ ## Recommended Stack
32
+
33
+ | Layer | Technology | Why |
34
+ |-------|-----------|-----|
35
+ | Desktop shell | Tauri 2.x | Rust backend, file system access, <10MB binary |
36
+ | UI framework | React 18+ (Vite) | ChartDB uses this exact stack |
37
+ | Canvas | React Flow (v12+) | Declarative node/edge system, built-in controls |
38
+ | Component library | shadcn/ui | ChartDB + NextERD both use it, Tailwind-based |
39
+ | State management | Zustand | React Flow's recommended store, ChartDB uses it |
40
+ | Local persistence (web) | Dexie.js (IndexedDB) | ChartDB pattern — instant save, no backend needed |
41
+ | Local persistence (desktop) | Tauri fs API | Native file dialogs, .erd.json files |
42
+ | DDL parsing | node-sql-parser | Bidirectional SQL-to-AST, multi-dialect |
43
+ | Type safety | TypeScript (strict) | All reference implementations use TS |
44
+
45
+ ---
46
+
47
+ ## Data Model
48
+
49
+ The central JSON model is the most critical architectural decision. Every feature — rendering, parsing, exporting, undo/redo — reads from and writes to this model. DrawDB's approach of a dialect-neutral intermediate representation is the proven pattern.
50
+
51
+ ```typescript
52
+ // ============================================================
53
+ // Core ERD Data Model — dialect-neutral JSON
54
+ // ============================================================
55
+
56
+ /** Top-level document — serialized as .erd.json */
57
+ export interface ERDDocument {
58
+ version: '1.0.0';
59
+ name: string;
60
+ description?: string;
61
+ createdAt: string; // ISO 8601
62
+ updatedAt: string;
63
+ /** Active notation mode affects rendering only, not the model */
64
+ notation: 'crowsfoot' | 'chen';
65
+ /** Target dialect for DDL export */
66
+ dialect: SQLDialect;
67
+ tables: Table[];
68
+ relationships: Relationship[];
69
+ /** Diagram metadata — positions, viewport, etc. */
70
+ diagram: DiagramMeta;
71
+ }
72
+
73
+ export type SQLDialect = 'postgresql' | 'mysql' | 'sqlite' | 'mariadb' | 'mssql';
74
+
75
+ export interface Table {
76
+ id: string; // nanoid or cuid
77
+ name: string;
78
+ schema?: string; // 'public', 'dbo', etc.
79
+ comment?: string;
80
+ columns: Column[];
81
+ indexes: Index[];
82
+ /** Chen-mode only: which columns to render as separate oval nodes */
83
+ chenAttributeDisplay?: 'inline' | 'ovals';
84
+ }
85
+
86
+ export interface Column {
87
+ id: string;
88
+ name: string;
89
+ type: string; // Raw SQL type string: 'VARCHAR(255)', 'INTEGER', etc.
90
+ isPrimaryKey: boolean;
91
+ isForeignKey: boolean;
92
+ isNullable: boolean;
93
+ isUnique: boolean;
94
+ isAutoIncrement: boolean;
95
+ defaultValue?: string;
96
+ comment?: string;
97
+ /** For composite PKs — order within the key */
98
+ pkOrdinal?: number;
99
+ /** FK reference (also represented in Relationship[], but stored here for column-level rendering) */
100
+ references?: {
101
+ tableId: string;
102
+ columnId: string;
103
+ };
104
+ }
105
+
106
+ export interface Index {
107
+ id: string;
108
+ name: string;
109
+ columns: string[]; // Column IDs
110
+ isUnique: boolean;
111
+ type?: 'btree' | 'hash' | 'gin' | 'gist';
112
+ }
113
+
114
+ export interface Relationship {
115
+ id: string;
116
+ name?: string; // e.g., 'fk_orders_customer'
117
+ sourceTableId: string;
118
+ sourceColumnId: string;
119
+ targetTableId: string;
120
+ targetColumnId: string;
121
+ /** Cardinality at source end */
122
+ sourceCardinality: Cardinality;
123
+ /** Cardinality at target end */
124
+ targetCardinality: Cardinality;
125
+ /** ON DELETE behavior */
126
+ onDelete: ReferentialAction;
127
+ /** ON UPDATE behavior */
128
+ onUpdate: ReferentialAction;
129
+ }
130
+
131
+ export type Cardinality = 'exactly-one' | 'zero-or-one' | 'one-or-many' | 'zero-or-many';
132
+
133
+ export type ReferentialAction = 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION';
134
+
135
+ export interface DiagramMeta {
136
+ /** Per-table positions on the canvas */
137
+ positions: Record<string, { x: number; y: number }>;
138
+ /** Viewport state for restoring pan/zoom */
139
+ viewport: {
140
+ x: number;
141
+ y: number;
142
+ zoom: number;
143
+ };
144
+ /** Grid snap settings */
145
+ gridSize: number;
146
+ snapToGrid: boolean;
147
+ }
148
+ ```
149
+
150
+ ### JSON Serialization Format (.erd.json)
151
+
152
+ ```json
153
+ {
154
+ "version": "1.0.0",
155
+ "name": "E-Commerce Schema",
156
+ "notation": "crowsfoot",
157
+ "dialect": "postgresql",
158
+ "tables": [
159
+ {
160
+ "id": "tbl_001",
161
+ "name": "users",
162
+ "columns": [
163
+ { "id": "col_001", "name": "id", "type": "UUID", "isPrimaryKey": true, "isForeignKey": false, "isNullable": false, "isUnique": true, "isAutoIncrement": false, "defaultValue": "gen_random_uuid()" },
164
+ { "id": "col_002", "name": "email", "type": "VARCHAR(255)", "isPrimaryKey": false, "isForeignKey": false, "isNullable": false, "isUnique": true, "isAutoIncrement": false },
165
+ { "id": "col_003", "name": "created_at", "type": "TIMESTAMPTZ", "isPrimaryKey": false, "isForeignKey": false, "isNullable": false, "isUnique": false, "isAutoIncrement": false, "defaultValue": "NOW()" }
166
+ ],
167
+ "indexes": []
168
+ }
169
+ ],
170
+ "relationships": [],
171
+ "diagram": {
172
+ "positions": { "tbl_001": { "x": 100, "y": 200 } },
173
+ "viewport": { "x": 0, "y": 0, "zoom": 1 },
174
+ "gridSize": 20,
175
+ "snapToGrid": true
176
+ }
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ## React Flow Node Types
183
+
184
+ ### Table Node (Crow's Foot Mode) — Primary
185
+
186
+ Based on React Flow's official `DatabaseSchemaNode` pattern, extended with PK/FK icons, nullable indicators, and per-column source/target handles for field-level connections.
187
+
188
+ ```tsx
189
+ // components/nodes/TableNode.tsx
190
+ import { memo } from 'react';
191
+ import { NodeProps, Handle, Position } from '@xyflow/react';
192
+ import { KeyRound, Link2, CircleDot } from 'lucide-react';
193
+ import { cn } from '@/lib/utils';
194
+ import type { Table, Column } from '@/types/erd';
195
+
196
+ interface TableNodeData {
197
+ table: Table;
198
+ isSelected: boolean;
199
+ }
200
+
201
+ export const TableNode = memo(({ data, selected }: NodeProps<TableNodeData>) => {
202
+ const { table } = data;
203
+ const pkColumns = table.columns.filter(c => c.isPrimaryKey);
204
+ const regularColumns = table.columns.filter(c => !c.isPrimaryKey);
205
+
206
+ return (
207
+ <div
208
+ className={cn(
209
+ 'rounded-lg border bg-card text-card-foreground shadow-sm min-w-[220px]',
210
+ 'transition-shadow duration-200',
211
+ selected && 'ring-2 ring-primary shadow-lg',
212
+ )}
213
+ >
214
+ {/* Table header */}
215
+ <div className="flex items-center gap-2 px-3 py-2 bg-muted/50 rounded-t-lg border-b">
216
+ <CircleDot className="h-4 w-4 text-muted-foreground" />
217
+ <span className="font-semibold text-sm">{table.name}</span>
218
+ <span className="ml-auto text-xs text-muted-foreground">
219
+ {table.columns.length} cols
220
+ </span>
221
+ </div>
222
+
223
+ {/* Primary key columns — always on top */}
224
+ {pkColumns.map((col) => (
225
+ <ColumnRow key={col.id} column={col} tableId={table.id} isPk />
226
+ ))}
227
+
228
+ {/* Separator between PK and regular columns */}
229
+ {pkColumns.length > 0 && regularColumns.length > 0 && (
230
+ <div className="border-t border-dashed" />
231
+ )}
232
+
233
+ {/* Regular columns */}
234
+ {regularColumns.map((col) => (
235
+ <ColumnRow key={col.id} column={col} tableId={table.id} />
236
+ ))}
237
+ </div>
238
+ );
239
+ });
240
+
241
+ TableNode.displayName = 'TableNode';
242
+
243
+ /** Individual column row with per-column handles */
244
+ function ColumnRow({
245
+ column,
246
+ tableId,
247
+ isPk = false,
248
+ }: {
249
+ column: Column;
250
+ tableId: string;
251
+ isPk?: boolean;
252
+ }) {
253
+ // Handle IDs follow the pattern: {tableId}.{columnId}
254
+ // This enables field-level edge connections
255
+ const handleId = `${tableId}.${column.id}`;
256
+
257
+ return (
258
+ <div className="relative flex items-center gap-2 px-3 py-1.5 text-xs hover:bg-muted/30 group">
259
+ {/* Source handle — left side (target for incoming FKs) */}
260
+ <Handle
261
+ type="target"
262
+ position={Position.Left}
263
+ id={`${handleId}-target`}
264
+ className="!w-2 !h-2 !bg-primary/60 !border-primary"
265
+ style={{ top: '50%' }}
266
+ />
267
+
268
+ {/* Column icon */}
269
+ {isPk ? (
270
+ <KeyRound className="h-3.5 w-3.5 text-amber-500 shrink-0" />
271
+ ) : column.isForeignKey ? (
272
+ <Link2 className="h-3.5 w-3.5 text-blue-500 shrink-0" />
273
+ ) : (
274
+ <span className="w-3.5 shrink-0" />
275
+ )}
276
+
277
+ {/* Column name */}
278
+ <span className={cn('font-mono', isPk && 'font-semibold')}>
279
+ {column.name}
280
+ </span>
281
+
282
+ {/* Column type */}
283
+ <span className="ml-auto text-muted-foreground font-mono">
284
+ {column.type}
285
+ {!column.isNullable && (
286
+ <span className="text-red-400 ml-1" title="NOT NULL">*</span>
287
+ )}
288
+ </span>
289
+
290
+ {/* Source handle — right side (source for outgoing FKs) */}
291
+ <Handle
292
+ type="source"
293
+ position={Position.Right}
294
+ id={`${handleId}-source`}
295
+ className="!w-2 !h-2 !bg-primary/60 !border-primary"
296
+ style={{ top: '50%' }}
297
+ />
298
+ </div>
299
+ );
300
+ }
301
+ ```
302
+
303
+ ### Entity Node (Chen Mode)
304
+
305
+ In Chen notation, entities are rectangles and attributes are separate oval nodes connected by lines. The same data model renders differently.
306
+
307
+ ```tsx
308
+ // components/nodes/ChenEntityNode.tsx
309
+ import { memo } from 'react';
310
+ import { NodeProps, Handle, Position } from '@xyflow/react';
311
+ import { cn } from '@/lib/utils';
312
+
313
+ interface ChenEntityNodeData {
314
+ name: string;
315
+ isWeak: boolean;
316
+ }
317
+
318
+ export const ChenEntityNode = memo(({ data, selected }: NodeProps<ChenEntityNodeData>) => (
319
+ <div
320
+ className={cn(
321
+ 'px-6 py-3 bg-card border-2 rounded-sm text-center font-semibold',
322
+ data.isWeak && 'border-double border-4',
323
+ selected && 'ring-2 ring-primary',
324
+ )}
325
+ >
326
+ {data.name}
327
+ {/* Handles on all 4 sides for flexible attribute/relationship connections */}
328
+ <Handle type="source" position={Position.Top} id="top" />
329
+ <Handle type="source" position={Position.Right} id="right" />
330
+ <Handle type="source" position={Position.Bottom} id="bottom" />
331
+ <Handle type="source" position={Position.Left} id="left" />
332
+ </div>
333
+ ));
334
+
335
+ ChenEntityNode.displayName = 'ChenEntityNode';
336
+
337
+ // components/nodes/ChenAttributeNode.tsx
338
+ export const ChenAttributeNode = memo(({ data, selected }: NodeProps<{
339
+ name: string;
340
+ variant: 'simple' | 'key' | 'multivalued' | 'derived' | 'composite' | 'partial-key';
341
+ }>) => (
342
+ <div
343
+ className={cn(
344
+ 'px-4 py-2 rounded-full bg-card text-center text-sm border',
345
+ data.variant === 'key' && 'font-bold [&>span]:underline',
346
+ data.variant === 'multivalued' && 'border-double border-4',
347
+ data.variant === 'derived' && 'border-dashed',
348
+ data.variant === 'partial-key' && 'font-bold [&>span]:underline [&>span]:decoration-dashed',
349
+ selected && 'ring-2 ring-primary',
350
+ )}
351
+ >
352
+ <span>{data.name}</span>
353
+ <Handle type="target" position={Position.Top} id="top" />
354
+ <Handle type="target" position={Position.Bottom} id="bottom" />
355
+ <Handle type="target" position={Position.Left} id="left" />
356
+ <Handle type="target" position={Position.Right} id="right" />
357
+ </div>
358
+ ));
359
+
360
+ ChenAttributeNode.displayName = 'ChenAttributeNode';
361
+
362
+ // components/nodes/ChenRelationshipNode.tsx (Diamond shape)
363
+ export const ChenRelationshipNode = memo(({ data, selected }: NodeProps<{
364
+ name: string;
365
+ isIdentifying: boolean;
366
+ }>) => (
367
+ <div className="relative" style={{ width: 120, height: 80 }}>
368
+ <svg viewBox="0 0 120 80" className="absolute inset-0">
369
+ <polygon
370
+ points="60,2 118,40 60,78 2,40"
371
+ className={cn(
372
+ 'fill-card stroke-foreground',
373
+ data.isIdentifying ? 'stroke-[3]' : 'stroke-[1.5]',
374
+ selected && 'stroke-primary',
375
+ )}
376
+ />
377
+ {data.isIdentifying && (
378
+ <polygon
379
+ points="60,8 112,40 60,72 8,40"
380
+ className="fill-none stroke-foreground stroke-[1.5]"
381
+ />
382
+ )}
383
+ </svg>
384
+ <span className="absolute inset-0 flex items-center justify-center text-xs font-medium">
385
+ {data.name}
386
+ </span>
387
+ <Handle type="source" position={Position.Top} id="top" style={{ left: '50%' }} />
388
+ <Handle type="source" position={Position.Right} id="right" style={{ top: '50%' }} />
389
+ <Handle type="source" position={Position.Bottom} id="bottom" style={{ left: '50%' }} />
390
+ <Handle type="source" position={Position.Left} id="left" style={{ top: '50%' }} />
391
+ </div>
392
+ ));
393
+
394
+ ChenRelationshipNode.displayName = 'ChenRelationshipNode';
395
+ ```
396
+
397
+ ### Node Type Registration
398
+
399
+ ```tsx
400
+ // lib/node-types.ts
401
+ import { TableNode } from '@/components/nodes/TableNode';
402
+ import { ChenEntityNode, ChenAttributeNode, ChenRelationshipNode } from '@/components/nodes/ChenNodes';
403
+
404
+ export const crowsFootNodeTypes = {
405
+ table: TableNode,
406
+ } as const;
407
+
408
+ export const chenNodeTypes = {
409
+ entity: ChenEntityNode,
410
+ attribute: ChenAttributeNode,
411
+ relationship: ChenRelationshipNode,
412
+ } as const;
413
+ ```
414
+
415
+ ---
416
+
417
+ ## React Flow Edge Types
418
+
419
+ ### Crow's Foot Edge — Custom SVG Markers
420
+
421
+ The key insight from `relliv/crows-foot-notations`: define 4 SVG marker symbols in a `<defs>` block, then reference them as `markerStart`/`markerEnd` on React Flow edges.
422
+
423
+ ```tsx
424
+ // components/edges/CrowsFootMarkers.tsx
425
+ /**
426
+ * SVG marker definitions for Crow's Foot notation.
427
+ * Render this ONCE inside the ReactFlow component.
428
+ *
429
+ * 4 cardinality markers:
430
+ * exactly-one: --|-- (single perpendicular line)
431
+ * zero-or-one: --O|-- (circle + perpendicular line)
432
+ * one-or-many: --<|-- (fork + perpendicular line)
433
+ * zero-or-many: --O<-- (circle + fork)
434
+ */
435
+ export function CrowsFootMarkerDefs() {
436
+ return (
437
+ <svg style={{ position: 'absolute', width: 0, height: 0 }}>
438
+ <defs>
439
+ {/* Exactly One: perpendicular line */}
440
+ <marker
441
+ id="cf-exactly-one"
442
+ viewBox="0 0 20 20"
443
+ refX="18"
444
+ refY="10"
445
+ markerWidth="20"
446
+ markerHeight="20"
447
+ orient="auto-start-reverse"
448
+ markerUnits="userSpaceOnUse"
449
+ >
450
+ <line x1="18" y1="2" x2="18" y2="18" stroke="currentColor" strokeWidth="2" />
451
+ <line x1="12" y1="2" x2="12" y2="18" stroke="currentColor" strokeWidth="2" />
452
+ </marker>
453
+
454
+ {/* Zero or One: circle + perpendicular line */}
455
+ <marker
456
+ id="cf-zero-or-one"
457
+ viewBox="0 0 30 20"
458
+ refX="28"
459
+ refY="10"
460
+ markerWidth="30"
461
+ markerHeight="20"
462
+ orient="auto-start-reverse"
463
+ markerUnits="userSpaceOnUse"
464
+ >
465
+ <circle cx="10" cy="10" r="6" fill="white" stroke="currentColor" strokeWidth="2" />
466
+ <line x1="28" y1="2" x2="28" y2="18" stroke="currentColor" strokeWidth="2" />
467
+ </marker>
468
+
469
+ {/* One or Many: fork (crow's foot) + perpendicular line */}
470
+ <marker
471
+ id="cf-one-or-many"
472
+ viewBox="0 0 24 20"
473
+ refX="22"
474
+ refY="10"
475
+ markerWidth="24"
476
+ markerHeight="20"
477
+ orient="auto-start-reverse"
478
+ markerUnits="userSpaceOnUse"
479
+ >
480
+ {/* Perpendicular line */}
481
+ <line x1="22" y1="2" x2="22" y2="18" stroke="currentColor" strokeWidth="2" />
482
+ {/* Fork: three lines diverging from right to left */}
483
+ <line x1="16" y1="10" x2="4" y2="2" stroke="currentColor" strokeWidth="1.5" />
484
+ <line x1="16" y1="10" x2="4" y2="10" stroke="currentColor" strokeWidth="1.5" />
485
+ <line x1="16" y1="10" x2="4" y2="18" stroke="currentColor" strokeWidth="1.5" />
486
+ </marker>
487
+
488
+ {/* Zero or Many: circle + fork */}
489
+ <marker
490
+ id="cf-zero-or-many"
491
+ viewBox="0 0 34 20"
492
+ refX="32"
493
+ refY="10"
494
+ markerWidth="34"
495
+ markerHeight="20"
496
+ orient="auto-start-reverse"
497
+ markerUnits="userSpaceOnUse"
498
+ >
499
+ {/* Circle (zero) */}
500
+ <circle cx="8" cy="10" r="6" fill="white" stroke="currentColor" strokeWidth="2" />
501
+ {/* Fork: three lines diverging */}
502
+ <line x1="26" y1="10" x2="16" y2="2" stroke="currentColor" strokeWidth="1.5" />
503
+ <line x1="26" y1="10" x2="16" y2="10" stroke="currentColor" strokeWidth="1.5" />
504
+ <line x1="26" y1="10" x2="16" y2="18" stroke="currentColor" strokeWidth="1.5" />
505
+ </marker>
506
+ </defs>
507
+ </svg>
508
+ );
509
+ }
510
+
511
+ /** Map cardinality enum to marker ID */
512
+ export function getMarkerId(cardinality: Cardinality): string {
513
+ const map: Record<Cardinality, string> = {
514
+ 'exactly-one': 'url(#cf-exactly-one)',
515
+ 'zero-or-one': 'url(#cf-zero-or-one)',
516
+ 'one-or-many': 'url(#cf-one-or-many)',
517
+ 'zero-or-many': 'url(#cf-zero-or-many)',
518
+ };
519
+ return map[cardinality];
520
+ }
521
+ ```
522
+
523
+ ### Crow's Foot Edge Component
524
+
525
+ ```tsx
526
+ // components/edges/CrowsFootEdge.tsx
527
+ import { memo } from 'react';
528
+ import { EdgeProps, getBezierPath } from '@xyflow/react';
529
+ import { getMarkerId } from './CrowsFootMarkers';
530
+ import type { Cardinality } from '@/types/erd';
531
+
532
+ interface CrowsFootEdgeData {
533
+ sourceCardinality: Cardinality;
534
+ targetCardinality: Cardinality;
535
+ relationshipName?: string;
536
+ }
537
+
538
+ export const CrowsFootEdge = memo(({
539
+ id,
540
+ sourceX, sourceY,
541
+ targetX, targetY,
542
+ sourcePosition, targetPosition,
543
+ data,
544
+ selected,
545
+ }: EdgeProps<CrowsFootEdgeData>) => {
546
+ const [edgePath, labelX, labelY] = getBezierPath({
547
+ sourceX, sourceY, targetX, targetY,
548
+ sourcePosition, targetPosition,
549
+ });
550
+
551
+ return (
552
+ <>
553
+ {/* Invisible wider path for easier click/hover targeting */}
554
+ <path
555
+ d={edgePath}
556
+ fill="none"
557
+ stroke="transparent"
558
+ strokeWidth={20}
559
+ className="react-flow__edge-interaction"
560
+ />
561
+ {/* Visible edge line */}
562
+ <path
563
+ id={id}
564
+ d={edgePath}
565
+ fill="none"
566
+ stroke={selected ? 'hsl(var(--primary))' : 'hsl(var(--muted-foreground))'}
567
+ strokeWidth={selected ? 2.5 : 1.5}
568
+ markerStart={data ? getMarkerId(data.sourceCardinality) : undefined}
569
+ markerEnd={data ? getMarkerId(data.targetCardinality) : undefined}
570
+ />
571
+ {/* Relationship label */}
572
+ {data?.relationshipName && (
573
+ <foreignObject
574
+ width={120}
575
+ height={24}
576
+ x={labelX - 60}
577
+ y={labelY - 12}
578
+ requiredExtensions="http://www.w3.org/1999/xhtml"
579
+ >
580
+ <div className="text-[10px] text-muted-foreground text-center bg-background/80 rounded px-1">
581
+ {data.relationshipName}
582
+ </div>
583
+ </foreignObject>
584
+ )}
585
+ </>
586
+ );
587
+ });
588
+
589
+ CrowsFootEdge.displayName = 'CrowsFootEdge';
590
+ ```
591
+
592
+ ### Chen Notation Edge
593
+
594
+ ```tsx
595
+ // components/edges/ChenEdge.tsx
596
+ // Simpler edge — cardinality labels as text, participation as line style
597
+ import { memo } from 'react';
598
+ import { EdgeProps, getStraightPath } from '@xyflow/react';
599
+
600
+ interface ChenEdgeData {
601
+ cardinality?: string; // '1', 'M', 'N'
602
+ isTotalParticipation: boolean;
603
+ }
604
+
605
+ export const ChenEdge = memo(({
606
+ sourceX, sourceY, targetX, targetY, data, id,
607
+ }: EdgeProps<ChenEdgeData>) => {
608
+ const [path, labelX, labelY] = getStraightPath({
609
+ sourceX, sourceY, targetX, targetY,
610
+ });
611
+
612
+ return (
613
+ <>
614
+ <path
615
+ id={id}
616
+ d={path}
617
+ fill="none"
618
+ stroke="hsl(var(--foreground))"
619
+ strokeWidth={data?.isTotalParticipation ? 3 : 1.5}
620
+ />
621
+ {data?.cardinality && (
622
+ <text x={labelX} y={labelY - 8} textAnchor="middle" className="text-xs fill-foreground">
623
+ {data.cardinality}
624
+ </text>
625
+ )}
626
+ </>
627
+ );
628
+ });
629
+
630
+ ChenEdge.displayName = 'ChenEdge';
631
+ ```
632
+
633
+ ### Edge Type Registration
634
+
635
+ ```tsx
636
+ // lib/edge-types.ts
637
+ import { CrowsFootEdge } from '@/components/edges/CrowsFootEdge';
638
+ import { ChenEdge } from '@/components/edges/ChenEdge';
639
+
640
+ export const crowsFootEdgeTypes = {
641
+ crowsfoot: CrowsFootEdge,
642
+ } as const;
643
+
644
+ export const chenEdgeTypes = {
645
+ chen: ChenEdge,
646
+ } as const;
647
+ ```
648
+
649
+ ---
650
+
651
+ ## Canvas Features
652
+
653
+ ### Core React Flow Setup
654
+
655
+ ```tsx
656
+ // components/ERDCanvas.tsx
657
+ import { useCallback } from 'react';
658
+ import {
659
+ ReactFlow,
660
+ Background,
661
+ Controls,
662
+ MiniMap,
663
+ Panel,
664
+ type OnConnect,
665
+ BackgroundVariant,
666
+ } from '@xyflow/react';
667
+ import '@xyflow/react/dist/style.css';
668
+ import { crowsFootNodeTypes } from '@/lib/node-types';
669
+ import { crowsFootEdgeTypes } from '@/lib/edge-types';
670
+ import { CrowsFootMarkerDefs } from '@/components/edges/CrowsFootMarkers';
671
+ import { useERDStore } from '@/store/erd-store';
672
+
673
+ export function ERDCanvas() {
674
+ const { nodes, edges, onNodesChange, onEdgesChange } = useERDStore();
675
+
676
+ const onConnect: OnConnect = useCallback((connection) => {
677
+ // Create relationship from connection
678
+ // Parse handle IDs to get table + column: "tbl_001.col_002-source"
679
+ useERDStore.getState().addRelationship(connection);
680
+ }, []);
681
+
682
+ return (
683
+ <div className="h-full w-full">
684
+ <CrowsFootMarkerDefs />
685
+ <ReactFlow
686
+ nodes={nodes}
687
+ edges={edges}
688
+ onNodesChange={onNodesChange}
689
+ onEdgesChange={onEdgesChange}
690
+ onConnect={onConnect}
691
+ nodeTypes={crowsFootNodeTypes}
692
+ edgeTypes={crowsFootEdgeTypes}
693
+ snapToGrid
694
+ snapGrid={[20, 20]}
695
+ fitView
696
+ minZoom={0.1}
697
+ maxZoom={4}
698
+ deleteKeyCode={['Backspace', 'Delete']}
699
+ multiSelectionKeyCode="Shift"
700
+ proOptions={{ hideAttribution: true }}
701
+ >
702
+ <Background variant={BackgroundVariant.Dots} gap={20} size={1} />
703
+ <Controls showInteractive={false} />
704
+ <MiniMap
705
+ nodeStrokeWidth={3}
706
+ zoomable
707
+ pannable
708
+ className="!bg-muted/50 !border-border"
709
+ />
710
+ <Panel position="top-right">
711
+ {/* Toolbar: add table, import, export, undo/redo, notation toggle */}
712
+ </Panel>
713
+ </ReactFlow>
714
+ </div>
715
+ );
716
+ }
717
+ ```
718
+
719
+ ### Zustand Store with Undo/Redo
720
+
721
+ ```typescript
722
+ // store/erd-store.ts
723
+ import { create } from 'zustand';
724
+ import { temporal } from 'zundo';
725
+ import {
726
+ type Node, type Edge,
727
+ applyNodeChanges, applyEdgeChanges,
728
+ type NodeChange, type EdgeChange,
729
+ } from '@xyflow/react';
730
+ import { nanoid } from 'nanoid';
731
+ import type { ERDDocument, Table, Relationship } from '@/types/erd';
732
+
733
+ interface ERDState {
734
+ document: ERDDocument;
735
+ nodes: Node[];
736
+ edges: Edge[];
737
+ // React Flow handlers
738
+ onNodesChange: (changes: NodeChange[]) => void;
739
+ onEdgesChange: (changes: EdgeChange[]) => void;
740
+ // ERD operations
741
+ addTable: (table: Table) => void;
742
+ updateTable: (tableId: string, updates: Partial<Table>) => void;
743
+ removeTable: (tableId: string) => void;
744
+ addRelationship: (connection: any) => void;
745
+ removeRelationship: (relId: string) => void;
746
+ // I/O
747
+ loadDocument: (doc: ERDDocument) => void;
748
+ toDocument: () => ERDDocument;
749
+ // Sync: convert ERDDocument <-> React Flow nodes/edges
750
+ syncFromModel: () => void;
751
+ }
752
+
753
+ /**
754
+ * Zustand store wrapped with zundo for undo/redo.
755
+ * Ctrl+Z / Ctrl+Shift+Z call useTemporalStore().undo() / .redo()
756
+ */
757
+ export const useERDStore = create<ERDState>()(
758
+ temporal(
759
+ (set, get) => ({
760
+ document: createEmptyDocument(),
761
+ nodes: [],
762
+ edges: [],
763
+
764
+ onNodesChange: (changes) => {
765
+ set({ nodes: applyNodeChanges(changes, get().nodes) });
766
+ // Sync position changes back to document.diagram.positions
767
+ for (const change of changes) {
768
+ if (change.type === 'position' && change.position) {
769
+ get().document.diagram.positions[change.id] = change.position;
770
+ }
771
+ }
772
+ },
773
+
774
+ onEdgesChange: (changes) => {
775
+ set({ edges: applyEdgeChanges(changes, get().edges) });
776
+ },
777
+
778
+ addTable: (table) => {
779
+ const doc = get().document;
780
+ // Immutable update — required for zundo temporal middleware undo/redo
781
+ set({ document: { ...doc, tables: [...doc.tables, table] } });
782
+ get().syncFromModel();
783
+ },
784
+
785
+ updateTable: (tableId, updates) => {
786
+ const doc = get().document;
787
+ // Immutable update — map produces new array, preserving zundo snapshots
788
+ set({
789
+ document: {
790
+ ...doc,
791
+ tables: doc.tables.map(t =>
792
+ t.id === tableId ? { ...t, ...updates } : t
793
+ ),
794
+ },
795
+ });
796
+ get().syncFromModel();
797
+ },
798
+
799
+ removeTable: (tableId) => {
800
+ const doc = get().document;
801
+ // Immutable update — filter + destructure, never mutate doc in-place
802
+ const { [tableId]: _, ...remainingPositions } = doc.diagram.positions;
803
+ set({
804
+ document: {
805
+ ...doc,
806
+ tables: doc.tables.filter(t => t.id !== tableId),
807
+ relationships: doc.relationships.filter(
808
+ r => r.sourceTableId !== tableId && r.targetTableId !== tableId
809
+ ),
810
+ diagram: { ...doc.diagram, positions: remainingPositions },
811
+ },
812
+ });
813
+ get().syncFromModel();
814
+ },
815
+
816
+ addRelationship: (connection) => {
817
+ // Parse handle IDs: "tbl_001.col_002-source" -> tableId="tbl_001", columnId="col_002"
818
+ const parseHandle = (handleId: string) => {
819
+ const [combined] = handleId.split('-');
820
+ const [tableId, columnId] = combined.split('.');
821
+ return { tableId, columnId };
822
+ };
823
+ const source = parseHandle(connection.sourceHandle);
824
+ const target = parseHandle(connection.targetHandle);
825
+ const rel: Relationship = {
826
+ id: nanoid(),
827
+ sourceTableId: source.tableId,
828
+ sourceColumnId: source.columnId,
829
+ targetTableId: target.tableId,
830
+ targetColumnId: target.columnId,
831
+ sourceCardinality: 'exactly-one',
832
+ targetCardinality: 'zero-or-many',
833
+ onDelete: 'NO ACTION',
834
+ onUpdate: 'NO ACTION',
835
+ };
836
+ const doc = get().document;
837
+ doc.relationships.push(rel);
838
+ set({ document: { ...doc } });
839
+ get().syncFromModel();
840
+ },
841
+
842
+ removeRelationship: (relId) => {
843
+ const doc = get().document;
844
+ doc.relationships = doc.relationships.filter(r => r.id !== relId);
845
+ set({ document: { ...doc } });
846
+ get().syncFromModel();
847
+ },
848
+
849
+ loadDocument: (doc) => {
850
+ set({ document: doc });
851
+ get().syncFromModel();
852
+ },
853
+
854
+ toDocument: () => {
855
+ const state = get();
856
+ // Sync current positions back to document
857
+ for (const node of state.nodes) {
858
+ if (node.position) {
859
+ state.document.diagram.positions[node.id] = node.position;
860
+ }
861
+ }
862
+ state.document.updatedAt = new Date().toISOString();
863
+ return structuredClone(state.document);
864
+ },
865
+
866
+ syncFromModel: () => {
867
+ const doc = get().document;
868
+ const nodes: Node[] = doc.tables.map(table => ({
869
+ id: table.id,
870
+ type: 'table',
871
+ position: doc.diagram.positions[table.id] ?? { x: 0, y: 0 },
872
+ data: { table, isSelected: false },
873
+ }));
874
+ const edges: Edge[] = doc.relationships.map(rel => ({
875
+ id: rel.id,
876
+ source: rel.sourceTableId,
877
+ target: rel.targetTableId,
878
+ sourceHandle: `${rel.sourceTableId}.${rel.sourceColumnId}-source`,
879
+ targetHandle: `${rel.targetTableId}.${rel.targetColumnId}-target`,
880
+ type: 'crowsfoot',
881
+ data: {
882
+ sourceCardinality: rel.sourceCardinality,
883
+ targetCardinality: rel.targetCardinality,
884
+ relationshipName: rel.name,
885
+ },
886
+ }));
887
+ set({ nodes, edges });
888
+ },
889
+ }),
890
+ {
891
+ // zundo: limit undo history to 100 steps
892
+ limit: 100,
893
+ // Only track meaningful changes (not every mouse move)
894
+ partialize: (state) => ({
895
+ document: state.document,
896
+ }),
897
+ }
898
+ )
899
+ );
900
+
901
+ function createEmptyDocument(): ERDDocument {
902
+ return {
903
+ version: '1.0.0',
904
+ name: 'Untitled',
905
+ createdAt: new Date().toISOString(),
906
+ updatedAt: new Date().toISOString(),
907
+ notation: 'crowsfoot',
908
+ dialect: 'postgresql',
909
+ tables: [],
910
+ relationships: [],
911
+ diagram: {
912
+ positions: {},
913
+ viewport: { x: 0, y: 0, zoom: 1 },
914
+ gridSize: 20,
915
+ snapToGrid: true,
916
+ },
917
+ };
918
+ }
919
+ ```
920
+
921
+ ### Auto-Layout (dagre)
922
+
923
+ ```typescript
924
+ // lib/auto-layout.ts
925
+ import dagre from '@dagrejs/dagre';
926
+ import type { Node, Edge } from '@xyflow/react';
927
+
928
+ /**
929
+ * Auto-layout tables using dagre graph algorithm.
930
+ * ChartDB and Liam ERD both use dagre for auto-layout.
931
+ */
932
+ export function autoLayout(
933
+ nodes: Node[],
934
+ edges: Edge[],
935
+ direction: 'TB' | 'LR' = 'LR',
936
+ ): Node[] {
937
+ const g = new dagre.graphlib.Graph();
938
+ g.setDefaultEdgeLabel(() => ({}));
939
+ g.setGraph({
940
+ rankdir: direction,
941
+ nodesep: 60, // horizontal spacing
942
+ ranksep: 100, // vertical spacing between ranks
943
+ edgesep: 20,
944
+ marginx: 40,
945
+ marginy: 40,
946
+ });
947
+
948
+ // Estimate node dimensions (table height = header + rows * 28px)
949
+ for (const node of nodes) {
950
+ const colCount = node.data?.table?.columns?.length ?? 4;
951
+ const height = 40 + colCount * 28; // header + rows
952
+ g.setNode(node.id, { width: 240, height });
953
+ }
954
+
955
+ for (const edge of edges) {
956
+ g.setEdge(edge.source, edge.target);
957
+ }
958
+
959
+ dagre.layout(g);
960
+
961
+ return nodes.map(node => {
962
+ const pos = g.node(node.id);
963
+ return {
964
+ ...node,
965
+ position: { x: pos.x - 120, y: pos.y - (pos.height / 2) },
966
+ };
967
+ });
968
+ }
969
+ ```
970
+
971
+ ### Performance: Virtual Rendering for 100+ Tables
972
+
973
+ JointJS insight: when table count exceeds ~50, render only visible nodes. React Flow v12 handles this natively with `nodeExtent` and built-in viewport culling. Additional optimizations:
974
+
975
+ ```typescript
976
+ // Performance patterns for large schemas
977
+
978
+ // 1. Memoize node components (already done with memo())
979
+ // 2. Use nodeTypes/edgeTypes outside of the component to prevent re-registration
980
+ // 3. Debounce position sync (don't write to store on every pixel of drag)
981
+ import { useDebouncedCallback } from 'use-debounce';
982
+
983
+ const debouncedSync = useDebouncedCallback(() => {
984
+ useERDStore.getState().syncPositionsToDocument();
985
+ }, 200);
986
+
987
+ // 4. For 100+ tables, collapse columns by default and expand on click
988
+ // 5. Use React Flow's `hidden` prop to hide tables not matching a filter
989
+ // 6. Batch node updates: applyNodeChanges handles arrays efficiently
990
+ ```
991
+
992
+ ---
993
+
994
+ ## Parser/Generator Architecture (DrawDB Pattern)
995
+
996
+ DrawDB's architecture is the gold standard: separate parser and generator for each SQL dialect, all converting to/from a central JSON model. This enables round-tripping: `DDL -> JSON -> DDL` without information loss.
997
+
998
+ ```
999
+ +----------------+ +---------------+ +--------------------+
1000
+ | MySQL DDL |---->| |---->| MySQL DDL |
1001
+ +----------------+ | | +--------------------+
1002
+ +----------------+ | Central | +--------------------+
1003
+ | PostgreSQL |---->| JSON |---->| PostgreSQL DDL |
1004
+ +----------------+ | Model | +--------------------+
1005
+ +----------------+ | (ERDDocument) | +--------------------+
1006
+ | SQLite DDL |---->| |---->| SQLite DDL |
1007
+ +----------------+ | | +--------------------+
1008
+ +----------------+ | | +--------------------+
1009
+ | Prisma .psl |---->| |---->| DBML |
1010
+ +----------------+ +---------------+ +--------------------+
1011
+ ```
1012
+
1013
+ ### Parser Interface
1014
+
1015
+ ```typescript
1016
+ // lib/parsers/types.ts
1017
+
1018
+ /** All parsers convert their input format to ERDDocument */
1019
+ export interface DDLParser {
1020
+ dialect: SQLDialect;
1021
+ /** Parse DDL string into document model */
1022
+ parse(ddl: string): ERDDocument;
1023
+ /** Supported file extensions */
1024
+ extensions: string[];
1025
+ }
1026
+
1027
+ /** All generators convert ERDDocument to their output format */
1028
+ export interface DDLGenerator {
1029
+ dialect: SQLDialect;
1030
+ /** Generate DDL string from document model */
1031
+ generate(doc: ERDDocument): string;
1032
+ /** File extension for export */
1033
+ extension: string;
1034
+ }
1035
+ ```
1036
+
1037
+ ### PostgreSQL Parser (using node-sql-parser)
1038
+
1039
+ ```typescript
1040
+ // lib/parsers/postgres-parser.ts
1041
+ import { Parser } from 'node-sql-parser';
1042
+ import type { ERDDocument, Table, Column, Relationship } from '@/types/erd';
1043
+ import { nanoid } from 'nanoid';
1044
+
1045
+ const sqlParser = new Parser();
1046
+
1047
+ export function parsePostgres(ddl: string): Partial<ERDDocument> {
1048
+ const ast = sqlParser.astify(ddl, { database: 'PostgresQL' });
1049
+ const statements = Array.isArray(ast) ? ast : [ast];
1050
+
1051
+ const tables: Table[] = [];
1052
+ const relationships: Relationship[] = [];
1053
+
1054
+ for (const stmt of statements) {
1055
+ if (stmt.type !== 'create' || stmt.keyword !== 'table') continue;
1056
+
1057
+ const tableName = stmt.table?.[0]?.table ?? 'unknown';
1058
+ const tableId = nanoid();
1059
+ const columns: Column[] = [];
1060
+
1061
+ // Extract columns
1062
+ for (const def of stmt.create_definitions ?? []) {
1063
+ if (def.resource === 'column') {
1064
+ const colId = nanoid();
1065
+ columns.push({
1066
+ id: colId,
1067
+ name: def.column?.column ?? 'unknown',
1068
+ type: formatColumnType(def),
1069
+ isPrimaryKey: false, // Set below from constraints
1070
+ isForeignKey: false,
1071
+ isNullable: !hasConstraint(def, 'not null'),
1072
+ isUnique: hasConstraint(def, 'unique'),
1073
+ isAutoIncrement: hasConstraint(def, 'auto_increment'),
1074
+ defaultValue: extractDefault(def),
1075
+ });
1076
+ }
1077
+
1078
+ // PRIMARY KEY constraint
1079
+ if (def.resource === 'constraint' && def.constraint_type === 'primary key') {
1080
+ for (const keyCol of def.definition ?? []) {
1081
+ const col = columns.find(c => c.name === keyCol.column);
1082
+ if (col) col.isPrimaryKey = true;
1083
+ }
1084
+ }
1085
+
1086
+ // FOREIGN KEY constraint
1087
+ if (def.resource === 'constraint' && def.constraint_type === 'REFERENCES') {
1088
+ const sourceCol = columns.find(c => c.name === def.definition?.[0]?.column);
1089
+ if (sourceCol) {
1090
+ sourceCol.isForeignKey = true;
1091
+ // Relationship will be resolved after all tables are parsed
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ tables.push({
1097
+ id: tableId,
1098
+ name: tableName,
1099
+ columns,
1100
+ indexes: [],
1101
+ });
1102
+ }
1103
+
1104
+ // Second pass: resolve FK relationships across tables
1105
+ resolveRelationships(tables, statements, relationships);
1106
+
1107
+ return { tables, relationships };
1108
+ }
1109
+
1110
+ function formatColumnType(def: any): string {
1111
+ const dt = def.definition?.dataType ?? 'TEXT';
1112
+ const length = def.definition?.length;
1113
+ return length ? `${dt}(${length})` : dt;
1114
+ }
1115
+
1116
+ function hasConstraint(def: any, type: string): boolean {
1117
+ return def.definition?.constraint?.some?.((c: any) =>
1118
+ c.type?.toLowerCase() === type
1119
+ ) ?? false;
1120
+ }
1121
+
1122
+ function extractDefault(def: any): string | undefined {
1123
+ const d = def.definition?.constraint?.find?.((c: any) => c.type === 'default');
1124
+ return d?.value?.value?.toString();
1125
+ }
1126
+
1127
+ function resolveRelationships(
1128
+ tables: Table[],
1129
+ statements: any[],
1130
+ relationships: Relationship[]
1131
+ ): void {
1132
+ // Build name -> id lookup
1133
+ const tableByName = new Map(tables.map(t => [t.name, t]));
1134
+
1135
+ for (const stmt of statements) {
1136
+ if (stmt.type !== 'create' || stmt.keyword !== 'table') continue;
1137
+ const srcTableName = stmt.table?.[0]?.table;
1138
+ const srcTable = tableByName.get(srcTableName);
1139
+ if (!srcTable) continue;
1140
+
1141
+ for (const def of stmt.create_definitions ?? []) {
1142
+ if (def.resource === 'constraint' && def.constraint_type === 'REFERENCES') {
1143
+ const srcColName = def.definition?.[0]?.column;
1144
+ const tgtTableName = def.reference_definition?.table?.[0]?.table;
1145
+ const tgtColName = def.reference_definition?.definition?.[0]?.column;
1146
+
1147
+ const tgtTable = tableByName.get(tgtTableName);
1148
+ const srcCol = srcTable.columns.find(c => c.name === srcColName);
1149
+ const tgtCol = tgtTable?.columns.find(c => c.name === tgtColName);
1150
+
1151
+ if (tgtTable && srcCol && tgtCol) {
1152
+ srcCol.references = { tableId: tgtTable.id, columnId: tgtCol.id };
1153
+ relationships.push({
1154
+ id: nanoid(),
1155
+ name: def.constraint ?? `fk_${srcTableName}_${srcColName}`,
1156
+ sourceTableId: srcTable.id,
1157
+ sourceColumnId: srcCol.id,
1158
+ targetTableId: tgtTable.id,
1159
+ targetColumnId: tgtCol.id,
1160
+ sourceCardinality: 'zero-or-many',
1161
+ targetCardinality: 'exactly-one',
1162
+ onDelete: extractAction(def, 'on_delete') ?? 'NO ACTION',
1163
+ onUpdate: extractAction(def, 'on_update') ?? 'NO ACTION',
1164
+ });
1165
+ }
1166
+ }
1167
+ }
1168
+ }
1169
+ }
1170
+
1171
+ function extractAction(def: any, key: string): ReferentialAction | undefined {
1172
+ const action = def.reference_definition?.[key];
1173
+ if (!action) return undefined;
1174
+ return action.toUpperCase().replace(' ', '_') as ReferentialAction;
1175
+ }
1176
+ ```
1177
+
1178
+ ### PostgreSQL Generator
1179
+
1180
+ ```typescript
1181
+ // lib/generators/postgres-generator.ts
1182
+ import type { ERDDocument, Table, Column, Relationship } from '@/types/erd';
1183
+
1184
+ export function generatePostgres(doc: ERDDocument): string {
1185
+ const lines: string[] = [];
1186
+ lines.push('-- Generated by ERD Editor');
1187
+ lines.push(`-- Dialect: PostgreSQL`);
1188
+ lines.push(`-- Date: ${new Date().toISOString()}\n`);
1189
+
1190
+ for (const table of doc.tables) {
1191
+ lines.push(generateCreateTable(table));
1192
+ lines.push('');
1193
+ }
1194
+
1195
+ // Foreign key constraints as ALTER TABLE (safer for circular references)
1196
+ for (const rel of doc.relationships) {
1197
+ const srcTable = doc.tables.find(t => t.id === rel.sourceTableId);
1198
+ const tgtTable = doc.tables.find(t => t.id === rel.targetTableId);
1199
+ const srcCol = srcTable?.columns.find(c => c.id === rel.sourceColumnId);
1200
+ const tgtCol = tgtTable?.columns.find(c => c.id === rel.targetColumnId);
1201
+ if (srcTable && tgtTable && srcCol && tgtCol) {
1202
+ lines.push(
1203
+ `ALTER TABLE "${srcTable.name}" ADD CONSTRAINT "${rel.name ?? `fk_${srcTable.name}_${srcCol.name}`}"`,
1204
+ ` FOREIGN KEY ("${srcCol.name}") REFERENCES "${tgtTable.name}" ("${tgtCol.name}")`,
1205
+ ` ON DELETE ${rel.onDelete} ON UPDATE ${rel.onUpdate};`,
1206
+ ''
1207
+ );
1208
+ }
1209
+ }
1210
+
1211
+ return lines.join('\n');
1212
+ }
1213
+
1214
+ function generateCreateTable(table: Table): string {
1215
+ const lines: string[] = [];
1216
+ lines.push(`CREATE TABLE "${table.name}" (`);
1217
+
1218
+ const colDefs: string[] = table.columns.map(col => {
1219
+ const parts: string[] = [` "${col.name}"`];
1220
+ parts.push(col.type);
1221
+ if (!col.isNullable) parts.push('NOT NULL');
1222
+ if (col.isUnique && !col.isPrimaryKey) parts.push('UNIQUE');
1223
+ if (col.defaultValue) parts.push(`DEFAULT ${col.defaultValue}`);
1224
+ return parts.join(' ');
1225
+ });
1226
+
1227
+ // Primary key constraint
1228
+ const pkCols = table.columns.filter(c => c.isPrimaryKey);
1229
+ if (pkCols.length > 0) {
1230
+ colDefs.push(
1231
+ ` PRIMARY KEY (${pkCols.map(c => `"${c.name}"`).join(', ')})`
1232
+ );
1233
+ }
1234
+
1235
+ lines.push(colDefs.join(',\n'));
1236
+ lines.push(');');
1237
+
1238
+ // Table comment
1239
+ if (table.comment) {
1240
+ lines.push(`COMMENT ON TABLE "${table.name}" IS '${table.comment.replace(/'/g, "''")}';`);
1241
+ }
1242
+
1243
+ return lines.join('\n');
1244
+ }
1245
+ ```
1246
+
1247
+ ### MySQL Generator (dialect differences)
1248
+
1249
+ ```typescript
1250
+ // lib/generators/mysql-generator.ts
1251
+ import type { ERDDocument, Table } from '@/types/erd';
1252
+
1253
+ export function generateMySql(doc: ERDDocument): string {
1254
+ const lines: string[] = [];
1255
+ lines.push('-- Generated by ERD Editor');
1256
+ lines.push(`-- Dialect: MySQL\n`);
1257
+
1258
+ for (const table of doc.tables) {
1259
+ lines.push(generateMySqlTable(table));
1260
+ lines.push('');
1261
+ }
1262
+
1263
+ return lines.join('\n');
1264
+ }
1265
+
1266
+ function generateMySqlTable(table: Table): string {
1267
+ const lines: string[] = [];
1268
+ // MySQL: backtick quoting, no schema prefix, ENGINE specification
1269
+ lines.push(`CREATE TABLE \`${table.name}\` (`);
1270
+
1271
+ const colDefs: string[] = table.columns.map(col => {
1272
+ const parts: string[] = [` \`${col.name}\``];
1273
+ // Type mapping: PostgreSQL -> MySQL
1274
+ parts.push(pgTypeToMySQL(col.type));
1275
+ if (col.isAutoIncrement) parts.push('AUTO_INCREMENT');
1276
+ if (!col.isNullable) parts.push('NOT NULL');
1277
+ if (col.isUnique && !col.isPrimaryKey) parts.push('UNIQUE');
1278
+ if (col.defaultValue) parts.push(`DEFAULT ${mysqlDefault(col.defaultValue)}`);
1279
+ return parts.join(' ');
1280
+ });
1281
+
1282
+ const pkCols = table.columns.filter(c => c.isPrimaryKey);
1283
+ if (pkCols.length > 0) {
1284
+ colDefs.push(
1285
+ ` PRIMARY KEY (${pkCols.map(c => `\`${c.name}\``).join(', ')})`
1286
+ );
1287
+ }
1288
+
1289
+ lines.push(colDefs.join(',\n'));
1290
+ lines.push(') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;');
1291
+
1292
+ return lines.join('\n');
1293
+ }
1294
+
1295
+ /** Map PostgreSQL types to MySQL equivalents */
1296
+ function pgTypeToMySQL(pgType: string): string {
1297
+ const map: Record<string, string> = {
1298
+ 'UUID': 'CHAR(36)',
1299
+ 'TIMESTAMPTZ': 'DATETIME',
1300
+ 'TIMESTAMP WITH TIME ZONE': 'DATETIME',
1301
+ 'TEXT': 'TEXT',
1302
+ 'JSONB': 'JSON',
1303
+ 'BOOLEAN': 'TINYINT(1)',
1304
+ 'SERIAL': 'INT',
1305
+ 'BIGSERIAL': 'BIGINT',
1306
+ 'REAL': 'FLOAT',
1307
+ 'DOUBLE PRECISION': 'DOUBLE',
1308
+ };
1309
+ const upper = pgType.toUpperCase();
1310
+ return map[upper] ?? pgType;
1311
+ }
1312
+
1313
+ function mysqlDefault(value: string): string {
1314
+ // Convert PG functions to MySQL equivalents
1315
+ if (value === 'gen_random_uuid()') return '(UUID())';
1316
+ if (value === 'NOW()') return 'CURRENT_TIMESTAMP';
1317
+ return value;
1318
+ }
1319
+ ```
1320
+
1321
+ ### Generator Registry
1322
+
1323
+ ```typescript
1324
+ // lib/generators/index.ts
1325
+ import { generatePostgres } from './postgres-generator';
1326
+ import { generateMySql } from './mysql-generator';
1327
+ import type { ERDDocument, SQLDialect } from '@/types/erd';
1328
+
1329
+ type GeneratorFn = (doc: ERDDocument) => string;
1330
+
1331
+ const generators: Record<SQLDialect, GeneratorFn> = {
1332
+ postgresql: generatePostgres,
1333
+ mysql: generateMySql,
1334
+ sqlite: generateSqlite, // Same pattern, different type mapping
1335
+ mariadb: generateMySql, // MariaDB is MySQL-compatible with minor differences
1336
+ mssql: generateMssql, // T-SQL square bracket quoting, IDENTITY instead of AUTO_INCREMENT
1337
+ };
1338
+
1339
+ export function generateDDL(doc: ERDDocument, dialect?: SQLDialect): string {
1340
+ const target = dialect ?? doc.dialect;
1341
+ const gen = generators[target];
1342
+ if (!gen) throw new Error(`No generator for dialect: ${target}`);
1343
+ return gen(doc);
1344
+ }
1345
+ ```
1346
+
1347
+ ---
1348
+
1349
+ ## Import Flows
1350
+
1351
+ ### DDL Import (node-sql-parser)
1352
+
1353
+ ```typescript
1354
+ // lib/import/ddl-import.ts
1355
+ import { parsePostgres } from '@/lib/parsers/postgres-parser';
1356
+ import type { ERDDocument, SQLDialect } from '@/types/erd';
1357
+ import { autoLayout } from '@/lib/auto-layout';
1358
+ import { useERDStore } from '@/store/erd-store';
1359
+
1360
+ /**
1361
+ * Import DDL string: parse -> generate positions -> load into store.
1362
+ * Flow: SQL DDL -> node-sql-parser AST -> ERDDocument -> React Flow nodes/edges
1363
+ */
1364
+ export async function importDDL(ddl: string, dialect: SQLDialect): Promise<void> {
1365
+ const parsers: Record<string, (ddl: string) => Partial<ERDDocument>> = {
1366
+ postgresql: parsePostgres,
1367
+ mysql: parseMySql,
1368
+ sqlite: parseSqlite,
1369
+ };
1370
+
1371
+ const parser = parsers[dialect];
1372
+ if (!parser) throw new Error(`No parser for dialect: ${dialect}`);
1373
+
1374
+ const partial = parser(ddl);
1375
+ const doc: ERDDocument = {
1376
+ version: '1.0.0',
1377
+ name: 'Imported Schema',
1378
+ notation: 'crowsfoot',
1379
+ dialect,
1380
+ createdAt: new Date().toISOString(),
1381
+ updatedAt: new Date().toISOString(),
1382
+ tables: partial.tables ?? [],
1383
+ relationships: partial.relationships ?? [],
1384
+ diagram: {
1385
+ positions: {},
1386
+ viewport: { x: 0, y: 0, zoom: 1 },
1387
+ gridSize: 20,
1388
+ snapToGrid: true,
1389
+ },
1390
+ };
1391
+
1392
+ // Auto-layout since imported DDL has no position data
1393
+ const store = useERDStore.getState();
1394
+ store.loadDocument(doc);
1395
+ const laid = autoLayout(store.nodes, store.edges);
1396
+ // Write positions back
1397
+ for (const node of laid) {
1398
+ doc.diagram.positions[node.id] = node.position;
1399
+ }
1400
+ store.loadDocument(doc);
1401
+ }
1402
+ ```
1403
+
1404
+ ### Schema Import (ChartDB "Smart Query" Pattern)
1405
+
1406
+ ChartDB's killer feature: paste a single SQL query against your live database, get the full schema as JSON. The query uses `information_schema` to extract tables, columns, constraints, and relationships in one shot.
1407
+
1408
+ ```sql
1409
+ -- PostgreSQL "Smart Query" — paste into any SQL client connected to your DB
1410
+ -- Returns full schema as JSON
1411
+ SELECT json_build_object(
1412
+ 'tables', (
1413
+ SELECT json_agg(json_build_object(
1414
+ 'name', t.table_name,
1415
+ 'schema', t.table_schema,
1416
+ 'columns', (
1417
+ SELECT json_agg(json_build_object(
1418
+ 'name', c.column_name,
1419
+ 'type', c.data_type ||
1420
+ CASE WHEN c.character_maximum_length IS NOT NULL
1421
+ THEN '(' || c.character_maximum_length || ')'
1422
+ ELSE '' END,
1423
+ 'isNullable', c.is_nullable = 'YES',
1424
+ 'default', c.column_default,
1425
+ 'isPrimaryKey', EXISTS (
1426
+ SELECT 1 FROM information_schema.key_column_usage kcu
1427
+ JOIN information_schema.table_constraints tc
1428
+ ON tc.constraint_name = kcu.constraint_name
1429
+ WHERE tc.constraint_type = 'PRIMARY KEY'
1430
+ AND kcu.table_name = c.table_name
1431
+ AND kcu.column_name = c.column_name
1432
+ )
1433
+ ) ORDER BY c.ordinal_position)
1434
+ FROM information_schema.columns c
1435
+ WHERE c.table_name = t.table_name
1436
+ AND c.table_schema = t.table_schema
1437
+ ),
1438
+ 'foreignKeys', (
1439
+ SELECT json_agg(json_build_object(
1440
+ 'column', kcu.column_name,
1441
+ 'referencedTable', ccu.table_name,
1442
+ 'referencedColumn', ccu.column_name
1443
+ ))
1444
+ FROM information_schema.key_column_usage kcu
1445
+ JOIN information_schema.table_constraints tc
1446
+ ON tc.constraint_name = kcu.constraint_name
1447
+ JOIN information_schema.constraint_column_usage ccu
1448
+ ON ccu.constraint_name = tc.constraint_name
1449
+ WHERE tc.constraint_type = 'FOREIGN KEY'
1450
+ AND kcu.table_name = t.table_name
1451
+ AND kcu.table_schema = t.table_schema
1452
+ )
1453
+ ))
1454
+ FROM information_schema.tables t
1455
+ WHERE t.table_schema = 'public'
1456
+ AND t.table_type = 'BASE TABLE'
1457
+ )
1458
+ ) AS schema_json;
1459
+ ```
1460
+
1461
+ ```sql
1462
+ -- MySQL equivalent "Smart Query"
1463
+ SELECT JSON_OBJECT(
1464
+ 'tables', (
1465
+ SELECT JSON_ARRAYAGG(JSON_OBJECT(
1466
+ 'name', t.TABLE_NAME,
1467
+ 'columns', (
1468
+ SELECT JSON_ARRAYAGG(JSON_OBJECT(
1469
+ 'name', c.COLUMN_NAME,
1470
+ 'type', c.COLUMN_TYPE,
1471
+ 'isNullable', c.IS_NULLABLE = 'YES',
1472
+ 'default', c.COLUMN_DEFAULT,
1473
+ 'isPrimaryKey', c.COLUMN_KEY = 'PRI'
1474
+ ) ORDER BY c.ORDINAL_POSITION)
1475
+ FROM information_schema.COLUMNS c
1476
+ WHERE c.TABLE_NAME = t.TABLE_NAME
1477
+ AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
1478
+ )
1479
+ ))
1480
+ FROM information_schema.TABLES t
1481
+ WHERE t.TABLE_SCHEMA = DATABASE()
1482
+ AND t.TABLE_TYPE = 'BASE TABLE'
1483
+ )
1484
+ ) AS schema_json;
1485
+ ```
1486
+
1487
+ ### File Import (Prisma Schema)
1488
+
1489
+ ```typescript
1490
+ // lib/import/prisma-import.ts
1491
+ // Liam ERD pattern: parse Prisma .prisma files into ERDDocument
1492
+ import { nanoid } from 'nanoid';
1493
+ import type { ERDDocument, Table, Column, Relationship } from '@/types/erd';
1494
+
1495
+ /**
1496
+ * Minimal Prisma parser — extracts models and relations.
1497
+ * For production, use @mrleebo/prisma-ast or prisma-schema-parser.
1498
+ */
1499
+ export function parsePrismaSchema(schema: string): Partial<ERDDocument> {
1500
+ const tables: Table[] = [];
1501
+ const relationships: Relationship[] = [];
1502
+
1503
+ const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
1504
+ let match;
1505
+
1506
+ while ((match = modelRegex.exec(schema)) !== null) {
1507
+ const modelName = match[1];
1508
+ const body = match[2];
1509
+ const tableId = nanoid();
1510
+ const columns: Column[] = [];
1511
+
1512
+ for (const line of body.split('\n')) {
1513
+ const trimmed = line.trim();
1514
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('@@')) continue;
1515
+
1516
+ // Field line: name Type modifiers
1517
+ const fieldMatch = trimmed.match(/^(\w+)\s+(\w+)(\?)?(\[\])?\s*(.*)/);
1518
+ if (!fieldMatch) continue;
1519
+
1520
+ const [, name, type, optional, array, modifiers] = fieldMatch;
1521
+
1522
+ // Skip relation fields (they have @relation)
1523
+ if (modifiers.includes('@relation')) continue;
1524
+
1525
+ // Skip Prisma-only types (other model references without @relation)
1526
+ const prismaTypes = new Set([
1527
+ 'String', 'Int', 'Float', 'Boolean', 'DateTime',
1528
+ 'Json', 'BigInt', 'Decimal', 'Bytes',
1529
+ ]);
1530
+ if (!prismaTypes.has(type)) continue;
1531
+
1532
+ columns.push({
1533
+ id: nanoid(),
1534
+ name: extractMapName(modifiers) ?? name,
1535
+ type: prismaTypeToSQL(type),
1536
+ isPrimaryKey: modifiers.includes('@id'),
1537
+ isForeignKey: false,
1538
+ isNullable: !!optional,
1539
+ isUnique: modifiers.includes('@unique'),
1540
+ isAutoIncrement: modifiers.includes('autoincrement()'),
1541
+ defaultValue: extractPrismaDefault(modifiers),
1542
+ });
1543
+ }
1544
+
1545
+ tables.push({
1546
+ id: tableId,
1547
+ name: extractTableMap(body) ?? modelName.toLowerCase() + 's',
1548
+ columns,
1549
+ indexes: [],
1550
+ });
1551
+ }
1552
+
1553
+ return { tables, relationships };
1554
+ }
1555
+
1556
+ function prismaTypeToSQL(prismaType: string): string {
1557
+ const map: Record<string, string> = {
1558
+ 'String': 'TEXT',
1559
+ 'Int': 'INTEGER',
1560
+ 'Float': 'REAL',
1561
+ 'Boolean': 'BOOLEAN',
1562
+ 'DateTime': 'TIMESTAMPTZ',
1563
+ 'Json': 'JSONB',
1564
+ 'BigInt': 'BIGINT',
1565
+ 'Decimal': 'DECIMAL',
1566
+ 'Bytes': 'BYTEA',
1567
+ };
1568
+ return map[prismaType] ?? 'TEXT';
1569
+ }
1570
+
1571
+ function extractMapName(modifiers: string): string | undefined {
1572
+ const match = modifiers.match(/@map\("(\w+)"\)/);
1573
+ return match?.[1];
1574
+ }
1575
+
1576
+ function extractTableMap(body: string): string | undefined {
1577
+ const match = body.match(/@@map\("(\w+)"\)/);
1578
+ return match?.[1];
1579
+ }
1580
+
1581
+ function extractPrismaDefault(modifiers: string): string | undefined {
1582
+ const match = modifiers.match(/@default\(([^)]+)\)/);
1583
+ if (!match) return undefined;
1584
+ const val = match[1];
1585
+ if (val === 'now()') return 'NOW()';
1586
+ if (val === 'cuid()') return 'gen_random_uuid()';
1587
+ if (val === 'uuid()') return 'gen_random_uuid()';
1588
+ if (val === 'autoincrement()') return undefined; // Handled by isAutoIncrement
1589
+ return val;
1590
+ }
1591
+ ```
1592
+
1593
+ ---
1594
+
1595
+ ## Export Flows
1596
+
1597
+ ### DDL Export (Multi-Dialect)
1598
+
1599
+ ```typescript
1600
+ // Already covered in Generator Registry above.
1601
+ // Usage from UI:
1602
+ function handleExportDDL(dialect: SQLDialect) {
1603
+ const doc = useERDStore.getState().toDocument();
1604
+ const ddl = generateDDL(doc, dialect);
1605
+
1606
+ // Desktop (Tauri): save to file
1607
+ // Web: download as .sql file
1608
+ downloadFile(ddl, `${doc.name}.sql`, 'text/sql');
1609
+ }
1610
+ ```
1611
+
1612
+ ### Image Export (PNG/SVG)
1613
+
1614
+ ```typescript
1615
+ // lib/export/image-export.ts
1616
+ import { toPng, toSvg } from '@xyflow/react';
1617
+
1618
+ export async function exportAsPng(filename: string): Promise<void> {
1619
+ const dataUrl = await toPng(
1620
+ document.querySelector('.react-flow') as HTMLElement,
1621
+ {
1622
+ backgroundColor: '#ffffff',
1623
+ quality: 1,
1624
+ width: 4096, // High-res export
1625
+ height: 2048,
1626
+ }
1627
+ );
1628
+ downloadDataUrl(dataUrl, `${filename}.png`);
1629
+ }
1630
+
1631
+ export async function exportAsSvg(filename: string): Promise<void> {
1632
+ const svg = await toSvg(
1633
+ document.querySelector('.react-flow') as HTMLElement,
1634
+ { backgroundColor: '#ffffff' }
1635
+ );
1636
+ downloadDataUrl(svg, `${filename}.svg`);
1637
+ }
1638
+
1639
+ function downloadDataUrl(dataUrl: string, filename: string): void {
1640
+ const link = document.createElement('a');
1641
+ link.href = dataUrl;
1642
+ link.download = filename;
1643
+ link.click();
1644
+ }
1645
+ ```
1646
+
1647
+ ### DBML Export (dbdiagram.io interop)
1648
+
1649
+ ```typescript
1650
+ // lib/export/dbml-export.ts
1651
+ import type { ERDDocument, Cardinality } from '@/types/erd';
1652
+
1653
+ export function generateDBML(doc: ERDDocument): string {
1654
+ const lines: string[] = [];
1655
+
1656
+ for (const table of doc.tables) {
1657
+ lines.push(`Table ${table.name} {`);
1658
+ for (const col of table.columns) {
1659
+ const attrs: string[] = [];
1660
+ if (col.isPrimaryKey) attrs.push('pk');
1661
+ if (!col.isNullable) attrs.push('not null');
1662
+ if (col.isUnique) attrs.push('unique');
1663
+ if (col.defaultValue) attrs.push(`default: '${col.defaultValue}'`);
1664
+ if (col.comment) attrs.push(`note: '${col.comment}'`);
1665
+ const attrStr = attrs.length > 0 ? ` [${attrs.join(', ')}]` : '';
1666
+ lines.push(` ${col.name} ${col.type}${attrStr}`);
1667
+ }
1668
+ lines.push('}\n');
1669
+ }
1670
+
1671
+ // Relationships
1672
+ for (const rel of doc.relationships) {
1673
+ const srcTable = doc.tables.find(t => t.id === rel.sourceTableId);
1674
+ const tgtTable = doc.tables.find(t => t.id === rel.targetTableId);
1675
+ const srcCol = srcTable?.columns.find(c => c.id === rel.sourceColumnId);
1676
+ const tgtCol = tgtTable?.columns.find(c => c.id === rel.targetColumnId);
1677
+ if (srcTable && tgtTable && srcCol && tgtCol) {
1678
+ const symbol = cardinalityToDBML(rel.sourceCardinality, rel.targetCardinality);
1679
+ lines.push(`Ref: ${srcTable.name}.${srcCol.name} ${symbol} ${tgtTable.name}.${tgtCol.name}`);
1680
+ }
1681
+ }
1682
+
1683
+ return lines.join('\n');
1684
+ }
1685
+
1686
+ function cardinalityToDBML(source: Cardinality, target: Cardinality): string {
1687
+ // DBML uses: - (one-to-one), < (one-to-many), > (many-to-one), <> (many-to-many)
1688
+ if (target === 'zero-or-many' || target === 'one-or-many') return '<';
1689
+ if (source === 'zero-or-many' || source === 'one-or-many') return '>';
1690
+ return '-';
1691
+ }
1692
+ ```
1693
+
1694
+ ---
1695
+
1696
+ ## MCP Integration (ERFlow Pattern)
1697
+
1698
+ ERFlow exposes 25+ MCP tools for natural language schema editing from CLI/IDE. Implement the same pattern for the ERD editor:
1699
+
1700
+ ```typescript
1701
+ // mcp/erd-mcp-tools.ts
1702
+ // Register as MCP tools via the Claude MCP protocol
1703
+
1704
+ export const erdMCPTools = {
1705
+ 'erd.addTable': {
1706
+ description: 'Add a new table to the ERD',
1707
+ parameters: {
1708
+ name: { type: 'string', description: 'Table name' },
1709
+ columns: { type: 'array', description: 'Column definitions' },
1710
+ },
1711
+ handler: async ({ name, columns }: { name: string; columns: any[] }) => {
1712
+ const table = createTableFromNL(name, columns);
1713
+ useERDStore.getState().addTable(table);
1714
+ return { success: true, tableId: table.id };
1715
+ },
1716
+ },
1717
+
1718
+ 'erd.addRelationship': {
1719
+ description: 'Add a FK relationship between two tables',
1720
+ parameters: {
1721
+ from: { type: 'string', description: 'Source table.column' },
1722
+ to: { type: 'string', description: 'Target table.column' },
1723
+ cardinality: { type: 'string', description: 'one-to-one, one-to-many, many-to-many' },
1724
+ },
1725
+ handler: async ({ from, to, cardinality }: {
1726
+ from: string; to: string; cardinality: string;
1727
+ }) => {
1728
+ const [srcTable, srcCol] = resolveTableColumn(from);
1729
+ const [tgtTable, tgtCol] = resolveTableColumn(to);
1730
+ const { source, target } = mapNLCardinality(cardinality);
1731
+ useERDStore.getState().addRelationship({
1732
+ sourceHandle: `${srcTable.id}.${srcCol.id}-source`,
1733
+ targetHandle: `${tgtTable.id}.${tgtCol.id}-target`,
1734
+ });
1735
+ return { success: true };
1736
+ },
1737
+ },
1738
+
1739
+ 'erd.exportDDL': {
1740
+ description: 'Export the current ERD as SQL DDL',
1741
+ parameters: {
1742
+ dialect: { type: 'string', enum: ['postgresql', 'mysql', 'sqlite'] },
1743
+ },
1744
+ handler: async ({ dialect }: { dialect: SQLDialect }) => {
1745
+ const doc = useERDStore.getState().toDocument();
1746
+ return { ddl: generateDDL(doc, dialect) };
1747
+ },
1748
+ },
1749
+
1750
+ 'erd.importDDL': {
1751
+ description: 'Import SQL DDL into the ERD',
1752
+ parameters: {
1753
+ sql: { type: 'string' },
1754
+ dialect: { type: 'string' },
1755
+ },
1756
+ handler: async ({ sql, dialect }: { sql: string; dialect: SQLDialect }) => {
1757
+ await importDDL(sql, dialect);
1758
+ return {
1759
+ success: true,
1760
+ tableCount: useERDStore.getState().document.tables.length,
1761
+ };
1762
+ },
1763
+ },
1764
+
1765
+ // Additional tools following ERFlow pattern:
1766
+ // erd.renameTable, erd.renameColumn, erd.addColumn, erd.removeColumn,
1767
+ // erd.changeColumnType, erd.setNullable, erd.addIndex,
1768
+ // erd.generateMigration (checkpoint-based diff)
1769
+ };
1770
+ ```
1771
+
1772
+ ---
1773
+
1774
+ ## Persistence (Tauri)
1775
+
1776
+ ### Desktop: File System Save/Load
1777
+
1778
+ ```typescript
1779
+ // lib/persistence/tauri-fs.ts
1780
+ import { save, open } from '@tauri-apps/plugin-dialog';
1781
+ import { writeTextFile, readTextFile } from '@tauri-apps/plugin-fs';
1782
+ import { appDataDir } from '@tauri-apps/api/path';
1783
+ import type { ERDDocument } from '@/types/erd';
1784
+ import { useERDStore } from '@/store/erd-store';
1785
+
1786
+ const ERD_FILTER = {
1787
+ name: 'ERD Files',
1788
+ extensions: ['erd.json'],
1789
+ };
1790
+
1791
+ export async function saveDocument(): Promise<void> {
1792
+ const doc = useERDStore.getState().toDocument();
1793
+ const filePath = await save({
1794
+ defaultPath: `${doc.name}.erd.json`,
1795
+ filters: [ERD_FILTER],
1796
+ });
1797
+ if (filePath) {
1798
+ await writeTextFile(filePath, JSON.stringify(doc, null, 2));
1799
+ }
1800
+ }
1801
+
1802
+ export async function openDocument(): Promise<void> {
1803
+ const filePath = await open({
1804
+ filters: [ERD_FILTER],
1805
+ multiple: false,
1806
+ });
1807
+ if (filePath) {
1808
+ const content = await readTextFile(filePath as string);
1809
+ const doc: ERDDocument = JSON.parse(content);
1810
+ useERDStore.getState().loadDocument(doc);
1811
+ }
1812
+ }
1813
+
1814
+ /** Auto-save every 30 seconds to a temp file */
1815
+ export function startAutoSave(intervalMs = 30_000): () => void {
1816
+ const timer = setInterval(async () => {
1817
+ const doc = useERDStore.getState().toDocument();
1818
+ const tempPath = `${await appDataDir()}/autosave.erd.json`;
1819
+ await writeTextFile(tempPath, JSON.stringify(doc));
1820
+ }, intervalMs);
1821
+ return () => clearInterval(timer);
1822
+ }
1823
+ ```
1824
+
1825
+ ### Web Fallback: Dexie.js (IndexedDB)
1826
+
1827
+ ```typescript
1828
+ // lib/persistence/dexie-store.ts
1829
+ // ChartDB uses Dexie.js for zero-backend persistence in the browser
1830
+ import Dexie, { type Table as DexieTable } from 'dexie';
1831
+ import type { ERDDocument } from '@/types/erd';
1832
+
1833
+ class ERDDatabase extends Dexie {
1834
+ documents!: DexieTable<ERDDocument & { id: string }, string>;
1835
+
1836
+ constructor() {
1837
+ super('erd-editor');
1838
+ this.version(1).stores({
1839
+ documents: 'id, name, updatedAt',
1840
+ });
1841
+ }
1842
+ }
1843
+
1844
+ export const db = new ERDDatabase();
1845
+
1846
+ export async function saveToIndexedDB(doc: ERDDocument): Promise<void> {
1847
+ await db.documents.put({ ...doc, id: doc.name });
1848
+ }
1849
+
1850
+ export async function loadFromIndexedDB(id: string): Promise<ERDDocument | undefined> {
1851
+ return db.documents.get(id);
1852
+ }
1853
+
1854
+ export async function listDocuments(): Promise<ERDDocument[]> {
1855
+ return db.documents.orderBy('updatedAt').reverse().toArray();
1856
+ }
1857
+ ```
1858
+
1859
+ ---
1860
+
1861
+ ## Project Structure
1862
+
1863
+ ```
1864
+ src/
1865
+ types/
1866
+ erd.ts # ERDDocument, Table, Column, Relationship types
1867
+ components/
1868
+ ERDCanvas.tsx # Main React Flow canvas wrapper
1869
+ nodes/
1870
+ TableNode.tsx # Crow's Foot table card with per-column handles
1871
+ ChenEntityNode.tsx # Chen rectangle entity
1872
+ ChenAttributeNode.tsx # Chen oval attribute
1873
+ ChenRelationshipNode.tsx # Chen diamond relationship
1874
+ edges/
1875
+ CrowsFootMarkers.tsx # SVG <defs> for 4 cardinality markers
1876
+ CrowsFootEdge.tsx # Custom edge with markerStart/markerEnd
1877
+ ChenEdge.tsx # Simple line with cardinality label
1878
+ panels/
1879
+ TableEditor.tsx # Side panel for editing table/column properties
1880
+ ImportDialog.tsx # DDL paste / file upload dialog
1881
+ ExportDialog.tsx # Dialect picker + DDL preview
1882
+ store/
1883
+ erd-store.ts # Zustand + zundo (undo/redo)
1884
+ lib/
1885
+ node-types.ts # Node type registry (Crow's Foot + Chen)
1886
+ edge-types.ts # Edge type registry
1887
+ auto-layout.ts # dagre-based auto-layout
1888
+ parsers/
1889
+ types.ts # DDLParser / DDLGenerator interfaces
1890
+ postgres-parser.ts # PG DDL -> ERDDocument
1891
+ mysql-parser.ts # MySQL DDL -> ERDDocument
1892
+ prisma-parser.ts # Prisma schema -> ERDDocument
1893
+ generators/
1894
+ index.ts # Generator registry + generateDDL()
1895
+ postgres-generator.ts # ERDDocument -> PG DDL
1896
+ mysql-generator.ts # ERDDocument -> MySQL DDL
1897
+ dbml-generator.ts # ERDDocument -> DBML
1898
+ import/
1899
+ ddl-import.ts # Orchestrates parse + auto-layout + load
1900
+ smart-query.ts # ChartDB-style information_schema queries
1901
+ export/
1902
+ image-export.ts # toPng() / toSvg()
1903
+ persistence/
1904
+ tauri-fs.ts # Tauri file system save/load/autosave
1905
+ dexie-store.ts # IndexedDB fallback for web
1906
+ mcp/
1907
+ erd-mcp-tools.ts # MCP tool definitions for NL editing
1908
+ ```
1909
+
1910
+ ---
1911
+
1912
+ ## Reference Implementations — Study Order
1913
+
1914
+ | Priority | Project | Why Study It | Key Files to Read |
1915
+ |----------|---------|-------------|-------------------|
1916
+ | 1 | **ChartDB** | Closest stack match (React + Vite + ReactFlow + shadcn + Dexie) | `src/pages/editor-page/`, `src/context/chartdb-context/` |
1917
+ | 2 | **DrawDB** | Gold-standard parser/generator architecture | `src/utils/importFrom/`, `src/utils/exportAs/` |
1918
+ | 3 | **React Flow DatabaseSchemaNode** | Official per-column handle pattern | `reactflow.dev/ui/components/database-schema-node` |
1919
+ | 4 | **NextERD** | Simplest React Flow + shadcn integration, good for learning | Full codebase (~small) |
1920
+ | 5 | **Liam ERD** | 100+ table performance, schema file importers | `src/` layout engine |
1921
+ | 6 | **dineug/erd-editor** | .erd.json format design, VS Code integration | Format spec |
1922
+ | 7 | **JointJS 4.2** | Virtual rendering, z-ordering for massive schemas | Docs: ERD shapes API |
1923
+ | 8 | **ERFlow** | MCP tool design patterns for NL schema editing | Tool definitions |
1924
+
1925
+ ---
1926
+
1927
+ ## When to Use
1928
+
1929
+ - Building a visual database schema editor with React
1930
+ - Need Crow's Foot or Chen notation rendering on a canvas
1931
+ - Importing existing schemas (DDL, Prisma, live database) into a visual editor
1932
+ - Exporting ERDs to multiple SQL dialects
1933
+ - Adding MCP/NL editing capabilities to a schema tool
1934
+
1935
+ ## When NOT to Use
1936
+
1937
+ - **Simple static ERD diagrams** — Use Mermaid `erDiagram` syntax instead (see `database-schema-designer.md`)
1938
+ - **Database migration tooling only** — Use Prisma Migrate, Drizzle Kit, or Alembic directly
1939
+ - **UML diagrams** — Different domain; use PlantUML or Excalidraw
1940
+ - **Existing app with JointJS** — JointJS 4.2 has built-in ERD primitives; do not add React Flow on top
1941
+
1942
+ ## Related Skills
1943
+
1944
+ - `er-diagram-components.md` — Canonical reference for all ER notation components (Chen, Crow's Foot, attribute types, cardinality rules). Read this first for theory.
1945
+ - `erd-creator-textbook-research.md` — Academic research findings behind these patterns
1946
+ - `database-schema-designer.md` — Schema design methodology (Prisma, Drizzle, RLS, seeds). Use for the data modeling side.
1947
+ - `reserved-word-context-aware-quoting.md` — Quoting rules for DDL generators
1948
+ - `regex-alternation-ordering-sql-types.md` — SQL type parsing edge cases
1949
+ - `postgresql-to-mysql-runtime-translation.md` — PG-to-MySQL type mapping reference
1950
+
1951
+ ## References
1952
+
1953
+ - [ChartDB](https://github.com/chartdb/chartdb) — MIT, React + Vite + ReactFlow + shadcn + Dexie.js
1954
+ - [DrawDB](https://github.com/drawdb-io/drawdb) — AGPL-3.0, ~24,400 stars, gold-standard DDL parsers
1955
+ - [Liam ERD](https://github.com/liam-hq/liam) — Apache-2.0, handles 100+ tables, CLI tool
1956
+ - [NextERD](https://github.com/vaxad/NextERD) — Next.js + React Flow + shadcn, small readable codebase
1957
+ - [dineug/erd-editor](https://github.com/dineug/erd-editor) — Web Component, VS Code + IntelliJ integration
1958
+ - [React Flow DatabaseSchemaNode](https://reactflow.dev/ui/components/database-schema-node) — Official per-column handle pattern
1959
+ - [relliv/crows-foot-notations](https://github.com/relliv/crows-foot-notations) — SVG symbol reference
1960
+ - [node-sql-parser](https://github.com/nicereporter/node-sql-parser) — Bidirectional SQL-AST, multi-dialect
1961
+ - [sql-ddl-to-json-schema](https://github.com/nicereporter/node-sql-parser) — DDL-focused, nearley grammar
1962
+ - [ERFlow](https://github.com/ageborn-dev/erflow) — MCP-based ERD editing, 25+ tools
1963
+ - [JointJS 4.2](https://www.jointjs.com/) — Built-in ERD shapes + Crow's Foot + virtual rendering
1964
+ - [zundo](https://github.com/charkour/zundo) — Undo/redo middleware for Zustand
1965
+ - [@dagrejs/dagre](https://github.com/dagrejs/dagre) — Directed graph auto-layout