| 1 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | import type { EnvironmentShellStatus } from "@t3tools/client-runtime/state/shell"; |
| 3 | import type { EnvironmentId, MessageId } from "@t3tools/contracts"; |
| 4 | import { Atom } from "effect/reactivity"; |
| 5 | |
| 6 | import { appAtomRegistry } from "./atom-registry"; |
| 7 | import { environmentShell } from "./shell"; |
| 8 | import { threadOutboxManager } from "./thread-outbox"; |
| 9 | |
| 10 | const threadOutboxShellStatusesAtom = Atom.make( |
| 11 | (get): ReadonlyMap<EnvironmentId, EnvironmentShellStatus> => { |
| 12 | const statuses = new Map<EnvironmentId, EnvironmentShellStatus>(); |
| 13 | for (const queue of Object.values(get(threadOutboxManager.queuedMessagesByThreadKeyAtom))) { |
| 14 | const environmentId = queue[0]?.environmentId; |
| 15 | if (environmentId !== undefined && !statuses.has(environmentId)) { |
| 16 | statuses.set(environmentId, get(environmentShell.stateValueAtom(environmentId)).status); |
| 17 | } |
| 18 | } |
| 19 | return statuses; |
| 20 | }, |
| 21 | ).pipe(Atom.withLabel("mobile:thread-outbox:shell-statuses")); |
| 22 | |
| 23 | /** |
| 24 | * Queued pending tasks the outbox drain must not deliver right now: the one |
| 25 | * open in the new-task editor, plus any whose latest edits could not be saved |
| 26 | * back yet (delivering those would send stale content). Editing sessions hold |
| 27 | * their message id here and release it once the queued payload is current. |
| 28 | */ |
| 29 | export const editingQueuedMessageIdsAtom = Atom.make<Readonly<Record<MessageId, true>>>({}).pipe( |
| 30 | Atom.keepAlive, |
| 31 | Atom.withLabel("mobile:thread-outbox:editing-message-ids"), |
| 32 | ); |
| 33 | |
| 34 | export const dispatchingQueuedMessageIdAtom = Atom.make<MessageId | null>(null).pipe( |
| 35 | Atom.keepAlive, |
| 36 | Atom.withLabel("mobile:thread-outbox:dispatching-message-id"), |
| 37 | ); |
| 38 | |
| 39 | export function holdEditingQueuedMessage(messageId: MessageId): void { |
| 40 | const current = appAtomRegistry.get(editingQueuedMessageIdsAtom); |
| 41 | if (current[messageId]) { |
| 42 | return; |
| 43 | } |
| 44 | appAtomRegistry.set(editingQueuedMessageIdsAtom, { ...current, [messageId]: true }); |
| 45 | } |
| 46 | |
| 47 | export function releaseEditingQueuedMessage(messageId: MessageId): void { |
| 48 | const current = appAtomRegistry.get(editingQueuedMessageIdsAtom); |
| 49 | if (!current[messageId]) { |
| 50 | return; |
| 51 | } |
| 52 | const next = { ...current }; |
| 53 | delete next[messageId]; |
| 54 | appAtomRegistry.set(editingQueuedMessageIdsAtom, next); |
| 55 | } |
| 56 | |
| 57 | export function useThreadOutboxMessages() { |
| 58 | return useAtomValue(threadOutboxManager.queuedMessagesByThreadKeyAtom); |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | * Thread keys (`environmentId:threadId`) of existing threads with a message |
| 63 | * waiting in the outbox. Creations are excluded: they have no thread row yet |
| 64 | * and surface as pending tasks instead. Derived once so list builders and |
| 65 | * reorder planners agree on which settled threads are pulled back to active. |
| 66 | */ |
| 67 | export const queuedThreadKeysAtom = Atom.make((get): ReadonlySet<string> => { |
| 68 | const keys = new Set<string>(); |
| 69 | for (const [threadKey, queue] of Object.entries( |
| 70 | get(threadOutboxManager.queuedMessagesByThreadKeyAtom), |
| 71 | )) { |
| 72 | if (queue.some((message) => message.creation === undefined)) { |
| 73 | keys.add(threadKey); |
| 74 | } |
| 75 | } |
| 76 | return keys; |
| 77 | }).pipe(Atom.withLabel("mobile:thread-outbox:queued-thread-keys")); |
| 78 | |
| 79 | export function useQueuedThreadKeys(): ReadonlySet<string> { |
| 80 | return useAtomValue(queuedThreadKeysAtom); |
| 81 | } |
| 82 | |
| 83 | export function useThreadOutboxShellStatuses() { |
| 84 | return useAtomValue(threadOutboxShellStatusesAtom); |
| 85 | } |