Realtime collaboratieve lijsten- en kanban-app gebouwd op Elixir/Phoenix/Ash: live sync, rollen en uitnodigingen, optimistic locking, LexoRank-ordening en GDPR-conformiteit.
Listify is een app voor realtime collaboratieve lijsten en kanban: meerdere mensen bewerken dezelfde lijsten, zien wijzigingen meteen verschijnen en beheren leden, rollen en meldingen. Een greenfield-project gestuurd door een gedetailleerde specificatie, in productie en zelf gehost.

Ik wilde een echt testterrein om de Elixir/Phoenix/Ash-stack te beproeven op een niet-triviaal domein: realtime samenwerking. Geen demo-todolijst, maar een volledige app — bewerkingsconcurrentie, rollen, uitnodigingen, meldingen, audit, GDPR — gestuurd door een spec die vóór de code geschreven werd, om de architectuurbeslissingen vooraf vast te leggen in plaats van te improviseren.
De app is 100% Phoenix LiveView — geen SPA-framework, de interactiviteit wordt server-gestuurd over WebSocket. Bedrijfslogica, persistentie en autorisatie verlopen allemaal via Ash 3.4 (AshPostgres), georganiseerd in domeinen: Accounts, Lists, Messaging, Notifications, Audit, Backups, Moderation.
Phoenix LiveView (server-gestuurde UI)
│
├── Ash-domeinen (logica, autorisatie, persistentie)
│ Accounts · Lists · Messaging · Notifications · Audit · Backups
├── Phoenix PubSub (realtime signalering)
├── Oban (jobs: mailer, exports, scheduled, critical)
└── PostgreSQL 16 + Redis (sessies)
Elke item- of lijstmutatie publiceert een bericht op een PubSub-topic (items:list:<id>). Maar het kernpunt: de LiveView gebruikt de uitgezonden payload niet — ze ontvangt een eenvoudig signaal ({:item_updated, id}) en herleest de volledige resource via Ash, met autorisatie en policies toegepast. PubSub is een signaleringsmechanisme, geen datatransport — wat de hele klasse van “verouderde payload”-bugs elimineert.
Gelijktijdige itembewerkingen worden bewaakt door een lock_version die atomair in de SQL WHERE-clausule wordt gecontroleerd (niet na het lezen — dus geen race). Een conflict geeft een 409 met de serverversie terug zodat de client opnieuw kan ophalen. Maar alleen de “zware” velden (titel, beschrijving, labels) worden bewaakt; hot velden (status, rang, toewijzing) verlopen via aparte last-write-wins-acties om snel te blijven. Dit is geen pessimistic locking — het is gerichte conflictdetectie.

Itemordening gebruikt LexoRank (rang-strings) in plaats van gehele posities: herordenen voegt een rang tussen twee buren in O(1), zonder ooit te hernummeren of een globale sequentie te vergrendelen. Twee gelijktijdige herordeningen coördineren niet — de bit-string-wiskunde garandeert monotoniciteit.
Queue-topologie vastgelegd in de spec: default (10), mailer (5), exports (3), scheduled (2), critical (1). Een vijftiental workers: uitnodigings- en vermeldingsmails, deadline-herinneringen, dagelijkse digest, reset van terugkerende lijsten, GDPR-export, uitgestelde accountpurge, S3-backupopschoning.
listify.josephpire.dev (Docker Swarm, 2 replica’s, rolling updates)Vergelijkbare projecten