Skip to content

feat: floating composer - #3674

Open
aerodeval wants to merge 72 commits into
frappe:developfrom
aerodeval:feat/floating-composer
Open

aerodeval wants to merge 72 commits into
frappe:developfrom
aerodeval:feat/floating-composer

Conversation

@aerodeval

@aerodeval aerodeval commented Aug 5, 2026

Copy link
Copy Markdown
Member

New Communication Composer experience in frappe helpdesk

Why

the earlier composer provided very less space to work with and would often ruin the experience while typing as most of the content which agents wanted to refer to was hidden by the composer. This PR aims to solve this problem by adding a floating composer which provides flexibility to the user as they can drag around and move the composer once it is floating.

Depends on

Built using the component in frappe/ui folder frappe/frappe#40280

PR What this branch takes from it
frappe/frappe#40280 EmailComposer / CommentComposer in @framework/ui
frappe/frappe#42119 the composer API this branch is written against: show-* flags, split v-model:to/cc/bcc, flat submit payload, extensions
frappe/frappe#42696 submitting (spinner on Send), #footer slot (the staged saved reply actions render there), toolbar start, body bottom fade, attach button spinner
frappe/frappe#42706 spinner after the Send label instead of before it; recipient chips keep the name and avatar the host seeds, which is how the To chip shows the ticket contact
frappe/frappe#42966 recipient chips drag between To, CC and BCC; the From select shrinks instead of stretching the row; no floating toolbar on text selection

What changes

Drafts, mentions, saved replies, attachments, signatures and the existing localStorage draft keys all behave as before.

Beyond the composer itself:

File Change
components/CommentTextEditor.vue, EmailEditor.vue, CommunicationArea.vue deleted, replaced by components/communication-area/
communication-area/recipients.ts parses Name <email> addresses and fills chip labels from Contacts, bounded to the addresses being labelled
communication-area/useDockedResize.ts docked-height drag, stored per browser and clamped to the viewport on read
ticket-agent/TicketActivityPanel.vue composer overlays the thread; the timeline reserves its height instead of reflowing, and holds the reserve while it is dragged
ticket-agent/timeline/TicketTimeline.vue reply hands the composer the sender name the feed already resolved, so the To chip needs no lookup
editor/config.ts tiptap extensions pulled out as helpdeskExtensions so every editor and the composer share one list
stores/agent.ts, composables/useSavedReplyActionOptions.ts loadOnce() so readers share one agent fetch instead of racing
SavedReplyActions/SavedReplyActions.vue retry re-sends the batch the failed request carried, not whatever is staged by then; chip radius matched to the attachments
types.ts ReplyPayload, what the timeline hands the composer
modals/ShortcutsModal.vue E listed
pages/ticket/MobileTicketAgent.vue drops the props the old editor took

Before / after

Before After
Reply area Always-expanded editor pinned under the timeline, fixed height Minimized pill; composer opens over the timeline, height is drag-set and remembered
Channels Separate email box and comment box, toggled from outside One window, Email / Comment tabs in its header
Window Fixed in the page Docked or floating — draggable, resizable from every edge and corner
Empty pill label "Add a comment" "Send a reply" (clicking it opens the email reply)

Screenshots

Before After
Reply bar
Minimized pill
Email editor
Docked composer
Floating window
Someone else typing

Demo:

Screen.Recording.2026-09-18.at.2.23.09.AM.mov

The header already started a resize anywhere that wasn't a button, but
the tab switcher and window controls cover both top corners, so grabbing
one did nothing.

The empty pill also read "Add a comment" while clicking it opens the
email reply.
@aerodeval aerodeval changed the title feat: floating composer for ticket replies feat: floating composer Aug 5, 2026
…mposer

# Conflicts:
#	desk/src/components/CommunicationArea.vue
#	desk/src/components/EmailEditor.vue
@codecov-commenter

