diff --git a/neode-ui/src/views/GoalDetail.vue b/neode-ui/src/views/GoalDetail.vue
index f824f067..17de96a7 100644
--- a/neode-ui/src/views/GoalDetail.vue
+++ b/neode-ui/src/views/GoalDetail.vue
@@ -231,7 +231,7 @@ import { useGoalStore } from '@/stores/goals'
import { useAppLauncherStore } from '@/stores/appLauncher'
import { getGoalById, ZEUS_CHANNEL_MIN_SATS } from '@/data/goals'
import type { GoalStep } from '@/types/goals'
-import { goalStepTargetPath } from './goals/goalStepActions'
+import { goalStepRouteOverride } from './goals/goalStepActions'
import BackButton from '@/components/BackButton.vue'
import ReceiveBitcoinModal from '@/components/ReceiveBitcoinModal.vue'
import { rpcClient } from '@/api/rpc-client'
@@ -398,9 +398,16 @@ async function installApp(step: GoalStep) {
function openConfigureStep(step: GoalStep) {
ensureGoalStarted()
goalStore.completeStep(goalId.value, step.id)
- const targetPath = goalStepTargetPath(step)
- if (targetPath) {
- router.push(targetPath)
+ const override = goalStepRouteOverride(step)
+ if (override) {
+ // Internal screens (channels, web5, settings) — tag where we came from so
+ // their back button returns to this wizard.
+ router.push({ path: override, query: { from: 'goal', goal: goalId.value } })
+ } else if (step.appId) {
+ // Launch the app itself: iframe apps overlay on top of the wizard,
+ // tab-only apps open a tab (mobile: the in-app browser) — the app
+ // launcher handles every case.
+ useAppLauncherStore().openSession(step.appId)
}
}
@@ -421,7 +428,8 @@ function ensureGoalStarted() {
}
function goBack() {
- router.push('/dashboard')
+ // The goal cards live on Home's Setup tab — return there, not the dashboard.
+ router.push({ path: '/dashboard', query: { tab: 'setup' } })
}
// ── Fund-wallet step: live sync status + on-chain balance ───────────────────
diff --git a/neode-ui/src/views/Home.vue b/neode-ui/src/views/Home.vue
index e06072f6..d7855cdd 100644
--- a/neode-ui/src/views/Home.vue
+++ b/neode-ui/src/views/Home.vue
@@ -287,7 +287,7 @@
diff --git a/neode-ui/src/views/goals/__tests__/goalStepActions.test.ts b/neode-ui/src/views/goals/__tests__/goalStepActions.test.ts
index c948ef72..45e22ad5 100644
--- a/neode-ui/src/views/goals/__tests__/goalStepActions.test.ts
+++ b/neode-ui/src/views/goals/__tests__/goalStepActions.test.ts
@@ -1,29 +1,37 @@
import { describe, expect, it } from 'vitest'
import { GOALS } from '@/data/goals'
-import { goalStepTargetPath } from '../goalStepActions'
+import { goalStepRouteOverride } from '../goalStepActions'
import type { GoalStep } from '@/types/goals'
describe('goalStepActions', () => {
- it('routes app-backed steps to their app details page', () => {
- expect(goalStepTargetPath(step({ id: 'configure-filebrowser', appId: 'filebrowser' }))).toBe('/dashboard/apps/filebrowser')
+ it('app-backed configure steps have no route override — they launch the app itself', () => {
+ expect(goalStepRouteOverride(step({ id: 'configure-filebrowser', appId: 'filebrowser' }))).toBeNull()
+ expect(goalStepRouteOverride(step({ id: 'configure-store', appId: 'btcpay-server' }))).toBeNull()
})
it('routes built-in identity and backup steps to their owning screens', () => {
- expect(goalStepTargetPath(step({ id: 'setup-nostr' }))).toBe('/dashboard/web5')
- expect(goalStepTargetPath(step({ id: 'export-identity' }))).toBe('/dashboard/web5/credentials')
- expect(goalStepTargetPath(step({ id: 'create-passphrase' }))).toBe('/dashboard/settings')
+ expect(goalStepRouteOverride(step({ id: 'setup-nostr' }))).toBe('/dashboard/web5')
+ expect(goalStepRouteOverride(step({ id: 'export-identity' }))).toBe('/dashboard/web5/credentials')
+ expect(goalStepRouteOverride(step({ id: 'create-passphrase' }))).toBe('/dashboard/settings')
+ })
+
+ it('routes channel steps to the Lightning channels screen', () => {
+ expect(goalStepRouteOverride(step({ id: 'open-channel' }))).toBe('/dashboard/apps/lnd/channels')
+ expect(goalStepRouteOverride(step({ id: 'open-channels' }))).toBe('/dashboard/apps/lnd/channels')
+ expect(goalStepRouteOverride(step({ id: 'open-zeus-channel' }))).toBe('/dashboard/apps/lnd/channels')
})
it('keeps passive info steps without a target route', () => {
- expect(goalStepTargetPath(step({ id: 'sync-setup' }))).toBeNull()
+ expect(goalStepRouteOverride(step({ id: 'sync-setup' }))).toBeNull()
})
- it('gives every configure step in the shipped goals a destination', () => {
+ it('gives every shipped configure step a destination — a route override or an app to launch', () => {
const configureSteps = GOALS.flatMap((goal) => goal.steps.filter((candidate) => candidate.action === 'configure'))
- expect(configureSteps.map((candidate) => [candidate.id, goalStepTargetPath(candidate)])).toEqual(
- configureSteps.map((candidate) => [candidate.id, expect.any(String)]),
- )
+ for (const candidate of configureSteps) {
+ const destination = goalStepRouteOverride(candidate) ?? candidate.appId ?? null
+ expect(destination, `configure step ${candidate.id} has no destination`).not.toBeNull()
+ }
})
})
diff --git a/neode-ui/src/views/goals/goalStepActions.ts b/neode-ui/src/views/goals/goalStepActions.ts
index 136e6bb4..647c765e 100644
--- a/neode-ui/src/views/goals/goalStepActions.ts
+++ b/neode-ui/src/views/goals/goalStepActions.ts
@@ -1,5 +1,6 @@
import type { GoalStep } from '@/types/goals'
+// Steps that land on an internal screen rather than launching an app UI.
const STEP_ROUTE_OVERRIDES: Record = {
'setup-nostr': '/dashboard/web5',
'export-identity': '/dashboard/web5/credentials',
@@ -13,9 +14,11 @@ const STEP_ROUTE_OVERRIDES: Record = {
'open-zeus-channel': '/dashboard/apps/lnd/channels',
}
-export function goalStepTargetPath(step: GoalStep): string | null {
- const override = STEP_ROUTE_OVERRIDES[step.id]
- if (override) return override
- if (step.appId) return `/dashboard/apps/${step.appId}`
- return null
+/**
+ * Internal route a step navigates to, or null when the step should launch its
+ * app instead (via the app launcher — iframe apps overlay on top, tab-only
+ * apps open a tab / the mobile in-app browser).
+ */
+export function goalStepRouteOverride(step: GoalStep): string | null {
+ return STEP_ROUTE_OVERRIDES[step.id] ?? null
}