docs(02-02): complete tracer tab plan

This commit is contained in:
archipelago 2026-07-30 09:22:58 -04:00
parent 2668705516
commit a579556a4f
4 changed files with 239 additions and 11 deletions

View File

@ -101,7 +101,7 @@ Which phases cover which requirements. Updated during roadmap creation.
| FED-05 | Phase 1 | Pending |
| FED-06 | Phase 1 | Pending |
| PERF-01 | Phase 2 | Complete |
| PERF-02 | Phase 2 | Pending |
| PERF-02 | Phase 2 | In Progress (02-02 tracer done; 02-04..02-07 extend to remaining tabs) |
| PERF-03 | Phase 2 | Pending |
| MNODE-01 | Phase 3 | Pending |
| MNODE-02 | Phase 3 | Pending |

View File

@ -73,7 +73,7 @@ Plans:
3. Secondary screens open without a blocking full reload; repeat visits are instant
4. The fixes are verified on real node hardware (not just the dev box) — the sluggishness the user reported is gone on-device
**Plans**: 2/8 plans executed
**Plans**: 3/8 plans executed
Plans:
**Wave 1**
@ -82,7 +82,7 @@ Plans:
**Wave 2** *(blocked on Wave 1 completion)*
- [ ] 02-02-PLAN.md — TRACER: KeepAlive host, hook reactivation, app-store tab, refresh indicator (PERF-02)
- [x] 02-02-PLAN.md — TRACER: KeepAlive host, hook reactivation, app-store tab, refresh indicator (PERF-02)
- [x] 02-03-PLAN.md — Secondary screens: per-item cache, parallel loads, purge on logout (PERF-03)
**Wave 3** *(blocked on Wave 2 completion)*
@ -196,7 +196,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Federation & Mesh Hardening | 0/10 | Planned | - |
| 2. UI Performance | 2/8 | In Progress| |
| 2. UI Performance | 3/8 | In Progress| |
| 3. Multinode Verification Pass | 0/TBD | Not started | - |
| 4. Lifecycle Perfection & Quadlet Default | 0/TBD | Not started | - |
| 5. Registry-Distributed Manifests | 0/TBD | Not started | - |

View File

@ -5,15 +5,15 @@ milestone_name: milestone
current_phase: 02
current_phase_name: ui-performance
status: executing
stopped_at: Completed 02-03-PLAN.md
last_updated: "2026-07-30T12:27:28.785Z"
stopped_at: Completed 02-02-PLAN.md
last_updated: "2026-07-30T13:20:35.538Z"
last_activity: 2026-07-30
last_activity_desc: Phase 02 execution started
progress:
total_phases: 8
completed_phases: 0
total_plans: 18
completed_plans: 2
completed_plans: 3
percent: 0
---
@ -29,11 +29,11 @@ See: .planning/PROJECT.md (updated 2026-07-29)
## Current Position
Phase: 02 (ui-performance) — EXECUTING
Plan: 2 of 8
Plan: 3 of 8
Status: Ready to execute
Last activity: 2026-07-30 — Phase 02 execution started
Progress: [█░░░░░░░░░] 11%
Progress: [██░░░░░░░░] 17%
## Performance Metrics
@ -54,6 +54,7 @@ Progress: [█░░░░░░░░░] 11%
|------|----------|-------|-------|
| Phase 02 P01 | 100min | 3 tasks | 5 files |
| Phase 02 P03 | 45min | 3 tasks | 5 files |
| Phase 02 P02 | 105min | 3 tasks | 11 files |
## Accumulated Context
@ -79,6 +80,10 @@ Decisions are logged in PROJECT.md (10 locked ADRs in the `<decisions>` block +
- [Phase ?]: AppDetails/MarketplaceAppDetails/OpenWrtGateway converted to per-item (or single-key) keyed useCachedResource; CloudFolder.vue's existing store-level cache left as-is (cloud.ts TTL gate is a follow-up, out of this plan's file scope)
- [Phase ?]: Wallet/send flow (SendBitcoinModal.vue) reported as an unplanned-item gap — named by findings as owned by 02-03 but not in files_modified; its cost is pure client-side remount, not a caching problem
- [Phase ?]: PERF-03 reverted to Pending in REQUIREMENTS.md after an initial mark-complete was premature — its own text requires real-node-hardware verification, which is 02-08's job (also declares PERF-03); 02-03 delivers the code-level portion only
- [Phase ?]: 02-02: DashboardRouterView final shape uses statically-named per-route KeepAlive wrapper components (dashboardViewWrappers.ts) with :include name-matching, restoring pre-restructure view-wrapper DOM/animations byte-for-byte after a checkpoint-caught regression
- [Phase ?]: 02-02: HARD RULE for rest of Phase 02 — perf work must be visually invisible; verify against the real dev preview before considering a checkpoint satisfied
- [Phase ?]: 02-02: app-catalog persist:true ttl 300000ms; bitcoin.prune-status persist:true ttl 30000ms — both explicit per T-02-01, no default relied on
- [Phase ?]: 02-02: PERF-02 reverted to Pending/In-Progress in REQUIREMENTS.md after an automated mark-complete run — PERF-02 also spans 02-04..02-07 (extending KeepAlive caching to every remaining main tab); this plan proves the architecture on the tracer tab only
### Pending Todos
@ -113,6 +118,6 @@ None yet.
## Session Continuity
Last session: 2026-07-30T12:26:09.237Z
Stopped at: Completed 02-03-PLAN.md
Last session: 2026-07-30T13:19:36.903Z
Stopped at: Completed 02-02-PLAN.md
Resume file: None

View File

