UI/UX Part 06 — Message Composer, Attachments, Voice Notes & Drafts UX Architecture

Reusable P2P Communication Platform

Status: UI/UX architecture specification
UI Series: Part 06
Desktop UI: Dioxus
Android UI: Kotlin + Jetpack Compose
Core runtime: Rust
Primary purpose: define the complete message composition experience across desktop and Android, including text entry, drafts, replies, edits, attachments, paste/share flows, voice notes, camera/media capture, preview, reordering, offline queuing, validation, autosave, accessibility, process-death recovery, and Rust-backed send semantics.


1. Purpose

The composer is where user intent becomes durable outbound content.

It must handle:

plain text
multiline text
reply
edit
attachments
images
video
documents
voice notes
paste
drag/drop
shared content
offline send
draft recovery
permission flows

without duplicating message-domain logic in the UI.

The governing principle is:

The composer owns temporary input interaction; Rust owns draft persistence, validation, attachment preparation, message creation, and send truth.


2. Architectural Position

User Input
   │
   ├── text
   ├── attachment
   ├── voice note
   ├── paste/drop/share
   └── reply/edit intent
   │
   ▼
Platform Composer UI
   │
   ▼
Composer Presenter / ViewModel
   │
   ▼
Rust Composition Service
   │
   ├── validation
   ├── draft persistence
   ├── attachment ingestion
   ├── message creation
   └── outbox

3. Composer State

Recommended shared presentation model:

#![allow(unused)]
fn main() {
pub struct ComposerSnapshot {
    pub conversation: ConversationId,
    pub mode: ComposerMode,
    pub text: String,
    pub attachments: Vec<ComposerAttachment>,
    pub can_send: bool,
    pub validation: ComposerValidation,
    pub draft_state: DraftState,
    pub voice_state: VoiceComposerState,
}
}

4. Composer Modes

#![allow(unused)]
fn main() {
pub enum ComposerMode {
    New,
    Reply {
        target: MessageReferenceView,
    },
    Edit {
        target: MessageId,
    },
}
}

Only one primary mode should be active at a time.


5. UI-Owned vs Rust-Owned Composer State

UI may own transient presentation details:

text field focus
IME state
cursor position
attachment drag hover
expanded attachment sheet
recording gesture progress

Rust should own or validate:

draft content if durable
reply target
edit target
attachment list
attachment identity
validation
send eligibility

6. Draft Strategy

Recommended:

draft is a first-class Rust domain object

rather than only in ViewModel/component memory.

This improves:

process-death recovery
desktop restart
device crash recovery
future multi-device draft sync

7. Draft Identity

#![allow(unused)]
fn main() {
pub struct DraftId([u8; 16]);
}

Usually one active draft per conversation.


8. Draft Model

#![allow(unused)]
fn main() {
pub struct MessageDraft {
    pub id: DraftId,
    pub conversation: ConversationId,
    pub mode: ComposerMode,
    pub text: String,
    pub attachments: Vec<DraftAttachmentRef>,
    pub updated_at: Timestamp,
}
}

9. Draft Is Not a Message

A draft must not:

consume MessageSequence
trigger notifications
send typing receipts
appear as sent history

until user sends.


10. Autosave

Draft autosave should be:

debounced

not persisted per keystroke.

Example:

300–1000 ms after change

Tune by storage/backend.


11. Crash Safety

If application crashes after user typed:

draft reappears

according to autosave boundary.


12. Draft Emptying

If user clears composer:

delete empty draft

unless reply/edit state still meaningful.


13. Draft in Inbox

Part 04 may show:

Draft: ...

from Rust draft projection.


14. Draft Sync

Initial recommendation:

device-local

Later optional:

account-wide encrypted draft sync

Do not require cross-device draft sync in v1.


15. Multi-Device Draft Conflict

If later synced:

latest device edit
or
per-device drafts

must be explicit.

Avoid silent text merging.


16. Text Input

Support:

Unicode
emoji
multiline
RTL
mixed scripts
clipboard
hardware keyboard
IME composition

17. Text Length

UI may show limit hint.

Rust remains authority.


18. Validation

Examples:

message too long
too many attachments
attachment too large
unsupported file
editing not allowed
conversation read-only

19. Validation Timing

Use:

lightweight local prevalidation
+
authoritative Rust validation

for responsive UX.


20. Send Button State

Enable when:

text non-empty
or
valid attachment exists
or
valid voice note exists

and Rust says sending allowed.


21. Empty Whitespace

Trim policy must be defined.

Recommended:

message consisting only of whitespace → invalid

while preserving meaningful leading/trailing spaces inside code blocks if supported.


22. Enter / Send Behavior

Android phone:

Enter → newline
send button → send

Desktop:

Enter → configurable
Ctrl/Cmd+Enter → send

Recommendation for desktop default:

Enter → newline
Ctrl/Cmd+Enter → send

or user setting later.


23. IME Action

Android keyboard action can show:

Send

only if product wants single-line-like behavior.

For multiline chats, explicit send button is clearer.


24. Composer Expansion

Text area expands vertically up to a bounded max height.

Then scroll internally.


25. Desktop Composer Height

Example:

1–8 lines

before internal scrolling.


26. Android Composer Height

Example:

1–6 lines

to preserve screen space above keyboard.


27. Reply Mode

When replying:

reply context strip

appears above composer.

Contains:

sender
message excerpt/type
cancel

28. Reply Context

Tap/click reply context can:

jump to original

if useful.


29. Cancel Reply

Clears reply target but preserves typed text.


30. Edit Mode

Edit context must be visually distinct:

Editing message

with:

original text loaded
save action
cancel

31. Cancel Edit

Restores previous draft only if architecture explicitly preserved it.

Recommendation:

editing temporarily replaces composer

and original draft is stashed locally/Rust-side until edit completes/cancels.


32. Edit Save

Rust validates:

ownership
time/policy
message still exists
content constraints

33. Edit Conflict

If message changed remotely while editing:

show updated state
offer reload/overwrite only if supported

Avoid silent overwrite.


34. Attachments Overview

Supported categories:

Photos
Videos
Documents
Audio
Camera capture
Other files

35. Attachment Picker Architecture

UI requests platform picker.

Rust receives safe file handles/FDs/paths.


36. Android Attachment Flow

Compose
→ Activity Result API
→ URI
→ content metadata
→ FD/stream handle
→ Rust file ingestion

37. Desktop Attachment Flow

Dioxus
→ native file dialog / drag-drop
→ path/handle
→ Rust file ingestion

38. Never Copy Giant Files Through UI State

Hard rule:

no multi-GB ByteArray in Kotlin
no giant Vec<u8> in Dioxus component state

Use:

descriptor
path
stream
handle

39. Attachment Ingestion

Rust creates:

#![allow(unused)]
fn main() {
pub struct ComposerAttachment {
    pub id: ComposerAttachmentId,
    pub kind: AttachmentKind,
    pub display_name: String,
    pub size: u64,
    pub preview: Option<AttachmentPreviewRef>,
    pub state: ComposerAttachmentState,
}
}

40. Attachment States

#![allow(unused)]
fn main() {
pub enum ComposerAttachmentState {
    Inspecting,
    Ready,
    Preparing,
    Invalid,
    Failed,
}
}

41. Inspecting

Rust validates:

type
size
readability
security policy

42. Ready

Can be sent.


43. Preparing

Examples:

thumbnail
video metadata
image processing
encryption preparation

44. Invalid

Examples:

too large
unsupported
not readable

45. Attachment Preview Strip

Display above composer.

Each item:

thumbnail/icon
name
size/type
remove
state

46. Reordering Attachments

Useful if multi-attachment message preserves ordering.

Support drag on desktop.

Android can support long-press reorder later.


47. Initial Recommendation

Support attachment ordering in data model.

Manual reordering optional for v1.


48. Remove Attachment

Removes draft reference.

If Rust staged temporary file:

release staging resource

when no longer referenced.


49. Attachment Count Limit

Rust enforces.

UI shows clear limit.


50. Duplicate Attachment

Policy:

allow duplicates
or
coalesce identical selection

Recommendation:

allow unless exact same handle selected accidentally in one picker batch

51. Clipboard Paste

Desktop:

paste text
image
file path where available

Android:

paste text
image/content URI if platform provides

52. Paste Image

Create attachment draft.

Do not encode large image into text field.


53. Drag and Drop

Desktop drop over conversation/composer:

visual overlay
drop
ingest
preview

54. Drag Over State

Presentation-only.


55. Invalid Drop

Show:

Unsupported file
File too large

inline/snackbar.


56. Share Into App

Android incoming ACTION_SEND / ACTION_SEND_MULTIPLE:

external app
→ Kotlin intent
→ safe content handles
→ choose conversation
→ create composer draft

57. Share Text

Populate composer text.


58. Share Files

Populate attachment draft.


59. Share + Existing Draft

Conflict policy required.

Recommendation:

ask:
Replace draft / Add to draft / Cancel

if existing draft non-empty.


60. Camera Capture

Android:

camera permission
capture flow
result handle
Rust ingestion

61. Desktop Camera Capture

Optional.

