feat(sf-fe-a4): analysis sidebar (scrollspy) + breadcrumb + user avatar #344
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#344
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/sf-fe-a4-sidebar"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
SF Frontend Wave 2 parallel со A2. Wire
site-finder/analysis/[cad]/page.tsx(A1 shell) navigation + header.Files
NEW components
frontend/src/components/site-finder/analysis/:AnalysisSidebar.tsx— sticky 240px nav,IntersectionObserverscrollspy на#section-*, active highlight via--accent/--accent-soft, sub-sections для 3.1/3.2/3.3, Sources footerAnalysisBreadcrumb.tsx—SiteFinder → {cad} → АнализсChevronRight,aria-current="page"UserAvatar.tsx—localStorage.gd_org_idвuseEffect(SSR-safe), 2-letter monogram, fallback "Demo Org" +Building2iconPage wire
frontend/src/app/site-finder/analysis/[cad]/page.tsx:SidebarPlaceholder→<AnalysisSidebar/><AnalysisBreadcrumb cadNum={cad}/>+<UserAvatar/>#section-3-1/3-2/3-3anchor divs для scrollspy targetsDep
frontend/package.json— добавленlucide-react ^0.511.0(был imported в A1 shell, не был в deps)Constraints
site-finder/page.tsx(A2),components/ui/*(A3), section contentVisual verify
/site-finder/analysis/66:41:0204016:10— sidebar 5 items, section 3 expands → 3.1/3.2/3.3localStorage.gd_org_id = "PRINZIP"→ avatar "PR" monogramPart of plan vault
code/patterns/SiteFinder_Frontend_Migration_Plan_May18.md§A4.Deep review — verdict: HIGH (do NOT merge yet)
Code quality is solid — scrollspy/SSR/avatar logic is correct. Blocker is lockfile drift that will break the prod deploy build.
HIGH — must fix before merge
frontend/package-lock.jsonnot updated.package.jsondeclareslucide-react ^0.511.0, but the lockfile in this branch is byte-identical tomain(no diff).frontend/Dockerfileusesnpm ci --legacy-peer-deps, which fails hard whenpackage.jsonandpackage-lock.jsonare out of sync. TheDeployworkflow (.forgejo/workflows/deploy.yml, triggered on push tomainforfrontend/**) runsdocker build→ that npm ci will error with "Missing: lucide-react@x.x.x from lock file" and the prod frontend image build will fail.npm install(ornpm install lucide-react@^0.511.0) infrontend/, commit the resultingpackage-lock.jsondiff, push fixup.Positive findings
AnalysisSidebar.tsx:42-79) — correct:IntersectionObserverinstantiated inuseEffect,observerRef.current?.disconnect()cleanup, empty deps array,setActiveIdonly on observer callback (no infinite render).rootMargin: "-8px 0px -40% 0px"+ALL_SECTION_IDS.find(id => visible.has(id))correctly picks topmost visible section in DOM order.UserAvatar.tsx:31-37) — correct pattern: initial state isnull,localStorageread is inuseEffect(post-hydration). Server render and first client render produce identical output ("Demo Org" + Building2), no hydration mismatch.typeof window === "undefined"guard +try/catchfor Safari private mode also fine.UserAvatar.tsx:18-27) —"PRINZIP"→"PR"✅,"DEMO-ORG-1"→"DO"✅,?fallback for pathological inputs.aria-current="page",encodeURIComponent(cadNum),aria-hiddenon chevron separators,noopener noreferreron external Sources links. Could drop"use client"since no hooks are used, but no harm.top: 56matches headerminHeight: 56✅.--accent/--accent-soft), Lucide icons, noany, no XSS surface.MINOR (non-blocking, optional fixup)
AnalysisBreadcrumb.tsx:1—"use client"not needed (component is pure-props, no hooks). Removing it would let it be a server component, slightly less JS shipped. Optional.AnalysisSidebar.tsx:64—handleAnchorClickcallssetActiveId(targetId)afterscrollIntoView. Observer will re-fire and confirm during the smooth scroll — fine, intentional optimistic update. No fix needed, just noting.AnalysisSidebar.tsx:35— observer captures DOM at mount. If Wave 3 conditionally renders sub-section anchors, scrollspy will miss them. Currently fine because anchors are static inpage.tsx. Leave a// TODO Wave 3if conditional rendering planned.Verdict
🟠 HIGH — Approve once
package-lock.jsonis regenerated and pushed. Re-ping for re-review after fixup.Rationale: (a) scrollspy correctness verified — observer cleanup, no re-render storms, correct topmost-visible logic; (b) hydration safety verified —
localStorageinuseEffect, identical SSR/first-client render; (c) lockfile not updated →npm ciin prod Dockerfile will fail on next push to main, blocking deploy.Merged via deep-code-reviewer — verdict APPROVE.
Highlights:
typeof windowguard +useEffectdeferred read + try/catch.use(params)pattern correct.66:41:0204016:10; href single-encoded viaencodeURIComponent.gd_org_id.lucide-react ^0.511.0valid on npm, peer dep^19.0.0matches React 19 — fixes latent A1 build break.site-finder/page.tsx(A2 #343),components/ui/*(A3), section content (Wave 3).Minor (non-blocking, post-Wave-3 if needed):
#section-3-1/3-2/3-3invisible 0-height divs may not trigger IntersectionObserverisIntersecting=truewith current rootMargin until Wave 3 fills them with real height. Main sections 1/2/3/4/5 unaffected.