Preserve inherited previews for paginated thread forks (#60559)

## Why

Reference-backed forks can lack a local opening user message, causing them to disappear from thread listings or lose their inherited preview when a new turn is written. Compaction can also remove the opening message from the model context used to prepare a fork.

## What changed

- Persist inherited previews and preserve them when recording new turns and merging thread-list metadata.
- Include reference-backed forks with inherited history in thread listings before their first local message.
- Recover opening previews from transcript history within the requested fork boundary when compaction omits them from model context.
- Use the shared `user_message_preview` helper when deriving previews from rollout items.

## Testing

Extend thread-fork integration tests to check previews in listings before and after a new turn, after reopening, and when forking at a boundary after compaction.

GitOrigin-RevId: 5962cf71ea7104f07c9013cc77259b105e4c60d7
This commit is contained in:
Eric Traut
2026-11-10 08:52:02 +00:00
committed by copyberry
parent ae5c75dbfc
commit f988a5f0da
11 changed files with 184 additions and 26 deletions
@@ -55,7 +55,10 @@ pub(crate) struct PendingThreadMetadataPatch {
}
impl ThreadMetadataSync {
pub(crate) async fn for_create(params: &CreateThreadParams) -> Self {
pub(crate) async fn for_create(
params: &CreateThreadParams,
inherited_preview: Option<&str>,
) -> Self {
let created_at = Utc::now();
let cwd = params.metadata.cwd.clone().unwrap_or_default();
let git_info = if get_git_repo_root(cwd.as_path()).is_some() {
@@ -93,7 +96,8 @@ impl ThreadMetadataSync {
Self {
thread_id: params.thread_id,
cwd_seen: !cwd.as_os_str().is_empty(),
preview_seen: guardian_review,
preview_seen: guardian_review
|| inherited_preview.is_some_and(|preview| !preview.is_empty()),
first_user_message_seen: guardian_review,
title_seen: guardian_review,
pending_update: Some(update),
@@ -485,7 +489,7 @@ mod tests {
#[tokio::test]
async fn create_metadata_records_originator_without_project() {
let thread_id = ThreadId::new();
let sync = ThreadMetadataSync::for_create(&CreateThreadParams {
let params = CreateThreadParams {
attached_thread: false,
creator_user_id: None,
creator_account_id: None,
@@ -511,8 +515,8 @@ mod tests {
model_provider: "test-provider".to_string(),
memory_mode: ThreadMemoryMode::Enabled,
},
})
.await;
};
let sync = ThreadMetadataSync::for_create(&params, /*inherited_preview*/ None).await;
let update = sync.take_pending_update().expect("pending metadata update");
assert_eq!(