Audit Scope Merge 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: Merge the "Information System" and "Device" sections in ProjectCreateView and ProjectSettingsView into a single "受評範圍 (Audit Scope)" section, frontend-only, no BE / schema changes.

Architecture: Build a new AuditScopeSection.vue wrapper component that hosts the existing SystemPicker and DevicePicker components stacked vertically. Wire it into both views, replacing the separate tabs/steps. All save logic stays exactly the same — syncProjectSystems() and syncProjectDevices() continue to fire independently from the wrapper's events.

Tech Stack: Vue 3 (Composition API + <script setup lang="ts">), PrimeVue (TabView, Steps), vue-i18n, Pinia (menuStore), Vuelidate (existing).

Repository: All code work happens in ~/Projects/Billows/Audit-Manager/compliance-manager-fe/. Design + plan + changelog live in ~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/.

Spec reference: docs/features/FR-017-2604-audit-scope-merge/design.md


§1

Notes for Implementer

Testing approach (READ THIS)

This is a Vue 3 frontend project that does NOT have a unit test framework for components (no Vitest / no @vue/test-utils in package.json). Only Cypress E2E tests exist (cypress:open, cypress:run).

Therefore TDD as "write failing test first" does not apply here. Each task uses manual browser verification as the validation step:

  1. Make the change
  2. Run dev server: npm run dev (port 5180)
  3. Open the affected screen in browser
  4. Verify the specific behavior listed in the task
  5. Commit when verified

If there is an existing Cypress test for the affected view, run it after the change. Do NOT add new Cypress tests as part of this plan unless explicitly extending coverage — out of scope.

Branch convention

Work in branch feature/merge-project-setting-item (already created in BE repo; create matching branch in FE repo: git checkout -b feature/merge-project-setting-item).

Commit style

Mirror the BE repo's recent style:

  • feat: ... for new components
  • refactor: ... for view restructuring
  • chore(i18n): ... for translation key changes
  • docs: ... for changelog

Each commit must be focused — one task = one commit unless explicitly noted.

Don't do these

  • ❌ Don't modify SystemPicker.vue or DevicePicker.vue (use them as-is via the wrapper)
  • ❌ Don't touch DeviceManage.vue or InformationSystemManage.vue (independent management pages, out of scope)
  • ❌ Don't touch any BE file
  • ❌ Don't introduce new dependencies
  • ❌ Don't delete the old i18n keys (tab_audit_systems, tab_devices, step_audit_systems, step_devices, etc.) — keep them for safety; cleanup is a follow-up PR
  • ❌ Don't add the "future hierarchy" hint UI — user explicitly declined

§2

File Structure (decided up front)

File Action Responsibility
src/components/grc/AuditScopeSection.vue Create Wrapper that stacks SystemPicker (top) and DevicePicker (bottom). Pure layout + props/emits passthrough. No API calls, no internal state.
src/views/project/ProjectSettingsView.vue Modify Collapse Tab 2 (Audit Systems) and Tab 4 (Devices) into a single new Tab 2 (Audit Scope) backed by AuditScopeSection.
src/views/project/ProjectCreateView.vue Modify 5 steps → 4 steps with order: basic / participants / audit-scope / review. Replace step 1 (system) and step 3 (device) with a single audit-scope step. Update review section accordingly.
src/config/locales/i18n/zh-tw/project-settings.json Modify Add tab_audit_scope, section_audit_scope keys
src/config/locales/i18n/en/project-settings.json Modify Same as above (English)
src/config/locales/i18n/zh-tw/project-create.json Modify Add step_audit_scope, audit_scope_subtitle, review_audit_scope keys
src/config/locales/i18n/en/project-create.json Modify Same as above (English)
~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/changelog/2026-04-25-audit-scope-merge.md Create Changelog (in BE repo)

§3

Task 1: Setup — Verify starting state

Goal: Confirm clean baseline before changes.

Files: None (verification only)

cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git status
git log -1 --oneline

Expected: on branch feature/merge-project-setting-item, last commit is 33d592d docs: 新增 audit-scope-merge ..., working tree clean.

cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
git status
git checkout -b feature/merge-project-setting-item || git checkout feature/merge-project-setting-item
git status

Expected: clean working tree, on branch feature/merge-project-setting-item.

cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
npm run dev

In browser, open /project/projects/new and /project/projects/<some-existing-uid>/settings. Verify both load. Note current Tab 2 = Audit Systems and Tab 4 = Devices in Settings; Step 1 = Audit Systems and Step 3 = Devices in Create.

Stop the dev server (Ctrl-C). No commit needed for this task.


§4

Task 2: Add i18n keys (zh-tw + en)

Goal: Define all new translation keys before they're referenced from views.

Files:

  • Modify: src/config/locales/i18n/zh-tw/project-settings.json
  • Modify: src/config/locales/i18n/en/project-settings.json
  • Modify: src/config/locales/i18n/zh-tw/project-create.json
  • Modify: src/config/locales/i18n/en/project-create.json
cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
grep -n "tab_audit_systems\|tab_devices" src/config/locales/i18n/zh-tw/project-settings.json
grep -n "step_audit_systems\|step_devices\|review_audit_systems\|review_devices" src/config/locales/i18n/zh-tw/project-create.json

Note the surrounding key naming and JSON nesting (e.g., flat keys vs nested). All new keys must follow the same pattern.

Add these new keys (place near existing tab_* keys, do NOT remove old ones):

"tab_audit_scope": "受評範圍 ({systems} 系統 / {devices} 設備)",
"section_audit_scope": "受評範圍",
"section_audit_scope_desc": "本專案要稽核的資訊系統與設備"
"tab_audit_scope": "Audit Scope ({systems} systems / {devices} devices)",
"section_audit_scope": "Audit Scope",
"section_audit_scope_desc": "Information systems and devices in scope for this audit"

Add these (place near existing step_* keys):

"step_audit_scope": "受評範圍",
"audit_scope_title": "受評範圍",
"audit_scope_subtitle": "選擇本專案要稽核的系統與設備",
"review_audit_scope": "受評範圍",
"review_audit_scope_systems": "資訊系統",
"review_audit_scope_devices": "設備"
"step_audit_scope": "Audit Scope",
"audit_scope_title": "Audit Scope",
"audit_scope_subtitle": "Select systems and devices for this audit",
"review_audit_scope": "Audit Scope",
"review_audit_scope_systems": "Information Systems",
"review_audit_scope_devices": "Devices"
cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
for f in src/config/locales/i18n/zh-tw/project-settings.json \
         src/config/locales/i18n/en/project-settings.json \
         src/config/locales/i18n/zh-tw/project-create.json \
         src/config/locales/i18n/en/project-create.json; do
  python3 -c "import json; json.load(open('$f'))" && echo "OK: $f"
done

Expected: 4 lines of OK: .... If any fails, fix the JSON before continuing.

git add src/config/locales/i18n/zh-tw/project-settings.json \
        src/config/locales/i18n/en/project-settings.json \
        src/config/locales/i18n/zh-tw/project-create.json \
        src/config/locales/i18n/en/project-create.json
git commit -m "chore(i18n): 新增 audit-scope-merge 翻譯 key (zh-tw + en)"

§5

Task 3: Create AuditScopeSection.vue component

Goal: Build the wrapper component that hosts SystemPicker + DevicePicker stacked.

Files:

  • Create: src/components/grc/AuditScopeSection.vue

The wrapper uses these existing keys for inner picker headers; confirm they're already present:

cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
grep -n "section_audit_systems\|section_devices" \
  src/config/locales/i18n/zh-tw/project-settings.json \
  src/config/locales/i18n/en/project-settings.json

Expected: 4 matches (2 keys × 2 locales). If any are missing, add them BEFORE proceeding (use the same wording you'd see in current SystemPicker / DevicePicker #header slots).

<script setup lang="ts">
/**
 * 受評範圍 section — 包裝 SystemPicker + DevicePicker,stacked 版面
 * 用於 ProjectCreateView 和 ProjectSettingsView
 *
 * 設計:純 layout wrapper。所有 props/emits 透傳給內部 picker。
 * 不做 API 呼叫、不存內部狀態。
 */
import { useI18n } from 'vue-i18n'
import SystemPicker from '@/components/grc/SystemPicker.vue'
import DevicePicker from '@/components/grc/DevicePicker.vue'

interface SystemMenuItem {
    id: string
    name: string
    abbreviation: string | null
}

withDefaults(defineProps<{
    // System
    selectedSystemIds: string[]
    selectedSystemDetails: Record<string, any>
    infoSystemMenu: SystemMenuItem[]
    // Device
    selectedDevices: string[]
    selectedDeviceDetails: Record<string, any>
    deviceMenu: any[]
    // Shared
    userMenu: any[]
    readonly?: boolean
    confirmBeforeRemove?: boolean
    /** 是否顯示 section 標題(Settings tab 用 false,Create step 用 true) */
    showSectionHeader?: boolean
}>(), {
    readonly: false,
    confirmBeforeRemove: false,
    showSectionHeader: false,
})

defineEmits<{
    // System
    'update:selectedSystemIds': [ids: string[]]
    'update:selectedSystemDetails': [details: Record<string, any>]
    'update:infoSystemMenu': [menu: SystemMenuItem[]]
    'change-systems': []
    // Device
    'update:selectedDevices': [ids: string[]]
    'update:selectedDeviceDetails': [details: Record<string, any>]
    'change-devices': []
    'device-created': []
    'device-detail-saved': []
}>()

const { t } = useI18n()
</script>

<template>
    <div class="flex flex-column gap-4">
        <!-- Optional section header (used by Create wizard step) -->
        <div v-if="showSectionHeader">
            <h3 class="m-0">{{ t('lang.project_create.audit_scope_title') }}</h3>
            <p class="m-0 mt-2 text-sm" style="color: var(--text-muted);">
                {{ t('lang.project_create.audit_scope_subtitle') }}
            </p>
        </div>

        <!-- System block (top) -->
        <div class="border-round-lg p-4 surface-section"
             style="border: 1px solid var(--surface-border)">
            <SystemPicker
                :selectedIds="selectedSystemIds"
                :systemMenu="infoSystemMenu"
                :systemDetails="selectedSystemDetails"
                :userMenu="userMenu"
                :readonly="readonly"
                :confirmBeforeRemove="confirmBeforeRemove"
                @update:selectedIds="$emit('update:selectedSystemIds', $event)"
                @update:systemMenu="$emit('update:infoSystemMenu', $event)"
                @update:systemDetails="$emit('update:selectedSystemDetails', $event)"
                @change="$emit('change-systems')"
            >
                <template #header>
                    <h4 class="m-0">{{ t('lang.project_settings.section_audit_systems') }}</h4>
                </template>
            </SystemPicker>
        </div>

        <!-- Device block (bottom) -->
        <div class="border-round-lg p-4 surface-section"
             style="border: 1px solid var(--surface-border)">
            <DevicePicker
                :selectedIds="selectedDevices"
                :deviceDetails="selectedDeviceDetails"
                :deviceMenu="deviceMenu"
                :readonly="readonly"
                :confirmBeforeRemove="confirmBeforeRemove"
                @update:selectedIds="$emit('update:selectedDevices', $event)"
                @update:deviceDetails="$emit('update:selectedDeviceDetails', $event)"
                @change="$emit('change-devices')"
                @created="$emit('device-created')"
                @detailSaved="$emit('device-detail-saved')"
            >
                <template #header>
                    <h4 class="m-0">{{ t('lang.project_settings.section_devices') }}</h4>
                </template>
            </DevicePicker>
        </div>
    </div>
</template>
cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
ls -la src/components/grc/AuditScopeSection.vue

Run dev server briefly to check Vue compilation:

npm run dev

The dev server should start without errors. AuditScopeSection isn't used yet, so no visual to check. Stop the server.

git add src/components/grc/AuditScopeSection.vue
git commit -m "feat(grc): 新增 AuditScopeSection 元件包裝 System+Device picker"

§6

Task 4: Refactor ProjectSettingsView.vue — collapse 4 tabs to 3

Goal: Replace Tab 2 (Audit Systems) and Tab 4 (Devices) with a single new Tab 2 (Audit Scope).

Files:

  • Modify: src/views/project/ProjectSettingsView.vue

In the <script setup> section, find these lines:

import SystemPicker from '@/components/grc/SystemPicker.vue'
import DevicePicker from '@/components/grc/DevicePicker.vue'

Replace with:

import AuditScopeSection from '@/components/grc/AuditScopeSection.vue'

(Remove both old picker imports — the wrapper handles them internally.)

In the <template>, locate the existing Tab 2 (Audit Systems, line ~254):

<!-- Tab 2: Audit Systems -->
<TabPanel :header="t('lang.project_settings.tab_audit_systems', { count: selectedSystemIds.length })">
    <div class="border-round-xl p-5 shadow-3 surface-card"
         style="border: 1px solid var(--surface-border)">
        <SystemPicker v-model:selectedIds="selectedSystemIds"
                      v-model:systemMenu="infoSystemMenu"
                      v-model:systemDetails="selectedSystemDetails"
                      :userMenu="userMenu ?? []"
                      :readonly="!canEdit"
                      :confirmBeforeRemove="true"
                      @change="syncProjectSystems">
            <template #header>...</template>
        </SystemPicker>
    </div>
</TabPanel>

Replace with the new merged Tab 2:

<!-- Tab 2: Audit Scope (merged Systems + Devices) -->
<TabPanel :header="t('lang.project_settings.tab_audit_scope', {
    systems: selectedSystemIds.length,
    devices: selectedDevices.length
})">
    <div class="border-round-xl p-5 shadow-3 surface-card"
         style="border: 1px solid var(--surface-border)">
        <AuditScopeSection
            v-model:selectedSystemIds="selectedSystemIds"
            v-model:selectedSystemDetails="selectedSystemDetails"
            v-model:infoSystemMenu="infoSystemMenu"
            v-model:selectedDevices="selectedDevices"
            v-model:selectedDeviceDetails="selectedDeviceDetails"
            :deviceMenu="deviceMenu ?? []"
            :userMenu="userMenu ?? []"
            :readonly="!canEdit"
            :confirmBeforeRemove="true"
            @change-systems="syncProjectSystems"
            @change-devices="syncProjectDevices"
            @device-created="menuStore.fetchDeviceMenu()"
            @device-detail-saved="fetchProject(); menuStore.fetchDeviceMenu()"
        />
    </div>
</TabPanel>

Locate the existing Tab 4 (line ~291 in the original file):

<!-- Tab 4: Devices -->
<TabPanel :header="t('lang.project_settings.tab_devices', { count: selectedDevices.length })">
    <div class="border-round-xl p-5 shadow-3 surface-card"
         style="border: 1px solid var(--surface-border)">
        <DevicePicker v-model:selectedIds="selectedDevices" .../>
    </div>
</TabPanel>

Delete the entire <TabPanel> block (Tab 3 Participants stays unchanged and becomes the new Tab 3).

The TabView should now have exactly 3 panels in this order:

  1. 基本資訊 (Tab 1, unchanged)
  2. 受評範圍 (Tab 2, NEW merged)
  3. 參與者 (Tab 3, was Tab 3, unchanged)
npm run dev

Open /project/projects/<existing-uid>/settings (use a real project UID from your dev environment).

Verify:

Stop the dev server.

git add src/views/project/ProjectSettingsView.vue
git commit -m "refactor(project-settings): 合併稽核系統與設備 tab 為「受評範圍」"

§7

Task 5: Refactor ProjectCreateView.vue — 5 steps to 4 steps with reorder

Goal: Reorder steps to (basic / participants / audit-scope / review). Replace step 1 (system) and step 3 (device) with a single audit-scope step. Update review section.

Files:

  • Modify: src/views/project/ProjectCreateView.vue

Find these lines:

import SystemPicker from '@/components/grc/SystemPicker.vue'
import DevicePicker from '@/components/grc/DevicePicker.vue'

Replace with:

import AuditScopeSection from '@/components/grc/AuditScopeSection.vue'

Find the existing stepsItems computed (line ~38):

const stepsItems = computed(() => [
    { label: t('lang.project_create.step_basic_info') },
    { label: t('lang.project_create.step_audit_systems') },
    { label: t('lang.project_create.step_participants_depts') },
    { label: t('lang.project_create.step_devices') },
    { label: t('lang.project_create.step_review') },
])

Replace with:

const stepsItems = computed(() => [
    { label: t('lang.project_create.step_basic_info') },
    { label: t('lang.project_create.step_participants_depts') },
    { label: t('lang.project_create.step_audit_scope') },
    { label: t('lang.project_create.step_review') },
])

New step indices:

  • 0: 基本資訊 (was 0)
  • 1: 參與者 (was 2)
  • 2: 受評範圍 (NEW, replaces was-1 + was-3)
  • 3: 確認 (was 4)

⚠️ Critical: do all 4 of these changes in a single edit pass. Mid-state (e.g., two blocks both keyed currentStep === 2) will break the wizard.

The template currently has 5 v-if / v-else-if blocks for currentStep === 0..4. Restructure to 4 blocks for currentStep === 0..3 as follows:

Old block Old condition Action New condition
Step 0 Basic Info v-if="currentStep === 0" Keep as-is v-if="currentStep === 0"
Step 1 Audit Systems v-else-if="currentStep === 1" DELETE entirely (gone)
Step 2 Participants v-else-if="currentStep === 2" Renumber to 1 v-else-if="currentStep === 1"
(NEW Audit Scope) (none) INSERT new block here v-else-if="currentStep === 2"
Step 3 Devices v-else-if="currentStep === 3" DELETE entirely (gone)
Step 4 Review v-else-if="currentStep === 4" Renumber to 3 v-else-if="currentStep === 3"

Recommended edit order to avoid duplicate keys mid-edit:

  1. First delete BOTH old "Audit Systems" (cond=1) and "Devices" (cond=3) blocks
  2. Then renumber Participants block (cond=2 → cond=1)
  3. Then renumber Review block (cond=4 → cond=3)
  4. Finally INSERT the new Audit Scope block (cond=2) between Participants and Review

The new Audit Scope block to insert:

<!-- Step 2: Audit Scope (merged Systems + Devices) -->
<div v-else-if="currentStep === 2" class="flex flex-column gap-5">
    <AuditScopeSection
        v-model:selectedSystemIds="selectedSystemIds"
        v-model:selectedSystemDetails="selectedSystemDetails"
        v-model:infoSystemMenu="infoSystemMenu"
        v-model:selectedDevices="selectedDevices"
        v-model:selectedDeviceDetails="selectedDeviceDetails"
        :deviceMenu="deviceMenu ?? []"
        :userMenu="userMenu ?? []"
        :showSectionHeader="true"
        @device-created="menuStore.fetchDeviceMenu()"
    />
</div>

(Note: in Create wizard, change-systems / change-devices events are not wired — there's no real-time sync during create; everything saves via handleCreate at the final step.)

Step 3 (Review): confirmed renumbered to v-else-if="currentStep === 3" (per the table above).

Inside the Step 3 (review) block, locate these two existing sub-sections:

<!-- Audit Systems --> ... (currentStep = 1 edit link)
<!-- Devices --> ... (currentStep = 3 edit link)

Replace BOTH with this single combined block (place it AFTER the Participants block to match the new step order: basic → participants → audit-scope):

<!-- Audit Scope (merged Systems + Devices) -->
<div class="p-4 border-round-lg surface-section" style="border: 1px solid var(--surface-border)">
    <div class="flex align-items-center justify-content-between mb-3">
        <span class="font-medium">{{ t('lang.project_create.review_audit_scope') }}</span>
        <button class="cursor-pointer border-none bg-transparent text-primary"
                @click="currentStep = 2">{{ t('lang.project_create.btn_edit') }}</button>
    </div>

    <!-- Systems sub-block -->
    <div class="mb-4">
        <div class="text-sm text-color-secondary mb-2">{{ t('lang.project_create.review_audit_scope_systems') }}</div>
        <div v-if="selectedSystemIds.length > 0" class="flex flex-column gap-2">
            <div v-for="sysId in selectedSystemIds" :key="sysId"
                 class="flex align-items-center justify-content-between px-3 py-2 border-round surface-card"
                 style="border: 1px solid var(--surface-border)">
                <span class="font-medium">{{ infoSystemMenu.find(s => s.id === sysId)?.name ?? sysId }}</span>
                <span v-if="infoSystemMenu.find(s => s.id === sysId)?.abbreviation"
                      class="text-color-secondary text-sm">
                    {{ infoSystemMenu.find(s => s.id === sysId)?.abbreviation }}
                </span>
            </div>
        </div>
        <span v-else class="text-color-secondary">{{ t('lang.project_create.review_none') }}</span>
    </div>

    <!-- Devices sub-block -->
    <div>
        <div class="text-sm text-color-secondary mb-2">{{ t('lang.project_create.review_audit_scope_devices') }}</div>
        <div v-if="selectedDevices.length > 0" class="flex flex-wrap gap-2">
            <span v-for="devId in selectedDevices" :key="devId"
                  class="inline-flex align-items-center gap-1 px-2 py-1 border-round surface-card"
                  style="border: 1px solid var(--surface-border)">
                <i class="pi pi-desktop"></i>
                {{ (deviceMenu?.find((d: any) => d.uid === devId)?.name) ?? devId }}
            </span>
        </div>
        <span v-else class="text-color-secondary">{{ t('lang.project_create.review_none') }}</span>
    </div>
</div>

Also: update the Participants edit link (still in review block) — its @click="currentStep = 2" was pointing at old step 2 (participants). Now participants are at step 1, so change to @click="currentStep = 1".

Final review block sub-sections in this order:

  1. Basic Info (@click="currentStep = 0")
  2. Participants (@click="currentStep = 1")
  3. Audit Scope (@click="currentStep = 2")

Look at handleNext (around line 108):

async function handleNext() {
    if (currentStep.value === 0) {
        const valid = await v$.value.$validate()
        if (!valid) return
    }
    currentStep.value++
}

This is fine — only step 0 has validation. New step 0 is still basic info. No change needed.

Look at handleBack (around line 116):

function handleBack() {
    if (currentStep.value === 0) {
        router.push({ name: 'project-list' })
    } else {
        currentStep.value--
        if (currentStep.value === 0) v$.value.$reset()
    }
}

No change needed — still resets validation when returning to step 0.

Look at handleCreate (around line 125). It builds audit_systems from selectedSystemIds and devices from selectedDevices. This is unchanged because the underlying refs are unchanged — only the UI grouping moved. No code change needed here.

npm run dev

Open /project/projects/new. Verify:

Empty-state check:

Stop the dev server.

git add src/views/project/ProjectCreateView.vue
git commit -m "refactor(project-create): 4 步驟重構 + 合併「受評範圍」步驟"

§8

Task 6: Regression smoke test

Goal: Confirm unrelated views still work — especially anything that consumes Device or InformationSystem data outside the changed views.

Files: None (verification only)

npm run dev

Open /project/projects/<existing-uid>/auditor-overview. Verify:

Open any project's job execution view, click into a job that has devices attached. Verify:

Open /device/device-manage. Verify:

Open /information-system/manage. Verify:

In Settings or anywhere with the language switcher, toggle between zh-tw and en. Verify:

Stop the dev server. No commit needed.


§9

Task 7: i18n key cleanup audit

Goal: Confirm we didn't leave any orphaned/unused references to old keys, and check if old keys can be safely removed in a follow-up. This task does NOT delete keys — just produces a report.

Files: None (audit only)

cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
echo "=== tab_audit_systems references ==="
grep -rn "tab_audit_systems" src/ --include="*.vue" --include="*.ts" --include="*.js"
echo "=== tab_devices references ==="
grep -rn "tab_devices" src/ --include="*.vue" --include="*.ts" --include="*.js"
echo "=== step_audit_systems references ==="
grep -rn "step_audit_systems" src/ --include="*.vue" --include="*.ts" --include="*.js"
echo "=== step_devices references ==="
grep -rn "step_devices" src/ --include="*.vue" --include="*.ts" --include="*.js"
echo "=== review_audit_systems references ==="
grep -rn "review_audit_systems" src/ --include="*.vue" --include="*.ts" --include="*.js"
echo "=== review_devices references ==="
grep -rn "review_devices" src/ --include="*.vue" --include="*.ts" --include="*.js"

Expected: only the i18n JSON files themselves should match (no .vue / .ts / .js references). If any code file still uses these keys, that's a bug — report and fix.

echo "=== tab_audit_scope ==="
grep -rn "tab_audit_scope" src/
echo "=== step_audit_scope ==="
grep -rn "step_audit_scope" src/
echo "=== review_audit_scope ==="
grep -rn "review_audit_scope" src/

Expected: each new key appears in 2 locale files + at least one Vue file.

If old keys are confirmed orphaned, note in the PR description that they can be removed in a follow-up cleanup PR. Do NOT remove them in this PR — keep for safety.

No commit needed.


§10

Task 8: Create changelog

Goal: Document the change per CLAUDE.md convention.

Files:

  • Create: ~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/changelog/2026-04-25-audit-scope-merge.md
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git status

Should be on feature/merge-project-setting-item, clean working tree.

Create docs/changelog/2026-04-25-audit-scope-merge.md with this content:

# 2026-04-25 — 專案受評範圍合併(Audit Scope Consolidation)

## 需求說明

ProjectCreate 與 ProjectSettings 兩個畫面內,「資訊系統」與「設備」原本分屬獨立 step / tab。從 user 視角兩者都屬「本專案要稽核的範圍」之組成元件,分散造成認知負擔。

本次將兩者在 UI 層合併為單一 section「受評範圍 (Audit Scope)」。BE / schema 完全不動,作為將來 OSCAL SSP 匯出 / 跨模組引用的階段性踏腳石。

設計文件:`docs/features/FR-017-2604-audit-scope-merge/design.md`
實作計畫:`docs/features/FR-017-2604-audit-scope-merge/implementation-plan.md`

## 變更範圍

### Frontend (compliance-manager-fe)

| 檔案 | 動作 |
|------|------|
| `src/components/grc/AuditScopeSection.vue` | 新增 — 包裝 SystemPicker + DevicePicker,stacked 版面 |
| `src/views/project/ProjectSettingsView.vue` | 修改 — 4 tabs → 3 tabs(合併 Tab 2 系統 + Tab 4 設備 為新 Tab 2 受評範圍) |
| `src/views/project/ProjectCreateView.vue` | 修改 — 5 steps → 4 steps,順序改為:基本資訊 → 參與者 → 受評範圍 → 確認 |
| `src/config/locales/i18n/zh-tw/project-settings.json` | 修改 — 新增 `tab_audit_scope` 等 key(保留舊 key) |
| `src/config/locales/i18n/en/project-settings.json` | 修改 — 同上(英文) |
| `src/config/locales/i18n/zh-tw/project-create.json` | 修改 — 新增 `step_audit_scope``review_audit_scope` 等 key |
| `src/config/locales/i18n/en/project-create.json` | 修改 — 同上(英文) |

### Backend

無變更。

## API 變更

無。`PUT /api/1.0/grc-project/<id>` 仍以 `audit_systems` + `devices` 兩個 key 各自接收,FE 仍各自呼叫 sync。

## 行為差異

| 情境 | 之前 | 現在 |
|------|------|------|
| ProjectSettings tab 數 | 4(基本/系統/參與者/設備) | 3(基本/受評範圍/參與者) |
| ProjectSettings 受評範圍 tab header | n/a | `受評範圍 (3 系統 / 5 設備)` |
| ProjectCreate step 數 | 5 | 4 |
| ProjectCreate step 順序 | 基本→系統→參與者→設備→確認 | 基本→參與者→受評範圍→確認 |
| ProjectCreate 確認頁區段 | 4(基本/系統/參與者/設備) | 3(基本/參與者/受評範圍)|
| 既有 SystemPicker / DevicePicker 元件 || 完全不變,由 AuditScopeSection 包裝 |
| 存檔 API call | `PUT` 各自 sync | `PUT` 各自 sync(不變) |

## Phase 2 預留(不在本次)

- OSCAL SSP exporter(可選 a 純讀現有表 / b 加 mapping 小表 / c 加 FK)
- ComponentService 抽象(給 control-implementation / POA&M / evidence ref 引用)
- 樹狀 ComponentPicker(系統下層展示設備)

## 不做的事

- 不動 BE / schema
- 不動 jedi-device 套件 / jedi_information_system 模組
- 不動 SystemPicker / DevicePicker 元件本身
- 不動 DeviceManage / InformationSystemManage 獨立管理頁
- 不動 job_execution_device_mapping、evidence、device_view 聚合
- 不刪除舊的 i18n key(之後 PR 再清理)

## 參考

- 設計文件:`docs/features/FR-017-2604-audit-scope-merge/design.md`
- 實作計畫:`docs/features/FR-017-2604-audit-scope-merge/implementation-plan.md`
git add docs/changelog/2026-04-25-audit-scope-merge.md
git commit -m "docs(changelog): audit-scope-merge 變更紀錄"

§11

Task 9: Final verification & PR prep

Goal: Final sanity pass before declaring done.

Files: None (verification only)

cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
git log --oneline feature/merge-project-setting-item ^main 2>/dev/null || git log --oneline -10

Expected commits (in order):

  1. chore(i18n): 新增 audit-scope-merge 翻譯 key (zh-tw + en)
  2. feat(grc): 新增 AuditScopeSection 元件包裝 System+Device picker
  3. refactor(project-settings): 合併稽核系統與設備 tab 為「受評範圍」
  4. refactor(project-create): 4 步驟重構 + 合併「受評範圍」步驟
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git log --oneline feature/merge-project-setting-item ^main 2>/dev/null || git log --oneline -5

Expected commits:

  1. docs: 新增 audit-scope-merge 功能設計文件 + 加 .superpowers/ 到 gitignore
  2. docs(changelog): audit-scope-merge 變更紀錄

Run dev server one more time. Walk through:

This implementation is complete. The implementer should report:

  • All tasks completed
  • All commits in place
  • Manual verification passed
  • Open PRs in both repos with branch feature/merge-project-setting-item

PR descriptions should reference docs/features/FR-017-2604-audit-scope-merge/design.md and docs/changelog/2026-04-25-audit-scope-merge.md.


§12

Quick Reference

Test data setup

If you need a clean dev project to test with: log in as blsit / Billows@123! (tenant_id=102, org_unit_id=99). Use any existing project UID for Settings tests, or create a new one for the Create flow.

Common pitfalls

  • i18n raw key showing in browser: forgot to add the key in one of the 4 locale files, or typo in the key reference
  • update:selectedSystemIds not firing: check the wrapper passes through the right event name (it's update:selectedIds from inner SystemPicker → re-emitted as update:selectedSystemIds from wrapper)
  • Device count not updating in tab header: the wrapper passes selectedDevices through v-model; verify both :value binding and update: event are correctly wired

Out of scope (do NOT do)

  • Adding the "future hierarchy" hint UI
  • Changing tab/step header count display format from "3 系統 / 5 設備" to anything else
  • Removing old i18n keys
  • Adding Cypress tests
  • Modifying SystemPicker / DevicePicker
  • Any BE change