docs(02-12): plan the KeepAlive entrance-animation-replay gap closure
02-11 named a second, distinct cause of Discover's revisit slowness with full profiling evidence (card-stagger/home-card-animate classes never removed from the DOM, so Chromium restarts the CSS entrance animation on every KeepAlive reattach) but deliberately left it unfixed — the blast radius (Discover/Apps/Marketplace/Home + Web5 sub-cards) exceeded that plan's scope. This gap plan fixes every affected site with one shared composable rather than per-file patches, decouples Home.vue's dual-purpose animateCards ref so its overlay/EasyHome visibility logic stays untouched, and ends at a blocking human-verify checkpoint before any SUMMARY is written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4435f95ef3
commit
59798ebff5
@@ -0,0 +1,265 @@
|
||||
---
|
||||
phase: 02-ui-performance
|
||||
plan: 12
|
||||
type: execute
|
||||
wave: 9
|
||||
depends_on: ["02-11"]
|
||||
files_modified:
|
||||
- neode-ui/src/composables/useEntranceStagger.ts
|
||||
- neode-ui/src/composables/__tests__/useEntranceStagger.test.ts
|
||||
- neode-ui/src/views/Discover.vue
|
||||
- neode-ui/src/views/Apps.vue
|
||||
- neode-ui/src/views/Marketplace.vue
|
||||
- neode-ui/src/views/web5/Web5.vue
|
||||
- neode-ui/src/views/Home.vue
|
||||
- neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts
|
||||
- .planning/phases/02-ui-performance/02-FINDINGS.md
|
||||
- .planning/phases/02-ui-performance/02-PERF-CARD-STAGGER.json
|
||||
- .planning/REQUIREMENTS.md
|
||||
autonomous: false
|
||||
gap_closure: true
|
||||
requirements: [PERF-02]
|
||||
must_haves:
|
||||
truths:
|
||||
- "Every `card-stagger`/`home-card-animate` entrance-animation site named in 02-11-SUMMARY.md's blast radius (Discover.vue, Apps.vue, Marketplace.vue, Home.vue, plus the Web5 sub-cards that consume Web5.vue's flag) is fixed by ONE shared mechanism, not seven ad-hoc patches"
|
||||
- "First genuine mount of a session still plays the entrance animation exactly as before — no visual/timing/easing change to the animation itself, only to whether it replays on a later KeepAlive reactivation"
|
||||
- "A KeepAlive deactivate/reactivate round-trip on a surviving instance never re-applies the entrance class — proven with a regression test that fails on the pre-fix code and passes after"
|
||||
- "A navigate-away mid-animation, then a later return, never leaves a card half-animated or invisible — the class comes off on deactivate regardless of whether the animation had finished"
|
||||
- "Home.vue's dual-purpose `animateCards` ref (gates both the welcome-overlay visibility AND the entrance-class trigger) is not itself touched — a separate ref carries the entrance-only concern so the overlay/EasyHome visibility contract this phase's own checkpoint history flagged as fragile stays provably unchanged"
|
||||
- "keepAliveTabs.test.ts stays byte-for-byte unmodified and green — this plan touches animation-replay timing only, never the structural DOM shape or the dashboard's own tab slide/depth transitions"
|
||||
- "Discover, Apps, Marketplace and Home are all re-measured on archi-dev-box with the frozen harness after the fix; none regresses past its 02-11 FINAL number"
|
||||
artifacts:
|
||||
- "neode-ui/src/composables/useEntranceStagger.ts — the single shared mechanism (an entrance-stagger flag that clears itself on KeepAlive deactivation)"
|
||||
- ".planning/phases/02-ui-performance/02-FINDINGS.md — new subsection recording the fix, its regression tests, and the re-measured numbers"
|
||||
- ".planning/phases/02-ui-performance/02-PERF-CARD-STAGGER.json — post-fix 5-run/surface re-measure against archi-dev-box"
|
||||
key_links:
|
||||
- "useEntranceStagger.ts -> Discover.vue/Apps.vue/Marketplace.vue/Web5.vue (arm-at-setup-time usage) and Home.vue (manual arm() from the welcome-typing watcher, decoupled from animateCards)"
|
||||
- "02-PERF-CARD-STAGGER.json -> 02-PERF-FINAL.json (02-11) — same four surfaces, same harness, joined by surface `id`"
|
||||
---
|
||||
|
||||
<objective>
|
||||
02-11 profiled Discover's revisit cost directly (not guessed) and found a SECOND, distinct cause beyond the three leaked pollers it fixed: the `card-stagger`/`home-card-animate` entrance-animation classes are baked into the DOM at first mount and never programmatically removed. Chromium restarts `animation-fill-mode:forwards` CSS animations on DOM reattachment — independent of Vue's own reactivity — so every KeepAlive detach/reactivate cycle replays the entrance animation on a revisit, even though the JS-level "once per session" flag governing each view is itself correct and untouched. 02-11 named this cause with full profiling evidence (a diagnostic showing Discover's card count transiently doubling 19->34->19 on every single revisit, and an `animationstart`/`animationend` event log spanning the whole revisit window) but deliberately left it unfixed — the blast radius (Discover.vue, Apps.vue, Marketplace.vue, Home.vue, and several Web5 sub-cards) exceeded that plan's scope and needed its own real-device verification budget.
|
||||
|
||||
This plan closes that gap. It is a bug fix, not a design change: the entrance animation is correct and stays exactly as-is on a genuine first visit. What's wrong is that it replays on a revisit to an already-visited, KeepAlive-cached tab — which directly contradicts this phase's own approved acceptance criteria ("switching between main tabs renders the target view immediately from cached state ... no blank screens or long spinners on tabs already visited this session"). Stopping the replay restores the intended behavior; it does not redesign anything. Do not touch the animation's keyframes, durations, easing, or the final visual look of any card — only whether the browser is ever given a reason to run it twice.
|
||||
|
||||
Fix every affected site with ONE shared mechanism (a composable), not seven independent patches — 02-11-SUMMARY.md explicitly flagged that "seven copies of this logic will drift" as the reason to scope this as a dedicated follow-up with its own verification budget rather than a quick multi-file patch.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@CLAUDE.md
|
||||
|
||||
Read in full:
|
||||
- `.planning/phases/02-ui-performance/02-11-SUMMARY.md` — the evidence and the named blast radius (search "Discover's second cause").
|
||||
- `.planning/phases/02-ui-performance/02-FINDINGS.md`, the section starting `### Discover: a second, distinct cause found` — the exact mechanism, the diagnostic proving it (card-count doubling, animation event log), and why 02-11 didn't fix it.
|
||||
- `neode-ui/src/views/Discover.vue`, `neode-ui/src/views/Apps.vue`, `neode-ui/src/views/Marketplace.vue`, `neode-ui/src/views/web5/Web5.vue`, `neode-ui/src/views/Home.vue` — every current `*AnimationDone`/`animateCards` site.
|
||||
- `neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts` — the established test pattern (synthetic consumer components using the exact `onActivated`/`onDeactivated` idiom, plus at least one test against a real converted view using `vm.$.uid`/`findComponent(...).props(...)` for instance-identity assertions that sidestep CSS-selector ambiguity).
|
||||
|
||||
Read only the relevant part of these (they are large):
|
||||
- `.planning/phases/02-ui-performance/02-02-SUMMARY.md` — search "HARD RULE" and "checkpoint" — the KeepAlive wrapper architecture, and why this phase's own Task 3 checkpoint history treats visual/animation regressions as the single highest-risk failure mode for exactly this class of change (broken margins, dead transitions, caught only by a human eyeballing the real preview).
|
||||
|
||||
Do not re-read `02-PERF-BASELINE.json`/`02-PERF-AFTER.json`/`02-PERF-REMEASURE.json`/`02-PERF-FINAL.json` in full — extract the Discover/Apps/Marketplace/Home rows with `node -e`/`jq` if a specific number is needed.
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Build the shared entrance-stagger composable and prove its mechanism in isolation</name>
|
||||
<files>neode-ui/src/composables/useEntranceStagger.ts, neode-ui/src/composables/__tests__/useEntranceStagger.test.ts</files>
|
||||
<action>
|
||||
Create `neode-ui/src/composables/useEntranceStagger.ts`. It must expose a reactive flag and an `arm()` function, and must clear the flag to `false` in `onDeactivated` — before any later reactivation can hand a stale class to the browser:
|
||||
|
||||
```ts
|
||||
import { ref, onDeactivated } from 'vue'
|
||||
|
||||
export function useEntranceStagger() {
|
||||
const showStagger = ref(false)
|
||||
|
||||
// Chromium restarts `animation-fill-mode: forwards` CSS animations on DOM
|
||||
// reattachment (a KeepAlive detach/reactivate cycle), independent of Vue's
|
||||
// own reactivity — see 02-FINDINGS.md "Discover: a second, distinct cause".
|
||||
// Clearing this flag the instant the owning instance deactivates removes
|
||||
// the class before any later reattachment has anything left to restart.
|
||||
// A navigate-away mid-animation is handled identically: the class comes
|
||||
// off immediately, which resolves to the CSS's own already-designed
|
||||
// "no animation, fully shown" end state (see e.g. Home.vue's
|
||||
// `.home-card:not(.home-card-animate)` rule) — visible only off-screen,
|
||||
// never as an on-screen flash, because it only ever fires on deactivate.
|
||||
onDeactivated(() => {
|
||||
showStagger.value = false
|
||||
})
|
||||
|
||||
return {
|
||||
showStagger,
|
||||
arm: () => { showStagger.value = true },
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Two call idioms this composable must support (both are needed by Task 2, prove both here):
|
||||
1. **Arm synchronously in `<script setup>`'s own body**, guarded by the caller's existing module-scoped once-per-session flag (`if (!xAnimationDone) { xAnimationDone = true; arm() }`) — this must take effect before the component's first render, so the entrance class is present at first paint exactly as it is today.
|
||||
2. **Arm later, from inside a watcher or event handler** (Home.vue's welcome-typing sequence starts the animation only after login, not at mount) — `arm()` is safe to call any time before the component next deactivates.
|
||||
|
||||
Write `neode-ui/src/composables/__tests__/useEntranceStagger.test.ts`, mirroring `keepAliveLifecycle.test.ts`'s own synthetic-consumer style (a small `defineComponent` mounted inside a real `KeepAlive`, `vi.useFakeTimers()` not needed here). Cover, against the REAL composable (not mocked):
|
||||
- Calling `arm()` during setup makes `showStagger.value` true before the first render (assert on the rendered class, not just the ref).
|
||||
- Deactivating an armed instance (toggle the `KeepAlive` branch) clears `showStagger` to `false`.
|
||||
- Reactivating that SAME instance afterward does not re-set `showStagger` to `true` (no automatic re-arm — the caller decides if/when to arm again).
|
||||
- A genuinely different component instance (its own `arm()` never yet called) starts with `showStagger` false and becomes true only when ITS OWN `arm()` runs — proving the flag is per-instance, not shared global state.
|
||||
- A deactivate that happens mid-"animation" (i.e., before any natural completion signal) still clears the flag — the composable has no dependency on `animationend` at all, so this is inherent, but assert it explicitly as a named regression case.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd neode-ui && npx vitest run src/composables/__tests__/useEntranceStagger.test.ts 2>&1 | tail -25</automated>
|
||||
</verify>
|
||||
<done>useEntranceStagger.ts exists, exports `showStagger`/`arm`, clears on `onDeactivated`, and its own test file proves the full contract (arms before first render, clears on deactivate, does not self-reapply on reactivate, is per-instance) against the real composable.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Wire the composable into every affected view, without touching the animation itself or Home's overlay logic</name>
|
||||
<files>neode-ui/src/views/Discover.vue, neode-ui/src/views/Apps.vue, neode-ui/src/views/Marketplace.vue, neode-ui/src/views/web5/Web5.vue, neode-ui/src/views/Home.vue, neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts</files>
|
||||
<behavior>
|
||||
First mount of Discover/Apps/Marketplace/Web5/Home plays its entrance animation exactly as today (byte-identical visual result — same classes, same CSS, same timing). A KeepAlive deactivate/reactivate round-trip on any of these five views never re-applies the entrance class to the surviving instance. A genuinely fresh component instance (a new mount, e.g. after LRU eviction, or a synthetic test double standing in for one) still gets the class applied on ITS first mount.
|
||||
</behavior>
|
||||
<action>
|
||||
**Discover.vue, Apps.vue, Marketplace.vue, Web5.vue** — identical pattern in each (only the module-scoped flag's name differs: `discoverAnimationDone`/`appsAnimationDone`/`marketplaceAnimationDone`/`web5AnimationDone`). Replace:
|
||||
|
||||
```ts
|
||||
const showStagger = !xAnimationDone
|
||||
```
|
||||
with:
|
||||
```ts
|
||||
const { showStagger, arm: armXStagger } = useEntranceStagger()
|
||||
if (!xAnimationDone) {
|
||||
xAnimationDone = true
|
||||
armXStagger()
|
||||
}
|
||||
```
|
||||
and delete the now-redundant `xAnimationDone = true` line from that view's existing `onMounted` block (Apps.vue's and Web5.vue's `onMounted` blocks do other unrelated work too — e.g. `armConnectionGuard()`, the DID lookup, `armWeb5Live()` — leave everything else in those blocks untouched). `showStagger` stays a plain identifier referenced directly in the template (`:show-stagger="showStagger"` / `:showStagger="showStagger"` / `:stagger="showStagger"`) — Vue's `<script setup>` compiler auto-unwraps a top-level ref referenced by name, so no template edits are needed in these four files, and no prop-type changes are needed in any child component (`AppGrid.vue`, `FeaturedApps.vue`, `AppCard.vue`, `MarketplaceAppCard.vue`, `Web5Wallet.vue`, `Web5Identities.vue`, `Web5NodeVisibility.vue`, `Web5NostrRelays.vue`, `Web5QuickActions.vue`, `Web5SharedContent.vue`, `Web5Domains.vue`) — they already declare `showStagger`/`stagger` as a reactive `boolean` prop and will re-render (removing their own `card-stagger` class) the instant the parent's ref flips to `false`. Confirm this by reading each child's prop declaration before assuming it — do not add a change to a file that doesn't need one.
|
||||
|
||||
**Home.vue** — do NOT reuse `animateCards` for this composable; it has a second, load-bearing duty (gating `showWelcomeBlock && !animateCards` visibility on several elements, and the `:animate`/`:show` props passed to `EasyHome`) that must stay completely untouched. Add a second, independent flag:
|
||||
|
||||
```ts
|
||||
const { showStagger: cardEntranceStagger, arm: armCardEntrance } = useEntranceStagger()
|
||||
```
|
||||
|
||||
In the `watch(() => loginTransition.startWelcomeTyping, ...)` handler, alongside the existing `animateCards.value = true`, add `armCardEntrance()`. Then retarget ONLY the five `:class="{ 'home-card-animate': animateCards, ... }"` template bindings (the ones that also gate `home-card-animate`) to read `cardEntranceStagger` instead of `animateCards` for that specific class key — leave every other use of `animateCards` in the template (the `opacity-0 pointer-events-none` overlay bindings, `EasyHome`'s `:animate`/`:show` props) exactly as they are, unchanged, same identifier. Where a single `:class` binding currently has both `'home-card-animate': animateCards` and `'opacity-0 pointer-events-none': showWelcomeBlock && !animateCards` in the same object (line ~247), only the `home-card-animate` key's value changes; the `opacity-0 pointer-events-none` key's condition keeps reading `animateCards` unchanged.
|
||||
|
||||
**Regression tests** — add a new `describe('keepAliveLifecycle: 02-12 gap closure — entrance-stagger class no longer replays on KeepAlive reactivation', ...)` block to `keepAliveLifecycle.test.ts`, following its existing dual-coverage convention (a synthetic mechanism test plus a real-view test):
|
||||
1. A synthetic test using the real `useEntranceStagger()` composable directly (not Discover.vue) inside a `KeepAlive`-wrapped host, mirroring the file's existing "one-shot intro flag" test shape: mount, `arm()` on setup, assert the rendered class is present; deactivate + reactivate the SAME instance, assert the class stays absent; then mount a SEPARATE fresh instance (its own `arm()` call) and assert the class IS present on that new instance — the three explicit assertions this gap closure must prove (first mount applies; surviving-instance round-trip does not reapply; a genuinely fresh mount does).
|
||||
2. A real-view test: mount the real `DashboardRouterView` + real `Discover.vue` at `/dashboard/discover` (stub or mock whatever Discover.vue's setup needs to complete without a real network — mirror how the file already stubs Server.vue's dependencies), do the away-hop to a synthetic `/dashboard/settings` and back, and assert on `wrapper.findComponent(AppGrid).props('showStagger')` (or `FeaturedApps`, whichever is simpler to reach given the stubs chosen): `true` immediately after first mount, `false` after the round-trip, on the SAME `Discover` instance (pin instance identity via `vm.$.uid` too, exactly like the file's existing Server/Web5 tests, so a false pass from an accidental remount is ruled out).
|
||||
|
||||
Run the full suite, type-check, and build; confirm `keepAliveTabs.test.ts` is byte-for-byte unmodified (`git diff --stat`) and still green; grep the built bundle for a string unique to `useEntranceStagger.ts` (e.g. a distinctive local variable name survives minification poorly — instead grep for the new composable's file being present in the build's chunk manifest, or grep the AppGrid/FeaturedApps/Web5 chunk for a substring only introduced by this change) to rule out the silent-no-op-build hazard CLAUDE.md warns about.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd neode-ui && npm test 2>&1 | tail -20</automated>
|
||||
<automated>cd neode-ui && npm run type-check</automated>
|
||||
<automated>cd neode-ui && npm run build 2>&1 | tail -10</automated>
|
||||
<automated>git diff --stat -- neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts (must print nothing)</automated>
|
||||
</verify>
|
||||
<done>Discover.vue, Apps.vue, Marketplace.vue, Web5.vue and Home.vue all use the shared composable; Home's `animateCards` is provably untouched (a decoupled second ref carries the entrance-only concern); the new keepAliveLifecycle.test.ts describe block proves first-mount/no-replay-on-reactivate/fresh-mount-does-replay against both the composable directly and a real Discover.vue; full suite/type-check/build green; keepAliveTabs.test.ts unmodified.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Deploy frontend-only to archi-dev-box and re-measure Discover, Apps, Marketplace and Home with the frozen harness</name>
|
||||
<files>.planning/phases/02-ui-performance/02-PERF-CARD-STAGGER.json, .planning/phases/02-ui-performance/02-FINDINGS.md, .planning/REQUIREMENTS.md</files>
|
||||
<precondition>No `cargo`/`rustc` process is active on this shared box (`pgrep -af 'cargo|rustc'`) — this is a frontend-only deploy and re-measure, but a concurrent build still saturates CPU and would corrupt the timing numbers. Re-check immediately before the deploy and again immediately before the harness run.</precondition>
|
||||
<action>
|
||||
Confirm `git diff --stat 3ee20430 -- neode-ui/e2e/perf/surfaces.ts neode-ui/e2e/perf/measure.ts neode-ui/e2e/perf/surface-perf.spec.ts` prints nothing — the frozen harness stays untouched by this plan.
|
||||
|
||||
Deploy frontend-only from THIS checkout (the main checkout — never a worktree; this box is archi-dev-box itself over loopback, and `deploy-to-target.sh` already refuses a worktree-sourced deploy via its own guard, which must not be modified or bypassed):
|
||||
```bash
|
||||
ARCHIPELAGO_TARGET=archipelago@archi-dev-box scripts/deploy-to-target.sh --frontend-only
|
||||
```
|
||||
Confirm the post-deploy health check passes and the deploy history records `dirty=false` at Task 2's last commit hash.
|
||||
|
||||
Re-run the frozen harness exactly as prior gap-closure plans did:
|
||||
```bash
|
||||
cd neode-ui && ARCHY_BASE_URL=http://archi-dev-box ARCHY_PERF_RUNS=5 \
|
||||
ARCHY_PERF_OUT=../.planning/phases/02-ui-performance/02-PERF-CARD-STAGGER.json \
|
||||
npx playwright test e2e/perf/surface-perf.spec.ts --project=chromium --reporter=line
|
||||
```
|
||||
(`ARCHY_PASSWORD` must be exported.) Confirm the frozen-harness `git diff --stat` gate above is STILL empty after the run.
|
||||
|
||||
Extract Discover/Apps/Marketplace/Home's rows from `02-PERF-CARD-STAGGER.json` and compare against their 02-11 `02-PERF-FINAL.json` numbers (Discover 1389ms; Apps/Marketplace/Home were not regressed by 02-11, record their FINAL numbers as the no-regression bar). Report min/median/max, not a bare median.
|
||||
|
||||
Write a `### Discover second-cause fix: re-measured (gap closure, 02-12)` subsection into `02-FINDINGS.md`: the fix (one sentence), the before number (1389ms, 02-11 FINAL) and the after number, and the same for Apps/Marketplace/Home (their FINAL numbers as the floor they must not regress below). State plainly whether Discover now meets the phase's <300ms bar, and if not, whether the residual is a further defect or a separately-named, already-documented cost (e.g. the per-mount `useCachedResource` setup cost 02-11 already attributed elsewhere).
|
||||
|
||||
Update `.planning/REQUIREMENTS.md`'s PERF-02 row to reflect this plan's outcome (the second Discover cause is now fixed and re-measured, not merely named).
|
||||
|
||||
Commit this task's docs+artifact changes (path-scoped `git add`, Co-Authored-By trailer) but do NOT push yet — Task 4's checkpoint must be approved first, per this plan's own `autonomous: false` gate.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>test -f .planning/phases/02-ui-performance/02-PERF-CARD-STAGGER.json && node -e "const r=require('./.planning/phases/02-ui-performance/02-PERF-CARD-STAGGER.json'); console.log('baseUrl:', r.baseUrl, 'runs:', r.runs)"</automated>
|
||||
<automated>git diff --stat 3ee20430 -- neode-ui/e2e/perf/surfaces.ts neode-ui/e2e/perf/measure.ts neode-ui/e2e/perf/surface-perf.spec.ts (must print nothing)</automated>
|
||||
<automated>grep -n "Discover second-cause fix" .planning/phases/02-ui-performance/02-FINDINGS.md</automated>
|
||||
</verify>
|
||||
<done>02-PERF-CARD-STAGGER.json exists (5 runs/surface, archi-dev-box, frozen harness). 02-FINDINGS.md records Discover/Apps/Marketplace/Home's before/after numbers with an explicit verdict for Discover against the <300ms bar. REQUIREMENTS.md's PERF-02 row reflects the fix. Committed, not yet pushed.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 4: Human verification on archi-dev-box — animation behavior across every touched surface</name>
|
||||
<what-built>
|
||||
The `card-stagger`/`home-card-animate` entrance-animation replay-on-KeepAlive-reactivation bug (02-11's evidenced-but-unfixed second cause on Discover) is fixed with one shared composable (`useEntranceStagger.ts`) applied to Discover.vue, Apps.vue, Marketplace.vue, Web5.vue, and Home.vue (via a decoupled second ref that leaves `animateCards`'s overlay/EasyHome duties untouched). Deployed frontend-only to archi-dev-box and re-measured with the frozen harness.
|
||||
|
||||
**Before/after (median revisit ms, archi-dev-box, frozen harness, 5 runs):**
|
||||
- Discover: 1389ms (02-11 FINAL) -> [fill in this task's measured number before presenting this checkpoint]
|
||||
- Apps / Marketplace / Home: 02-11 FINAL numbers (no-regression floor) -> this task's measured numbers
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
On archi-dev-box (`http://archi-dev-box`), in a fresh browser session (private/incognito, or a cleared session, so "first visit this session" is genuine):
|
||||
|
||||
1. **First visit to Discover, Apps, Marketplace, and Home**: confirm each still plays its entrance animation exactly as before — same stagger timing, same fade/fly-in look, nothing removed or shortened.
|
||||
2. **Switch away from each tab and back** (e.g. Discover -> Settings -> Discover; Home -> Discover -> Home): confirm the content is simply there on return — no re-stagger, no re-fade, no visible flicker or flash.
|
||||
3. **The dashboard's own tab slide/depth transitions still play**, and page margins are unchanged on every tab — these are the two things a previous change in this same phase (02-02) broke on its first attempt, and this plan does not touch `keepAliveTabs.test.ts` or the KeepAlive host itself, but a human check on the real preview is the only thing that can confirm a CSS-animation-adjacent change didn't have a side effect.
|
||||
4. **Navigate away from Discover/Apps/Marketplace/Home mid-animation** (click away within ~200ms of arriving) **and then return**: confirm nothing is left half-animated, invisible, or stuck at a partial opacity — cards should simply be fully visible on return, matching their finished state.
|
||||
5. **Home specifically**: log in fresh (or trigger the welcome-typing sequence if reachable) and confirm the welcome banner/typing effect and the home-card reveal still behave exactly as before, and that switching away from Home and back after the typing sequence has completed does not hide or fade the home cards again.
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "verified" (or describe anything that looks wrong) to approve. On approval: the executor pushes Task 2/3's commits (`git push gitea-ai main`), then writes 02-12-SUMMARY.md and updates STATE.md/ROADMAP.md/REQUIREMENTS.md's tracking exactly as any other completed plan. No SUMMARY.md is written before this checkpoint is approved.</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| fixed build -> `/opt/archipelago/web-ui` on a real node | Task 3's frontend-only deploy to archi-dev-box |
|
||||
| operator workstation -> archi-dev-box UI login | `ARCHY_PASSWORD` crosses this boundary at runtime to drive the harness re-measure |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-02-12-01 | Information Disclosure | `ARCHY_PASSWORD` | high | mitigate | Environment variable only, never inlined into a committed command, never echoed into 02-FINDINGS.md, the plan, or the SUMMARY. |
|
||||
| T-02-12-02 | Tampering | `keepAliveTabs.test.ts` (visual/structural contract) | high | mitigate | Task 2 explicitly forbids editing this file; `git diff --stat` on this specific file is a hard verify gate in Task 2. |
|
||||
| T-02-12-03 | Tampering | frozen harness (`surfaces.ts`/`measure.ts`/`surface-perf.spec.ts`) | medium | mitigate | `git diff --stat` gate before and after Task 3's harness run, identical to every prior gap-closure plan this phase. |
|
||||
| T-02-12-04 | Tampering | `scripts/deploy-to-target.sh` worktree-safety guard | critical | mitigate | This plan runs from the main checkout only (per its own hard constraints); the guard itself is never modified or bypassed. |
|
||||
| T-02-12-05 | Denial of Service (regression) | Home.vue's `showWelcomeBlock`/`animateCards`-gated overlay visibility | high | mitigate | A separate, decoupled ref (`cardEntranceStagger`) carries the entrance-only concern; `animateCards` itself is never reassigned or read differently by this plan — verified by `git diff` review of Home.vue showing only additive lines plus five retargeted `:class` keys, not a rename or removal of any `animateCards` read site. |
|
||||
| T-02-12-SC | Tampering | npm/pip/cargo installs | high | accept | No new package-manager dependency is needed for a Vue composable or its tests. If one becomes necessary, halt and route through the Package Legitimacy Gate. |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- Every `card-stagger`/`home-card-animate` site named in 02-11-SUMMARY.md's blast radius is fixed by the one shared composable, not per-file patches.
|
||||
- A regression test (both the synthetic composable-level test and a real-Discover.vue test) fails on the pre-fix code and passes after, proving: first mount applies the class; a surviving instance's deactivate/reactivate round-trip does not reapply it; a genuinely fresh instance does.
|
||||
- Full vitest suite, type-check, and build are green; `keepAliveTabs.test.ts` is byte-for-byte unmodified and still passing.
|
||||
- Discover, Apps, Marketplace, and Home are re-measured on archi-dev-box with the frozen harness (git diff --stat confirms it untouched); none regresses below its 02-11 FINAL number.
|
||||
- A human confirms, on the real archi-dev-box preview, that first-visit animations are unchanged, revisits show no replay, the dashboard's own tab transitions and page margins are unaffected, and a mid-animation navigate-away/return leaves nothing half-animated.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Discover's second, previously-evidenced-but-unfixed cause (CSS entrance-animation replay on KeepAlive reactivation) is fixed and re-measured, not merely named.
|
||||
- Apps.vue, Marketplace.vue, Home.vue, and the Web5 sub-cards carrying the identical defect are fixed by the same mechanism, in the same plan, closing 02-11's full named blast radius rather than leaving a partial fix.
|
||||
- No visual or animation change to any surface's genuine first-visit behavior; no regression to `keepAliveTabs.test.ts`'s structural contract or the dashboard's own tab transitions; no regression to Home's welcome-overlay visibility logic.
|
||||
- A human has verified the fix on real archi-dev-box hardware before this plan is considered complete.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/02-ui-performance/02-12-SUMMARY.md` ONLY after Task 4's checkpoint is approved — per-file before/after description of the fix, the regression tests and their pass/fail-before proof, the re-measured numbers for all four surfaces, confirmation of the human verification outcome, and confirmation the visual/animation and Home-overlay constraints held throughout.
|
||||
</output>
|
||||
Reference in New Issue
Block a user