diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..8cee53983 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,36 @@ +{ + "permissions": { + "allow": [ + "Bash(find /Users/scroobz/Navigation/client/tests/unit -name \"*.test.tsx\" -exec wc -l {} +)", + "Bash(npx vitest run --reporter=verbose)", + "Bash(npx cypress run --spec \"cypress/e2e/smoke-critical.cy.ts\" --headless)", + "Bash(npx cypress verify)", + "Bash(npx cypress install)", + "Bash(npx cypress install --force)", + "Bash(python3 -c \"import sys,json; p=json.load\\(sys.stdin\\); print\\(p.get\\(''scripts'',{}\\).get\\(''test:all'',''''\\)\\)\")", + "Bash(/Users/scroobz/Library/Caches/Cypress/14.5.4/Cypress.app/Contents/MacOS/Cypress --smoke-test --ping=514)", + "Bash(git -C /Users/scroobz/Navigation/client log --oneline -5 -- cypress.config.ts package.json)", + "Bash(python3 -c \"import sys,json; p=json.load\\(sys.stdin\\); deps=p.get\\(''devDependencies'',{}\\); print\\(''cypress:'', deps.get\\(''cypress'',''not found''\\)\\)\")", + "Read(//Users/scroobz/Library/Caches/Cypress/**)", + "Bash(yarn add cypress@^15.10.0 --dev)", + "Bash(CYPRESS_INSTALL_BINARY=/Users/scroobz/Library/Caches/Cypress/15.10.0/Cypress.app npx cypress verify)", + "Bash(CYPRESS_INSTALL_BINARY=/Users/scroobz/Library/Caches/Cypress/15.10.0/Cypress.app yarn add cypress@15.10.0 --dev)", + "WebSearch", + "WebFetch(domain:store.steampowered.com)", + "WebFetch(domain:localhost)", + "WebFetch(domain:zooniverse.github.io)", + "WebFetch(domain:api.nasa.gov)", + "WebFetch(domain:panoptes.docs.apiary.io)", + "WebFetch(domain:exoplanetarchive.ipac.caltech.edu)", + "WebFetch(domain:www.minorplanetcenter.net)", + "WebFetch(domain:www.aavso.org)", + "WebFetch(domain:seti.berkeley.edu)", + "WebFetch(domain:www.swpc.noaa.gov)", + "WebFetch(domain:api.helioviewer.org)", + "WebFetch(domain:alerce.science)", + "WebFetch(domain:science.alerce.online)", + "WebFetch(domain:alerce.readthedocs.io)", + "WebFetch(domain:developer.zooniverse.org)" + ] + } +} diff --git a/.knowns/archive/2026-04-20-project-management-reset/README.md b/.knowns/archive/2026-04-20-project-management-reset/README.md new file mode 100644 index 000000000..ac1db6939 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/README.md @@ -0,0 +1,5 @@ +# 2026-04-20-project-management-reset + +Archived 92 task file(s) from Star Sailors Web Client during the Star Sailors project-management reset. + +These files were not deleted. They were removed from the active Knowns task board so this week can focus on MVP closure, distribution, testing, and agent handoff. diff --git a/.knowns/tasks/task-1jurpa - create-workflow-for-organising-setting-up-anomalies-essentially-superseding-sytizen-repo-.md b/.knowns/archive/2026-04-20-project-management-reset/task-1jurpa - create-workflow-for-organising-setting-up-anomalies-essentially-superseding-sytizen-repo-.md similarity index 100% rename from .knowns/tasks/task-1jurpa - create-workflow-for-organising-setting-up-anomalies-essentially-superseding-sytizen-repo-.md rename to .knowns/archive/2026-04-20-project-management-reset/task-1jurpa - create-workflow-for-organising-setting-up-anomalies-essentially-superseding-sytizen-repo-.md diff --git a/.knowns/tasks/task-30-p1-design-tokens.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p1-design-tokens.md similarity index 97% rename from .knowns/tasks/task-30-p1-design-tokens.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p1-design-tokens.md index 25c9f61cb..6f21d3563 100644 --- a/.knowns/tasks/task-30-p1-design-tokens.md +++ b/.knowns/archive/2026-04-20-project-management-reset/task-30-p1-design-tokens.md @@ -1,8 +1,9 @@ --- id: task-30-p1-design-tokens title: "3.0 Phase 1: Design Tokens, Fonts & Theme" -status: in-progress +status: done priority: high +parent: task-sxdk5w phase: "3.0-p1" specRefs: - "specs/redesign/3-0-unified-web-client" diff --git a/.knowns/tasks/task-30-p10-living-world-bg.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p10-living-world-bg.md similarity index 100% rename from .knowns/tasks/task-30-p10-living-world-bg.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p10-living-world-bg.md diff --git a/.knowns/tasks/task-30-p11-intro-sequence.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p11-intro-sequence.md similarity index 100% rename from .knowns/tasks/task-30-p11-intro-sequence.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p11-intro-sequence.md diff --git a/.knowns/tasks/task-30-p2-landing-page.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p2-landing-page.md similarity index 100% rename from .knowns/tasks/task-30-p2-landing-page.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p2-landing-page.md diff --git a/.knowns/tasks/task-30-p3-logged-in-landing.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p3-logged-in-landing.md similarity index 100% rename from .knowns/tasks/task-30-p3-logged-in-landing.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p3-logged-in-landing.md diff --git a/.knowns/tasks/task-30-p4-coral-teaser.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p4-coral-teaser.md similarity index 100% rename from .knowns/tasks/task-30-p4-coral-teaser.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p4-coral-teaser.md diff --git a/.knowns/tasks/task-30-p5-micro-surveys.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p5-micro-surveys.md similarity index 100% rename from .knowns/tasks/task-30-p5-micro-surveys.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p5-micro-surveys.md diff --git a/.knowns/tasks/task-30-p6-posthog-sentry.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p6-posthog-sentry.md similarity index 100% rename from .knowns/tasks/task-30-p6-posthog-sentry.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p6-posthog-sentry.md diff --git a/.knowns/tasks/task-30-p7-prisma-stats.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p7-prisma-stats.md similarity index 100% rename from .knowns/tasks/task-30-p7-prisma-stats.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p7-prisma-stats.md diff --git a/.knowns/tasks/task-30-p8-hub-layout.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p8-hub-layout.md similarity index 100% rename from .knowns/tasks/task-30-p8-hub-layout.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p8-hub-layout.md diff --git a/.knowns/tasks/task-30-p9-structure-visual-states.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p9-structure-visual-states.md similarity index 100% rename from .knowns/tasks/task-30-p9-structure-visual-states.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p9-structure-visual-states.md diff --git a/.knowns/tasks/task-30-p9b-referral-growth.md b/.knowns/archive/2026-04-20-project-management-reset/task-30-p9b-referral-growth.md similarity index 100% rename from .knowns/tasks/task-30-p9b-referral-growth.md rename to .knowns/archive/2026-04-20-project-management-reset/task-30-p9b-referral-growth.md diff --git a/.knowns/tasks/task-3h2mqd - Migrate-deploy-mechanics-write-paths-to-API-routes.md b/.knowns/archive/2026-04-20-project-management-reset/task-3h2mqd - Migrate-deploy-mechanics-write-paths-to-API-routes.md similarity index 100% rename from .knowns/tasks/task-3h2mqd - Migrate-deploy-mechanics-write-paths-to-API-routes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-3h2mqd - Migrate-deploy-mechanics-write-paths-to-API-routes.md diff --git a/.knowns/tasks/task-4k2m8p - migrate-raw-sql-to-prisma-model-queries.md b/.knowns/archive/2026-04-20-project-management-reset/task-4k2m8p - migrate-raw-sql-to-prisma-model-queries.md similarity index 100% rename from .knowns/tasks/task-4k2m8p - migrate-raw-sql-to-prisma-model-queries.md rename to .knowns/archive/2026-04-20-project-management-reset/task-4k2m8p - migrate-raw-sql-to-prisma-model-queries.md diff --git a/.knowns/tasks/task-5xnt0i - remove-all-duplicate-functions.md b/.knowns/archive/2026-04-20-project-management-reset/task-5xnt0i - remove-all-duplicate-functions.md similarity index 100% rename from .knowns/tasks/task-5xnt0i - remove-all-duplicate-functions.md rename to .knowns/archive/2026-04-20-project-management-reset/task-5xnt0i - remove-all-duplicate-functions.md diff --git a/.knowns/tasks/task-6m5r9p - Migrate-extraction-and-nps-writes-to-api-routes.md b/.knowns/archive/2026-04-20-project-management-reset/task-6m5r9p - Migrate-extraction-and-nps-writes-to-api-routes.md similarity index 100% rename from .knowns/tasks/task-6m5r9p - Migrate-extraction-and-nps-writes-to-api-routes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-6m5r9p - Migrate-extraction-and-nps-writes-to-api-routes.md diff --git a/.knowns/tasks/task-7a3f1c - rename-mineralDeposits-to-mineral_deposits.md b/.knowns/archive/2026-04-20-project-management-reset/task-7a3f1c - rename-mineralDeposits-to-mineral_deposits.md similarity index 100% rename from .knowns/tasks/task-7a3f1c - rename-mineralDeposits-to-mineral_deposits.md rename to .knowns/archive/2026-04-20-project-management-reset/task-7a3f1c - rename-mineralDeposits-to-mineral_deposits.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-7kznko - Sketch-the-30-homepage-hero-section.md b/.knowns/archive/2026-04-20-project-management-reset/task-7kznko - Sketch-the-30-homepage-hero-section.md new file mode 100644 index 000000000..e780e061c --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-7kznko - Sketch-the-30-homepage-hero-section.md @@ -0,0 +1,88 @@ +--- +id: 7kznko +title: Sketch the 3.0 homepage hero section +status: done +priority: medium +labels: + - sketch + - design +createdAt: '2026-03-26T02:31:50.403Z' +updatedAt: '2026-03-26T09:18:05.246Z' +timeSpent: 0 +--- +# Sketch the 3.0 homepage hero section + +## Description + + +The 3.0 redesign needs a hero section sketch. What's the headline, visual treatment, and primary CTA? How does it communicate the platform's purpose? + + +## Acceptance Criteria + +- [ ] #1 Hero section sketch complete +- [ ] #2 Headline direction and CTA identified + + +## Hero Section Sketch + +### Layout (mobile-first, single column → two-column on lg) + +``` +┌─────────────────────────────────────────────────────┐ +│ [NAV: Logo left · "Sign in" right] │ +├─────────────────────────────────────────────────────┤ +│ │ +│ ░░ star-field + sunburst-hero (primary glow) ░░ │ +│ │ +│ ┌─────────────────────────────┐ │ +│ │ 01 / REAL ASTRONOMY │ ← micro label │ +│ │ │ │ +│ │ [HEADLINE — 2–3 lines] │ ← text-4xl/5xl │ +│ │ │ │ +│ │ [Subhead — 1 line] │ ← text-base │ +│ │ │ │ +│ │ [CTA button] [secondary] │ │ +│ └─────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────┐ │ +│ │ Live stat: X classifications · Y users │ │ +│ └───────────────────────────────────────────────┘ │ +│ │ +│ ── SCROLL ── ↓ Three product cards (01/02/03) ── │ +└─────────────────────────────────────────────────────┘ +``` + +### Visual treatment + +- **Background**: `bg-background` + `star-field` at 20% opacity + `sunburst-hero` (primary blue + glow rising from bottom of hero block) +- **Headline text**: Syne Mono, `text-4xl md:text-5xl`, white/foreground, no gradient treatment + — the glow is in the background, not the text +- **CTA button**: `bg-primary text-white` with `btn-glow` shadow, label "Launch Station" +- **Secondary action**: ghost button, "See how it works" → scrolls to product cards +- **Live stat strip**: single row, `font-mono text-xs text-muted-foreground`, pulls from + Prisma-backed live stats (already implemented, task-30-p7) +- **Scan-line overlay**: very low opacity (0.04) over entire hero for texture + +### Primary CTA + +**"Launch Station"** → `/auth` (logs in or creates account) + +The CTA is about entering the game, not "learning more." Users who want to understand first +scroll down to the three product cards. + +### What the hero must communicate (in order) + +1. This is a game +2. The science is real +3. You can start now + +### Headline direction → see task u6zo6x for final copy options + +## Implementation Notes + + +kanban: f7k41f + + diff --git a/.knowns/tasks/task-8ao08x - create-docs-including-spec-docs-in-knowns-for-all-mechanics-in-frontend.md b/.knowns/archive/2026-04-20-project-management-reset/task-8ao08x - create-docs-including-spec-docs-in-knowns-for-all-mechanics-in-frontend.md similarity index 100% rename from .knowns/tasks/task-8ao08x - create-docs-including-spec-docs-in-knowns-for-all-mechanics-in-frontend.md rename to .knowns/archive/2026-04-20-project-management-reset/task-8ao08x - create-docs-including-spec-docs-in-knowns-for-all-mechanics-in-frontend.md diff --git a/.knowns/tasks/task-8vco3o - fix-test-action-failures-in-219.md b/.knowns/archive/2026-04-20-project-management-reset/task-8vco3o - fix-test-action-failures-in-219.md similarity index 100% rename from .knowns/tasks/task-8vco3o - fix-test-action-failures-in-219.md rename to .knowns/archive/2026-04-20-project-management-reset/task-8vco3o - fix-test-action-failures-in-219.md diff --git a/.knowns/tasks/task-9k164d - Architecture-migration-Supabase-SSR-server-actions-for-Nextjs-only-flows.md b/.knowns/archive/2026-04-20-project-management-reset/task-9k164d - Architecture-migration-Supabase-SSR-server-actions-for-Nextjs-only-flows.md similarity index 100% rename from .knowns/tasks/task-9k164d - Architecture-migration-Supabase-SSR-server-actions-for-Nextjs-only-flows.md rename to .knowns/archive/2026-04-20-project-management-reset/task-9k164d - Architecture-migration-Supabase-SSR-server-actions-for-Nextjs-only-flows.md diff --git a/.knowns/tasks/task-9ztpbl - Decide-the-persistent-HUD-strip-composition-for-the-game-hub.md b/.knowns/archive/2026-04-20-project-management-reset/task-9ztpbl - Decide-the-persistent-HUD-strip-composition-for-the-game-hub.md similarity index 98% rename from .knowns/tasks/task-9ztpbl - Decide-the-persistent-HUD-strip-composition-for-the-game-hub.md rename to .knowns/archive/2026-04-20-project-management-reset/task-9ztpbl - Decide-the-persistent-HUD-strip-composition-for-the-game-hub.md index 1054b751d..f7c0a9162 100644 --- a/.knowns/tasks/task-9ztpbl - Decide-the-persistent-HUD-strip-composition-for-the-game-hub.md +++ b/.knowns/archive/2026-04-20-project-management-reset/task-9ztpbl - Decide-the-persistent-HUD-strip-composition-for-the-game-hub.md @@ -1,7 +1,7 @@ --- id: 9ztpbl title: Decide the persistent HUD strip composition for the game hub -status: todo +status: done priority: high labels: [] createdAt: '2026-04-01T11:56:01.878Z' diff --git a/.knowns/tasks/task-a9e2f4 - drop-deprecated-orphaned-tables.md b/.knowns/archive/2026-04-20-project-management-reset/task-a9e2f4 - drop-deprecated-orphaned-tables.md similarity index 100% rename from .knowns/tasks/task-a9e2f4 - drop-deprecated-orphaned-tables.md rename to .knowns/archive/2026-04-20-project-management-reset/task-a9e2f4 - drop-deprecated-orphaned-tables.md diff --git a/.knowns/tasks/task-aaigmw - Document-typography-and-colour-tokens-for-the-3-0-redesign.md b/.knowns/archive/2026-04-20-project-management-reset/task-aaigmw - Document-typography-and-colour-tokens-for-the-3-0-redesign.md similarity index 98% rename from .knowns/tasks/task-aaigmw - Document-typography-and-colour-tokens-for-the-3-0-redesign.md rename to .knowns/archive/2026-04-20-project-management-reset/task-aaigmw - Document-typography-and-colour-tokens-for-the-3-0-redesign.md index 35f2bd2d2..c495cb6a8 100644 --- a/.knowns/tasks/task-aaigmw - Document-typography-and-colour-tokens-for-the-3-0-redesign.md +++ b/.knowns/archive/2026-04-20-project-management-reset/task-aaigmw - Document-typography-and-colour-tokens-for-the-3-0-redesign.md @@ -1,7 +1,7 @@ --- id: aaigmw title: Document typography and colour tokens for the 3.0 redesign -status: todo +status: done priority: high assignee: Liam labels: diff --git a/.knowns/tasks/task-b2m7qe - 22-ecosystem-homepage-and-routing-clarity.md b/.knowns/archive/2026-04-20-project-management-reset/task-b2m7qe - 22-ecosystem-homepage-and-routing-clarity.md similarity index 100% rename from .knowns/tasks/task-b2m7qe - 22-ecosystem-homepage-and-routing-clarity.md rename to .knowns/archive/2026-04-20-project-management-reset/task-b2m7qe - 22-ecosystem-homepage-and-routing-clarity.md diff --git a/.knowns/tasks/task-c2s8rn - Migrate-deploy-social-surveyor-writes-to-api-routes.md b/.knowns/archive/2026-04-20-project-management-reset/task-c2s8rn - Migrate-deploy-social-surveyor-writes-to-api-routes.md similarity index 100% rename from .knowns/tasks/task-c2s8rn - Migrate-deploy-social-surveyor-writes-to-api-routes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-c2s8rn - Migrate-deploy-social-surveyor-writes-to-api-routes.md diff --git a/.knowns/tasks/task-c7r2ya - 22-web-notification-architecture-no-third-party.md b/.knowns/archive/2026-04-20-project-management-reset/task-c7r2ya - 22-web-notification-architecture-no-third-party.md similarity index 100% rename from .knowns/tasks/task-c7r2ya - 22-web-notification-architecture-no-third-party.md rename to .knowns/archive/2026-04-20-project-management-reset/task-c7r2ya - 22-web-notification-architecture-no-third-party.md diff --git a/.knowns/tasks/task-d4q1sn - fix-knowns-docs-sort-and-spec-coverage-regression.md b/.knowns/archive/2026-04-20-project-management-reset/task-d4q1sn - fix-knowns-docs-sort-and-spec-coverage-regression.md similarity index 100% rename from .knowns/tasks/task-d4q1sn - fix-knowns-docs-sort-and-spec-coverage-regression.md rename to .knowns/archive/2026-04-20-project-management-reset/task-d4q1sn - fix-knowns-docs-sort-and-spec-coverage-regression.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-data-fetching-optimization.md b/.knowns/archive/2026-04-20-project-management-reset/task-data-fetching-optimization.md new file mode 100644 index 000000000..efcc0572c --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-data-fetching-optimization.md @@ -0,0 +1,17 @@ +# Task: Data Fetching & Caching Optimization + +## Status +**Status:** Backlog +**Priority:** High +**Created:** 2026-04-02 +**Context:** Many components use manual `useEffect` + `fetch` patterns, leading to redundant code and sub-optimal performance on mobile. + +## Objectives +- [ ] Integrate a modern data-fetching library (e.g., SWR or TanStack Query). +- [ ] Consolidate fragmented API calls into "Aggregated Summary" endpoints. +- [ ] Implement robust error handling and loading states globally. + +## Strategy +- Identify a suitable library (SWR is already in use in some parts of the ecosystem, but let's check `package.json`). +- Refactor `GameClient.tsx` and major tabs to use hooks for data fetching. +- Work with backend/API routes to create `/api/gameplay/summary` endpoints. diff --git a/.knowns/tasks/task-e9f2px - 22-landing-page-visual-overhaul-and-ecosystem-routing.md b/.knowns/archive/2026-04-20-project-management-reset/task-e9f2px - 22-landing-page-visual-overhaul-and-ecosystem-routing.md similarity index 100% rename from .knowns/tasks/task-e9f2px - 22-landing-page-visual-overhaul-and-ecosystem-routing.md rename to .knowns/archive/2026-04-20-project-management-reset/task-e9f2px - 22-landing-page-visual-overhaul-and-ecosystem-routing.md diff --git a/.knowns/tasks/task-f4n8kd - 22-posthog-survey-inventory-gap-analysis.md b/.knowns/archive/2026-04-20-project-management-reset/task-f4n8kd - 22-posthog-survey-inventory-gap-analysis.md similarity index 100% rename from .knowns/tasks/task-f4n8kd - 22-posthog-survey-inventory-gap-analysis.md rename to .knowns/archive/2026-04-20-project-management-reset/task-f4n8kd - 22-posthog-survey-inventory-gap-analysis.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-folder-restructuring.md b/.knowns/archive/2026-04-20-project-management-reset/task-folder-restructuring.md new file mode 100644 index 000000000..68c06c7dc --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-folder-restructuring.md @@ -0,0 +1,17 @@ +# Task: Feature-First Folder Structure Migration + +## Status +**Status:** In Progress +**Priority:** Medium +**Created:** 2026-04-02 +**Context:** The project structure is inconsistent, with logic split across `src/components`, `src/app`, and `src/lib`. + +## Objectives +- [ ] Consolidate feature-specific logic into `src/features/`. +- [ ] Ensure components, hooks, and types for a feature live together. +- [ ] Reduce top-level directory clutter. + +## Strategy +- Follow the established pattern in `src/features/game/`. +- Create `src/features/telescope/`, `src/features/satellite/`, etc. +- Update imports across the codebase using search-and-replace. diff --git a/.knowns/tasks/task-fv3m1q - 22-referral-system-hardening-and-promotion.md b/.knowns/archive/2026-04-20-project-management-reset/task-fv3m1q - 22-referral-system-hardening-and-promotion.md similarity index 100% rename from .knowns/tasks/task-fv3m1q - 22-referral-system-hardening-and-promotion.md rename to .knowns/archive/2026-04-20-project-management-reset/task-fv3m1q - 22-referral-system-hardening-and-promotion.md diff --git a/.knowns/tasks/task-fydcth - either-remove-guest-login-option-or-make-it-impossible-to-do-too-much-without-graduating-to-full-account.md b/.knowns/archive/2026-04-20-project-management-reset/task-fydcth - either-remove-guest-login-option-or-make-it-impossible-to-do-too-much-without-graduating-to-full-account.md similarity index 100% rename from .knowns/tasks/task-fydcth - either-remove-guest-login-option-or-make-it-impossible-to-do-too-much-without-graduating-to-full-account.md rename to .knowns/archive/2026-04-20-project-management-reset/task-fydcth - either-remove-guest-login-option-or-make-it-impossible-to-do-too-much-without-graduating-to-full-account.md diff --git a/.knowns/tasks/task-g5zk5h - add-posthog-survey-to-an-appropriate-point-in-the-user-flow.md b/.knowns/archive/2026-04-20-project-management-reset/task-g5zk5h - add-posthog-survey-to-an-appropriate-point-in-the-user-flow.md similarity index 100% rename from .knowns/tasks/task-g5zk5h - add-posthog-survey-to-an-appropriate-point-in-the-user-flow.md rename to .knowns/archive/2026-04-20-project-management-reset/task-g5zk5h - add-posthog-survey-to-an-appropriate-point-in-the-user-flow.md diff --git a/.knowns/tasks/task-gyjwvy - Gameplay-API-rollout-verification-and-follow-up-list.md b/.knowns/archive/2026-04-20-project-management-reset/task-gyjwvy - Gameplay-API-rollout-verification-and-follow-up-list.md similarity index 100% rename from .knowns/tasks/task-gyjwvy - Gameplay-API-rollout-verification-and-follow-up-list.md rename to .knowns/archive/2026-04-20-project-management-reset/task-gyjwvy - Gameplay-API-rollout-verification-and-follow-up-list.md diff --git a/.knowns/tasks/task-h6p3mv - migrate-non-knowns-markdown-into-knowns.md b/.knowns/archive/2026-04-20-project-management-reset/task-h6p3mv - migrate-non-knowns-markdown-into-knowns.md similarity index 100% rename from .knowns/tasks/task-h6p3mv - migrate-non-knowns-markdown-into-knowns.md rename to .knowns/archive/2026-04-20-project-management-reset/task-h6p3mv - migrate-non-knowns-markdown-into-knowns.md diff --git a/.knowns/tasks/task-h7m2qc - Eliminate-remaining-active-client-TSX-write-mutations-via-gameplay-api-routes.md b/.knowns/archive/2026-04-20-project-management-reset/task-h7m2qc - Eliminate-remaining-active-client-TSX-write-mutations-via-gameplay-api-routes.md similarity index 100% rename from .knowns/tasks/task-h7m2qc - Eliminate-remaining-active-client-TSX-write-mutations-via-gameplay-api-routes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-h7m2qc - Eliminate-remaining-active-client-TSX-write-mutations-via-gameplay-api-routes.md diff --git a/.knowns/tasks/task-jfp2pp - Architecture-migration-API-routes-for-standalone-gameexperiment-mechanics.md b/.knowns/archive/2026-04-20-project-management-reset/task-jfp2pp - Architecture-migration-API-routes-for-standalone-gameexperiment-mechanics.md similarity index 100% rename from .knowns/tasks/task-jfp2pp - Architecture-migration-API-routes-for-standalone-gameexperiment-mechanics.md rename to .knowns/archive/2026-04-20-project-management-reset/task-jfp2pp - Architecture-migration-API-routes-for-standalone-gameexperiment-mechanics.md diff --git a/.knowns/tasks/task-k2b7wn - normalise-client-side-mineral-deposit-key-names.md b/.knowns/archive/2026-04-20-project-management-reset/task-k2b7wn - normalise-client-side-mineral-deposit-key-names.md similarity index 100% rename from .knowns/tasks/task-k2b7wn - normalise-client-side-mineral-deposit-key-names.md rename to .knowns/archive/2026-04-20-project-management-reset/task-k2b7wn - normalise-client-side-mineral-deposit-key-names.md diff --git a/.knowns/tasks/task-k3v9tq - fix-rover-setup-layout-breakage.md b/.knowns/archive/2026-04-20-project-management-reset/task-k3v9tq - fix-rover-setup-layout-breakage.md similarity index 100% rename from .knowns/tasks/task-k3v9tq - fix-rover-setup-layout-breakage.md rename to .knowns/archive/2026-04-20-project-management-reset/task-k3v9tq - fix-rover-setup-layout-breakage.md diff --git a/.knowns/tasks/task-k4m9px - 22-feature-based-filesystem-migration.md b/.knowns/archive/2026-04-20-project-management-reset/task-k4m9px - 22-feature-based-filesystem-migration.md similarity index 100% rename from .knowns/tasks/task-k4m9px - 22-feature-based-filesystem-migration.md rename to .knowns/archive/2026-04-20-project-management-reset/task-k4m9px - 22-feature-based-filesystem-migration.md diff --git a/.knowns/tasks/task-kc6w9s - 22-ecosystem-expansion-mechanics-web-to-godot.md b/.knowns/archive/2026-04-20-project-management-reset/task-kc6w9s - 22-ecosystem-expansion-mechanics-web-to-godot.md similarity index 100% rename from .knowns/tasks/task-kc6w9s - 22-ecosystem-expansion-mechanics-web-to-godot.md rename to .knowns/archive/2026-04-20-project-management-reset/task-kc6w9s - 22-ecosystem-expansion-mechanics-web-to-godot.md diff --git a/.knowns/tasks/task-l9s3pw - 22-survey-rewards-dedup-and-delivery-rules.md b/.knowns/archive/2026-04-20-project-management-reset/task-l9s3pw - 22-survey-rewards-dedup-and-delivery-rules.md similarity index 100% rename from .knowns/tasks/task-l9s3pw - 22-survey-rewards-dedup-and-delivery-rules.md rename to .knowns/archive/2026-04-20-project-management-reset/task-l9s3pw - 22-survey-rewards-dedup-and-delivery-rules.md diff --git a/.knowns/tasks/task-lp7v3x - landing-page-variant-demo-suite.md b/.knowns/archive/2026-04-20-project-management-reset/task-lp7v3x - landing-page-variant-demo-suite.md similarity index 100% rename from .knowns/tasks/task-lp7v3x - landing-page-variant-demo-suite.md rename to .knowns/archive/2026-04-20-project-management-reset/task-lp7v3x - landing-page-variant-demo-suite.md diff --git a/.knowns/tasks/task-m1v6ht - 22-community-event-notification-workflow-github-actions.md b/.knowns/archive/2026-04-20-project-management-reset/task-m1v6ht - 22-community-event-notification-workflow-github-actions.md similarity index 100% rename from .knowns/tasks/task-m1v6ht - 22-community-event-notification-workflow-github-actions.md rename to .knowns/archive/2026-04-20-project-management-reset/task-m1v6ht - 22-community-event-notification-workflow-github-actions.md diff --git a/.knowns/tasks/task-m4p8sv - 22-posthog-mechanic-micro-surveys-and-session-tokening.md b/.knowns/archive/2026-04-20-project-management-reset/task-m4p8sv - 22-posthog-mechanic-micro-surveys-and-session-tokening.md similarity index 100% rename from .knowns/tasks/task-m4p8sv - 22-posthog-mechanic-micro-surveys-and-session-tokening.md rename to .knowns/archive/2026-04-20-project-management-reset/task-m4p8sv - 22-posthog-mechanic-micro-surveys-and-session-tokening.md diff --git a/.knowns/tasks/task-m8p2na - fix-setup-navigation-hard-refresh-auth-flicker.md b/.knowns/archive/2026-04-20-project-management-reset/task-m8p2na - fix-setup-navigation-hard-refresh-auth-flicker.md similarity index 100% rename from .knowns/tasks/task-m8p2na - fix-setup-navigation-hard-refresh-auth-flicker.md rename to .knowns/archive/2026-04-20-project-management-reset/task-m8p2na - fix-setup-navigation-hard-refresh-auth-flicker.md diff --git a/.knowns/tasks/task-m9p4rs - push-db-migrations-003-004-005-to-production.md b/.knowns/archive/2026-04-20-project-management-reset/task-m9p4rs - push-db-migrations-003-004-005-to-production.md similarity index 100% rename from .knowns/tasks/task-m9p4rs - push-db-migrations-003-004-005-to-production.md rename to .knowns/archive/2026-04-20-project-management-reset/task-m9p4rs - push-db-migrations-003-004-005-to-production.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-mobile-ux-simplification.md b/.knowns/archive/2026-04-20-project-management-reset/task-mobile-ux-simplification.md new file mode 100644 index 000000000..4a66e8e08 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-mobile-ux-simplification.md @@ -0,0 +1,17 @@ +# Task: Streamline "Build" Phase for Mobile UX + +## Status +**Status:** Backlog +**Priority:** Medium +**Created:** 2026-04-02 +**Context:** The current deployment flow is multi-step and requires significant navigation, which is friction-heavy for mobile users. + +## Objectives +- [ ] Implement "One-Tap Deployment" directly from the Hub's `StructureCard`. +- [ ] Create a "Unified Mission Feed" to bypass viewport-specific navigation for active tasks. +- [ ] Simplify touch targets and navigation paths for the core "Build -> Do" loop. + +## Strategy +- Add a "Quick Action" button to `StructureCard` when in an "undeployed" state. +- Develop a `GlobalMissionFeed` component that aggregates pending anomalies from all structures. +- Reduce the number of confirmation dialogs for mobile users. diff --git a/.knowns/tasks/task-n2w7qd - remove-route-to-route-import-coupling.md b/.knowns/archive/2026-04-20-project-management-reset/task-n2w7qd - remove-route-to-route-import-coupling.md similarity index 100% rename from .knowns/tasks/task-n2w7qd - remove-route-to-route-import-coupling.md rename to .knowns/archive/2026-04-20-project-management-reset/task-n2w7qd - remove-route-to-route-import-coupling.md diff --git a/.knowns/tasks/task-n51zqx - Architecture-migration-use-client-audit-and-server-first-fetch.md b/.knowns/archive/2026-04-20-project-management-reset/task-n51zqx - Architecture-migration-use-client-audit-and-server-first-fetch.md similarity index 100% rename from .knowns/tasks/task-n51zqx - Architecture-migration-use-client-audit-and-server-first-fetch.md rename to .knowns/archive/2026-04-20-project-management-reset/task-n51zqx - Architecture-migration-use-client-audit-and-server-first-fetch.md diff --git a/.knowns/tasks/task-njy7p4 - 22-navigation-consistency-and-route-hardening.md b/.knowns/archive/2026-04-20-project-management-reset/task-njy7p4 - 22-navigation-consistency-and-route-hardening.md similarity index 100% rename from .knowns/tasks/task-njy7p4 - 22-navigation-consistency-and-route-hardening.md rename to .knowns/archive/2026-04-20-project-management-reset/task-njy7p4 - 22-navigation-consistency-and-route-hardening.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-ob1-galaxyzoo-branch-bug.md b/.knowns/archive/2026-04-20-project-management-reset/task-ob1-galaxyzoo-branch-bug.md new file mode 100644 index 000000000..5fc7c3a50 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-ob1-galaxyzoo-branch-bug.md @@ -0,0 +1,42 @@ +--- +id: task-ob1-galaxyzoo-branch-bug +title: "Onboarding: Fix GalaxyZoo showTutorial branch renders same component" +status: done +priority: high +parent: task-sxdk5w +createdAt: '2026-04-07T00:00:00Z' +--- + +# Onboarding: Fix GalaxyZoo showTutorial branch renders same component + +## Status +**Status:** Open +**Priority:** High +**Created:** 2026-04-07 + +## Context +`TelescopeGalaxyZoo` (default export in `src/components/projects/Telescopes/GalaxyZoo.tsx`) has +a `showTutorial` boolean toggled by a "View Tutorial / Return to Task" button. Both branches of +the conditional render `` — the tutorial is never dismissed and the +classification form is never shown as the standalone view. + +```tsx +// line 247–251 — both branches identical +{showTutorial ? ( + +) : ( + +)} +``` + +## Objectives +- [ ] Replace the `showTutorial ? X : Y` with a proper split: + - `showTutorial === true` → render only `` (tutorial flow) + - `showTutorial === false` → render the classification form directly (skip tutorial, go straight to classify) +- [ ] The "View Tutorial" button should toggle back to the tutorial; "Return to Task" should show just the form + +## Strategy +- The classification form is already embedded inside `GalaxyZooTutorial` (shows after `handleTutorialComplete` sets `showClassification=true`) +- Simplest fix: keep the tutorial-gated flow as default; the "Return to Task" path should render a + standalone classification panel (duplicate the form section, not the whole tutorial component) +- Alternatively: extract the classification form into its own component and compose both paths from it diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-ob2-missing-tutorial-selectors.md b/.knowns/archive/2026-04-20-project-management-reset/task-ob2-missing-tutorial-selectors.md new file mode 100644 index 000000000..e45fdd288 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-ob2-missing-tutorial-selectors.md @@ -0,0 +1,45 @@ +--- +id: task-ob2-missing-tutorial-selectors +title: "Onboarding: data-tutorial selectors referenced in TutorialWrapper steps don't exist in DOM" +status: done +priority: high +parent: task-sxdk5w +createdAt: '2026-04-07T00:00:00Z' +--- + +# Onboarding: data-tutorial selectors don't exist in rendered components + +## Status +**Status:** Open +**Priority:** High +**Created:** 2026-04-07 + +## Context +`TutorialWrapper.tsx` defines step sets (TELESCOPE_INTRO_STEPS, TELESCOPE_DEPLOY_STEPS, +SATELLITE_INTRO_STEPS, ROVER_INTRO_STEPS, SOLAR_INTRO_STEPS) with `highlightSelector` values +like `[data-tutorial='deploy-telescope']`, `[data-tutorial='project-options']`, +`[data-tutorial='confirm-deploy']`, `[data-tutorial='deploy-rover']`, +`[data-tutorial='participate-solar']`, etc. + +`InteractiveTutorial.tsx` uses `document.querySelector(step.highlightSelector)` to position the +highlight cutout. When the selector matches nothing, the highlight silently renders at (0,0) — +the tutorial runs but the pointer never points at anything real. + +A grep for these specific values finds them only in `TutorialWrapper.tsx` itself, not in any +structure/deploy component. + +## Objectives +- [ ] Audit all `highlightSelector` values in every step set in `TutorialWrapper.tsx` +- [ ] Add the matching `data-tutorial="…"` attributes to the actual rendered elements: + - `[data-tutorial='deploy-telescope']` → the "Deploy Telescope" button in TelescopeSection / TelescopeDeploySidebar + - `[data-tutorial='project-options']` → the project option cards in the deploy sidebar + - `[data-tutorial='confirm-deploy']` → the confirm/deploy button in DeploySidebar + - `[data-tutorial='deploy-rover']` → the rover deploy button in RoverSection + - `[data-tutorial='participate-solar']` → the participate button in SolarHealth + - `[data-tutorial='planet-nav-next']` → the next-planet nav button for satellite tutorial + - `[data-tutorial='deploy-satellite']` → the satellite deploy button + +## Strategy +- Add `data-tutorial` attributes alongside existing `data-*` attributes in each component +- Do NOT restructure component logic — attribute additions only +- After adding, manually verify in browser that the highlight cutout correctly frames each element diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-ob3-tutorial-never-instantiated.md b/.knowns/archive/2026-04-20-project-management-reset/task-ob3-tutorial-never-instantiated.md new file mode 100644 index 000000000..8022bbe82 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-ob3-tutorial-never-instantiated.md @@ -0,0 +1,39 @@ +--- +id: task-ob3-tutorial-never-instantiated +title: "Onboarding: TutorialWrapper + step sets defined but never used in routing" +status: done +priority: high +parent: task-sxdk5w +createdAt: '2026-04-07T00:00:00Z' +--- + +# Onboarding: Tutorial step sets exist but are never instantiated + +## Status +**Status:** Open +**Priority:** High +**Created:** 2026-04-07 + +## Context +`TutorialWrapper.tsx` defines and exports `SATELLITE_INTRO_STEPS` and `ROVER_INTRO_STEPS`, and +defines (but does not export) `TELESCOPE_INTRO_STEPS`, `TELESCOPE_DEPLOY_STEPS`, +`SOLAR_INTRO_STEPS`. `InteractiveTutorial.tsx` also defines `ONBOARDING_STEPS` and +`DEPLOYMENT_STEPS` internally but does not export or use them. + +None of these step sets are imported or instantiated anywhere in the app's routing. No page or +layout renders ``. The entire interactive tutorial +system is built but dead. + +## Objectives +- [ ] Export `TELESCOPE_INTRO_STEPS`, `TELESCOPE_DEPLOY_STEPS`, `SOLAR_INTRO_STEPS` from `TutorialWrapper.tsx` +- [ ] Wrap the telescope structure viewport (or setup page) with `` +- [ ] Wrap the rover structure viewport with `` +- [ ] Wrap the satellite structure viewport with `` +- [ ] Wrap the deploy sidebar or setup pages with `` +- [ ] Ensure `data-tutorial` attributes exist on target elements (see task-ob2) +- [ ] Smoke test: new user flow shows tutorial overlay on first visit, not on second + +## Strategy +- Start with telescope setup page (`/setup/telescope`) — it's the most common first-run path +- Add `` at the page level so it wraps the entire viewport content +- Confirm `TutorialId` type in `useUserPreferences` includes the new IDs diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-ob4-missing-onboarding-projects.md b/.knowns/archive/2026-04-20-project-management-reset/task-ob4-missing-onboarding-projects.md new file mode 100644 index 000000000..3f2b28d7b --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-ob4-missing-onboarding-projects.md @@ -0,0 +1,38 @@ +--- +id: task-ob4-missing-onboarding-projects +title: "Onboarding: Solar Monitoring and Asteroid Hunting absent from project selection" +status: done +priority: medium +createdAt: '2026-04-07T00:00:00Z' +--- + +# Onboarding: Only 3 of 5 projects shown in selection step + +## Status +**Status:** Open +**Priority:** Medium +**Created:** 2026-04-07 + +## Context +`src/components/onboarding/onboarding-data.ts` exports `PROJECTS` with 3 entries: +- planet-hunting → Telescope +- rover-training → Rover +- cloud-tracking → Satellite + +`SETUP_MAP` already has entries for `"asteroid-hunting"` (→ `/setup/telescope`) and +`"solar-monitoring"` (→ `/setup/solar`), but neither has a corresponding `PROJECTS` entry so +they never appear in the selection UI. + +`ProjectType` in `useUserPreferences` needs to be checked to confirm it includes the new IDs. + +## Objectives +- [ ] Add "Asteroid Hunting" entry to `PROJECTS` in `onboarding-data.ts`: + - `id: "asteroid-hunting"`, icon: `Telescope` (or `Target`), color: orange/amber, structure: `"Telescope"`, description referencing the Daily Minor Planet / asteroid detection mission +- [ ] Add "Solar Monitoring" entry to `PROJECTS`: + - `id: "solar-monitoring"`, icon: `Sun` or `Zap`, color: yellow, structure: `"Solar Observatory"`, description referencing sunspot classification +- [ ] Confirm `ProjectType` union in `useUserPreferences.ts` includes `"asteroid-hunting"` and `"solar-monitoring"` +- [ ] Update `SETUP_MAP` if needed (entries already exist, verify they're correct) + +## Strategy +- Keep the same shape as existing PROJECTS entries (id, name, icon, color, bg, border, glow, structure, description) +- Asteroid Hunting and Solar Monitoring are separate missions; don't merge with planet-hunting diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-ob5-no-reentry-path.md b/.knowns/archive/2026-04-20-project-management-reset/task-ob5-no-reentry-path.md new file mode 100644 index 000000000..66d5ad230 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-ob5-no-reentry-path.md @@ -0,0 +1,34 @@ +--- +id: task-ob5-no-reentry-path +title: "Onboarding: no re-entry context when user bounces mid-flow" +status: done +priority: medium +parent: task-sxdk5w +createdAt: '2026-04-07T00:00:00Z' +--- + +# Onboarding: Mid-flow bounce lands at "Station Arrival" with no context + +## Status +**Status:** Open +**Priority:** Medium +**Created:** 2026-04-07 + +## Context +If a user reaches "project-selection" or "structure-intro" and then refreshes or navigates away, +`onboarding/page.tsx` always starts at `step = "intro"` on remount. They see "Station Arrival" +again with no memory of where they were. There is no step persistence between page loads. + +The existing `preferences.projectInterests` is set only on finalise, so there's no in-progress +signal to resume from. + +## Objectives +- [ ] Persist the current onboarding step (and selected project) to localStorage so the page can resume +- [ ] On mount, if `localStorage.onboarding_step` is set and `!hasCompletedOnboarding`, restore to that step and project +- [ ] Clear the persisted step when `completeOnboarding()` is called +- [ ] If user is at "structure-intro" step but has no selectedProject in storage, fall back to "project-selection" + +## Strategy +- Use a key like `ss_onboarding_step` and `ss_onboarding_project` in localStorage +- Read these in the same `useEffect` block that currently initialises `showIntro` +- Don't use `useUserPreferences` for this — it's transient state that shouldn't outlive the onboarding flow diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-ob6-setup-pages-no-tutorial.md b/.knowns/archive/2026-04-20-project-management-reset/task-ob6-setup-pages-no-tutorial.md new file mode 100644 index 000000000..f26b5ab25 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-ob6-setup-pages-no-tutorial.md @@ -0,0 +1,35 @@ +--- +id: task-ob6-setup-pages-no-tutorial +title: "Onboarding: setup pages don't invoke tutorial system — first action is unaided" +status: done +priority: medium +parent: task-sxdk5w +createdAt: '2026-04-07T00:00:00Z' +--- + +# Onboarding: Setup pages don't trigger the interactive tutorial + +## Status +**Status:** Open +**Priority:** Medium +**Created:** 2026-04-07 + +## Context +After `StructureIntroStep`, the user is sent to `/setup/telescope`, `/setup/rover`, or +`/setup/satellite` via `SETUP_MAP`. These setup pages are the first real interaction with station +mechanics, but they render without any tutorial overlay. + +`TutorialWrapper` and the step sets exist and are functional — they're just never mounted on +these pages. The user is dropped into a deploy flow with no guidance. + +## Objectives +- [ ] Wrap `/setup/telescope` content with `` +- [ ] Wrap `/setup/rover` content with `` +- [ ] Wrap `/setup/satellite` content with `` +- [ ] Confirm `data-tutorial` attributes exist on the elements targeted by each step (see task-ob2) +- [ ] TutorialId literals must be added to the `TutorialId` union in `useUserPreferences.ts` + +## Strategy +- Read the setup page files first to understand their current layout before wrapping +- The wrapper should surround the page's main content area, not the outermost layout shell +- This is blocked by task-ob2 (selectors) and task-ob3 (exports) — complete those first diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-ob7-alpha-watermark.md b/.knowns/archive/2026-04-20-project-management-reset/task-ob7-alpha-watermark.md new file mode 100644 index 000000000..bc1342a5b --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-ob7-alpha-watermark.md @@ -0,0 +1,32 @@ +--- +id: task-ob7-alpha-watermark +title: "Onboarding: remove v3.0.0-alpha version watermark from footer" +status: done +priority: low +parent: task-sxdk5w +createdAt: '2026-04-07T00:00:00Z' +--- + +# Onboarding: v3.0.0-alpha watermark visible in production onboarding footer + +## Status +**Status:** Open +**Priority:** Low +**Created:** 2026-04-07 + +## Context +`src/app/onboarding/page.tsx` line 150 renders a hardcoded version string in the HUD footer: + +```tsx +v3.0.0-alpha +``` + +This is a hardcoded string that leaks internal versioning to new users on their very first +interaction with the product. It adds no value and signals "unfinished" at the exact moment +first impressions matter most. + +## Objectives +- [ ] Remove the version span entirely from the onboarding footer + +## Strategy +- Simple deletion — don't replace with anything, the footer reads fine with just the "System Online" indicator diff --git a/.knowns/tasks/task-p7l3mx - Update-privacy-policy-and-terms.md b/.knowns/archive/2026-04-20-project-management-reset/task-p7l3mx - Update-privacy-policy-and-terms.md similarity index 100% rename from .knowns/tasks/task-p7l3mx - Update-privacy-policy-and-terms.md rename to .knowns/archive/2026-04-20-project-management-reset/task-p7l3mx - Update-privacy-policy-and-terms.md diff --git a/.knowns/tasks/task-p8d4zn - 22-webapp-user-flow-ux-audit-and-fixes.md b/.knowns/archive/2026-04-20-project-management-reset/task-p8d4zn - 22-webapp-user-flow-ux-audit-and-fixes.md similarity index 100% rename from .knowns/tasks/task-p8d4zn - 22-webapp-user-flow-ux-audit-and-fixes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-p8d4zn - 22-webapp-user-flow-ux-audit-and-fixes.md diff --git a/.knowns/tasks/task-p8v2qn - 22-posthog-dashboard-for-mechanic-micro-surveys.md b/.knowns/archive/2026-04-20-project-management-reset/task-p8v2qn - 22-posthog-dashboard-for-mechanic-micro-surveys.md similarity index 100% rename from .knowns/tasks/task-p8v2qn - 22-posthog-dashboard-for-mechanic-micro-surveys.md rename to .knowns/archive/2026-04-20-project-management-reset/task-p8v2qn - 22-posthog-dashboard-for-mechanic-micro-surveys.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-performance-audit.md b/.knowns/archive/2026-04-20-project-management-reset/task-performance-audit.md new file mode 100644 index 000000000..54cf530d7 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-performance-audit.md @@ -0,0 +1,17 @@ +# Task: Asset & Animation Performance Audit + +## Status +**Status:** Backlog +**Priority:** Low +**Created:** 2026-04-02 +**Context:** Visual flair is important, but heavy DOM-based animations can lag on older mobile devices. + +## Objectives +- [ ] Optimize `LivingWorldBg.tsx` for high entity counts. +- [ ] Convert heavy SVG/DOM animations to `` where appropriate. +- [ ] Audit image sizes and formats (WebP migration). + +## Strategy +- Profile the `LivingWorldBg` with 100+ entities to identify bottlenecks. +- Benchmark a Canvas-based starfield vs the current CSS-based one. +- Implement lazy loading or lower-quality assets for low-power devices. diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-planet-painter-restoration.md b/.knowns/archive/2026-04-20-project-management-reset/task-planet-painter-restoration.md new file mode 100644 index 000000000..cdb6b2084 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-planet-painter-restoration.md @@ -0,0 +1,32 @@ +# Task: Restore Planet Painter Functionality + +## Status +**Status:** Deferred +**Priority:** Low +**Created:** 2026-04-02 +**Context:** Removed during a simplification sweep to improve performance and focus on core gameplay. + +## Background +The "Planet Painter" was a complex 3D component used at the end of the Planet Hunters mission for specific anomalies. It allowed users to visualize and configure planets based on classification data. + +## What Was Removed +- `src/app/planets/paint/[id]/page.tsx`: The main page for the planet painter. +- `src/app/planets/[id]/page.tsx`: Planet detail page that used the generator. +- `src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator.tsx`: The core generator component. +- `src/components/discovery/data-sources/Astronomers/PlanetHunters/planetViewer.tsx`: The 3D viewer using `@react-three/fiber`. +- `src/components/discovery/data-sources/Astronomers/PlanetHunters/planet.tsx`: The 3D planet mesh and shaders. +- `src/components/discovery/data-sources/Astronomers/PlanetHunters/SettingsPanel.tsx`: UI for adjusting planet parameters. +- `src/components/discovery/planets/physics.ts`: Physics and mineral logic for planets. +- Shaders in `src/shared/utils/generators/PH/`. + +## Why It Was Removed +- **High Complexity:** Involved thousands of lines of code and heavy 3D dependencies. +- **Low Usage:** It was an "end-game" mechanic that few users reached. +- **Performance:** Caused potential slowdowns on mobile devices due to WebGL and complex shaders. +- **User Experience:** The component was not well-designed and felt disconnected from the core loop. + +## Restoration Requirements +1. **Redesign:** The UI needs to be more intuitive and better integrated into the main game flow. +2. **Performance:** Optimize the 3D rendering for mobile devices. +3. **Connectivity:** Ensure it clearly links back to the citizen science data that "paints" the planet. +4. **Simplification:** Reduce the number of controls and focus on the visual reward of "discovering" a planet. diff --git a/.knowns/tasks/task-q5ais9 - Migrate-social-mutation-paths-to-API-routes.md b/.knowns/archive/2026-04-20-project-management-reset/task-q5ais9 - Migrate-social-mutation-paths-to-API-routes.md similarity index 100% rename from .knowns/tasks/task-q5ais9 - Migrate-social-mutation-paths-to-API-routes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-q5ais9 - Migrate-social-mutation-paths-to-API-routes.md diff --git a/.knowns/tasks/task-q9m4ls - source-js-to-ts-and-css-elimination-pass.md b/.knowns/archive/2026-04-20-project-management-reset/task-q9m4ls - source-js-to-ts-and-css-elimination-pass.md similarity index 100% rename from .knowns/tasks/task-q9m4ls - source-js-to-ts-and-css-elimination-pass.md rename to .knowns/archive/2026-04-20-project-management-reset/task-q9m4ls - source-js-to-ts-and-css-elimination-pass.md diff --git a/.knowns/tasks/task-r4s8yd - Migrate-additional-mechanic-config-social-and-zoodex-writes-to-api-routes.md b/.knowns/archive/2026-04-20-project-management-reset/task-r4s8yd - Migrate-additional-mechanic-config-social-and-zoodex-writes-to-api-routes.md similarity index 100% rename from .knowns/tasks/task-r4s8yd - Migrate-additional-mechanic-config-social-and-zoodex-writes-to-api-routes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-r4s8yd - Migrate-additional-mechanic-config-social-and-zoodex-writes-to-api-routes.md diff --git a/.knowns/tasks/task-r6c3k1 - ecosystem-v0-release-readiness-for-client.md b/.knowns/archive/2026-04-20-project-management-reset/task-r6c3k1 - ecosystem-v0-release-readiness-for-client.md similarity index 100% rename from .knowns/tasks/task-r6c3k1 - ecosystem-v0-release-readiness-for-client.md rename to .knowns/archive/2026-04-20-project-management-reset/task-r6c3k1 - ecosystem-v0-release-readiness-for-client.md diff --git a/.knowns/tasks/task-r7d2km - Protect-annotation-and-posthog-dependencies-and-validate-tests.md b/.knowns/archive/2026-04-20-project-management-reset/task-r7d2km - Protect-annotation-and-posthog-dependencies-and-validate-tests.md similarity index 100% rename from .knowns/tasks/task-r7d2km - Protect-annotation-and-posthog-dependencies-and-validate-tests.md rename to .knowns/archive/2026-04-20-project-management-reset/task-r7d2km - Protect-annotation-and-posthog-dependencies-and-validate-tests.md diff --git a/.knowns/tasks/task-r8t6vy - component-and-page-consolidation-audit.md b/.knowns/archive/2026-04-20-project-management-reset/task-r8t6vy - component-and-page-consolidation-audit.md similarity index 100% rename from .knowns/tasks/task-r8t6vy - component-and-page-consolidation-audit.md rename to .knowns/archive/2026-04-20-project-management-reset/task-r8t6vy - component-and-page-consolidation-audit.md diff --git a/.knowns/tasks/task-redesign-3-0-orchestration.md b/.knowns/archive/2026-04-20-project-management-reset/task-redesign-3-0-orchestration.md similarity index 100% rename from .knowns/tasks/task-redesign-3-0-orchestration.md rename to .knowns/archive/2026-04-20-project-management-reset/task-redesign-3-0-orchestration.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-refactor-god-components.md b/.knowns/archive/2026-04-20-project-management-reset/task-refactor-god-components.md new file mode 100644 index 000000000..d03d59adc --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-refactor-god-components.md @@ -0,0 +1,17 @@ +# Task: Refactor "God Components" (NextScene, ResearchPanel) + +## Status +**Status:** In Progress +**Priority:** High +**Created:** 2026-04-02 +**Context:** Components like `NextScene.tsx` and `CompactResearchPanel.tsx` have grown to 600-700+ lines, making them hard to maintain and prone to regressions. + +## Objectives +- [ ] Decompose `NextScene.tsx` into smaller, mission-specific "ResultCards". +- [ ] Refactor `CompactResearchPanel.tsx` to use a data-driven approach for upgrades. +- [ ] Reduce line counts and improve modularity. + +## Strategy +- Extract mission-specific logic into dedicated components in `src/components/projects/(classifications)/results/`. +- Create a centralized `upgrades-config.ts` for the research panel. +- Use a registry pattern to load result components based on classification type. diff --git a/.knowns/tasks/task-rf8k2n - 22-star-sailors-feedback-immediate-ux-stability-sprint.md b/.knowns/archive/2026-04-20-project-management-reset/task-rf8k2n - 22-star-sailors-feedback-immediate-ux-stability-sprint.md similarity index 100% rename from .knowns/tasks/task-rf8k2n - 22-star-sailors-feedback-immediate-ux-stability-sprint.md rename to .knowns/archive/2026-04-20-project-management-reset/task-rf8k2n - 22-star-sailors-feedback-immediate-ux-stability-sprint.md diff --git a/.knowns/tasks/task-s3q9lx - 22-full-test-suite-green-and-ci-alignment.md b/.knowns/archive/2026-04-20-project-management-reset/task-s3q9lx - 22-full-test-suite-green-and-ci-alignment.md similarity index 100% rename from .knowns/tasks/task-s3q9lx - 22-full-test-suite-green-and-ci-alignment.md rename to .knowns/archive/2026-04-20-project-management-reset/task-s3q9lx - 22-full-test-suite-green-and-ci-alignment.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-sxdk5w - MVP-Sprint-Onboarding-Repair-Tutorial-Wiring.md b/.knowns/archive/2026-04-20-project-management-reset/task-sxdk5w - MVP-Sprint-Onboarding-Repair-Tutorial-Wiring.md new file mode 100644 index 000000000..887e232c7 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-sxdk5w - MVP-Sprint-Onboarding-Repair-Tutorial-Wiring.md @@ -0,0 +1,135 @@ +--- +id: sxdk5w +title: 'MVP Sprint: Onboarding Repair & Tutorial Wiring' +status: done +priority: high +labels: + - sprint + - mvp + - onboarding +createdAt: '2026-04-13T03:07:51.912Z' +updatedAt: '2026-04-13T03:07:51.912Z' +timeSpent: 0 +--- +# MVP Sprint: Onboarding Repair & Tutorial Wiring + +## Description + + +Sprint to get Star Sailors to a shippable MVP. Focus: fix broken onboarding, wire the tutorial +system, remove alpha signals. Prerequisite for any public launch. + +Analysed 2026-04-13. See doc `incomplete-tickets-reference-2026-04-13` for the full ticket archive. + + +--- + +## MVP Analysis — 2026-04-13 + +### What the game is +Star Sailors is a citizen science PWA where users deploy four "structures" (Telescope, Satellite, +Rover, Solar Observatory) and classify real astronomical data (TESS light curves, Mars terrain, +orbital imagery, SDO sunspot images). The hub (`/game`) is the mission control screen. + +### Current state: what works +- **Hub layout** — Two-column grid (left: mission brief + sector radar + structure cards; right: + referrals + mission log + leaderboard + coral fishtank). Solid structure. +- **StationNav** — Bottom mobile nav with console-button-style press animation. Polished. +- **HUDStrip** — Three live metrics (Signals / Pending / Classifications) with odometer animation. + Already resolved ticket 9ztpbl — metrics and position were decided in code. +- **AnimatePresence transitions** — Base ↔ viewport transitions are smooth (scale + opacity). +- **LivingWorldBg + TelescopeBackground** — Ambient layers load deferred, no jank. +- **MissionBriefCard** — Contextual CTA adapts to state (boot / progress / alert). Good. +- **Referral system** — Wired and functional. +- **Onboarding flow** — Intro → project-selection (all 5 projects, including asteroid+solar which + were believed missing but were already in onboarding-data.ts) → structure-intro → /setup/*. +- **API routes** — All write paths migrated to server routes. No client-side mutations. +- **PostHog + Sentry** — Instrumented. + +### Current state: what's broken or missing for MVP + +#### Critical — blocks first-run experience entirely +| Ticket | Issue | File | +|--------|-------|------| +| task-ob1 | GalaxyZoo `showTutorial` both branches render same component — classification form never shown | `src/components/projects/Telescopes/GalaxyZoo.tsx:247` | +| task-ob2 | All `data-tutorial` highlight selectors in TutorialWrapper point to elements that don't have those attributes — tutorial highlights render at (0,0) | `TutorialWrapper.tsx` + structure/deploy components | +| task-ob3 | TutorialWrapper, TELESCOPE_INTRO_STEPS, ROVER_INTRO_STEPS, SOLAR_INTRO_STEPS defined but never mounted anywhere — tutorial system is dead | Setup pages + viewport wrappers | +| task-ob6 | Setup pages (/setup/telescope, /setup/rover, /setup/satellite) drop users into deploy flow with zero guidance | Depends on ob2+ob3 | + +#### High — degrades new user retention +| Ticket | Issue | +|--------|-------| +| task-ob5 | Mid-flow onboarding bounce resets to "Station Arrival" — no step persistence | +| task-30-p1 | One remaining item: dark/light mode visual QA pass (all implementation done) | + +#### Quick wins +| Ticket | Issue | +|--------|-------| +| task-ob7 | `v3.0.0-alpha` hardcoded in onboarding footer — one-line delete | + +### UX rating (pre-sprint) +| Area | Score | Notes | +|------|-------|-------| +| Visual design | 8/10 | Consistent sci-fi dark theme, good animation, colour system cohesive | +| Mobile layout | 7/10 | Bottom nav works well; structure cards horizontal scroll on mobile is acceptable | +| Desktop layout | 7/10 | Right column is content-dense but not overwhelming | +| Responsiveness | 8/10 | `h-[100dvh]`, `pb-[80px] md:pb-0`, breakpoint-aware grid — solid | +| Onboarding UX | 3/10 | Tutorial system completely dead; GalaxyZoo core mechanic broken; alpha watermark visible | +| First-run clarity | 4/10 | MissionBriefCard helps but no guided tutorial means users deploy blind | +| Performance | 7/10 | Dynamic imports on heavy components, deferred ambient layers, 60s polling interval | +| Consistency | 8/10 | Design tokens applied consistently; Nunito headings, mono data, colour per structure | + +**Overall MVP readiness: 5/10.** The hub shell is well-built. The onboarding-to-classification +path is broken. No new user can be guided through their first classification without fixes. + +### PWA simulation +Skipped — no additional >500MB install required, but running a full Next.js production build + +PWA install cycle was deferred. The `PWAPrompt` component is present and renders after the game +hub loads. Service worker registration and manifest are assumed to be in place from prior work. + +### Design elements needed for MVP + +The following UI elements need design decisions before the sprint is complete: + +1. **Tutorial overlay style** — `InteractiveTutorial.tsx` renders a highlight cutout + tooltip. + The visual style exists but has never been seen in production. Needs a design review pass + once ob2+ob3 are implemented to confirm the overlay doesn't clash with dark backgrounds. + +2. **Empty state for structure viewports** — When a user visits a structure tab before deploying, + what do they see? Currently falls through to the classification content even if nothing is + deployed. Needs a clear "deploy first" empty state per viewport. + +3. **`aaigmw` — Typography and colour token spec** — The design token doc is not written. + This is a pre-existing gap (assigned @Liam). Not blocking the sprint but needed before + any further design work starts. + +4. **Homepage hero, nav IA, about copy** — Tickets `7kznko`, `uayboz`, `u6zo6x`, `xdbtg8` + are all design/copy todo items. Not in this sprint — parked in the reference doc. + +--- + +## Sprint Tasks (child tickets) + +Ordered by dependency: + +1. **task-ob7** — Remove alpha watermark (1 line, no deps) ← start here +2. **task-ob1** — Fix GalaxyZoo showTutorial conditional (no deps, isolated) +3. **task-ob2** — Add data-tutorial attributes to DOM elements (enables ob3+ob6) +4. **task-ob3** — Wire TutorialWrapper into routing (depends on ob2) +5. **task-ob5** — Persist onboarding step to localStorage (no deps) +6. **task-ob6** — Wrap setup pages with TutorialWrapper (depends on ob2+ob3) +7. **task-30-p1** — Visual QA pass on dark/light mode tokens (sign-off gate) + +## Questions before implementation + +Before starting ob3 and ob6, answers needed: + +1. Should the tutorial auto-start on first visit, or require the user to click "Start Tutorial"? + (TutorialWrapper currently auto-starts based on `hasTutorialCompleted` check) + +2. For the GalaxyZoo fix (ob1): should "Return to Task" show a standalone classification panel, + or should it skip to the step after tutorial completion inside GalaxyZooTutorial? The latter + is simpler; the former is more correct UX. + +3. Is there a mobile-specific design concern for the tutorial overlay highlight cutout? + 375px is tight for a tooltip + highlight simultaneously. diff --git a/.knowns/tasks/task-tdi1lj - Add-linkreferrals-between-client-and-saily.md b/.knowns/archive/2026-04-20-project-management-reset/task-tdi1lj - Add-linkreferrals-between-client-and-saily.md similarity index 100% rename from .knowns/tasks/task-tdi1lj - Add-linkreferrals-between-client-and-saily.md rename to .knowns/archive/2026-04-20-project-management-reset/task-tdi1lj - Add-linkreferrals-between-client-and-saily.md diff --git a/.knowns/tasks/task-te6e1b - set-up-test-suite.md b/.knowns/archive/2026-04-20-project-management-reset/task-te6e1b - set-up-test-suite.md similarity index 100% rename from .knowns/tasks/task-te6e1b - set-up-test-suite.md rename to .knowns/archive/2026-04-20-project-management-reset/task-te6e1b - set-up-test-suite.md diff --git a/.knowns/tasks/task-u5k2re - 22-saily-core-daily-anomaly-and-annotation-workflow.md b/.knowns/archive/2026-04-20-project-management-reset/task-u5k2re - 22-saily-core-daily-anomaly-and-annotation-workflow.md similarity index 100% rename from .knowns/tasks/task-u5k2re - 22-saily-core-daily-anomaly-and-annotation-workflow.md rename to .knowns/archive/2026-04-20-project-management-reset/task-u5k2re - 22-saily-core-daily-anomaly-and-annotation-workflow.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-u6zo6x - Write-homepage-headline-and-subhead-variants.md b/.knowns/archive/2026-04-20-project-management-reset/task-u6zo6x - Write-homepage-headline-and-subhead-variants.md new file mode 100644 index 000000000..583f9b717 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-u6zo6x - Write-homepage-headline-and-subhead-variants.md @@ -0,0 +1,86 @@ +--- +id: u6zo6x +title: Write homepage headline and subhead variants +status: done +priority: medium +labels: + - writing +createdAt: '2026-03-26T02:31:50.705Z' +updatedAt: '2026-03-26T09:18:05.567Z' +timeSpent: 0 +--- +# Write homepage headline and subhead variants + +## Description + + +The 3.0 homepage needs headline + subhead options to test. Write 3–5 variants that communicate the citizen science gaming angle. + + +## Acceptance Criteria + +- [ ] #1 At least 3 headline/subhead variants written + + +## Headline / Subhead Variants + +All use Syne Mono at `text-4xl md:text-5xl`. Subheads at `text-base text-muted-foreground`. +The micro label above reads `01 / REAL ASTRONOMY` in all variants. + +--- + +**Option A — Direct, game-first** +> Headline: `Real science.` +> Headline line 2: `Your kind of game.` +> Subhead: Deploy telescopes. Classify real NASA data. Make actual discoveries. + +*Shortest. Punchy. The existing metadata title already uses this — validates it's the right direction.* + +--- + +**Option B — Mission framing** +> Headline: `Your station.` +> Headline line 2: `The universe's data.` +> Subhead: You're a citizen scientist now. Deploy structures, classify real signals, and help map the cosmos. + +*Emphasises ownership and scale. "Station" ties to the game hub UX.* + +--- + +**Option C — Action-first** +> Headline: `Deploy. Classify.` +> Headline line 2: `Discover.` +> Subhead: Real telescope data. Real exoplanet candidates. Real science — wrapped in a game you'll actually play. + +*Verb-driven. Works well with the terminal/monospace aesthetic.* + +--- + +**Option D — Tension/contrast** +> Headline: `This isn't a simulation.` +> Subhead: Star Sailors puts real NASA and TESS data in your hands. Play the game. Do the science. + +*Single headline line. Bold claim up front. Risk: could be confusing without context.* + +--- + +**Option E — Community angle** +> Headline: `Join the fleet.` +> Headline line 2: `Map the cosmos.` +> Subhead: Thousands of citizen scientists have already made discoveries. Your telescope is ready. + +*Social proof baked into headline. Works if live-stat strip shows real numbers.* + +--- + +### Recommendation + +**Option A** for launch — shortest, matches existing SEO title, proven direction. +**Option C** as A/B test — strongest verb energy, suits monospace type treatment. + +## Implementation Notes + + +kanban: k0drei + + diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-uayboz - Sketch-the-30-navigation-and-top-level-IA.md b/.knowns/archive/2026-04-20-project-management-reset/task-uayboz - Sketch-the-30-navigation-and-top-level-IA.md new file mode 100644 index 000000000..4b4b8c47c --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-uayboz - Sketch-the-30-navigation-and-top-level-IA.md @@ -0,0 +1,97 @@ +--- +id: uayboz +title: Sketch the 3.0 navigation and top-level IA +status: done +priority: medium +labels: + - sketch + - design +createdAt: '2026-03-26T02:31:51.002Z' +updatedAt: '2026-03-26T09:18:05.891Z' +timeSpent: 0 +--- +# Sketch the 3.0 navigation and top-level IA + +## Description + + +The 3.0 navigation structure needs a sketch. What's in the nav, how is it organised, and how does it handle logged-in vs logged-out states? + + +## Acceptance Criteria + +- [ ] #1 Navigation sketch complete +- [ ] #2 Logged-in and logged-out states covered + + +## Navigation IA Sketch + +### Top-level routes + +``` +/ → Landing (logged-out gate — redirects to /game if session exists) +/auth → Sign in / create account +/game → Mission hub (requires auth) +/onboarding → First-run flow (requires auth, redirects away if completed) +/about → About / mission page (public) +/referrals → Referral page (auth) +/leaderboards → Public leaderboards +/posts → Community posts (auth) +``` + +### Logged-out nav (homepage `/`) + +``` +┌──────────────────────────────────────────────────────┐ +│ ★ STAR SAILORS [About] [Sign in →] │ +└──────────────────────────────────────────────────────┘ +``` + +- Logo left: `★ STAR SAILORS` in Syne Mono +- `About` → `/about` (ghost link, `text-muted-foreground`) +- `Sign in →` → `/auth` (primary button with glow) +- No hamburger on mobile — just logo + sign-in button (About moves to footer) +- No product links in the nav — the three product cards are on the hero page below the fold + +### Logged-in nav (game hub `/game`) + +The hub uses `CommandHeader` which is its own fixed top bar — not a shared nav component. + +``` +┌──────────────────────────────────────────────────────┐ +│ [agency ID] ★ STAR SAILORS [🔔] [◯ profile] │ +└──────────────────────────────────────────────────────┘ +``` + +- Agency ID left (username or truncated UUID) +- Logo center +- Alerts icon + profile avatar right +- No route links — navigation is the bottom StationNav (SCOPE / SAT / ROVER / SOLAR / HOME) + +### Bottom navigation (in-game, mobile-first) + +``` +┌─────────────────────────────────────────┐ +│ [🔭 SCOPE] [🛰 SAT] [★ HOME] [🚗 ROVER] [☀ SOLAR] │ +└─────────────────────────────────────────┘ +``` + +- HOME (★) is the center anchor — slightly larger, round button +- Console-button physical press animation on tap (already implemented) +- Alert LED above each button when signals pending +- Hidden on desktop — right-column and sidebar handle navigation on lg+ + +### IA decisions + +1. **No global nav in the game** — the hub IS the nav. Structure tabs are the navigation system. +2. **About and public routes** share a minimal logged-out nav — consistent logo + CTA only. +3. **No breadcrumbs** — routes are shallow; back navigation via ViewportHeader `← back` button. +4. **Onboarding is a full-screen takeover** — no nav visible during onboarding flow. +5. **Mobile-first**: everything works in bottom nav; right column and sidebars are desktop-only enhancements. + +## Implementation Notes + + +kanban: s5j08d + + diff --git a/.knowns/tasks/task-utvnlb - Migrate-classification-write-paths-to-API-routes.md b/.knowns/archive/2026-04-20-project-management-reset/task-utvnlb - Migrate-classification-write-paths-to-API-routes.md similarity index 100% rename from .knowns/tasks/task-utvnlb - Migrate-classification-write-paths-to-API-routes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-utvnlb - Migrate-classification-write-paths-to-API-routes.md diff --git a/.knowns/tasks/task-uzm1um - fix-frontend-game-route-slowness.md b/.knowns/archive/2026-04-20-project-management-reset/task-uzm1um - fix-frontend-game-route-slowness.md similarity index 100% rename from .knowns/tasks/task-uzm1um - fix-frontend-game-route-slowness.md rename to .knowns/archive/2026-04-20-project-management-reset/task-uzm1um - fix-frontend-game-route-slowness.md diff --git a/.knowns/tasks/task-v4sw7k - Remove-deprecated-auth-helper-packages-and-migrate-middlewarehooks.md b/.knowns/archive/2026-04-20-project-management-reset/task-v4sw7k - Remove-deprecated-auth-helper-packages-and-migrate-middlewarehooks.md similarity index 100% rename from .knowns/tasks/task-v4sw7k - Remove-deprecated-auth-helper-packages-and-migrate-middlewarehooks.md rename to .knowns/archive/2026-04-20-project-management-reset/task-v4sw7k - Remove-deprecated-auth-helper-packages-and-migrate-middlewarehooks.md diff --git a/.knowns/tasks/task-vh9j2c - Migrate-deploy-read-paths-to-authenticated-API-routes.md b/.knowns/archive/2026-04-20-project-management-reset/task-vh9j2c - Migrate-deploy-read-paths-to-authenticated-API-routes.md similarity index 100% rename from .knowns/tasks/task-vh9j2c - Migrate-deploy-read-paths-to-authenticated-API-routes.md rename to .knowns/archive/2026-04-20-project-management-reset/task-vh9j2c - Migrate-deploy-read-paths-to-authenticated-API-routes.md diff --git a/.knowns/tasks/task-w4h6rz - fix-setup-pages-full-height-background-coverage.md b/.knowns/archive/2026-04-20-project-management-reset/task-w4h6rz - fix-setup-pages-full-height-background-coverage.md similarity index 100% rename from .knowns/tasks/task-w4h6rz - fix-setup-pages-full-height-background-coverage.md rename to .knowns/archive/2026-04-20-project-management-reset/task-w4h6rz - fix-setup-pages-full-height-background-coverage.md diff --git a/.knowns/tasks/task-xbdv2s - Specsheet-minigame-compatibility-matrix-for-migration.md b/.knowns/archive/2026-04-20-project-management-reset/task-xbdv2s - Specsheet-minigame-compatibility-matrix-for-migration.md similarity index 100% rename from .knowns/tasks/task-xbdv2s - Specsheet-minigame-compatibility-matrix-for-migration.md rename to .knowns/archive/2026-04-20-project-management-reset/task-xbdv2s - Specsheet-minigame-compatibility-matrix-for-migration.md diff --git a/.knowns/archive/2026-04-20-project-management-reset/task-xdbtg8 - Write-the-about-mission-page-copy.md b/.knowns/archive/2026-04-20-project-management-reset/task-xdbtg8 - Write-the-about-mission-page-copy.md new file mode 100644 index 000000000..ef98939d4 --- /dev/null +++ b/.knowns/archive/2026-04-20-project-management-reset/task-xdbtg8 - Write-the-about-mission-page-copy.md @@ -0,0 +1,128 @@ +--- +id: xdbtg8 +title: Write the about / mission page copy +status: done +priority: medium +labels: + - writing +createdAt: '2026-03-26T02:31:51.301Z' +updatedAt: '2026-03-26T09:18:06.203Z' +timeSpent: 0 +--- +# Write the about / mission page copy + +## Description + + +The about page explains why Star Sailors exists. Write the copy — mission statement, how it works, and why citizen science gaming matters. + + +## Acceptance Criteria + +- [ ] #1 Mission statement written +- [ ] #2 Full page copy drafted + + +## About / Mission Page — Full Copy Draft + +--- + +### Mission statement (one sentence, for og:description, meta, and page header) + +> **Star Sailors puts real NASA telescope data in the hands of anyone willing to play.** + +--- + +### Page copy + +#### Section 1 — What we are + +`MICRO LABEL: 01 / MISSION` + +**Science needs more hands than scientists have.** + +There are more telescope observations, orbital images, and solar readings than any team of +researchers can process alone. That's been true since the 1990s. It's more true today. + +Star Sailors is built on a simple bet: if we make the analysis feel like a game, more people will +do it — and the science will move faster. + +--- + +#### Section 2 — How it works + +`MICRO LABEL: 02 / HOW IT WORKS` + +**You run a space station.** + +When you join Star Sailors, you're given a station. You deploy structures — a telescope, a +satellite, a rover, a solar observatory — and those structures pull in real data from NASA's TESS +satellite, ESA orbital missions, and SDO solar imagery. + +Your job is to classify what you find. Smooth galaxy or spiral? Sunspot or artifact? Cloud pattern +or geological feature? + +Each classification goes into a database that real astronomers use. Your name can end up in a +research paper. That's not a metaphor. + +--- + +#### Section 3 — The three experiences + +`MICRO LABEL: 03 / PLAY` + +**Pick your mission.** + +- **Star Sailors** — The full game loop. Deploy four structures, run missions across multiple + science domains, earn Stardust, climb the leaderboard. Strategy + science. + +- **Experiment 1** — Rocket missions. Fly to real TESS targets, scan them with live light-curve + data. Focused, fast, game-first. + +- **Saily** — One anomaly per day. Annotate it. Submit. Come back tomorrow. For the streak + chasers. + +--- + +#### Section 4 — Why it matters + +`MICRO LABEL: 04 / WHY` + +**Citizen scientists have already found things astronomers missed.** + +The original Galaxy Zoo project had volunteers discover a class of galaxy — the Voorwerp — that +wasn't in any catalogue. Planet hunters using TESS data have flagged exoplanet candidates that +automated pipelines skipped. + +The pattern is consistent: humans notice things algorithms don't. Pattern recognition, intuition, +the feeling that something is slightly off — these are not bugs. They're the point. + +We built Star Sailors to give that capability to as many people as possible, wrapped in something +that doesn't feel like homework. + +--- + +#### Section 5 — CTA + +`MICRO LABEL: 05 / START` + +**Your station is ready.** + +[Launch Star Sailors →] `/auth` + +--- + +### Page structure notes (for developer) + +- Numbered section labels: `uppercase text-[10px] tracking-widest text-muted-foreground/60` +- Section headings: `text-2xl` Syne Mono +- Body: `text-base text-muted-foreground` max-width ~65ch for readability +- Background: `star-field` at low opacity + dark bg (same as onboarding) +- No images required — the copy works without illustration + +## Implementation Notes + + +kanban: vgwltf + + diff --git a/.knowns/tasks/task-y3n4kw - 22-native-macos-widget-for-survey-pulse.md b/.knowns/archive/2026-04-20-project-management-reset/task-y3n4kw - 22-native-macos-widget-for-survey-pulse.md similarity index 100% rename from .knowns/tasks/task-y3n4kw - 22-native-macos-widget-for-survey-pulse.md rename to .knowns/archive/2026-04-20-project-management-reset/task-y3n4kw - 22-native-macos-widget-for-survey-pulse.md diff --git a/.knowns/tasks/task-y8l2mb - Migrate-layout-header-nav-auth-helper-usage-to-ssr-browser-auth-utils.md b/.knowns/archive/2026-04-20-project-management-reset/task-y8l2mb - Migrate-layout-header-nav-auth-helper-usage-to-ssr-browser-auth-utils.md similarity index 100% rename from .knowns/tasks/task-y8l2mb - Migrate-layout-header-nav-auth-helper-usage-to-ssr-browser-auth-utils.md rename to .knowns/archive/2026-04-20-project-management-reset/task-y8l2mb - Migrate-layout-header-nav-auth-helper-usage-to-ssr-browser-auth-utils.md diff --git a/.knowns/tasks/task-zihdoc - add-posthog-session-replay.md b/.knowns/archive/2026-04-20-project-management-reset/task-zihdoc - add-posthog-session-replay.md similarity index 100% rename from .knowns/tasks/task-zihdoc - add-posthog-session-replay.md rename to .knowns/archive/2026-04-20-project-management-reset/task-zihdoc - add-posthog-session-replay.md diff --git a/.knowns/docs/3-0-design-token-spec.md b/.knowns/docs/3-0-design-token-spec.md new file mode 100644 index 000000000..23d3ee766 --- /dev/null +++ b/.knowns/docs/3-0-design-token-spec.md @@ -0,0 +1,158 @@ +--- +title: 3.0 Design Token Spec +createdAt: '2026-04-13T03:21:30.956Z' +updatedAt: '2026-04-13T03:21:30.956Z' +description: >- + Typography and colour tokens for the 3.0 redesign — fonts, weights, hex + palette, spacing scale, dark/light variants. Derived from globals.css and + tailwind.config.ts. Closes aaigmw. +tags: + - design + - tokens + - '3.0' +--- + +# 3.0 Design Token Spec + +Authoritative token reference. All values are live in the codebase — this doc +locks decisions and gives a single source of truth. Closes ticket `aaigmw`. + +--- + +## Typography + +### Typeface + +**One font only: Syne Mono** (Google Fonts) + +``` +Weight: 400 — the only weight in this family +CSS variable: --font-syne-mono +next/font/google import: Syne_Mono +``` + +Applied globally in `tailwind.config.ts` — display, sans, and mono all resolve +to Syne Mono. **All text on the platform is monospaced.** This is intentional. + +### Type scale + +| Role | Tailwind | Size | +|------|----------|------| +| Hero display | `text-4xl` – `text-5xl` | 36–48px | +| Section heading | `text-2xl` – `text-3xl` | 24–30px | +| Card heading | `text-lg` – `text-xl` | 18–20px | +| Body | `text-sm` – `text-base` | 14–16px | +| HUD / label | `text-xs` | 12px | +| Micro label | `text-[10px]` / `text-[11px]` | 10–11px | + +### Conventions + +- `uppercase tracking-widest` — nav items, section labels, HUD strip labels +- `tabular-nums` — all numeric readouts (HUD, leaderboard, odometers) +- `font-bold` + colour — used to create emphasis (no separate bold typeface weight) +- Hierarchy = size + colour + spacing, not weight variants + +--- + +## Colour Palette + +### Primary (same in both modes) + +| Token | Hex | Usage | +|-------|-----|-------| +| Primary | `#00A1E4` | CTAs, links, active states, primary glow | +| Primary RGB | `0, 161, 228` | Opacity/glow variants via `rgba()` | + +### Dark mode backgrounds + +| Token | Hex | Usage | +|-------|-----|-------| +| Background | `#181A1F` | Page | +| Foreground | `#D8DEE9` | Body text | +| Muted foreground | `#A3B1C8` | Labels, hints | +| Card fill | `rgba(0,0,0,0.25)` | Panels | +| Border | `rgba(0,161,228,0.15)` | Panel edges | + +### Light mode backgrounds + +| Token | Hex | Usage | +|-------|-----|-------| +| Background | `#ECEFF4` | Page | +| Foreground | `#2E3440` | Body text | +| Muted foreground | `#4C566A` | Labels, hints | + +### Secondary / accent + +| Name | Hex | Role | +|------|-----|------| +| Navy | `#012F6B` | Secondary dark fills, depth layers | +| Teal | `#5FCBC3` | Accent colour (CSS `--accent`) | + +### Structure accent colours + +| Structure | Hex | Tailwind | +|-----------|-----|----------| +| Telescope | `#2DD4BF` | `teal-400` | +| Satellite | `#38BDF8` | `sky-400` | +| Rover | `#FCD34D` | `amber-400` | +| Solar | `#FB923C` | `orange-400` | + +Used for: card borders, nav button glows, HUD icons, status LEDs. Do not mix +structure colours across structures. + +### Glow shadows + +``` +glow-teal: 0 0 12px rgba(45,212,191,0.3), 0 0 24px rgba(45,212,191,0.1) +glow-amber: 0 0 12px rgba(251,191,36,0.3), 0 0 24px rgba(251,191,36,0.1) +glow-sky: 0 0 12px rgba(56,189,248,0.3), 0 0 24px rgba(56,189,248,0.1) +``` + +For icon glow: `filter: drop-shadow(0 0 3px )` at ~0.7 opacity. + +--- + +## Spacing + +Base: 4px. No custom scale — standard Tailwind. + +| Context | Value | +|---------|-------| +| Mobile page padding | `px-4` – `px-6` | +| Desktop page padding | `px-8` – `px-12` | +| Card inner padding | `p-4` – `p-6` | +| Card gap | `gap-3` – `gap-4` | +| Command header height | `h-14` (56px) | +| HUD strip vertical | `py-1.5` (6px) | +| Mobile bottom nav clearance | `pb-[80px]` | + +--- + +## Background utilities + +| Class | Description | +|-------|-------------| +| `star-field` | Two-layer radial dot pattern (28px + 56px), low-opacity white | +| `sunburst-hero` | Radial primary glow from bottom edge — hero sections | +| `sunburst-lg` | Softer glow ellipse — card/section bg layer | +| `scan-line` | CRT scanline repeat at 4px intervals | +| `sci-fi-grid` | Faint primary-tinted grid | +| `sci-fi-panel` | Glass card: backdrop-blur + panel-bg + panel-border | + +--- + +## Dark/light mode QA + +- `#00A1E4` on dark `#181A1F`: ~4.6:1 — passes AA for UI elements +- `#00A1E4` on light `#ECEFF4`: ~3.2:1 — UI elements only, not body text +- `amber-300` (#FCD34D) on light bg: decorative only, not text +- `star-field` opacity: use ≤0.3 on light mode + +--- + +## Undecided / out of scope for now + +- Illustration style (none — product uses real astronomy imagery) +- Icon set additions beyond Lucide +- Motion easing tokens (Framer Motion defaults + Tailwind keyframes) + diff --git a/.knowns/docs/incomplete-tickets-reference-2026-04-13.md b/.knowns/docs/incomplete-tickets-reference-2026-04-13.md new file mode 100644 index 000000000..a47eb1720 --- /dev/null +++ b/.knowns/docs/incomplete-tickets-reference-2026-04-13.md @@ -0,0 +1,110 @@ +--- +title: Incomplete Tickets Reference — 2026-04-13 +createdAt: '2026-04-13T03:05:09.307Z' +updatedAt: '2026-04-13T03:05:09.307Z' +description: >- + Snapshot of all open/todo/in-progress tickets as of 2026-04-13. Revisit before + next sprint planning. +tags: + - archive + - tickets + - sprint-planning +revisitBy: '2026-04-20' +--- + +# Incomplete Tickets — Snapshot 2026-04-13 + +All tickets that were **not completed** as of 2026-04-13. This document exists so they are not lost +between sprints. Revisit this before the next sprint planning session. + +--- + +## In-Progress + +### `task-30-p1-design-tokens` · HIGH +**3.0 Phase 1: Design Tokens, Fonts & Theme** + +One item remains: manual QA pass to verify dark/light mode tokens look correct visually. +All implementation tasks are checked off — this is a sign-off gate only. + +**Blocked by:** Liam doing a visual review pass. + +--- + +## Engineering — Onboarding Bugs (all HIGH or MEDIUM) + +These were created 2026-04-07 as a batch. They are engineering-ready with full context. + +### `task-ob1-galaxyzoo-branch-bug` · HIGH +**GalaxyZoo: both `showTutorial` branches render `` — form never shown** +- File: `src/components/projects/Telescopes/GalaxyZoo.tsx` lines 247–251 +- Fix: split the conditional so `showTutorial=false` renders the standalone classification form +- Strategy: extract classification form or compose from shared component + +### `task-ob2-missing-tutorial-selectors` · HIGH +**`data-tutorial` attributes referenced in TutorialWrapper don't exist in the DOM** +- File: `TutorialWrapper.tsx` — all `highlightSelector` values point to elements that have no matching attribute +- Fix: add `data-tutorial="…"` attributes to real rendered elements in TelescopeSection, DeploySidebar, RoverSection, SolarHealth, satellite nav +- Prerequisite for: ob3, ob6 + +### `task-ob3-tutorial-never-instantiated` · HIGH +**TutorialWrapper and all step sets are defined but never mounted anywhere in the app** +- The full tutorial system (TutorialWrapper, TELESCOPE_INTRO_STEPS, ROVER_INTRO_STEPS, etc.) is built but dead +- Fix: export the unexported step sets, wrap telescope/rover/satellite/solar setup pages with `` +- Depends on: ob2 + +### ~~`task-ob4-missing-onboarding-projects`~~ · CLOSED 2026-04-13 +**Already shipped** — `onboarding-data.ts` already contains all 5 projects including +asteroid-hunting and solar-monitoring. Ticket marked done. + +### `task-ob5-no-reentry-path` · MEDIUM +**Mid-flow onboarding bounce resets to "Station Arrival" (no step persistence)** +- File: `src/app/onboarding/page.tsx` +- Fix: persist `ss_onboarding_step` and `ss_onboarding_project` to localStorage; restore on mount if `!hasCompletedOnboarding` + +### `task-ob6-setup-pages-no-tutorial` · MEDIUM +**Setup pages (/setup/telescope, /setup/rover, /setup/satellite) have no tutorial overlay** +- Depends on: ob2 (selectors), ob3 (exports) +- Fix: wrap each setup page content with the appropriate `` + +### `task-ob7-alpha-watermark` · LOW +**Hardcoded `v3.0.0-alpha` string renders in onboarding footer** +- File: `src/app/onboarding/page.tsx` line 150 +- Fix: delete the `` entirely — one-liner + +--- + +## Design / Copy (all TODO, not engineering-blocked) + +### ~~`9ztpbl`~~ · CLOSED 2026-04-13 +**Already implemented** — `HUDStrip.tsx` shows Signals / Pending / Classifications in a top +strip. Decision was made in code. Ticket marked done. + +### `aaigmw` · HIGH · assigned @Liam +**Document typography and colour tokens for the 3.0 redesign** +- Fonts at which weights, hex colour palette (teal/amber/sky + neutrals), spacing scale, dark/light variants +- Needs to be a short markdown spec so developers can build from it + +### `7kznko` · MEDIUM +**Sketch the 3.0 homepage hero section** +- Headline, visual treatment, primary CTA +- Input for: landing page pass + +### `uayboz` · MEDIUM +**Sketch the 3.0 navigation and top-level IA** +- What's in the nav; logged-in vs logged-out states + +### `u6zo6x` · MEDIUM +**Write homepage headline and subhead variants (3–5 options)** +- Citizen science gaming angle + +### `xdbtg8` · MEDIUM +**Write the about / mission page copy** +- Mission statement, how it works, why citizen science gaming matters + +--- + +## Notes + +- 7 corrupted task files were skipped by `knowns task list` — run `knowns task validate ` on them +- `knowns` is on v0.11.4; v0.18.3 is available — `brew upgrade knowns` diff --git a/.knowns/tasks/task-7kznko - Sketch-the-30-homepage-hero-section.md b/.knowns/tasks/task-7kznko - Sketch-the-30-homepage-hero-section.md deleted file mode 100644 index dcf1745d1..000000000 --- a/.knowns/tasks/task-7kznko - Sketch-the-30-homepage-hero-section.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: 7kznko -title: Sketch the 3.0 homepage hero section -status: todo -priority: medium -labels: - - sketch - - design -createdAt: '2026-03-26T02:31:50.403Z' -updatedAt: '2026-03-26T09:18:05.246Z' -timeSpent: 0 ---- -# Sketch the 3.0 homepage hero section - -## Description - - -The 3.0 redesign needs a hero section sketch. What's the headline, visual treatment, and primary CTA? How does it communicate the platform's purpose? - - -## Acceptance Criteria - -- [ ] #1 Hero section sketch complete -- [ ] #2 Headline direction and CTA identified - - -## Implementation Notes - - -kanban: f7k41f - - diff --git a/.knowns/tasks/task-oquxw4 - inspiration-3d-rooms.md b/.knowns/tasks/task-oquxw4 - inspiration-3d-rooms.md new file mode 100644 index 000000000..cfc9c72d9 --- /dev/null +++ b/.knowns/tasks/task-oquxw4 - inspiration-3d-rooms.md @@ -0,0 +1,19 @@ +--- +id: oquxw4 +title: "Inspiration - 3d rooms" +status: todo +priority: low +labels: + - ideas +createdAt: '2026-04-24T11:44:21Z' +updatedAt: '2026-04-24T11:44:21Z' +timeSpent: 0 +--- + +# Inspiration - 3d rooms + +## Description + + +https://www.producthunt.com/p/floors-js/3d-room-builder-avatar-customisation - what about something like this for the construction, rocket ship construction/rooms, etc + diff --git a/.knowns/tasks/task-ssw001 - define-and-lock-star-sailors-3-0-mvp-route-map.md b/.knowns/tasks/task-ssw001 - define-and-lock-star-sailors-3-0-mvp-route-map.md new file mode 100644 index 000000000..a9fe245a7 --- /dev/null +++ b/.knowns/tasks/task-ssw001 - define-and-lock-star-sailors-3-0-mvp-route-map.md @@ -0,0 +1,47 @@ +--- +id: ssw001 +title: "Define and lock Star Sailors 3.0 MVP route map" +status: todo +priority: high +labels: + - mvp + - planning + - web-client +createdAt: '2026-04-20T00:00:00+10:00' +updatedAt: '2026-04-20T00:00:00+10:00' +timeSpent: 0 +assignee: '@me' +--- + +# Define and lock Star Sailors 3.0 MVP route map + +## Description + + +This is part of the 2026-04-20 Star Sailors MVP/project-management reset. Project: Star Sailors Web Client. Work must stay tied to MVP closure, distribution, testing, or agent handoff for this week. + + +## Implementation Plan + + +1. Audit the current landing, game hub, onboarding, setup, and first classification routes. +2. Write the locked first-session path and identify every screen that belongs in MVP. +3. Move non-MVP route work into backlog/reference notes. + + +## Acceptance Criteria + +- [ ] Golden path is documented from landing to first science contribution. +- [ ] Current blocker list is limited to tester-visible failures. +- [ ] First external tester script exists or is updated. + +## Source Context + +- Obsidian command center: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/Star-Sailors-MVP-Command-Center.md +- Weekly plan: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/This-Week-MVP-Execution-Plan-2026-04-20.md + +## Implementation Notes + + +Created by project-management reset on 2026-04-20. + diff --git a/.knowns/tasks/task-ssw002 - stabilize-first-session-onboarding-and-science-contribution.md b/.knowns/tasks/task-ssw002 - stabilize-first-session-onboarding-and-science-contribution.md new file mode 100644 index 000000000..d34897a4b --- /dev/null +++ b/.knowns/tasks/task-ssw002 - stabilize-first-session-onboarding-and-science-contribution.md @@ -0,0 +1,46 @@ +--- +id: ssw002 +title: "Stabilize first-session onboarding and science contribution" +status: todo +priority: high +labels: + - mvp + - onboarding + - classification +createdAt: '2026-04-20T00:00:00+10:00' +updatedAt: '2026-04-20T00:00:00+10:00' +timeSpent: 0 +assignee: '@me' +--- + +# Stabilize first-session onboarding and science contribution + +## Description + + +This is part of the 2026-04-20 Star Sailors MVP/project-management reset. Project: Star Sailors Web Client. Work must stay tied to MVP closure, distribution, testing, or agent handoff for this week. + + +## Implementation Plan + + +1. Make the first user path launch into the web client, not a vague ecosystem browse. +2. Verify project choice, structure setup, deploy/classify flow, reward/progress update, and feedback prompt. + + +## Acceptance Criteria + +- [ ] A new user can complete one science contribution without manual DB edits. +- [ ] The path works for the chosen MVP project type. +- [ ] Failures are captured as concrete follow-up notes. + +## Source Context + +- Obsidian command center: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/Star-Sailors-MVP-Command-Center.md +- Weekly plan: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/This-Week-MVP-Execution-Plan-2026-04-20.md + +## Implementation Notes + + +Created by project-management reset on 2026-04-20. + diff --git a/.knowns/tasks/task-ssw003 - run-mobile-pwa-and-safe-area-release-pass.md b/.knowns/tasks/task-ssw003 - run-mobile-pwa-and-safe-area-release-pass.md new file mode 100644 index 000000000..620c9b8d7 --- /dev/null +++ b/.knowns/tasks/task-ssw003 - run-mobile-pwa-and-safe-area-release-pass.md @@ -0,0 +1,47 @@ +--- +id: ssw003 +title: "Run mobile PWA and safe-area release pass" +status: todo +priority: high +labels: + - mvp + - mobile + - pwa + - ux +createdAt: '2026-04-20T00:00:00+10:00' +updatedAt: '2026-04-20T00:00:00+10:00' +timeSpent: 0 +assignee: '@me' +--- + +# Run mobile PWA and safe-area release pass + +## Description + + +This is part of the 2026-04-20 Star Sailors MVP/project-management reset. Project: Star Sailors Web Client. Work must stay tied to MVP closure, distribution, testing, or agent handoff for this week. + + +## Implementation Plan + + +1. Check /apt, /game, and active structure viewports on narrow mobile sizes. +2. Fix or document PWA status bar, safe-area, bottom nav, modal, and pinch-zoom issues on the MVP path. + + +## Acceptance Criteria + +- [ ] Landing page remains zoomable/accessibility-safe where appropriate. +- [ ] Game route critical UI is not clipped by phone safe areas. +- [ ] Known unresolved layout defects are written into the tester notes. + +## Source Context + +- Obsidian command center: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/Star-Sailors-MVP-Command-Center.md +- Weekly plan: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/This-Week-MVP-Execution-Plan-2026-04-20.md + +## Implementation Notes + + +Created by project-management reset on 2026-04-20. + diff --git a/.knowns/tasks/task-ssw004 - prepare-web-client-distribution-and-feedback-cohort.md b/.knowns/tasks/task-ssw004 - prepare-web-client-distribution-and-feedback-cohort.md new file mode 100644 index 000000000..92789b7dd --- /dev/null +++ b/.knowns/tasks/task-ssw004 - prepare-web-client-distribution-and-feedback-cohort.md @@ -0,0 +1,46 @@ +--- +id: ssw004 +title: "Prepare web-client distribution and feedback cohort" +status: todo +priority: high +labels: + - release + - testing + - analytics +createdAt: '2026-04-20T00:00:00+10:00' +updatedAt: '2026-04-20T00:00:00+10:00' +timeSpent: 0 +assignee: '@me' +--- + +# Prepare web-client distribution and feedback cohort + +## Description + + +This is part of the 2026-04-20 Star Sailors MVP/project-management reset. Project: Star Sailors Web Client. Work must stay tied to MVP closure, distribution, testing, or agent handoff for this week. + + +## Implementation Plan + + +1. Confirm production/staging URL, release command sequence, PostHog/Sentry coverage, and feedback capture. +2. Create the first tester script for web-client MVP validation. + + +## Acceptance Criteria + +- [ ] Tester can be sent one URL plus a short script. +- [ ] PostHog/Sentry or equivalent captures the critical events/errors. +- [ ] Rollback/disable path is documented. + +## Source Context + +- Obsidian command center: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/Star-Sailors-MVP-Command-Center.md +- Weekly plan: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/This-Week-MVP-Execution-Plan-2026-04-20.md + +## Implementation Notes + + +Created by project-management reset on 2026-04-20. + diff --git a/.knowns/tasks/task-ssw005 - specify-the-citizen-science-project-intake-surface-for-the-web-client.md b/.knowns/tasks/task-ssw005 - specify-the-citizen-science-project-intake-surface-for-the-web-client.md new file mode 100644 index 000000000..a2e8e6d6d --- /dev/null +++ b/.knowns/tasks/task-ssw005 - specify-the-citizen-science-project-intake-surface-for-the-web-client.md @@ -0,0 +1,45 @@ +--- +id: ssw005 +title: "Specify the citizen-science project intake surface for the web client" +status: todo +priority: medium +labels: + - project-intake + - citizen-science + - admin +createdAt: '2026-04-20T00:00:00+10:00' +updatedAt: '2026-04-20T00:00:00+10:00' +timeSpent: 0 +assignee: '@me' +--- + +# Specify the citizen-science project intake surface for the web client + +## Description + + +This is part of the 2026-04-20 Star Sailors MVP/project-management reset. Project: Star Sailors Web Client. Work must stay tied to MVP closure, distribution, testing, or agent handoff for this week. + + +## Implementation Plan + + +1. Turn the new intake template into a web-client integration spec. +2. Define the minimum fields needed to add a new project/game without rewriting the app. + + +## Acceptance Criteria + +- [ ] Spec names required data model fields, UI surface, and task generation path. +- [ ] Integration can be implemented later with Go/PocketBase or Prisma without changing MVP scope. + +## Source Context + +- Obsidian command center: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/Star-Sailors-MVP-Command-Center.md +- Weekly plan: /Users/scroobz/Navigation/quartz/content/Studio/Project-Management/This-Week-MVP-Execution-Plan-2026-04-20.md + +## Implementation Notes + + +Created by project-management reset on 2026-04-20. + diff --git a/.knowns/tasks/task-u6zo6x - Write-homepage-headline-and-subhead-variants.md b/.knowns/tasks/task-u6zo6x - Write-homepage-headline-and-subhead-variants.md deleted file mode 100644 index 044a3a32e..000000000 --- a/.knowns/tasks/task-u6zo6x - Write-homepage-headline-and-subhead-variants.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -id: u6zo6x -title: Write homepage headline and subhead variants -status: todo -priority: medium -labels: - - writing -createdAt: '2026-03-26T02:31:50.705Z' -updatedAt: '2026-03-26T09:18:05.567Z' -timeSpent: 0 ---- -# Write homepage headline and subhead variants - -## Description - - -The 3.0 homepage needs headline + subhead options to test. Write 3–5 variants that communicate the citizen science gaming angle. - - -## Acceptance Criteria - -- [ ] #1 At least 3 headline/subhead variants written - - -## Implementation Notes - - -kanban: k0drei - - diff --git a/.knowns/tasks/task-uayboz - Sketch-the-30-navigation-and-top-level-IA.md b/.knowns/tasks/task-uayboz - Sketch-the-30-navigation-and-top-level-IA.md deleted file mode 100644 index 91c7c21fc..000000000 --- a/.knowns/tasks/task-uayboz - Sketch-the-30-navigation-and-top-level-IA.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: uayboz -title: Sketch the 3.0 navigation and top-level IA -status: todo -priority: medium -labels: - - sketch - - design -createdAt: '2026-03-26T02:31:51.002Z' -updatedAt: '2026-03-26T09:18:05.891Z' -timeSpent: 0 ---- -# Sketch the 3.0 navigation and top-level IA - -## Description - - -The 3.0 navigation structure needs a sketch. What's in the nav, how is it organised, and how does it handle logged-in vs logged-out states? - - -## Acceptance Criteria - -- [ ] #1 Navigation sketch complete -- [ ] #2 Logged-in and logged-out states covered - - -## Implementation Notes - - -kanban: s5j08d - - diff --git a/.knowns/tasks/task-xdbtg8 - Write-the-about-mission-page-copy.md b/.knowns/tasks/task-xdbtg8 - Write-the-about-mission-page-copy.md deleted file mode 100644 index 90f4b6975..000000000 --- a/.knowns/tasks/task-xdbtg8 - Write-the-about-mission-page-copy.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -id: xdbtg8 -title: Write the about / mission page copy -status: todo -priority: medium -labels: - - writing -createdAt: '2026-03-26T02:31:51.301Z' -updatedAt: '2026-03-26T09:18:06.203Z' -timeSpent: 0 ---- -# Write the about / mission page copy - -## Description - - -The about page explains why Star Sailors exists. Write the copy — mission statement, how it works, and why citizen science gaming matters. - - -## Acceptance Criteria - -- [ ] #1 Mission statement written -- [ ] #2 Full page copy drafted - - -## Implementation Notes - - -kanban: vgwltf - - diff --git a/public/fonts/SyneMono-Regular.woff2 b/public/fonts/SyneMono-Regular.woff2 new file mode 100644 index 000000000..4b56588b2 --- /dev/null +++ b/public/fonts/SyneMono-Regular.woff2 @@ -0,0 +1,11 @@ + + + + + Error 404 (Not Found)!!1 + + +

