Skip to content

Spaces and room codes

Verified against v0.21.0 · internal/api/spaces.go, internal/api/passcode.go, internal/api/authz.go, web/src/pages/OrgDirectory.tsx, web/src/pages/SpacePage.tsx, web/src/pages/SpaceSettingsPage.tsx, web/src/components/DecksPanel.tsx, web/src/components/Kudos.tsx, web/src/components/AppShell.tsx, web/src/pages/Landing.tsx, web/src/App.tsx, internal/store/sessions.go

A space is a team’s room. It has a memorable URL — /o/acme/s/platform-team — a roster, and a history of every session run in it.

The URL is the point. It survives being pinned to a channel topic, and it is the same tomorrow, which is why Parley does not bury spaces behind a random slug.

Signed in, the front page opens on the space you used last — its name is the page’s heading — with every round open in it right now, who is sitting at each, and a Join that puts you straight into the round — or Open, when nobody is sitting at it yet. That list refreshes every few seconds, so a teammate sitting down shows up without a reload. “Used last” means the last time you opened the space, not the day you joined it — the space page tells the server it was opened, so the table you use every week stays on top.

Below it, your spaces are grouped by org, most recently active first. Each org’s panel carries its own Browse link to the directory and, for an org administrator, Plugins. A row for a protected space shows a padlock and the word room code, so you know before you open it whether you will need six characters to hand anyone you invite. A row with a round open in it right now shows a small face-down card when somebody is at the table, or a dashed empty seat when nobody is. In more than one org, a row of buttons narrows the page to one of them; past six spaces a Find a space box filters by name, and Enter opens the first match.

Create a space stays on the same page. In more than one org it always shows an Org picker for where the space goes; it follows the org the page is narrowed to until you pick one yourself.

When joining another space, the entry screen distinguishes a display name in open mode from the team account required by an OIDC instance. Until authentication mode is known, the entry screen makes no account-free promise. Room code errors appear beside the field and are announced to assistive technology. In space settings, the last owner’s disabled removal and demotion controls are explained with visible guidance to promote another member first.

Signed out, or in no spaces yet, the page introduces the product instead: what a space is, that the link is the invite, and a box to name your first one. Once you belong to a space that introduction gets out of the way — the list is why you came.

A space is either private or visible to its org. A private space is reachable only by its link or an invitation; an org-visible one is additionally listed in the org’s directory at /o/acme, which every member of that org can browse. The front page carries a Browse org link for every org you belong to — including when you have joined no space yet, which is exactly when you need it. New spaces are org-visible, so a team can find its own rooms without anybody pasting a URL — and an owner can take one back out of the directory from Space settings at any time.

Opening /o/acme while signed out asks you to sign in first, then shows the directory, so a bookmarked or shared directory link works from a cold start.

The directory loads 50 spaces at a time, with a Show more spaces button for the rest. An org that has been running for a year has more rooms than anybody wants sent at once, and this is the first page a new member opens.

Being listed is discovery, not entry. Org visibility decides who can find a space; the room code still decides who gets in. A space that is both listed and protected shows a Room code badge in the directory, and opening it puts you in front of the same gate anyone with the link would meet. Only a space with no room code is open-on-click, and then only to members of its org.

On an instance with no sign-in configured, org visibility is refused outright: every visitor there is handed an anonymous identity and enrolled in the only org, so a listed, room-code-free space would be a room the whole internet could walk into. Such instances get private spaces, and the room code stays the door. Turn on OIDC if you want a directory.

New spaces are protected by default. Joining one needs a six-character code, drawn from a 25-character alphabet with the confusable letters removed — no O against 0, no I against 1 — because this code gets read aloud in a meeting.

Codes are matched case-insensitively and compared in constant time. Wrong guesses are throttled per client address: eight attempts per minute, per space.

/o/<org>/s/<slug> carries a one-line invite strip above the session list: the room code, Copy room code, and Copy invite link. Send the link, and keep the room code for someone who has the address but not the link. Nothing on it changes the door. Minting a new code — which immediately retires the old one and drops everyone still holding it back to the gate — and opening the space entirely both live under Access on the settings page, which is owner-only. The server itself still accepts either request from any member.

Copy invite link puts one link on the clipboard, built from the address you are already on, with the room code carried in the URL fragment: https://parley.example/o/acme/s/platform-team#c=V4KW7J. Opening it is the whole join — there is no code to read off a second line and retype.

The room code goes in the fragment rather than a query string on purpose. A fragment is never sent to the server and never appears in a Referer header, so a link pasted into a chat does not scatter the room code through access logs, proxy history, or the analytics of whatever site someone clicks through to next. Parley reads it once on arrival and wipes it from the address bar, so it does not survive into a bookmark, the back button, or a screenshot. The link is still a secret in transit — treat it exactly as you would the room code itself.

Copy room code, on the strip and beside the Access controls in settings, takes just the six characters, for when you are reading it out or the link is already pinned somewhere. An open space has no room code, so the invite is only the link, and the strip says so in the same one line rather than showing an empty panel.

An open space is appropriate only on a trusted network. For public reachability, keep the room code or put an external SSO/authentication proxy in front and add ingress abuse controls.

Under the invite strip, a space’s sessions come in three groups. Any room with somebody in it right now sits on a card at the top, On the table now, with how many are there, a Rejoin link, the faces and names of up to five of the space’s members who are in (the facilitator first), and how far it has got — “4 of 9 settled” for planning poker, “5 of 6 answered” for a standup. People in the room through a signed link are counted but never named, so they show up as “and 2 more”. Rooms that are still open but empty follow in one list, each saying when it was last active: a story added, a standup entry written, the turn moving on, or somebody being in the room. A vote on its own does not count, because votes carry no time — the voter being present does. Ended sessions fold into a Logbook below, closed by default, where a poker session shows how many stories it settled and a standup how many people answered; whether you left the Logbook open is remembered in your own browser, for each space separately.

Once a space has more than six sessions, a search box and a kind filter appear above the list, and they apply to all three groups at once, and the number of matches is announced to screen readers as you type. Press / anywhere on the page to jump to the search box. The server sends a space’s 50 newest sessions and no more, so when the list is full it says Showing the latest 50 sessions — a search that finds nothing there has not proven an older session never existed.

Everything that changes a space lives on its own route, /o/<org>/s/<slug>/settings, reached from Settings at the foot of the sidebar — the rail on a wide screen, the same sheet on a phone. The link, and everything that changes the space, are owner-only: a member who types the URL is told to ask an owner, and the server refuses the requests behind it in any case.

The page holds five things, in order: Members with their role controls, Access (above), Space for the name, Decks — the card sets this space has saved — and a fenced Danger zone. Decks are the one panel a member sees too, read-only: they pick a deck when they start a session, so which ones exist is reference rather than administration.

Splitting it out keeps the page a team opens daily to what it came for. The session list and the invite are one audience and one lifetime; renaming, rotating and deleting are another.

A rename changes the display name only. The slug does not move with it, so /o/acme/s/platform-team keeps working and every invite already pinned to a channel topic keeps working with it.

Deleting sits alone in the danger zone. It is the one genuinely irreversible thing in Parley: it takes the space and everything under it — every session, story, vote and standup, for everybody — and there is no undo and no archive. The button asks you to type the space’s name back before it will fire, and anyone with a room open is disconnected as it lands.

Each session row carries a Manage button for owners, offering the same two things for one session: rename it, or delete it and its history. That is distinct from a facilitator closing a session, which ends the meeting, keeps the record, and can be reopened.

The space settings page for Platform Team: a Back to Platform Team link, a Members panel listing all six with Make owner and Remove beside each and an Owner chip on Dana Whitfield, an Access panel showing K7QM4P with Copy invite link, Copy room code, New room code and Make open, a Who can find it panel reading Unlisted, a Space panel for renaming, a Decks panel holding Platform sizing and an ordinal Bug triage with Edit and Delete beside each and a New deck button, a Standup schedule panel with weekday checkboxes, an opening time, a time zone and a window, and a red-bordered Danger Zone holding a Delete Platform Team button.The space settings page for Platform Team: a Back to Platform Team link, a Members panel listing all six with Make owner and Remove beside each and an Owner chip on Dana Whitfield, an Access panel showing K7QM4P with Copy invite link, Copy room code, New room code and Make open, a Who can find it panel reading Unlisted, a Space panel for renaming, a Decks panel holding Platform sizing and an ordinal Bug triage with Edit and Delete beside each and a New deck button, a Standup schedule panel with weekday checkboxes, an opening time, a time zone and a window, and a red-bordered Danger Zone holding a Delete Platform Team button.
Everything that changes the space, on one owner-only route — the space's own decks included, and the irreversible thing fenced off at the bottom.

The roster shows everybody who has joined, with a live presence dot and, when they are sitting at a table, which one. Clicking through takes you to them.

Presence comes from the WebSocket hub rather than from a “last seen” timestamp, so it reflects an actual open connection.

A space page for Platform Team: a sidebar listing its sessions and its six members with presence dots and an Owner chip beside Dana Whitfield, and in the middle a one-line invite strip reading ROOM CODE K7QM4P with a Copy invite link button, above the list of recent sessions. No search box or filters are shown, because the space has six sessions.A space page for Platform Team: a sidebar listing its sessions and its six members with presence dots and an Owner chip beside Dana Whitfield, and in the middle a one-line invite strip reading ROOM CODE K7QM4P with a Copy invite link button, above the list of recent sessions. No search box or filters are shown, because the space has six sessions.
The table first: the room code to read out, then what has been run. Nothing on this page changes the door.

The sidebar roster names everyone in the space and their role — Owner or Member. The person who created the space is its first owner. The controls that change a role live on the settings page, under Members.

There, an owner gets two controls beside each member: Make owner / Make member to change their standing, and Remove to take their seat away. Removal takes effect on that person’s very next request: they drop straight back to the room code gate. It is not a ban — a current room code lets them rejoin as a plain member.

A space always needs somebody who can manage it, so the last remaining owner can be neither demoted nor removed; both controls are disabled and the server refuses the request outright. Stepping down yourself is fine once you have promoted somebody else first.

Every session ever run in the space is listed with a badge and a relative date. Once there are more than six, you can search the titles and filter by kind — poker or standup. The list is newest first.

The badge says one of three things, and it counts people rather than reporting whether anyone remembered to close the session:

  • N here, with a green dot — that many people have a socket open on it right now.
  • open — nobody is in it, but it has not been ended. Join it and it is live again.
  • ended — somebody closed it. It is read-only history.

The count comes from the same WebSocket presence the roster uses, so it means an actual open connection. Two things make it approximate by design: a presence record ages out roughly 100 seconds after the last heartbeat, and the page re-reads the space every 30 seconds. Somebody who closes their laptop can therefore linger in the count for up to about two minutes. That is the design, not a bug — it is the price of never asking a database for a “last seen” timestamp and calling it presence.

Every space has a kudos wall, in a column beside the session list on a wide screen and below it on a narrow one: pick somebody from the roster — or press Thank beside their name in the sidebar — say in a sentence what they did, up to 280 characters, with a countdown once 40 are left, and it appears at the top of the wall with their name on it. It needs no meeting: thanking a colleague on a Thursday afternoon does not wait for a ceremony to be open.

Only the sender can withdraw a kudo, and nobody can edit one: a misdirected thank-you can be taken back, but text somebody has already read is never rewritten underneath them. A kudo also outlives the people in it — one given to somebody who has since left the space stays on the wall, because it is a record of something that happened rather than an attribute of a membership.

There is no count, no total and no ordering by anything but time. That is a decision, not an omission: the moment a number sits beside a name, the wall becomes a scoreboard, and thanking people stops being free. Kudos has the whole of that argument, and what guests can and cannot do with one.

CapabilityStatusWhat to do instead
Per-user or per-team accessNot builtPut an SSO proxy in front, or turn on OIDC and keep spaces open only on a trusted network. Owners manage membership, but a room code is still the only door.
Filtering history by dateNot builtThe list is newest first; scroll, and search matches titles.
Roles beyond owner and memberNot builtOwnership governs membership and the space itself — renaming and deleting the space and its sessions, and the settings page that holds those controls. It does not govern the door at the server: any member, owner or not, can still rotate the room code or open the space through the API. Treat membership as trust, and see the threat model.
Knock-to-joinNot builtShare the code. Requesting entry from the door is on the roadmap.