Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,17 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Generate Prisma client
run: pnpm db:generate

- name: Build Nuxt (pre-compiled server avoids 50s+ Nitro JIT)
run: pnpm build

- name: Run vitest
run: pnpm test
env:
NUXT_USE_BUILD: '1'
NUXT_SESSION_PASSWORD: 'ci-test-secret-min-32-chars-long!!'

e2e:
name: E2E Tests (Playwright)
Expand All @@ -106,6 +110,9 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Generate Prisma client
run: pnpm db:generate

- name: Build Nuxt
run: pnpm build

Expand All @@ -114,6 +121,8 @@ jobs:

- name: Run Playwright E2E tests
run: pnpm test:e2e
env:
NUXT_SESSION_PASSWORD: 'ci-test-secret-min-32-chars-long!!'

- name: Upload Playwright report
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -142,5 +151,8 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Generate Prisma client
run: pnpm db:generate

- name: Build
run: pnpm build
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ All SEO content (FAQ, HowTo, tips, value props, section headings) lives in the `

## Don't

- **NEVER push to `main` without explicit user permission.** `main` auto-deploys to production on Render. Every push causes a multi-minute 502 outage for hundreds of active users. Work on feature branches; the user decides when to merge.
- Change the daily word algorithm — breaks word selection globally
- Add console.logs to production code
- Modify `.nuxt/` or `.output/` manually (auto-generated)
Expand Down
19 changes: 12 additions & 7 deletions components/app/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@
border: 1px solid rgba(0, 0, 0, 0.1);
"
/>
{{ subtitle }}
<template v-if="subtitle!.includes('Unlimited')">
{{ subtitle!.replace('Unlimited', '')
}}<span class="text-accent">Unlimited</span>
</template>
<template v-else>{{ subtitle }}</template>
</button>
</div>

Expand All @@ -84,13 +88,14 @@
aria-label="Game results"
@click="$emit('results')"
>
<Trophy :size="20" aria-hidden="true" />
<BarChart2 :size="20" aria-hidden="true" />
</button>
<!-- Profile + Settings: hidden on mobile (accessible via sidebar) -->
<ClientOnly>
<NuxtLink
v-if="loggedIn"
to="/profile"
class="p-2 text-muted hover:text-ink transition-colors"
class="hidden sm:flex p-2 text-muted hover:text-ink transition-colors"
aria-label="Profile"
>
<img
Expand All @@ -104,18 +109,18 @@
</NuxtLink>
<button
v-else
class="p-2 text-muted hover:text-ink transition-colors"
class="hidden sm:flex p-2 text-muted hover:text-ink transition-colors"
aria-label="Sign in"
@click="openLoginModal()"
>
<User :size="20" aria-hidden="true" />
</button>
<template #fallback>
<span class="p-2 text-muted"><User :size="20" /></span>
<span class="hidden sm:flex p-2 text-muted"><User :size="20" /></span>
</template>
</ClientOnly>
<button
class="p-2 text-muted hover:text-ink transition-colors"
class="hidden sm:flex p-2 text-muted hover:text-ink transition-colors"
aria-label="Settings"
@click="$emit('settings')"
>
Expand All @@ -126,7 +131,7 @@
</template>

<script setup lang="ts">
import { Info, Menu, Settings, Trophy, User } from 'lucide-vue-next';
import { Info, Menu, Settings, BarChart2, User } from 'lucide-vue-next';

const { loggedIn, avatarUrl } = useAuth();
const { openLoginModal } = useLoginModal();
Expand Down
6 changes: 5 additions & 1 deletion components/app/AppShell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
:show-streak="true"
:streak-count="streakCount"
@toggle-sidebar="sidebarOpen = !sidebarOpen"
@streak="navigateTo('/profile')"
@streak="showStreakModal = true"
@settings="showSettings = !showSettings"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
/>
</div>
Expand All @@ -43,6 +43,9 @@
is always available. Game-specific options (difficulty) are inert
when no game is active. Labels fall back to English. -->
<GameSettingsModal :visible="showSettings" @close="showSettings = false" />

<!-- Streak modal — available on all pages -->
<GameStreakModal :visible="showStreakModal" @close="showStreakModal = false" />
</div>
</template>

Expand Down Expand Up @@ -75,6 +78,7 @@ const props = withDefaults(

const sidebarOpen = ref(false);
const showSettings = ref(false);
const showStreakModal = ref(false);

// Product-wide streak — single source via composable (client-only, returns 0 during SSR)
const { streak: streakCount } = useProductStreak();
Expand Down
6 changes: 5 additions & 1 deletion components/app/AppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,11 @@
<!-- You section -->
<div class="py-4 editorial-rule">
<div class="mono-label px-6 pb-2">You</div>
<SidebarItem icon="BarChart2" label="Statistics" href="/profile#statistics" />
<SidebarItem
icon="ChartNoAxesCombined"
label="Statistics"
href="/profile#statistics"
/>
<SidebarItem icon="Award" label="Badges" href="/profile#badges" />
<SidebarItem icon="Calendar" label="Archive" :href="`/${langCode}/archive`" />
<SidebarItem
Expand Down
2 changes: 2 additions & 0 deletions components/app/SidebarItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
Zap,
Compass,
BarChart2,
ChartNoAxesCombined,
Award,
Calendar,
Settings,
Expand Down Expand Up @@ -92,6 +93,7 @@ const iconMap: Record<string, any> = {
Zap,
Compass,
BarChart2,
ChartNoAxesCombined,
Award,
Calendar,
Settings,
Expand Down
15 changes: 7 additions & 8 deletions components/game/SettingsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,12 @@
<div v-else>
<button
class="w-full px-4 py-2 bg-ink text-paper text-sm font-semibold rounded-md hover:opacity-90 transition-opacity"
@click="authLoginWithGoogle()"
@click="
$emit('close');
openLoginModal();
"
>
Sign in with Google
Sign in
</button>
<p class="text-xs text-center text-muted mt-1">Sync settings across devices</p>
</div>
Expand All @@ -249,12 +252,8 @@ defineEmits<{ close: [] }>();
const settings = useSettingsStore();
const lang = useLanguageStore();
const game = useGameStore();
const {
loggedIn: authLoggedIn,
user: authUser,
loginWithGoogle: authLoginWithGoogle,
logout: authLogout,
} = useAuth();
const { loggedIn: authLoggedIn, user: authUser, logout: authLogout } = useAuth();
const { openLoginModal } = useLoginModal();

/** Easy mode (allow any word) — synced with game store. */
const allowAnyWord = computed({
Expand Down
Loading
Loading