Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
473d6c2
Implement ChatService persistent history stitching
rohvani Aug 12, 2026
e3c57f5
Improve ChatService history readability
rohvani Aug 13, 2026
e122b68
Discover new outbound ChatService conversations
rohvani Aug 13, 2026
908ce3d
Restore resident links in stitched legacy history
rohvani Aug 13, 2026
ae176d7
Reconcile stitched history with live messages
rohvani Aug 14, 2026
0a32098
Refresh ChatService history after outbound bursts
rohvani Aug 14, 2026
66ff4c7
Merge branch 'project/lua_editor' into pepper/pchat-revamp
Geenz Aug 14, 2026
77c4811
Cover cached mute-list ChatService synchronization
rohvani Aug 20, 2026
467c311
Normalize IM sender names during history reconciliation
rohvani Aug 26, 2026
20d6cc9
Publish ChatService history in Conversation Log
rohvani Aug 26, 2026
9909963
Preserve IM offers across history refreshes
rohvani Aug 27, 2026
33fd7dd
Replay IM transcripts without blank frames
rohvani Aug 31, 2026
cae8a81
Avoid redundant IM transcript reconstruction
rohvani Aug 31, 2026
229c420
Keep mobile conversations current in Conversation Log
rohvani Aug 31, 2026
b938e46
Preserve dated legacy history ordering
rohvani Aug 31, 2026
3f96467
p#700 Add a postponed size update on viewDidMoveToWindow
akleshchev Aug 28, 2026
ce1b758
p#700 Fix issue with tittle bar on Windows
akleshchev Aug 28, 2026
38635da
Scope friendship offer deduplication by sender
rohvani Aug 31, 2026
cfbbdcb
Restore canonical ChatService tail ordering
rohvani Sep 1, 2026
56b34b3
Merge pull request #6208 from secondlife/pepper/chat-fixes
rohvani Sep 1, 2026
fe57d2c
Consolidate ChatService reconciliation and fix live IM replay
rohvani Sep 10, 2026
bbb4772
Preserve repeated IM order across conversation reopening
rohvani Sep 11, 2026
3c9dee3
Share the ChatService quiet timer across incoming and outgoing IMs
rohvani Sep 11, 2026
6d8dd42
Skip system conversations during ChatService discovery
rohvani Sep 11, 2026
c9cb369
Reconcile live IMs recorded shortly after delivery
rohvani Sep 16, 2026
cc5d3dd
Merge pull request #6298 from secondlife/pepper/chat-fixes-2
rohvani Sep 17, 2026
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
46 changes: 42 additions & 4 deletions indra/llwindow/llopenglview-objc.mm
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ - (unsigned long)getVramSize

- (void)viewDidMoveToWindow
{
// viewDidMoveToWindow is called both when the view is attached to a window
// and when it is detached (window == nil).
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(performDeferredResolutionUpdate)
object:nil];

if (![self window])
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
return;
}

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowResized:) name:NSWindowDidResizeNotification
object:[self window]];
Expand All @@ -157,11 +169,37 @@ - (void)viewDidMoveToWindow
object:[self window]];


NSRect wnd_rect = [[self window] frame];
NSRect dev_rect = [self convertRectToBacking:wnd_rect];
if (!NSEqualSizes(wnd_rect.size,dev_rect.size))
if (windowCallbacksReady())
{
NSRect wnd_rect = [[self window] frame];
NSRect dev_rect = [self convertRectToBacking:wnd_rect];
if (!NSEqualSizes(wnd_rect.size,dev_rect.size))
{
// If window size actually changes, update size immediately
callResize(dev_rect.size.width, dev_rect.size.height);
}
else
{
// Defer size update to let the window settle on its final display
// before checkSettings() reads the live backing size.
callRequestResolutionUpdate();
}
}
else
{
// Defer so that mCallbacks is the real callbacks object,
// not the null_callbacks used during LLWindowMacOSX construction.
// afterDelay will fire on the next loop iteration.
NSLog(@"viewDidMoveToWindow triggered, but callbacks not ready, postponing resolution update", nil);
[self performSelector:@selector(performDeferredResolutionUpdate) withObject:nil afterDelay:0];
}
}

- (void)performDeferredResolutionUpdate
{
if ([self window])
{
callResize(dev_rect.size.width, dev_rect.size.height);
callRequestResolutionUpdate();
}
}

Expand Down
5 changes: 5 additions & 0 deletions indra/llwindow/llwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ class LLWindow : public LLInstanceTracker<LLWindow>
virtual void restore() = 0;
bool getFullscreen() { return mFullscreen; };
virtual bool getPosition(LLCoordScreen *position) = 0;

// Returns the platform-defined window size in physical (backing) pixels.
// On macOS: backing content view size (excludes title bar).
// On Windows: outer window frame size (includes title bar and borders).
virtual bool getSize(LLCoordScreen *size) = 0;

virtual bool getSize(LLCoordWindow *size) = 0;
virtual bool setPosition(LLCoordScreen position) = 0;
bool setSize(LLCoordScreen size);
Expand Down
4 changes: 4 additions & 0 deletions indra/llwindow/llwindowcallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ void LLWindowCallbacks::handleResize(LLWindow *window, const S32 width, const S3
{
}

void LLWindowCallbacks::handleRequestResolutionUpdate(LLWindow* window)
{
}

void LLWindowCallbacks::handleFocus(LLWindow *window)
{
LL_WARNS("COCOA") << "Called handleFocus proto" << LL_ENDL;
Expand Down
1 change: 1 addition & 0 deletions indra/llwindow/llwindowcallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class LLWindowCallbacks
virtual void handleScrollWheel(LLWindow *window, S32 clicks);
virtual void handleScrollHWheel(LLWindow *window, S32 clicks);
virtual void handleResize(LLWindow *window, S32 width, S32 height);
virtual void handleRequestResolutionUpdate(LLWindow* window);
virtual void handleFocus(LLWindow *window);
virtual void handleFocusLost(LLWindow *window);
virtual void handleMenuSelect(LLWindow *window, S32 menu_item);
Expand Down
2 changes: 2 additions & 0 deletions indra/llwindow/llwindowmacosx-objc.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ void callLeftMouseDown(float *pos, unsigned int mask);
void callLeftMouseUp(float *pos, unsigned int mask);
void callDoubleClick(float *pos, unsigned int mask);
void callResize(unsigned int width, unsigned int height);
void callRequestResolutionUpdate();
void callMouseMoved(float *pos, unsigned int mask);
void callMouseDragged(float *pos, unsigned int mask);
void callScrollMoved(float deltaX, float deltaY);
Expand All @@ -157,6 +158,7 @@ void callFocusLost();
void callModifier(unsigned int mask);
void callQuitHandler();
void commitCurrentPreedit(GLViewRef glView);
bool windowCallbacksReady();

#include <string>
void callHandleDragEntered(std::string url);
Expand Down
17 changes: 15 additions & 2 deletions indra/llwindow/llwindowmacosx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ LLWindowMacOSX::LLWindowMacOSX(LLWindowCallbacks* callbacks,
// Route them to a dummy callback structure until the end of constructor.
LLWindowCallbacks null_callbacks;
mCallbacks = &null_callbacks;
mIsConstructing = true;

// Voodoo for calling cocoa from carbon (see llwindowmacosx-objc.mm).
setupCocoa();
Expand Down Expand Up @@ -236,9 +237,8 @@ LLWindowMacOSX::LLWindowMacOSX(LLWindowCallbacks* callbacks,
}

mCallbacks = callbacks;
mIsConstructing = false;
stop_glerror();


}

// These functions are used as wrappers for our internal event handling callbacks.
Expand Down Expand Up @@ -412,6 +412,14 @@ void callResize(unsigned int width, unsigned int height)
}
}

void callRequestResolutionUpdate()
{
if (gWindowImplementation && gWindowImplementation->getCallbacks())
{
gWindowImplementation->getCallbacks()->handleRequestResolutionUpdate(gWindowImplementation);
}
}

void callMouseMoved(float *pos, MASK mask)
{
if (!gWindowImplementation)
Expand Down Expand Up @@ -705,6 +713,11 @@ void getPreeditLocation(float *location, unsigned int length)
}
}

bool windowCallbacksReady()
{
return gWindowImplementation && !gWindowImplementation->isConstructing();
}

void LLWindowMacOSX::updateMouseDeltas(float* deltas)
{
if (mCursorDecoupled)
Expand Down
9 changes: 9 additions & 0 deletions indra/llwindow/llwindowmacosx.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ class LLWindowMacOSX : public LLWindow
void restore() override;
bool getFullscreen();
bool getPosition(LLCoordScreen *position) override;

// Content view size in backing pixels (excludes title bar).
// Note: on Windows, getSize(LLCoordScreen) returns the outer window frame size (includes title bar and borders).
bool getSize(LLCoordScreen *size) override;

bool getSize(LLCoordWindow *size) override;
bool setPosition(LLCoordScreen position) override;
bool setSizeImpl(LLCoordScreen size) override;
Expand Down Expand Up @@ -147,6 +151,10 @@ class LLWindowMacOSX : public LLWindow
// enable or disable multithreaded GL
static void setUseMultGL(bool use_mult_gl);

// During construction null_callbacks are used,
// this is a way to determine when callbacks are ready.
bool isConstructing() const { return mIsConstructing; }

protected:
LLWindowMacOSX(LLWindowCallbacks* callbacks,
const std::string& title, const std::string& name, int x, int y, int width, int height, U32 flags,
Expand Down Expand Up @@ -223,6 +231,7 @@ class LLWindowMacOSX : public LLWindow
bool mMinimized;
U32 mFSAASamples;
bool mForceRebuild;
bool mIsConstructing = true;

S32 mDragOverrideCursor;

Expand Down
4 changes: 4 additions & 0 deletions indra/llwindow/llwindowwin32.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ class LLWindowWin32 : public LLWindow
void restore() override;
bool getFullscreen();
bool getPosition(LLCoordScreen *position) override;

// Outer window frame size in pixels (includes title bar and borders).
// Note OS Specific behavior: On macOS excludes title bar.
bool getSize(LLCoordScreen *size) override;

bool getSize(LLCoordWindow *size) override;
bool setPosition(LLCoordScreen position) override;
bool setSizeImpl(LLCoordScreen size) override;
Expand Down
6 changes: 6 additions & 0 deletions indra/newview/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ set(viewer_SOURCE_FILES
llchathistory.cpp
llchatitemscontainerctrl.cpp
llchatmsgbox.cpp
llchatservicehistory.cpp
llchatservicehistorycore.cpp
llchiclet.cpp
llchicletbar.cpp
llclassifiedinfo.cpp
Expand Down Expand Up @@ -811,6 +813,8 @@ set(viewer_HEADER_FILES
llchathistory.h
llchatitemscontainerctrl.h
llchatmsgbox.h
llchatservicehistory.h
llchatservicehistorycore.h
llchiclet.h
llchicletbar.h
llclassifiedinfo.h
Expand Down Expand Up @@ -2405,6 +2409,8 @@ if (LL_TESTS)
include(LLAddBuildTest)
SET(viewer_TEST_SOURCE_FILES
llagentaccess.cpp
llchatservicehistorycore.cpp
llconversationlog.cpp
lldateutil.cpp
# llmediadataclient.cpp
lllogininstance.cpp
Expand Down
11 changes: 11 additions & 0 deletions indra/newview/app_settings/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16528,6 +16528,17 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>ChatServiceEnabled</key>
<map>
<key>Comment</key>
<string>Enable accumulated direct-message history from ChatService for this login.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>SingleModeDoubleClickOpenWindow</key>
<map>
<key>Comment</key>
Expand Down
8 changes: 8 additions & 0 deletions indra/newview/llappviewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@
#include "lldrawpoolbump.h"
#include "llvieweraudio.h"
#include "llimview.h"
#include "llchatservicehistory.h"
#include "llviewerthrottle.h"
#include "llparcel.h"
#include "llavatariconctrl.h"
Expand Down Expand Up @@ -5698,6 +5699,8 @@ void LLAppViewer::idleShutdown()
// All floaters are closed. Tell server we want to quit.
if (!logoutRequestSent())
{
// Fence outstanding history work before logout tears down its account and capability context.
LLChatServiceHistory::stop();
sendLogoutRequest();

// Wait for a LogoutReply message
Expand Down Expand Up @@ -6114,6 +6117,9 @@ void LLAppViewer::idleNetwork()

void LLAppViewer::disconnectViewer()
{
// This is idempotent and also covers disconnect paths that bypass the normal logout request.
LLChatServiceHistory::stop();

if (gDisconnected)
{
return;
Expand Down Expand Up @@ -6476,6 +6482,8 @@ void LLAppViewer::handleLoginComplete()
gDebugInfo["MainloopTimeoutState"] = LLAppViewer::instance()->mMainloopTimeout->getState();
}

// Start only after login has established the per-account storage and region capability context.
LLChatServiceHistory::start();
mOnLoginCompleted();

writeDebugInfo();
Expand Down
10 changes: 9 additions & 1 deletion indra/newview/llavataractions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "llconversationlog.h"
#include "llfloateravatarpicker.h" // for LLFloaterAvatarPicker
#include "llfloaterconversationpreview.h"
#include "llchatservicehistory.h"
#include "llfloatergroupinvite.h"
#include "llfloatergroups.h"
#include "llfloaterreg.h"
Expand Down Expand Up @@ -1247,6 +1248,12 @@ void LLAvatarActions::inviteToGroup(const LLUUID& id)
// static
void LLAvatarActions::viewChatHistory(const LLUUID& id)
{
// A pending account-wide deletion suppresses both transcript and service sources.
if (LLChatServiceHistory::historySuppressed())
{
return;
}

const std::vector<LLConversation>& conversations = LLConversationLog::instance().getConversations();
std::vector<LLConversation>::const_iterator iter = conversations.begin();

Expand All @@ -1259,14 +1266,15 @@ void LLAvatarActions::viewChatHistory(const LLUUID& id)
}
}

if (LLLogChat::isTranscriptExist(id))
if (LLLogChat::isTranscriptExist(id) || LLChatServiceHistory::localHistoryExists(id))
{
LLAvatarName avatar_name;
LLSD extended_id(id);

LLAvatarNameCache::get(id, &avatar_name);
extended_id[LL_FCP_COMPLETE_NAME] = avatar_name.getCompleteName();
extended_id[LL_FCP_ACCOUNT_NAME] = avatar_name.getAccountName();
extended_id[LL_FCP_PARTICIPANT_ID] = id;
LLFloaterReg::showInstance("preview_conversation", extended_id, true);
}
}
Expand Down
30 changes: 25 additions & 5 deletions indra/newview/llchathistory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <boost/signals2.hpp>

#include "llavatarnamecache.h"
#include "llchatservicehistory.h"
#include "llinstantmessage.h"

#include "llimview.h"
Expand Down Expand Up @@ -1019,7 +1020,12 @@ class LLChatHistoryHeader: public LLPanel
menu->setItemVisible("Zoom In", should_show_zoom && gObjectList.findObject(mAvatarID));
menu->setItemEnabled("Block Unblock", LLAvatarActions::canBlock(mAvatarID));
menu->setItemEnabled("Mute Text", LLAvatarActions::canBlock(mAvatarID));
menu->setItemEnabled("Chat History", LLLogChat::isTranscriptExist(mAvatarID));

const bool history_available =
!LLChatServiceHistory::historySuppressed() &&
(LLLogChat::isTranscriptExist(mAvatarID) ||
LLChatServiceHistory::localHistoryExists(mAvatarID));
menu->setItemEnabled("Chat History", history_available);
}

menu->setItemEnabled("Map", (LLAvatarTracker::instance().isBuddyOnline(mAvatarID) && is_agent_mappable(mAvatarID)) || gAgent.isGodlike() );
Expand Down Expand Up @@ -1266,6 +1272,16 @@ void LLChatHistory::onClickMoreText()
void LLChatHistory::clear()
{
mLastFromName.clear();

// Inline views die on the mortician pass. Detach this transcript's current
// views now so a synchronous replay cannot draw old and replacement panels together.
const LLView* document_view = mEditor->getDocumentView();
const LLView::child_list_t document_children = *document_view->getChildList();
for (LLView* child : document_children)
{
mEditor->removeDocumentChild(child);
}

mEditor->clear();
mLastFromID = LLUUID::null;
}
Expand Down Expand Up @@ -1517,14 +1533,18 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
bool create_toast = true;
if (notification->getName() == "OfferFriendship")
{
// We don't want multiple friendship offers to appear, this code checks if there are previous offers
// by iterating though all panels.
// Note: it might be better to simply add a "pending offer" flag somewhere
// Keep one actionable offer per sender without hiding requests from other residents.
const LLUUID from_id = notification->getPayload()["from_id"].asUUID();
for (auto& panel : LLToastNotifyPanel::instance_snapshot())
{
LLIMToastNotifyPanel * imtoastp = dynamic_cast<LLIMToastNotifyPanel *>(&panel);
const std::string& notification_name = panel.getNotificationName();
if (notification_name == "OfferFriendship"
LLNotificationPtr panel_notification = LLNotificationsUtil::find(panel.getID());
// A synchronous transcript replay replaces panels already queued for deletion.
if (!panel.isDead()
&& notification_name == "OfferFriendship"
&& panel_notification
&& panel_notification->getPayload()["from_id"].asUUID() == from_id
&& panel.isControlPanelEnabled()
&& imtoastp)
{
Expand Down
Loading
Loading