Skip to content

feat(desktop): pop the composer out into a draggable floating window - #49488

Merged
OutThisLife merged 3 commits into
mainfrom
bb/composer-popout
Jun 20, 2026
Merged

feat(desktop): pop the composer out into a draggable floating window#49488
OutThisLife merged 3 commits into
mainfrom
bb/composer-popout

Conversation

@OutThisLife

@OutThisLife OutThisLife commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lets you pop the desktop composer out of its docked slot into a compact, draggable floating window — entirely gesture-driven, no chrome/buttons.

  • Peel out: drag the docked composer upward off the dock → it detaches and follows the cursor in one motion.
  • Redock: drag it toward the bottom-center dock zone and release. A bottom radial glow ramps up with proximity (continuous, peaks at the zone) to signal the drop target. Dock trigger is tight/center-only so the bottom corners stay free to park in.
  • Double-click the grab area toggles dock/float.
  • Floating composer: compact one-sentence width, grows upward as text wraps, anchored to its bottom-right corner. Move it via the 5px transparent grab platform around it (cursor-grab, diagonal ///// hatch on hover). The docked composer gets the same transparent grab margin (width compensated so the surface doesn't shift).
  • Persistence: popped state + last position survive reloads; undocking restores the last spot. State is shared, but secondary windows (Ctrl+Shift+N, subagent windows) always start docked and can't pop out.
  • Floating-mode model pill collapses to a chevron-only square.
  • Composer focus now shifts the border ~15% toward foreground (lighten/darken by mode) instead of changing the fill; input is cursor-text.

Implementation notes

  • New store/composer-popout.ts (persisted atoms + clamped position) and composer/hooks/use-popout-drag.ts (the gesture state machine).
  • Drag is rAF-coalesced and only persists to localStorage on release (never per move).
  • Floating composer zeroes its measured thread-clearance vars so the thread reclaims the space.

Note: the codicon/worktree Vite server.fs.allow fix is intentionally not included here — it's already covered by #40735.

Test plan

  • Drag docked composer up → peels into a float; drag to bottom-center → redocks (glow intensifies near zone).
  • Double-click grab area toggles dock/float; undock restores last position.
  • Park in bottom-left/right corners without it snapping back.
  • Reload → popped state + position restored.
  • Ctrl+Shift+N / subagent window → composer starts docked, can't pop out.
  • Typing grows the floating box upward; + aligns to first line; model pill is chevron-only.
  • Light + dark mode; macOS + Windows pointer drag.

Gesture-driven: drag the docked composer up to peel it out, drag it back to
the bottom-center dock zone (radial glow ramps with proximity) to redock, and
double-click the grab area to toggle. Floating composer is compact, grows
upward as it wraps, and can be moved by its 5px transparent grab platform
(diagonal hatch on hover). Position + popped state persist; secondary windows
always start docked. rAF-coalesced drag, persisted only on release.
@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: bb/composer-popout vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11225 on HEAD, 11225 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5885 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/feature New feature or request comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have labels Jun 20, 2026
…ip editing

- Peel-off undock drops the floating composer under the cursor (centered
  horizontally, preserving the vertical grab offset) instead of snapping to
  the docked corner.
- Unify the / · @ · ? completion drawer and the attach (+) menu onto one
  shared glassy panel primitive (composerPanelCard): smallest theme font,
  hairline border, nous shadow; floats off the composer, inset from the left.
- Directive chips: Backspace removes the chip + its auto-inserted trailing
  space atomically (no orphaned space), and a phantom trailing block left by
  contenteditable no longer falsely expands the composer to two rows.
- Model picker: scroll area capped at max(150px, 30dvh); footer rows aligned
  (matching icons, dropped a redundant margin).
- Composer focus shifts the border ~15% toward foreground (no fill change);
  input is cursor-text; trimmed control icon/button sizes.
@OutThisLife
OutThisLife enabled auto-merge June 20, 2026 07:13
@OutThisLife
OutThisLife merged commit 1585272 into main Jun 20, 2026
34 checks passed
@OutThisLife
OutThisLife deleted the bb/composer-popout branch June 20, 2026 07:16
teknium1 added a commit that referenced this pull request Jun 24, 2026
…ound (#51728)

The desktop composer threw an uncaught "Composer is not available" at
startup and the input went unresponsive (#49903). assistant-ui's composer
mutators (setText/send/…) throw when the thread's composer core isn't bound
yet; the read path is null-safe but the writes are not. ChatBar pushes draft
text via aui.composer().setText() from mount-time effects (draft restore,
clearDraft, external inserts), and the v0.17.0 popout refactor (#49488)
widened the unbound window by moving the composer out of the contain wrapper
into a sibling of the thread — so the throw surfaced as an uncaught error
that wedged the input.

Wrap every composer mutation in a setComposerText helper that swallows the
unbound-core throw. The contentEditable DOM + draftRef already hold the text
and the draft-editor sync re-applies it once the core attaches, so the draft
is never lost — only the premature state push is skipped.
pai-scaffolde pushed a commit to pai-scaffolde/hermes-agent that referenced this pull request Jun 28, 2026
…ound (NousResearch#51728)

The desktop composer threw an uncaught "Composer is not available" at
startup and the input went unresponsive (NousResearch#49903). assistant-ui's composer
mutators (setText/send/…) throw when the thread's composer core isn't bound
yet; the read path is null-safe but the writes are not. ChatBar pushes draft
text via aui.composer().setText() from mount-time effects (draft restore,
clearDraft, external inserts), and the v0.17.0 popout refactor (NousResearch#49488)
widened the unbound window by moving the composer out of the contain wrapper
into a sibling of the thread — so the throw surfaced as an uncaught error
that wedged the input.

Wrap every composer mutation in a setComposerText helper that swallows the
unbound-core throw. The contentEditable DOM + draftRef already hold the text
and the draft-editor sync re-applies it once the core attaches, so the draft
is never lost — only the premature state push is skipped.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…ousResearch#49488)

* feat(desktop): pop the composer out into a draggable floating window

Gesture-driven: drag the docked composer up to peel it out, drag it back to
the bottom-center dock zone (radial glow ramps with proximity) to redock, and
double-click the grab area to toggle. Floating composer is compact, grows
upward as it wraps, and can be moved by its 5px transparent grab platform
(diagonal hatch on hover). Position + popped state persist; secondary windows
always start docked. rAF-coalesced drag, persisted only on release.

* fix(desktop): keep floating composer radius consistent with docked

* fix(desktop): composer popout polish — peel-off placement, panels, chip editing

- Peel-off undock drops the floating composer under the cursor (centered
  horizontally, preserving the vertical grab offset) instead of snapping to
  the docked corner.
- Unify the / · @ · ? completion drawer and the attach (+) menu onto one
  shared glassy panel primitive (composerPanelCard): smallest theme font,
  hairline border, nous shadow; floats off the composer, inset from the left.
- Directive chips: Backspace removes the chip + its auto-inserted trailing
  space atomically (no orphaned space), and a phantom trailing block left by
  contenteditable no longer falsely expands the composer to two rows.
- Model picker: scroll area capped at max(150px, 30dvh); footer rows aligned
  (matching icons, dropped a redundant margin).
- Composer focus shifts the border ~15% toward foreground (no fill change);
  input is cursor-text; trimmed control icon/button sizes.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…ound (NousResearch#51728)

The desktop composer threw an uncaught "Composer is not available" at
startup and the input went unresponsive (NousResearch#49903). assistant-ui's composer
mutators (setText/send/…) throw when the thread's composer core isn't bound
yet; the read path is null-safe but the writes are not. ChatBar pushes draft
text via aui.composer().setText() from mount-time effects (draft restore,
clearDraft, external inserts), and the v0.17.0 popout refactor (NousResearch#49488)
widened the unbound window by moving the composer out of the contain wrapper
into a sibling of the thread — so the throw surfaced as an uncaught error
that wedged the input.

Wrap every composer mutation in a setComposerText helper that swallows the
unbound-core throw. The contentEditable DOM + draftRef already hold the text
and the draft-editor sync re-applies it once the core attaches, so the draft
is never lost — only the premature state push is skipped.
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…ousResearch#49488)

* feat(desktop): pop the composer out into a draggable floating window

Gesture-driven: drag the docked composer up to peel it out, drag it back to
the bottom-center dock zone (radial glow ramps with proximity) to redock, and
double-click the grab area to toggle. Floating composer is compact, grows
upward as it wraps, and can be moved by its 5px transparent grab platform
(diagonal hatch on hover). Position + popped state persist; secondary windows
always start docked. rAF-coalesced drag, persisted only on release.

* fix(desktop): keep floating composer radius consistent with docked

* fix(desktop): composer popout polish — peel-off placement, panels, chip editing

- Peel-off undock drops the floating composer under the cursor (centered
  horizontally, preserving the vertical grab offset) instead of snapping to
  the docked corner.
- Unify the / · @ · ? completion drawer and the attach (+) menu onto one
  shared glassy panel primitive (composerPanelCard): smallest theme font,
  hairline border, nous shadow; floats off the composer, inset from the left.
- Directive chips: Backspace removes the chip + its auto-inserted trailing
  space atomically (no orphaned space), and a phantom trailing block left by
  contenteditable no longer falsely expands the composer to two rows.
- Model picker: scroll area capped at max(150px, 30dvh); footer rows aligned
  (matching icons, dropped a redundant margin).
- Composer focus shifts the border ~15% toward foreground (no fill change);
  input is cursor-text; trimmed control icon/button sizes.
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…ound (NousResearch#51728)

The desktop composer threw an uncaught "Composer is not available" at
startup and the input went unresponsive (NousResearch#49903). assistant-ui's composer
mutators (setText/send/…) throw when the thread's composer core isn't bound
yet; the read path is null-safe but the writes are not. ChatBar pushes draft
text via aui.composer().setText() from mount-time effects (draft restore,
clearDraft, external inserts), and the v0.17.0 popout refactor (NousResearch#49488)
widened the unbound window by moving the composer out of the contain wrapper
into a sibling of the thread — so the throw surfaced as an uncaught error
that wedged the input.

Wrap every composer mutation in a setComposerText helper that swallows the
unbound-core throw. The contentEditable DOM + draftRef already hold the text
and the draft-editor sync re-applies it once the core attaches, so the draft
is never lost — only the premature state push is skipped.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…ousResearch#49488)

* feat(desktop): pop the composer out into a draggable floating window

Gesture-driven: drag the docked composer up to peel it out, drag it back to
the bottom-center dock zone (radial glow ramps with proximity) to redock, and
double-click the grab area to toggle. Floating composer is compact, grows
upward as it wraps, and can be moved by its 5px transparent grab platform
(diagonal hatch on hover). Position + popped state persist; secondary windows
always start docked. rAF-coalesced drag, persisted only on release.

* fix(desktop): keep floating composer radius consistent with docked

* fix(desktop): composer popout polish — peel-off placement, panels, chip editing

- Peel-off undock drops the floating composer under the cursor (centered
  horizontally, preserving the vertical grab offset) instead of snapping to
  the docked corner.
- Unify the / · @ · ? completion drawer and the attach (+) menu onto one
  shared glassy panel primitive (composerPanelCard): smallest theme font,
  hairline border, nous shadow; floats off the composer, inset from the left.
- Directive chips: Backspace removes the chip + its auto-inserted trailing
  space atomically (no orphaned space), and a phantom trailing block left by
  contenteditable no longer falsely expands the composer to two rows.
- Model picker: scroll area capped at max(150px, 30dvh); footer rows aligned
  (matching icons, dropped a redundant margin).
- Composer focus shifts the border ~15% toward foreground (no fill change);
  input is cursor-text; trimmed control icon/button sizes.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…ound (NousResearch#51728)

The desktop composer threw an uncaught "Composer is not available" at
startup and the input went unresponsive (NousResearch#49903). assistant-ui's composer
mutators (setText/send/…) throw when the thread's composer core isn't bound
yet; the read path is null-safe but the writes are not. ChatBar pushes draft
text via aui.composer().setText() from mount-time effects (draft restore,
clearDraft, external inserts), and the v0.17.0 popout refactor (NousResearch#49488)
widened the unbound window by moving the composer out of the contain wrapper
into a sibling of the thread — so the throw surfaced as an uncaught error
that wedged the input.

Wrap every composer mutation in a setComposerText helper that swallows the
unbound-core throw. The contentEditable DOM + draftRef already hold the text
and the draft-editor sync re-applies it once the core attaches, so the draft
is never lost — only the premature state push is skipped.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…ousResearch#49488)

* feat(desktop): pop the composer out into a draggable floating window

Gesture-driven: drag the docked composer up to peel it out, drag it back to
the bottom-center dock zone (radial glow ramps with proximity) to redock, and
double-click the grab area to toggle. Floating composer is compact, grows
upward as it wraps, and can be moved by its 5px transparent grab platform
(diagonal hatch on hover). Position + popped state persist; secondary windows
always start docked. rAF-coalesced drag, persisted only on release.

* fix(desktop): keep floating composer radius consistent with docked

* fix(desktop): composer popout polish — peel-off placement, panels, chip editing

- Peel-off undock drops the floating composer under the cursor (centered
  horizontally, preserving the vertical grab offset) instead of snapping to
  the docked corner.
- Unify the / · @ · ? completion drawer and the attach (+) menu onto one
  shared glassy panel primitive (composerPanelCard): smallest theme font,
  hairline border, nous shadow; floats off the composer, inset from the left.
- Directive chips: Backspace removes the chip + its auto-inserted trailing
  space atomically (no orphaned space), and a phantom trailing block left by
  contenteditable no longer falsely expands the composer to two rows.
- Model picker: scroll area capped at max(150px, 30dvh); footer rows aligned
  (matching icons, dropped a redundant margin).
- Composer focus shifts the border ~15% toward foreground (no fill change);
  input is cursor-text; trimmed control icon/button sizes.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…ound (NousResearch#51728)

The desktop composer threw an uncaught "Composer is not available" at
startup and the input went unresponsive (NousResearch#49903). assistant-ui's composer
mutators (setText/send/…) throw when the thread's composer core isn't bound
yet; the read path is null-safe but the writes are not. ChatBar pushes draft
text via aui.composer().setText() from mount-time effects (draft restore,
clearDraft, external inserts), and the v0.17.0 popout refactor (NousResearch#49488)
widened the unbound window by moving the composer out of the contain wrapper
into a sibling of the thread — so the throw surfaced as an uncaught error
that wedged the input.

Wrap every composer mutation in a setComposerText helper that swallows the
unbound-core throw. The contentEditable DOM + draftRef already hold the text
and the draft-editor sync re-applies it once the core attaches, so the draft
is never lost — only the premature state push is skipped.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…ousResearch#49488)

* feat(desktop): pop the composer out into a draggable floating window

Gesture-driven: drag the docked composer up to peel it out, drag it back to
the bottom-center dock zone (radial glow ramps with proximity) to redock, and
double-click the grab area to toggle. Floating composer is compact, grows
upward as it wraps, and can be moved by its 5px transparent grab platform
(diagonal hatch on hover). Position + popped state persist; secondary windows
always start docked. rAF-coalesced drag, persisted only on release.

* fix(desktop): keep floating composer radius consistent with docked

* fix(desktop): composer popout polish — peel-off placement, panels, chip editing

- Peel-off undock drops the floating composer under the cursor (centered
  horizontally, preserving the vertical grab offset) instead of snapping to
  the docked corner.
- Unify the / · @ · ? completion drawer and the attach (+) menu onto one
  shared glassy panel primitive (composerPanelCard): smallest theme font,
  hairline border, nous shadow; floats off the composer, inset from the left.
- Directive chips: Backspace removes the chip + its auto-inserted trailing
  space atomically (no orphaned space), and a phantom trailing block left by
  contenteditable no longer falsely expands the composer to two rows.
- Model picker: scroll area capped at max(150px, 30dvh); footer rows aligned
  (matching icons, dropped a redundant margin).
- Composer focus shifts the border ~15% toward foreground (no fill change);
  input is cursor-text; trimmed control icon/button sizes.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…ound (NousResearch#51728)

The desktop composer threw an uncaught "Composer is not available" at
startup and the input went unresponsive (NousResearch#49903). assistant-ui's composer
mutators (setText/send/…) throw when the thread's composer core isn't bound
yet; the read path is null-safe but the writes are not. ChatBar pushes draft
text via aui.composer().setText() from mount-time effects (draft restore,
clearDraft, external inserts), and the v0.17.0 popout refactor (NousResearch#49488)
widened the unbound window by moving the composer out of the contain wrapper
into a sibling of the thread — so the throw surfaced as an uncaught error
that wedged the input.

Wrap every composer mutation in a setComposerText helper that swallows the
unbound-core throw. The contentEditable DOM + draftRef already hold the text
and the draft-editor sync re-applies it once the core attaches, so the draft
is never lost — only the premature state push is skipped.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants