Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

1. **폴링 (polling)** — 당겨서 새로고침 후 각 계정의 새 게시물을 API로 가져오는 작업. 진행 중에는 `N/total` pill 표시. 폴링은 조용히 진행됨 — 계정 상태 dot(초기 로딩 표시)을 띄우지 않음. HTTP 429를 받으면 이번 회차를 즉시 중단하고 `Retry-After`(없으면 `X-RateLimit-Reset`, 그래도 없으면 5분, 최대 1시간)만큼 다음 폴링을 건너뜀(웹·포그라운드 폴링도 Android 워커와 동일 정책). 폴링 중 일시적 실패(429·네트워크)는 dot으로 노출하지 않음.
2. **버퍼 (buffer)** — 폴링 완료 후 피드에 아직 반영되지 않은 게시물 임시 보관소. "N new" 버튼으로 표시됨 (대기 중 상태).
3. **피드 반영 (flush)** — "N new" 클릭 시 버퍼를 피드에 표시하는 동작. 새 글은 최상단에 삽입되고 화면은 **최상단(가장 새 글)으로 스크롤**(사용자가 새 글을 바로 읽도록). 구분선은 새 글과 기존 글 사이 경계로 남음. (콜드 스타트에서 마운트가 세팅한 경계 구분선은 `flushNonce`가 아닌 `boundaryNonce`를 올려 최상단이 아니라 구분선을 화면 중앙에 맞춰 스크롤함.)
3. **피드 반영 (flush)** — "N new" 클릭 시 버퍼를 피드에 표시하는 동작. 새 글은 최상단에 삽입되고, 새 글(위)과 기존 글(아래) 사이에 구분선이 남으며 화면은 **그 구분선이 세로 중앙에 오도록 스크롤**(콜드 스타트 경계 구분선과 동일한 착지). `flushNonce`(flush)와 `boundaryNonce`(콜드 스타트)는 서로 다른 신호지만 둘 다 SubscribedPage에서 `centerScrollOnDivider`로 같은 중앙 정렬 스크롤을 유발함.

## 엔드유저 UI

Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useSubscribedFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,9 @@ export function useSubscribedFeed(
setStagedCount(0);
flush();
if (prevTopId) setDividerPostId(prevTopId);
// Tell SubscribedPage to scroll to the newest post — the user asked to see
// the new posts. (The mount-seeded boundary divider leaves this untouched,
// so a cold-start open never auto-scrolls.)
// Tell SubscribedPage to center the seam — the divider between the new posts
// (above) and the already-seen ones (below) — same landing as a cold-start
// boundary (see boundaryNonce).
setFlushNonce((n) => n + 1);
}, [flush]);

Expand Down
30 changes: 13 additions & 17 deletions src/pages/SubscribedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,29 +51,25 @@ export function SubscribedPage({
// the first load after login, newly added accounts, and new-post polling.
const { pullDistance, armed } = usePullToRefresh(scrollContainerRef, refresh);

// A user-initiated flush ("N new" tap) scrolls to the newest post so the new
// posts are what the user lands on; the divider stays as the seam below them.
// Both a user-initiated flush ("N new" tap, flushNonce) and the mount-seeded
// boundary divider (cold start after a background sync — the "tapped the
// notification after hours away" case, boundaryNonce) land the user on the
// seam, centered: the new/unseen posts above the fold, the already-seen ones
// below. The cold-start case also overrides scroll-anchor restore for that
// open (see useRestoreScrollAnchor's skip below) so the two don't fight.
const lastFlushNonce = useRef(flushNonce);
useEffect(() => {
if (flushNonce === lastFlushNonce.current) return;
lastFlushNonce.current = flushNonce;
requestAnimationFrame(() =>
scrollContainerRef.current?.scrollTo({ top: 0, behavior: "smooth" }),
);
}, [flushNonce, scrollContainerRef]);

// The mount-seeded boundary divider (cold start after a background sync — the
// "tapped the notification after hours away" case) lands the user at the seam,
// centered: the unseen posts above the fold, the already-seen ones below. This
// overrides scroll-anchor restore for that open (see useRestoreScrollAnchor's
// skip below) so the two don't fight over the position.
const lastBoundaryNonce = useRef(boundaryNonce);
useEffect(() => {
if (boundaryNonce === lastBoundaryNonce.current) return;
if (
flushNonce === lastFlushNonce.current &&
boundaryNonce === lastBoundaryNonce.current
)
return;
lastFlushNonce.current = flushNonce;
lastBoundaryNonce.current = boundaryNonce;
const el = scrollContainerRef.current;
if (el) requestAnimationFrame(() => centerScrollOnDivider(el));
}, [boundaryNonce, scrollContainerRef]);
}, [flushNonce, boundaryNonce, scrollContainerRef]);

// Track scroll position for the floating button and the status grid:
// - the status grid and the top-of-feed signals show only at the very top
Expand Down
Loading