@@ -1476,6 +1476,13 @@ impl PanelState {
14761476 // Wrapped composer lines add height in every phase; see
14771477 // `input_extra_height`.
14781478 let input_extra = self . input_extra_height ( ) ;
1479+ // A notice is now a row in the panel rather than a floating box, so
1480+ // it costs height like every other row.
1481+ let notice = if self . toast . is_some ( ) {
1482+ NOTICE_ROW_HEIGHT
1483+ } else {
1484+ 0.0
1485+ } ;
14791486 if self . has_conversation ( ) {
14801487 // `CHAT_ESTIMATE_SURPLUS`: the collapsed baseline and the footer
14811488 // estimate both over-count the chat composition slightly —
@@ -1486,6 +1493,7 @@ impl PanelState {
14861493 + attachments
14871494 + selection
14881495 + input_extra
1496+ + notice
14891497 + self . transcript_height ( )
14901498 + self . chat_error_height ( )
14911499 + self . footer_height ( )
@@ -1495,7 +1503,7 @@ impl PanelState {
14951503
14961504 match self . phase {
14971505 Phase :: Hidden | Phase :: WarmingUp { .. } | Phase :: Idle => {
1498- ( theme:: PANEL_HEIGHT_COLLAPSED + attachments + selection + input_extra)
1506+ ( theme:: PANEL_HEIGHT_COLLAPSED + attachments + selection + input_extra + notice )
14991507 . min ( self . max_panel_height ( ) )
15001508 }
15011509 // `COLLAPSED` is input-plus-chrome only. Everything `footer()`
@@ -1507,6 +1515,7 @@ impl PanelState {
15071515 + attachments
15081516 + selection
15091517 + input_extra
1518+ + notice
15101519 + self . answer_height ( )
15111520 + self . footer_height ( ) )
15121521 . min ( self . max_panel_height ( ) ) ,
@@ -1737,9 +1746,10 @@ pub fn view(state: &PanelState, appearance: theme::Appearance) -> Element<'_, Me
17371746 body = body. push ( widgets:: railed ( RailState :: Inactive , footer ( state) ) ) ;
17381747 }
17391748
1740- body = body. push ( widgets:: railed ( input_rail_state ( state) , input_row ( state) ) ) ;
1741-
1742- let mut stack = column ! [ body] . spacing ( space ( 2.0 ) ) ;
1749+ // Notices live *in* the panel, on the rail, directly above the composer —
1750+ // not floating over it (owner ruling, 2026-08-02: "can we just show it in
1751+ // the chat window?"). A floating box overlapped the conversation and,
1752+ // being outside the layout, could not be sized against it.
17431753 if let Some ( toast) = & state. toast {
17441754 let action = if toast. offer_diagnostics {
17451755 Action :: new (
@@ -1750,9 +1760,16 @@ pub fn view(state: &PanelState, appearance: theme::Appearance) -> Element<'_, Me
17501760 } else {
17511761 Action :: new ( Key :: ActionDismiss , "esc" , Message :: DismissToast )
17521762 } ;
1753- stack = stack. push ( widgets:: toast ( toast. severity , & toast. body , Some ( action) ) ) ;
1763+ body = body. push ( widgets:: railed (
1764+ RailState :: Alert ,
1765+ widgets:: toast ( toast. severity , & toast. body , Some ( action) ) ,
1766+ ) ) ;
17541767 }
17551768
1769+ body = body. push ( widgets:: railed ( input_rail_state ( state) , input_row ( state) ) ) ;
1770+
1771+ let stack = column ! [ body] . spacing ( space ( 2.0 ) ) ;
1772+
17561773 // The quick-pick and the `@` finder float over the panel as menus
17571774 // (t3-style) instead of replacing the body: the panel stays where it was,
17581775 // the menu arrives and leaves, and mid-conversation the window does not
@@ -3270,6 +3287,10 @@ const PICKER_PANEL_HEIGHT: f32 = 500.0;
32703287const PICKER_MENU_WIDTH : f32 = 560.0 ;
32713288/// The running activity card: header row plus a three-row scrolling interior.
32723289/// Fixed so a streaming run never resizes the window (§16's reserve rule).
3290+ /// One notice row above the composer: bar-and-text plus the toast's own
3291+ /// padding and the column's spacing.
3292+ const NOTICE_ROW_HEIGHT : f32 = 52.0 ;
3293+
32733294const TASK_CARD_RUNNING_HEIGHT : f32 = 148.0 ;
32743295/// The settled card: one summary line in a quiet container.
32753296const TASK_CARD_DONE_HEIGHT : f32 = 34.0 ;
0 commit comments