# 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`

---

## 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

---

## 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) |

---

## Task 1: Setup — Verify starting state

**Goal:** Confirm clean baseline before changes.

**Files:** None (verification only)

- [ ] **Step 1: Confirm BE branch and clean working tree (BE repo)**

```bash
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.

- [ ] **Step 2: Switch to FE repo and create matching branch**

```bash
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`.

- [ ] **Step 3: Smoke-test current dev server**

```bash
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.

---

## 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`

- [ ] **Step 1: Inspect current key shape in each file**

```bash
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.

- [ ] **Step 2: Add new keys to `zh-tw/project-settings.json`**

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

```json
"tab_audit_scope": "受評範圍 ({systems} 系統 / {devices} 設備)",
"section_audit_scope": "受評範圍",
"section_audit_scope_desc": "本專案要稽核的資訊系統與設備"
```

- [ ] **Step 3: Add corresponding keys to `en/project-settings.json`**

```json
"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"
```

- [ ] **Step 4: Add new keys to `zh-tw/project-create.json`**

Add these (place near existing `step_*` keys):

```json
"step_audit_scope": "受評範圍",
"audit_scope_title": "受評範圍",
"audit_scope_subtitle": "選擇本專案要稽核的系統與設備",
"review_audit_scope": "受評範圍",
"review_audit_scope_systems": "資訊系統",
"review_audit_scope_devices": "設備"
```

- [ ] **Step 5: Add corresponding keys to `en/project-create.json`**

```json
"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"
```

- [ ] **Step 6: Validate JSON syntax**

```bash
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.

- [ ] **Step 7: Commit**

```bash
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)"
```

---

## Task 3: Create `AuditScopeSection.vue` component

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

**Files:**
- Create: `src/components/grc/AuditScopeSection.vue`

- [ ] **Step 1: Verify inner picker i18n keys exist (pre-flight check)**

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

```bash
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).

- [ ] **Step 2: Create the file with the full component**

```vue
<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>
```

- [ ] **Step 3: Sanity check — open file in editor, verify no syntax errors**

```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
ls -la src/components/grc/AuditScopeSection.vue
```

Run dev server briefly to check Vue compilation:

```bash
npm run dev
```

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

- [ ] **Step 4: Commit**

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

---

## 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`

- [ ] **Step 1: Add AuditScopeSection import**

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

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

Replace with:

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

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

- [ ] **Step 2: Replace Tab 2 and Tab 4 in template with new merged Tab 2**

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

```vue
<!-- 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:

```vue
<!-- 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>
```

- [ ] **Step 3: Delete the old Tab 4 (Devices)**

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

```vue
<!-- 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).

- [ ] **Step 4: Verify final tab order**

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)

- [ ] **Step 5: Browser verification**

```bash
npm run dev
```

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

Verify:
- [ ] Three tabs visible: 基本資訊 / 受評範圍 / 參與者
- [ ] Tab 2 header shows count format `受評範圍 (3 系統 / 5 設備)` (numbers should match what was in the project before)
- [ ] Click Tab 2 → see two stacked sections: 資訊系統 (top) + 設備 (bottom)
- [ ] Both pickers show pre-selected items correctly
- [ ] Add a system → network tab shows `PUT /api/1.0/grc-project/<id>` with `audit_systems` body
- [ ] Add a device → network tab shows `PUT /api/1.0/grc-project/<id>` with `devices` body
- [ ] Both PUT requests succeed (200)
- [ ] Switch to other tab and back → state preserved
- [ ] Login as a non-manager user → both blocks render readonly

Stop the dev server.

- [ ] **Step 6: Commit**

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

---

## 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`

- [ ] **Step 1: Update imports**

Find these lines:

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

Replace with:

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

- [ ] **Step 2: Update `stepsItems` array (5 → 4 steps with new order)**

Find the existing `stepsItems` computed (line ~38):

```ts
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:

```ts
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)

- [ ] **Step 3: Update step content blocks in template (atomic, do all in one edit)**

⚠️ **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:

