diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 6de72149..d8c89d20 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -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 | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 220be077..374f03f5 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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 | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 60bf4996..7d635789 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -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 `` 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 diff --git a/.planning/phases/02-ui-performance/02-02-SUMMARY.md b/.planning/phases/02-ui-performance/02-02-SUMMARY.md new file mode 100644 index 00000000..395d53cc --- /dev/null +++ b/.planning/phases/02-ui-performance/02-02-SUMMARY.md @@ -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:) 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: 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:), 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:` wrapper components from `dashboardViewWrappers.ts` behind ``, 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:`) 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 `` +- `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 `` 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 `` toggles, not a class merged onto an arbitrary descendant. Splitting navigation across two sibling `` branches behind a shared stable div broke that contract. Fix (commit `26687055`) restores the pre-restructure rendered DOM exactly: single `` → `` → 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:`) 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 `` 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 `` 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 `` → `` → 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