Backgrounding 'node .output/server/index.mjs &' swallowed startup
crashes — failures presented as a useless 30s 'Wait for server'
timeout. Pipe stderr to a log file and cat it on failure so the
next crash is one click away.
server/plugins/validate-env.js process.exit(1)s on boot when these
are missing — the playwright job's 'Start server' step backgrounds
the process and returns instantly, so the crash was silent until
'Wait for server' timed out at 30s.
Stub values are safe: ALLOW_DEV_TEST_ENDPOINTS=true short-circuits
the Resend call in invite.post.js, and Helcim API calls are mocked
at the page.route level in join-flow.spec.js.
Without this flag, server/middleware/03.rate-limit.js applies the
100-req/60s general limit to /api/dev/test-login and trips during
parallel e2e runs. The invite.post.js dev short-circuit also
depends on this env var to skip the Resend call (no RESEND_API_KEY
in CI).
A11y bug: /board contrast violations (since fixed via --text-faint).
Wave-Slack: /api/auth/member missing slackInvited (fixed), markSlackInvited
non-reactive (fixed), deprecated slackInviteStatus serialization (fixed),
spec-vs-UI wave-language mismatch.
Known gotchas: /admin/series-management Delete is a no-op for empty
series; past-deadline and sold-out events render identically.
Simplify follow-ups: STATUS_LABELS dedup completed.
E2e infrastructure gaps: other email routes still send live in dev,
no dev seeder for arbitrary member status, SSR useFetch blocks
page.route mocking, self-cancel paid registrations not e2e-tested,
visual snapshot regen process.
Driven by:
- contribution-amount redesign on /join and /accept-invite
- board post card text color fix (a11y)
- --text-faint variable adjustment (a11y)
- STATUS_LABELS softer member-facing copy
- dev-DB seed drift on /events and /connections
Bump --text-faint from #746a58 (4.01:1 on cream surfaces — fails AA)
to #665c4b (4.94:1 — passes AA for small text). Preserves the "quieter
than --text-dim" semantic the variable was named for. Lifts ~33 sites
into compliance with one diff.
Also keeps the BoardPostCard per-selector swap to --text-dim that
shipped with the original /board fix; can revert to --text-faint
in a follow-up now that the variable itself is accessible.
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.
Pre-registrant invite was the only email route calling Resend directly
(bypassing server/utils/resend.js), so dev/e2e runs were dispatching
real email. Gate just the network call; DB updates (jti, status,
activity log) still run. Mirrors the bypass pattern in
server/middleware/03.rate-limit.js.
Other email routes via server/utils/resend.js still send live in dev
mode — wrapper refactor tracked in BACKLOG.
/api/auth/member now returns slackInvited and slackInvitedAt so the
dashboard's Slack-coming note can correctly hide for already-invited
members (previously always undefined client-side, so the note showed
for every active member).
Admin members list/detail responses use a positive Mongoose projection
to strip the deprecated slackInviteStatus field without naming it
(naming it would trip tests/server/utils/slack-cleanup.test.js's
literal-string gate). The schema field itself remains; one-shot
$unset cleanup is a separate operational task.
Login helpers now hit dev endpoints via APIRequestContext instead of
page.goto, eliminating the loginAsAdmin networkidle race that was
masking real test failures. Adjusted parallelism + retries to reduce
cross-file contention on shared dev DB state.
Single source of truth for every open issue across the codebase. Pulls
from LAUNCH_READINESS.md (post-launch sections), TODO.md (deferred
features + simplify follow-ups + wave-Slack pilot), and a fresh sweep
of in-code TODO/FIXME comments.
LAUNCH_READINESS.md now keeps only the pre-cutover deploy checklist and
points to BACKLOG.md for everything else. Cutover note corrected — it
has not happened yet.
Force-added BACKLOG.md despite the /docs/ gitignore rule because
LAUNCH_READINESS.md is tracked and now references it.
Three small wins from the 2026-04-29 simplify-pass review:
- STATUS_LABELS triplication in admin/members/index.vue replaced with v-for
- ImageUpload alt-text input now has :focus styling via scoped CSS
- paymentBridge → signupBridge rename (cookie + functions + JWT scope)
After commit 90acc35 issued the cookie for $0 signups too, the "payment"
framing was wrong — there's no payment in a $0 signup. The cookie is
about bridging the gap between signup-form submit and email verify, not
about payment specifically.
Changes:
- setPaymentBridgeCookie → setSignupBridgeCookie
- getPaymentBridgeMember → getSignupBridgeMember
- Cookie wire name payment-bridge → signup-bridge
- JWT scope payment_bridge → signup_bridge
Touches both /api/helcim/subscription (signup activation) and
/api/helcim/initialize-payment (paid Helcim checkout) which both consume
the cookie. In-flight signup sessions started before this lands will
need to re-submit the form (cookie name mismatch); cutover hasn't
happened yet, so the only impact is local dev sessions.
The alt-text input was hard-coding border/bg via inline style="..." after
the phantom-Tailwind sweep, which can't carry pseudo-class rules.
Per CLAUDE.md, inputs focus to --candle. Moved to a scoped style block
with a real :focus rule.
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.
Ships the 5 launch-flow fixes decided 2026-04-30:
- /join, dashboard, welcome-email copy aligned to monthly-waves model
- welcome email now sends on free /accept-invite activations
- /join signups auto-link to matching PreRegistration records
When a /join submitter's email matches a pending/selected/invited
PreRegistration, mark the pre-reg as accepted and link memberId to the
new Member. Prevents the same person from appearing as both an active
member and an unaccepted pre-registrant. Silent — no email, no UI.
Adds the PreRegistration mock to helcim-customer and free-signup-flow
test suites, since both invoke the customer handler at runtime.
Free invite acceptance previously created a Member and signed them in
without sending the welcome email — pre-registrants got nothing as the
join confirmation. Wire sendWelcomeEmail into the free branch matching
the pattern in members/create.post.js.
Paid /accept-invite activations continue to receive the welcome email
via /api/helcim/subscription on the pending_payment → active transition,
so this only changes the free path.
- /join "How membership works" lists community (not Slack) as a benefit;
adds a note that Slack invitations come in monthly onboarding waves.
- Dashboard slack-coming note drops "2–3 weeks" timeline; uses the same
monthly-waves phrasing.
- Welcome email no longer points new members to Slack (which they don't
yet have access to); directs them to reply instead.
Auto-generated update from Serena — adds new language entries
(ansible, crystal, haxe, hlsl, json, lean4, luau, msl, ocaml,
python_ty, solidity, systemverilog), trims the inline tool list
in favor of a docs link, and adds the 'added_modes' field.
- 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
Test file: drop step markers, regression explainers, and the lead
comment block that restated the contract; hoist the shared subscription
request body to a const; move Member mock defaults into the test that
uses them. Two it() cases unchanged.
Events page: drop WCAG comment that narrated what the
.past-toggle:focus-visible selector already says.
Two tests guarding the regression where /api/helcim/customer skipped
setPaymentBridgeCookie for $0 signups and left the user unable to
complete activation. Second test confirms the auth gate on
/api/helcim/subscription still rejects fresh unauthenticated calls.
Free ($0) signups need the same short-lived bridge cookie as paid signups
so /api/helcim/subscription can identify the member during activation
without a verified auth session. Drops the contributionAmount > 0 guard
that broke free-tier activation in the same flow.
Custom .past-toggle button had no focus indicator — keyboard users
got nothing. Match the canonical WCAG 2.4.7 outline used on .btn
and .zine-select (dashed candle, 3px offset).
Error state and main registration card swap bg-ember-*/border-ember-* and
bg-guild-*/border-guild-* utilities for design tokens in a scoped style
block. Error state uses the codebase's --ember + 8% color-mix pattern;
registration card uses --surface + dashed --border per the zine spec.
Swap bg-guild-*/border-guild-*/text-guild-* utility classes for design tokens
in a scoped style block. Drops rounded-* per the no-rounded-corners rule and
uses dashed borders for the structural block per the zine spec.
Verified clean 2026-04-29: grep for guild-[0-9]|candlelight-[0-9]|ember-[0-9]
across app/layouts/, app/pages/admin/, and app/components/admin/ returns zero
matches. All admin surfaces already use design tokens.
oidc-provider's devInteractions is a quick-start scaffold that, when
enabled, mutates configuration.url to its own urlFor('interaction')
helper — emitting /interaction/UID instead of our /oidc/interaction/UID.
That made /oidc/auth redirect to a 404 in local dev and forced a stale
TODO entry. We already have our own interaction handler at
server/routes/oidc/interaction/[uid].get.ts, so devInteractions is
unnecessary; disabling it makes dev match prod and clears the
oidc-provider warning "your configuration is not in effect".
Sibling sweep to dc2becf: NaturalDateInput.vue and ImageUpload.vue used
candlelight-/ember-/guild-* utility classes that aren't defined in the
project's Tailwind palette and rendered as no-ops. Swapped to inline
styles using --candle, --ember, --text-dim/faint/bright, --border,
--input-bg, --surface. Drag-state and parsed-date notices follow the
color-mix(... 15%) + 1px solid pattern from dc2becf.
Previously the publicTicket comparison block ran whenever a Member record
existed, which surfaced "$0 saved" for cancelled/suspended/guest accounts.
Use the canonical hasMemberAccess helper so only active/pending_payment
members see the savings comparison.
Wave-based onboarding makes the auto-invite + polling path obsolete.
- Removes SlackService.inviteUserToSlack — admins now send invites
through Slack's UI and flip the flag in our admin endpoint.
- Removes the slack_invite_failed admin alert + its detector. The
alert no longer has a meaningful trigger (we don't attempt invites).
- Archives server/utils/checkSlackJoins.js (and its test) under
_archive/ in case the polling pattern is needed again post-pilot.
- Deletes the Nitro plugin that scheduled checkSlackJoins on boot
+ hourly. Nothing in nitro.config / nuxt.config / package.json
registered it elsewhere.
- Drops the slack_invite_failed branch from adminAlerts.test; the
enum slug stays in adminAlertDismissal so historical dismissal
rows continue to validate.
notifyNewMember (vetting-channel notification) and findUserByEmail
(used by the auto-flag helper) are retained.