Fix corrupted Unicode characters (⌘, —, ✓, →, ·) that were displaying
as mojibake (⌘, â€", âœ", â†', ·) in the TIPS array and other strings.
Co-authored-by: Ishaan Gupta <ishaanxgupta@users.noreply.github.com>
Drive-ingested PDFs persist a raw googleapis.com URL that the browser can't fetch directly (403). Detect googleapis.com hostnames in PdfViewer and rewrite to `/v3/drive-proxy/<documentId>` with credentials. R2-hosted PDFs are unaffected.
Use /v3/auth/org-summaries for per-org plan tiers in settings (same as console v2).
Extract chat empty state, add space-aware subtitle, and use AutoSpaceIcon in selectors.
- Keep home Recently saved and suggestions in a stable two-column layout
- Show skeleton while recents load and empty state when none exist
- Cap org switcher dropdown height with scroll for long org lists
- Show plan tier badges on all orgs, sorted by plan rank
This PR fixes a bug in auto scroll after the first msg auto scroll was not working properly it is fixed now
It also tightens Support & Help on small screens: slightly reduced card/title spacing and stacked-button gaps, and replaces fixed-height pills with responsive vertical padding so label text is not flush with the pill edges on mobile devices
## Before
<img width="382" height="232" alt="Screenshot 2026-05-18 at 6 56 48 PM" src="https://github.com/user-attachments/assets/62c583a2-2417-44b6-8502-d6a7950eb136" />
## After
<img width="396" height="267" alt="Screenshot 2026-05-18 at 6 57 04 PM" src="https://github.com/user-attachments/assets/919dbf50-664a-44ec-aa95-287ca13fd77a" />
- Replace hand-rolled motion.div sheet with Drawer from @ui/components
- Use insideOut Button for primary CTA; standardize close button with INSET shadow recipe
- Wrap content in recessed card (bg-#14161A + chrome-detail inset shadow)
- Replace 3 generic feature pills with 2x2 Check-list of honest PWA benefits
- Steps now use connected numbered circles with title + description (matches install-steps.tsx)
- Swap GradientLogo for actual app icon (/android-chrome-512x512.png)
- Add safe-area padding so CTA clears the home indicator
- Parallel noun-phrase copy: Instant launch / Full-screen mode / Native-app feel / Stays signed in
- Fix `authClient.apiKey.create()` returning undefined: better-auth resolves to `{ data, error }`, not the key directly. Use `res.data.key` (with `res.error` handling) in both `RaycastDetail` and `settings/integrations.tsx`.
- Extract `RaycastSetupModal` and reuse it across the nova home Raycast card and the Settings → Integrations Raycast modal. The new modal matches the plugins-connect design system (icon box, pill close, inset card with shared `InstallSteps`, install pill).
- Add `/settings/integrations` redirect page so `app.supermemory.ai/settings/integrations?q=raycast` lands on the Integrations tab and auto-opens the Raycast modal (the `?q=raycast` auto-trigger logic already existed; the route was the missing piece).
Nova mobile pass
- viewport: viewportFit cover for iOS safe-area-inset
- safe-area utilities (pb-safe, pt-safe, bottom-safe-5, scroll-fade-x) in globals.css
- chat FAB pinned above iPhone home indicator; chat sidebar widths responsive across sm/md/lg with min() clamps
- chat input CoT panel max-h capped via min(60dvh, 420px)
- header tab strip swapped from visible scrollbar to scroll-fade-x mask + snap-x
- nova empty state uses svh on mobile, dvh from sm up
Add-memory modal rebuilt for mobile
- mobile shell switched from fullscreen Dialog to vaul Drawer at 85svh with swipe-down dismissal and scaled background
- in-modal header removed; tabs moved to the bottom of the sheet for thumb reach
- four tab compactLabels: Note, Links, Files, Connections
- desktop tabs now render only when !isMobile (no DOM duplication)
- note/link content state lifted to parent so switching tabs preserves typed input
- NoteContent snapshots initialContent via lazy useState so the editor isn't reset on every keystroke
- shared Drawer base uses rounded-t-xl
- removed legacy pt-4 on tab content for mobile
Connections — replace expiresAt with sync-run health
- new useConnectionHealth hook reads the latest sync run and matches auth-failure patterns; backend errorKind field still needed (TODO)
- regex tightened so 401/403 require co-occurring auth/token/grant context; refresh_token requires expired/revoked/invalid/missing qualifier
- badge label changed Disconnected -> Needs reauth
- Reconnect button replaces the sync action when needsReauth, kicks off the same OAuth flow
- per-row reconnect tracking via mutation.variables instead of a single shared id (no race when multiple rows clicked)
- fallback toast when authLink is missing so the spinner can't get stuck
- sync history panel timeline capped at max-h-260 with internal scroll
- useSyncRuns no longer refetches on mount; cache (30s) actually applies, cutting N requests per modal open
## Summary
Changes the highlight "Chat" button behavior:
- **Before**: Clicking "Chat" immediately opened the chat view with "Tell me more about XYZ" sent as the user's first message
- **After**: Clicking "Chat" reveals an inline reply input right under the highlight card. The user types their response, and when they submit:
- The highlight content appears as the **assistant's first message** (prefixed with "Here is a highlight from your memories:")
- The user's reply appears as the second message
- A fresh chat thread is created (no overwriting existing conversations)
### Files Changed
- **`apps/web/components/highlights-card.tsx`** - Added inline reply input UI with send/cancel buttons, keyboard handling (Enter/Escape), auto-focus, and state reset on item navigation or items refresh
- **`apps/web/components/chat/index.tsx`** - Added `queuedHighlightContent` prop, fresh thread creation for highlight chats, deferred `setMessages` to ensure correct Chat instance targeting, and `awaitingHighlightInjectionRef` for safe reply dispatch
- **`apps/web/app/(app)/page.tsx`** - Added `queuedHighlightContent` state, updated `handleHighlightsChat` to accept highlight content + user reply, cleared stale highlight content in `handleHomeChatStart`
- **`apps/web/components/dashboard-view.tsx`** - Updated `onHighlightsChat` type signature
- **`apps/web/components/memories-grid.tsx`** - Updated `HighlightsProps.onChat` type signature
## Testing
### TypeScript
- `npx tsc --noEmit` — no new type errors (all errors are pre-existing in unrelated files)
### Biome
- `bunx biome check --write` — clean on all changed files (pre-existing warnings only in dashboard-view.tsx)
### Automated Tests (via testing subagent)
- **25 passed / 0 failed / 1 skipped** (26 total)
- Tests covered: inline reply input visibility, keyboard handling (Enter/Escape), reply state reset on navigation, highlight content injection as assistant message, user reply dispatch, fresh thread creation, stale highlight content prevention, pagination dot handler
- The testing agent discovered and fixed a race condition where `setMessages` was called before `useChat` had recreated its internal Chat object after `setFallbackChatId` — the fix defers `setMessages` to a separate effect gated on `currentChatId` matching the target
- 1 test skipped (T12): Nova FAB button not separately visible from highlights card button at 1440px viewport — expected structural behavior
---
**Session Details**
- Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/9cbf2106-b7b9-4d7c-b275-2a7e72417793)
- Requested by: Soham Daga (soham@supermemory.com)
- Address comments on this PR. Add `(aside)` to your comment to have me ignore it.
## Summary
- Add a "Cancel subscription" button to the billing card for Pro and Scale users, alongside the existing "Manage billing" portal link.
- Cancellation is scheduled end-of-cycle via `autumn.updateSubscription({ planId, cancelAction: "cancel_end_of_cycle" })`, so users keep paid features until period end.
- Confirmation dialog surfaces remaining days from `useTokenUsage` and the correct plan name (Pro/Scale).
- Enterprise users do not see the cancel button — contract-based, portal/sales only.
Previously, the only path off a paid plan was Autumn's external customer portal, which contradicted the in-app "cancel anytime from the Billing tab" copy in the support FAQ.