404. That’s an error. +

The requested URL /s/synemono/v15/K2FyfZZVWnDZQhc-MKq-ZC_TVCS_f.woff2 was not found on this server. That’s all we know. diff --git a/scripts/data/import-galaxy-zoo.ts b/scripts/data/import-galaxy-zoo.ts new file mode 100644 index 000000000..c1943a318 --- /dev/null +++ b/scripts/data/import-galaxy-zoo.ts @@ -0,0 +1,79 @@ +import { PrismaClient } from "@prisma/client"; +import axios from "axios"; + +const prisma = new PrismaClient(); + +// Galaxy Zoo project ID on Zooniverse is 12268 (or similar, we can use a specific subject set) +// For this example, we'll use a known open subject set or a general query +const GALAXY_ZOO_PROJECT_ID = "12268"; + +async function importGalaxyZooSubjects() { + console.log("🚀 Starting Galaxy Zoo subject import..."); + + try { + // 1. Fetch subjects from Zooniverse Panoptes API + // We'll get 20 subjects for the prototype + const response = await axios.get(`https://www.zooniverse.org/api/subjects`, { + params: { + project_id: GALAXY_ZOO_PROJECT_ID, + page_size: 20, + }, + headers: { + "Accept": "application/vnd.api+json; version=1", + "Content-Type": "application/json", + } + }); + + const subjects = response.data.subjects; + console.log(`📦 Found ${subjects.length} subjects from Zooniverse.`); + + for (const subject of subjects) { + const externalId = subject.id; + // Zooniverse subjects often have multiple locations (images), we take the first one + const avatarUrl = subject.locations[0]["image/jpeg"] || subject.locations[0]["image/png"]; + + if (!avatarUrl) continue; + + // 2. Check if already exists to avoid duplicates + const existing = await prisma.anomaly.findFirst({ + where: { + ticId: `zooniverse-${externalId}`, + anomalySet: "telescope-galaxyZoo" + } + }); + + if (existing) { + console.log(`⏩ Subject ${externalId} already exists, skipping.`); + continue; + } + + // 3. Create anomaly in our database + await prisma.anomaly.create({ + data: { + content: `Galaxy ${externalId}`, + ticId: `zooniverse-${externalId}`, + anomalySet: "telescope-galaxyZoo", + avatarUrl: avatarUrl, + anomalytype: "galaxy", + type: "Galaxy", + classificationStatus: "unclassified", + anomalyConfiguration: { + zooniverse_id: externalId, + metadata: subject.metadata, + source: "Zooniverse Galaxy Zoo" + } + } + }); + + console.log(`✅ Imported Galaxy ${externalId}`); + } + + console.log("✨ Import completed successfully!"); + } catch (error) { + console.error("❌ Error importing subjects:", error); + } finally { + await prisma.$disconnect(); + } +} + +importGalaxyZooSubjects(); diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts deleted file mode 100644 index 5aabed70a..000000000 --- a/src/app/api/auth/logout/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextResponse } from "next/server"; - -import { createSupabaseServerClient } from "@/lib/supabase/ssr"; - -export const dynamic = "force-dynamic"; - -export async function POST() { - const supabase = createSupabaseServerClient(); - const { error } = await supabase.auth.signOut(); - - if (error) { - return NextResponse.json({ error: error.message }, { status: 500 }); - } - - return NextResponse.json({ success: true }); -} diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts deleted file mode 100644 index 34728fad2..000000000 --- a/src/app/api/auth/me/route.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NextResponse } from "next/server"; - -import { getRouteSupabaseWithUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -export async function GET() { - const { user, authError } = await getRouteSupabaseWithUser(); - - if (authError || !user) { - return NextResponse.json({ authenticated: false, user: null }); - } - - return NextResponse.json({ - authenticated: true, - user: { - id: user.id, - email: user.email ?? null, - isAnonymous: Boolean((user as any).is_anonymous), - }, - }); -} diff --git a/src/app/api/balloon/upload-image/route.ts b/src/app/api/balloon/upload-image/route.ts deleted file mode 100644 index af79fdad6..000000000 --- a/src/app/api/balloon/upload-image/route.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { NextResponse, NextRequest } from "next/server"; -import axios from "axios"; - -export async function POST(req: NextRequest) { - try { - const formData = await req.formData(); - const file = formData.get("file") as Blob | null; - - if (!file) { - return NextResponse.json({ error: "No file uploaded" }, { status: 400 }); - } - - if (!process.env.OPENAI_API_KEY) { - return NextResponse.json({ error: "OpenAI API key is not configured" }, { status: 503 }); - } - - const arrayBuffer = await file.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); - const base64Image = buffer.toString("base64"); - - const openaiRes = await axios.post( - "https://api.openai.com/v1/chat/completions", - { - model: "gpt-4-turbo", - messages: [ - { - role: "system", - content: - "Return strict JSON with keys: species (string), confidence (number 0-1), traits (string[]), summary (string).", - }, - { - role: "user", - content: [ - { type: "text", text: "Classify the organism in this image." }, - { type: "image_url", image_url: { url: `data:image/jpeg;base64,${base64Image}` } }, - ], - }, - ], - max_tokens: 250, - }, - { - headers: { - Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, - "Content-Type": "application/json", - }, - } - ); - - const raw = String(openaiRes.data?.choices?.[0]?.message?.content || "").replace(/```json|```/g, "").trim(); - const parsed = raw ? JSON.parse(raw) : null; - if (!parsed || typeof parsed !== "object") { - return NextResponse.json({ error: "Model returned invalid classification payload" }, { status: 502 }); - } - - return NextResponse.json(parsed); - } catch (error: any) { - console.error("Error in balloon upload-image route:", error.response?.data || error.message); - return NextResponse.json({ error: "Failed to classify image" }, { status: 500 }); - } -} diff --git a/src/app/api/exofop/route.ts b/src/app/api/exofop/route.ts deleted file mode 100644 index 40e54ea8e..000000000 --- a/src/app/api/exofop/route.ts +++ /dev/null @@ -1,87 +0,0 @@ -import fs from 'fs/promises' -import path from 'path' -import { NextResponse } from 'next/server' - -const EXOFOP_FILE_PATH = path.join(process.cwd(), 'citizen', 'archive', 'data', 'exofop.csv') - -let cache: Record | null = null -let cacheMtimeMs = 0 - -function parseCSVLine(line: string) { - const parts: string[] = [] - let cur = '' - let inQuotes = false - for (let i = 0; i < line.length; i++) { - const ch = line[i] - if (ch === '"') { - inQuotes = !inQuotes - continue - } - if (ch === ',' && !inQuotes) { - parts.push(cur) - cur = '' - } else { - cur += ch - } - } - parts.push(cur) - return parts -} - -export async function GET(req: Request) { - try { - const url = new URL(req.url) - const ticQuery = url.searchParams.get('tic') - - let map = cache - const stat = await fs.stat(EXOFOP_FILE_PATH).catch(() => null) - if (!stat) { - if (ticQuery) { - return NextResponse.json({ tic: ticQuery, result: null, available: false }) - } - return NextResponse.json({ count: 0, available: false }) - } - - if (!map || stat.mtimeMs !== cacheMtimeMs) { - const raw = await fs.readFile(EXOFOP_FILE_PATH, 'utf8') - const lines = raw.split(/\r?\n/).filter((l) => l && l.trim().length > 0) - - const headerIndex = lines.findIndex((l) => l.toUpperCase().includes('"TICID"')) - if (headerIndex === -1) { - return NextResponse.json({ error: 'ExoFOP header not found' }, { status: 500 }) - } - - const headerCols = parseCSVLine(lines[headerIndex]).map((c) => c.trim()) - const ticIdx = headerCols.findIndex((c) => c.toUpperCase().includes('TICID')) - const metalIdx = headerCols.findIndex((c) => c.toUpperCase().includes('STELLAR METALLICITY')) - - if (ticIdx === -1) { - return NextResponse.json({ error: 'TICID column not found' }, { status: 500 }) - } - - map = {} - for (let i = headerIndex + 1; i < lines.length; i++) { - const line = lines[i] - if (!line) continue - const parts = parseCSVLine(line) - const tic = parts[ticIdx] - if (!tic) continue - const metal = metalIdx !== -1 ? (parts[metalIdx] || '') : '' - map[tic.replace(/\"/g, '').trim()] = { metallicity: metal ? metal.trim() : null } - } - - cache = map - cacheMtimeMs = stat.mtimeMs - } - - if (ticQuery) { - const found = map[String(ticQuery)] || null - return NextResponse.json({ tic: ticQuery, result: found, available: true }) - } - - // otherwise return a small summary - return NextResponse.json({ count: Object.keys(map).length, available: true }) - } catch (err: any) { - return NextResponse.json({ error: String(err?.message || err) }, { status: 500 }) - } -} diff --git a/src/app/api/gameplay/achievements/route.ts b/src/app/api/gameplay/achievements/route.ts index 2d5c9b416..58d9b2f19 100644 --- a/src/app/api/gameplay/achievements/route.ts +++ b/src/app/api/gameplay/achievements/route.ts @@ -15,6 +15,7 @@ const VALID_TYPES = new Set([ "planet-inspection", "sunspot", "satellite-planetFour", + "galaxyZoo", ]); function createEmptyAchievements() { diff --git a/src/app/api/gameplay/active-planet/route.ts b/src/app/api/gameplay/active-planet/route.ts index 8497446dd..fc3a10ff1 100644 --- a/src/app/api/gameplay/active-planet/route.ts +++ b/src/app/api/gameplay/active-planet/route.ts @@ -9,10 +9,10 @@ async function resolveLocation( userId: string, requestedLocation?: number ) { - if (Number.isFinite(requestedLocation)) { + if (requestedLocation !== undefined && Number.isFinite(requestedLocation)) { const { error: updateError } = await supabase .from("profiles") - .update({ location: requestedLocation }) + .update({ location: BigInt(requestedLocation) }) .eq("id", userId); if (updateError) { @@ -32,7 +32,7 @@ async function resolveLocation( throw new Error(profileError.message); } - const location = profile?.location ?? 30; + const location = profile?.location ?? BigInt(30); if (profile?.location == null) { const { error: updateError } = await supabase .from("profiles") @@ -58,7 +58,7 @@ async function fetchActivePlanetPayload(userId: string, requestedLocation?: numb const { data: planet, error: planetError } = await supabase .from("anomalies") .select("*") - .eq("id", location) + .eq("id", BigInt(location)) .maybeSingle(); if (planetError) { @@ -69,7 +69,7 @@ async function fetchActivePlanetPayload(userId: string, requestedLocation?: numb .from("classifications") .select("*") .eq("author", userId) - .eq("anomaly", location) + .eq("anomaly", BigInt(location)) .eq("classificationtype", "lightcurve"); if (classificationsError) { @@ -77,9 +77,13 @@ async function fetchActivePlanetPayload(userId: string, requestedLocation?: numb } return NextResponse.json({ - location, - planet: planet ?? null, - classifications: classifications ?? [], + location: location.toString(), + planet: planet ? { ...planet, id: planet.id.toString() } : null, + classifications: (classifications ?? []).map((c: any) => ({ + ...c, + id: c.id.toString(), + anomaly: c.anomaly?.toString(), + })), }); } catch (error: any) { return NextResponse.json( diff --git a/src/app/api/gameplay/alerts/route.ts b/src/app/api/gameplay/alerts/route.ts deleted file mode 100644 index e8a65c147..000000000 --- a/src/app/api/gameplay/alerts/route.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { NextResponse } from "next/server"; -import { subDays } from "date-fns"; - -import { getRouteSupabaseWithUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -type AlertItem = { - id: string; - type: "anomaly" | "event" | "completion"; - message: string; - anomalyId?: number; - eventId?: number; - classificationId?: number; - anomaly?: { - anomalytype?: string; - }; -}; - -function normalizeRelation(value: T | T[] | null | undefined): T | null { - if (!value) return null; - if (Array.isArray(value)) return (value[0] as T) ?? null; - return value; -} - -export async function GET() { - const { supabase, user, authError } = await getRouteSupabaseWithUser(); - if (authError || !user) { - return NextResponse.json({ alerts: [] }); - } - - const weekAgo = subDays(new Date(), 7).toISOString(); - const alerts: AlertItem[] = []; - - const { data: linkedAnomalies, error: linkedError } = await supabase - .from("linked_anomalies") - .select( - ` - id, - date, - anomaly:anomalies( - id, - anomalytype, - content, - classification_status - ) - ` - ) - .eq("author", user.id) - .gte("date", weekAgo) - .order("date", { ascending: false }); - - if (linkedError) { - return NextResponse.json({ error: linkedError.message }, { status: 500 }); - } - - const linkedIds = (linkedAnomalies || []) - .map((la: any) => normalizeRelation<{ id: number }>(la.anomaly)?.id) - .filter(Boolean); - - let classifiedAnomalyIds = new Set(); - if (linkedIds.length > 0) { - const { data: existingClassifications } = await supabase - .from("classifications") - .select("anomaly") - .eq("author", user.id) - .in("anomaly", linkedIds); - - classifiedAnomalyIds = new Set((existingClassifications || []).map((c: any) => c.anomaly)); - } - - const unclassifiedAnomalies = (linkedAnomalies || []).filter((la: any) => { - const anomaly = normalizeRelation<{ id: number }>(la.anomaly); - return anomaly?.id && !classifiedAnomalyIds.has(anomaly.id); - }); - - for (const linkedAnomaly of unclassifiedAnomalies) { - const anomaly = normalizeRelation<{ id: number; anomalytype?: string }>(linkedAnomaly.anomaly); - if (!anomaly?.id) continue; - const anomalyType = anomaly.anomalytype || "unknown object"; - alerts.push({ - id: `anomaly-${linkedAnomaly.id}`, - type: "anomaly", - message: "New anomaly discovered, classify it for bonus stardust", - anomalyId: anomaly.id, - anomaly: { anomalytype: anomalyType }, - }); - } - - const { data: upcomingEvents } = await supabase - .from("events") - .select( - ` - id, - type, - time, - completed, - location:anomalies(id, content), - classification_location:classifications(id, author) - ` - ) - .eq("completed", false) - .gte("time", new Date().toISOString()) - .order("time", { ascending: true }) - .limit(5); - - (upcomingEvents || []).forEach((event: any) => { - if (event.classification_location?.author !== user.id) { - return; - } - alerts.push({ - id: `event-${event.id}`, - type: "event", - message: `Upcoming ${event.type} event on planet ${event.location?.content || "Unknown"}`, - eventId: event.id, - classificationId: event.classification_location?.id, - }); - }); - - if (alerts.length === 0) { - alerts.push({ - id: "completion", - type: "completion", - message: "No primary objects left to classify. Great work!", - }); - } - - return NextResponse.json({ alerts }); -} diff --git a/src/app/api/gameplay/classifications/[id]/route.ts b/src/app/api/gameplay/classifications/[id]/route.ts deleted file mode 100644 index 903297f51..000000000 --- a/src/app/api/gameplay/classifications/[id]/route.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -import { prisma } from "@/lib/server/prisma"; - -export const dynamic = "force-dynamic"; - -export async function GET(_request: NextRequest, props: { params: Promise<{ id: string }> }) { - const params = await props.params; - const id = Number(params.id); - if (!Number.isFinite(id)) { - return NextResponse.json({ error: "Invalid classification ID" }, { status: 400 }); - } - - const rows = await prisma.$queryRaw>>` - SELECT * - FROM classifications - WHERE id = ${id} - LIMIT 1 - `; - const classification = rows[0]; - if (!classification) { - return NextResponse.json({ error: "Classification not found" }, { status: 404 }); - } - - const sourceId = Number( - (classification.classificationConfiguration as Record | undefined)?.source_classification_id - ); - let sourceMedia: unknown[] = []; - if (Number.isFinite(sourceId)) { - const sourceRows = await prisma.$queryRaw>` - SELECT media - FROM classifications - WHERE id = ${sourceId} - LIMIT 1 - `; - sourceMedia = sourceRows[0]?.media || []; - } - - return NextResponse.json({ classification, sourceMedia }); -} diff --git a/src/app/api/gameplay/deploy/telescope/route.ts b/src/app/api/gameplay/deploy/telescope/route.ts deleted file mode 100644 index 585bb4cb6..000000000 --- a/src/app/api/gameplay/deploy/telescope/route.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { revalidatePath } from "next/cache"; - -import { prisma } from "@/lib/server/prisma"; -import { getRouteUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -type DeploymentType = "stellar" | "planetary"; - -type DeployBody = { - deploymentType?: DeploymentType; - anomalyIds?: number[]; -}; - -function computeSetsToFetch( - deploymentType: DeploymentType, - options: { includeActiveAsteroids: boolean; includeNgts: boolean } -) { - if (deploymentType === "stellar") { - return ["diskDetective", "superwasp-variable", "telescope-superwasp-variable"]; - } - - const sets = ["telescope-tess", "telescope-minorPlanet"]; - if (options.includeActiveAsteroids) { - sets.push("active-asteroids"); - } - if (options.includeNgts) { - sets.push("telescope-ngts"); - } - return sets; -} - -export async function GET(request: NextRequest) { - try { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const action = request.nextUrl.searchParams.get("action"); - - if (action === "anomalies") { - const deploymentType = request.nextUrl.searchParams.get("deploymentType") as DeploymentType | null; - if (deploymentType !== "stellar" && deploymentType !== "planetary") { - return NextResponse.json({ error: "Invalid deploymentType" }, { status: 400 }); - } - - let includeActiveAsteroids = false; - let includeNgts = false; - - if (deploymentType === "planetary") { - const [minorPlanetRows, ngtsData] = await Promise.all([ - prisma.$queryRaw>` - SELECT COUNT(*)::bigint AS count - FROM classifications - WHERE author::text = ${user.id} - AND classificationtype = 'telescope-minorPlanet' - `, - prisma.$queryRaw>` - SELECT tech_type - FROM researched - WHERE user_id::text = ${user.id} - AND tech_type = 'ngtsAccess' - LIMIT 1 - `, - ]); - - includeActiveAsteroids = Number(minorPlanetRows[0]?.count ?? 0) >= 2; - includeNgts = ngtsData.length > 0; - } - - const setsToFetch = computeSetsToFetch(deploymentType, { - includeActiveAsteroids, - includeNgts, - }); - - const data = await prisma.$queryRaw>>` - SELECT * - FROM anomalies - WHERE "anomalySet" = ANY(${setsToFetch}::text[]) - `; - - return NextResponse.json({ anomalies: data }); - } - - if (action === "status") { - const oneWeekAgo = new Date(); - oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); - - const [linkedRes, commentsRes, votesRes] = await Promise.all([ - prisma.$queryRaw>` - SELECT id - FROM linked_anomalies - WHERE automaton = 'Telescope' - AND author::text = ${user.id} - AND date >= ${oneWeekAgo.toISOString()} - `, - prisma.$queryRaw>` - SELECT c.id, cls.author AS classification_author - FROM comments c - LEFT JOIN classifications cls ON cls.id = c.classification_id - WHERE c.author::text = ${user.id} - AND c.created_at >= ${oneWeekAgo.toISOString()} - `, - prisma.$queryRaw>` - SELECT v.id, cls.author AS classification_author - FROM votes v - LEFT JOIN classifications cls ON cls.id = v.classification_id - WHERE v.user_id::text = ${user.id} - AND v.vote_type = 'up' - AND v.created_at >= ${oneWeekAgo.toISOString()} - `, - ]); - - const linkedCount = linkedRes.length; - const validComments = commentsRes.filter((c) => c.classification_author && c.classification_author !== user.id); - const validVotes = votesRes.filter((v) => v.classification_author && v.classification_author !== user.id); - - const additionalDeploys = Math.floor(validVotes.length / 3) + validComments.length; - const userCanRedeploy = linkedCount + additionalDeploys > linkedCount; - - if (linkedCount === 0) { - return NextResponse.json({ alreadyDeployed: false, deploymentMessage: null }); - } - - if (userCanRedeploy) { - return NextResponse.json({ - alreadyDeployed: false, - deploymentMessage: "You have earned additional deploys by interacting with the community this week!", - }); - } - - return NextResponse.json({ - alreadyDeployed: true, - deploymentMessage: "Telescope has already been deployed this week. Recalibrate & search again next week.", - }); - } - - if (action === "skill-progress") { - const start = new Date("2000-01-01").toISOString(); - - const [telescopeRows, weatherRows] = await Promise.all([ - prisma.$queryRaw>` - SELECT COUNT(*)::bigint AS count - FROM classifications - WHERE author::text = ${user.id} - AND classificationtype IN ('planet', 'telescope-minorPlanet') - AND created_at >= ${start} - `, - prisma.$queryRaw>` - SELECT COUNT(*)::bigint AS count - FROM classifications - WHERE author::text = ${user.id} - AND classificationtype IN ('cloud', 'lidar-jovianVortexHunter') - AND created_at >= ${start} - `, - ]); - - return NextResponse.json({ - skillProgress: { - telescope: Number(telescopeRows[0]?.count ?? 0), - weather: Number(weatherRows[0]?.count ?? 0), - }, - }); - } - - return NextResponse.json({ error: "Invalid action" }, { status: 400 }); - } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); - console.error("[telescope] GET error:", message); - return NextResponse.json({ error: "Internal server error", message }, { status: 500 }); - } -} - -export async function POST(request: NextRequest) { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const body = (await request.json().catch(() => ({}))) as DeployBody; - const deploymentType = body?.deploymentType; - const anomalyIds = Array.isArray(body?.anomalyIds) - ? body.anomalyIds.map((id) => Number(id)).filter((id) => Number.isFinite(id)) - : []; - - if (deploymentType !== "stellar" && deploymentType !== "planetary") { - return NextResponse.json({ error: "Invalid deploymentType" }, { status: 400 }); - } - - if (anomalyIds.length === 0) { - return NextResponse.json({ error: "No anomalies selected" }, { status: 400 }); - } - - const upgradeRows = await prisma.$queryRaw>` - SELECT tech_type - FROM researched - WHERE user_id::text = ${user.id} - AND tech_type = 'probereceptors' - `; - - const maxAnomalies = (upgradeRows || []).length > 0 ? 6 : 4; - const uniqueIds = Array.from(new Set(anomalyIds)).slice(0, maxAnomalies); - - const rows = uniqueIds.map((anomalyId) => ({ - author: user.id, - anomaly_id: anomalyId, - classification_id: null, - automaton: "Telescope", - })); - - await prisma.$executeRaw` - INSERT INTO linked_anomalies (author, anomaly_id, classification_id, automaton) - SELECT x.author, x.anomaly_id, x.classification_id, x.automaton - FROM jsonb_to_recordset(${JSON.stringify(rows)}::jsonb) - AS x(author text, anomaly_id int, classification_id int, automaton text) - `; - - revalidatePath("/activity/deploy"); - revalidatePath("/structures/telescope"); - revalidatePath("/game"); - - return NextResponse.json({ success: true, inserted: rows.length }); -} diff --git a/src/app/api/gameplay/inventory/route.ts b/src/app/api/gameplay/inventory/route.ts index fa324b2b0..8ca1d9a5d 100644 --- a/src/app/api/gameplay/inventory/route.ts +++ b/src/app/api/gameplay/inventory/route.ts @@ -91,34 +91,6 @@ const inventoryItems: InventoryItem[] = [ ItemCategory: "StructureResearch", locationType: 'Surface', }, - // { - // id: 3107, - // name: "Launchpad", - // description: "Launch rockets and satellites", - // icon_url: "/assets/Items/Launchpad.jpg", - // ItemCategory: "Structure", - // locationType: 'Surface', - // }, - // { - // id: 3108, - // name: "First rocket", - // description: "Travel the solar-system", - // icon_url: "/assets/Items/Rocket.png", - // ItemCategory: "Structure", - // locationType: 'Atmosphere', - // }, - - - // Classification structures introduced in C2 - // { - // id: 31010, - // name: "Physics Lab", - // description: "Catalogue results from different particle experiments across the universe", - // icon_url: "/assets/Items/PhysicsLab.png", - // ItemCategory: "Structure", - // locationType: 'Surface', - // }, - // Community stations { id: 31011, diff --git a/src/app/api/gameplay/milestones/progress/route.ts b/src/app/api/gameplay/milestones/progress/route.ts deleted file mode 100644 index a81f39085..000000000 --- a/src/app/api/gameplay/milestones/progress/route.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { NextResponse } from "next/server"; - -import { prisma } from "@/lib/server/prisma"; -import { getRouteUser } from "@/lib/server/supabaseRoute"; -import { - ALL_MINERAL_TYPES, - ALL_RESEARCH_UPGRADES, - CLASSIFICATION_ROUTES, - type ClassificationType, -} from "@/src/types/achievement"; - -export const dynamic = "force-dynamic"; - -const VALID_TYPES: ClassificationType[] = [ - "DiskDetective", - "automaton-aiForMars", - "balloon-marsCloudShapes", - "cloud", - "lidar-jovianVortexHunter", - "planet", - "planet-inspection", - "sunspot", - "satellite-planetFour", -]; - -export async function GET() { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const userId = user.id; - - const [researchData, classificationData, mineralsData] = await Promise.all([ - prisma.$queryRaw>` - SELECT tech_type FROM researched WHERE user_id = ${userId} - `, - prisma.$queryRaw>` - SELECT classificationtype FROM classifications WHERE author = ${userId} - `, - prisma.$queryRaw | null }>>` - SELECT mineral_configuration FROM mineral_deposits WHERE owner = ${userId} - `, - ]); - - const unlockedUpgrades = new Set((researchData || []).map((r) => r.tech_type)); - const allUpgrades = { - type: "all-upgrades" as const, - total: ALL_RESEARCH_UPGRADES.length, - unlocked: ALL_RESEARCH_UPGRADES.filter((u) => unlockedUpgrades.has(u.techType)).length, - upgrades: ALL_RESEARCH_UPGRADES.map((upgrade) => ({ - name: upgrade.name, - techType: upgrade.techType, - isUnlocked: unlockedUpgrades.has(upgrade.techType), - })), - }; - - const classificationCounts: Record = {}; - (classificationData || []).forEach((row) => { - const type = row.classificationtype; - if (VALID_TYPES.includes(type as ClassificationType)) { - classificationCounts[type] = (classificationCounts[type] || 0) + 1; - } - }); - - const classificationDiversity = { - type: "classification-diversity" as const, - total: VALID_TYPES.length, - completed: VALID_TYPES.filter((type) => (classificationCounts[type] || 0) > 0).length, - classifications: VALID_TYPES.map((type) => { - const routeInfo = CLASSIFICATION_ROUTES[type]; - return { - type, - name: type, - count: classificationCounts[type] || 0, - isComplete: (classificationCounts[type] || 0) > 0, - route: routeInfo.route, - hint: routeInfo.hint, - }; - }), - }; - - const extractedTypes = new Set(); - (mineralsData || []).forEach((deposit) => { - const config = deposit.mineral_configuration; - if (!config) return; - const type = config.type || config.mineralType; - if (!type) return; - - const normalizedType = ALL_MINERAL_TYPES.find((mt) => { - return mt.toLowerCase().replace(/[^a-z0-9]/g, "") === String(type).toLowerCase().replace(/[^a-z0-9]/g, ""); - }); - if (normalizedType) { - extractedTypes.add(normalizedType); - } - }); - - const resourceExtraction = { - type: "resource-extraction" as const, - total: ALL_MINERAL_TYPES.length, - extracted: extractedTypes.size, - resources: ALL_MINERAL_TYPES.map((type) => ({ - type, - name: type, - isExtracted: extractedTypes.has(type), - })), - }; - - return NextResponse.json({ - allUpgrades, - classificationDiversity, - resourceExtraction, - }); -} diff --git a/src/app/api/gameplay/mineral-deposits/bulk/route.ts b/src/app/api/gameplay/mineral-deposits/bulk/route.ts deleted file mode 100644 index 9a6aaab45..000000000 --- a/src/app/api/gameplay/mineral-deposits/bulk/route.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { revalidatePath } from "next/cache"; - -import { prisma } from "@/lib/server/prisma"; -import { getRouteUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -type BulkBody = { - deposits?: Array>; -}; - -export async function POST(request: NextRequest) { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const body = (await request.json().catch(() => ({}))) as BulkBody; - const deposits = Array.isArray(body?.deposits) ? body.deposits : []; - if (deposits.length === 0) { - return NextResponse.json({ error: "No deposits provided" }, { status: 400 }); - } - - // Client payloads now use canonical snake_case keys. - const normalized = deposits.map((row) => ({ - ...(row as Record), - owner: user.id, - })); - - const data = await prisma.$queryRaw>` - INSERT INTO mineral_deposits - SELECT * - FROM jsonb_populate_recordset(NULL::mineral_deposits, ${JSON.stringify(normalized)}::jsonb) - RETURNING id - `; - - revalidatePath("/inventory"); - revalidatePath("/viewports/rover"); - - return NextResponse.json({ success: true, count: data.length }); -} diff --git a/src/app/api/gameplay/nps/status/route.ts b/src/app/api/gameplay/nps/status/route.ts deleted file mode 100644 index f7a21bf11..000000000 --- a/src/app/api/gameplay/nps/status/route.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NextResponse } from "next/server"; - -import { prisma } from "@/lib/server/prisma"; -import { getRouteUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -export async function GET() { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const [classificationRows, surveyRows] = await Promise.all([ - prisma.$queryRaw>` - SELECT id - FROM classifications - WHERE author = ${user.id} - LIMIT 1 - `, - prisma.$queryRaw>` - SELECT id - FROM nps_surveys - WHERE user_id = ${user.id} - LIMIT 1 - `, - ]); - - const hasClassification = classificationRows.length > 0; - const hasCompletedNps = surveyRows.length > 0; - - return NextResponse.json({ - hasClassification, - hasCompletedNps, - shouldShowNps: hasClassification && !hasCompletedNps, - }); -} diff --git a/src/app/api/gameplay/page-data/route.ts b/src/app/api/gameplay/page-data/route.ts index 88a1e3875..057971fa8 100644 --- a/src/app/api/gameplay/page-data/route.ts +++ b/src/app/api/gameplay/page-data/route.ts @@ -5,28 +5,31 @@ import { getRouteUser } from "@/lib/server/supabaseRoute"; export const dynamic = "force-dynamic"; export async function GET() { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - + let userId = "(unknown)"; try { - return await getPageData(user.id); + const { user, authError } = await getRouteUser(); + if (authError || !user) { + console.error("[page-data] auth failed:", authError?.message); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + userId = user.id; + console.log("[page-data] fetching for user:", userId); + + const data = await getGamePageDataForUser(userId); + return NextResponse.json(data); } catch (err: unknown) { const message = err instanceof Error ? err.message : String(err); - console.error(`[page-data] Error for user ${user.id}:`, message); - - if (message.includes("Can't reach database server") || message.includes("PrismaClient")) { + const stack = err instanceof Error ? err.stack : undefined; + console.error(`[page-data] ERROR for user ${userId}:`, message); + console.error(`[page-data] STACK:`, stack); + + if (message.includes("Can't reach database server") || message.includes("connect ECONNREFUSED")) { return NextResponse.json({ error: "Database unavailable" }, { status: 503 }); } if (message.includes("relation") && message.includes("does not exist")) { return NextResponse.json({ error: "Database schema error" }, { status: 503 }); } - return NextResponse.json({ error: "Internal server error", message }, { status: 500 }); + return NextResponse.json({ error: "Internal server error", detail: message }, { status: 500 }); } } - -async function getPageData(userId: string) { - const data = await getGamePageDataForUser(userId); - return NextResponse.json(data); -} diff --git a/src/app/api/gameplay/planets/[id]/route.ts b/src/app/api/gameplay/planets/[id]/route.ts deleted file mode 100644 index dd25e9c25..000000000 --- a/src/app/api/gameplay/planets/[id]/route.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -import { Prisma } from "@prisma/client"; - -import { prisma } from "@/lib/server/prisma"; - -export const dynamic = "force-dynamic"; - -type PlanetClassificationRow = Record & { - id: number; - anomaly: number | null; - anomaly_data: Record | null; - classificationConfiguration?: unknown; - media?: unknown; -}; - -type VoteCountRow = { - classification_id: number; - count: bigint | number; -}; - -export async function GET(_request: NextRequest, props: { params: Promise<{ id: string }> }) { - const params = await props.params; - const id = Number(params.id); - if (!Number.isFinite(id)) { - return NextResponse.json({ error: "Invalid classification ID" }, { status: 400 }); - } - - const classificationRows = await prisma.$queryRaw` - SELECT - c.*, - json_build_object( - 'id', a.id, - 'content', a.content, - 'anomalytype', a.anomalytype, - 'mass', a.mass, - 'radius', a.radius, - 'density', a.density, - 'gravity', a.gravity, - 'temperature', a.temperature, - 'orbital_period', a.orbital_period, - 'avatar_url', a.avatar_url, - 'created_at', a.created_at - ) AS anomaly_data - FROM classifications c - LEFT JOIN anomalies a ON a.id = c.anomaly - WHERE c.id = ${id} - LIMIT 1 - `; - const classification = classificationRows[0]; - if (!classification) { - return NextResponse.json({ error: "Classification not found" }, { status: 404 }); - } - - const parentPlanetLocation = Number(classification?.anomaly); - let relatedRows: PlanetClassificationRow[] = []; - if (Number.isFinite(parentPlanetLocation)) { - relatedRows = await prisma.$queryRaw(Prisma.sql` - SELECT - c.*, - json_build_object( - 'id', a.id, - 'content', a.content, - 'anomalytype', a.anomalytype, - 'mass', a.mass, - 'radius', a.radius, - 'density', a.density, - 'gravity', a.gravity, - 'temperature', a.temperature, - 'orbital_period', a.orbital_period, - 'avatar_url', a.avatar_url, - 'created_at', a.created_at - ) AS anomaly_data - FROM classifications c - LEFT JOIN anomalies a ON a.id = c.anomaly - WHERE (c."classificationConfiguration" ->> 'parentPlanetLocation') = ${String(parentPlanetLocation)} - ORDER BY c.created_at DESC - LIMIT 500 - `); - } - - const relatedIds = relatedRows.map((row) => row.id); - let voteCounts = new Map(); - if (relatedIds.length > 0) { - const voteRows = await prisma.$queryRaw(Prisma.sql` - SELECT classification_id, COUNT(*)::bigint AS count - FROM votes - WHERE classification_id IN (${Prisma.join(relatedIds)}) - GROUP BY classification_id - `); - voteCounts = new Map(voteRows.map((row) => [row.classification_id, Number(row.count)])); - } - - const normalize = (row: PlanetClassificationRow) => ({ - ...row, - anomaly: row.anomaly_data, - votes: voteCounts.get(row.id) ?? 0, - }); - - return NextResponse.json({ - classification: normalize(classification), - relatedClassifications: relatedRows.map(normalize), - }); -} diff --git a/src/app/api/gameplay/profile/code/route.ts b/src/app/api/gameplay/profile/code/route.ts deleted file mode 100644 index 0635faf89..000000000 --- a/src/app/api/gameplay/profile/code/route.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NextResponse } from "next/server"; - -import { prisma } from "@/lib/server/prisma"; -import { getRouteUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -export async function GET() { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ referralCode: null }, { status: 200 }); - } - - const rows = await prisma.$queryRaw>` - SELECT referral_code FROM profiles WHERE id::text = ${user.id} LIMIT 1 - `; - - return NextResponse.json({ referralCode: rows[0]?.referral_code ?? null }); -} diff --git a/src/app/api/gameplay/profile/getting-started/route.ts b/src/app/api/gameplay/profile/getting-started/route.ts deleted file mode 100644 index 8eb04935b..000000000 --- a/src/app/api/gameplay/profile/getting-started/route.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NextResponse } from "next/server"; - -import { prisma } from "@/lib/server/prisma"; -import { getRouteUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -export async function GET() { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const classifications = await prisma.$queryRaw>` - SELECT classificationtype - FROM classifications - WHERE author = ${user.id} - `; - - const types = Array.from( - new Set( - (classifications || []) - .map((c: any) => c.classificationtype) - .filter(Boolean) - ) - ) as string[]; - - const toolCounts = types.reduce( - (acc, type) => { - if (["planet", "telescope-minorPlanet", "sunspot", "active-asteroid"].includes(type)) acc.telescope++; - else if (["balloon-marsCloudShapes", "cloud", "lidar-jovianVortexHunter", "satellite-planetFour"].includes(type)) - acc.satellite++; - else if (["automaton-aiForMars"].includes(type)) acc.rover++; - return acc; - }, - { telescope: 0, satellite: 0, rover: 0 } - ); - - return NextResponse.json({ - totalClassifications: classifications.length, - classificationTypes: types, - toolsUsed: toolCounts, - }); -} diff --git a/src/app/api/gameplay/profile/me/route.ts b/src/app/api/gameplay/profile/me/route.ts index 0849a4a77..952c412fb 100644 --- a/src/app/api/gameplay/profile/me/route.ts +++ b/src/app/api/gameplay/profile/me/route.ts @@ -17,9 +17,10 @@ export async function GET() { username: string | null; full_name: string | null; referral_code: string | null; + location: bigint | null; }> >` - SELECT avatar_url, username, full_name, referral_code + SELECT avatar_url, username, full_name, referral_code, location FROM profiles WHERE id::text = ${user.id} LIMIT 1 @@ -30,5 +31,6 @@ export async function GET() { username: rows[0]?.username ?? null, full_name: rows[0]?.full_name ?? null, referral_code: rows[0]?.referral_code ?? null, + location: rows[0]?.location ? rows[0].location.toString() : null, }); } diff --git a/src/app/api/gameplay/skills/unlock/route.ts b/src/app/api/gameplay/skills/unlock/route.ts deleted file mode 100644 index b55c3c6a4..000000000 --- a/src/app/api/gameplay/skills/unlock/route.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { revalidatePath } from "next/cache"; - -import { prisma } from "@/lib/server/prisma"; -import { getRouteUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -type UnlockBody = { - skillId?: string; -}; - -export async function POST(request: NextRequest) { - const { user, authError } = await getRouteUser(); - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const body = (await request.json().catch(() => ({}))) as UnlockBody; - const skillId = typeof body?.skillId === "string" ? body.skillId.trim() : ""; - if (!skillId) { - return NextResponse.json({ error: "Invalid skillId" }, { status: 400 }); - } - - const existingRows = await prisma.$queryRaw>` - SELECT id - FROM user_skills - WHERE user_id = ${user.id} - AND skill_id = ${skillId} - LIMIT 1 - `; - const existing = existingRows[0]; - - if (existing?.id) { - return NextResponse.json({ success: true, alreadyUnlocked: true }); - } - - await prisma.$executeRaw` - INSERT INTO user_skills (user_id, skill_id, unlocked_at) - VALUES (${user.id}, ${skillId}, ${new Date().toISOString()}) - `; - - revalidatePath("/game"); - revalidatePath("/activity/deploy"); - - return NextResponse.json({ success: true, alreadyUnlocked: false }); -} diff --git a/src/app/api/gameplay/survey-reward/route.ts b/src/app/api/gameplay/survey-reward/route.ts deleted file mode 100644 index 5b7d95dfb..000000000 --- a/src/app/api/gameplay/survey-reward/route.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -import { prisma } from "@/lib/server/prisma"; -import { isPrismaUniqueConstraintError } from "@/lib/server/researched"; -import { getRouteUser } from "@/lib/server/supabaseRoute"; - -export const dynamic = "force-dynamic"; - -const SURVEY_STARDUST_REWARD = 5; - -export async function POST(req: NextRequest) { - const { user, authError } = await getRouteUser(); - - if (authError || !user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - let body: { surveyId?: string; surveyName?: string }; - try { - body = await req.json(); - } catch { - return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); - } - - const { surveyId, surveyName } = body; - - if (!surveyId || typeof surveyId !== "string") { - return NextResponse.json({ error: "surveyId is required" }, { status: 400 }); - } - - const userId = user.id; - - try { - await prisma.surveyReward.create({ - data: { - userId, - surveyId, - surveyName: surveyName ?? null, - stardustGranted: SURVEY_STARDUST_REWARD, - }, - }); - } catch (error) { - if (isPrismaUniqueConstraintError(error)) { - return NextResponse.json({ granted: false, alreadyGranted: true }); - } - - throw error; - } - - return NextResponse.json({ - granted: true, - alreadyGranted: false, - stardust: SURVEY_STARDUST_REWARD, - }); -} diff --git a/src/app/apt/page.tsx b/src/app/apt/page.tsx deleted file mode 100644 index a97c05c67..000000000 --- a/src/app/apt/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from "next/navigation"; - -export default function AptPage() { - redirect("/"); -} diff --git a/src/app/auth/callback/route.ts b/src/app/auth/callback/route.ts new file mode 100644 index 000000000..0a971fbaa --- /dev/null +++ b/src/app/auth/callback/route.ts @@ -0,0 +1,38 @@ +import { NextResponse, type NextRequest } from "next/server"; +import { createServerClient } from "@supabase/ssr"; +import { cookies } from "next/headers"; + +export async function GET(request: NextRequest) { + const { searchParams, origin } = new URL(request.url); + const code = searchParams.get("code"); + const next = searchParams.get("next") ?? "/game"; + + if (code) { + const cookieStore = cookies(); + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + async getAll() { + return (await cookieStore).getAll(); + }, + async setAll(cookiesToSet) { + const store = await cookieStore; + cookiesToSet.forEach(({ name, value, options }) => + store.set(name, value, options) + ); + }, + }, + } + ); + + const { error } = await supabase.auth.exchangeCodeForSession(code); + if (!error) { + return NextResponse.redirect(`${origin}${next}`); + } + } + + // Something went wrong — send to auth with an error flag + return NextResponse.redirect(`${origin}/auth?error=oauth_callback_failed`); +} diff --git a/src/app/game/GameClient.tsx b/src/app/game/GameClient.tsx index ff06bbb1e..a9eb67957 100644 --- a/src/app/game/GameClient.tsx +++ b/src/app/game/GameClient.tsx @@ -18,9 +18,6 @@ const TelescopeBackground = dynamic( ), } ); -const NPSPopup = dynamic(() => import("@/src/components/ui/helpers/nps-popup"), { - loading: () => null, -}); const CompleteProfileForm = dynamic( () => import("@/src/components/profile/setup/FinishProfile"), { @@ -74,14 +71,11 @@ import { ErrorBoundary } from "@/src/components/ui/ErrorBoundary"; // Station UI components — critical path (statically imported) import { CommandHeader } from "@/src/features/game/components/station/CommandHeader"; -import { SectorBar } from "@/src/features/game/components/station/SectorBar"; import { MissionBriefCard } from "@/src/features/game/components/station/MissionBriefCard"; -import { StationCard } from "@/src/features/game/components/station/StationCard"; import { StationNav } from "@/src/features/game/components/station/StationNav"; import { ViewportHeader } from "@/src/features/game/components/station/ViewportHeader"; import { SectionLabel } from "@/src/features/game/components/station/SectionLabel"; import { MissionLogPanel, buildLogEntries } from "@/src/features/game/components/station/MissionLogPanel"; -import { StationSchematic } from "@/src/features/game/components/station/StationSchematic"; import { SectorRadar } from "@/src/features/game/components/station/SectorRadar"; import { HUDStrip } from "@/src/features/game/components/station/HUDStrip"; import { StructureCard, StructureState, StructureId } from "@/src/features/game/components/station/StructureCard"; @@ -97,7 +91,6 @@ const LivingWorldBg = dynamic(() => import("@/src/features/game/components/ import RecentActivity from "@/src/components/social/activity/RecentActivity"; import ProjectPreferencesModal from "@/src/components/onboarding/ProjectPreferencesModal"; import PushNotificationPrompt from "@/src/features/notifications/components/PushNotificationPrompt"; -import ActivityHeader from "@/src/components/scenes/deploy/ActivityHeader"; import { GameSurveys } from "@/src/features/surveys/components/GameSurveys"; import ReferralMissionPrompt from "@/src/features/game/components/ReferralMissionPrompt"; @@ -108,28 +101,15 @@ import UseDarkMode from "@/src/shared/hooks/useDarkMode"; // Types / utils import { cn } from "@/src/shared/utils"; -import { getOrCreateAnalyticsSessionToken } from "@/src/lib/analytics/session-token"; -import { - MECHANIC_SURVEYS, - SURVEY_DISPLAY_DELAY_MS, - surveyStorageKey, -} from "@/src/features/surveys/mechanic-surveys"; -import type { MechanicMicroSurvey } from "@/src/features/surveys/types"; -import { ProjectType } from "@/src/hooks/useUserPreferences"; + import { buildClientReferralUrl } from "@/src/features/referrals/referral-links"; // Icons -import { Telescope, Satellite, Car, Package, Sun, FlaskConical, AlertTriangle } from "lucide-react"; +import { Telescope, Satellite, Car, Package, Sun, FlaskConical } from "lucide-react"; type ViewMode = "base" | "telescope" | "satellite" | "rover" | "solar" | "inventory"; type StructureSignalMap = Record; -const POSTHOG_SURVEY_ID = "019c83d3-d0a5-0000-4e8f-5b7fd8794666"; -const POSTHOG_SURVEY_SHOWN_KEY = "posthog_survey_webapp_22_shown_v1"; -const POSTHOG_SURVEY_LAST_SHOWN_AT_KEY = "posthog_survey_webapp_22_last_shown_at_v1"; -const POSTHOG_SURVEY_COOLDOWN_MS = 14 * 24 * 60 * 60 * 1000; -const SURVEY_REWARD_GRANTED_KEY = "posthog_survey_webapp_22_reward_granted_v1"; -const SURVEY_REWARD_STARDUST = 5; const REFERRAL_MISSION_DISMISSED_KEY = "referral_mission_prompt_dismissed_v1"; const GAME_DATA_REFRESH_MS = 60_000; const INCOMING_SIGNAL_WINDOW_MS = 1_600; @@ -160,20 +140,29 @@ export default function GameClient({ initialData, user }: GameClientProps) { const [showPreferencesModal, setShowPreferencesModal] = useState(false); const [openedViewports, setOpenedViewports] = useState>(new Set()); const [referralMissionDismissed, setReferralMissionDismissed] = useState(true); - const [surveyRewardToast, setSurveyRewardToast] = useState<{ - variant: "success" | "error" | "info"; - title: string; - description: string; - } | null>(null); - const [activeMechanicSurvey, setActiveMechanicSurvey] = useState(null); - const [analyticsSessionToken] = useState(() => getOrCreateAnalyticsSessionToken()); - // Guided deploy overlay const [guidedDeployTarget, setGuidedDeployTarget] = useState(null); const [ambientReady, setAmbientReady] = useState(false); + const EMPTY_DATA = { + profile: null, + classifications: [], + linkedAnomalies: [], + activityFeed: [], + otherClassifications: [], + visibleStructures: { telescope: true, satellites: false, rovers: false, balloons: false }, + hubLeaderboard: { entries: [], currentUser: null }, + referralCode: null, + referralCount: 0, + hasReferral: false, + hasRoverMineralDeposits: false, + incompletePlanet: null, + planetTargets: [], + }; + // Use initialData as state so it can be updated if needed (optimistic updates or refetch) - const [data, setData] = useState(initialData); + const [data, setData] = useState(initialData ?? EMPTY_DATA); + const [isLoadingData, setIsLoadingData] = useState(initialData === null); const [incomingStructures, setIncomingStructures] = useState>(new Set()); const previousSignalCountsRef = useRef(null); const incomingTimeoutsRef = useRef>>>({}); @@ -203,6 +192,11 @@ export default function GameClient({ initialData, user }: GameClientProps) { router.replace(`/game?${params.toString()}`, { scroll: false }); }, [router, posthog, activeView]); + const handleQuickDeploy = useCallback((id: StructureId) => { + posthog?.capture("quick_deploy_used", { structure: id }); + markTutorialComplete(`${id}-deploy` as TutorialId); + }, [markTutorialComplete, posthog]); + const handleStructureClick = useCallback((id: StructureId) => { const tutorialKey = `${id}-deploy` as TutorialId; if (!hasTutorialCompleted(tutorialKey)) { @@ -296,6 +290,13 @@ export default function GameClient({ initialData, user }: GameClientProps) { } }, []); + // If server passed no initialData (cold start / timeout protection), fetch immediately + useEffect(() => { + if (!isLoadingData) return; + refreshGameData().finally(() => setIsLoadingData(false)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { let isRefreshing = false; @@ -402,6 +403,13 @@ export default function GameClient({ initialData, user }: GameClientProps) { }; }, []); + const alerts = useMemo>>(() => ({ + telescope: signalCounts.telescope > 0, + satellite: signalCounts.satellite > 0, + rover: signalCounts.rover > 0, + solar: signalCounts.solar > 0, + }), [signalCounts]); + return (

{showAmbientLayers && ambientReady ? ( @@ -432,6 +440,9 @@ export default function GameClient({ initialData, user }: GameClientProps) { agencyId={data.profile?.username || data.profile?.id} /> + {/* Header Spacer to prevent overlap */} +
+ {/* ─── Persistent HUD Strip ─── */} s + r.signals, 0)} @@ -439,7 +450,7 @@ export default function GameClient({ initialData, user }: GameClientProps) { classifications={data.classifications?.length ?? 0} /> -
+
{activeView === "base" ? ( handleStructureClick(id)} + onQuickDeploy={() => handleQuickDeploy(id)} />
); @@ -659,6 +671,12 @@ export default function GameClient({ initialData, user }: GameClientProps) { + +
); } diff --git a/src/app/game/page.tsx b/src/app/game/page.tsx index 9366c2d75..f0f081ae5 100644 --- a/src/app/game/page.tsx +++ b/src/app/game/page.tsx @@ -1,13 +1,10 @@ import { Suspense } from "react"; import { redirect } from "next/navigation"; import { createSupabaseServerClient } from "@/src/lib/supabase/ssr"; -import { getGamePageData } from "@/src/features/gameplay/actions/game-actions"; import GameClient from "./GameClient"; export const dynamic = "force-dynamic"; -// ─── Loading skeleton ─────────────────────────────────────────────────────── - function ControlStationSkeleton() { return (
@@ -26,24 +23,34 @@ function ControlStationSkeleton() { } export default async function GamePage() { - const supabase = createSupabaseServerClient(); - const { data: { session } } = await supabase.auth.getSession(); + console.log("[GamePage] render start"); + try { + const supabase = createSupabaseServerClient(); + console.log("[GamePage] supabase client created"); - if (!session) { - redirect("/auth"); - } + const { data: { session }, error: sessionError } = await supabase.auth.getSession(); + console.log("[GamePage] getSession done", { hasSession: !!session, sessionError }); - try { - const initialData = await getGamePageData(); - + if (sessionError) { + console.error("[GamePage] getSession error:", sessionError); + } + + if (!session) { + console.log("[GamePage] no session, redirecting to /auth"); + redirect("/auth"); + } + + console.log("[GamePage] rendering GameClient for user", session.user.id); return ( }> - + ); - } catch (error) { - console.error("[Game Page] Critical Error:", error); - // You could redirect to an error page or a custom error state - throw error; // Let the Next.js error boundary handle it + } catch (err) { + // Log the real error — visible in Vercel function logs + console.error("[GamePage] FATAL ERROR:", err); + console.error("[GamePage] error message:", err instanceof Error ? err.message : String(err)); + console.error("[GamePage] error stack:", err instanceof Error ? err.stack : "no stack"); + throw err; } } diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx index cf8f57b47..71276dc84 100644 --- a/src/app/onboarding/page.tsx +++ b/src/app/onboarding/page.tsx @@ -3,101 +3,94 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { usePostHog } from "posthog-js/react"; -import { - Telescope, - Car, - Satellite, - ArrowRight, - Check, - Shield, - Zap, - ChevronRight, - Loader2 -} from "lucide-react"; -import { cn } from "@/src/shared/utils"; +import { Loader2 } from "lucide-react"; import { useUserPreferences, ProjectType } from "@/src/hooks/useUserPreferences"; import { useAuthUser } from "@/src/hooks/useAuthUser"; import { IntroSequence } from "@/src/components/onboarding/IntroSequence"; -import { OnboardingSchematic } from "@/src/components/onboarding/OnboardingSchematic"; +import { IntroStep, ProjectSelectionStep, StructureIntroStep } from "@/src/components/onboarding/OnboardingSteps"; +import { SETUP_MAP } from "@/src/components/onboarding/onboarding-data"; type Step = "intro" | "project-selection" | "structure-intro"; -const PROJECTS = [ - { - id: "planet-hunting" as ProjectType, - name: "Planet Hunting", - icon: Telescope, - color: "text-teal-400", - bg: "bg-teal-500/10", - border: "border-teal-500/30", - glow: "glow-teal", - structure: "Telescope", - description: "Search for dips in light from distant stars using TESS data. Discover new worlds." - }, - { - id: "rover-training" as ProjectType, - name: "Rover Training", - icon: Car, - color: "text-amber-400", - bg: "bg-amber-500/10", - border: "border-amber-500/30", - glow: "glow-amber", - structure: "Rover", - description: "Analyse Martian terrain images to help autonomous rovers navigate safely." - }, - { - id: "cloud-tracking" as ProjectType, - name: "Cloud Spotting", - icon: Satellite, - color: "text-sky-400", - bg: "bg-sky-500/10", - border: "border-sky-500/30", - glow: "glow-sky", - structure: "Satellite", - description: "Track Martian clouds and Jovian vortices using orbital imagery. Map planetary weather." - } -]; +const SS_ONBOARDING_STEP = "ss_onboarding_step"; +const SS_ONBOARDING_PROJECT = "ss_onboarding_project"; -const SETUP_MAP: Record = { - "planet-hunting": "/setup/telescope", - "rover-training": "/setup/rover", - "cloud-tracking": "/setup/satellite", - "asteroid-hunting": "/setup/telescope", - "ice-tracking": "/setup/satellite", - "solar-monitoring": "/setup/solar" -}; +function safeGet(key: string): string | null { + try { return localStorage.getItem(key); } catch { return null; } +} +function safeSet(key: string, value: string) { + try { localStorage.setItem(key, value); } catch { /* ignore */ } +} +function safeClear(...keys: string[]) { + try { keys.forEach((k) => localStorage.removeItem(k)); } catch { /* ignore */ } +} export default function OnboardingPage() { const router = useRouter(); const posthog = usePostHog(); const { user, isLoading: authLoading } = useAuthUser(); const { preferences, isLoading: prefsLoading, setProjectInterests, completeOnboarding, hasTutorialCompleted, markTutorialComplete } = useUserPreferences(); - + const [showIntro, setShowIntro] = useState(false); const [step, setStep] = useState("intro"); const [selectedProject, setSelectedProject] = useState(null); const [isPoweringUp, setIsPoweringUp] = useState(false); - // Redirect unauthenticated users + // Redirect unauthenticated users and check for existing profile in background useEffect(() => { - if (!authLoading && !user) { + if (authLoading) return; + + if (!user) { router.push("/auth"); + return; + } + + // Check if user already has a profile to skip onboarding (silent background check) + async function checkExistingProfile() { + try { + const res = await fetch("/api/gameplay/profile/me"); + if (res.ok) { + const profile = await res.json(); + // If profile has a username, it's a returning user + if (profile && profile.username) { + completeOnboarding(); + router.replace("/game"); + } + } + } catch (err) { + console.error("Error checking existing profile:", err); + } + } + + // Only check if we haven't already marked onboarding as complete locally + if (!preferences.hasCompletedOnboarding) { + checkExistingProfile(); } - }, [user, authLoading, router]); + }, [user, authLoading, router, completeOnboarding, preferences.hasCompletedOnboarding]); - // Skip onboarding for returning users + // Skip onboarding for returning users (based on local preferences) useEffect(() => { if (!prefsLoading && preferences.hasCompletedOnboarding) { router.replace("/game"); } }, [prefsLoading, preferences.hasCompletedOnboarding, router]); - // Show intro sequence on first visit only + // Restore in-progress step from localStorage on mount useEffect(() => { - if (!prefsLoading && !preferences.hasCompletedOnboarding) { - if (!hasTutorialCompleted("intro-seen")) { - setShowIntro(true); + if (prefsLoading || preferences.hasCompletedOnboarding) return; + const savedStep = safeGet(SS_ONBOARDING_STEP) as Step | null; + const savedProject = safeGet(SS_ONBOARDING_PROJECT) as ProjectType | null; + if (savedStep && savedStep !== "intro") { + if (savedStep === "structure-intro" && !savedProject) { + setStep("project-selection"); + } else { + setStep(savedStep); + if (savedProject) setSelectedProject(savedProject); } + return; // skip intro sequence when resuming + } + if (!hasTutorialCompleted("intro-seen")) { + setShowIntro(true); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [prefsLoading]); @@ -122,16 +115,14 @@ export default function OnboardingPage() { setIsPoweringUp(true); setTimeout(() => { setStep("project-selection"); + safeSet(SS_ONBOARDING_STEP, "project-selection"); setIsPoweringUp(false); }, 2000); }; - const handleProjectSelect = (id: ProjectType) => { - setSelectedProject(id); - }; - const handleFinalise = () => { if (!selectedProject) return; + safeClear(SS_ONBOARDING_STEP, SS_ONBOARDING_PROJECT); setProjectInterests([selectedProject]); completeOnboarding(); posthog?.capture("onboarding_completed", { userId: user.id, project: selectedProject }); @@ -153,147 +144,33 @@ export default function OnboardingPage() {
- {/* STEP 1: INTRO */} {step === "intro" && ( -
-
- -
-

- Station
Arrival. -

-

- Welcome, Sailor. Your operator station is currently in standby mode. - We need to initialise your primary mission protocol. -

- -
- -
-
+ )} - {/* STEP 2: PROJECT SELECTION */} {step === "project-selection" && ( -
-
- Protocol 01 -

Select your first project

-

This determines which structure you will deploy first.

-
- -
- - {PROJECTS.map((p) => ( - - ))} -
- -
- -
-
+ { + setSelectedProject(p); + safeSet(SS_ONBOARDING_PROJECT, p); + }} + onContinue={() => { + setStep("structure-intro"); + safeSet(SS_ONBOARDING_STEP, "structure-intro"); + }} + /> )} - {/* STEP 3: STRUCTURE INTRO */} - {step === "structure-intro" && selectedProject && (() => { - const p = PROJECTS.find(proj => proj.id === selectedProject)!; - return ( -
-
- -
- Hardware Assigned -

- {p.structure}
initialised. -

-

- To begin the {p.name} mission, you need to deploy your first {p.structure}. - We'll guide you through the setup on the next screen. -

- -
-
-
- -
-

You'll earn your first stardust for completing the setup.

-
-
- -
- -
-
- ); - })()} + {step === "structure-intro" && selectedProject && ( + + )} {/* HUD footer */}
@@ -301,7 +178,6 @@ export default function OnboardingPage() {
System Online
- v3.0.0-alpha
diff --git a/src/app/planets/[id]/page.tsx b/src/app/planets/[id]/page.tsx deleted file mode 100644 index e323001b9..000000000 --- a/src/app/planets/[id]/page.tsx +++ /dev/null @@ -1,623 +0,0 @@ -'use client'; - -import React, { useEffect, useCallback, useState, useMemo } from "react"; -import { useSession } from "@/src/lib/auth/session-context"; -import { PostCardSingleWithGenerator } from "@/src/components/social/posts/PostWithGen"; -import Image from "next/image"; -import { Button } from "@/src/components/ui/button"; -import { ArrowLeft, Droplets, FileText, Info, Thermometer } from "lucide-react"; -import PlanetGenerator from "@/src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator"; -// import { PlanetGenerator } from "starsailors-planet-generator"; -// import WeatherEventStatus from "@/src/components/discovery/data-sources/Weather/EventsCounter"; -// import BiomassStats from "@/src/components/deployment/missions/structures/Biologists/BiomassOnPlanet"; -import BiomeAggregator from "@/src/components/discovery/data-sources/BiomeAggregator"; -import PlanetProgress from "@/src/components/deployment/missions/structures/Astronomers/PlanetHunters/PlanetCompletion"; -// import WeatherGenerator from "@/src/components/discovery/data-sources/Weather/SimpleWeatherEvents"; -import MainHeader from "@/src/components/layout/Header/MainHeader"; -import UseDarkMode from "@/src/shared/hooks/useDarkMode"; - -export interface Classification { - id: number; - content: string | null; - author: string | null; - anomaly: Anomaly | null; - media: (string | { uploadUrl?: string })[] | null; - classificationtype: string | null; - classificationConfiguration?: any; - created_at: string; - title?: string; - votes?: number; - category?: string; - tags?: string[]; - images?: string[]; - relatedClassifications?: Classification[]; -}; - -type PlanetData = { - name: string; - galaxy: string; - diameter: string; - dayLength: string; - temperature: string; - climate: string; -}; - -type FocusView = "planet" | "overview" | "Climate" | "atmosphere" | "exploration" | "map" | "edit" - -export type Anomaly = { - id: number; - content: string | null; - anomalytype?: string | null; - mass?: number | null; - radius?: number | null; - density?: number | null; - gravity?: number | null; - temperature?: number | null; - orbital_period?: number | null; -avatar_url?: string | null; - created_at?: string; -}; - -export interface AggregatedCloud { - annotationOptions: Record; - classificationOptions: Record>; - additionalFields: Record>; - cloudColours?: Record; -}; - -export interface AggregatedP4 { - fanCount: number; - blotchCount: number; - classificationCounts: Record; -}; - -export interface AggregatedAI4M { - sandCount: number; - soilCount: number; - bedrockCount: number; - rockCount: number; - unlabelledCount: number; - classificationCounts: Record; -}; - -export interface AggregatedBalloon { - shapeCount: number; - vortexCount: number; - streakCount: number; - unlabelledCount: number; - classificationCounts: Record; -}; - -export interface AI4MClassification { - id: number; - classificationConfiguration: any; - annotationOptions: any[]; -}; - -import { useParams } from 'next/navigation'; -import Link from "next/link"; - -export default function TestPlanetWrapper() { - const params = useParams(); - const id = params?.id as string; - - const session = useSession(); - const { isDark, toggleDarkMode } = UseDarkMode(); - - const [classification, setClassification] = useState(null); - const [anomaly, setAnomaly] = useState(null); - const [currentView, setCurrentView] = useState("planet"); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [dominantBiome, setDominantBiome] = useState('Barren (Pending)'); - const [cloudSummary, setCloudSummary] = useState(null); - const [p4Summary, setP4Summary] = useState(null); - const [ai4MSummary, setAI4MSummary] = useState(null); - const [relatedClassifications, setRelatedClassifications] = useState([]); - const [period, setPeriod] = useState(""); - const [showCurrentUser, setShowCurrentUser] = useState(true); - const [showMetadata, setShowMetadata] = useState(false); - const [currentPlanet, setCurrentPlanet] = useState("orionis") - const [density, setDensity] = useState(null); - const [temperature, setTemperature] = useState(null); - const [biomassScore, setBiomassScore] = useState(0); - const [surveyorPeriod, setSurveyorPeriod] = useState("") - const [comments, setComments] = useState([]); - const [timeLeft, setTimeLeft] = useState(0); - - const getTimeUntilNextSunday = () => { - const now = new Date(); - const nextSunday = new Date(now); - nextSunday.setDate(now.getDate() + (7 - now.getDay()) % 7); - nextSunday.setHours(10, 1, 0, 0); - return nextSunday.getTime() - now.getTime(); - }; - - const calculateBiomass = (temperature?: number, radius?: number, surveyorPeriod?: number): number => { - const T = temperature ?? 300; - const R = radius ?? 1; - const P = surveyorPeriod ?? 1.5; // default to 1.5 if undefined or invalid - - return ( - 0.1 * - (T / (T + 300)) * - (1 / (1 + Math.exp(-(R - 1.2)))) * - (1 / (1 + Math.exp(-(1.5 - P)))) - ); -}; - - const formatTime = (timeInSeconds: number) => { - const hours = String(Math.floor(timeInSeconds / 3600)).padStart(2, "0"); - const minutes = String(Math.floor((timeInSeconds % 3600) / 60)).padStart(2, "0"); - const seconds = String(timeInSeconds % 60).padStart(2, "0"); - return `${hours}:${minutes}:${seconds}`; - }; - - useEffect(() => { - // Ensure all parameters are parsed correctly - const orbital = parseFloat(surveyorPeriod); - const rad = density !== null && !isNaN(density) ? density : 1; // Default to 1 if density is invalid - const temp = temperature !== null && !isNaN(temperature) ? temperature : 300; // Default to 300 if temperature is invalid - - // Ensure orbital is a valid number, fallback to a default value if invalid - const validOrbital = !isNaN(orbital) ? orbital : 1.5; - - // Calculate biomass score with valid parameters - setBiomassScore(calculateBiomass(temp, rad, validOrbital)); -}, [density, temperature, surveyorPeriod]); // Re-run when these values change - - const MemoizedPlanetGenerator = useMemo(() => { - return classification?.id && classification.author ? ( - // - - ) : null; - }, [classification?.id, classification?.author]); - - useEffect(() => { - if (!params?.id) return; - - const fetchClassifications = async () => { - const response = await fetch(`/api/gameplay/planets/${params.id}`, { cache: "no-store" }); - const payload = await response.json().catch(() => null); - if (!response.ok || !payload?.classification) { - console.error("Error fetching classification", payload?.error || response.statusText); - setError("Failed to fetch classification data"); - return; - } - - const data = payload.classification as Classification; - setClassification(data); - setAnomaly(data.anomaly); - - const periodFromField = data.classificationConfiguration?.additionalFields?.field_0; - if (periodFromField) setPeriod(periodFromField); - - setRelatedClassifications((payload.relatedClassifications ?? []) as Classification[]); - - setLoading(false); - }; - - fetchClassifications(); - }, [session, params?.id]); - - const fetchComments = async () => { - if (!classification) return; - - const response = await fetch( - `/api/gameplay/social/comments?classificationId=${classification.id}&order=desc`, - { cache: "no-store" } - ); - const payload = await response.json().catch(() => null); - const data = payload?.comments; - if (!response.ok || !Array.isArray(data)) { - console.error("Error fetching comments: ", payload?.error || response.statusText); - } else { - let aggregatedComments: any[] = []; - - // Create a map to store the aggregated comments by category and author - const aggregatedMap: Record = {}; - let biomassSum = 0; // Variable to track biomass sum - - data?.forEach((comment) => { - // Create a unique key based on category and author - const key = `${comment.category}-${comment.author}`; - - if (aggregatedMap[key]) { - // If a comment of this type exists, aggregate the values - if (comment.value) { - aggregatedMap[key].value += parseFloat(comment.value) || 0; - } - } else { - // If this is the first time we encounter this comment type and author, just add it - aggregatedMap[key] = { ...comment, value: parseFloat(comment.value) || 0 }; - } - - // Check for "Biomass" comments and aggregate their value - if (comment.category === "Biomass") { - biomassSum += parseFloat(comment.value) || 0; - } - }); - - // Convert the map back to an array - aggregatedComments = Object.values(aggregatedMap); - - // Now, set the aggregated comments to state - setComments(aggregatedComments); - - // Update the biomass score by adding the biomass sum - setBiomassScore(prevScore => prevScore + biomassSum); - - // Extract specific comments after aggregation for individual fields like OrbitalPeriod, PlanetType, etc. - const orbitalPeriodComment = aggregatedComments.find( - (comment) => comment.category === "OrbitalPeriod" && comment.author === classification.author - ); - - const radiusComment = aggregatedComments.find( - (comment) => comment.category === "PlanetType" && comment.author === classification.author - ); - - const temperatureComment = aggregatedComments.find( - (comment) => comment.category === "Temperature" && comment.author === classification.author - ); - - if (orbitalPeriodComment) setSurveyorPeriod(orbitalPeriodComment.value || ""); - if (radiusComment && radiusComment.value) { - const radiusValue = parseFloat(radiusComment.value); - setDensity(!isNaN(radiusValue) ? radiusValue : null); - } - if (temperatureComment && temperatureComment.value) { - const tempValue = parseFloat(temperatureComment.value); - setTemperature(!isNaN(tempValue) ? tempValue : null); - } - }; - }; - - const fetchDensity = async () => { - if (!classification) return; - - const response = await fetch( - `/api/gameplay/social/comments?classificationId=${classification.id}&category=Density&order=desc`, - { cache: "no-store" } - ); - const payload = await response.json().catch(() => null); - const data = payload?.comments; - if (!response.ok || !Array.isArray(data)) { - console.error("Error fetching comments: ", payload?.error || response.statusText); - } else if (data && data.length > 0 && data[0].value) { - const densityValue = parseFloat(data[0].value); - if (!isNaN(densityValue)) { - setDensity(densityValue); - } - }; - }; - - useEffect(() => { - fetchComments(); - fetchDensity(); - }, [classification]); - - useEffect(() => { - const timer = setInterval(() => { - setTimeLeft(prev => { - const time = Math.max(getTimeUntilNextSunday() / 1000, 300); - return Math.floor(time); - }); - }, 1000); - - return () => clearInterval(timer); - }, []); - - const handleViewChange = (view: FocusView) => { - setCurrentView(view) - }; - - const handleBackToMain = () => { - setCurrentView('planet'); - }; - - // Planet generator content - const PlanetComponent = useMemo(() => ( -
- {classification && !loading && MemoizedPlanetGenerator} -
- ), [classification, loading, MemoizedPlanetGenerator]); - - const ClimateComponent = () => ( -
- {/*
-

Classification Configuration

-
-
-                {JSON.stringify(classification?.classificationConfiguration, null, 2)}
-              
-
-
- - {relatedClassifications.length > 0 && ( -
-

Related Classifications

-
- {relatedClassifications.map((rc, index) => ( -
-
-                      {JSON.stringify(rc, null, 2)}
-                    
-
- ))} -
-
- )} */} -
- {(cloudSummary || p4Summary || ai4MSummary) && ( - setDominantBiome(biome)} biomassVersion={biomassScore} /> - )} - -
-
- ); - - const AtmosphereComponent = () => ( -
- {/* {classification?.id !== undefined && ( - - )} */} -
- ); - - const ExplorationComponent = () => ( -
- {classification && ( - - )} -
- ); - - const MapComponent = () => ( -
- -
- ); - - const EditComponent = () => ( -
- -
- ); - - const renderFocusComponent = () => { - switch (currentView) { - case "planet": - return PlanetComponent - case "overview": - return PlanetComponent - case "Climate": - return - case "atmosphere": - return - case "exploration": - return - case "map": - return - case "edit": - return - default: - return PlanetComponent - }; - }; - - return ( -
- {}} - activityFeed={[]} - otherClassifications={[]} - /> -
-
- background -
-
- - {/* {dominantBiome && ( -
- -
- )} */} - -
- {currentView !== "planet" && ( -
- -
- )} - -
-
-
Biomass:
- {biomassScore} -
-
-
Density:
- {density} - g/cm^3 -
-
-
Main biome:
- {dominantBiome} -
- {/*
-
Period:
- {surveyorPeriod} Days {/* // or {period} -
*/} -
- -
- - - {/* */} - - -
- -
- {renderFocusComponent()} -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - - -
-
-
-
- ); -}; diff --git a/src/app/planets/edit/[id]/page.tsx b/src/app/planets/edit/[id]/page.tsx index 7cafad4b9..7fcebc345 100644 --- a/src/app/planets/edit/[id]/page.tsx +++ b/src/app/planets/edit/[id]/page.tsx @@ -5,7 +5,21 @@ import { useSession } from "@/src/lib/auth/session-context"; import Navbar from "@/src/components/layout/Navbar"; import { PostCardSingleWithGeneratorEditMode } from "@/src/components/social/posts/PostWithGen"; -import { Classification } from "../../[id]/page"; +interface Classification { + id: number; + content: string | null; + author: string | null; + anomaly?: { id: number; content: string | null } | null; + media?: (string | { uploadUrl?: string })[] | null; + classificationtype: string | null; + classificationConfiguration?: any; + created_at: string; + title?: string; + votes?: number; + category?: string; + tags?: string[]; + images?: string[]; +}; export default function EditPlanetAnomaly(props: { params: Promise<{ id: string }> }) { const params = use(props.params); diff --git a/src/app/planets/paint/[id]/page.tsx b/src/app/planets/paint/[id]/page.tsx deleted file mode 100644 index ae938e727..000000000 --- a/src/app/planets/paint/[id]/page.tsx +++ /dev/null @@ -1,214 +0,0 @@ -"use client" - -import { useState, useEffect } from "react" -import { useParams, useRouter } from "next/navigation" -import PlanetViewer from "@/src/components/discovery/data-sources/Astronomers/PlanetHunters/planetViewer" -import { type PlanetConfig, defaultPlanetConfig } from "@/src/components/discovery/planets/planet-config" -import SettingsPanel from "@/src/components/discovery/data-sources/Astronomers/PlanetHunters/SettingsPanel" -import { Button } from "@/src/components/ui/button" -import MainHeader from "@/src/components/layout/Header/MainHeader" -import UseDarkMode from "@/src/shared/hooks/useDarkMode" -import { TelescopeBackground } from "@/src/components/classification/telescope/telescope-background" - -export default function PlanetGeneratorPage() { - const router = useRouter() - const params = useParams() - const planetId = params?.id as string | undefined; - - const [planetConfig, setPlanetConfig] = useState(defaultPlanetConfig) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const { isDark, toggleDarkMode } = UseDarkMode() - - const handleConfigChange = (newConfig: Partial) => { - setPlanetConfig((prev) => ({ ...prev, ...newConfig })) - } - - useEffect(() => { - const fetchPlanetData = async () => { - if (!planetId) { - setError("No planet ID provided.") - setLoading(false) - return - } - - try { - const idAsNumber = Number.parseInt(planetId) - if (isNaN(idAsNumber)) { - throw new Error("Invalid planet ID") - } - - const response = await fetch(`/api/gameplay/classifications/${idAsNumber}`, { - cache: "no-store", - }) - const payload = await response.json().catch(() => null) - if (!response.ok || !payload?.classification) { - throw new Error(payload?.error ?? "Failed to load planet data") - } - - const data = payload.classification - - if (!data) { - throw new Error("Planet not found") - } - - let config = data.classificationConfiguration - - // Ensure it's parsed if Supabase returned it as a string - if (typeof config === "string") { - try { - config = JSON.parse(config) - } catch (err) { - console.warn("Failed to parse classificationConfiguration JSON string:", err) - config = {} - } - } - - if (config?.planetConfiguration) { - setPlanetConfig(config.planetConfiguration) - } - - setError(null) - } catch (err) { - console.error("Error fetching planet data:", err) - setError(err instanceof Error ? err.message : "Failed to load planet data") - } finally { - setLoading(false) - } - } - - fetchPlanetData() - }, [planetId]) - - // Hide sidebar for this page - useEffect(() => { - if (typeof window !== "undefined") { - window.dispatchEvent(new CustomEvent("sidebar:collapse", { detail: { collapsed: true } })); - window.localStorage.setItem("hideSidebar", "true"); - } - return () => { - if (typeof window !== "undefined") { - window.localStorage.removeItem("hideSidebar"); - window.dispatchEvent(new CustomEvent("sidebar:collapse", { detail: { collapsed: false } })); - } - }; - }, []); - - if (loading) { - return ( -
-
- {}} - /> -
- {}} - activityFeed={[]} - otherClassifications={[]} - /> -
-
-

Loading planet data...

-
-
- ) - } - - if (error) { - return ( -
-
- {}} - /> -
- {}} - activityFeed={[]} - otherClassifications={[]} - /> -
-

Error

-

{error}

- -
-
- ) - } - - return ( -
-
- {}} - /> -
- {}} - activityFeed={[]} - otherClassifications={[]} - /> -
- {/* Planet Viewer */} -
- -
- - {/* Settings Panel */} -
-
-
-

