视觉画布设计
canvas-design
基于设计原则创作静态视觉作品,用于海报与品牌图形。
schema-diagram【文档与办公】Generates a self-contained interactive ERD document from a Laravel project's live database — a single standalone HTML file with domain-coloured table cards, foreign-key edges anchored to the actual columns, search across tables and columns, a per-table inspector showing both directions of every relation, and a committed JSON schema payload that makes…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
project-laravel), domain boundary discovery (project-ddd), or the wider docs
tree (project-docs).Produces one file: docs/03-architecture/03-database-erd.html. It opens off a filesystem, on
a plane, with no network and no toolchain — and it draws every table, column, index and
foreign key in the database.
| Piece | Lives in | Needs | Committed |
|---|---|---|---|
| Schema extractor | app/Support/Erd/ErdSchema.php + app/Console/Commands/GenerateErdCommand.php | A migrated database | — |
| Domain map | app/Support/Erd/ErdDomains.php | A human | — |
| Payload | docs/03-architecture/erd-schema.json | — | Yes |
| Diagram app | resources/js/erd/ (React Flow island) | — | — |
| Coverage tests | tests/Feature/Docs/ErdDomainCoverageTest.php | — | — |
| Document | docs/03-architecture/03-database-erd.html | Node | Yes |
| Companion page | docs/03-architecture/03-database-erd.md | — | Yes |
php artisan docs:erd # database -> JSON (needs a DB, no Node)
npm run build:erd # JSON -> HTML (needs Node, no DB)
The split is the design. Either half runs alone, and the committed JSON means the document rebuilds on a machine that has never seen the database.
1. Read the database, never the migrations. Migrations are the instructions; only the database is the result. A mature project's migration folder is full of column changes, index swaps, renames and drops whose net effect is exactly what a parser gets wrong — silently.
2. Every table gets a domain, assigned by a person. A prefix heuristic mis-files tables and
gives no sign that it did. The generator refuses to write when the map has a gap in either
direction. That friction is the feature; do not add a default => arm to make it pass.
3. Commit both outputs. The JSON so schema drift shows up in a pull request diff. The HTML so a reader needs nothing but a browser.
php artisan migrate:status | tail -5 # is the schema at head?
A diagram of a half-migrated schema is worse than none. But do not migrate someone's dev database to build a document — that is a side effect they did not ask for, and it can be destructive. Build in a throwaway instead, and drop it:
mysql -e "CREATE DATABASE erd_tmp"
DB_DATABASE=erd_tmp php artisan migrate --force
DB_DATABASE=erd_tmp php artisan docs:erd
mysql -e "DROP DATABASE erd_tmp"
Say plainly in the summary that you did this and that their database is untouched. The About panel records the migration head it was read at, so the document proves its own provenance.
This is the only part that takes judgement, and everything else is mechanical once it exists.
Read references/domain-map.md. Draft it from the code, then correct it by hand:
ls database/migrations | sed 's/^[0-9_]*//;s/\.php$//' | sort
ls app/Models
Aim for 8–14 domains named after the product's own nouns — never layers. Framework and
package tables (cache, jobs, sessions, media, telescope_*) all go in one system
domain that starts switched off.
Show the user the proposed map before writing it. Getting deployment_mail_sandboxes filed
under Data Services rather than Deployment is a decision they own.
references/schema-extractor.md has both PHP files verbatim. Copy them, then run:
php artisan docs:erd
It will fail listing unmapped tables. That is the gate working — fill them in and re-run.
references/diagram-app.md (seven modules) and references/diagram-styles.md (the
stylesheet). Copy verbatim, then change exactly two things: PRODUCT in ErdBoard.jsx and
About.jsx, and the brand faces at the top of erd.css.
Rewrite the prose in About.jsx for the product. It is the only writing in the document
and the first thing anyone reads.
npm i @xyflow/react @dagrejs/dagre react react-dom
npm i -D @vitejs/plugin-react vite
npm run build:erd
open docs/03-architecture/03-database-erd.html
Verify before calling it done — see the checklist below.
A 1 MB generated HTML file is not readable in a pull request, does not appear in a grep, and
does not render on GitHub. Write docs/03-architecture/03-database-erd.md beside it: a link,
the four figures, the domain table with per-domain counts, what it does not draw, the two
regeneration commands, and a source-file table. That page is what the docs tree indexes and
what a reviewer actually reads.
references/coverage-tests.md. docs:erd --check protects the document; the suite protects
the repository — it fails on the pull request that adds the table, not six weeks later.
- run: php artisan docs:erd --check
php artisan migrate
php artisan docs:erd # fails if a new table has no domain — add it
npm run build:erd
php artisan test --filter=ErdDomainCoverage
git add docs/03-architecture/erd-schema.json docs/03-architecture/03-database-erd.html
Regenerating the JSON and forgetting the HTML leaves a document confidently showing last month's schema. Check both files are staged.
Open the file and confirm each of these. Every failure here renders successfully and is wrong, which is why the list exists.
width/height instead of initialWidth/initialHeight — React Flow skips
measurement, handle bounds never exist, and each edge is dropped in silence.ResizeObserver and rAF, so nodes stay hidden and fitView never lands — a healthy
page reports as broken, with exactly the symptoms of the bug above.useUpdateNodeInternals.stats.relations with all domains on.file://, with the network off. No console error, no blank canvas.auditable_type + auditable_id resolve at
runtime; an arrow to any one table is a claim the schema does not make.system starts off. Cache, queue, sessions and Telescope are plumbing, and most of them
join to nothing but fog.beyondcode/laravel-er-diagram-generator and friends read
Eloquent relations and emit a static image — no search, no inspector, no domains, and a
picture that cannot answer "what references this table".| Don't | Do |
|---|---|
Parse database/migrations | Introspect via Schema::getTables() |
| Infer domains from table-name prefixes | An explicit map, plus a build that fails on a gap |
default => 'system' in the domain map | Let the gate fail; add the table deliberately |
| Fetch the JSON at runtime | Inline it — file:// refuses cross-origin |
| Link a CDN or a webfont | Inline everything; declare fonts brand-first with a system fallback |
width/height on a node | initialWidth/initialHeight — the box is seeded and still measured |
| Migrate the user's dev DB to build a doc | Build in a throwaway database, drop it, say so |
fitView on a setTimeout | useNodesInitialized — the event, not a guess |
| Re-derive nodes when selection changes | Paint state onto the nodes already in state |
| Commit only the HTML | Commit the JSON too — it is the reviewable diff |
| Leave the HTML as the only doc | Write the companion .md — the HTML is not reviewable or greppable |
| Ship a static PNG/SVG "for simplicity" | It cannot answer the one question people open an ERD to ask |
| File | Contents |
|---|---|
references/schema-extractor.md | ErdSchema + GenerateErdCommand verbatim, the payload shape, and the five introspection traps (server-wide getTables(), sentinel migrations, MySQL type noise, morphs, FK-less tables) |
references/domain-map.md | ErdDomains template, how to choose 8–14 domains, the tested 13-colour palette, and the two-way coverage gate |
references/diagram-app.md | All seven resources/js/erd/ modules verbatim, the five silent React Flow failure modes, the browser-throttling trap, and the interaction model |
references/diagram-styles.md | erd.css verbatim — dark/light token blocks, node geometry that must agree with geometry.js |
references/build-pipeline.md | build.mjs verbatim, why one standalone file, the two escaping traps, dependencies, dev loop, and CI |
references/coverage-tests.md | The Pest suite that pins domain coverage, legend completeness, payload shape and morph handling — plus why it must filter on getCurrentSchemaName() to work on SQLite |