From 888d6af3dc99f40ba5c2a03fe6e211fcfa451710 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Mon, 13 Jul 2026 22:48:15 +0300 Subject: [PATCH 01/10] docs: add design spec for root->threads redirect Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6e6a8ddc-a5d6-49dc-99c9-f9b7fcbc00ce --- ...6-07-13-redirect-root-to-threads-design.md | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-13-redirect-root-to-threads-design.md diff --git a/docs/superpowers/specs/2026-07-13-redirect-root-to-threads-design.md b/docs/superpowers/specs/2026-07-13-redirect-root-to-threads-design.md new file mode 100644 index 00000000..8c252045 --- /dev/null +++ b/docs/superpowers/specs/2026-07-13-redirect-root-to-threads-design.md @@ -0,0 +1,134 @@ +# Redirect logged-in users from root to /threads — Design + +Date: 2026-07-13 +Component: `web/` (Nuxt 4 client-only SPA) + +## Problem + +When a logged-in user visits the root landing page (`/`), they currently see +the full marketing page with a "Dashboard" button that links to `/threads`. +Returning users generally want to go straight to their dashboard. We want an +opt-in, per-browser "always skip the landing page" behavior that redirects them +to `/threads` directly — with no backend changes and fast page loads. + +## Goals + +- Logged-in users who have opted in on a given browser are redirected from `/` + to `/threads` immediately, before the heavy landing page renders. +- The opt-in is discoverable via a persistent popover under the "Dashboard" + button, modeled on the reference UX: a card reading "Skip this page next + time?" with a ✕ dismiss control and an "Always open dashboard →" link. +- Purely client-side / per-browser (localStorage). No API or backend changes. +- On logout, the preference is fully cleared (store + localStorage). + +## Non-goals + +- No server-side persistence or cross-device sync. +- No change to `/threads` or its auth guard behavior. +- No redirect on any page other than `/`. + +## Context (current state) + +- `web/` is a client-only Nuxt 4 SPA (`ssr: false`); Firebase resolves auth + state on the client and populates `useAuthStore()` — `authUser` (nullable) + and `authStateChanged` (bool). +- The **Dashboard** button lives in `web/app/layouts/website.vue`, shown when + `authStore.authUser !== null`, linking to `{ name: 'threads' }`. +- Root `/` (`web/app/pages/index.vue`) uses the `website` layout. +- `/threads` already has an `auth` middleware that waits for + `authStateChanged` and bounces unauthenticated users to `/login`. +- Existing localStorage convention: keys prefixed `httpsms_` (e.g. + `httpsms_last_login_method` in `web/app/components/FirebaseAuth.vue`), all + access wrapped in `try/catch`. +- Logout is triggered in `web/app/components/MessageThreadHeader.vue` and + `web/app/pages/settings/index.vue`, both calling `authStore.resetState()`, + `phonesStore.resetState()`, and `threadsStore.resetState()`. + +## Design + +### 1. Preference store — `web/app/stores/redirectPreference.ts` + +A dedicated Pinia store (setup style, matching existing stores) owning the +per-browser preference. + +- localStorage key: `httpsms_redirect_to_threads` (value `'true'` when enabled). +- State: + - `enabled: Ref` — hydrated from localStorage on store creation. + - `dismissedThisSession: Ref` — in-memory only, defaults `false`. +- Actions: + - `enable()` — set `enabled = true`, write localStorage, then + `navigateTo('/threads')` so the click also takes the user to the dashboard. + - `dismiss()` — set `dismissedThisSession = true` (no persistence). + - `resetState()` — set `enabled = false`, clear `dismissedThisSession`, and + remove the localStorage key. +- All localStorage reads/writes wrapped in `try/catch` (mirroring + `FirebaseAuth.vue`) so private-mode / disabled storage never throws. + +### 2. Optimistic redirect — page middleware on `index.vue` + +- A page-scoped middleware (via `definePageMeta({ middleware: [...] })` on + `index.vue`) reads `localStorage['httpsms_redirect_to_threads']` + **synchronously**. If truthy, it calls + `navigateTo('/threads', { replace: true })` and returns. +- Runs before the landing page renders and does **not** wait for Firebase, so + the redirect is immediate (optimistic). If the session turns out to be + invalid, `/threads`' existing `auth` guard redirects to `/login`. +- localStorage access wrapped in `try/catch`; on any error, fall through and + render the landing page normally. + +### 3. Popover component — rendered in `website.vue` + +A new component (e.g. `web/app/components/RedirectPromptPopover.vue`) anchored +under the Dashboard button in the app bar. + +- Implemented with a Vuetify `v-menu` / positioned card (persistent, not + hover-triggered) matching the reference: line 1 "Skip this page next time?" + with a trailing ✕ icon button; line 2 an "Always open dashboard →" link. +- Visible only when **all** hold: + - current route is `/` (root), + - `authStore.authUser !== null`, + - `!redirectPreferenceStore.enabled`, + - `!redirectPreferenceStore.dismissedThisSession`. +- ✕ button → `redirectPreferenceStore.dismiss()` (hidden for the session, + reappears next visit until opt-in). +- "Always open dashboard →" link → `redirectPreferenceStore.enable()`. +- Link carries `text-decoration-none hover:text-decoration-underline` per repo + convention. + +### 4. Logout wiring + +At each existing logout site (`MessageThreadHeader.vue`, +`settings/index.vue`), add `redirectPreferenceStore.resetState()` alongside the +existing `resetState()` calls, so logout clears both the in-memory value and +the persisted localStorage flag. A fresh login then starts opted-out. + +## Data flow + +1. User (logged in, opted out) lands on `/` → sees landing page + popover. +2. Clicks "Always open dashboard →" → `enable()` writes localStorage + + navigates to `/threads`. +3. Next visit to `/` → page middleware reads the flag synchronously → + `navigateTo('/threads', { replace: true })` before render. +4. User logs out → `resetState()` clears store + localStorage → next `/` visit + shows the landing page again (no redirect). + +## Error handling + +- All localStorage access is guarded with `try/catch`; failures degrade + gracefully to "not opted in" (landing page renders, popover may show). +- Optimistic redirect relies on `/threads`' existing auth guard as the + correctness backstop for stale/invalid sessions. + +## Testing + +- Follow existing Jest patterns in `web/` where present. At minimum, manual + verification of: opt-in via popover, redirect on next `/` visit, ✕ session + dismissal, and full clear on logout. Add unit coverage for the preference + store's `enable`/`dismiss`/`resetState` and hydration if the repo's test + setup supports Pinia stores. + +## Rollout / isolation + +- All work performed in a git worktree off `main` + (`../httpsms-threads-redirect`, branch `feat/redirect-to-threads`), isolated + from the current `AchoArnold/affiliates-page` branch. From dfadea65260ef77a85db66cb3e691946b8dab9cf Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Mon, 13 Jul 2026 22:56:50 +0300 Subject: [PATCH 02/10] docs: add implementation plan for root->threads redirect Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6e6a8ddc-a5d6-49dc-99c9-f9b7fcbc00ce --- .../2026-07-13-redirect-root-to-threads.md | 407 ++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-13-redirect-root-to-threads.md diff --git a/docs/superpowers/plans/2026-07-13-redirect-root-to-threads.md b/docs/superpowers/plans/2026-07-13-redirect-root-to-threads.md new file mode 100644 index 00000000..0f928cfa --- /dev/null +++ b/docs/superpowers/plans/2026-07-13-redirect-root-to-threads.md @@ -0,0 +1,407 @@ +# Root → /threads Redirect Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Let logged-in users opt in (per-browser) to be redirected from the root landing page (`/`) straight to `/threads`, surfaced via a persistent popover under the "Dashboard" button. + +**Architecture:** A dedicated Pinia store owns a localStorage-backed preference. A page-scoped middleware on `index.vue` reads the flag synchronously and redirects before render (optimistic; `/threads`' existing auth guard is the correctness backstop). A popover component in the `website` layout drives opt-in/dismiss. Logout clears store + localStorage. + +**Tech Stack:** Nuxt 4 (client-only SPA, `ssr: false`), Vue 3 ` + + + + +``` + +Notes: `v-btn` close control uses `color="warning"` per repo convention. The card is absolutely positioned under its anchor (Task 4 wraps it in a relatively-positioned container). `computed`, `useRoute` auto-imported. + +- [ ] **Step 2: Lint** + +Run: `cd web && pnpm lint:js && pnpm lint:style` +Expected: PASS. + +- [ ] **Step 3: Commit** + +```bash +git add web/app/components/RedirectPromptPopover.vue +git commit -m "feat(web): add redirect opt-in popover component" +``` + +--- + +### Task 4: Mount the popover under the Dashboard button + +**Files:** +- Modify: `web/app/layouts/website.vue` (around lines 103-112, the Dashboard `v-btn`) + +**Interfaces:** +- Consumes: `` (Task 3). Nuxt auto-imports components from `web/app/components`, so no explicit import is needed. +- Produces: the popover rendered relative to the Dashboard button. + +- [ ] **Step 1: Wrap the Dashboard button with a positioned container hosting the popover** + +In `web/app/layouts/website.vue`, replace the existing Dashboard button block: + +```vue + + Dashboard + +``` + +with: + +```vue +
+ + Dashboard + + +
+``` + +Note: `position-relative` (Vuetify utility) makes the popover's `position: absolute` anchor to this container. + +- [ ] **Step 2: Lint** + +Run: `cd web && pnpm lint:js && pnpm lint:style` +Expected: PASS. + +- [ ] **Step 3: Manual verification** + +Run: `cd web && pnpm dev`. Log in, then visit `/`. +Expected: the "Skip this page next time?" popover appears under the Dashboard button. Clicking ✕ hides it (reappears after reload). Clicking "Always open dashboard →" navigates to `/threads`, and a subsequent visit to `/` auto-redirects. Stop the dev server when done. + +- [ ] **Step 4: Commit** + +```bash +git add web/app/layouts/website.vue +git commit -m "feat(web): mount redirect opt-in popover under Dashboard button" +``` + +--- + +### Task 5: Clear the preference on logout + +**Files:** +- Modify: `web/app/components/MessageThreadHeader.vue:68-73` (the `logout` function) +- Modify: `web/app/pages/settings/index.vue:773-775` (the sign-out handler) + +**Interfaces:** +- Consumes: `useRedirectPreferenceStore()` (`resetState`). +- Produces: nothing new. + +- [ ] **Step 1: Wire reset into MessageThreadHeader logout** + +In `web/app/components/MessageThreadHeader.vue`, first ensure the store instance exists near the other store instances in ` + + + + From 753d9993f4d2a5c5c1a8197ce4d0dc68dae4cfcb Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Mon, 13 Jul 2026 23:13:42 +0300 Subject: [PATCH 06/10] feat(web): mount redirect opt-in popover under Dashboard button --- web/app/layouts/website.vue | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/web/app/layouts/website.vue b/web/app/layouts/website.vue index 1aadb1b6..bc644931 100644 --- a/web/app/layouts/website.vue +++ b/web/app/layouts/website.vue @@ -100,16 +100,21 @@ function goToPricing() { Get Started  For Free - - Dashboard - + + Dashboard + + + From 01605f7e57e7d960a426d17633f2441bc3a1cce1 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Mon, 13 Jul 2026 23:16:42 +0300 Subject: [PATCH 07/10] feat(web): clear threads-redirect preference on logout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- web/app/components/MessageThreadHeader.vue | 2 ++ web/app/pages/settings/index.vue | 2 ++ 2 files changed, 4 insertions(+) diff --git a/web/app/components/MessageThreadHeader.vue b/web/app/components/MessageThreadHeader.vue index 6d13a7e2..1e1a81eb 100644 --- a/web/app/components/MessageThreadHeader.vue +++ b/web/app/components/MessageThreadHeader.vue @@ -27,6 +27,7 @@ const phonesStore = usePhonesStore() const threadsStore = useThreadsStore() const appStore = useAppStore() const notificationsStore = useNotificationsStore() +const redirectPreferenceStore = useRedirectPreferenceStore() const selectedMenuItem = ref(-1) @@ -71,6 +72,7 @@ async function logout() { authStore.resetState() phonesStore.resetState() threadsStore.resetState() + redirectPreferenceStore.resetState() notificationsStore.addNotification({ type: 'info', message: 'You have successfully logged out', diff --git a/web/app/pages/settings/index.vue b/web/app/pages/settings/index.vue index 17e35c7c..9aa28d5f 100644 --- a/web/app/pages/settings/index.vue +++ b/web/app/pages/settings/index.vue @@ -47,6 +47,7 @@ const authStore = useAuthStore() const phonesStore = usePhonesStore() const billingStore = useBillingStore() const notificationsStore = useNotificationsStore() +const redirectPreferenceStore = useRedirectPreferenceStore() const firebaseUser = ref(null) const gravatarUrl = ref(null) @@ -773,6 +774,7 @@ async function deleteUserAccount() { await signOut(auth) authStore.resetState() phonesStore.resetState() + redirectPreferenceStore.resetState() notificationsStore.addNotification({ type: 'info', message: 'You have successfully logged out', From d0af493c7eb8821b4d7ef2e0016975b20360d735 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Mon, 13 Jul 2026 23:22:52 +0300 Subject: [PATCH 08/10] fix(web): use replace navigation in redirect enable() to avoid stuck Back button Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6e6a8ddc-a5d6-49dc-99c9-f9b7fcbc00ce --- web/app/stores/redirectPreference.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/stores/redirectPreference.ts b/web/app/stores/redirectPreference.ts index bd2ad8ba..b8f4d4d5 100644 --- a/web/app/stores/redirectPreference.ts +++ b/web/app/stores/redirectPreference.ts @@ -24,7 +24,7 @@ export const useRedirectPreferenceStore = defineStore( } catch (error) { console.error(error) } - navigateTo('/threads') + navigateTo('/threads', { replace: true }) } function dismiss() { From 6cfe7e7487a6142689f4be25246bc1aa2929a357 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Mon, 13 Jul 2026 23:36:59 +0300 Subject: [PATCH 09/10] ci(web): pin pnpm to 11.11.0 to avoid installer bug pnpm 11.12.0 crashes pnpm/action-setup (pnpm/action-setup#276). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6e6a8ddc-a5d6-49dc-99c9-f9b7fcbc00ce --- .github/workflows/web.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 350bf325..fd5ea84e 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -34,7 +34,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: - version: 11 + version: 11.11.0 - name: Install dependencies 📦 run: pnpm install @@ -66,7 +66,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: - version: 11 + version: 11.11.0 - name: Install dependencies 📦 run: pnpm install From 935a985d7a5c431db1b4a5b3310359bfe56b9001 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Mon, 13 Jul 2026 23:41:16 +0300 Subject: [PATCH 10/10] fix(web): import ref from vue, drop gitignored docs Resolves Codacy type findings; removes gitignored docs/ files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6e6a8ddc-a5d6-49dc-99c9-f9b7fcbc00ce --- .../2026-07-13-redirect-root-to-threads.md | 407 ------------------ ...6-07-13-redirect-root-to-threads-design.md | 134 ------ web/app/stores/redirectPreference.ts | 1 + 3 files changed, 1 insertion(+), 541 deletions(-) delete mode 100644 docs/superpowers/plans/2026-07-13-redirect-root-to-threads.md delete mode 100644 docs/superpowers/specs/2026-07-13-redirect-root-to-threads-design.md diff --git a/docs/superpowers/plans/2026-07-13-redirect-root-to-threads.md b/docs/superpowers/plans/2026-07-13-redirect-root-to-threads.md deleted file mode 100644 index 0f928cfa..00000000 --- a/docs/superpowers/plans/2026-07-13-redirect-root-to-threads.md +++ /dev/null @@ -1,407 +0,0 @@ -# Root → /threads Redirect Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Let logged-in users opt in (per-browser) to be redirected from the root landing page (`/`) straight to `/threads`, surfaced via a persistent popover under the "Dashboard" button. - -**Architecture:** A dedicated Pinia store owns a localStorage-backed preference. A page-scoped middleware on `index.vue` reads the flag synchronously and redirects before render (optimistic; `/threads`' existing auth guard is the correctness backstop). A popover component in the `website` layout drives opt-in/dismiss. Logout clears store + localStorage. - -**Tech Stack:** Nuxt 4 (client-only SPA, `ssr: false`), Vue 3 ` - - - - -``` - -Notes: `v-btn` close control uses `color="warning"` per repo convention. The card is absolutely positioned under its anchor (Task 4 wraps it in a relatively-positioned container). `computed`, `useRoute` auto-imported. - -- [ ] **Step 2: Lint** - -Run: `cd web && pnpm lint:js && pnpm lint:style` -Expected: PASS. - -- [ ] **Step 3: Commit** - -```bash -git add web/app/components/RedirectPromptPopover.vue -git commit -m "feat(web): add redirect opt-in popover component" -``` - ---- - -### Task 4: Mount the popover under the Dashboard button - -**Files:** -- Modify: `web/app/layouts/website.vue` (around lines 103-112, the Dashboard `v-btn`) - -**Interfaces:** -- Consumes: `` (Task 3). Nuxt auto-imports components from `web/app/components`, so no explicit import is needed. -- Produces: the popover rendered relative to the Dashboard button. - -- [ ] **Step 1: Wrap the Dashboard button with a positioned container hosting the popover** - -In `web/app/layouts/website.vue`, replace the existing Dashboard button block: - -```vue - - Dashboard - -``` - -with: - -```vue -
- - Dashboard - - -
-``` - -Note: `position-relative` (Vuetify utility) makes the popover's `position: absolute` anchor to this container. - -- [ ] **Step 2: Lint** - -Run: `cd web && pnpm lint:js && pnpm lint:style` -Expected: PASS. - -- [ ] **Step 3: Manual verification** - -Run: `cd web && pnpm dev`. Log in, then visit `/`. -Expected: the "Skip this page next time?" popover appears under the Dashboard button. Clicking ✕ hides it (reappears after reload). Clicking "Always open dashboard →" navigates to `/threads`, and a subsequent visit to `/` auto-redirects. Stop the dev server when done. - -- [ ] **Step 4: Commit** - -```bash -git add web/app/layouts/website.vue -git commit -m "feat(web): mount redirect opt-in popover under Dashboard button" -``` - ---- - -### Task 5: Clear the preference on logout - -**Files:** -- Modify: `web/app/components/MessageThreadHeader.vue:68-73` (the `logout` function) -- Modify: `web/app/pages/settings/index.vue:773-775` (the sign-out handler) - -**Interfaces:** -- Consumes: `useRedirectPreferenceStore()` (`resetState`). -- Produces: nothing new. - -- [ ] **Step 1: Wire reset into MessageThreadHeader logout** - -In `web/app/components/MessageThreadHeader.vue`, first ensure the store instance exists near the other store instances in `