Skip to content

Studio: an admin role, ownership that can be handed on, and a support page - #13

Merged
visualfart merged 1 commit into
mainfrom
feat/studio-admin
Sep 30, 2026
Merged

visualfart merged 1 commit into
mainfrom
feat/studio-admin

Conversation

@visualfart

Copy link
Copy Markdown
Owner

Roles

admin joins the role list. It does everything the owner does except the three irreversible acts: deleting the workspace, handing it on, and — no, admins can manage billing, as you asked. So the owner keeps only deletion and the handover.

There is now exactly one owner, enforced by making the role impossible to grant: it isn't in the invite list and a role change refuses it. It moves only through a handover, which demotes the previous owner to admin in the same batch, so there is never a moment with two owners or none. Team gains a role dropdown and a "Make owner" button.

Everyone but a viewer counts as an editor, so an admin takes a paid seat.

Support

/admin, for whoever runs a hosted Studio: find any workspace or person, set a plan by hand, hand a workspace over when the owner has gone, take someone out, and read what Stripe says about a customer.

  • Never writes to Stripe. It reads subscriptions and invoices; a failure there shows as a line on the page rather than hiding the rest.
  • Who counts is SUPER_ADMINS — addresses separated by commas, checked against the signed-in person on every request. Deliberately not a column, so editing the database grants nobody access.
  • Everything is recorded in admin_actions (migration 0008): who, what, before, after, and the reason typed into the drawer.
  • A signed-in person who isn't on the list gets 404, not 403, so the surface isn't something to probe.

A plan set by hand is separate from a subscription. enterprise is the one a Stripe event never overwrites, so it's the one for comps and deals invoiced elsewhere.

Also

  • Templates gain the twelve packs modelled on published design systems. shadcn is held back: five of its tokens carry its own var(--surface) CSS instead of an alias, so a copy would land in a workspace with broken aliases showing. That's a pack bug with its own fix.
  • Onboarding: a new workspace lands on its Home, where the checklist already says what's left, rather than being dropped into the importer with no way out. The importer gains a "Not now".
  • Emails rebuilt: tables and inline styles, a mark drawn from table cells so it survives a blocked-images inbox, preview text, and a greeting by first name — including the invitee's, when they already have an account.

Checked

84 tests pass, including six new ones covering the admin surface; tsc -b clean across all four projects; docs check clean. I also ran Studio locally and used the page: signed in as a super admin, opened a workspace, set it to Enterprise with a reason, and confirmed the record showed free → enterprise with who and why. The two emails were rendered and looked at.

Before this is useful in production

SUPER_ADMINS is already set on the production Worker. Migration 0008 needs applying to the remote D1 after merge.

🤖 Generated with Claude Code

… page

**Roles.** `admin` joins the role list: everything the owner can do except
billing's three irreversible acts. There is now exactly one owner, enforced by
making the role impossible to grant — it is not invitable and a role change
refuses it. It moves only through a handover, which demotes the previous owner
to admin in the same batch, so there is never a moment with two owners or none.
Admins may manage billing, as the owner does.

**Support**, at /admin, for whoever runs a hosted Studio: find any workspace or
person, set a plan by hand, hand a workspace over when the owner has gone, take
someone out, and read what Stripe says about a customer. It never writes to
Stripe. Who counts is the SUPER_ADMINS secret, checked against the signed-in
address on every request and deliberately not a column, so editing the database
grants nobody access. Every change is recorded in admin_actions with the address
that made it, the before and after, and the reason given. A signed-in person who
isn't on the list is told the endpoint doesn't exist.

**Templates** gain the twelve packs modelled on published design systems, for a
team whose product already uses one. shadcn is held back: five of its tokens
carry its own `var(--surface)` CSS rather than an alias, so a copy would land
with broken aliases showing.

**Onboarding.** A new workspace lands on its Home, where the checklist already
says what's left, instead of being dropped into the importer with no way out;
the importer gains one.

**Emails** are rebuilt as tables with inline styles, a mark drawn from table
cells so it survives a blocked-images inbox, preview text, and a greeting by
first name — including the invitee's, when they already have an account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@visualfart
visualfart merged commit 8a5bd29 into main Sep 30, 2026
1 check passed
@visualfart
visualfart deleted the feat/studio-admin branch September 30, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant