The Create Invitation flow rendered "/invite?t=…" instead of
"https://host/invite?t=…" because the origin was gated on an unset
PUBLIC_ORIGIN env var.
Solution: OpContext now carries `origin` (always set by the route
handler from Astro.url.origin), and invitations.ts builds the magic
link from it. No env vars required.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three more resources land. /admin/users replaces the old participants
tab, /admin/invitations replaces the old invites tab, /admin/join_requests
replaces the read-only join queue.
- src/admin/resources/users.ts ("People"): single resource for all users,
filter chips swap visible columns (council shows member_number +
focus_tags; pilots/team show role + last_seen_at). Form fields are
conditional — title / pull_quote / focus_tags / cab_joined_date /
member_number render only when role === cab. No ops.create (users
come via invites); deactivateUser is the delete handler.
- src/admin/resources/invitations.ts: form-for-create, summary-for-view.
Create generates a token via generateInviteToken(), stores its hash,
surfaces the magic link as a one-shot ?invite_url= block in the panel.
Revoke is an action (sets expires_at = now); the row stays for audit.
- src/admin/resources/join-requests.ts: form: null, review-mode panel
with the user's summary + approve_as_cab / decline actions.
Plumbing to support the above:
- src/admin/resource-types.ts: new Resource.summary callback (read-only
field pairs for review panels); OpContext.result lets ops surface
ActionResults (e.g. invite-link).
- src/admin/components/ResourceEditPanel.astro: review mode when an
existing item is shown and resource.summary is defined; renders the
?invite_url= block above the summary with a copy-to-clipboard button.
- src/admin/components/ResourceListView.astro: "+ New" suppressed when
ops.create is undefined.
- src/pages/admin/[resource].astro: captures ctx.result and action
handler return values, propagates them via &invite_url=...; routes to
the list view (not the row) when an action removes the item.
- src/lib/db.ts: adds getJoinRequestById, deleteJoinRequest,
getInviteById.
Deviation from the original delta: no approve_as_pilot action and no
invite-link result on join-request approval. The existing
join_requests schema only stores user_id — requests come from
already-authenticated pilots asking for a CAB upgrade, not from
strangers needing an invite. The schema change for stranger sign-ups
is left for a future follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>