Not required if desktop product initially uses file picker only.


62. Photo Capture

Use platform-native capture or camera module.

Avoid duplicating full camera stack in composer.


63. Video Capture

Likewise.

May need duration/file-size limit.


64. Image Preprocessing

Potential:

orientation normalization
thumbnail generation
optional downscale

Rust-first where practical.


65. Original vs Compressed Image

User may choose:

Original quality
Optimized

later.


66. Video Compression

Do not silently recompress large video in v1 unless product policy explicitly says.


67. Attachment Security

Rust must inspect:

path safety
readability
size
type

before accepting.


68. Filename Display

Use safe display name.

Never trust filename as path.


69. MIME

Treat MIME as hint, not sole truth.


70. Voice Notes

Voice-note composition is distinct from live calls.


71. Voice Note States

#![allow(unused)]
fn main() {
pub enum VoiceComposerState {
    Idle,
    RequestingPermission,
    Recording,
    Paused,
    Preview,
    Preparing,
    Failed,
}
}

72. Voice Note Flow

press/tap record
→ permission if needed
→ recording
→ stop
→ preview
→ send or discard

73. Android Microphone Permission

Requested contextually when recording begins.


74. Desktop Microphone Permission

Platform-specific if required.


75. Hold-to-Record vs Tap-to-Record

Android can support familiar hold-to-record interaction.

Desktop should prefer explicit click record/stop.


76. Android Hold-to-Record

Possible gestures:

hold → record
slide left → cancel
slide up → lock recording

Only if interaction is thoroughly tested/accessibility-friendly.


77. Accessibility Alternative

Always provide explicit:

Start recording
Stop
Cancel

actions.

Do not require gesture-only recording.


78. Recording Timer

Show duration.


79. Waveform

Optional visual aid.

Not correctness requirement.


80. Recording Buffer

Audio path should not store raw PCM in Compose state.

Rust/native audio subsystem owns media buffers.


81. Voice Recording Format

Use product-selected encoded format from Rust media pipeline.

UI only sees:

duration
waveform summary
state
temporary clip ID

82. Pause Recording

Optional.

Useful for longer voice notes.

Not required for v1.


83. Voice Preview

After recording:

play
scrub
delete
send

84. Voice Playback

Use media/audio service.

No giant audio data in UI state.


85. Voice Send

Rust turns temporary clip into attachment/message content.


86. Voice Draft Persistence

Policy options:

persist unfinished voice note
or
discard on exit

Recommendation:

persist completed preview clip
discard active in-progress raw recording on crash

unless robust crash-safe recording exists.


87. Interrupted Recording

If app backgrounds unexpectedly:

pause/stop safely

according to platform policy.


88. Incoming Call During Voice Recording

Recommendation:

pause/stop voice recording
preserve completed portion if possible

call gets priority.


89. Call While Composer Draft Exists

Draft remains intact.


90. Send While Offline

Rust accepts message into outbox if policy allows.

UI clears composer only after durable local message creation succeeds.


91. Important Send Rule

Do not clear input immediately on button tap before Rust accepts it.

Correct:

tap Send
→ Rust creates durable pending message
→ success
→ clear draft/composer

92. Send Failure Before Durable Commit

Composer remains intact.

Show error.


93. Send Accepted but Network Offline

Composer clears.

Timeline shows:

Queued

94. Double Send Protection

Disable duplicate send while same submission is being committed, or use CommandId.


95. Compose Recomposition Safety

Never invoke send from recomposition.

Only explicit event.


96. Dioxus Re-render Safety

Same.


97. Send Command

#![allow(unused)]
fn main() {
pub struct SendMessageCommand {
    pub command_id: CommandId,
    pub conversation: ConversationId,
    pub draft_id: Option<DraftId>,
    pub mode: SendMode,
    pub text: String,
    pub attachments: Vec<ComposerAttachmentId>,
}
}

98. Send Mode

#![allow(unused)]
fn main() {
pub enum SendMode {
    New,
    Reply(MessageId),
}
}

Edit is separate command.


99. Sending Attachment Message

Rust determines:

message metadata
content keys
outbox
transfer state

UI does not manually coordinate message + file transfer.


100. Attachment Send Atomicity

If message references attachment:

message + attachment intent

must be created consistently.


101. Preparation Before Send

Some attachments may need:

metadata extraction
thumbnail
hash

before message can be committed.


102. Large File UX

If preparation takes time:

Preparing…

in composer attachment card.


103. User Can Continue Typing

Attachment preparation should not freeze text input.


104. Send During Preparation

Two options:

disable send until ready

or:

allow send and finish preparation in outbox

Recommendation:

allow only if backend can guarantee durable staged attachment

Otherwise disable until safely staged.


105. Attachment Auto-Upload

Do not upload before user sends unless product explicitly wants pre-upload.

For P2P architecture, staging locally before send is enough.


106. Draft Attachment Storage

Rust may copy/stage selected content into app-controlled secure storage if original URI/path may disappear.


107. Android URI Lifetime

Content URI permissions may not survive process death.

If draft persistence is desired:

persist URI permission
or
copy safely into app-owned draft staging

depending provider/support.


108. Desktop Path Lifetime

Original path may move/delete.

For durable draft attachments:

stage/copy or detect missing at send

109. Draft Staging Policy

Recommended:

small/medium attachment → secure app staging
large attachment → durable source handle/reference when possible

with clear invalidation handling.


110. Missing Draft Attachment

On restore:

File is no longer available
Remove / Re-select

111. Draft Storage Quota

Bound staged draft data.


112. Old Draft Cleanup

GC abandoned drafts after user-defined/system retention.


113. Draft Cleanup Safety

Do not delete staged data still referenced by active draft.


114. Conversation Read-Only

Examples:

blocked
left group
archived read-only policy
security issue

Composer becomes disabled with clear reason.


115. Blocked Contact

Composer:

You blocked this contact
Unblock

116. Removed from Group

Composer:

You can no longer send messages to this group

117. Security Hold

If identity requires review:

sending may be blocked
Review security

according to Part 28 policy.


118. Permissioned Group

If only admins can post:

Only admins can send messages

119. Slow Mode

Future group feature.

Composer displays cooldown.

Not required initially.


120. Attachment Picker UX — Android

Recommended bottom sheet:

Camera
Photos
Video
Document
Audio
Other File

121. Attachment Picker UX — Desktop

Toolbar/menu:

Attach File
Attach Photo

plus drag/drop and paste.


122. Attachment Button

Should remain easy to reach without cluttering composer.


123. Composer Action Layout — Android

Recommended:

[+] [ Text field................ ] [Voice/Send]

When text/attachments present:

voice button → send

124. Composer Action Layout — Desktop

Recommended:

Attachments / Emoji
Multiline Text Editor
Voice optional
Send

125. Emoji Picker

Android:

system keyboard emoji is enough

Desktop may provide emoji picker later.

Not required in v1.


126. Sticker/GIF

Future extension.

Do not complicate base composer.


127. Mentions

Group composer may support:

@name

128. Mention Suggestions

Triggered by:

@

with group member search.


129. Mention Data

Rust supplies valid member IDs.

UI formats suggestions.


130. Mention Token

Do not encode mention only as plain display name.

Use logical ID in message content model.


131. Autocomplete

Potential:

mentions
commands
emoji

Keep modular.


132. Slash Commands

Only if product needs.

Plugins may add safe commands later.


133. Plugin Composer Extensions

Part 24 may allow:

attachment provider
composer action
structured message form

through safe declarative extension points.


134. Plugin Boundary

Plugin must not directly manipulate core composer internals.

Host exposes permissioned API.


135. Structured Messages

Future:

location
poll
contact card
ERP record

Composer extension can produce typed payload.


136. Location Sharing

If added:

explicit permission
preview
precision controls

not automatic.


137. Message Scheduling

Future feature.

Would require:

scheduled outbox

and dedicated UX.

Not part of v1 composer.


138. Disappearing Message Timer

Future per-conversation setting.

Composer may show timer indicator.

Not necessary now.


139. Attachment Captions

Allow optional text caption in same message or per attachment depending product model.


140. Recommendation

For v1:

one text body + ordered attachments

Simpler and interoperable.


141. Per-Attachment Caption

Can be added later.


142. File Names

User may rename attachment display name before send if product supports.

Not necessary initially.


143. Sensitive Attachment Warning

Potential:

executable
archive
unknown file

warning.

Do not over-warn normal documents.


144. Metadata Stripping

Optional privacy feature for photos:

remove EXIF location

before send.


145. Default Recommendation

Strip sensitive location metadata from images by default if technically safe, or offer setting.

Rust media/file preparation owns it.


146. Original Metadata Option

Advanced user can preserve original if desired.


147. Clipboard Privacy

Do not automatically read clipboard.

Only read on explicit paste.


148. Android Clipboard Toast

Respect platform behavior.


149. Voice Note Privacy

Do not upload recording during capture unless user sends.


150. Draft Privacy

Drafts are sensitive local data.

Encrypt/protect under same local storage security policy.


151. Draft Notification

Do not show draft text in OS notifications.


152. Draft Search

Drafts may appear in inbox preview.

