Administration
The operator area is at /admin.
Nothing on the public side links to it beyond one footer link, so it is not somewhere a visitor wanders into.
Projects
One instance hosts as many projects as you have products.
Each gets a board at /p/<slug>, and the slug is what your users’ URLs carry.
A slug can change. When it does, the old one answers with a permanent redirect to the new one, so links people have already shared keep working. Your own tooling should hold the project’s id rather than its slug, which is why the admin API addresses projects by id and the public API by slug.
Designing a status flow
Columns are per project, and you choose them.
Fewer columns read faster. A visitor should understand the shape of your roadmap in about three seconds, and six columns is already at the edge of that. The default flow — proposed, under review, planned, in progress, shipped, declined — is a reasonable starting point precisely because it is the shortest one that still distinguishes “we have not decided” from “we decided no”.
Two of those are terminal, and the board collapses them behind a “show finished and declined” toggle. That is worth keeping: a board whose first screenful is things you already shipped buries the things you have not.
Choosing how your users identify themselves
This is the decision with the most consequences, and it is per project.
| Mode | Friction | What you get |
|---|---|---|
anonymous | none | A signed cookie plus a hashed address. Weak deduplication, no notifications. |
magic_link | one email click | A durable identity. Real vote deduplication, and you can tell people what happened to their suggestion. |
oidc | a provider login | The strongest, and the obvious choice when the app already has an identity provider. |
Pick anonymous when you want the widest possible participation and you can live with the vote counts being directional rather than exact. Nobody is turned away, and nobody hears back.
Pick magic_link when the votes are going to inform a decision, or when you want people to learn that the thing they asked for shipped. It costs one click, and that click is what makes a vote count mean one person.
Pick oidc when your users already sign in to your product. There is no new account and no new password, and the identity is as good as the one your app already trusts.
The provider is configured per board, unless the instance sets one in its environment, in which case every board uses it and the per-board fields become read-only. See Sign-in.
An anonymous identity upgrades in place. Somebody who votes and comments anonymously and later verifies keeps everything they did: the votes and comments carry across rather than being orphaned. So starting anonymous and tightening later does not throw away the participation you already have.
Note that magic_link needs working SMTP. Without it the link is written to the log, where your users cannot read it. See Self-hosting.
Offering a sign-in without requiring one
A board in anonymous mode that has an OIDC provider configured offers it rather than requiring it.
A visitor is asked once, before their first vote or comment: sign in, or carry on as a guest.
Choosing guest creates a local account for that board under a generated name, which they can rename or delete from Your activity.
Without a provider configured there is nothing to offer, so the board says the same thing in a line under the comment box instead of in a dialog.
Requiring an account to comment
Require an account to comment asks for a signed-in visitor before a comment while leaving voting anonymous. It is the middle ground between a board anybody can talk on and one nobody can take part in without signing in: a vote is a number and a comment is a voice.
It needs a way to sign in, so connect a provider first. Serelane refuses the setting on a board that offers neither, because it would otherwise close commenting to everybody with no route back in.
Moderation
Two queues, both under the project’s Moderation tab.
Comments can be reported by readers. A reported comment stays visible until you act on it, because hiding on report alone hands a heckler’s veto to whoever clicks first.
Suggestions never appear on the board until you accept one. That queue is the one to keep short: somebody who suggests something and hears nothing for a month learns not to suggest anything again.
When two people ask for the same thing, merge rather than decline. Merging moves the votes onto the entry that stays, so the count reflects everybody who asked. Declining a duplicate throws that signal away and reads, to the person who wrote it, as a no.
Serelane also holds repeated content for review rather than refusing it. Two people hitting the same bug write remarkably similar sentences, and the second of them is not spam.
Members
Roles are per project, so somebody can run one board without touching another.
Instance settings, and the accounts that can reach them, live under Instance settings and are separate from any single project.
Signing in to the console through a provider
When an instance configures the admin console’s OIDC provider, the sign-in screen offers a provider button beside the password form.
Only an account that already exists on the instance may use it.
A provider sign-in never creates one and never grants is_superadmin, it only signs in whoever the verified email address already belongs to.
This is also how an invitation can be completed without ever choosing a password. An operator invited by email signs in through the provider on first visit, and the account is active from that point on.
The password form stays visible next to the button rather than behind it. It is the break-glass route: if the provider is unreachable or misconfigured, an operator with a password can still get in.
Requiring a role
By default the provider button is open to every account on the instance whose address the provider has verified. An instance whose provider also serves the boards will want more than that, because the people who can sign up there are not the people who should reach the console.
Setting SERELANE_ADMIN_OIDC_ROLE requires the provider to assert a role before a console sign-in is allowed.
See Admin console for the variables and the claim shapes.
The role decides whether the provider will vouch for an admin sign-in at all.
It decides nothing else.
Who that sign-in is for is still the verified address and the account it already belongs to, and what that operator may do is still their project memberships and their is_superadmin flag on this instance.
Granting the role to somebody with no account here lets them press the button and be refused.
What a revoked role does to a session that already exists
The role is checked when a session is created, not while it is used. Taking the role away at the provider refuses the next sign-in. It does not reach back into a session that is already open.
Console sessions last 14 days and that clock is reset by use, so an operator who keeps working can hold a session open indefinitely. Revoking a role is therefore not, on its own, a way to remove somebody. Delete their account under Instance settings: it drops every session that account holds along with it.
Moving from two provider projects to one
A deployment that kept a separate, restricted project for admin sign-in can collapse it into the public one once the role requirement is in place. Do it in this order, so that no step depends on one that has not happened yet.
Set up the provider first.
Create an admin role in the surviving project, the one that allows public self-registration.
Register two applications in it, one per redirect URI, so the provider can tell the two sign-ins apart: <base URL>/api/v1/public/auth/callback for the boards and <base URL>/api/v1/admin/auth/callback for the console.
Separate clients in one project is the arrangement this feature is built for.
Grant the admin role to the operators, and to nobody else.
Leave any project-wide requirement that every user hold a role switched off, or public sign-up stops working.
Then move Serelane across without changing what it enforces.
Point SERELANE_ADMIN_OIDC_ISSUER and SERELANE_ADMIN_OIDC_CLIENT_ID at the new console application, and the board provider at the new board application, but leave SERELANE_ADMIN_OIDC_ROLE unset.
Restart and confirm that operators still sign in and that boards still work.
Nothing is being restricted yet, so anything that breaks here is the move itself rather than the role.
Check what the provider actually asserts before relying on it.
Sign in as an operator who holds the role and read the claim out of the decoded ID token or the UserInfo response.
The claim name and the shape you see there are what SERELANE_ADMIN_OIDC_ROLE_CLAIM and SERELANE_ADMIN_OIDC_ROLE_ORG have to match.
Then turn the requirement on.
Set SERELANE_ADMIN_OIDC_ROLE, restart, and verify both directions: an operator with the role gets in, and an account without it is refused even though it exists on the instance.
Keep one operator with a password throughout, because a claim that does not match is a door that is shut to everybody until it is fixed.
Only then retire the old admin project and withdraw its grants. Existing console sessions were issued before any of this and are unaffected by all of it, so end them by deleting the accounts that should no longer have one.