SequelPG

v0.3.0 was about what you can do inside the query editor. v0.3.2 was about getting data in and out of a database. v0.3.3 is about seeing the shape of a schema at a glance — a real entity-relationship diagram, not a static picture. A new Diagram tab (⌘5) draws the selected schema as cards you can arrange, with foreign keys routed as tidy right-angle connectors that go around your tables rather than through them.

A diagram you can actually arrange

Pick a schema from the in-tab picker and every base table becomes a card listing its columns — each with a primary-key or foreign-key marker and a nullability cue. Foreign keys are drawn as rounded right-angle connectors with an arrowhead pointing at the referenced (parent) table and a one-to-one / many-to-one cue inferred from the constraint shape. Hover a relationship line to highlight it when a dense schema gets busy.

The diagram fits to the window when you open the tab (and on demand via a fit button). From there it is fully interactive: drag tables to arrange them, pinch or use the zoom controls to scale between 10% and 250%, drag the background to pan, double-click (or right-click) a table to collapse it to just its header, and hide tables you don’t want to see — with a Show All action to bring them back.

Auto Layout: force-directed, but deterministic

The Auto Layout button runs a Fruchterman–Reingold force-directed placement: every pair of cards repels, and every foreign-key edge pulls its two tables together, so related tables cluster into neighbourhoods and relationship lines stay short and largely un-crossed. The catch with force-directed layouts is that they usually jitter — run them twice and you get two different pictures. This one is seeded from a degree-ordered grid (most-connected tables first, ties broken by name), so the same schema lays out the same way every time.

Two details keep it well-behaved. A gravity term pulls every node gently toward the centroid — without it, a table with no foreign keys feels only repulsion and drifts off to infinity (the fix ForceAtlas2 is known for) — and the whole simulation is confined to a bounded frame so nothing escapes. A final overlap-resolution pass separates any cards still touching by pushing them apart along their smaller overlap axis. All of it is pure geometry with no SwiftUI and no database, so the layout engine is unit-tested directly on plain node and edge values.

Lines that route around tables, not through them

The connectors are the interesting engineering. Instead of drawing a straight line between two cards (which would cut across whatever sits between them), each foreign key is routed by an A* search on a coordinate grid built from the corridors just outside every card edge — plus extra lane lines dropped into wide gaps so several parallel runs can spread out instead of overlapping. The search charges a turn penalty, so a route prefers long straight runs with few bends, and a crossing penalty for every already-routed line a candidate segment would cross.

Shortest connections route first and claim the direct paths; longer ones detour around them. Attach points are distributed along each card’s side and ordered by where their counterpart sits, so several lines leaving the same side fan out cleanly rather than stacking up and immediately crossing. The result is that a relationship line never passes through a table card, and crossings between lines are reduced (though, honestly, not always eliminated — that’s what dragging and hover-highlight are for). Routes are recomputed only when the geometry settles — load, auto-layout, collapse/hide, end of a drag — never on hover, selection, or panning, so interaction stays cheap; mid-drag the canvas falls back to a direct connector until you let go.

Your arrangement sticks

The layout you build is saved per connection and schema, as JSON under Application Support keyed by the profile’s UUID and the percent-encoded schema name. It’s file-based rather than UserDefaults on purpose: ConnectionStore is the only component in the app that owns defaults, and a per-schema layout can grow large enough that a defaults plist is the wrong home for it. Positions, collapsed state, and hidden state are written atomically and restored the next time you open that schema.

One deliberate exception: the viewport is not restored. The diagram re-fits to the window on every open, so a stale saved pan/zoom can never drop your schema off-screen where you can’t find it. And if a save ever fails, it’s logged, not thrown — the only consequence is that the next open falls back to auto-layout.

Export: PNG, SVG, PDF — the whole diagram

You can export the diagram three ways. PNG (at 1×, 2×, or 3× pixel density) and PDF run the same content view you see on screen through SwiftUI’s ImageRenderer — rendered at the diagram’s full extent, not the zoomed viewport, so an export always captures the entire schema at full fidelity (the PDF is true vector output).

SVG takes a separate path: a standalone, deterministic serializer that reuses the exact same metrics and geometry as the canvas, so the vector file matches the screen edge for edge. It renders with a fixed light palette independent of the app’s dark/light theme, so a diagram you drop into documentation, a wiki, or a printout reads cleanly instead of inheriting a dark UI background.

Built on a pure model

Everything above sits on a pure, deterministic build step: introspection results — base tables, their columns, and the schema’s foreign-key constraints — are turned into table nodes and edges with no live database in the loop, so it unit-tests cleanly. Cross-schema foreign keys whose parent isn’t on the canvas are dropped. Cardinality is inferred from the constraint’s shape: one-to-one when the referencing columns are exactly the child’s primary key, many-to-one otherwise. The view models never touch the database — AppViewModel loads the schema and pushes a built diagram in — which keeps the graph an editable source of truth for Phase 2, when the diagram becomes a place to edit your DDL, not just read it.

Concretely, v0.3.3 means

  • A new Diagram main tab (⌘5) with an in-tab schema picker, fit-to-window, pinch/zoom, background pan, drag-to-arrange, collapse, hide / Show All, and hover-to-highlight on relationship lines
  • New Models/ERDDiagram.swift and Models/ERDLayout.swift — the pure, value-typed graph (nodes, FK edges, inferred cardinality) and the saved view-state snapshot
  • New Utilities/ERDLayoutEngine.swift — the deterministic, grid-seeded Fruchterman–Reingold auto-layout with centroid gravity and overlap resolution — plus the shared ERDMetrics / ERDGeometry the canvas and the exporters all read so geometry agrees everywhere
  • New Utilities/ERDRouter.swift — the A* obstacle-avoiding orthogonal connector router with turn and crossing penalties, distributed attach points, and lane insertion
  • New Utilities/ERDSVGRenderer.swift and Utilities/ERDImageExporter.swift for SVG, PNG (1×/2×/3×), and PDF export of the full-extent diagram
  • New Services/ERDLayoutStore.swift persisting positions and collapsed/hidden state per profile + schema as JSON under Application Support
  • A new ERDViewModel and the DiagramTabView / ERDCanvasView / ERDTableNodeView / ERDExportSheet views, all unit-tested against the pure geometry and model layers

Grab v0.3.3 from the latest release or build from source with Xcode. Open a schema, press ⌘5, hit Auto Layout, and you have a map of how your tables actually relate — ready to drag into shape and export for the next design doc.