feat(web): redirect logged-in users from root to /threads (opt-in)#950
feat(web): redirect logged-in users from root to /threads (opt-in)#950AchoArnold wants to merge 10 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6e6a8ddc-a5d6-49dc-99c9-f9b7fcbc00ce
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6e6a8ddc-a5d6-49dc-99c9-f9b7fcbc00ce
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Back button Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6e6a8ddc-a5d6-49dc-99c9-f9b7fcbc00ce
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| ErrorProne | 2 high |
🟢 Metrics 8 complexity · 0 duplication
Metric Results Complexity 8 Duplication 0
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Greptile SummaryThis PR lets logged-in users opt in (per-browser) to skip the marketing landing page and be redirected straight to
Confidence Score: 4/5The change is client-only with no backend impact; the worst-case failure is a redirect that doesn't persist across page reloads in private-mode browsers, with the existing The implementation is well-structured and follows existing patterns. The two issues — a duplicated magic string between the store and the middleware, and navigation proceeding after a failed localStorage write — are both narrow edge cases that affect only private-mode or storage-disabled browsers and degrade gracefully rather than breaking anything.
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant U as User Browser
participant MW as redirectToThreads Middleware
participant LS as localStorage
participant IDX as index.vue (Landing Page)
participant PPOP as RedirectPromptPopover
participant STORE as redirectPreferenceStore
participant THR as /threads
Note over U,THR: First visit (opted-out)
U->>MW: Navigate to /
MW->>LS: getItem('httpsms_redirect_to_threads')
LS-->>MW: null
MW-->>IDX: Proceed (no redirect)
IDX->>PPOP: "Render popover (authUser != null)"
PPOP->>U: Show Skip this page next time?
Note over U,THR: User clicks Always open dashboard
U->>STORE: enable()
STORE->>LS: setItem('httpsms_redirect_to_threads', 'true')
STORE->>THR: "navigateTo('/threads', {replace: true})"
Note over U,THR: Subsequent visit (opted-in)
U->>MW: Navigate to /
MW->>LS: getItem('httpsms_redirect_to_threads')
LS-->>MW: 'true'
MW->>THR: "navigateTo('/threads', {replace: true})"
Note over U,THR: Logout
U->>STORE: resetState()
STORE->>LS: removeItem('httpsms_redirect_to_threads')
STORE-->>U: "enabled=false, dismissedThisSession=false"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant U as User Browser
participant MW as redirectToThreads Middleware
participant LS as localStorage
participant IDX as index.vue (Landing Page)
participant PPOP as RedirectPromptPopover
participant STORE as redirectPreferenceStore
participant THR as /threads
Note over U,THR: First visit (opted-out)
U->>MW: Navigate to /
MW->>LS: getItem('httpsms_redirect_to_threads')
LS-->>MW: null
MW-->>IDX: Proceed (no redirect)
IDX->>PPOP: "Render popover (authUser != null)"
PPOP->>U: Show Skip this page next time?
Note over U,THR: User clicks Always open dashboard
U->>STORE: enable()
STORE->>LS: setItem('httpsms_redirect_to_threads', 'true')
STORE->>THR: "navigateTo('/threads', {replace: true})"
Note over U,THR: Subsequent visit (opted-in)
U->>MW: Navigate to /
MW->>LS: getItem('httpsms_redirect_to_threads')
LS-->>MW: 'true'
MW->>THR: "navigateTo('/threads', {replace: true})"
Note over U,THR: Logout
U->>STORE: resetState()
STORE->>LS: removeItem('httpsms_redirect_to_threads')
STORE-->>U: "enabled=false, dismissedThisSession=false"
Reviews (1): Last reviewed commit: "fix(web): use replace navigation in redi..." | Re-trigger Greptile |
| export default defineNuxtRouteMiddleware(() => { | ||
| try { | ||
| if (localStorage.getItem('httpsms_redirect_to_threads') === 'true') { | ||
| return navigateTo('/threads', { replace: true }) | ||
| } | ||
| } catch (error) { | ||
| console.error(error) | ||
| } | ||
| }) |
There was a problem hiding this comment.
The localStorage key is hardcoded here as a string literal, while the store defines it as a constant
STORAGE_KEY. If the key ever needs to change, this file won't be caught by a simple search-and-replace on the constant, and the redirect middleware will silently stop working while the store continues writing the new key.
| export default defineNuxtRouteMiddleware(() => { | |
| try { | |
| if (localStorage.getItem('httpsms_redirect_to_threads') === 'true') { | |
| return navigateTo('/threads', { replace: true }) | |
| } | |
| } catch (error) { | |
| console.error(error) | |
| } | |
| }) | |
| import { STORAGE_KEY } from '~/stores/redirectPreference' | |
| export default defineNuxtRouteMiddleware(() => { | |
| try { | |
| if (localStorage.getItem(STORAGE_KEY) === 'true') { | |
| return navigateTo('/threads', { replace: true }) | |
| } | |
| } catch (error) { | |
| console.error(error) | |
| } | |
| }) |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| @@ -0,0 +1,46 @@ | |||
| import { defineStore } from 'pinia' | |||
|
|
|||
| const STORAGE_KEY = 'httpsms_redirect_to_threads' | |||
There was a problem hiding this comment.
| function enable() { | ||
| enabled.value = true | ||
| try { | ||
| localStorage.setItem(STORAGE_KEY, 'true') | ||
| } catch (error) { | ||
| console.error(error) | ||
| } | ||
| navigateTo('/threads', { replace: true }) | ||
| } |
There was a problem hiding this comment.
Navigation proceeds even when persistence fails
enabled.value = true is set unconditionally, then localStorage.setItem is attempted. If the write throws (e.g. private-mode quota denial), the catch block is entered but navigateTo('/threads', ...) still runs afterward. The user is taken to /threads for this session, but on their next visit the middleware finds no key in localStorage and the landing page renders again — giving the impression that the "Always open dashboard" action had no lasting effect.
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
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
What
Logged-in users can opt in (per-browser) to skip the marketing landing page and go straight to
/threads./reads alocalStorageflag synchronously and redirects to/threadsbefore the heavy landing page renders./threads' existing auth guard is the correctness backstop.localStorage(keyhttpsms_redirect_to_threads). No backend/API changes.localStorageflag, so a fresh login starts opted-out.Testing
pnpm lint(eslint + stylelint + prettier) — passpnpm run generate— pass (25 routes prerendered, 0 link errors)Design spec and implementation plan are included under
docs/superpowers/(force-added;docs/is gitignored).