Constraints and planned work
The hard rules are on the Architecture page. This page covers everything else a developer needs to keep in mind.
Not built
Section titled “Not built”| Area | State |
|---|---|
| Undo, redo, revisions | Changes are captured (app.atomic_change), but undoChange, redoChange, collapseSession, discardSession and the revision reads are stubs. Nothing writes app.revision. |
| Sharing | shareMap is a stub. There’s no cross-organisation sharing model. |
| Public maps | app.map.visibility exists and map_select lets anyone read a public map’s row, but network RLS needs membership, and every map route needs a session. setMapVisibility has no UI. Anonymous reads must not cross into app. |
| Visualisation export on the server | exportVisualisation is a stub. PNG export happens in the browser. |
| Removing a row before import | removeImportRow is a stub. |
| Access requests | No access_request table or flow. People join only by invitation. |
| Real-time co-editing | Not in scope for V1. |
| Organisations in the UI | createOrg and renameOrg exist, but have no UI. |
| Tag groups and categories in the UI | Created only by imports and the public API. |
| Connections-only CSV import | Refused. Matching From/To against the map’s existing nodes would need its own rules for duplicate labels. |
| CSV export of connections | CSV export is nodes only. |
| Deletes in the public API | Every v1 endpoint creates or updates. |
| API keys in the UI | Keys are minted with a script and revoked by deleting the row. |
| Progressive reveal and force parameters in the UI | A view’s step, connection rules and forces have no designed controls: they’re set through the public API or the hidden raw parameters panel. |
| Soft-delete filtering | Organisation reads filter deleted_at, but getMap and getGraph don’t check whether the map’s organisation is deleted. |
Rules the code relies on
Section titled “Rules the code relies on”These aren’t all enforced by the database. Code that writes data must keep to them.
-
Placed nodes: at most 200 per map (
MAX_NODES_IN_EDIT_MODE).placeNodesandmoveNodesenforce it on the server. -
A connection has a type or a note, not both. The schema allows both. The editor clears one when you set the other (
handleConnectionEntryIntentinmap-editor.tsx), because both the canvas label and the panel show the type’s name first, so a note beside it would be invisible. Imports can still produce both. -
Definition names aren’t unique. Nothing in the database stops two measures called “Members”. Anything that offers to create a definition must first match existing names, ignoring case (see
buildConnectionEntryOptions). A duplicate that looks structured is worse than free text, because clustering and filtering split on it. -
Direction is per connection type. There’s no per-connection direction to set or import.
-
Tags are true-only. There’s no
falseto store, for tags or for boolean connection types. Importers drop false values. -
Deleting a node deletes its measures, tags and every connection touching it, through the composite foreign keys.
-
Invitations expire after 7 days. Accepting needs a confirmed email matching the invitation, and is safe to repeat.
-
An organisation always keeps an
org_admin.removeMemberandchangeMemberRoleenforce this in application code, not in RLS. -
Imports are capped at 10 MB. They aren’t transactional: a failure part-way leaves what was written. A CSV file of nodes needs a label column or an ID column.
-
.in()filters on long id lists must usefetchAllRowsIn. A URL over about 15 KB (roughly 390 UUIDs) is rejected by the API gateway. See Import and export pipeline. -
The shared dev database is on the Supabase free plan, with about 500 MB of space. That limit, or a second developer, will mean moving to a paid plan and turning branching back on.
-
Every data change records a data event. A map’s recipe reads the change log to tell whether it’s out of date, so a write that changes nodes, connections or their values without recording an event in
DATA_EVENT_TYPESleaves a stale recipe looking current (see Editing internals). -
Pruning the change log would break recipe staleness.
app.map_recipe_statereads the newest data change fromapp.atomic_change. If individual changes are pruned, as planned for revision history, the evidence of an old change goes with them. Before pruning ships, staleness must read from permanent session summaries instead.
UI constraints
Section titled “UI constraints”- All three modes stay mounted. Anything with a global listener (the dock’s Esc handling, toasts) must cope with several live instances, most of them hidden.
- Two side panels are tight on a laptop. With both open, the chrome is 688px wide, and the dock always narrows the canvas. A narrow-screen layout isn’t built.
Planned work
Section titled “Planned work”- A directory of entities across maps. A node belongs to one map and isn’t shared.
When maps need to know they describe the same organisation, the plan is an
organisation-wide directory, with a nullable
node.directory_entry_id. Each map keeps its own node, values and history. - A shared vocabulary library. Measures, tags and connection types belong to one map. When an organisation wants the same vocabulary everywhere, the plan is a library whose entries are copied into a map, which can then change them freely.
- Collections in the sidebar. A tag already names a set of nodes, so a collection can be a display flag on a tag or tag group, not a new entity. The code name needs care: “collection” already means a TanStack DB collection in the editor.
- Revision history, on top of the change capture that already exists.
- Narrow screens: the dock floating over the canvas below some width.