Design and UI tweaks
This commit is contained in:
parent
53f81b3605
commit
d5c91dd66b
4 changed files with 424 additions and 61 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Ghost Guild — Open Backlog
|
||||
|
||||
_Last consolidated: 2026-05-18. Single source of truth for every open issue across the codebase. Pulls from `LAUNCH_READINESS.md`, `TODO.md`, the post-launch backlog memory, and a fresh sweep of in-code TODO/FIXME comments._
|
||||
_Last consolidated: 2026-05-24. Single source of truth for every open issue across the codebase. Pulls from `LAUNCH_READINESS.md`, `TODO.md`, the post-launch backlog memory, and a fresh sweep of in-code TODO/FIXME comments._
|
||||
|
||||
Cutover has not happened yet. Deploy steps + Activation + Open decisions live separately in [`LAUNCH_READINESS.md`](./LAUNCH_READINESS.md). This file is the everything-else.
|
||||
|
||||
|
|
@ -15,6 +15,7 @@ Operational steps that have to run during cutover. Full details + env-var list i
|
|||
- [ ] Provision the Dokploy app, set env vars (full list in LAUNCH_READINESS.md), confirm `BASE_URL` exact-matches the public origin and `NODE_ENV=production`.
|
||||
- [ ] Add the daily Dokploy Scheduled Task that POSTs to `/api/internal/reconcile-payments` with `X-Reconcile-Token`.
|
||||
- [ ] **Run `node scripts/migrate-contribution-amount.cjs --apply` against prod Mongo BEFORE the new code serves traffic.**
|
||||
- [ ] **After deploying `feature/contribution-form-polish`: run `node scripts/migrate-annual-contribution-to-cadence-unit.cjs --apply` against prod Mongo.** Converts existing annual Member rows from monthly-equivalent storage to cadence-unit storage (×12 on `contributionAmount` for `billingCadence='annual'` rows). Idempotent via transient `contributionAmountConverted` marker. Without this step, the UI will render `$15/yr` for existing annual members until they update their contribution.
|
||||
- [ ] Set `NUXT_HELCIM_MONTHLY_PLAN_ID=50302` and `NUXT_HELCIM_ANNUAL_PLAN_ID=50303` in Dokploy.
|
||||
- [ ] Set `NUXT_RECONCILE_TOKEN` to a 32+ char random string.
|
||||
- [ ] Push local `main` to `origin/main`.
|
||||
|
|
@ -47,9 +48,10 @@ Membership status is being decoupled from payment status. Copy + UI gates alread
|
|||
## Known gotchas (post-launch)
|
||||
|
||||
- **Admin edit does not sync Helcim `recurringAmount`.** `/admin/members/[id]` PUT writes `contributionAmount` direct to Mongo by design. Admins must PATCH Helcim manually. The admin form already shows an `--ember`-bordered notice (commit `e756170`); a real sync flow is a future enhancement.
|
||||
- **Cadence switch rejected on active subscriptions.** `server/api/members/update-contribution.post.js:206` refuses cadence changes mid-subscription with a TODO comment pointing here. No UI toggle exists on `/member/account`. Adding cadence switch requires a Helcim subscription replacement flow, not a plain update.
|
||||
- **S2 test fixture id/slug mismatch (local dev only).** Seeded S2 series has `id: 'test-s2-drop-in-allowed'` but `slug: 'test-s2-drop-in-allowed-series'`. Doesn't affect prod — fix the seed script if anyone re-runs fixtures.
|
||||
- **`/admin/series-management` "Delete" button doesn't actually delete.** Click handler iterates events to PUT-unlink each from the series, never calls `DELETE /api/admin/series/:id`. For an empty series the button is a no-op; for a series with events it just orphans them. Either rename to "Unlink events" or add the actual DELETE call. Surfaced by `e2e/admin-series.spec.js` (delete test skipped). Flagged 2026-04-30.
|
||||
- **Cadence switch rejected on active subscriptions.** `server/api/members/update-contribution.post.js:206` refuses cadence changes mid-subscription with a TODO comment pointing here. No UI toggle exists on `/member/account` — `ContributionAmountField` is rendered with `:allow-cadence-change="false"` there. Adding cadence switch requires a Helcim subscription replacement flow, not a plain update.
|
||||
- **`contributionAmount` is now cadence-unit (not monthly-equivalent).** Post `feature/contribution-form-polish`: `Member.contributionAmount` reads as $180 for "$180/year" annual members, not $15. Server no longer multiplies by 12 anywhere; UI uses `formatContribution(amount, cadence)` from `app/config/contributions.js`. See memory `project_contribution_form_polish`.
|
||||
- ~~**S2 test fixture id/slug mismatch (local dev only).**~~ Verified resolved 2026-05-24: no `test-s2-drop-in-allowed` reference remains anywhere in `scripts/`, `tests/`, or JSON — the fixture was removed.
|
||||
- ~~**`/admin/series-management` "Delete" button doesn't actually delete.**~~ Fixed 2026-05-24 (`fix/backlog-batch-2026-05-24`): `deleteSeries` (`app/pages/admin/series/index.vue`) now calls `DELETE /api/admin/series/[id]` (the endpoint already unlinks events server-side), replacing the redundant per-event PUT loop. `e2e/admin-series.spec.js` delete test un-skipped.
|
||||
- **Past-deadline events and sold-out events render identically.** `EventTicketPurchase.vue` falls through to "Event Sold Out" panel for both `tickets.available.reason === 'Registration deadline has passed'` and zero-stock cases. If "Registration closed" is meant to read differently from "Sold out," add a distinct branch. Flagged 2026-04-30 (no e2e written — gated on this UX decision).
|
||||
|
||||
---
|
||||
|
|
@ -57,7 +59,7 @@ Membership status is being decoupled from payment status. Copy + UI gates alread
|
|||
## Accessibility / a11y
|
||||
|
||||
- [ ] **Button minimum target size.** Site-wide `.btn` renders ~35px tall. WCAG AA 2.5.8 (24×24) passes; AAA 2.5.5 (44×44) fails. Bumping padding affects every button — design call, not a drop-in fix. Flagged 2026-04-11.
|
||||
- [ ] **`/board` color-contrast violations (WCAG AA).** `.block-label` ("Offering" tag) and `.slack-handle` use `#746a58` on `#e8dfc8` → 4.01:1; AA needs 4.5:1 for small text. Surfaced by `e2e/a11y.spec.js` (the `/board` route fails; test is intentionally left red until fixed). Likely a single CSS variable adjustment. Flagged 2026-04-30.
|
||||
- ~~**`/board` color-contrast violations (WCAG AA).**~~ Verified done 2026-05-24: `.block-label` and `.slack-handle` in `BoardPostCard.vue` now use `var(--text-dim)` on the cream card bg (code comments note `--text-faint` was insufficient there). `--text-faint` itself was also darkened from `#746a58` to `#665c4b` (`main.css:33`, 4.94:1 on `--surface`).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -72,9 +74,9 @@ Membership status is being decoupled from payment status. Copy + UI gates alread
|
|||
|
||||
## Wave-Slack pilot follow-ups
|
||||
|
||||
- [ ] **`/api/auth/member` doesn't return `slackInvited`.** Dashboard's Slack-coming note is gated on `memberData.slackInvited`, which is always `undefined` client-side, so the note shows for *every* active member regardless of state. Real bug. Add `slackInvited` (and `slackInvitedAt`) to the auth/member response. Surfaced by wave-slack §7.2 e2e (skipped pending this fix). Flagged 2026-04-30.
|
||||
- [ ] **Admin members list row mutation isn't reactive.** `markSlackInvited` in `app/pages/admin/members/index.vue` does `Object.assign(member, res.member)` on a plain object inside a `useFetch` array; Vue doesn't react, so the "Mark as Slack invited" button stays visible until a manual reload. Fix: `members.value[i] = { ...members.value[i], ...res.member }` or `splice`. Detail page uses the right pattern (covered by §6.6). Surfaced by wave-slack §6.2 e2e (skipped pending this fix). Flagged 2026-04-30.
|
||||
- [ ] **Deprecated `slackInviteStatus` field still serialized.** Removed from UI but still on `Member` documents and the `/api/admin/members` payload. Project it away in the API response and run a one-shot `$unset` cleanup. Surfaced by wave-slack §6.7 e2e. Flagged 2026-04-30.
|
||||
- ~~**`/api/auth/member` doesn't return `slackInvited`.**~~ Verified done 2026-05-24: `server/api/auth/member.get.js:20-21` returns both `slackInvited` and `slackInvitedAt`.
|
||||
- ~~**Admin members list row mutation isn't reactive.**~~ Verified done 2026-05-24: `markSlackInvited` (`app/pages/admin/members/index.vue:843`) now does `members.value[idx] = { ...members.value[idx], ...res.member }`.
|
||||
- [ ] **Deprecated `slackInviteStatus` — optional DB cleanup only.** No longer serialized: removed from the `Member` schema and the UI, and `server/api/admin/members.get.js` projects only current schema paths (`Object.keys(Member.schema.paths)`), so stale doc values can't leak into the payload. Remaining: an optional one-shot `$unset` to tidy old Mongo docs — nothing reads the field. Narrowed 2026-05-24.
|
||||
- [ ] **Spec vs shipped-UI mismatch on wave language.** `docs/specs/wave-based-slack-onboarding-tests.md` §7.5 asserts "no wave/cohort/batch language" in the dashboard note, but the shipped welcome-email and dashboard copy say "monthly onboarding waves." Decide which side wins; update the other.
|
||||
- [ ] **E2E coverage for `e2e/wave-slack-onboarding.spec.js`.** 9 of 16 scaffolded tests now passing (admin Slack-invited button + non-trivial dashboard cases). 7 remain skipped pending the bugs above (7.2, 6.2), seeding gaps (7.4 — no dev endpoint to mint members of arbitrary status), Open Questions (7.8, 6.9), or spec-vs-UI conflicts (7.5, 6.7).
|
||||
- [ ] **Pilot exit decision (~8 weeks post-launch).** Either restore `server/_archive/utils/checkSlackJoins.js` + its plugin if polling is needed, or delete the archive permanently. Driven by whether the manual-invite cadence is sustainable post-pilot.
|
||||
|
|
@ -82,20 +84,35 @@ Membership status is being decoupled from payment status. Copy + UI gates alread
|
|||
|
||||
---
|
||||
|
||||
## Contribution-form-polish follow-ups
|
||||
|
||||
From `feature/contribution-form-polish` (14 commits, see memory `project_contribution_form_polish`). Captured 2026-05-23.
|
||||
|
||||
- ~~**`account.vue` `currentContributionLabel` uses long-form `/year`, `/month`.**~~ Done 2026-05-24: now returns `formatContribution(amount, cadence.value)` (`/yr`, `/mo`).
|
||||
- [ ] **Annual-abandoned-signup `billingCadence` corruption — `/join` half still open + existing-row cleanup.** A member who picks annual but abandons before `/api/helcim/subscription` runs is left at `billingCadence: 'monthly'` + cadence-unit `contributionAmount` (e.g. 180) + `status: 'pending_payment'`, rendering `$180/mo` in admin views (member can't see it — not yet signed in). `billingCadence` is only corrected to `'annual'` once the subscription call runs.
|
||||
- ~~Invite-accept path.~~ Fixed 2026-05-24 (`c3b1c59`): `inviteAcceptSchema` now accepts `cadence`, `accept-invite.vue` sends it, and `accept.post.js` persists `billingCadence` at `Member.create` ($0 forced to `'monthly'`).
|
||||
- ~~`/join` path had the same gap.~~ Fixed 2026-05-24 (`426f233`): `helcimCustomerSchema` accepts `cadence`, `join.vue` sends it to `/api/helcim/customer`, and `customer.post.js` persists `billingCadence` in both the new-member create branch and the guest-upgrade `$set` branch ($0 forced to `'monthly'`). (`/api/members/create` carries the same omission but is uncalled by any frontend — left as-is.)
|
||||
- ~~One-shot cleanup for rows corrupted before these fixes.~~ Verified no-op 2026-05-24: swept the `ghost-guild` DB (59 members) — 0 rows with `billingCadence: 'annual'`, 0 with an annual-magnitude `contributionAmount` (≥60), and all 17 `pending_payment` rows sit at monthly presets {0,5,15,30,50}. No corruption exists, so no script was written or run. Because both code fixes land before cutover, there's no pre-fix production window in which this corruption could occur. (If the fixes somehow ship *after* real annual signups have happened, re-run the sweep post-launch.)
|
||||
- [ ] **`/member/payment-setup` is monthly-only by design — lossy `Math.floor(amount/12)` redirect from account.vue.** Spec already deferred this. Annual members landing on payment-setup via account.vue's `requiresPaymentSetup` recovery path get their cadence-unit amount floor-divided to monthly at the redirect (e.g. $100/yr → tier=8 → $96/yr after re-charge). Reachable only for corrupt-state annual members who lost their `helcimSubscriptionId`. Long-term: payment-setup accepts `?cadence=` and passes through to `update-contribution`. WHY comment lives at `account.vue:472-474`.
|
||||
- [ ] **No unit tests on `ContributionAmountField.vue`.** The cadence-toggle math (×12 / floor(/12)) and soft-max threshold ($500/mo equiv) live entirely in the component. E2E tests on `/join` + `/accept-invite` cover the happy paths. A small Vitest suite around toggle math, preset selection, and emission would protect against regressions as the component gains more consumers. _Deferred 2026-05-24: a mounted test needs `@vue/test-utils` (not installed; not bundled by `@nuxt/test-utils`) plus a `~` alias / Nuxt env for the `client` vitest project. Out of scope for the low-risk batch — pick up with a deliberate decision on the test approach._
|
||||
- [ ] **Migration script TOCTOU.** `scripts/migrate-annual-contribution-to-cadence-unit.cjs` reads then writes in two separate ops per doc. If a member updates their contribution via the UI between the read and the write, the migration overwrites the new value with `(stale × 12)`. Mitigation: run during a brief maintenance window, or refactor to a single `updateOne` with `$mul` + marker guard. Low-impact given the small annual-member population.
|
||||
|
||||
---
|
||||
|
||||
## Simplify-pass follow-ups (still open)
|
||||
|
||||
Items surfaced during the 2026-04-29 /simplify review. The 2026-04-30 small-wins batch shipped 3 items (STATUS_LABELS dedup, ImageUpload focus, signupBridge rename). Remaining:
|
||||
|
||||
- [ ] **Extract `.tint-candle` / `.tint-ember` utility classes.** The `color-mix(in srgb, var(--candle) 15%, transparent)` + matching border pattern is now inlined as `style=""` in ~9 sites across `EventSeriesTicketCard.vue`, `SeriesPassPurchase.vue`, `NaturalDateInput.vue`, `ImageUpload.vue`. Promote to utility classes in `app/assets/css/main.css` so future tints don't keep multiplying inline styles (and so `:hover` / `:focus` variants are reachable).
|
||||
- [ ] **Audit `member &&` truthy checks in sibling ticket/subscription routes.** Commit `f66455e` fixed `server/api/events/[id]/tickets/available.get.js:115` to use `hasMemberAccess(member)`. Same anti-pattern likely exists in adjacent routes (`tickets/purchase.post.js`, subscription endpoints). Guests/suspended/cancelled members would currently look like full members for any feature gated on truthiness alone.
|
||||
- [ ] **STATUS_LABELS dedup — verify.** The 2026-04-30 small-wins batch claimed STATUS_LABELS dedup, but `e2e/admin-members.spec.js` expansion found an inline copy still at `app/pages/admin/members/index.vue:491` and another at `app/pages/member/account.vue:420`. Either the previous dedup was partial or a new copy was reintroduced — confirm and finish dedup into a shared constants module.
|
||||
- [ ] **`app/pages/admin/members/[id].vue` status select still hand-written.** Commit `441a5f5` aligned the index page's status `<select>` to `STATUS_LABELS`, but the detail page (`[id].vue`) still hand-codes raw status options. Refactor to drive from the same constant.
|
||||
- ~~**Extract `.tint-candle` / `.tint-ember` utility classes.**~~ Done 2026-05-24 (`fix/backlog-batch-2026-05-24`): added `.tint-candle` / `.tint-ember` to `app/assets/css/main.css` and replaced the five inline `style=""` candle tints in `SeriesPassPurchase.vue` + `EventSeriesTicketCard.vue` with the class. (`NaturalDateInput.vue` had no such inline tint; `ImageUpload.vue:29` is a conditional drag-state `:style` with only `border-color`, left as-is.)
|
||||
- ~~**Audit `member &&` truthy checks in sibling ticket/subscription routes.**~~ Verified resolved 2026-05-24: `tickets/purchase.post.js:34` already gates access/pricing via `hasMemberAccess(member) ? member : null`; the remaining `member &&` lines are harmless existence checks (recording `memberId`, the auto-login decision). No truthy pricing/access gating remains in `events`/`members`/`helcim` routes.
|
||||
- ~~**STATUS_LABELS dedup — verify.**~~ Verified done 2026-05-24: single shared module `app/config/memberStatus.js`; `index.vue`, `account.vue`, and `[id].vue` all import `STATUS_LABELS` with no inline copies remaining.
|
||||
- ~~**`app/pages/admin/members/[id].vue` status select still hand-written.**~~ Verified done 2026-05-24: `[id].vue:65-67` drives the status `<select>` from `STATUS_LABELS` (`v-for="(label, value) in STATUS_LABELS"`).
|
||||
|
||||
---
|
||||
|
||||
## Optional / low-priority
|
||||
|
||||
- [ ] **Welcome-email Slack-timing mention.** Currently the welcome email doesn't mention Slack timing — the dashboard carries that note. Could add a one-line "Slack invitation comes in monthly waves — there may be a short wait" if the dashboard turns out not to be enough signal.
|
||||
- ~~**Welcome-email Slack-timing mention.**~~ Done 2026-05-24 (`fix/backlog-batch-2026-05-24`): `sendWelcomeEmail` (`server/utils/resend.js`) now includes "Your Slack invitation arrives in our monthly onboarding waves — there may be a short wait."
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -103,7 +120,7 @@ Items surfaced during the 2026-04-29 /simplify review. The 2026-04-30 small-wins
|
|||
|
||||
Surfaced during the 2026-04-30 e2e expansion. None block a green suite, but each blocks specific coverage from being added.
|
||||
|
||||
- [ ] **Other email routes still send real emails in dev mode.** The `ALLOW_DEV_TEST_ENDPOINTS` short-circuit was added to `server/api/admin/pre-registrants/invite.post.js` (which calls `new Resend(...)` directly), but the five wrapper functions in `server/utils/resend.js` (event registration, cancellation, waitlist, series pass, welcome) still dispatch live. Either add the same gate to each wrapper, or refactor the wrappers into a single `sendEmail({ from, to, subject, text, html })` helper holding the gate centrally — would also dedupe ~5 near-identical try/catch blocks.
|
||||
- ~~**Other email routes still send real emails in dev mode.**~~ Fixed 2026-05-24 (`fix/backlog-batch-2026-05-24`): all five `server/utils/resend.js` wrappers now early-return via a shared `skipEmailInDev` guard when `ALLOW_DEV_TEST_ENDPOINTS=true`, mirroring `pre-registrants/invite.post.js`. Covered by `tests/server/utils/resend.test.js`. (The wrappers still keep their own try/catch — full `sendEmail` dedup deferred as a non-blocking cleanup.)
|
||||
- [ ] **No dev endpoint to seed members of arbitrary status.** Wave-slack §7.4 (note hidden for suspended/cancelled/guest) is gated on this. `/api/dev/test-login` only mints an `active` admin. A minimal `/api/dev/members.post` accepting `{ email, status, slackInvited, ... }` would unblock many more dashboard-state e2e tests.
|
||||
- [ ] **SSR `useFetch` blocks `page.route` mocking.** Page-level fetches in `[slug].vue` files run during SSR and can't be intercepted client-side. Affects: hidden-event 404 e2e, any test that needs a mocked event payload before client hydration. Either expose a client-side fetch alternative, add a server-side test mock layer, or accept that DB seeding is required for these cases.
|
||||
- [ ] **Self-cancel block on paid event registrations not e2e-tested.** Requires seeding a logged-in member with a paid registration row. Out of scope for this round.
|
||||
|
|
@ -120,5 +137,6 @@ Surfaced during the 2026-04-30 e2e expansion. None block a green suite, but each
|
|||
- `project_pre_registrants.md` — invitation system + pre-reg lifecycle
|
||||
- `project_helcim_plan_model.md` — cadence-keyed plan model
|
||||
- `project_contribution_amount_redesign.md` — arbitrary $ amount + guidance presets
|
||||
- `project_contribution_form_polish.md` — ContributionAmountField component + cadence-unit contract shift (supersedes the cadence-math claims in `project_contribution_amount_redesign`)
|
||||
- `project_receipts.md` — Phase 1 done, Phase 2 pending
|
||||
- `project_email_automation_future.md` — Tranzac reference for full system
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue