The old "Members always get free access" sat at the bottom of the
Ticketing section next to the top-level Enable Ticketing toggle, which
conflated the member-vs-public audience split with the ticketing
mechanism. Admins read it as "I need to enable ticketing for free
public events," the opposite of how the system works.
Move the note next to Public Tickets Available (where the audience
split actually matters) and rephrase: public pricing applies to
non-members; members register from their dashboard regardless.
Events are often scheduled before the platform (Zoom link, Slack
channel) is chosen. The current workaround is a placeholder URL like
"https://us02web.zoom.us/j/TBD", which leaks to the public page as a
broken link.
Accept the literal "TBD" (case-insensitive) in both the Mongoose
validator and the form-side validator. The public detail page renders
"Platform TBD" instead of a link when the location matches.
The Event model and Zod schemas already supported membersOnly, but the
admin form never exposed it — public/private was implicit and not
editable from the UI.
Add a fifth checkbox alongside the other Event Settings, hydrate it on
edit, reset it in saveAndCreateAnother.
Top-level $fetch in <script setup> does not forward auth cookies to the
SSR request, so requireAdmin rejected and the form hydrated empty.
Client refetch then triggered hydration mismatches; in dev the
description textarea stayed DOM-empty and the browser's native required
validation blocked saves.
Switch to useFetch (SSR-aware, forwards cookies). Mirror the
admin/members/[id].vue pattern: extract populateEditForm, call it with
the initial payload, watch for client-side updates.
- Hide the location field's static help text when a validation error is
shown so the two near-identical messages stop stacking.
- Replace `process.client` with `import.meta.client` (Nuxt 3+ pattern).
- Accept either String or Date for EventTicketPurchase.eventStartDate;
the parent passes the API's ISO string, which was logging a Vue prop
type warning on every public event page render.
Editing an event was pulling its UTC startDate/endDate, slicing off the
"Z" with toISOString().slice(0, 16), and then handing the bare digits to
a datetime-local input. The input reinterprets them as local time, so
each save shifted the time by the browser's UTC offset. Same pattern
for registrationDeadline and earlyBirdDeadline.
Format the value using local-time components instead so the round-trip
matches what the admin sees.
Promote inline STATUS_LABELS copies (admin/members/index.vue,
member/account.vue) into app/config/memberStatus.js, matching the
app/config/circles.js pattern. Drive admin/members/[id].vue status
select from the same constant — completes the alignment started in
441a5f5.
Use the softer member-facing copy as canonical: "Paused" / "Closed"
instead of "Suspended" / "Cancelled".
Also fix markSlackInvited's non-reactive Object.assign on a plain
object inside a useFetch array — replace with index-find + element
reassignment so the row UI refreshes without a manual reload.
The status options were duplicated three times in admin/members/index.vue
(filter dropdown, edit-modal dropdown, statusLabel helper). The recent
"Pending Payment" → "Payment setup incomplete" rename only landed in
two of the three sites. Both <select>s now v-for over the existing
STATUS_LABELS const, so any future label change happens in one place.
Side effect: the edit-modal dropdown order is now
(active, pending_payment, suspended, cancelled) to match the filter
dropdown — was previously pending_payment-first.
- B: token-equivalent rgba → color-mix(srgb, var(--ember|green|candle) X%, transparent) so colors track dark mode
- C: drop stale var(--green, #...) fallbacks (canonical token now defined in main.css)
- F: inline circle badge → <CircleBadge/> in admin/index, members/[id], members/index
Replaces the placeholder Slack-invite handler with a call to the new
PATCH /api/admin/members/:id/slack-status endpoint. Status labels are
reworded to match reality (no Slack API call from this app):
- Pending → Not yet invited
- Invited → Invited <slackInvitedAt>
- Action button copy → 'Mark as Slack invited'
- Removes slackInviteStatus reads from the member detail page (the
remaining repo-wide sweep lands in the cleanup task).
Friendlier tone + ghost emoji on invite/welcome subjects; invite
templates now offer a reply-to-this-email fallback; tighten OIDC
wiki sign-in and event registration confirmation copy.
- Add Board to exploreItems in AppNavigation
- Update ecology.vue + connections.vue redirects to /board
- Rename all communityEcology refs to board in member profiles, dashboard, admin, onboarding
- Update API path /api/members/me/community-ecology → /api/members/me/board
Top-level `await useFetch` inside a ClientOnly boundary prevented the
component from mounting at all — the slot stayed an empty span and no
alerts ever rendered. Nuxt forwards request cookies on same-origin
server calls, so `requireAdmin` is satisfied during SSR and the panel
can render normally.
Admin interface to review, filter, and batch-invite the 95 pre-registrants
from Baby Ghosts. Accept-invitation page pre-fills their data and collects
circle, pronouns, motivation, contribution tier, and agreement before
creating their member record.
Add aria-labels to form controls (selects, checkboxes, switches), set
html lang attribute and page title, fix color contrast for --candle-dim
and --text-faint tokens, underline inline links, remove opacity hack.
Harden dev login endpoints with atomic findOneAndUpdate and tokenVersion
in JWT. Update Playwright timeouts and E2E test helpers.
Migrate the entire admin section from the dark guild-* Tailwind theme
to the zine design system (dashed borders, CSS custom properties,
Brygada 1918 + Commit Mono, cream/dark mode palette).
- Replace admin top-nav layout with sidebar matching default layout
- Reskin dashboard, members, events, series management pages
- Reskin events/create and series/create form pages
- Add dev-only test login endpoint (GET /api/dev/test-login)
- Redirect duplicate admin/dashboard.vue to /admin
- Update CLAUDE.md design system docs
Wrap auth-dependent sidebar navigation and meta in ClientOnly with
SSR fallback slots to prevent hydration mismatch that caused all
authenticated nav links to point to wrong pages. Fix admin events
page crash by replacing empty string USelect values with 'all'.
Native <input type="checkbox"> elements were invisible in production
because @tailwindcss/forms is not installed. UCheckbox renders properly
with the Nuxt UI theme.
Standardizes color values and styling using the new tokens:
- Replaces hardcoded colors with semantic variables
- Updates background/text/border classes for light/dark mode
- Migrates inputs to UInput/USelect/UTextarea components
- Removes redundant style declarations