@ -0,0 +1,223 @@
---
phase: 02-ui-performance
plan: 02
subsystem: ui
tags: [vue, keepalive, vue-router, sessionstorage, useCachedResource, transitions]
# Dependency graph
requires:
- phase: 02-ui-performance
provides: "02-01's D-09 profiling findings (02-FINDINGS.md Ranked Fix Order) selecting the tracer tab"
provides:
- "Route-path KeepAlive classifier (shouldKeepAlive/KEEP_ALIVE_PATHS/KEEP_ALIVE_MAX) every later main-tab plan registers into"
- "DashboardRouterView.vue KeepAlive host with statically-named per-route wrapper components (dashboardViewWrappers.ts) preserving pre-restructure visuals/transitions exactly"
- "onActivated reactivation fix in useCachedResource.ts, live for all 9 consumers"
- "RefreshIndicator.vue subtle background-refresh affordance (state-driven, no layout shift)"
- "Marketplace.vue converted to cached resources (app-catalog, bitcoin.prune-status) as the reference pattern for onMounted/onActivated/onDeactivated side-effect audits"
affects: [02-04, 02-05, 02-06, 02-07, 02-08]
# Tech tracking
tech-stack:
added: []
patterns:
- "Route-path exact-match KeepAlive classification instead of name-based include/exclude (async components have no inferable name)"
- "Statically-named per-route KeepAlive wrapper components (KeepWrap:<path>) as the byte-for-byte-preserving bridge between :include name matching and the pre-existing view-wrapper DOM/animation contract"
- "onActivated(() => refreshIfStale()) as the standard reactivation hook alongside onScopeDispose in useCachedResource"
- "persist decided explicitly per cache key (never defaulted) per T-02-01"
key-files:
created:
- neode-ui/src/views/dashboard/keepAliveRoutes.ts
- neode-ui/src/views/dashboard/DashboardRouterView.vue
- neode-ui/src/views/dashboard/dashboardViewWrappers.ts
- neode-ui/src/components/RefreshIndicator.vue
- neode-ui/src/composables/__tests__/useCachedResource.test.ts
- neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts
- neode-ui/src/views/__tests__/MarketplaceRefresh.test.ts
modified:
- neode-ui/src/composables/useCachedResource.ts
- neode-ui/src/views/Dashboard.vue
- neode-ui/src/views/Marketplace.vue
- neode-ui/src/views/dashboard/useRouteTransitions.ts
key-decisions:
- "Tracer tab = Marketplace.vue (app store), the top-ranked entry in 02-FINDINGS.md Ranked Fix Order (worst-measured revisit, 2033ms) and the surface the user explicitly called out as slow"
- "DashboardRouterView.vue's final shape restores the pre-restructure rendered DOM exactly: Transition > KeepAlive(:include=[wrapper names]) > keyed per-route KeepWrap:<path> wrapper whose render emits the old view-wrapper markup byte-for-byte, because dashboard-styles.css scopes transitions as .{name}-enter-active.view-wrapper compound selectors"
- "Task-1 scroll-retention Map was deleted — kept-alive tabs keep scroll for free via their cached subtree; non-kept routes reset-to-top exactly as before the phase"
- "app-catalog persists (ttl 300000ms, non-sensitive/near-static, D-06); bitcoin.prune-status persists (ttl 30000ms, non-sensitive/small) — both explicit decisions per T-02-01, no default relied on"
- "HARD RULE for all remaining Phase 02 plans: perf work must be visually invisible — never change existing visuals/animations. keepAliveTabs.test.ts now pins the padded-wrapper-inside-view-wrapper DOM shape as a structural regression test"
patterns-established:
- "Per-route KeepAlive wrapper components with static names (KeepWrap:<path>), memoized in a factory keyed by route path, so :include can name-match without depending on async-component name inference (RESEARCH A1 sidestepped)"
- "Cache-key persist is always an explicit per-resource decision, never left at the composable's default"
- "onMounted/onActivated/onDeactivated side-effect audit convention: once-per-session setup stays in onMounted; cache-gated fetches need no per-view hook because useCachedResource's own onActivated revalidates them"
requirements-completed: []
requirements-note: "PERF-02 is NOT marked complete in REQUIREMENTS.md despite being this plan's sole `requirements:` entry — PERF-02 also appears in 02-04, 02-05, 02-06 and 02-07's frontmatter, which extend the KeepAlive/cache architecture proven here to every remaining main tab. This plan delivers the tracer (one tab) only; an automated `requirements.mark-complete PERF-02` run was reverted after cross-checking ROADMAP.md's plan list, mirroring the PERF-03 precedent set in 02-03-SUMMARY.md. Do not re-mark PERF-02 complete until 02-07 lands."
coverage:
- id: D1
description: "One main tab (Marketplace) renders instantly from a surviving component instance on tab round-trip, with no spinner/blank frame, scroll and search/category state preserved"
requirement: PERF-02
verification:
- kind: unit
ref: "neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts"
status: pass
- kind: manual_procedural
ref: "Task 3 checkpoint:human-verify, approved on :8100 dev preview against archi-dev after fix commit 26687055"
status: pass
human_judgment: true
rationale: "Visual/perceptual parity (no blank frame, animation identical to pre-change, margins intact) is a judgment call unit tests cannot fully prove; this is exactly what the checkpoint caught on first attempt"
- id: D2
description: "useCachedResource revalidates in the background exactly once on reactivation past TTL, and not at all within TTL; loadState transitions ready -> refreshing, never back to loading"
requirement: PERF-02
verification:
- kind: unit
ref: "neode-ui/src/composables/__tests__/useCachedResource.test.ts"
status: pass
human_judgment: false
- id: D3
description: "RefreshIndicator renders nothing for ready/idle/loading and a labeled aria-live=polite element for refreshing, with no layout shift"
requirement: PERF-02
verification:
- kind: unit
ref: "neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts"
status: pass
human_judgment: false
- id: D4
description: "A rejected background refresh on Marketplace.vue keeps prior content on screen and raises no toast (D-07)"
requirement: PERF-02
verification:
- kind: unit
ref: "neode-ui/src/views/__tests__/MarketplaceRefresh.test.ts"
status: pass
human_judgment: false
- id: D5
description: "Route transition animations (slide/depth) and page margins are unchanged from before the KeepAlive restructure"
requirement: PERF-02
verification:
- kind: unit
ref: "neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts (structural DOM-shape assertions)"
status: pass
- kind: manual_procedural
ref: "Task 3 checkpoint:human-verify, approved on :8100 dev preview against archi-dev after fix commit 26687055"
status: pass
human_judgment: true
rationale: "First checkpoint attempt failed on exactly this criterion (broken margins, dead slide animations); only a human eyeballing the real preview caught and confirmed the fix — this is inherently a visual judgment, not something a unit test alone can close out"
duration: 105min
completed: 2026-07-30
status: complete
---
# Phase 02 Plan 02: Tracer Tab — KeepAlive Host, Hook Reactivation, Refresh Indicator Summary
**Marketplace.vue survives tab round-trips via a route-path-classified KeepAlive host with statically-named per-route wrappers, a stale-while-revalidate `onActivated` fix in `useCachedResource`, and a subtle non-shifting refresh indicator — with the original page margins and slide/depth animations restored byte-for-byte after a checkpoint-caught regression**
## Performance
- **Duration:** 105 min
- **Started:** 2026-07-30T07:15:17-04:00
- **Completed:** 2026-07-30T08:40:27-04:00 (fix commit; checkpoint approved shortly after on dev preview)
- **Tasks:** 3 (Task 1 tracer, Task 2 auto, Task 3 checkpoint:human-verify)
- **Files modified:** 11 (7 created, 4 modified — see Files Created/Modified)
## Accomplishments
- **Tracer tab chosen: Marketplace** (`neode-ui/src/views/Marketplace.vue`) — the top entry in `02-FINDINGS.md`'s Ranked Fix Order (worst-measured revisit at 2033ms) and the exact surface the user called out as slow ("often app store").
- Route-path classifier (`keepAliveRoutes.ts`: `shouldKeepAlive`, `KEEP_ALIVE_PATHS` seeded with only the tracer tab's path, `KEEP_ALIVE_MAX=6`) deliberately avoids KeepAlive `include`/`exclude` name-matching, since every route is an async component with no inferable name (RESEARCH A1).
- `DashboardRouterView.vue` extracted from `Dashboard.vue`'s inline nested RouterView as the shared KeepAlive host every later plan builds on.
- `onActivated(() => refreshIfStale())` added to `useCachedResource.ts`, closing the reactivation gap for all 9 consumers (8 pre-existing + Marketplace).
- `RefreshIndicator.vue`: presentational, `state`-driven, renders nothing for `ready`/`idle`/`loading`, an `aria-live="polite"`-labeled element for `refreshing`, reserved-space so it never shifts layout.
- Marketplace's catalog and Bitcoin prune-status fetches moved onto keyed `useCachedResource` entries (`app-catalog`, `bitcoin.prune-status`), each with an explicit `persist` decision.
- **Checkpoint-caught regression and fix:** the first Task 3 verification on the real dev preview failed — outer page margins broke and the up/down slide animations for main-tab switches stopped playing. Root cause and fix are recorded in detail below and in commit `26687055`. Re-verified and approved by the user on the second pass.
## Task Commits
Each task was committed atomically:
1. **Task 1: One main tab survives a tab round-trip and revalidates on return** - `385c9d86` (feat, tdd)
2. **Task 2: Subtle refresh indicator and correct per-visit behavior on the tracer tab** - `a9a20039` (feat, tdd)
3. **Fix (post-checkpoint-failure): restore page margins and slide transitions broken by the restructure** - `26687055` (fix)
4. **Task 3: Confirm the tracer tab feels instant on the dev preview against archi-dev** - checkpoint:human-verify, approved on the :8100 dev preview against archi-dev after the fix above (no code commit — verification-only task)
**Plan metadata:** (this commit) - `docs(02-02): complete tracer tab plan`
_Note: Tasks 1 and 2 are TDD tasks; tests were written and made to pass within the same task commit per the repo's existing single-commit-per-task convention (see prior 02-01/02-03 history) rather than split into separate test/feat commits._
## Files Created/Modified
- `neode-ui/src/views/dashboard/keepAliveRoutes.ts` - Exact-match route classifier: `shouldKeepAlive`, `KEEP_ALIVE_PATHS` (tracer tab's path only), `KEEP_ALIVE_MAX` (6)
- `neode-ui/src/views/dashboard/DashboardRouterView.vue` - Extracted KeepAlive host; renders per-route `KeepWrap:<path>` wrapper components from `dashboardViewWrappers.ts` behind `<KeepAlive :include>`, keyed at the wrapper root
- `neode-ui/src/views/dashboard/dashboardViewWrappers.ts` - **New in the fix commit.** Memoized per-route-path factory of statically-named wrapper components (`KeepWrap:<path>`) whose render emits the pre-restructure `view-wrapper` markup byte-for-byte (full-bleed chat/mesh shape or padded default shape + spacer), so `:include` can name-match without relying on async-component name inference
- `neode-ui/src/composables/useCachedResource.ts` - Added `onActivated(() => refreshIfStale())` beside the existing `onScopeDispose` registration
- `neode-ui/src/views/dashboard/useRouteTransitions.ts` - `TAB_ORDER` promoted from module-private `const` to an export, for 02-04 to widen `KEEP_ALIVE_PATHS` from
- `neode-ui/src/views/Dashboard.vue` - Inline nested RouterView block replaced with `<DashboardRouterView>`
- `neode-ui/src/views/Marketplace.vue` - `loadCommunityMarketplace()`/`loadBitcoinPruneStatus()` moved onto `useCachedResource` (`app-catalog`, `bitcoin.prune-status`); `RefreshIndicator` wired to a resource `loadState`
- `neode-ui/src/components/RefreshIndicator.vue` - Presentational background-refresh affordance, `state: ResourceLoadState`, `label?: string`
- `neode-ui/src/composables/__tests__/useCachedResource.test.ts` - Reactivation revalidation, TTL-gated refetch, rejected-refresh keep-last-value coverage
- `neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts` - Round-trip mount/activation counts, detail-route exclusion, structural DOM-shape (view-wrapper/animation) pinning
- `neode-ui/src/views/__tests__/MarketplaceRefresh.test.ts` - **New file (deviation, see below).** Rejected-background-refresh-keeps-content/no-toast coverage for Marketplace, isolated from the real-router tests in `keepAliveTabs.test.ts`
## Decisions Made
- **Tracer tab: Marketplace.vue** — top of `02-FINDINGS.md` Ranked Fix Order (worst-measured revisit, 2033ms), matching the user's own "app store is slow" report.
- **Final `DashboardRouterView.vue` template shape (post-fix — differs from the plan's original design):** The plan's original structure (a stable outer wrapper div + `view-wrapper` class pushed onto each component root via fallthrough attrs, with `<KeepAlive>` nested inside that stable wrapper) broke page margins and killed every slide/depth animation on the real dev preview. Root cause: `dashboard-styles.css` scopes every transition as a compound selector — `.{transitionName}-enter-active.view-wrapper` — and `.view-wrapper` itself is `position: absolute; inset: 0`, both of which require `.view-wrapper` to be the *keyed, direct* child element that `<Transition>` toggles, not a class merged onto an arbitrary descendant. Splitting navigation across two sibling `<Transition>` branches behind a shared stable div broke that contract. Fix (commit `26687055`) restores the pre-restructure rendered DOM exactly: single `<Transition :name="getTransitionName(route)">``<KeepAlive :max="KEEP_ALIVE_MAX" :include="[wrapper names]">` → a keyed per-route wrapper component (`:key="route.path"`). The new file `dashboardViewWrappers.ts` holds a memoized per-route-path factory of statically-named wrapper components (`KeepWrap:<path>`) whose render emits the old markup byte-for-byte — a keyed `div.view-wrapper` root containing either the full-bleed chat/mesh shape or the padded-default shape plus the trailing spacer div. Caching is gated by `:include` name-matching against wrapper names derived from `KEEP_ALIVE_PATHS`; because those names are static (not inferred from the wrapped async component), the RESEARCH A1 name-inference problem does not apply. **Consequence for 02-04:** widening the instance cache is a one-line change — add paths to `KEEP_ALIVE_PATHS` only; the wrapper names and `:include` list derive from it automatically. The Task-1 per-route scroll-retention `Map` was **deleted** in the fix: kept-alive tabs now retain scroll for free via their cached subtree (the DOM literally never unmounts), and non-kept routes reset-to-top exactly as they did before this phase — no explicit tracking code needed.
- **HARD RULE for all remaining Phase 02 plans (user directive, given after the checkpoint failure):** never change existing visuals or animations — performance work must be visually invisible. `keepAliveTabs.test.ts` now includes a structural assertion pinning the padded-wrapper-inside-`view-wrapper` DOM shape as a regression backstop.
- **Side-effect audit (Marketplace.vue):** `marketplaceAnimationDone` (the one-shot intro flag) stays in `onMounted` — it is genuinely once-per-session. The catalog load and the prune-status load needed **no** per-view `onMounted`/`onActivated`/`onDeactivated` hooks of their own: `useCachedResource`'s internal `onActivated` (added in Task 1) already revalidates them, staleness-gated, on every kept-alive reactivation. This view has no intervals, subscriptions, or window listeners, so no `onDeactivated` teardown was required. (Precedent recorded here for 02-04's lifecycle audit across the remaining tabs.)
- **Persist decisions (T-02-01, explicit per key, no default relied on):** `app-catalog``persist: true`, `ttlMs: 300000` (non-sensitive, small, near-static catalog data per D-06 discretion). `bitcoin.prune-status``persist: true`, `ttlMs: 30000` (non-sensitive, small; default TTL).
- **Checkpoint:** Task 3's `checkpoint:human-verify` was approved by the user on the `:8100` dev preview against archi-dev, on the second attempt — after the margin/animation fix in `26687055` — confirming instant round-trip render, TTL-gated background revalidation with the subtle indicator, unaffected secondary-screen behavior, correct transition animation, and silent failure handling with the backend stopped.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Rejected-refresh test moved to a new dedicated file**
- **Found during:** Task 2 (indicator + per-visit behavior correction)
- **Issue:** Plan's acceptance criteria implied extending `keepAliveTabs.test.ts` with the rejected-background-refresh assertion, but that file uses a real Vue Router instance (`createRouter`/`createMemoryHistory`) for its round-trip mount-count tests; `vi.mock('vue-router')`, needed to isolate Marketplace's refresh behavior, hoists to the top of the file and would clobber those real-router tests.
- **Fix:** Created `neode-ui/src/views/__tests__/MarketplaceRefresh.test.ts` as a separate file, mirroring the existing `CloudPeersRefresh.test.ts` convention already in this codebase for the same class of problem.
- **Files modified:** neode-ui/src/views/__tests__/MarketplaceRefresh.test.ts (new)
- **Verification:** Rejected-refresh test passes in isolation and alongside the full suite; `keepAliveTabs.test.ts`'s real-router tests remain unaffected.
- **Committed in:** a9a20039 (Task 2 commit)
**2. [Rule 1 - Bug] Dropped the ad-hoc `AbortSignal.timeout(8000)` in favor of the composable's own abort-on-unmount**
- **Found during:** Task 2 (Marketplace.vue conversion to cached resources)
- **Issue:** The pre-conversion `loadBitcoinPruneStatus()` used a manual `AbortSignal.timeout(8000)` on its fetch. Once the call moved behind `useCachedResource`, that manual timeout duplicated/conflicted with the composable's built-in abort-on-unmount signal.
- **Fix:** Removed the manual timeout, matching the existing `Cloud.vue` convention for cached fetches in this codebase.
- **Files modified:** neode-ui/src/views/Marketplace.vue
- **Verification:** `npm run test` and `npm run type-check` green; behavior matches the in-repo `Cloud.vue` reference pattern the plan named.
- **Committed in:** a9a20039 (Task 2 commit)
**3. [Rule 1 - Bug, caught by checkpoint] Restructured KeepAlive host broke page margins and slide/depth transitions**
- **Found during:** Task 3 (first checkpoint:human-verify attempt)
- **Issue:** The Task-1-built `DashboardRouterView.vue` (stable outer wrapper + `view-wrapper` fallthrough onto each component root, two sibling `<Transition>` branches) broke outer page margins entirely and killed every up/down main-tab slide animation, because `dashboard-styles.css` requires `.view-wrapper` to be the keyed, direct child that `<Transition>` toggles (compound selectors like `.{name}-enter-active.view-wrapper`; `.view-wrapper` is `position:absolute;inset:0`).
- **Fix:** Restored the pre-restructure rendered DOM exactly via a single `<Transition>``<KeepAlive :include>` → keyed statically-named per-route wrapper component (new `dashboardViewWrappers.ts`), as detailed in Decisions Made above. Deleted the now-unnecessary manual scroll-retention `Map`.
- **Files modified:** neode-ui/src/views/dashboard/DashboardRouterView.vue, neode-ui/src/views/dashboard/dashboardViewWrappers.ts (new), neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts (added structural DOM-shape assertions)
- **Verification:** Full test suite green; re-verified on the `:8100` dev preview against archi-dev — margins and slide/depth animations confirmed identical to pre-change; checkpoint approved.
- **Committed in:** 26687055
---
**Total deviations:** 3 auto-fixed (2 Rule 1 test/implementation-detail bugs in Task 2, 1 Rule 1 bug caught by the Task 3 checkpoint and fixed before re-verification)
**Impact on plan:** All three were necessary corrections within the plan's own stated files/scope. The Task 3 fix is the most consequential — it establishes the wrapper-component pattern (`dashboardViewWrappers.ts`) that 02-04 through 02-07 must reuse rather than re-deriving the original Task-1 design, and it establishes the hard "no visual change" rule for the rest of the phase. No scope creep.
## Issues Encountered
- First Task 3 verification attempt failed on the real dev preview (broken margins, dead slide animations) — see Deviation 3 above. Resolved by restoring the pre-restructure DOM shape via statically-named wrapper components; re-verified and approved on the second attempt.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- The shared architecture (route classifier, `DashboardRouterView.vue` host, `dashboardViewWrappers.ts` factory, `onActivated` hook fix, `RefreshIndicator.vue`) is proven end-to-end on one tab and ready for 02-04 to widen `KEEP_ALIVE_PATHS` (a one-line change; wrapper names and `:include` derive from it automatically).
- The onMounted/onActivated/onDeactivated side-effect audit precedent (Marketplace.vue: nothing needed beyond the composable's own reactivation) gives 02-04 a concrete template for auditing the remaining tabs.
- HARD RULE for the rest of Phase 02: perf work must be visually invisible — verify against the real dev preview, not just unit tests, before considering a plan's checkpoint satisfied.
- No blockers for 02-04.
---
*Phase: 02-ui-performance*
*Completed: 2026-07-30*
## Self-Check: PASSED