Do not include in global message search unless product explicitly wants.


153. Typing Indicator Trigger

Typing service can observe composer activity.


154. Typing Start

When user meaningfully begins input.


155. Typing Stop

On:

send
clear
focus loss
idle
conversation leave

156. Attachment-Only Typing

Do not send "typing" merely because attachment picker is open.


157. Voice Recording Indicator

Part 30 may send recording indicator while active.


158. Privacy Setting

If user disables typing/recording indicators:

composer still works

without sending ephemeral hints.


159. Focus Behavior — Desktop

Open conversation:

focus timeline or composer based on navigation intent

New-conversation command:

focus composer

160. Focus Behavior — Android

Opening conversation should not automatically open keyboard unless user entered via compose intent.


161. Restore Focus

After closing attachment sheet:

return to composer

if appropriate.


162. Sending Focus

After send:

keep composer focused

for rapid desktop chatting.

Android may keep keyboard open.


163. Accessibility — Composer

Text field label:

Message

Reply context announced.

Attachment controls have meaningful labels.


164. Voice Note Accessibility

Explicit controls:

Start recording
Stop recording
Play recording
Delete recording
Send recording

165. Attachment Preview Accessibility

Example:

PDF attachment timetable.pdf, 2.4 MB, remove

166. Send Button Accessibility

Announce disabled reason if useful:

Send unavailable, attachment still preparing

167. Large Font

Composer must expand without covering send/attachment controls.


168. RTL

Text field respects input direction.

Attachment controls and reply strips mirror correctly.


169. Reduced Motion

Attachment add/remove and voice recording animations should not depend on motion.


170. Color Independence

Recording state should use:

icon
label
timer

not red color alone.


171. Desktop Keyboard Shortcuts

Potential:

Ctrl/Cmd+Enter → Send
Esc → Cancel reply/edit
Ctrl/Cmd+Shift+A → Attach file

172. Android Hardware Keyboard

Support:

Ctrl+Enter → Send
Esc/Back → dismiss context

where feasible.


173. Desktop Paste Files

OS clipboard file list can be converted into attachment draft.


174. Desktop Screenshot Paste

Image clipboard becomes attachment.


175. Android Image Paste

Support where IME/content APIs provide rich content.


176. Rich Content from IME

Android keyboards may insert:

GIF
image

through content APIs.

Map to attachment ingestion if supported.


177. Validation UI

Prefer inline near affected item.

Examples:

File exceeds 2 GB limit
Message is too long

178. Global Composer Error

Use above composer for:

Conversation no longer writable

179. Snackbar

Good for:

Attachment removed
Draft restored

only if useful.


180. No Modal for Ordinary Validation

Do not interrupt with dialog for simple file-size error.


181. Send In Progress

Short local commit should be near-instant.

Do not show blocking spinner unless unusual.


182. Large Attachment Preparation

Show progress on attachment tile.


183. Attachment Hashing

If backend hashes large file:

Preparing 42%

optional.


184. User Cancel Preparation

Allow remove/cancel.


185. Composer Backgrounding

Android:

save draft
release UI-only state

186. Process Death

On restart:

restore draft from Rust
revalidate attachment staging

187. Desktop App Restart

Same.


188. Conversation Switch

Before leaving:

flush/debounce draft save

189. Rapid Conversation Switching

Drafts remain per conversation.


190. Multiple Windows Desktop

Two windows open same conversation.

Potential conflict.

Recommendation:

shared Rust draft

with one live draft state.


191. Draft Edit Conflict Across Windows

If both edit simultaneously:

last-writer or explicit lock

Recommendation:

single active draft lease per conversation per device

if this becomes real issue.


192. Draft Lease

Could expose:

Draft active in another window

for multi-window safety.


193. Attachment Staging Across Windows

Owned by Rust draft ID, not window.


194. Composer Search/Slash UI Layer

Autocomplete popup should not block typing.


195. Mention Popup — Desktop

Anchored below text cursor if feasible.


196. Mention Popup — Android

Dropdown/popup above IME/composer.


197. Mention Selection

Inserts structured mention token.


198. Message Formatting Toolbar

Desktop may optionally expose:

bold
italic
code

later.

Android can rely on markdown syntax or contextual toolbar.


199. Formatting Simplicity

Recommendation v1:

basic plain text
links
emoji
code formatting only if important

Avoid heavy rich-text editor complexity initially.


200. Code Message UX

If supporting code blocks:

monospace
preserve whitespace

Rust validation must not trim meaningful code whitespace.


201. Draft Version

#![allow(unused)]
fn main() {
pub struct DraftRevision(pub u64);
}

Useful for stale update prevention.


202. Composer Event

#![allow(unused)]
fn main() {
pub enum ComposerEvent {
    DraftChanged(ComposerSnapshot),
    AttachmentUpdated(ComposerAttachment),
    VoiceStateChanged(VoiceComposerState),
    WritePermissionChanged(bool),
}
}

203. Composer Commands

#![allow(unused)]
fn main() {
pub enum ComposerCommand {
    SetText(String),
    SetReply(Option<MessageId>),
    BeginEdit(MessageId),
    CancelEdit,
    AddAttachment(AttachmentSource),
    RemoveAttachment(ComposerAttachmentId),
    StartVoiceRecording,
    StopVoiceRecording,
    DiscardVoiceRecording,
    Send,
}
}

204. Avoid Per-Keystroke JNI Chatter

Android text updates can be debounced/batched to Rust draft persistence.

ViewModel may hold immediate local text while periodically syncing draft.


205. Immediate UI Text

Compose must remain responsive even if JNI/storage is briefly busy.


206. Draft Sync Strategy Android

Recommended:

Compose TextField immediate state
→ ViewModel
→ debounce
→ Rust draft update

207. Draft Sync Strategy Desktop

Dioxus signal immediate.

Debounced Rust draft persistence.


208. Send Uses Latest Text

Before send:

flush latest local text to Rust/send command payload

so debounce cannot lose final characters.


209. Rust Validation on Send

Rust checks:

current conversation writable
text
attachments
reply/edit target
security
resource limits

210. Send Result

#![allow(unused)]
fn main() {
pub enum SendCommitResult {
    Committed(MessageView),
    Rejected(UiError),
}
}

211. Composer Clear

Only after:

Committed

212. Draft Deletion

Committed send deletes/advances corresponding draft atomically.


213. Reply Clear

Reply target cleared after successful commit.


214. Attachment Ownership Transfer

On send:

draft attachment
→ message/outbox attachment

atomically.


215. Voice Clip Ownership Transfer

Same.


216. Edit Commit

Separate:

#![allow(unused)]
fn main() {
pub struct EditMessageCommand {
    pub message: MessageId,
    pub new_text: String,
}
}

Attachments on edits may be disallowed initially for simplicity.


217. Recommended v1 Edit Scope

Allow:

text edit only

Do not allow changing attachments initially.


218. Reply + Attachments

Allowed.


219. Attachment-Only Message

Allowed.


220. Empty Caption

Valid if attachment exists.


221. Voice-Only Message

Allowed.


222. Voice + Text

Possible:

voice attachment + caption

if message model supports.

Recommendation:

support later

unless already easy.


223. Sending Multiple Attachments

One message with ordered list is recommended.

Avoid creating N separate messages automatically unless user chooses.


224. Album Layout

Timeline can render image groups as album.

Part 05 renderer consumes attachment grouping.


225. File Attachment Group

Documents can render stacked.


226. Camera Capture Failure

Return to composer intact.


227. Permission Denied

Composer shows contextual explanation.


228. Storage Full

Cannot stage attachment/draft.

Show:

Storage is full
Manage Storage

229. Read-Only External URI

If file can be read once but not persist:

copy into staging

before draft can survive process death.


230. Temporary Camera File

Rust/file subsystem takes ownership after capture.


231. Draft Backup

Part 33 may include drafts optionally.

Recommendation:

user drafts included in full device backup

but not search index.


232. Draft Export

Not necessary.


233. Draft Encryption

Use local app encryption/security policy.


234. Draft Telemetry

Never send draft text to analytics.


235. Composer Logging

Do not log:

draft text
attachment filenames if sensitive
voice content

236. Crash Reports

Redact text field state.


237. Performance

Typing latency must remain local and immediate.

Do not block UI on:

disk
network
hashing
thumbnail generation

238. Attachment Preparation Worker

Background Rust worker.


239. Voice Recording Priority

Realtime audio capture higher priority than:

backup
semantic indexing

240. Draft Autosave Priority

Low but prompt.


241. Composer Memory

Avoid holding full attachments in memory.


242. Thumbnail Memory

Bound/cache.


243. Very Large Paste

If user pastes huge text:

validate length
offer file conversion only if product supports

244. Long Text

Display character count near limit.


245. Mention Count Limit

Bound mentions if protocol needs.


246. Attachment Abuse

Unknown conversation/request restrictions may disallow attachment sends until accepted.


247. Message Request Composer

If user replying to unknown request:

Accept / Reply

policy must be explicit.


248. Blocked Conversation

Composer disabled.


249. Archived Conversation

Archive should usually remain writable when opened.

Sending may automatically unarchive.


250. Unarchive on Send

Recommended:

sending in archived conversation → unarchive

251. Group Role Change

If permission changes while typing:

composer disables
draft preserved

252. Security State Change

If contact identity changes:

send may pause
draft preserved
Review Security

253. Connection Loss During Composition

No visual disruption beyond subtle offline status.


254. Connection Return

No composer reset.


255. Duplicate Send Race

Use CommandId.


256. Slow JNI Call

UI send button may briefly show commit state.

But avoid spinner if commit fast.


257. Android Back Behavior

Back while attachment sheet open:

close sheet

Back while voice preview:

stay conversation, optionally discard confirmation if unsaved

Back while composer has draft:

navigate back, draft autosaved

258. Desktop Esc Behavior

Priority:

close popup
cancel selection
cancel reply/edit
then normal navigation

Do not clear ordinary draft on Esc.


259. Attachment Preview Fullscreen

Tap attachment draft preview:

open local preview

before send.


260. Remove from Preview

Available.


261. Image Crop/Edit

Future.

Not required in v1.


262. Video Trim

Future.

Not required.


263. Voice Noise Processing

Part 26 DSP may apply recording profile if desired.


264. Voice Recording Quality

Product setting:

voice optimized

not user codec complexity.


265. Audio Focus

Voice recording must integrate with:

media playback
calls
notifications

through Android/platform audio layer.


266. Recording While Music Playing

Policy:

request audio focus
duck/pause other app according to Android behavior

267. Desktop Recording Device

Use current/default mic.

Advanced input selection in Calls & Media settings.


268. Recorder Failure

Examples:

mic unavailable
permission denied
device disconnected

Show inline.


269. Attachment MIME Preview Mapping

UI gets semantic:

Image
Video
PDF
Archive
Document
Audio
Unknown

not raw MIME string only.


270. International Filenames

Support Unicode safely.


271. Truncation

Truncate display name visually.

Full filename in tooltip/details.


272. Attachment Sorting

Preserve user-selected order.


273. Draft Timestamp

Used for inbox preview and cleanup.


274. Draft Restore Banner

Usually unnecessary.

Simply restore content.

Optional subtle:

Draft restored

after crash if helpful.


275. Draft Corruption

If draft cannot load:

show recovery message

without affecting conversation history.


276. Stale Reply Target

If reply target deleted:

reply context says Original unavailable

user may still send reply or cancel.


277. Stale Edit Target

If edit target deleted:

exit edit mode
preserve typed replacement as draft if possible

278. Attachment Revalidation on Restore

Check:

staged file exists
permissions still valid
size/type

279. Sendability Projection

Rust can expose:

#![allow(unused)]
fn main() {
pub enum ComposerSendability {
    Ready,
    Empty,
    Preparing,
    ReadOnly,
    Invalid,
    SecurityBlocked,
}
}

280. UI Presentation

Map to:

enabled send button
disabled reason
banner
inline warning

281. Android ViewModel

Owns:

immediate text
focus
picker effects
permission effects
temporary reorder UI

Rust owns durable draft and attachment preparation.


282. Dioxus Presenter

Owns:

input signal
focus
drag hover
popup state

Rust owns durable draft.


283. Shared Presentation API

#![allow(unused)]
fn main() {
pub trait ComposerPresentation {
    async fn snapshot(
        &self,
        conversation: ConversationId,
    ) -> Result<ComposerSnapshot, UiError>;

    async fn save_draft(
        &self,
        update: DraftUpdate,
    ) -> Result<DraftRevision, UiError>;

    async fn add_attachment(
        &self,
        source: AttachmentSource,
    ) -> Result<ComposerAttachment, UiError>;

    async fn remove_attachment(
        &self,
        id: ComposerAttachmentId,
    ) -> Result<(), UiError>;

    async fn send(
        &self,
        command: SendMessageCommand,
    ) -> Result<SendCommitResult, UiError>;
}
}

284. Voice API

#![allow(unused)]
fn main() {
pub trait VoiceComposerService {
    async fn start(&self, conversation: ConversationId) -> Result<(), UiError>;
    async fn stop(&self) -> Result<VoiceDraft, UiError>;
    async fn discard(&self) -> Result<(), UiError>;
}
}

Platform adapter handles permission/audio device integration.


285. Draft Update

#![allow(unused)]
fn main() {
pub struct DraftUpdate {
    pub draft: DraftId,
    pub expected_revision: DraftRevision,
    pub text: String,
    pub mode: ComposerMode,
}
}

286. Revision Protection

Prevents stale delayed autosave overwriting newer draft.


287. Android Debounce Race