```vue
<!-- 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).

- [ ] **Step 4: Update Review section to merge Audit Systems + Devices into single Audit Scope block**

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

```vue
<!-- 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):

```vue
<!-- 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"`)

- [ ] **Step 5: Update `handleNext` validation gate (if any depends on step index)**

Look at `handleNext` (around line 108):

```ts
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):

```ts
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.

- [ ] **Step 6: Verify `handleCreate` payload still matches BE expectation**

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.**

- [ ] **Step 7: Browser verification**

```bash
npm run dev
```

Open `/project/projects/new`. Verify:

- [ ] Step indicator shows 4 steps: 基本資訊 → 參與者 → 受評範圍 → 確認
- [ ] Step 0: basic info form works (validation on Next)
- [ ] Step 1: ParticipantPicker works (add at least one participant)
- [ ] Step 2: AuditScopeSection visible with section header "受評範圍" and subtitle; both stacked sections work
- [ ] Step 2: add a system, add a device
- [ ] Step 3: review page shows 3 sections (basic / participants / audit scope); audit scope shows BOTH systems and devices sub-blocks correctly
- [ ] Click "編輯" on audit scope → returns to step 2
- [ ] Click "編輯" on participants → returns to step 1
- [ ] Click "編輯" on basic info → returns to step 0
- [ ] Click "建立專案" → POST `/oscal-project/start` succeeds (200), redirects to project overview

Empty-state check:
- [ ] Restart create flow, skip step 2 entirely (no system, no device) → still creates successfully

Stop the dev server.

- [ ] **Step 8: Commit**

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

---

## 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)

- [ ] **Step 1: Start dev server**

```bash
npm run dev
```

- [ ] **Step 2: ProjectAuditorOverview**

Open `/project/projects/<existing-uid>/auditor-overview`. Verify:
- [ ] Page renders normally
- [ ] Audit systems and devices info (if displayed) still appear correctly

- [ ] **Step 3: JobExecutionDrawer**

Open any project's job execution view, click into a job that has devices attached. Verify:
- [ ] Drawer opens
- [ ] Devices listed correctly
- [ ] No console errors

- [ ] **Step 4: DeviceManage independent page**

Open `/device/device-manage`. Verify:
- [ ] List loads
- [ ] Can create a new device
- [ ] Can edit an existing device

- [ ] **Step 5: InformationSystemManage independent page**

Open `/information-system/manage`. Verify:
- [ ] List loads
- [ ] Can create a new system
- [ ] Can edit an existing system

- [ ] **Step 6: Locale switching**

In Settings or anywhere with the language switcher, toggle between zh-tw and en. Verify:
- [ ] New tab/step labels translate correctly
- [ ] No raw key strings (e.g., `lang.project_settings.tab_audit_scope`) visible

Stop the dev server. No commit needed.

---

## 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)

- [ ] **Step 1: Search for references to old keys**

```bash
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.

- [ ] **Step 2: Verify new keys are wired correctly**

```bash
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.

- [ ] **Step 3: Document findings in PR description (later)**

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.

---

## 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`

- [ ] **Step 1: Switch to BE repo**

```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git status
```

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

- [ ] **Step 2: Create changelog file**

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

```markdown
# 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`
```

- [ ] **Step 3: Commit changelog**

```bash
git add docs/changelog/2026-04-25-audit-scope-merge.md
git commit -m "docs(changelog): audit-scope-merge 變更紀錄"
```

---

## Task 9: Final verification & PR prep

**Goal:** Final sanity pass before declaring done.

**Files:** None (verification only)

- [ ] **Step 1: Review FE commits**

```bash
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 步驟重構 + 合併「受評範圍」步驟`

- [ ] **Step 2: Review BE commits**

```bash
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 變更紀錄`

- [ ] **Step 3: Final cross-browser smoke test**

Run dev server one more time. Walk through:
- [ ] Create a new project end-to-end (4 steps, finish with create)
- [ ] Edit the new project's settings (verify audit scope tab works for both add + remove of systems and devices)

- [ ] **Step 4: Report completion**

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`.

---

## 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
