fix(frontend): sidebar refresh/rotate flash + chart crosshair re-park on sidebar toggle; 2 flaky-test guards#326
Merged
Conversation
…shair on sidebar toggle; guard 2 flaky tests Bug A — sidebar "opens then shrinks back by itself" on refresh OR portrait↔landscape rotation: - Pre-paint a `.sidebar-collapsed` class (inline script in layout.tsx) + a globals.css rule renders the collapsed rail at its narrow width before React hydrates (the static export bakes the EXPANDED markup) — no wide→narrow flash on refresh. AppShell keeps the class synced to live state. - Gate the aside width/transform transition behind an `animate` flag that's on only for ~250ms around an EXPLICIT user toggle; refresh + rotation + resize switch instantly (no shrink animation). Explicit toggles still animate smoothly. Bug B — chart crosshair jumps to the far left when the sidebar expands/ collapses: - Replace the orientation-only matchMedia re-park with a width-delta ResizeObserver on the chart wrapper that debounce-bumps the AreaChart remount key after the container re-measures, re-parking defaultIndex at the latest point. Subsumes rotation + now also catches sidebar toggle + resize. Flaky tests (finish the shallow-clone / network hardening): - test_ranking_history smoke: explicit pytest.skip on an empty frame (shallow CI clone, fetch-depth=1) — mirrors the #325 + PR #284 guard; proven on a real depth-1 clone (46/46 git-dependent tests pass). - test_osap import scout: check the OpenAP API surface on the class instead of instantiating (the openassetpricing 0.0.2 ctor does a live Google-Drive fetch → flaky on Drive quota); the live path stays in the @network test. Verified: ruff clean; pytest -m "not network" 1407 passed; tsc clean on edited files; next build 506 routes; Playwright empirical Bug A x3 + Bug B all PASS. https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- FAIL (reduced-motion): the gated sidebar `[transition:…]` arbitrary class wasn't covered by the globals.css prefers-reduced-motion guard, so an explicit toggle still animated for reduced-motion users. Add `motion-reduce:transition-none` (always-on) so the toggle is instant under reduced motion. Verified: reduced-motion ctx → transitionProperty=none. - WARN (ResizeObserver deps): drop `chartData.length` from the dep array — the chart wrapper div persists across period switches (only the inner <AreaChart> remounts via key), so the observer needn't disconnect/re-observe on every period change; [loading, error, data] already covers wrapper-mount. Verified empirically (Playwright): regression — original 4 still PASS (refresh no-flash / transition gating / rotation instant / crosshair re-park); new — reduced-motion transition none on toggle + collapsed-rail focus ring fits within the aside (aside[0,72] link[9,62] ring±4, not clipped). tsc clean on edited files; next build 506 routes. https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
… text flash on refresh)
Follow-up to the Bug A refresh fix: the pre-paint CSS only forced the rail
WIDTH, so on a collapsed refresh the expanded inner content (wordmark
"QuantRank" + nav labels + section headers + footer chip) still painted for a
frame before React hydrated and hid it ("text pops up then disappears"). Extend
the pre-paint `html.sidebar-collapsed` CSS to render the ENTIRE collapsed layout
from the first frame — hide text (data-rail="hide"), center nav items
(navlink), stack the header (header), full-width chevron box (chevron), show the
icon theme-toggle (show) — mirroring the `collapsed ? 'md:…'` Tailwind classes
the React tree applies, via additive data-rail hooks that don't touch the
working React ternaries. The CSS agrees with React's collapsed classes
post-hydration (both keyed/synced to the same collapsed state) so they never
fight.
Verified (Playwright): collapsed refresh light + dark — max rendered width of
any hidden-text element = 0 across all frames (text never painted); regression —
original Bug A/B checks still PASS. tsc clean; next build 506 routes.
https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
dackclup
added a commit
that referenced
this pull request
May 30, 2026
…#326 §Gotchas (#327) Full docs-reviewer substance pass on CLAUDE.md → 5 MUST-FIX + 4 SHOULD-FIX, all fixed. CLAUDE.md only (+ PHASE_STATUS_INFLIGHT.md lockstep entry); no code/schema/compute/frontend change. MUST-FIX: §Phase status "In flight" marked PR #312 (tasteful-motion) as "THIS PR" → merged (e602485); "Recently merged" frozen at #310 → drained #311–#326 (16 entries, SHAs verified); "Next deliverables" Issue #67 flip listed pending → DONE (PR #294, USE_SECTOR_COE=True); "Section A-H/A-J" → "A-L" (helper is A-L since PR #221). SHOULD-FIX: §Stack "Phase 3b on this PR" → "(merged)" + edgartools 5.31→5.32; §Gotchas compute/main.py line refs re-anchored (840→879 · 1965-66→2084-85 · 717→728 · 785→805 · 972→1025); +2 §Gotchas for PR #326 invariants (sidebar data-rail↔globals.css pre-paint lockstep; AreaChart re-park debounce ≥300ms). Confirmed no drift: schema 0.10.11-phase4.6 · skills 46 · agents 19 · hooks 3. docs-reviewer re-check: DOCS-CLEAN (all 16 SHAs match, 5 line refs accurate, 2 gotchas code-backed). Lockstep via PHASE_STATUS_INFLIGHT.md side-file (PR #237).
dackclup
added a commit
that referenced
this pull request
May 30, 2026
…on-only rail (#328) On desktop, when the sidebar is COLLAPSED the 64px rail now keeps ONLY the collapse chevron (back to its original 32px square — no longer a full-width filled box), and the brand (green Q + "QuantRank") moves OUT to the top header. Expanding moves the brand back INTO the rail. Mobile drawer unchanged. Mechanism (reuses the PR #326 data-rail pre-paint pattern → no refresh flash): - Sidebar.tsx: rail brand Link carries data-rail="hide" (Q + wordmark hide together at md+ when collapsed); chevron drops the full-width/fill classes and re-centers (32px square). - AppShell.tsx: new brand Link in the top header carries data-rail="show" (hidden base, md:flex only when collapsed). - globals.css: pre-paint rules updated to match (header→center-the-chevron; chevron→margin-left:0). Verified (Playwright, real chromium): EXPANDED brand-in-rail/not-header · COLLAPSED brand-in-header/not-rail + chevron 36×36 square · COLLAPSED REFRESH rail brand never flashes (0px across 45 frames) · TOGGLE round-trip · mobile+collapsed=1 shows exactly ONE header wordmark (no double). frontend-design-reviewer: READY-FOR-SPOT-CHECK, 0 FAIL. tsc clean; next build 506 routes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three stability fixes (the two user-reported sidebar/chart bugs + finishing the flaky-test hardening). Frontend + test-only — zero schema / compute / scoring / valuation / output-JSON change.
Bug A — sidebar "opens then shrinks back by itself" on refresh OR portrait↔landscape rotation
Two root causes, two-part fix:
AppShell's mount effect readlocalStorageand collapsed it. Fix — a pre-paint inline<script>inlayout.tsxadds.sidebar-collapsedto<html>before the body paints (mirrors how next-themes pre-paints dark mode), and aglobals.cssrule renders the rail at its collapsed4remwidth immediately → React's post-hydration collapse is a no-op.AppShellkeeps the class synced to live state so it never fights React once interactive.<aside>carried a permanentwidth 200mstransition, so both the hydration-collapse and the breakpoint cross on rotation animated the shrink. Fix — a newanimateflag (owned byAppShell) is true only for ~250 ms around an explicit user toggle; at rest the aside istransition-none, so refresh + rotation + resize switch instantly. Explicit toggles still animate smoothly.Bug B — chart crosshair jumps to the far LEFT when the sidebar expands/collapses
Toggling the rail reflows the main-content width →
ResponsiveContainerre-measures, but Recharts appliesdefaultIndex(latest-point park) only on mount, so the crosshair drifted left. Fix (PriceHistoryChart.tsx) — replaced the orientation-onlymatchMediare-park with a width-deltaResizeObserverthat debounce-bumps the<AreaChart>remount key ~300 ms after the container re-measures, re-parking at the latest point. Subsumes rotation (it changes width too) and now also catches the sidebar toggle + window resize. A<1pxwidth-delta gate ignores height-only / crosshair-render churn.Flaky tests (เก็บ flaky ให้ครบ)
test_ranking_history.py::test_load_ranking_history_smoke_recent_window— explicitpytest.skipon an empty frame (shallow CI clone,actions/checkoutfetch-depth=1). Mirrors thelist_ranking_commitsguard from feat(frontend): fluid root font-size for app-wide responsive scaling #325 + PR fix(test): make manipulation_distribution smoke resilient to shallow clones #284; the test previously passed-on-empty only by coincidence of the empty-return path's.set_index(...). Proven on a real depth-1 clone (46/46 git-dependent tests pass).test_osap.py::test_package_imports_and_exposes_openap_class— was a default-lane (non-@network) test that instantiatedopenassetpricing.OpenAP(), whose 0.0.2 constructor does a live Google-Drive fetch → flaky on Drive quota (hit live in this session's pre-push gate:ColumnNotFoundErroron a "Quota exceeded" HTML page). Fix — check the API surface on the class (hasattr(OpenAP, method)) instead of an instance; the scout signal stays offline, the live path stays in the@networktest in the same file.Verification (empirical)
ruff check .clean ·pytest -m "not network"→ 1407 passed (was 1406 + 1 OSAP flake) ·tsc --noEmitclean on edited files ·next build→ 506 routes72pxacross all 62 frames of the first 700 ms (never wide)transitionProperty=none; during toggletransform, width; settles back tononetransitionProperty=none(instant, no shrink), rail lands at72pxbefore=1.0,transient@120ms=0.0(the bug),after-repark=1.0(fixed)Lockstep via
PHASE_STATUS_INFLIGHT.md(per #237). No new invariant beyond what the existing §Gotchas already frame.https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
Generated by Claude Code