Planet Settings

- ID: {planetId} -
- -
-
- - -
-
-
-
-
-
- ) -} diff --git a/src/app/research/page.tsx b/src/app/research/page.tsx index aca641f6e..c6caa4efa 100644 --- a/src/app/research/page.tsx +++ b/src/app/research/page.tsx @@ -4,7 +4,7 @@ import React, { Suspense, useEffect, useRef, useState } from "react"; import { useSearchParams } from "next/navigation"; import ActivityHeaderSection from "@/src/components/social/activity/ActivityHeaderSection"; import { Button } from "@/src/components/ui/button"; -import CompactResearchPanel from "@/src/components/research/CompactResearchPanel"; +import CompactResearchPanel from "@/src/features/research/components/CompactResearchPanel"; import ReferralCodePanel from "@/src/components/profile/setup/Referrals"; import UseDarkMode from "@/src/shared/hooks/useDarkMode"; import MainHeader from "@/src/components/layout/Header/MainHeader"; diff --git a/src/app/setup/rover/page.tsx b/src/app/setup/rover/page.tsx index dd7bd503e..8150cdfab 100644 --- a/src/app/setup/rover/page.tsx +++ b/src/app/setup/rover/page.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { SetupCard, SetupScaffold } from "@/src/features/setup/components/SetupScaffold"; +import TutorialWrapper, { ROVER_INTRO_STEPS } from "@/src/components/onboarding/TutorialWrapper"; type RoverSetupPayload = { maxWaypoints?: number; @@ -56,46 +57,54 @@ export default function RoverSetupPage() { }, [isLoading, setup]); return ( - -
- -
    -
  1. Open route planner and place waypoints.
  2. -
  3. Deploy rover to start traversal.
  4. -
  5. Return to Rover Viewport to classify findings at each stop.
  6. -
-
+ +
+ +
    +
  1. Open route planner and place waypoints.
  2. +
  3. Deploy rover to start traversal.
  4. +
  5. Return to Rover Viewport to classify findings at each stop.
  6. +
+
- -

{statusText}

- {setup && ( -
    -
  • Waypoint capacity: {setup.maxWaypoints ?? 4}
  • -
  • Fast deploy active: {setup.isFastDeployEnabled ? "Yes" : "No"}
  • -
- )} - {error &&

{error}

} -
- - -
-
-
-
+ +

{statusText}

+ {setup && ( +
    +
  • Waypoint capacity: {setup.maxWaypoints ?? 4}
  • +
  • Fast deploy active: {setup.isFastDeployEnabled ? "Yes" : "No"}
  • +
+ )} + {error &&

{error}

} +
+ + +
+
+
+
+ ); } diff --git a/src/app/setup/satellite/page.tsx b/src/app/setup/satellite/page.tsx index 80c3292a1..d39f5eefc 100644 --- a/src/app/setup/satellite/page.tsx +++ b/src/app/setup/satellite/page.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { SetupCard, SetupScaffold } from "@/src/features/setup/components/SetupScaffold"; +import TutorialWrapper, { SATELLITE_INTRO_STEPS } from "@/src/components/onboarding/TutorialWrapper"; type PlanetTarget = { id: number; name: string }; type DeployMode = "weather" | "planets" | "p-4"; @@ -93,6 +94,12 @@ export default function SatelliteSetupPage() { } return ( + Target Planet