diff --git a/docs/superpowers/plans/2026-07-21-g01-role-shortcut.md b/docs/superpowers/plans/2026-07-21-g01-role-shortcut.md new file mode 100644 index 0000000..4a320bf --- /dev/null +++ b/docs/superpowers/plans/2026-07-21-g01-role-shortcut.md @@ -0,0 +1,166 @@ +# G01 Role And Shortcut State 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:** Make G01 display the selected genealogy's real role and hide the application-review shortcut for ordinary members. + +**Architecture:** Keep `membership` on the selected genealogy as the sole role source. Derive the role label and visible shortcut list inside G01 so initial render and switcher changes share the same reactive rule. + +**Tech Stack:** Vue 3 Composition API, UniApp, PowerShell contract tests, existing G01 browser runtime smoke, MuMu Android manual verification. + +## Global Constraints + +- `membership === "created"` displays “管理员” and four shortcuts. +- `membership === "joined"` displays “成员” and only “世系图、成员、字辈诗”. +- Do not add a replacement shortcut or an invisible application-review hit target for members. +- Preserve the existing switcher, list scroll reset, assets, layout, and routes. +- MuMu screenshots are the visual acceptance source; browser runtime is logic regression evidence only. + +--- + +### Task 1: Drive G01 Role And Shortcuts From Membership + +**Files:** +- Create: `tests/g01-role-shortcut-contract.ps1` +- Modify: `tests/g01-switch-dialog-runtime-smoke.js` +- Modify: `pages/genealogy/g01-my-genealogies.vue` + +**Interfaces:** +- Consumes: `currentGenealogy.value.membership`, where the supported values are `created` and `joined`. +- Produces: `currentRoleLabel: ComputedRef` and `visibleShortcuts: ComputedRef>` for the existing G01 template. + +- [x] **Step 1: Write the failing source contract** + +```powershell +$ErrorActionPreference = 'Stop' +$page = Get-Content -Raw 'pages/genealogy/g01-my-genealogies.vue' + +function Assert-Contains([string]$Content, [string]$Pattern, [string]$Message) { + if (-not $Content.Contains($Pattern)) { throw $Message } +} + +Assert-Contains $page 'v-for="item in visibleShortcuts"' 'G01 must render the membership-filtered shortcut list' +Assert-Contains $page '{{ currentRoleLabel }}' 'G01 must render the membership-derived role label' +Assert-Contains $page 'currentGenealogy.value?.membership === "created"' 'G01 must derive owner state from membership' +Assert-Contains $page 'item.key !== "applications"' 'G01 must remove application review for joined members' + +Write-Output 'G01-ROLE-SHORTCUT-CONTRACT PASS' +``` + +- [x] **Step 2: Run the source contract and verify RED** + +Run: + +```powershell +powershell -ExecutionPolicy Bypass -File tests/g01-role-shortcut-contract.ps1 +``` + +Expected: FAIL with `G01 must render the membership-filtered shortcut list` because G01 still renders `shortcuts` directly. + +- [x] **Step 3: Extend the runtime smoke with role-state assertions** + +Immediately after selecting the second switcher item, add: + +```js + assert( + (await valueOf(send, "document.querySelector('.current-meta')?.textContent"))?.includes('成员'), + 'Joined genealogy did not display the member role' + ) + assert( + (await valueOf(send, "document.querySelectorAll('.shortcut-item').length")) === 3, + 'Joined genealogy did not hide the application-review shortcut' + ) + assert( + !(await valueOf(send, "document.querySelector('.shortcut-grid')?.textContent"))?.includes('申请审核'), + 'Joined genealogy still exposed application review' + ) +``` + +Immediately after restoring the first switcher item, add: + +```js + assert( + (await valueOf(send, "document.querySelector('.current-meta')?.textContent"))?.includes('管理员'), + 'Created genealogy did not restore the administrator role' + ) + assert( + (await valueOf(send, "document.querySelectorAll('.shortcut-item').length")) === 4, + 'Created genealogy did not restore all four shortcuts' + ) +``` + +- [x] **Step 4: Implement the minimal reactive role rule** + +Change the shortcut loop and role text in the template: + +```vue + +``` + +```vue +{{ currentRoleLabel }} +``` + +After `currentGenealogy`, add: + +```js +const isCurrentGenealogyOwner = computed( + () => currentGenealogy.value?.membership === "created", +); +const currentRoleLabel = computed(() => + isCurrentGenealogyOwner.value ? "管理员" : "成员", +); +``` + +After the existing `shortcuts` array, add: + +```js +const visibleShortcuts = computed(() => + isCurrentGenealogyOwner.value + ? shortcuts + : shortcuts.filter((item) => item.key !== "applications"), +); +``` + +- [x] **Step 5: Run focused automated verification** + +Run: + +```powershell +powershell -ExecutionPolicy Bypass -File tests/g01-role-shortcut-contract.ps1 +powershell -ExecutionPolicy Bypass -File tests/g01-genealogy-context-contract.ps1 +powershell -ExecutionPolicy Bypass -File tests/g01-empty-state-contract.ps1 +powershell -ExecutionPolicy Bypass -File tests/g01-error-state-contract.ps1 +powershell -ExecutionPolicy Bypass -File tests/g01-visual-contract.ps1 +git diff --check +``` + +Expected: every contract prints `PASS`; `git diff --check` exits 0. + +If the H5 debug page and Chrome remote-debugging port `9222` are available, also run: + +```powershell +node tests/g01-switch-dialog-runtime-smoke.js +``` + +Expected: `PASS G-01 switch dialog runtime smoke`. + +- [x] **Step 6: Verify both roles in MuMu Android** + +1. Open G01 with the created genealogy selected and capture the administrator state. +2. Open the switcher and select the joined genealogy. +3. Confirm the top role reads “成员”, exactly three shortcuts are visible, and there is no blank fourth slot or hidden application-review target. +4. Switch back to the created genealogy and confirm “管理员” plus four shortcuts return. +5. Confirm the list scroll resets to the top and the bottom tabbar remains unobstructed. + +- [x] **Step 7: Commit the tested fix** + +```powershell +git add tests/g01-role-shortcut-contract.ps1 tests/g01-switch-dialog-runtime-smoke.js pages/genealogy/g01-my-genealogies.vue docs/superpowers/plans/2026-07-21-g01-role-shortcut.md +git commit -m "fix: sync G01 role shortcuts" +``` diff --git a/pages/genealogy/g01-my-genealogies.vue b/pages/genealogy/g01-my-genealogies.vue index 84d4dce..f93622f 100644 --- a/pages/genealogy/g01-my-genealogies.vue +++ b/pages/genealogy/g01-my-genealogies.vue @@ -82,14 +82,14 @@ src="/static/assets/foundation/transparent/meta-admin.png" mode="aspectFit" /> - 管理员 + {{ currentRoleLabel }} item.id === selectedGenealogyId.value, ) || availableGenealogies.value[0], ); +const isCurrentGenealogyOwner = computed( + () => currentGenealogy.value?.membership === "created", +); +const currentRoleLabel = computed(() => + isCurrentGenealogyOwner.value ? "管理员" : "成员", +); // 仅用于页面样式阶段覆盖加入申请的关键状态,不作为接口数据。 const applicationRecords = [ @@ -468,6 +474,11 @@ const shortcuts = [ icon: "/static/assets/modules/genealogy/transparent/shortcut-application.png", }, ]; +const visibleShortcuts = computed(() => + isCurrentGenealogyOwner.value + ? shortcuts + : shortcuts.filter((item) => item.key !== "applications"), +); const openGenealogy = (genealogy) => { genealogyContext.setCurrentGenealogyId(genealogy.id); diff --git a/tests/g01-role-shortcut-contract.ps1 b/tests/g01-role-shortcut-contract.ps1 new file mode 100644 index 0000000..1ca8084 --- /dev/null +++ b/tests/g01-role-shortcut-contract.ps1 @@ -0,0 +1,13 @@ +$ErrorActionPreference = 'Stop' +$page = Get-Content -Raw 'pages/genealogy/g01-my-genealogies.vue' + +function Assert-Contains([string]$Content, [string]$Pattern, [string]$Message) { + if (-not $Content.Contains($Pattern)) { throw $Message } +} + +Assert-Contains $page 'v-for="item in visibleShortcuts"' 'G01 must render the membership-filtered shortcut list' +Assert-Contains $page '{{ currentRoleLabel }}' 'G01 must render the membership-derived role label' +Assert-Contains $page 'currentGenealogy.value?.membership === "created"' 'G01 must derive owner state from membership' +Assert-Contains $page 'item.key !== "applications"' 'G01 must remove application review for joined members' + +Write-Output 'G01-ROLE-SHORTCUT-CONTRACT PASS' diff --git a/tests/g01-switch-dialog-runtime-smoke.js b/tests/g01-switch-dialog-runtime-smoke.js index f67e3f7..1d679e8 100644 --- a/tests/g01-switch-dialog-runtime-smoke.js +++ b/tests/g01-switch-dialog-runtime-smoke.js @@ -261,10 +261,30 @@ const run = async () => { await valueOf(send, "document.querySelectorAll('.switcher-item')[1].click()") await waitFor(send, "!document.querySelector('.genealogy-switcher')", 'Selecting the second genealogy did not close the switcher') assert((await valueOf(send, "document.querySelector('.current-slip')?.textContent"))?.includes('山东'), 'Selecting the second genealogy did not update the current genealogy') + assert( + (await valueOf(send, "document.querySelector('.current-meta')?.textContent"))?.includes('成员'), + 'Joined genealogy did not display the member role' + ) + assert( + (await valueOf(send, "document.querySelectorAll('.shortcut-item').length")) === 3, + 'Joined genealogy did not hide the application-review shortcut' + ) + assert( + !(await valueOf(send, "document.querySelector('.shortcut-grid')?.textContent"))?.includes('申请审核'), + 'Joined genealogy still exposed application review' + ) await openSwitcher(send) await valueOf(send, "document.querySelectorAll('.switcher-item')[0].click()") await waitFor(send, "!document.querySelector('.genealogy-switcher')", 'Restoring the first genealogy did not close the switcher') + assert( + (await valueOf(send, "document.querySelector('.current-meta')?.textContent"))?.includes('管理员'), + 'Created genealogy did not restore the administrator role' + ) + assert( + (await valueOf(send, "document.querySelectorAll('.shortcut-item').length")) === 4, + 'Created genealogy did not restore all four shortcuts' + ) await openSwitcher(send) await clearClones(send)