2.0.0
Released
Features
#267 — Test infrastructure (Playwright E2E + Vitest unit tests). Set up the full testing infrastructure for Questify 2.0. Added
e2e/helpers/auth.tswith Supabase Admin API helpers (createTestUser,deleteTestUser,loginAs,signUpViaUI) that provision/teardown real test users usingE2E_SUPABASE_SERVICE_ROLE_KEY; emails use the.invalidTLD so no real mail is ever sent. Addede2e/fixtures.tswith custom Playwright fixtures (testUser,authenticatedPage) that auto-skip when the service role key is absent, keeping the smoke run clean in environments without live Supabase creds. Addede2e/global-setup.tswired intoplaywright.config.tsviaglobalSetup— logs whether live or smoke mode is active and validates required env vars before the suite starts. Expandede2e/auth.spec.tswith live login and signup round-trip tests (gated onE2E_SUPABASE_SERVICE_ROLE_KEY) alongside login-form validation smoke tests. Addede2e/quests.spec.tscovering create-quest, add-task, and complete-task flows for authenticated users. Addedsrc/lib/auth/validators.edge.test.tswith Vitest boundary-condition tests: exact-minimum password length, one-below-minimum rejection, name 120-char limit, whitespace-only name rejection, email trimming, and case-sensitive confirm-password mismatch. Addedsrc/lib/tasks/types.edge.test.tswith edge-case coverage fornextTaskStatus(blocked→done shortcut),sortTasksByOrder(tie-breaking bycreatedAt, immutability),reorderTaskIds(at-boundary no-ops, unknown id), andapplyTaskOrder(omitted ids excluded, empty id list). Addedtest:coveragescript toapps/web/package.jsonand@vitest/coverage-v8devDependency (runpnpm installto regenerate the lockfile). Updatedci.yml: thetestjob now runspnpm --filter web test:coverageto generate lcov/html/json-summary artifacts; a newcoverage-commentjob usesdavelosert/vitest-coverage-report-action@v2to post a coverage summary comment on every PR from the same repo;ci-passupdated to includecoverage-commentin its needs graph (skipped is treated as OK for push events and forks).#266 — Location-Based Task Check-ins (proximity-triggered check-in prompts). Allows tasks to have an optional physical location anchor (name + WGS-84 lat/lng + configurable radius, default 100 m). Gated behind
NEXT_PUBLIC_FEATURE_LOCATION_CHECKINS. New Supabase migration addslocation_name,location_lat,location_lng,location_radius_mcolumns totasks(with check constraints enforcing lat/lng co-presence and a positive radius), and auser_location_consenttable (one row per user, RLS-restricted to the owning user) that persists the user's explicit opt-in before any geolocation API call is made. Updatedapps/web/src/types/supabase.tswith the new task columns and the fulluser_location_consenttable shape. Addedapps/web/src/lib/location/:types.ts(TaskLocation,GeolocationState,ConsentState,ProximityHit,TaskWithLocation),haversine.ts(great-circle distance in metres),use-geolocation.ts(watchPositionwrapper with idle/pending/granted/denied/unavailable state machine, cleans up on unmount),use-proximity.ts(computes nearby tasks from current position using haversine, firesonProximityEnteron leading edge only — no repeat callbacks per task per entry),use-location-consent.ts(loads/upserts consent fromuser_location_consentvia the browser Supabase client; exposesgrantConsent()andrevokeConsent()). Added four new components underapps/web/src/components/location/:TaskLocationPicker(expandable inline form with place-name input, lat/lng fields, and a radius slider from 25 m to 5 km),LocationConsentDialog(opt-in prompt rendered before the geolocation API is ever called; privacy copy notes position is never stored),LocationCheckinBanner(proximity alert card with "Check in" — sets task toin_progress— and "Dismiss" per-session),NearMeFilterButton(toggle with pulse animation when active). Task manager updates:locationCheckinsEnabled+userIdprops forwarded from both quest page server components;useGeolocation/useProximity/useLocationConsentwired to location state; Near Me toggle in the header gates on consent and requests geolocation;LocationConsentDialogshown inline when consent is unknown; check-in banners rendered for all undismissed proximity hits; "Near me" active state filters displayed tasks to those whose anchor falls within the user's radius;TaskLocationPickerembedded in both create and edit forms;MapPinbadge shown on task cards that have a location. UpdatedQuestTaskwith alocation: TaskLocation | nullfield andmapTaskRowto derive it from the new DB columns. ExtendedcreateTaskPayloadSchemaandrawUpdateTaskPayloadSchemainserver.tswithlocationName,locationLat,locationLng,locationRadiusMZod fields;UpdateTaskPayloadtype extended to match;parseUpdateTaskPayloadpasses all four through;POST /api/tasksandPATCH /api/tasks/[id]persist the fields to Supabase. Updated test fixtures to addlocation: null. Verified withtsc --noEmit(0 errors) andeslint --max-warnings 0(0 warnings).#265 — Annual 'Well Played' Recap (year-end summary). Added a personalised Spotify-Wrapped-style year-end recap accessible from the profile page year-round. New Supabase migration adds
well_played_recaps(one cached row per user/year, unique constraint) and acompute_well_played_recap(p_user_id, p_year)PL/pgSQL function that aggregates quests completed, tasks marked done, AI investigations run, total active days (distinct calendar days with a completed task), longest consecutive-day streak, achievements unlocked, top quest-pack category, and favourite quest pack — then upserts the result so subsequent calls refresh rather than duplicate. The function isSECURITY DEFINERand grantsEXECUTEonly to theauthenticatedrole; RLS policies restrict reads and writes to the owning user. Addedapps/web/src/lib/recap/types.ts(WellPlayedRecap,WellPlayedRecapRow,mapRecapRow()) andapps/web/src/lib/recap/queries.ts(computeRecap(),getRecap(),listRecapYears()), following the same(supabase as any).rpc(...)cast pattern used elsewhere for functions not yet reflected in the generated types. Addedapps/web/src/components/recap/well-played-card.tsx: a celebratoryWellPlayedCardcomponent with a violet-fuchsia-pink gradient hero banner, machine-generated narrative sentence, a 6-stat grid (quests, tasks, investigations, active days, streak, badges), a highlights row for top category and favourite pack, and a Share button using the Web Share API with a clipboard-copy fallback. Added/profile/recap/[year]server page that computes stats on load (with a Refresh button), sets Open Graph metadata, and shows the computed-at timestamp. Added/api/recap/[year]/ogedge route using Next.jsImageResponseto produce a 1200×630 OG card for social sharing. The profile page now includes a "Well Played recaps" section at the bottom with link buttons for each year that has a cached recap plus always the current year, so users can generate their first recap immediately. Verified withtsc --noEmit(0 errors) andeslint --max-warnings 0(0 warnings).#264 — Admin panel (users, roles, privileges, quest packs, personas, audit log). Ported the full admin surface to Next.js. A shared
app/admin/layout.tsxprovides sidebar navigation (desktop) and a top-bar (mobile) linking all eight admin sections, with a server-sideisAdminOrSuperAdmin()gate that returnsnotFound()for authenticated non-admins; unauthenticated visitors are already bounced to/auth/loginby the existingPROTECTED_PATH_PREFIXESmiddleware. Added/admin(dashboard with user/quest/pack/role stat cards and a recent-activity feed from the audit log),/admin/users(searchable user list with inline role assign/revoke viaUserAdminPanel),/admin/roles(create/rename/delete non-system roles and manage per-role privilege assignments viaRoleAdminPanel, SuperAdmin-only),/admin/privileges(read-only privilege catalog),/admin/personas(create/edit/toggle model personas viaPersonaAdminPanel, requiresmodel_personas.writeprivilege), and/admin/audit-log(paginated, multi-filter append-only audit log — action, target type, actor ID, date range). The existing/admin/quest-packsand/admin/quest-jamspages are linked into the new shared nav without changes. Addedlib/admin/{users,roles,personas,audit}.tsfor typed Supabase queries andlib/admin/{user,role,persona}-actions.tsfor'use server'mutations; every mutating action writes an audit row via the service-role client (bypassing RLS) and callsrevalidatePath. AddedPOST /api/admin/recover-superadmin: a token-gated (BearerADMIN_RECOVERY_TOKEN, min 32 chars) bootstrap endpoint to grant SuperAdmin to any existing user — returns 501 when the env var is unset, idempotent via upsert, records asuperadmin.recoveraudit entry. AddedisAdminPath()helper tolib/auth/redirects.ts. Extended the server env schema inlib/env.tswith the optionalADMIN_RECOVERY_TOKENfield. Verified withtsc --noEmit(0 errors) andeslint --max-warnings 0(0 warnings).#263 — Skill Track Quest Packs (progressive, stage-unlocked learning paths). Added a new
skill_trackQuest Pack type alongside the existingtemplatetype. New Supabase migration adds atypecolumn (template | skill_track) toquest_packs, createsskill_track_stages(ordered stages per track with uniquequest_pack_id, stage_numberconstraint),skill_track_stage_tasks(task templates per stage), anduser_skill_track_progress(one row per user/track recordingcurrent_stage,started_at, andcompleted_at) — all with RLS policies matching the existing pack security model. Addedadvance_skill_track_stage(_quest_pack_id uuid)PL/pgSQL function (security definer) that atomically increments the caller'scurrent_stageor marks the track complete when the final stage is reached. Addedlib/quests/skill-tracks.tswithlistSkillTracks,getSkillTrackBySlug,getUserTrackProgress,getActiveUserTrackProgress,startSkillTrack,advanceSkillTrackStage,getUserSkillTracksWithProgress, andresumeTrackHrefhelpers. Extendedlib/quests/packs.tsto expose thetypefield onQuestPack. Updatedtypes/supabase.tswithQuestPackType, all three new tables, theadvance_skill_track_stageRPC, and thequest_pack_typeenum entry. Quest Pack browser now renders skill track cards with a distinct tinted border/background, aLayersbadge, and a "View Track" CTA in place of the template "Start this Quest" flow. Added/quest-packs/[slug]/trackdetail page showing the ordered stage map with a progress bar, stage-unlock state (completed ✓ / current / locked), per-stage collapsible task lists, and a Resume Track button for users mid-track.SkillTrackStageMapcomponent handles the locked/current/done stage states with appropriate CTAs per state. Admin panel extended with a type toggle to switch between creating a Quest Pack or a Skill Track; the skill track form supports dynamic stage addition with per-stage title, description, and tasks (one per line,title | descriptionformat). Addedskill-track-actions.tsserver actions (createSkillTrackAction,updateSkillTrackMetaAction).SkillTrackResumeWidgeton the dashboard surfaces active tracks with a per-track progress bar and "Resume Stage N" shortcut, and completed tracks with a revisit link. Seeded two starter skill tracks: "Productivity Fundamentals" (capture → prioritise daily → reflect and improve, 4 tasks each) and "Creative Projects" (spark → make → share, 4 tasks each). Verified withtsc --noEmit(0 errors).#262 — Stall Detection (healthy loop check-ins for stuck quests). Detects active quests that have gone quiet — no task updates or AI investigations for N days — and surfaces a gentle, supportive check-in prompt. New Supabase migration adds
quest_stall_notifications(one active record per quest, unique constraint) andquest_stall_events(append-only analytics log ofstall_detected,checked_in,dismissed,snoozed,resolvedevents). Addedcheck_stalled_quests(threshold_days int = 7)intended for a dailypg_cronjob: it upserts notifications for idle active quests (last activity = latest of taskupdated_atand investigationcreated_at) and auto-resolves notifications whose quests have since seen new activity. Addeddismiss_stall_notification(),snooze_stall_notification(days 1–30),log_stall_check_in(note text), andget_active_stall_notifications()DB functions with ownership enforcement and appropriate RLS policies. Gated behindNEXT_PUBLIC_FEATURE_STALL_DETECTIONfeature flag following the existing flag pattern. AddedStallNotificationtype andgetStallNotificationsForUser()query helper; extendedQuestSummarywith an optionalstall_notificationfield; updatedlistQuests()to accept a pre-fetched stall map so no extra round-trip is needed. Dashboard fetches stall notifications in parallel with other data and passes them intolistQuests. Added three API routes (POST /api/quests/[id]/stall/check-in,.../dismiss,.../snooze) each delegating to their DB function with server-side auth.StallBannercomponent renders an amber banner inside the quest card: shows idle-day count, optimistically reflects saved check-in notes, and offers an inline snooze dropdown (3 / 7 / 14 days) and a dismiss × button.CheckInModalis a full-page-overlay dialog with a resizable textarea (max 2000 chars), saves the note back via the check-in route, and passes it to the banner for immediate display without a reload.QuestCardaccepts ashowStallBannerprop and renders the banner when the flag is on and stall data is present. All components built with available UI primitives and Tailwind only — no additional dependencies. Verified withtsc --noEmit(0 errors).#261 — AI Coaching Interview Mode (conversational quest planning). Added a guided AI interview flow for quest creation, gated behind
NEXT_PUBLIC_FEATURE_COACHING_MODE. When the flag is on, the/quests/newpage shows a "Start with AI Coach" card above the standard form. Clicking it opens a chat interface where the AI coach asks 3–5 focused questions (goal, context, constraints, tasks, title) one at a time, then emits a structured JSON quest draft that auto-fills the form fields (title, goal, context, constraints) and surfaces a suggested-tasks panel for manual reference. After pre-fill the user reviews and edits the form normally before saving — no new database columns or migrations required. Addedsrc/lib/ai/coaching.tswith the coaching system prompt (COACHING_SYSTEM_PROMPT),CoachMessage/CoachQuestDrafttypes,buildCoachMessages(), andparseQuestDraft()(extracts the JSON block the AI embeds in its closing turn). AddedPOST /api/coach/interviewroute: accepts the full conversation history (capped at 20 turns), enforces auth and feature-flag gates, and calls Anthropic → OpenAI → DeepSeek in priority order using the samefetch-based helpers as the narrative route. Added three client components:coaching-interview.tsx(chat UI with message bubbles, bold-markdown renderer, typing indicator, progress bar, auto-scroll, Enter-to-send),coaching-entry.tsx("Start with AI Coach" gradient card), andcoaching-quest-creator.tsx(composes entry +QuestForm, remounts the form with AI-extracteddefaultValueswhen the draft arrives). Updatedquests/new/page.tsxto renderCoachingQuestCreatorinstead ofQuestFormdirectly, passing thecoachingEnabledflag. Updatedenv.tsand.env.examplewith the new flag and JSDoc. Verified withtsc --noEmit(0 errors).#260 — Quest Narrative Mode (AI-written completion story). Added an AI-generated epic/heroic completion story for finished quests, gated behind
NEXT_PUBLIC_FEATURE_NARRATIVE_MODE. New Supabase migration addsnarrative TEXT,narrative_model_type TEXT, andnarrative_generated_at TIMESTAMPTZcolumns toquests.POST /api/quests/[id]/narrativeis owner-only and completed-quest-only: it loads all tasks with their investigation summaries, resolves the quest's model persona (falls back to the default active persona), and generates narrative prose inline via the quest's selected AI provider (Anthropic / OpenAI / DeepSeek) using the samefetch-based approach as the background worker. The newbuildQuestNarrativePrompt()helper insrc/lib/ai/narrative.tsassembles an epic/heroic storytelling prompt from quest goal, context, completed vs. incomplete tasks, and per-task investigation summaries, targeting 250–500 words of flowing prose with no bullet points. Results are persisted directly toquests.narrativeandnarrative_generated_at. TheNarrativePanelclient component renders below the task section on completed quest detail pages: it shows a Generate Story / Regenerate Story button (with spinner), an animated skeleton while generating, a styled amber-tinted prose card with per-paragraph rendering, generation date, and a Copy link share button. Non-owners see the narrative read-only with an empty state if none has been generated yet. Updatedsrc/types/supabase.ts,env.ts,env.test.ts, and.env.example. Verified withtsc --noEmit(0 errors).#259 — Random Quest Challenge (Balatro-style daily/weekly challenges). Added seed-backed Random Quest Challenges with
quest_challenge_templates,quest_challenge_tasks, andquest_challenge_attempts, plus daily/weekly cadence/source enums, RLS policies, admin RBAC privileges, immutable attempt snapshots, and a quest-completion sync trigger that derives completion timestamps and XP bonuses from linked quests. Seeded an initial curated pool of short daily and weekly challenge prompts with 1–3 task templates. Added typed challenge period selection helpers, deterministic daily/weekly rotation, challenge query/action helpers, and an opt-in server action that creates an active quest prefilled with challenge prompt, constraints, reward context, and copied tasks while preventing duplicate attempts for the same period. Dashboard now surfaces daily and weekly challenge cards,/challengesshows open challenges plus attempt history, quest detail pages show linked challenge reward/completion state, and challenge completion unlocks a new Challenge Runner badge through the existing achievement pipeline. Updated Supabase types and focused period-selection tests. Verified withpnpm --filter web test -- src/lib/quest-challenges/queries.test.ts src/lib/achievements/server.test.ts,pnpm --filter web typecheck,pnpm --filter web lint,pnpm --filter web buildwith local public Supabase env placeholders after allowing Google Fonts network access,git diff --check, and browser smoke checks for/,/challenges, and/dashboardauth redirects.#258 — Party Quests (collaborative multi-user quests). Added Party Quest membership with
quest_party_members, invitation audit rows, owner/member roles, owner backfill for existing quests, and RLS helpers so party members can read/write shared quest, task, and investigation rows while public viewers remain read-only. Added optional task assignees with database validation that assignees belong to the quest party, member progress RPCs, and party completion syncing for assigned-task or all-task completion modes. Dashboard quest listing now includes invited/member quests with party metadata and assigned-task progress. Quest detail pages now show a Party Quest panel for inviting users by email/display name, removing members, leaving a party, and viewing who has completed assigned work. Task management now supports assigning tasks to party members during create/edit, and task APIs accept shared-party access for list/create/update/reorder/delete/investigation flows. Updated Supabase/web/worker types and focused task validation/UI tests. Verified withpnpm --filter web typecheck,pnpm --filter web test -- src/lib/tasks/server.test.ts src/lib/tasks/types.test.ts src/components/tasks/task-manager.test.tsx,pnpm --filter web lint,pnpm --filter worker typecheck, andpnpm --filter worker lint; local Supabase migration application could not run because thesupabase_db_questifycontainer was not running.#257 — Quest Jams (time-boxed sprint challenges). Added admin-created Quest Jams with
quest_jamsandquest_jam_entriestables, RLS policies, RBAC privileges, completion syncing from immutable quest completion timestamps, and RPCs for active/upcoming jam summaries, detail views, and leaderboard rankings by elapsed time from jam start. Added typed Quest Jam query/action/validation helpers, Supabase type coverage, and focused validator tests. Shipped/quest-jamsbrowse,/quest-jams/[id]detail with join flow and leaderboard, and/admin/quest-jamsmanagement for creating, updating, activating, and pausing jams. Users can join with an existing quest or create a new sprint quest in-place; completed linked quests count only when completion falls inside the jam window. Added dashboard/home navigation and jam revalidation after quest updates. Verified withpnpm --filter web typecheck,pnpm --filter web lint,pnpm --filter web test,git diff --check, and a local browser smoke check for/quest-jamswith local Supabase env placeholders.#256 — Year Theme guiding principle layer. Added user-owned Year Themes as a soft intention layer above quests. New Supabase schema adds
year_themeswith one theme per user/year, muted hex accent colors, archive metadata, owner-only RLS,quests.year_theme_id, and a trigger that prevents quests from referencing another user's theme. Added typed Year Theme validators, queries, and server actions, plus route-handler/server-action support so quest create/edit flows can optionally tag quests to the caller's own theme. The profile page now lets users create or edit the current calendar year's theme and review archived previous-year themes; the dashboard shows the active theme as a restrained banner with tagged-quest completion progress; quest cards and owner quest detail pages show the associated theme accent. Updated Supabase types, API/schema docs, quest validation coverage, and pgTAP RLS coverage for theme ownership and cross-user tagging denial. Verified withpnpm --filter web test,pnpm --filter web typecheck, andpnpm --filter web lint;pnpm supabase:testcould not run locally because the Supabase Postgres container was not running on127.0.0.1:54322.#255 — Achievements & collectible badges. Added the v2 achievement system with a new Supabase
achievement_rarityenum, user-ownedachievementstable,quests.quest_pack_idprovenance for pack-completion unlocks, immutablequests.completed_attimestamps for streak derivation, RLS policies for private owner access plus public badge display on public quest owners, and pgTAP coverage for achievement visibility/write rules. Added 14 starter achievement definitions spanning first quest, 5/10/25/50 quest milestones, 3/7/30-day streaks, Quest Pack completion, public quest completion, all-tasks-complete, AI Scout, Party Quest, and Quest Jam winner. Server-side unlock evaluation now runs after quest completion, task completion, bulk task completion, API-created completed quests, and the first AI task investigation, returning newly unlocked achievements to the UI without failing the primary mutation if badge evaluation errors. Achievement writes use the service-role client rather than authenticated RLS inserts, so clients cannot mint arbitrary badges. Added reusable achievement badge/icon/toast components, in-app unlock notifications in the task manager, a new authenticated/profilebadge gallery with locked mystery silhouettes, dashboard profile navigation, and compact badge strips on public owner cards and shared quest headers. Verified withpnpm --filter web typecheck,pnpm --filter web lint,pnpm --filter web test,pnpm --filter web build, and browser smoke checks for/quests/publicplus/profileauth redirect;pnpm supabase:testcould not run locally because the Supabase Postgres container was not running on127.0.0.1:54322.#254 — Model persona selector (AI model choice per quest). Ported the model persona selection experience into the Next.js 2.0 quest flow behind
NEXT_PUBLIC_FEATURE_MODEL_PERSONAS. Addedquests.model_persona_idwith a foreign key to the admin-managedmodel_personascatalog, backfilled existing quests to the recommended active persona, indexed persona joins, and updatedget_my_active_quests()to return the selected persona id. The create/edit quest form now shows a persona picker modal with name, tagline, speed/cost/context badges, and recommended flags; selections persist by persona key and emit themodel_persona_selectedanalytics event through the app's browser analytics bridge. Dashboard quest cards can show the selected persona inline, and task investigations now use the quest's selected persona by default while still accepting an explicitmodelPersonaKeyfor API callers. Updated Supabase types, env parsing, API/schema docs, and focused Vitest coverage for validation, env parsing, the picker UI, and analytics dispatch. Verified withpnpm --filter web test,pnpm --filter web typecheck,pnpm --filter web lint,pnpm --filter web buildafter allowing the build to fetch Google Fonts,git diff --check, and a browser smoke check that/quests/newredirects cleanly through auth with the feature flag enabled.#253 — Public quests (shareable quest pages + public browse). Added the public quest discovery route at
/quests/public, backed by new public quest query helpers that return public quest cards with owner attribution, task counts, completion counts, and status badges while preserving the existing owner-only dashboard query. Updated/quests/[id]so public quests render for unauthenticated visitors as read-only shared pages, with owner attribution, public-share actions in the header, and Open Graph/Twitter metadata derived from the quest title, goal/context, canonical URL, and attached images. Replaced the previous one-click share affordance with a share dialog that guards private quests, shows a copyable public link, supports native sharing when available, and renders a QR code via the newqrcodedependency. Added a narrow public-owner attribution RPC that returns onlyid,display_name, andavatar_urlfor owners of non-deleted public quests, plus pgTAP coverage confirming anonymous users cannot read full profile rows. Revalidated/quests/publicafter create/update/archive/delete quest actions so visibility changes appear promptly. Verified withpnpm --filter web typecheck,pnpm --filter web lint,pnpm --filter web test,git diff --check, and a browser smoke check for/quests/public;pnpm supabase:testcould not run locally because Supabase Postgres was not listening on127.0.0.1:54322.#252 — Quest Packs (browser, picker, template-based quest creation). Ported the Quest Packs experience to the Next.js 2.0 app. Added the public
/quest-packsbrowser with active-pack search, category filtering, pack cards, task-count summaries, preview modal, and "Start this Quest" links into/quests/new?pack=<slug>. Added public Route Handlers forGET /api/quest-packsandGET /api/quest-packs/[slug], backed by shared quest-pack helpers that return ordered packs with ordered template tasks. Upgraded the quest creation flow so a selected pack pre-fills the title/goal, preserves the pack through auth redirects, shows the exact template task preview, and still clones pack tasks into the created quest through the existing server action/API path. Added/admin/quest-packs, guarded by the existingquest_packs.writeprivilege, with admin create/edit forms, active toggles, and reorder controls. Migrated the v1 starter catalog intosupabase/seed.sqlwith ten idempotent Quest Packs and task lists matchingdocs/seed-data-summary.md. Added focused parser/slug utility coverage. Verified withpnpm --filter web typecheck,pnpm --filter web lint,pnpm --filter web test,pnpm --filter web buildwith local public Supabase env values, and a browser smoke check for/quest-packsplus/quests/new?pack=fitness-foundationsauth redirect preservation.#251 — AI Task Investigation (background worker + investigation history). Chose the standalone worker architecture for v2:
apps/workernow claims pendingtask_investigationsrows from Supabase, processes them without Redis/BullMQ, and writesrunning → succeeded/failedstatus updates back to the investigation history. Addedclaim_next_task_investigation()withFOR UPDATE SKIP LOCKEDplus service-role-only execute grants so multiple Railway/Fly worker replicas can claim work safely.POST /api/tasks/:id/investigationsnow validates the selected model persona, composes the final prompt from quest context, task details, custom prompt text, and image URLs, stores persona metadata/prompt/images intask_investigations, and returns202 Acceptedwith the queued run. Added a provider abstraction for OpenAI, Anthropic, and DeepSeek, carrying forwardAI_MAX_RESPONSE_TOKENS; OpenAI and Anthropic receive image attachments through their multimodal request schemas, while DeepSeek receives image URLs in the prompt context. The task UI now loads activemodel_personas, exposes a model persona selector, disables investigate actions when no persona catalog is available, displays model type and prompt history, and polls active pending/running investigations every three seconds so status changes appear near-real-time until Supabase Realtime is wired in. Documented the architecture decision indocs/ai-investigations.md, updated API/schema docs, added worker env vars (DEEPSEEK_API_KEY,AI_MAX_RESPONSE_TOKENS,AI_WORKER_POLL_INTERVAL_MS), and added focused worker coverage for provider parsing/summary helpers. Verified withpnpm --filter worker test,pnpm --filter worker typecheck,pnpm --filter worker lint,pnpm --filter worker build,pnpm --filter web typecheck,pnpm --filter web lint,pnpm --filter web test, andpnpm --filter web buildafter allowing the build to fetch Google Fonts.#250 — Task management (create, edit, reorder, status transitions). Added the authenticated task-management vertical slice for v2 quests. New API route handlers cover
GET /api/tasks?questId=...,POST /api/tasks,PATCH /api/tasks/:id,DELETE /api/tasks/:id,PATCH /api/tasks/reorder, andPOST /api/tasks/:id/investigations; every route checks the caller owns the parent quest/task before mutating RLS-protected rows. The task DTO helpers normalize Supabase snake_case rows into UI-friendly camelCase, validate payloads with zod, support bothsortOrderand issue-noteorder, normalizein-progressto the database enumin_progress, and preserve the schema'sblockedstatus while keeping the primary UI cycle totodo → in progress → done. Added an authenticated task workspace backed byget_my_active_quests()and embedded the same manager into owner-only quest detail pages: inline task creation, title/details editing, status toggles, up/down reorder controls, delete, compact/expanded view mode, progress summary, bulk completion, and per-task investigation history display. Investigation triggering currently creates a pendingtask_investigationsrow with the composed prompt so the future AI worker can pick it up. Addedlucide-reactto the web workspace, documented the task endpoints indocs/api-endpoints.md, and made public env parsing lazy so Next.js can collect API route modules during build without requiring Supabase runtime env at import time. Added focused Vitest coverage for task status/order helpers, payload validation, env parsing, and the task manager expanded/compact investigation rendering.#249 — Quest management (create, list, edit, archive, delete). Ported core quest management to the Next.js app on top of Supabase. Added authenticated pages for
/dashboard(quest list withall,active,draft,completed,failed, andarchivedfilters),/quests/new(create form with title, goal, context, constraints, image URLs, visibility, and Quest Pack picker), and/quests/[id](detail page with task list, status badge, images, share action, disabled AI investigate affordances for #251, inline edit, archive, and permanent delete controls). Added Route Handlers forGET/POST /api/quests,GET/PATCH/DELETE /api/quests/[id], andPATCH /api/quests/[id]/archive, all using the server Supabase client and authenticated ownership checks. Added quest server actions for form submissions with dashboard/detail revalidation, optimistic archive UI feedback, confirmation before permanent delete, and rollback if Quest Pack task cloning fails during creation. Addedapps/web/src/lib/quests/{queries,actions,validators,packs}.ts, quest UI components, and validator coverage for lifecycle statuses, image URLs, and dashboard filters. Added a Supabase migration and type update for the requiredfailedquest status so the lifecycle now supportsdraft → active → completed/failed → archived. Verified withpnpm --filter web typecheck,pnpm --filter web lint,pnpm --filter web test, and a local browser smoke check that anonymous access to/quests/newredirects through/auth/login?next=%2Fquests%2Fnew.#247 — Authentication (email/password signup, login, logout). Shipped the full email/password auth surface on
@supabase/ssrusing thecreateServerClient+createBrowserClientpattern (no custom JWT handling). New routes underapps/web/src/app/auth/:/auth/login(email + password sign-in, supports?next=for post-login redirects),/auth/signup(name + email + password — capturesfull_nameintouser_metadataso the trigger from #246 mirrors it intoprofiles),/auth/logout(POST/GET route handler that callssupabase.auth.signOut()and 303s to/auth/login),/auth/forgot-password(request reset email — deliberately vague success copy to avoid leaking account existence),/auth/reset-password(set new password after the email link — explicitly NOT in the auth-only redirect list, since the recovery email arrives with a live recovery session and bouncing the user off the page would make the flow impossible to complete),/auth/confirm("check your inbox" landing for the email-confirmation flow), and/auth/callback(exchanges the Supabase PKCEcodefor a session cookie and bounces to the safenextpath, rejecting open redirects). Added a placeholder/dashboardpage so the auth round-trip has a destination — real implementation lands with #249. The middleware (apps/web/src/lib/supabase/middleware.ts) was upgraded from a pure session-refresh into the route gate: anonymous visitors hitting/dashboardor/quests/*are bounced to/auth/login?next=<original-path>, and authenticated visitors hitting/auth/{login,signup,forgot-password,confirm}are bounced to/dashboard—/auth/callback,/auth/logout, and/auth/reset-passwordare intentionally excluded so signed-in users can still complete those flows. Middleware redirects copy anySet-Cookieheaderssupabase.auth.getUser()wrote onto the response so a rotated refresh token isn't silently dropped on the redirect hop. Server actions live inapps/web/src/lib/auth/actions.tswith a discriminatedAuthActionState(idle/error/info) consumed viauseFormState, zod schemas invalidators.ts(8-char minimum to match Supabase'sauth.minimum_password_length), friendly Supabase-error-code mapping inerrors.ts(invalid_credentials,user_already_exists,weak_password,email_not_confirmed, HTTP 429), and centralized redirect targets inredirects.tsso middleware / actions / links stay in sync. The sharedsafeInternalPath()helper rejects empty strings, off-site URLs, protocol-relative URLs (//attacker.example), and backslash variants — every user-controllablenexttarget (login action, login page, callback handler) routes through it so a crafted login link can't open-redirect the user post-authentication. AddedNEXT_PUBLIC_AUTH_EMAIL_CONFIRMATIONfeature flag toapps/web/src/lib/env.ts(with a boolean coercer) and.env.example— mirrors[auth.email].enable_confirmationsinsupabase/config.toml; when true the signup action redirects to/auth/confirminstead of/dashboard. New UI primitivesLabelandAlert(withdestructive/successvariants) added to@questify/ui. The legacy/loginplaceholder is now a server redirect to/auth/loginso existing bookmarks and the home CTA still resolve. Home page CTAs updated to point at/auth/loginand/auth/signup. Vitest coverage inapps/web/src/lib/auth/{validators,errors,redirects}.test.ts(the redirects suite assertssafeInternalPathblocks protocol-relative URLs and that/auth/reset-passwordis NOT auth-only); Playwright smoke spec ine2e/auth.spec.ts(every auth route renders the right form,/dashboardredirects anonymous users withnext=%2Fdashboard, legacy/loginredirects to/auth/login). Live signup/login round-trip e2e requires a real Supabase project and is gated behind #267 (test infrastructure).#248 — Authentication (Google + Facebook OAuth via Supabase). Added social sign-in on top of the email/password surface shipped in #247. New
signInWithOAuthAction(provider, next)server action inapps/web/src/lib/auth/actions.tscallssupabase.auth.signInWithOAuthwithskipBrowserRedirect: true(required for SSR — Supabase returns the authorization URL rather than trying to callwindow.location) then issues aredirect()to the provider. The redirect target includes?next=<safeInternalPath>so the post-login destination survives the OAuth round-trip. NewOAuthButtonsclient component (apps/web/src/components/auth/oauth-buttons.tsx) renders a Google button and a Facebook button, each backed byuseTransitionso the button shows "Redirecting…" and disables while the server action is in flight. Inline SVG icons for both providers — no new icon-library dependency. Apositionprop places the buttons above (signup, OAuth-first convention) or below (login, email-first convention) the email/password fields, separated by an "or" divider. Wired intoLoginFormandSignupForm. The existing/auth/callbackhandler already exchanged PKCE codes for session cookies — it now also checks for?error=params set by providers when the user denies access or an upstream error occurs, bouncing to/auth/login?error=oauth_deniedwithout leaking provider details. User profile data (full_name,avatar_url) is auto-populated from the provider's userinfo response by Supabase intoauth.users.raw_user_meta_data— the trigger from #246 mirrors this intoprofileswith no extra handling required. Existing email/password accounts are linked automatically by Supabase when the same verified email is presented via OAuth..env.exampleupdated: the four OAuth vars (GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,FACEBOOK_CLIENT_ID,FACEBOOK_CLIENT_SECRET) are now active entries (previously commented out) with the Supabase dashboard path, provider setup guide links, and the callback URL pattern to register in each provider's developer console.#246 — Supabase schema (full data model + RLS). Designed and shipped the complete v2 data model as six versioned migrations under
supabase/migrations/. Migration 1 (init_core) adds thepgcrypto+citextextensions, the sharedset_updated_at()trigger, three status enums (quest_status,task_status,investigation_status), and theprofilestable — auto-populated by anon_auth_user_createdtrigger that mirrorsauth.users1:1. Migration 2 (quests_tasks_investigations) landsquests(with soft-delete viadeleted_at,is_publicfor sharing),tasks(withsort_orderindexed), andtask_investigations(always-private AI run history), plususer_owns_quest()anduser_owns_task()SECURITY DEFINERhelpers used by the RLS policies. Migration 3 (packs_personas) adds the read-only reference catalog:quest_packs,quest_pack_tasks,model_personas— anon can read active quest packs (public catalog),model_personasis authenticated-only. Migration 4 (rbac) wires uproles,privileges,user_roles,role_privilegesplushas_role(role_key)/has_privilege(privilege_key)predicates used across every RLS policy, and back-fills admin-write policies on the migration-3 tables. Migration 5 (audit_beta) addsadmin_audit_log(append-only — no INSERT policy means only the service role can write) andbeta_signups(anon can INSERT, admin reads viabeta_signups.read). Migration 6 (functions) adds three Postgres helpers:get_quest_with_tasks(quest_id)(returns{ quest, tasks }JSON, RLS-respecting),get_my_active_quests()(dashboard query with task-completion counts), andclaim_admin_role()(one-shot SuperAdmin bootstrap that refuses if any SuperAdmin already exists, recorded in the audit log). Wrotesupabase/seed.sqlwith the three system roles (SuperAdmin / Admin / Support), ten granular privileges, four starter quest packs with task lists (learn-react,write-a-novel,couch-to-5k,ship-a-side-project), and six model personas across Anthropic / OpenAI / DeepSeek — all idempotent viaON CONFLICT DO NOTHING. Addedsupabase/tests/rls.sql(18 pgTAP assertions: owner can RW own, anon can read public quests but not private, tasks inherit ownership,task_investigationsare never publicly readable, RBAC predicates work, admins can write reference tables). Re-added.github/workflows/supabase.ymlwithdb lint+supabase test dbagainst a real local stack on every PR touchingsupabase/**. Updatedapps/web/src/types/supabase.tswith a hand-authoredDatabasetype matching the schema untilpnpm supabase:typegenruns against a real project. Documented everything indocs/schema.md— table inventory, RLS rules, RBAC model, helper functions, index strategy, and typegen workflow. Re-added thesupabase/CLI scaffold (config.toml, .gitignore, seed.sql) and the ninepnpm supabase:*scripts (start,stop,status,reset,test,diff,push,migration:new,typegen,typegen:local) since they were missing fromdevelop. Validated all eight SQL files throughlibpg_query— every statement parses cleanly.Hardened
release.ymlpost-deploy healthcheck in response to Codex review on PR #245. The previous version required/api/healthzto return 200, but that route only ships with #244 — every production deploy that landed before #244 merged would have failed the workflow on a 404. Now the workflow makes the root URL probe the required check (a deployment that can't render/is broken) and treats/api/healthzas soft: 200 logs the body, 404 is treated as "route not deployed yet, skip", and only 5xx fails the workflow. Replays of the request use the${DEPLOY_URL}env var to satisfy shellcheck and Turbo's env-vars rule.#245 — CI/CD pipeline (Vercel + GitHub Actions). Extended
release.ymlto trigger on push tomain(in addition tov*.*.*tags and manual dispatch) so merges to main auto-deploy to Vercel production; added a post-deploy/api/healthzprobe with retries so a failed deploy fails the workflow. Added a final aggregateci-passjob inci.ymlthat gates on lint, test, build, and (where applicable) e2e — this is the single required check for branch protection ondevelopandmain. Added.github/workflows/lighthouse.ymlto run Lighthouse CI against successful Vercel preview deploys (triggered by Vercel'sdeployment_statusevents) and post a sticky PR comment with Performance / Accessibility / Best Practices / SEO scores; Lighthouse config in.github/lighthouserc.jsonwarns under 80% perf and fails under 90% a11y. Added.github/workflows/deployment-comment.yml— a sticky-per-environment PR comment that updates as deploys move through pending → success / failure, with healthcheck URLs. Added.github/PULL_REQUEST_TEMPLATE.md,.github/ISSUE_TEMPLATE/{bug,feature,config}.yml, and.github/CODEOWNERS(default owner plus stricter ownership on.github/,supabase/,packages/ui/, and shared tooling). Newdocs/cicd.mdis the operational source of truth: full deploy-flow diagram, workflow inventory, Vercel project setup, required secrets / variables / environments, exact branch protection rules fordevelop+main, and rollback procedure. Linked from the README under a new "CI / CD" section.Fixed Supabase workflow validation step. Replaced
supabase config get project_id(theconfig getsubcommand was removed from the CLI — it now only acceptsconfig push) with an inline Pythontomllibparse that asserts required keys exist (project_id,api,db,auth),auth.enabled = true, andauth.email.enable_signup = true. Added a guard to thedb-lintjob so it skips when no migrations exist yet (the schema lands in #246).#244 — Configure Supabase project (Auth, Postgres, RLS). Scaffolded the Supabase CLI directory at
supabase/withconfig.toml(project_id, ports 54321–54327, email/password auth enabled, scaffolded-but-disabled Google + Facebook OAuth blocks for #248),seed.sql,migrations/.gitkeep, and.gitignore. Addedapps/web/src/types/supabase.ts(typedDatabasestub — overwritten bypnpm supabase:typegenonce #246 lands a real schema) and threaded theDatabasegeneric through the browser, server, and middleware Supabase clients. Added acreateServiceRoleClient()helper for trusted server-side work that bypasses RLS. Splitapps/web/src/lib/env.tsinto public/server schemas soSUPABASE_SERVICE_ROLE_KEYis validated server-side only and never inlined into the client bundle. Added a runtime/api/healthzroute that pingssupabase.auth.getSession()and returns{ ok, supabase, timestamp, version }for Vercel probes and e2e smoke tests, with a Vitest test for the response shape and cache headers. Wired thesupabaseCLI as a dev dependency plus ninepnpm supabase:*scripts (start,stop,status,reset,diff,push,migration:new,typegen,typegen:local). Added.github/workflows/supabase.yml— path-filtered validation that parsesconfig.toml, enforces the<utc-timestamp>_<slug>.sqlmigration naming convention, and runssupabase db lintagainst a real local stack. ThreadedSUPABASE_SERVICE_ROLE_KEY(with a placeholder fallback) throughci.ymlso server-side code can be exercised in CI. AddedVERCEL_GIT_COMMIT_SHAtoturbo.json#globalEnvfor the healthcheck's version stamp. Extendedvercel.jsonwith aCache-Control: no-storeheader on/api/healthz. Expanded the README with a Supabase section: command table, local-stack URLs, hosted-project linking flow, and a curl snippet for the healthcheck.#243 — Initialize Next.js 14 + Turborepo monorepo scaffold. Bootstrapped the v2 platform on the portfolio-standard stack (Next.js 14 · TypeScript · Tailwind CSS · Supabase · Turborepo · Vercel). Removed the v1 Nuxt app, BullMQ worker, Prisma package, and shared package to clear the way for the rebuild — v1 remains accessible on
developand prior release branches during the migration window (#268).Added
apps/web: Next.js 14 App Router with TypeScript, Tailwind, Supabase SSR client (src/lib/supabase/{client,server,middleware}.ts), Zod-validated env loader (src/lib/env.ts), session-refresh middleware, and placeholder/,/login,/questsroutes consuming the shared UI library.Added
packages/ui: shadcn/ui-style component library withcn()helper,Button(CVA variants),Input,Card, design tokens via HSL CSS variables (light + dark), and a Tailwind preset consumed by apps.Added
packages/typescript-config: sharedbase.json,nextjs.json, andreact-library.jsonpresets.Added
packages/eslint-config: flat-configbase.js,react.js, andnext.jspresets wired with@next/eslint-plugin-next,eslint-plugin-react-hooks,typescript-eslint,eslint-plugin-turbo, andeslint-config-prettier.Added root
turbo.jsonwithbuild,dev,lint,typecheck,test,test:e2e, andcleanpipelines, includingdependsOntopology and Supabase env declarations for build caching.Updated root
package.jsonto drive Turborepo (pnpm dev/build/lint/test), pinnedpnpm@9.12.0viapackageManager, setengines.node >= 20.0.0, and added Prettier + Tailwind plugin tooling.Replaced
.env.examplewith the v2 surface:NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY,SUPABASE_SERVICE_ROLE_KEY,SUPABASE_DB_URL,NEXT_PUBLIC_SITE_URL, AI provider keys, and documented OAuth/observability placeholders.Rewrote
README.mdfor the new stack: v1 → v2 layer comparison, repo layout, Turborepo command table, env-var reference, and links into epic #269's follow-up issues.Updated
vercel.jsonto build thewebworkspace viapnpm turbo run build --filter=webwithframework: "nextjs"; added.nvmrc(Node 20),.npmrc(auto-install-peers), Prettier config + ignore file, and gitignore entries for.next/,.turbo/, and.v1-archive/.GitHub Actions overhaul: rewrote
ci.ymlto run on Node 20 + pnpm 9 with splitlint/test/build/e2ejobs driven by Turborepo, including pnpm-store and.turbocaching, optionalTURBO_TOKENremote cache hooks, and a Playwright browser cache. Refreshedcodeql.ymlwith thesecurity-and-qualityquery suite, a weekly schedule, and pnpm/Node setup viapnpm/action-setup@v4. Replacedrelease.ymlto deploy to Vercel via the Vercel CLI (vercel pull → build → deploy --prebuilt --prod) onv*.*.*tags or manual dispatch, with a GitHub Environment URL output. Replacedstaging.ymlwith a Vercel preview deploy on pushes tostaging. Removed the Prisma-specificseed-staging.yml— the Supabase-driven replacement lands with issue #246. Restructured.github/dependabot.ymlto cover root tooling,apps/web,packages/ui,packages/eslint-config, and GitHub Actions, with grouped updates per ecosystem.Wired up Playwright e2e for
apps/web: addedplaywright.config.tsscoped toapps/web/e2e/with a chromium project, GitHub reporter in CI, trace/screenshot/video on failure, and an auto-booted Next.jswebServerthat seeds placeholder Supabase env vars. Addede2e/home.spec.tscovering the home-page hero copy, CTA targets, and the/loginnavigation flow. The previous CI failure happened because Playwright (with no config) was globbing the wholesrc/tree and picking up the Vitest*.test.tsxfiles, which thenrequire()'d the ESM-onlyvitestpackage and crashed. Vitest now stays insrc/**, Playwright stays ine2e/**. Extendedapps/web/tsconfig.jsonto includee2e/,playwright.config.ts, andvitest.config.tssotsc --noEmitcovers the test surface. AddedCI,PORT, andE2E_BASE_URLtoturbo.jsonsoturbo/no-undeclared-env-varsstays satisfied and Turborepo invalidates the cache when these change.Wired up Vitest in
apps/web: addedvitest.config.ts(jsdom env, automatic JSX runtime,@/path alias,passWithNoTests: true, V8 coverage), a sharedsrc/test/setup.tsthat loads@testing-library/jest-dom/vitestand seeds Supabase env defaults, plus two smoke tests —src/lib/env.test.ts(Zod env validator) andsrc/app/page.test.tsx(home-page render + nav links). Full test infrastructure (Playwright + cross-workspace coverage gates) lands with #267.Fixed CI lockfile + ESLint 9 compatibility: regenerated
pnpm-lock.yamlagainst the v2 workspace (old lockfile retained the stalemagicastoverride, breakingpnpm install --frozen-lockfile). Droppedeslint-config-nextfromapps/web(it requires ESLint ≤ 8) in favor of the shared flat config; theweblint script is noweslint . --max-warnings 0and readsapps/web/eslint.config.mjs. Disabled three rules in@questify/eslint-config/next(@next/next/no-duplicate-head,no-html-link-for-pages,no-page-custom-font) that callcontext.getAncestors()— an ESLint 8 API removed in 9 and not patched in@next/eslint-plugin-next@14.2.x; the rules can be re-enabled once we move to Next 15. Disabledreact/prop-typesglobally — TypeScript handles prop validation.
Design
Introduced the Questify Design System v1.0: parchment/indigo/gold colour palette replacing the previous blue theme, with full light and dark mode support.
Added
apps/nuxt/app/assets/css/tokens.cssexposing CSS custom properties for typography (--font-sans,--font-serif,--font-mono), spacing scale (--sp-1through--sp-10), border radius (--r-1through--r-full), elevation (--shadow-1/2/3), status soft fills, and motion tokens (--ease-out,--ease-spring,--dur-fast/base/slow). Includes.t-serif,.t-mono, and.t-displayutility classes.Updated
vuetify.config.tswith the new brand palette (primary indigo#4d3b9a, secondary gold#caa44b, parchment background#faf7f0) and matching dark-mode values; added component defaults forVBtn,VCard,VTextField,VSelect, andVChipto align radius and density with the design spec.Loaded Geist, Instrument Serif, and JetBrains Mono via Google Fonts in
nuxt.config.ts.Rebuilt the dashboard welcome section as a full-bleed dark hero panel with an italic serif greeting, monospace eyebrow label, decorative compass icon, and gold primary CTA — per design spec §10.
Updated
QuestCard.vueto render quest titles in Instrument Serif (weight 400, 1.25rem) with tonal status chips using semantic colours; replaced hardcodedgrey-darken-1on archived quests withsecondary.Removed the animated
radial-gradient/gradientShiftloop fromapp.vue; background is now a clean theme-colour transition.