Skip to content

Comments

feat: chat persistence fixes, mobile UX, analytics scrubbing, and toolbar polish#44

Open
batmn-dev wants to merge 5 commits intomainfrom
observe-these-fists-of-vengeance
Open

feat: chat persistence fixes, mobile UX, analytics scrubbing, and toolbar polish#44
batmn-dev wants to merge 5 commits intomainfrom
observe-these-fists-of-vengeance

Conversation

@batmn-dev
Copy link
Owner

@batmn-dev batmn-dev commented Feb 24, 2026

Summary

  • Fix stale closure data loss in edit persistencecacheAndAddMessage now reads the current IndexedDB cache directly instead of relying on stale closure-captured React state, preventing sequential calls from silently dropping messages. Edited user messages are deferred to onFinish to avoid mutating provider state mid-batch.
  • Scrub PII from analytics and improve observability — Adds a PII scrubbing layer for PostHog events with tests, ensuring no user-identifiable data leaks into analytics.
  • Simplify user message rendering and persist model per chat — Streamlines the user message component and stores the selected model at the chat level.
  • Improve mobile sidebar UX and add share action — Enhances mobile sidebar behavior, adds a share/publish drawer, and refines header layout.
  • Auto-play toolbar reveal on stream completion — Fresh assistant responses animate the action toolbar in via a mask-reveal keyframe; historical messages keep the hover-triggered reveal.
  • Include Convex auth loading in ChatsProvider — Prevents flash of empty state while auth is still resolving.
  • New agent skills — Documents the dual-message-state architecture and stale-closure-persistence pattern for future reference.

Test plan

  • Send a message and verify the assistant toolbar animates in after streaming completes
  • Reload the page and verify historical messages show toolbar on hover (no auto-animation)
  • Edit a user message mid-conversation and verify both the edited user message and new assistant response persist after refresh
  • Rapidly edit and re-send to confirm no messages are silently dropped
  • Verify mobile sidebar opens/closes correctly and share action works
  • Confirm chat list does not flash empty on initial auth load

Made with Cursor


Summary by cubic

Fixes edit persistence that could drop messages, adds a mobile share flow and sidebar polish, scrubs PII from analytics, smooths the assistant toolbar reveal, and prunes stale agent docs while simplifying Cursor rules. Also persists the selected model per chat and simplifies user message rendering.

  • New Features

    • Mobile share drawer and sidebar tweaks: close sidebar on navigation, hamburger icon, smoother sheet animations.
    • Persist model per chat from the header selector; still remembers last-used model on new chats.
    • Assistant action toolbar auto-reveals after a fresh stream; historical messages stay hover-triggered.
  • Bug Fixes

    • Edit persistence: read from IndexedDB in cacheAndAddMessage, defer edited user message persistence to onFinish, and ensure createdAt is set. Prevents dropped/duplicate messages during rapid edits.
    • Analytics: added PII scrubbing for PostHog (inputs/outputs) with tests; replace swallowed tool-call-log write errors with structured console.warn logs.
    • Prevent chat list flicker while Convex auth loads; mark /api/payclaw/status as deprecated with headers and a deprecation log.

Written for commit a8976fe. Summary will update on new commits.

batmn-dev and others added 4 commits February 23, 2026 15:31
- Add scrubForAnalytics utility to redact sensitive fields/patterns
  before PostHog capture (emails, phones, keys, addresses).
- Replace silently-swallowed tool-call-log write errors with structured
  console.warn JSON for debuggability.
- Mark /api/payclaw/status as deprecated with HTTP headers and log.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Replace markdown rendering in user messages with plain-text
  whitespace-pre-wrap, removing custom component overrides in both
  message-user and chat-preview-panel.
- Enhance ModelSelectorHeader to read the current chat's persisted
  model and write model changes back to the chat record.
- Add Payclaw architecture hardening plan (#47).
- Add ChatGPT nav list widget analysis research.

Co-authored-by: Cursor <cursoragent@cursor.com>
Restructure sidebar header for consistent home link placement, close
sidebar on chat navigation, swap to hamburger menu icon, add share
option to chat actions menu on mobile, and smooth sheet animations.

Co-authored-by: Cursor <cursoragent@cursor.com>
…toolbar reveal

- Read IndexedDB cache directly in cacheAndAddMessage instead of stale closure
  state, preventing the second sequential call from dropping the first's write
- Defer edited user message persistence to onFinish to avoid provider state
  mutations during the sendMessage/setMessages React batch
- Ensure createdAt on cached messages for correct sort order
- Auto-play mask-reveal animation on fresh stream completion; keep hover-trigger
  for historical messages
- Include Convex auth loading in ChatsProvider isLoading
- Add dual-message-state and stale-closure-persistence agent skills

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
Copy link

vercel bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
not-a-wrapper Ready Ready Preview, Comment Feb 24, 2026 7:09pm

@greptile-apps
Copy link

greptile-apps bot commented Feb 24, 2026

Greptile Summary

This PR fixes a critical stale closure data loss bug in edit persistence, adds PII scrubbing to analytics, improves mobile UX, and polishes the assistant toolbar animation.

Key changes:

  • Fixed stale closure bug where sequential cacheAndAddMessage calls would overwrite each other by reading current IndexedDB cache instead of relying on closure-captured React state
  • Deferred edited user message persistence to onFinish via pendingEditUserMsgRef to avoid mutating provider state mid-batch
  • Added scrubForAnalytics layer with comprehensive tests to redact PII (emails, phones, payment info) from PostHog events
  • Auto-play toolbar reveal animation on fresh assistant responses using didStreamRef and CSS mask-reveal keyframe
  • Fixed flash of empty chat list by including isConvexAuthLoading in loading condition
  • Simplified user message rendering by removing unnecessary markdown processing
  • Enhanced mobile sidebar with improved close button placement and navigation behavior
  • Added share/publish drawer component for public conversation sharing

Confidence Score: 5/5

  • Safe to merge with high confidence
  • The core persistence fix is well-architected with defensive programming (reading from IndexedDB directly), PII scrubbing has comprehensive test coverage, UI changes are polish improvements, and the deferred persistence pattern cleanly separates concerns
  • No files require special attention

Important Files Changed

Filename Overview
lib/chat-store/messages/provider.tsx Fixed stale closure bug by reading current IndexedDB cache instead of using closure-captured React state in cacheAndAddMessage
app/components/chat/use-chat-core.ts Added deferred edit persistence via pendingEditUserMsgRef to persist edited user messages in onFinish after stream completes
app/components/chat/use-chat-edit.ts Integrated deferred edit persistence by calling setPendingEditUserMessage to stage edited messages for onFinish
lib/posthog/scrub.ts Implemented PII scrubbing layer that redacts sensitive keys and patterns (email, phone) before sending analytics events
lib/posthog/tests/scrub.test.ts Comprehensive test coverage for PII scrubbing with nested objects, arrays, patterns, and edge cases
app/api/chat/route.ts Applied scrubForAnalytics to message input/output and improved error logging for tool call failures
app/components/chat/message-assistant.tsx Added auto-play toolbar reveal animation for fresh streams using didStreamRef and mask-reveal keyframe
lib/chat-store/chats/provider.tsx Fixed flash of empty state by including isConvexAuthLoading in loading condition
app/components/layout/share-publish-drawer.tsx New component for sharing/publishing conversations with copy link and social sharing actions

Sequence Diagram

sequenceDiagram
    participant User
    participant EditUI as Edit UI
    participant ChatCore as use-chat-core
    participant Provider as MessagesProvider
    participant IDB as IndexedDB
    participant Convex

    User->>EditUI: Edit message
    EditUI->>ChatCore: setPendingEditUserMessage(editedMsg)
    Note over ChatCore: Store in pendingEditUserMsgRef
    EditUI->>ChatCore: sendMessage()
    ChatCore->>Provider: Stream starts
    Provider-->>User: Display streaming response
    
    Note over ChatCore: onFinish triggered
    ChatCore->>Provider: cacheAndAddMessage(pendingEdit)
    Provider->>IDB: Read current cache
    IDB-->>Provider: Return existing messages
    Provider->>IDB: Write [existing + editedUser]
    Provider->>Convex: Persist editedUser
    
    ChatCore->>Provider: cacheAndAddMessage(assistantMsg)
    Provider->>IDB: Read current cache (includes editedUser)
    IDB-->>Provider: Return messages with editedUser
    Provider->>IDB: Write [existing + assistant]
    Provider->>Convex: Persist assistant
Loading

Last reviewed commit: 6b6aa99

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 26 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/components/layout/share-publish-drawer.tsx">

<violation number="1" location="app/components/layout/share-publish-drawer.tsx:40">
P2: URL-encode the tweet text before interpolating it into the intent URL; unencoded spaces/punctuation can produce an invalid query string.</violation>
</file>

<file name="app/components/chat/use-chat-core.ts">

<violation number="1" location="app/components/chat/use-chat-core.ts:186">
P2: Pending edited user messages are only persisted on successful stream completion. If the stream aborts/errors, the early return skips this block, so edited user messages can be dropped from persistence. Consider persisting (or at least clearing) pending edits before the early return on error/abort.</violation>
</file>

<file name="app/components/layout/chat-actions-menu.tsx">

<violation number="1" location="app/components/layout/chat-actions-menu.tsx:68">
P1: Share action targets the active session chatId instead of the menu’s chat prop, so sharing from a chat list will publish the wrong chat. Use the `chat.id` prop for mutations and drawer rendering.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

const handleShare = async () => {
if (!chatId) return
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Share action targets the active session chatId instead of the menu’s chat prop, so sharing from a chat list will publish the wrong chat. Use the chat.id prop for mutations and drawer rendering.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/components/layout/chat-actions-menu.tsx, line 68:

<comment>Share action targets the active session chatId instead of the menu’s chat prop, so sharing from a chat list will publish the wrong chat. Use the `chat.id` prop for mutations and drawer rendering.</comment>

<file context>
@@ -39,19 +47,36 @@ export function ChatActionsMenu({
   }
 
+  const handleShare = async () => {
+    if (!chatId) return
+    setIsShareLoading(true)
+    try {
</file context>
Fix with Cubic

const shareOnX = () => {
onOpenChange(false)
const text = `Check out this conversation I shared with Not A Wrapper! ${publicLink}`
window.open(`https://x.com/intent/tweet?text=${text}`, "_blank")
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: URL-encode the tweet text before interpolating it into the intent URL; unencoded spaces/punctuation can produce an invalid query string.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/components/layout/share-publish-drawer.tsx, line 40:

<comment>URL-encode the tweet text before interpolating it into the intent URL; unencoded spaces/punctuation can produce an invalid query string.</comment>

<file context>
@@ -0,0 +1,97 @@
+  const shareOnX = () => {
+    onOpenChange(false)
+    const text = `Check out this conversation I shared with Not A Wrapper! ${publicLink}`
+    window.open(`https://x.com/intent/tweet?text=${text}`, "_blank")
+  }
+
</file context>
Fix with Cubic

if (effectiveChatId) {
// Persist the edited user message first (if any) so the user→assistant
// pair is written in order before ID reconciliation.
const pendingEdit = pendingEditUserMsgRef.current
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Pending edited user messages are only persisted on successful stream completion. If the stream aborts/errors, the early return skips this block, so edited user messages can be dropped from persistence. Consider persisting (or at least clearing) pending edits before the early return on error/abort.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/components/chat/use-chat-core.ts, line 186:

<comment>Pending edited user messages are only persisted on successful stream completion. If the stream aborts/errors, the early return skips this block, so edited user messages can be dropped from persistence. Consider persisting (or at least clearing) pending edits before the early return on error/abort.</comment>

<file context>
@@ -166,6 +181,14 @@ export function useChatCore({
       if (effectiveChatId) {
+        // Persist the edited user message first (if any) so the user→assistant
+        // pair is written in order before ID reconciliation.
+        const pendingEdit = pendingEditUserMsgRef.current
+        if (pendingEdit) {
+          pendingEditUserMsgRef.current = null
</file context>
Fix with Cubic

Remove outdated archive, plans, and research files from .agents/.
Simplify and consolidate .cursor/rules; drop 070-documentation and 090-icon-sizing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant