Repository navigation
fix(mobile): permission mode picks follow changes made on other clients #34107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
593b977
9182999
8ccbde6
2ce4f23
685446d
5eb46d3
7616b6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ import { | |
| managedRuntimeModeDenial, | ||
| PROVIDER_SEND_TURN_MAX_ATTACHMENTS, | ||
| type EnvironmentId, | ||
| type ThreadId, | ||
| } from "@t3tools/contracts"; | ||
| import { buildTemporaryWorktreeBranchName } from "@t3tools/shared/git"; | ||
| import * as Cause from "effect/Cause"; | ||
|
|
@@ -78,6 +79,7 @@ import { | |
| type ComposerDraft, | ||
| getComposerDraftSnapshot, | ||
| mergeComposerDraftContent, | ||
| releaseComposerDraftRuntimeMode, | ||
| replaceComposerDraftAttachments, | ||
| removeDeliveredCloudQueuedMessage, | ||
| undoComposerDraftMerge, | ||
|
|
@@ -592,7 +594,9 @@ async function preserveUploadedAttachmentsForEditor( | |
| const deliveryInputsAtom = Atom.make((get) => { | ||
| const connectedEnvironments = get(workspaceConnections.environmentsAtom); | ||
| const operableEnvironments = new Set<EnvironmentId>(); | ||
| const pendingThreadIds = new Map<EnvironmentId, ReadonlySet<ThreadId>>(); | ||
| for (const { environmentId } of connectedEnvironments) { | ||
| pendingThreadIds.set(environmentId, get(threadEnvironment.pendingThreadIdsAtom(environmentId))); | ||
| if ( | ||
| sessionHasScope( | ||
| get(environmentSession.sessionStateAtom(environmentId)), | ||
|
|
@@ -612,6 +616,7 @@ const deliveryInputsAtom = Atom.make((get) => { | |
| serverConfigs: get(environmentServerConfigsAtom), | ||
| connectedEnvironments, | ||
| operableEnvironments, | ||
| pendingThreadIds, | ||
| }; | ||
| }).pipe(Atom.withLabel("mobile:thread-outbox:delivery-inputs")); | ||
|
|
||
|
|
@@ -831,6 +836,24 @@ export function createThreadOutboxDelivery(): { | |
| return false; | ||
| } | ||
| } | ||
| // Loading restores the saved draft for a key this release empties, so it must finish first. | ||
| const draftsLoaded = await waitForComposerDraftsLoaded().then( | ||
| () => true, | ||
| () => false, | ||
| ); | ||
| const threadKey = scopedThreadKey(queuedMessage.environmentId, queuedMessage.threadId); | ||
| // A newer pick can share this mode while another queued message still moves the thread off it. | ||
| const modeChangeQueued = ( | ||
| appAtomRegistry.get(threadOutboxManager.queuedMessagesByThreadKeyAtom)[threadKey] ?? [] | ||
| ).some( | ||
| (message) => | ||
| message.messageId !== queuedMessage.messageId && | ||
| message.runtimeMode !== undefined && | ||
| message.runtimeMode !== settings.runtimeMode, | ||
| ); | ||
| if (draftsLoaded && !modeChangeQueued) { | ||
| releaseComposerDraftRuntimeMode(threadKey, settings.runtimeMode); | ||
| } | ||
|
|
||
| if (settings.interactionMode !== thread.interactionMode) { | ||
| if (!hasAccess()) return true; | ||
|
|
@@ -1191,6 +1214,7 @@ export function createThreadOutboxDelivery(): { | |
| serverConfigs, | ||
| connectedEnvironments, | ||
| operableEnvironments, | ||
| pendingThreadIds, | ||
| } = appAtomRegistry.get(deliveryInputsAtom); | ||
| if (dispatchingQueuedMessageId !== null) { | ||
| return; | ||
|
|
@@ -1329,6 +1353,15 @@ export function createThreadOutboxDelivery(): { | |
| if (deliveryAction === "send" && !operableEnvironments.has(nextQueuedMessage.environmentId)) { | ||
| continue; | ||
| } | ||
| // An unconfirmed optimistic change, such as Resume's mode, can still be refused; the send | ||
| // must compare against what the server holds, or it skips a mode change it still needs. | ||
| if ( | ||
| deliveryAction === "send" && | ||
| creation === undefined && | ||
| pendingThreadIds.get(nextQueuedMessage.environmentId)?.has(nextQueuedMessage.threadId) | ||
| ) { | ||
| continue; | ||
|
Comment on lines
+1358
to
+1363
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Recheck pending thread state and use the live thread after The pending gate runs before Read the pending state and the live thread inside the callback. Defer when the thread is still pending. Otherwise compare against the live thread. Suggested fix
|
||
| } | ||
| const dispatchStep = resolveThreadOutboxDispatchStep({ | ||
| deliveryAction, | ||
| fileAttachments: nextQueuedMessage.attachments.filter( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.