diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 4167651..4b39e60 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -273,14 +273,6 @@ p a, blockquote a { min-width: 0; } -/* ---- Nuxt UI placeholder contrast ---- - Default Nuxt UI placeholder uses `text-dimmed` (#a6a09b) which fails WCAG - AA on cream and white backgrounds (≈2.4:1). Override globally to --text-dim - so USelect/USelectMenu placeholders meet the 4.5:1 ratio. */ -[data-slot="placeholder"] { - color: var(--text-dim); -} - /* ---- SHARED USelectMenu STYLES ---- Apply via: Classes are global (not scoped) because Nuxt UI portals the popup content to body. */ diff --git a/app/pages/join.vue b/app/pages/join.vue index 5a256e8..32cc816 100644 --- a/app/pages/join.vue +++ b/app/pages/join.vue @@ -1017,7 +1017,6 @@ onUnmounted(() => { .checkbox-label a, .checkbox-label :deep(a) { color: var(--candle); - text-decoration: underline; } /* ---- ERROR & SUCCESS BOXES ---- */ diff --git a/app/pages/member/profile.vue b/app/pages/member/profile.vue index 2f7f54f..0679c48 100644 --- a/app/pages/member/profile.vue +++ b/app/pages/member/profile.vue @@ -712,11 +712,11 @@ useHead({ .posts-empty-link { color: var(--candle); - text-decoration: underline; + text-decoration: none; } -.timezone-select :deep([data-slot="placeholder"]) { - color: var(--text-dim); +.posts-empty-link:hover { + text-decoration: underline; } .posts-list { diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-desktop-chromium-darwin.png index 994e117..f1a80e7 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-auth-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-auth-chromium-darwin.png index df45026..0765129 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-auth-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-auth-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-chromium-darwin.png index 76301e1..8c4ad03 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-dashboard-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-dashboard-desktop-chromium-darwin.png index b2a406b..8aeacc2 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-dashboard-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-dashboard-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-members-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-members-desktop-chromium-darwin.png index 8da2b9f..7838ee3 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-members-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-members-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-desktop-chromium-darwin.png index e6e2013..6e601d3 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-mobile-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-mobile-chromium-darwin.png index d4b7b0e..4cc7b93 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-mobile-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-mobile-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-desktop-chromium-darwin.png index 9aef789..b7875d9 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-mobile-auth-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-mobile-auth-chromium-darwin.png index 72e1d0a..4fffa70 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-mobile-auth-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-mobile-auth-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-desktop-chromium-darwin.png index 3cac92f..40d1ee5 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-mobile-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-mobile-chromium-darwin.png index babc01e..2c631f7 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-mobile-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-mobile-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-desktop-chromium-darwin.png index 264d678..880a306 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-mobile-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-mobile-chromium-darwin.png index 3a29d00..46c53d7 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-mobile-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-mobile-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-desktop-chromium-darwin.png index cc7d38b..90caa26 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-mobile-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-mobile-chromium-darwin.png index 4ef6dec..c7a4e0e 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-mobile-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-mobile-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-desktop-chromium-darwin.png index 676e384..8f1a75e 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-desktop-chromium-darwin.png index 3262023..280b1f5 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-mobile-auth-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-mobile-auth-chromium-darwin.png index 3016712..5af2e0b 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-mobile-auth-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-mobile-auth-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-desktop-chromium-darwin.png index a810ac5..44c77f9 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-mobile-auth-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-mobile-auth-chromium-darwin.png index da76ae2..b6edc45 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-mobile-auth-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-mobile-auth-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-desktop-chromium-darwin.png index aa7d97d..132c9ea 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-detail-desktop-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-detail-desktop-chromium-darwin.png index af1f86c..6f05986 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-detail-desktop-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-detail-desktop-chromium-darwin.png differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-mobile-chromium-darwin.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-mobile-chromium-darwin.png index 4ef6dec..c8f1848 100644 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-mobile-chromium-darwin.png and b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-mobile-chromium-darwin.png differ diff --git a/e2e/admin-board-channels.spec.js b/e2e/admin-board-channels.spec.js index 91111cf..7a2d1e7 100644 --- a/e2e/admin-board-channels.spec.js +++ b/e2e/admin-board-channels.spec.js @@ -11,7 +11,6 @@ test.describe('Admin board channels page', () => { test('create, edit, and delete a channel', async ({ adminPage }) => { await adminPage.goto('/admin/board-channels') - await adminPage.waitForLoadState('networkidle') await expect(adminPage.getByRole('heading', { name: 'Board Channels' })).toBeVisible({ timeout: 15000, }) @@ -19,14 +18,14 @@ test.describe('Admin board channels page', () => { const suffix = Date.now().toString().slice(-6) const channelName = `e2e-channel-${suffix}` const editedName = `e2e-channel-${suffix}-edited` + const slackId = `C${suffix}XYZ` // --- Create --- - // Create flow only takes a name; the Slack channel ID is auto-assigned on - // creation and only becomes editable in the Edit modal. await adminPage.getByRole('button', { name: '+ New Channel' }).click() await expect(adminPage.getByRole('heading', { name: 'New Channel' })).toBeVisible() - await adminPage.locator('input[placeholder="e.g., coop-formation"]').fill(channelName) + await adminPage.locator('input[placeholder="e.g., #coop-formation"]').fill(channelName) + await adminPage.locator('input[placeholder="C0123456789"]').fill(slackId) // Select the first available cooperative tag if any are present const firstTagCheckbox = adminPage.locator('.tag-select input[type="checkbox"]').first() @@ -45,7 +44,7 @@ test.describe('Admin board channels page', () => { await row.getByRole('button', { name: 'Edit' }).click() await expect(adminPage.getByRole('heading', { name: 'Edit Channel' })).toBeVisible() - const nameInput = adminPage.locator('input[placeholder="e.g., coop-formation"]') + const nameInput = adminPage.locator('input[placeholder="e.g., #coop-formation"]') await nameInput.fill(editedName) await adminPage.getByRole('button', { name: 'Save Changes' }).click() diff --git a/e2e/auth.spec.js b/e2e/auth.spec.js index f7c60f0..5b0daca 100644 --- a/e2e/auth.spec.js +++ b/e2e/auth.spec.js @@ -44,7 +44,6 @@ test.describe('Authentication flows', () => { test('logout clears auth', async ({ page }) => { await loginAsAdmin(page) await page.goto('/admin') - await page.waitForLoadState('networkidle') await expect(page.locator('.admin-tag')).toBeVisible({ timeout: 15000 }) // Set up response listener BEFORE clicking to avoid race diff --git a/e2e/board.spec.js b/e2e/board.spec.js index 945cd4d..6f2fe7e 100644 --- a/e2e/board.spec.js +++ b/e2e/board.spec.js @@ -9,7 +9,6 @@ test.describe('Board page', () => { test('clicking New Post reveals the form', async ({ memberPage }) => { await memberPage.goto('/board') - await memberPage.waitForLoadState('networkidle') await expect(memberPage.getByRole('button', { name: '+ New Post' }).first()).toBeVisible({ timeout: 15000, }) @@ -41,7 +40,6 @@ test.describe('Board page', () => { test('create, edit, and delete own post', async ({ memberPage }) => { await memberPage.goto('/board') - await memberPage.waitForLoadState('networkidle') await expect(memberPage.getByRole('button', { name: '+ New Post' }).first()).toBeVisible({ timeout: 15000, }) @@ -57,7 +55,7 @@ test.describe('Board page', () => { await memberPage.locator('#post-title').fill(originalTitle) await memberPage.locator('#post-seeking').fill('Playwright test seeking text') - await memberPage.getByRole('button', { name: 'Post', exact: true }).click() + await memberPage.getByRole('button', { name: 'Post' }).click() await expect(memberPage.getByRole('heading', { name: originalTitle })).toBeVisible({ timeout: 10000, @@ -77,10 +75,10 @@ test.describe('Board page', () => { timeout: 10000, }) - // --- Delete (in-card two-step confirm; not a native dialog) --- + // --- Delete (confirm dialog) --- + memberPage.once('dialog', (dialog) => dialog.accept()) const editedCard = memberPage.locator('article.board-post', { hasText: editedTitle }) await editedCard.getByRole('button', { name: 'Delete' }).click() - await editedCard.getByRole('button', { name: 'Confirm' }).click() await expect(memberPage.getByRole('heading', { name: editedTitle })).not.toBeVisible({ timeout: 10000, diff --git a/e2e/join-flow.spec.js b/e2e/join-flow.spec.js index 0062b95..919e9ec 100644 --- a/e2e/join-flow.spec.js +++ b/e2e/join-flow.spec.js @@ -68,12 +68,8 @@ test.describe('Join page — member signup flow', () => { await page.locator('#join-name').fill('Test User') await expect(page.locator('.form-submit')).toBeDisabled() - // Fill email — agreement still unchecked, so still disabled + // Fill email too — now all fields are populated and button should be enabled await page.locator('#join-email').fill('incomplete-test@example.com') - await expect(page.locator('.form-submit')).toBeDisabled() - - // Check the Community Guidelines agreement — now all required fields satisfied - await page.getByRole('checkbox', { name: /Community Guidelines/ }).check() await expect(page.locator('.form-submit')).toBeEnabled() }) @@ -87,9 +83,8 @@ test.describe('Join page — member signup flow', () => { await page.locator('#join-name').fill('E2E Test User') await page.locator('#join-email').fill(uniqueEmail) await page.locator('#circle-community').check({ force: true }) - // Contribution is now a numeric input with preset chips, not a select - await page.locator('#join-contribution').fill('0') - await page.getByRole('checkbox', { name: /Community Guidelines/ }).check() + await page.locator('#join-contribution').click() + await page.getByRole('option', { name: '$0/mo' }).click() await expect(page.locator('.form-submit')).toBeEnabled() @@ -98,10 +93,8 @@ test.describe('Join page — member signup flow', () => { await page.locator('.form-submit').click() - // Free tier flips the SignupFlowOverlay into its success state - await expect( - page.getByRole('heading', { name: 'Welcome to Ghost Guild!' }) - ).toBeVisible({ timeout: 15000 }) + // Free tier creates subscription then shows confirmation (step 3) + await expect(page.locator('.success-box')).toBeVisible({ timeout: 15000 }) }) test('duplicate email shows error', async ({ page }) => { @@ -116,13 +109,12 @@ test.describe('Join page — member signup flow', () => { await page.locator('#join-name').fill('Dup Test User') await page.locator('#join-email').fill(duplicateEmail) await page.locator('#circle-community').check({ force: true }) - await page.locator('#join-contribution').fill('0') - await page.getByRole('checkbox', { name: /Community Guidelines/ }).check() + await page.locator('#join-contribution').click() + await page.getByRole('option', { name: '$0/mo' }).click() await page.locator('.form-submit').click() - // Helcim 409 puts SignupFlowOverlay into its error state - const overlayError = page.locator('.signup-flow-overlay .error-box') - await expect(overlayError).toBeVisible({ timeout: 10000 }) - await expect(overlayError).toContainText(/already/i) + // Should show an error about the email already existing + await expect(page.locator('.error-box')).toBeVisible({ timeout: 10000 }) + await expect(page.locator('.error-box')).toContainText(/already/i) }) }) diff --git a/e2e/member-profile.spec.js b/e2e/member-profile.spec.js index 22af212..0fa51c8 100644 --- a/e2e/member-profile.spec.js +++ b/e2e/member-profile.spec.js @@ -3,11 +3,9 @@ import { test, expect } from './helpers/fixtures.js' test.describe('Member profile page', () => { test('profile page loads', async ({ adminPage }) => { await adminPage.goto('/member/profile') - await adminPage.waitForLoadState('networkidle') // Auth is checked client-side in onMounted — wait for profile form to render await expect(adminPage.getByText('Edit Profile')).toBeVisible({ timeout: 15000 }) - // Verify a stable structural section label, not transient marketing copy - await expect(adminPage.getByText('Show in Member Directory')).toBeVisible() + await expect(adminPage.getByText('How you appear to other members')).toBeVisible() }) test('form fields are present', async ({ adminPage }) => { @@ -26,7 +24,6 @@ test.describe('Member profile page', () => { test('bio field accepts input', async ({ adminPage }) => { await adminPage.goto('/member/profile') - await adminPage.waitForLoadState('networkidle') await expect(adminPage.getByText('Edit Profile')).toBeVisible({ timeout: 15000 }) const bio = adminPage.locator('textarea[placeholder*="Share your background"]') diff --git a/server/middleware/03.rate-limit.js b/server/middleware/03.rate-limit.js index 5ee6103..ac87ef7 100644 --- a/server/middleware/03.rate-limit.js +++ b/server/middleware/03.rate-limit.js @@ -43,11 +43,6 @@ export default defineEventHandler(async (event) => { const path = getRequestURL(event).pathname if (!path.startsWith('/api/')) return - // Bypass rate limiting in test/dev opt-in mode so parallel E2E runs from a - // single IP (127.0.0.1) do not exhaust the per-IP budget. Mirrors the gate - // used by /api/dev/* endpoints — only set in development and by Playwright. - if (process.env.ALLOW_DEV_TEST_ENDPOINTS === 'true') return - const ip = getClientIp(event) try {