Skip to content
Renaud Yasin
Back to the blog
· 5 min read

Phiremap: rebuilding IBIS dialogue mapping as a real-time collaborative web app

Owning the brief as a product owner building through Claude — taking a single-user desktop tool and deciding, store by store, how to turn it into a real-time collaborative canvas that also covers CRM and agile.

By

  • CRDT
  • Real-time collaboration
  • React
  • Architecture
  • Built with Claude
Phiremap — Phiremap: rebuilding IBIS dialogue mapping as a real-time collaborative web app

As a product owner, my job isn't to write every line — it's to decide what's worth building, set the brief and the guardrails, and steer it into existence through Claude. Phiremap was my test of how far that goes on a problem with real architectural teeth. I have a soft spot for dialogue mapping: you capture a messy conversation as a graph of questions, the positions that answer them, and the pros and cons that argue for or against — a method called IBIS (Issue-Based Information System). The idea is great, but the desktop tools built around it were single-user Java apps. The brief I set myself was to rebuild it as something a team can actually use today — browser-based, real-time, and collaborative — and then see how far the same canvas could stretch. My work wasn't typing the sync engine; it was deciding what the thing had to be and holding the line on those decisions while Claude built to them.

It stretched a long way. The original IBIS node types now share the canvas with two more families: a people & marketing graph (a lightweight CRM — people, orgs, segments, campaigns) and an agile one (Jira-style epics, stories, tasks, bugs, spikes). Twenty-four node types, one canvas, links whose type is inferred from the endpoints you connect.

Four node-type families share one canvas, reached from a single grouped Add-node menu.

The product decision that shaped everything

The architecture is deliberately split into two channels, because the data has two very different temperaments — and sorting one from the other was the call that mattered, not the code that implemented it:

  • Structured API — tRPC over HTTP (:3001). A Fastify server exposes ~14 workspace-scoped, role-tiered tRPC routers (nodes, links, views, tags, search, interop, AI, auth, org, workspace…) over Postgres via Prisma. This is the source of truth for anything you query or export.
  • Live canvas — Yjs CRDT over a Hocuspocus WebSocket (:3002). Every view is its own collaborative room. The React Flow canvas is bound to a Yjs document, so structure, position, and labels merge conflict-free across clients, with cursors, presence, and soft locks carried entirely in Yjs awareness — no database columns for any of that.

The decision I'm proudest of is what lives where, and it's a product judgment before it's a technical one. Structure/position/label go in the CRDT (they must merge). Rich-text detail, type-specific properties, tags, and the assignee stay relational over tRPC (they must be queried and never conflict). Ephemeral things — who's looking, who's editing — live only in awareness. Getting that triage right is the entire difference between a canvas that feels solid and one that fights itself; Claude can wire up any of those stores, but deciding which piece of state belongs in which was the work I couldn't delegate. The spine that keeps the three aligned is a single invariant: one id is the same across the CRDT node, the Prisma row, and the React Flow node.

A few more pieces I'd point to, each one a choice about where the line sits: the canvas is local-first (an IndexedDB layer hydrates it before the socket connects); on save the CRDT is both persisted as a blob and snapshotted back into the relational tables, so search, export, and AI never go stale; and transclusion — one node referenced by many "view placement" rows — lets the same node appear in several maps at once. There's also a small Go gateway that fronts API-key REST traffic, separate from the browser's two ports. And an Insights panel reads the map structurally — flagging gaps like a one-sided position with only pros — with optional Anthropic-powered node suggestions layered on top. I scoped that AI layer to degrade gracefully to the pure structural analysis when there's no API key, because a feature that only works when a paid key is present isn't honestly "done."

Insights — structural gap/balance analysis, with AI suggestions on top.

Where I chose to name the ceiling instead of over-building it

Because each view is an independent Yjs room, an edit to a transcluded node propagates live within its room but not into a separately open room until it reloads. I built a neat workaround for relational fields — a revision counter that rides in the CRDT (the content doesn't) to nudge peers to refetch — but true cross-room live propagation needs a shared sub-document or a Redis pub/sub backplane. That was the product call I'm most deliberate about: I scoped it as a known limitation rather than spend the build budget chasing it, partly because the same machinery would later unlock horizontal scaling anyway. When AI does the building, the cheap thing is to keep adding; the discipline is deciding what's genuinely worth building now and naming the rest honestly. Calling out the ceiling out loud was the right call.

Status & what I'd carry forward

It's feature-complete and tested — pure logic unit-tested, data paths integration-tested against Postgres — and the whole stack (Postgres, API, web, gateway) comes up with one docker compose up --build. Auth and teams (org + workspace RBAC) are implemented behind an AUTH_ENABLED flag, and there's a written plan to take it from "runs in Compose" to a hardened VPS deployment. It isn't live on the public internet yet.

What I'll carry into the next real-time app is that consistency is a placement problem before it's a sync problem — and that's exactly the kind of decision that stays mine even when Claude writes the implementation. Decide what merges, what's queried, and what's ephemeral first; give them one shared identity; and the conflict-free behavior mostly falls out. The CRDT was the easy part — the discipline of keeping content out of it, and of choosing which ceilings to leave standing, was the lesson.