The rectification session rendered its own textarea (no length ceiling,
no count), its own rAF scroll follow with a sticky-scroll helper, and its
own jump chip styled differently from the main chat's inline Tailwind
button, while the main chat re-ran scrollTo on every streamed token and
added a smooth scroll on settle. ChatComposer now accepts a controlled
value so the rectification surface reuses it without touching the main
draft store; useConversationScrollAnchor owns the follow through a
ResizeObserver, one frame per content change, for both surfaces; a
single JumpToLatestButton replaces both chips; the 720px transcript
override is gone. DESIGN.md records the shared composer, the jump
control, the reading width and the two shadow tokens.
BUG-477 BUG-478
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
Keep archive as a PATCH of archived_at rather than DELETE, pass entry_mode through onboarding, and let popstate to the default chat reuse selectSession. Then move send/stop/recovery and session management out of page.tsx so the home surface stays within the batch-two line budget.
Co-authored-by: Cursor <cursoragent@cursor.com>
Local fallbacks were creating fake saves and resurrecting deleted rows.
Pin and archive now live on chat_sessions so they follow the account.
Co-authored-by: Cursor <cursoragent@cursor.com>
Refresh, back, and login return were dropping the open conversation because selection lived only in React state.
Co-authored-by: Cursor <cursoragent@cursor.com>
List GET no longer ships transcripts; consult appends questions after reserve and ignores client history so dual-tab last-write-wins cannot erase messages.
Co-authored-by: Cursor <cursoragent@cursor.com>
The button was a sticky child of the scroll container, which reserves
composer-sized bottom padding. Sticky boxes are clamped by their
containing block's content box, so the 12px offset never applied and the
button parked 160px above the composer, on top of the transcript.
Anchor it to the composer wrapper's own top edge instead, so the offset
no longer depends on the --composer-reserve constant.
Co-authored-by: Cursor <cursoragent@cursor.com>
Framework-level UX fixes found while auditing staging (BUG-216..220).
- chat: route 44 previously discarded composer notices to sonner with
dedupe, so recovery, cancel and archive feedback is actually visible
(BUG-216)
- chat: anchor stream auto-scroll to bottom proximity and add a
jump-to-latest control, so reading history is no longer interrupted
on every token (BUG-218)
- reports: replace the silent 120s poll cutoff with an explicit
timed-out state, an 8m budget, stepped backoff and an elapsed
counter (BUG-217)
- reports: pause polling while the tab is hidden, via a shared hook
- app: add root error, global-error and not-found boundaries (BUG-219)
- admin: add antd SSR style extraction and the React 19 render adapter,
and move admin-only css out of the global stylesheet (BUG-220)
- membership: run bootstrap fetches concurrently and pause payment
polling while hidden
- build: configure optimizePackageImports
Verified on top of 2d370f2e: tsc, eslint, next build, and the related
frontend contract suites.
Co-authored-by: Cursor <cursoragent@cursor.com>