The compose encrypt path in src/taskpane/compose-view.ts calls saveItem() — a server round-trip on a draft that may carry a multi-MB encrypted body plus attachment — more often than it needs to.
encryptAndPrepareSend, compose-view.ts:512-537:
setStatus("Saving encrypted draft…");
await saveItem(); // 517
state.encrypted = true; // 521-524: in-memory only
state.preEncryptBody = html;
state.encryptedAttachmentId = attachmentId;
state.encryptedSnapshot = relevantStateString();
const stampedRecipients = recipientsKey();
state.encryptedRecipientsHeader = stampedRecipients;
await saveItem(); // 532
await setItemHeaders({ ... });
await saveItem(); // 537
Nothing between line 517 and line 532 touches the item — only module-local state. The save at 532 exists because the "save, write header, save" idiom is copied from persistEncryptOnSend (compose-view.ts:61-63), where the leading save is what gives a fresh draft an itemId before a header write. Here the save at 517 has already done that, so 532 is a second full upload of the same draft for no benefit. The user waits through it twice in the slowest part of the flow.
Related, on the same idiom: persistEncryptedRecipients (compose-view.ts:70-83) also does save → setItemHeaders → save, and is called from renderToggleUI() (line 342), which runs on every recipient change and every policy-editor keystroke. It is guarded by the state.encryptedRecipientsHeader !== expected comparison so it only fires when the value actually changes, but each fire is still two full draft saves. Once the draft is encrypted, editing recipients back and forth flips expected between the key and null and pays that cost each time.
Worth a human decision rather than a mechanical edit, because the save calls are deliberate (see the comment at 512-515 about racing the upload against Send, and the "flushes the header change to the server so the OnMessageSend handler sees it" comment at 56-60) and because a wrong trim here means OnMessageSend reads a stale header — which fails toward blocking a send, but is still a regression in the flow.
Possible directions:
- Drop the redundant
await saveItem() at line 532; keep 517 (pre-header, gives the itemId) and 537 (flushes the header).
- Factor the "save → set headers → save" idiom into one helper in
src/lib/office-helpers.ts so the three call sites cannot drift, with a parameter for skipping the leading save when the caller has just saved.
- For
persistEncryptedRecipients, consider debouncing or deferring the header write until the compose view settles, instead of writing on each renderToggleUI() transition.
The compose encrypt path in
src/taskpane/compose-view.tscallssaveItem()— a server round-trip on a draft that may carry a multi-MB encrypted body plus attachment — more often than it needs to.encryptAndPrepareSend,compose-view.ts:512-537:Nothing between line 517 and line 532 touches the item — only module-local state. The save at 532 exists because the "save, write header, save" idiom is copied from
persistEncryptOnSend(compose-view.ts:61-63), where the leading save is what gives a fresh draft anitemIdbefore a header write. Here the save at 517 has already done that, so 532 is a second full upload of the same draft for no benefit. The user waits through it twice in the slowest part of the flow.Related, on the same idiom:
persistEncryptedRecipients(compose-view.ts:70-83) also does save → setItemHeaders → save, and is called fromrenderToggleUI()(line 342), which runs on every recipient change and every policy-editor keystroke. It is guarded by thestate.encryptedRecipientsHeader !== expectedcomparison so it only fires when the value actually changes, but each fire is still two full draft saves. Once the draft is encrypted, editing recipients back and forth flipsexpectedbetween the key andnulland pays that cost each time.Worth a human decision rather than a mechanical edit, because the save calls are deliberate (see the comment at 512-515 about racing the upload against Send, and the "flushes the header change to the server so the OnMessageSend handler sees it" comment at 56-60) and because a wrong trim here means
OnMessageSendreads a stale header — which fails toward blocking a send, but is still a regression in the flow.Possible directions:
await saveItem()at line 532; keep 517 (pre-header, gives the itemId) and 537 (flushes the header).src/lib/office-helpers.tsso the three call sites cannot drift, with a parameter for skipping the leading save when the caller has just saved.persistEncryptedRecipients, consider debouncing or deferring the header write until the compose view settles, instead of writing on eachrenderToggleUI()transition.