Example:

type A
type B
send
old autosave A arrives later

Revision/flush prevents old draft resurrection.


288. Desktop Multi-Window Race

Same protection.


289. Composer Test Matrix

Required states:

empty
text
multiline
reply
edit
attachment
multiple attachments
preparing
invalid attachment
voice recording
voice preview
offline
read-only
security blocked
restored draft

290. Android Tests

Verify:

IME open/close
permission request
file picker
share intent
process death
rotation
large font
TalkBack
RTL

291. Desktop Tests

Verify:

drag/drop
paste image
keyboard send
Esc behavior
multi-window draft consistency
file dialog

292. Draft Tests

Scenario:

type text
kill app
restart

Expected:

draft restored

293. Send Commit Test

If Rust commit fails:

draft remains

294. Offline Send Test

If commit succeeds offline:

draft clears
timeline shows Queued

295. Attachment Restore Test

Draft references missing file.

UI shows reselect/remove.


296. Voice Interrupt Test

Incoming call interrupts voice recording safely.


297. Permission Denied Test

Mic/camera denied without losing draft.


298. Draft Revision Test

Stale autosave cannot overwrite newer draft.


299. Large File Test

No giant memory copy.

Preparation stays responsive.


300. Multiple Attachment Test

Order preserved.

Remove one without affecting others.


301. Reply/Delete Race

Target deleted while composing reply.

No crash.


302. Edit/Delete Race

Target deleted while editing.

Edit exits safely.


303. Security Change Test

Identity warning appears.

Draft preserved while sending blocked.


304. Accessibility Test

Entire composer usable with:

keyboard
TalkBack/screen reader
large text
no gestures

305. Performance Targets

Typing response:

immediate local

Draft autosave:

background

Attachment ingestion:

non-blocking

Voice controls:

low-latency

306. Initial Production Recommendation

For v1, support:

plain/multiline text
reply
text edit
durable drafts
file/photo/video/document attachments
drag/drop desktop
system picker Android
paste image/file where supported
voice notes
offline send
attachment preview/remove

Defer:

rich editor
GIF/sticker marketplace
image editor
video trim
scheduled messages
complex slash commands
per-attachment captions
multi-device draft sync

307. Definition of Done

UI/UX Part 06 is complete when:

  • composer temporary interaction state is separated from Rust durable draft/message truth
  • drafts survive app/process restart according to autosave policy
  • stale autosave cannot overwrite newer draft
  • reply and edit modes are explicit and recover safely
  • composer clears only after Rust durably commits a pending message
  • offline send produces queued timeline state rather than losing input
  • Android uses system picker/URI/FD flows rather than giant byte arrays
  • desktop supports file dialog, drag/drop, and paste
  • attachments are represented by Rust-owned draft attachment IDs
  • attachment preparation/validation is asynchronous and non-blocking
  • missing draft attachments are detected on restore
  • voice-note recording never puts raw PCM in Compose/Dioxus state
  • voice recording has accessible non-gesture controls
  • permissions are contextual and platform-native
  • process death, call interruption, permission denial, security block, and offline transitions preserve draft safely
  • attachment count/size/type limits are authoritative in Rust
  • accessibility, RTL, large font, reduced motion, keyboard, and screen-reader behavior are defined
  • send, attachment, draft, and voice operations have explicit Rust presentation APIs
  • v1 scope remains focused and avoids premature rich-editor complexity

308. Final Architecture

                    PLATFORM COMPOSER
             ┌──────────────┼──────────────┐
             │              │              │
           Text        Attachments       Voice
             │              │              │
             └──────────────┼──────────────┘
                            │
                    Presenter/ViewModel
                            │
                    Rust Composer Service
              ┌─────────────┼─────────────┐
              │             │             │
           Drafts       Attachment     Voice Draft
              │          Staging           │
              └─────────────┼─────────────┘
                            │
                      Send Commit
                            │
                         Outbox
                            │
                    Message Timeline

Platform-specific ingress:

Desktop:
    file dialog
    drag/drop
    clipboard
    microphone

Android:
    system picker
    share intent
    camera
    microphone
    IME

All converge into the same:

DraftId
ComposerAttachmentId
SendMessageCommand
MessageId

309. Final Principle

The composer should feel immediate and forgiving while preserving strong message-delivery correctness.

The right model is:

instant local typing
+
durable debounced drafts
+
platform-native file/media input
+
Rust-owned validation/staging
+
atomic send commit
+
offline outbox

not:

UI directly assembles and transmits message payloads

This makes composition fast and native on both Dioxus desktop and Android Compose while keeping the shared Rust communication engine authoritative.