Build Lucidspark
KINDAback to the verdict
A collaborative board for running workshops, where the canvas is the easy half and the difficult half is the facilitation: anonymity that actually holds, timers everyone sees identically, and voting that cannot be gamed by whoever votes last.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server and the socket layer.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally
Why Sessions, votes and notes with server-side visibility rules.
Get it Docker or your package manager. open ↗
Verify
psql connects and creates a table - have readyfree, and it costs an hour of eight people's time
Why Every claim here is about group behaviour: whether anonymity is believed, whether the timer feels fair, whether voting changes when totals are hidden. None of it is observable alone.
Get it A real team and a real question to work on, for an hour.
Verify
A full session runs with eight participants including a vote and a reveal - decidefree
Why Anonymity is a promise to participants, and whether the facilitator can see authorship changes what people write. Deciding it silently means deciding it against them.
Get it Decide whether the facilitator can see authorship, state it in the session, and write down why.
Verify
A written rule exists and is shown to participants when a session is anonymous - have readyfree
Why Timer synchronisation is only testable against skew, and machine clocks in a room full of laptops are genuinely inconsistent.
Get it Set a test machine's clock several minutes off and join a session with it.
Verify
The skewed client shows the correct remaining time
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- boards(id, title, created_by, created_at, archived_at) - sessions(id, board_id, phase, phase_started_at, phase_duration_seconds, anonymous, facilitator_id) - notes(id, board_id, author_id, body, x, y, colour, group_id, created_at) · author_id withheld when anonymous - groups(id, board_id, label, created_by) - votes(session_id, voter_id, note_id, weight, cast_at) · never returned to participants before reveal - reveals(session_id, at, revealed_by) · the moment results became visible, recorded - participants(session_id, user_id, display_name, joined_at, role) - A vote is stored with its voter for facilitator audit, but no query path returns another participant's votes before the reveal.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://localhost:5432/workshops | Boards, notes and votes, some written under a promise of anonymity. |
SOCKET_PORT | required | 3002 | The live session channel. |
ANONYMOUS_DEFAULT | required | false | Whether new sessions are anonymous. Stated to participants either way. |
FACILITATOR_SEES_AUTHORS | required | false | A promise to participants. It is shown to them and it changes what they write. |
VOTES_PER_PARTICIPANT | required | 5 | The allocation, enforced server side. |
REVEAL_TOTALS_BEFORE_END | required | never | Present as a constant. Visible running totals change the result. |
The build, in order
Notes on a board, together
Several people add and move notes simultaneously without conflict.
Whole-board broadcast collapses at workshop scale, during the workshop.
Files
src/sync.tsTwo people editing one note is normal in a workshop.
Files
src/reconcile.tssrc/reconcile.test.tsPeople join late and the pause is visible to everyone.
Files
src/join.ts
done when · tick each as it passeswatch out- Broadcasting the whole board on every change works with twenty notes and collapses at four hundred, which is a normal workshop output, and it collapses during the session rather than afterwards. Send per-note changes and reconcile at the note level.
Anonymity that holds at the API
An anonymous note's author cannot be discovered by anyone but the facilitator.
In the serialiser and the socket path both.
Files
src/anonymity.tstests/anonymity-leak.test.tsStored, and reachable only through a path with its own rules.
Files
src/anonymity.tsNotes written under anonymity were written under a promise.
Files
src/sessions.ts
done when · tick each as it passeswatch out- Hiding the author in the interface while the socket message still carries it means anyone who opens the developer tools can see who wrote the critical note, and in a workshop about a team's problems that is precisely the harm anonymity was for. Strip it server side and assert it in the suite.
One clock everyone sees
The timer shows the same remaining time on every screen.
Start and duration from the server, derived locally.
Files
src/phases.tsMachine clocks are wrong and a workshop timer is visible to everyone.
Files
src/timer.tssrc/timer.test.tsNot on whichever client reaches zero first.
Files
src/phases.ts
done when · tick each as it passeswatch out- A timer counted down independently on each client drifts, so participants see different remaining times and someone is cut off mid-sentence believing they had thirty seconds. Send the phase start and duration, derive locally, and correct against the server rather than counting down in isolation.
Voting nobody can watch
Votes are invisible until the reveal, including in the traffic.
Enforced in the query. Running totals change the result.
Files
src/votes.tstests/vote-secrecy.test.tsSo you know what you have left without seeing anyone else.
Files
src/votes.tsThe moment results became visible is part of the session record.
Files
src/reveal.ts
done when · tick each as it passeswatch out- Showing a running vote total during voting changes the result, because people converge on whatever is already winning, and that is a well known effect rather than a hypothetical one. Return no other participant's votes and no aggregate before the reveal, enforced in the query rather than the display.
Turning a board into an outcomeproduct builder
The workshop produces something usable after everyone leaves.
Nobody reads four hundred notes in a screenshot.
Files
src/export.tsThe ordering is most of what the session decided.
Files
src/export.tsThe session record is the part people come back for.
Files
src/archive.ts
done when · tick each as it passeswatch out- A workshop whose output is a screenshot of a board is a workshop whose output is lost, because nobody can read four hundred sticky notes in an image and nobody opens the board again. Export a document with the themes, the notes under them and the vote results in order.
That is the whole plan for Lucidspark. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Freeform drawing and diagramming.
- Video and audio for the session.
- Workshop format template libraries.
- Issue tracker integration.
- high-fidelity import and export
- presentation and enterprise controls
- multiplayer cursors and comments
- large template libraries
- Facilitator scripts that drive the phases automatically, which is where the timing gets reliable
- Breakout groups within a session, which multiplies every synchronisation problem here
- Comparing two sessions on the same question, which is where a workshop practice improves
Need the files? The project pack on the verdict page hands your agent the whole brief · more whiteboards.