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: Make rsf-design independently handle login, auth state, user info, menu loading, dynamic routing, homepage, and button permissions against the existing rsf-server APIs while rsf-admin remains available as the old entry.
Architecture: Replace the Art Design Pro template auth/menu assumptions with the real rsf-admin protocol. Introduce one backend-menu adapter that converts /auth/menu payloads into the route shape expected by the existing dynamic router, and keep unpublished resources out of the new menu entirely. Store token, user info, roles, and button permissions in the existing Pinia stores and drive guards/directives from that single source of truth.
Tech Stack: Vue 3, Vite, Pinia, Vue Router, Element Plus, Art Design Pro, Axios, Node node:test, pnpm
rsf-design/src/router/adapters/backendMenuAdapter.jsrsf-design/tests/backend-menu-adapter.test.mjsrsf-design/tests/auth-contract.test.mjsrsf-design/package.jsonrsf-design/src/api/auth.jsrsf-design/src/store/modules/user.jsrsf-design/src/views/auth/login/index.vuersf-design/src/router/core/MenuProcessor.jsrsf-design/src/router/core/ComponentLoader.jsrsf-design/src/router/guards/beforeEach.jsrsf-design/src/hooks/core/useAuth.jsrsf-design/src/directives/core/auth.jsrsf-design/src/directives/core/roles.jsrsf-design/src/config/index.jsrsf-design/src/views/dashboard/console/index.vuersf-admin/src/api/auth/index.js, rsf-admin/src/page/ResourceContent.js, docs/superpowers/specs/2026-03-28-rsf-design-replace-rsf-admin-design.mdFiles:
- Modify: rsf-design/package.json
- Create: rsf-design/tests/auth-contract.test.mjs
- Create: rsf-design/tests/backend-menu-adapter.test.mjs
import assert from 'node:assert/strict'
import test from 'node:test'
import { buildLoginPayload, normalizeLoginResponse } from '../src/api/auth.js'
test('buildLoginPayload matches the rsf-server login contract', () => {
assert.deepEqual(buildLoginPayload({ username: 'demo', password: '123456' }), {
username: 'demo',
password: '123456'
})
})
test('normalizeLoginResponse extracts the real token fields', () => {
assert.deepEqual(
normalizeLoginResponse({
code: 200,
data: { accessToken: 'abc', user: { id: 1 } }
}),
{ accessToken: 'abc', refreshToken: '', user: { id: 1 } }
)
})
Run: node --test rsf-design/tests/auth-contract.test.mjs
Expected: FAIL because buildLoginPayload and normalizeLoginResponse do not exist yet.
import assert from 'node:assert/strict'
import test from 'node:test'
import { adaptBackendMenuTree } from '../src/router/adapters/backendMenuAdapter.js'
test('adapts a backend menu leaf into a routable phase-1 page', () => {
const result = adaptBackendMenuTree([
{
id: 10,
path: 'system',
name: 'System',
children: [
{ id: 11, path: 'user-login', component: 'userLogin', title: '登录日志' }
]
}
])
assert.equal(result[0].children[0].component, '/system/user-login')
})
Run: node --test rsf-design/tests/backend-menu-adapter.test.mjs
Expected: FAIL because the adapter module does not exist yet.
{
"scripts": {
"test": "node --test tests"
}
}
rsf-design rootRun: pnpm test
Expected: FAIL, but the command should execute the new test files instead of erroring due to a missing script.
git add rsf-design/package.json rsf-design/tests/auth-contract.test.mjs rsf-design/tests/backend-menu-adapter.test.mjs
git commit -m "test: add rsf-design phase-1 protocol tests"
Files:
- Modify: rsf-design/src/api/auth.js
- Modify: rsf-design/src/store/modules/user.js
- Test: rsf-design/tests/auth-contract.test.mjs
test('normalizeUserInfo returns roles and buttons arrays safely', () => {
assert.deepEqual(
normalizeUserInfo({ userId: 1, roles: ['R_ADMIN'], buttons: ['add'] }),
{ userId: 1, roles: ['R_ADMIN'], buttons: ['add'] }
)
})
Run: node --test rsf-design/tests/auth-contract.test.mjs
Expected: FAIL with missing exports or assertion mismatch.
export function buildLoginPayload({ username, password }) {
return { username, password }
}
export function normalizeLoginResponse(payload) {
return {
accessToken: payload?.data?.accessToken || '',
refreshToken: payload?.data?.refreshToken || '',
user: payload?.data?.user || {}
}
}
export function normalizeUserInfo(data) {
return {
...data,
roles: Array.isArray(data?.roles) ? data.roles : [],
buttons: Array.isArray(data?.buttons) ? data.buttons : []
}
}
function fetchLogin(params) {
return request.post({ url: '/login', params: buildLoginPayload(params) })
}
function fetchGetUserInfo() {
return request.get({ url: '/auth/user' })
}
function fetchGetMenuList() {
return request.get({ url: '/auth/menu' })
}
const setToken = (newAccessToken, newRefreshToken = '') => {
accessToken.value = newAccessToken || ''
refreshToken.value = newRefreshToken || ''
}
Run: node --test rsf-design/tests/auth-contract.test.mjs
Expected: PASS.
git add rsf-design/src/api/auth.js rsf-design/src/store/modules/user.js rsf-design/tests/auth-contract.test.mjs
git commit -m "feat: align rsf-design auth api with rsf-server"
Files:
- Modify: rsf-design/src/views/auth/login/index.vue
- Modify: rsf-design/src/config/index.js
- Test: rsf-design/tests/auth-contract.test.mjs
test('normalizeLoginResponse accepts the old backend accessToken shape', () => {
const result = normalizeLoginResponse({
code: 200,
data: { accessToken: 'token-1', refreshToken: '', user: { username: 'admin' } }
})
assert.equal(result.accessToken, 'token-1')
})
Run: node --test rsf-design/tests/auth-contract.test.mjs
Expected: PASS if the previous task is complete. Use this as a safety baseline before editing the login page.
const formData = reactive({
username: '',
password: '',
rememberPassword: true
})
const handleSubmit = async () => {
const payload = normalizeLoginResponse(await fetchLogin(formData))
userStore.setToken(payload.accessToken, payload.refreshToken)
userStore.setLoginStatus(true)
if (payload.user && Object.keys(payload.user).length > 0) userStore.setUserInfo(payload.user)
router.push(route.query.redirect || '/')
}
systemInfo: {
name: 'RSF Design'
}
Run: pnpm lint rsf-design/src/views/auth/login/index.vue rsf-design/src/config/index.js
Expected: PASS, or no syntax errors if the lint command needs to be run as pnpm lint.
Run: pnpm --dir rsf-design build
Expected: PASS.
git add rsf-design/src/views/auth/login/index.vue rsf-design/src/config/index.js
git commit -m "feat: rebuild rsf-design login flow for rsf-server"
Files:
- Create: rsf-design/src/router/adapters/backendMenuAdapter.js
- Modify: rsf-design/src/router/core/MenuProcessor.js
- Modify: rsf-design/src/router/core/ComponentLoader.js
- Test: rsf-design/tests/backend-menu-adapter.test.mjs
test('drops unpublished resources from the phase-1 menu tree', () => {
const result = adaptBackendMenuTree([
{
id: 1,
path: 'system',
children: [
{ id: 2, path: 'user-login', component: 'userLogin', title: '登录日志' },
{ id: 3, path: 'host', component: 'host', title: '主机管理' }
]
}
])
assert.equal(result[0].children.length, 1)
assert.equal(result[0].children[0].meta.title, '登录日志')
})
Run: node --test rsf-design/tests/backend-menu-adapter.test.mjs
Expected: FAIL.
const PHASE_1_COMPONENTS = {
console: '/dashboard/console',
user: '/system/user',
role: '/system/role',
menu: '/system/menu',
userLogin: '/system/user-login'
}
export function adaptBackendMenuTree(menuTree) {
// normalize ids, names, meta, child paths, and component mappings here
}
MenuProcessor to fetch /auth/menu through the auth API and hand off to the adapterasync processBackendMenu() {
const list = await fetchGetMenuList()
return adaptBackendMenuTree(list)
}
ComponentLoader treat adapted component strings as final Vue view paths onlyload(componentPath) {
const fullPath = `../../views${componentPath}.vue`
const fullPathWithIndex = `../../views${componentPath}/index.vue`
// no resource-name guessing here
}
Run: node --test rsf-design/tests/backend-menu-adapter.test.mjs
Expected: PASS.
git add rsf-design/src/router/adapters/backendMenuAdapter.js rsf-design/src/router/core/MenuProcessor.js rsf-design/src/router/core/ComponentLoader.js rsf-design/tests/backend-menu-adapter.test.mjs
git commit -m "feat: adapt backend menus for rsf-design dynamic routing"
Files:
- Modify: rsf-design/src/router/guards/beforeEach.js
- Modify: rsf-design/src/hooks/core/useAuth.js
- Modify: rsf-design/src/directives/core/auth.js
- Modify: rsf-design/src/directives/core/roles.js
- Modify: rsf-design/src/views/dashboard/console/index.vue
test('normalizeUserInfo preserves buttons for permission checks', () => {
const result = normalizeUserInfo({ buttons: ['add', 'edit'] })
assert.deepEqual(result.buttons, ['add', 'edit'])
})
Run: node --test rsf-design/tests/auth-contract.test.mjs
Expected: PASS before editing permission consumers.
const data = normalizeUserInfo(await fetchGetUserInfo())
userStore.setUserInfo(data)
const menuList = await menuProcessor.getMenuList()
useAuth and directives read from store-backed user info and current route meta consistentlyconst backendAuthList = Array.isArray(route.meta.authList) ? route.meta.authList : []
const frontendAuthList = Array.isArray(info.value?.buttons) ? info.value.buttons : []
<template>
<div class="art-full-height">
<ElRow :gutter="20" class="mb-5">
<ElCol :xs="24" :md="8">
<ArtStatsCard title="当前阶段" count="1" description="运行骨架替换" icon="ri:rocket-line" />
</ElCol>
</ElRow>
</div>
</template>
Run: pnpm --dir rsf-design test
Expected: PASS.
Run: pnpm --dir rsf-design build
Expected: PASS.
git add rsf-design/src/router/guards/beforeEach.js rsf-design/src/hooks/core/useAuth.js rsf-design/src/directives/core/auth.js rsf-design/src/directives/core/roles.js rsf-design/src/views/dashboard/console/index.vue
git commit -m "feat: wire rsf-design phase-1 auth, permissions, and homepage"
Files:
- Modify: docs/superpowers/specs/2026-03-28-rsf-design-replace-rsf-admin-design.md (only if implementation changes the agreed contract)
- Verify: rsf-design/.env
- Verify: rsf-design/vite.config.js
Run: Get-Content rsf-design/.env
Expected: VITE_ACCESS_MODE = backend
Run: pnpm --dir rsf-design dev
Expected: Vite starts successfully and prints the local URL.
Checklist:
- Login with a real rsf-server account
- Refresh after login and verify the menu rebuilds
- Visit a protected route directly and verify it resolves after route registration
- Verify hidden buttons remain hidden for a low-permission account
- Verify an unpublished resource does not appear in the new menu
- Verify rsf-admin still runs from its old entry unchanged
Run: pnpm --dir rsf-design test && pnpm --dir rsf-design build
Expected: PASS.
git add rsf-design/.env rsf-design/vite.config.js
git commit -m "chore: verify rsf-design phase-1 replacement readiness"