codecov-commenter commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.10%. Comparing base (c3ae467) to head (6c2e233).
⚠️ Report is 169 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3674      +/-   ##
===========================================
+ Coverage    73.76%   75.10%   +1.33%     
===========================================
  Files          148      154       +6     
  Lines        10841    11439     +598     
===========================================
+ Hits          7997     8591     +594     
- Misses        2844     2848       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The framework composer only took a label, so the send state was the word
"Sending...". frappe/frappe#42696 adds a `submitting` prop that lights the
button's own spinner; pass it and shorten the labels to "Sending" and
"Commenting".
The panes padded both sides on top of the composer's own padding, so Send
and the CC/BCC toggles ended 10px short of the Close button above them. Keep
only the start inset, which puts the text under the tab labels, and let the
composer's padding meet the chrome on the end side.
The composer fades its own bottom edge since frappe/frappe#42696.
The panel sat under the Send button because the composer had nowhere to put
it. frappe/frappe#42696 adds a `#footer` slot between the body and the
utilities row; render it there, where develop's editor had it.
@aerodeval
aerodeval marked this pull request as ready for review September 10, 2026 07:11
@mergify

mergify Bot commented Sep 10, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge.

Reviews (31) · Last reviewed commit: "fix(saved-replies): trim the header padd..."

Comment thread desk/src/components/CommunicationArea.vue Outdated
The docked resize cursor came from a positional selector into the library's
title bar. The header slot div fills that bar, so a conditional class on it
does the same without naming library internals.
Comment thread desk/src/components/ticket-agent/TicketActivityPanel.vue
…mposer

# Conflicts:
#	desk/src/components/CommentTextEditor.vue
#	desk/src/components/EmailEditor.vue
#	desk/src/components/SavedReplyActions/SavedReplyActions.vue
#	desk/src/components/index.ts
#	desk/src/components/ticket-agent/TicketActivityPanel.vue
frappe-ui has no ./frappe subpath and no rounded-lg alias any more.
A media node sent mid-upload has no src and the file is unrecoverable. Also
forwards the editor's signal and progress callback to the uploader.
A failed request was cached as "no contact" for the session, and the page
size counted addresses when one address can own several contacts.
The staged list can be cleared while the request is out, so the retry was
re-sending nothing.
The height is stored for the whole browser, so it outlives the window it was
dragged in.
Hold the timeline reserve during a drag, drop the duplicate tabpanel
scroller, keep the composer open when replying from the thread, and
outline the channel switcher.
Comment thread desk/src/components/communication-area/recipients.ts
The drag sized the whole column, so every header row opened at the
editor's expense. Size the message area instead and let the window
follow it.
The drag stopped at the body's resting floor and then snapped the window
shut with nothing moving on screen, ended the gesture at the collapse so
reopening needed a second click, and held the timeline's reserved height
back until release, where it landed in one jump.

The body now shrinks to nothing before collapsing, the gesture stays live
through the collapse so pulling back up reopens it, and the timeline
follows the composer through the drag. Resizing is handle-only and the
resize cursor holds until the pointer comes up.
Hold the thread's reserved space until the drag ends so it stops moving
under the pointer, capture the pointer on the composer root so collapsing
mid-drag cannot drop the gesture, and carry the resize cursor there too,
since a captured pointer takes its cursor from whatever holds the capture.

Frost the window only when it is docked.
Badge rounds itself fully, so the action chips read as pills next to the
recipient chips above them, which use rounded-4. Pin both the chips and
the overflow badge to that radius, and drop the block's right margin.
The composer draws its own attachment chips, so AttachmentList stopped
being used and took the preview dialog with it. Fill the composer's
attachments slot with it in both channels.
Trim the composer column to 9px and inset the panel by half a pixel so
its border sits on the column edge, then match the chip row and pull the
header's controls back to where the composer's own icons sit.
Cap the chip and carry the name in a title, and hold the icons at their
size: Button renders slot content bare, so a prefix or suffix icon is a
plain flex child and collapses once the chip has a width to fit into.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants