From d5a4ed3d77361a539e8a63facc62622c8862f3e3 Mon Sep 17 00:00:00 2001
From: william garrity <wgarrity@mieweb.com>
Date: Tue, 31 Mar 2026 21:23:33 -0400
Subject: [PATCH 1/4] feat(condensed): add condensed view support for Messaging
 components

- Add 31 data-slot attributes across 7 files:
  MessageThread (5), ConversationHeader (7), MessageList (7),
  MessageBubble (8), MessageComposer (6), AttachmentPicker (1)
- Add ~315 lines of condensed CSS covering:
  conversation header, conversation list items, message list,
  date separators, typing indicator, empty state, load more,
  message bubbles, composer with aligned input/buttons/char count
- Fix composer vertical alignment: flex centering, textarea
  height override, character counter repositioning
---
 src/components/Messaging/AttachmentPicker.tsx |   1 +
 .../Messaging/ConversationHeader.tsx          |  20 +-
 src/components/Messaging/MessageBubble.tsx    |  13 +-
 src/components/Messaging/MessageComposer.tsx  |  11 +-
 src/components/Messaging/MessageList.tsx      |  15 +-
 src/components/Messaging/MessageThread.tsx    |   5 +
 src/styles/condensed-view.css                 | 315 ++++++++++++++++++
 7 files changed, 371 insertions(+), 9 deletions(-)

diff --git a/src/components/Messaging/AttachmentPicker.tsx b/src/components/Messaging/AttachmentPicker.tsx
index 414788d1..9e297932 100644
--- a/src/components/Messaging/AttachmentPicker.tsx
+++ b/src/components/Messaging/AttachmentPicker.tsx
@@ -364,6 +364,7 @@ const AttachmentPicker = React.forwardRef<
           type="button"
           onClick={handleClick}
           disabled={disabled}
+          data-slot="attachment-picker-button"
           className={cn(
             'inline-flex items-center justify-center',
             'rounded-full p-2',
diff --git a/src/components/Messaging/ConversationHeader.tsx b/src/components/Messaging/ConversationHeader.tsx
index aabdb8de..4cc214e2 100644
--- a/src/components/Messaging/ConversationHeader.tsx
+++ b/src/components/Messaging/ConversationHeader.tsx
@@ -165,6 +165,7 @@ const ConversationHeader = React.forwardRef<
     return (
       <header
         ref={ref}
+        data-slot="conversation-header"
         className={cn(headerVariants({ size }), className)}
         {...props}
       >
@@ -201,7 +202,10 @@ const ConversationHeader = React.forwardRef<
           ))}
 
         {/* Avatar */}
-        <div className="relative shrink-0">
+        <div
+          data-slot="conversation-header-avatar"
+          className="relative shrink-0"
+        >
           <div
             className={cn(
               'flex h-10 w-10 items-center justify-center rounded-full',
@@ -232,7 +236,7 @@ const ConversationHeader = React.forwardRef<
         </div>
 
         {/* Title and subtitle */}
-        <div className="min-w-0 flex-1">
+        <div data-slot="conversation-header-info" className="min-w-0 flex-1">
           <h2 className="truncate font-semibold text-neutral-900 dark:text-neutral-100">
             {displayTitle}
           </h2>
@@ -325,6 +329,7 @@ const ConversationListItem = React.forwardRef<
       ref={ref}
       type="button"
       onClick={() => onSelect?.(conversation)}
+      data-slot="conversation-list-item"
       className={cn(
         'flex w-full items-center gap-3 px-4 py-3',
         'text-left transition-colors',
@@ -338,7 +343,10 @@ const ConversationListItem = React.forwardRef<
       {...props}
     >
       {/* Avatar */}
-      <div className="relative shrink-0">
+      <div
+        data-slot="conversation-list-item-avatar"
+        className="relative shrink-0"
+      >
         <div
           className={cn(
             'flex h-12 w-12 items-center justify-center rounded-full',
@@ -367,7 +375,10 @@ const ConversationListItem = React.forwardRef<
       </div>
 
       {/* Content */}
-      <div className="min-w-0 flex-1">
+      <div
+        data-slot="conversation-list-item-content"
+        className="min-w-0 flex-1"
+      >
         <div className="flex items-center justify-between gap-2">
           <h3
             className={cn(
@@ -398,6 +409,7 @@ const ConversationListItem = React.forwardRef<
           </p>
           {isUnread && (
             <span
+              data-slot="conversation-list-item-badge"
               className={cn(
                 'flex shrink-0 items-center justify-center',
                 'h-5 min-w-[20px] rounded-full px-1.5',
diff --git a/src/components/Messaging/MessageBubble.tsx b/src/components/Messaging/MessageBubble.tsx
index df7c6a38..83bdb3be 100644
--- a/src/components/Messaging/MessageBubble.tsx
+++ b/src/components/Messaging/MessageBubble.tsx
@@ -469,6 +469,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
       return (
         <div
           ref={ref}
+          data-slot="message-system"
           className={cn(bubbleVariants({ variant: 'system' }), className)}
           role="status"
           aria-live="polite"
@@ -482,6 +483,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
     return (
       <div
         ref={ref}
+        data-slot="message-bubble"
         className={cn(
           'group flex items-end gap-2',
           isOutgoing ? 'flex-row-reverse' : 'flex-row',
@@ -491,7 +493,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
       >
         {/* Avatar */}
         {showAvatar && !isOutgoing && (
-          <div className="mb-1 shrink-0">
+          <div data-slot="message-avatar" className="mb-1 shrink-0">
             <div
               className={cn(
                 'flex h-8 w-8 items-center justify-center rounded-full',
@@ -520,7 +522,10 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
         >
           {/* Sender name (for group chats) */}
           {showSenderName && !isOutgoing && (
-            <span className="mb-1 px-1 text-xs font-medium text-neutral-500 dark:text-neutral-400">
+            <span
+              data-slot="message-sender-name"
+              className="mb-1 px-1 text-xs font-medium text-neutral-500 dark:text-neutral-400"
+            >
               {message.sender.name}
             </span>
           )}
@@ -528,6 +533,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
           {/* Reply preview */}
           {message.replyTo && (
             <div
+              data-slot="message-reply-preview"
               className={cn(
                 'mb-1 max-w-full rounded-lg px-3 py-1.5 text-xs',
                 isOutgoing
@@ -542,6 +548,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
 
           {/* Bubble */}
           <div
+            data-slot="message-bubble-content"
             className={cn(bubbleVariants({ variant, status: message.status }))}
             role="article"
             aria-label={`Message from ${message.sender.name}`}
@@ -580,6 +587,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
 
           {/* Footer: timestamp, status, read receipts */}
           <div
+            data-slot="message-footer"
             className={cn(
               'mt-1 flex items-center gap-2 px-1',
               isOutgoing ? 'flex-row-reverse' : 'flex-row'
@@ -636,6 +644,7 @@ const MessageBubble = React.forwardRef<HTMLDivElement, MessageBubbleProps>(
           {/* Reactions */}
           {message.reactions && message.reactions.length > 0 && (
             <div
+              data-slot="message-reactions"
               className={cn(
                 '-mt-1 flex flex-wrap gap-1',
                 isOutgoing ? 'justify-end' : 'justify-start'
diff --git a/src/components/Messaging/MessageComposer.tsx b/src/components/Messaging/MessageComposer.tsx
index 7e3282d2..b62f5fbc 100644
--- a/src/components/Messaging/MessageComposer.tsx
+++ b/src/components/Messaging/MessageComposer.tsx
@@ -124,6 +124,7 @@ const SendButton = React.forwardRef<HTMLButtonElement, SendButtonProps>(
         ref={ref}
         type="submit"
         disabled={disabled || !canSend || isLoading}
+        data-slot="composer-send-button"
         className={cn(sendButtonVariants({ variant, canSend }), className)}
         aria-label={isLoading ? 'Sending message' : 'Send message'}
         {...props}
@@ -420,10 +421,15 @@ const MessageComposer = React.forwardRef<
         onError={onError}
         className={cn('w-full', className)}
       >
-        <form onSubmit={handleSubmit} className="w-full">
+        <form
+          onSubmit={handleSubmit}
+          data-slot="message-composer"
+          className="w-full"
+        >
           {/* Reply preview */}
           {replyTo && (
             <div
+              data-slot="composer-reply-preview"
               className={cn(
                 'flex items-center gap-2 px-4 py-2',
                 'bg-neutral-50 dark:bg-neutral-800/50',
@@ -469,6 +475,7 @@ const MessageComposer = React.forwardRef<
           {/* Attachment previews */}
           {attachments.length > 0 && (
             <div
+              data-slot="composer-attachments"
               className={cn(
                 'flex flex-wrap gap-2 p-3',
                 'border-t border-neutral-200 dark:border-neutral-700'
@@ -486,6 +493,7 @@ const MessageComposer = React.forwardRef<
 
           {/* Input area */}
           <div
+            data-slot="composer-input-area"
             className={cn(
               'flex items-center gap-2 p-3',
               'bg-white dark:bg-neutral-900',
@@ -516,6 +524,7 @@ const MessageComposer = React.forwardRef<
             <div className="relative flex-1">
               <textarea
                 ref={textareaRef}
+                data-slot="composer-input"
                 value={content}
                 onChange={(e) => setContent(e.target.value)}
                 onKeyDown={handleKeyDown}
diff --git a/src/components/Messaging/MessageList.tsx b/src/components/Messaging/MessageList.tsx
index 2368e7aa..0b7a8c79 100644
--- a/src/components/Messaging/MessageList.tsx
+++ b/src/components/Messaging/MessageList.tsx
@@ -110,6 +110,7 @@ function SkeletonMessage({
 }: SkeletonMessageProps) {
   return (
     <div
+      data-slot="skeleton-message"
       className={cn(
         'flex items-end gap-2',
         isOutgoing ? 'flex-row-reverse' : 'flex-row',
@@ -162,6 +163,7 @@ function TypingIndicator({ typingState, className }: TypingIndicatorProps) {
 
   return (
     <div
+      data-slot="typing-indicator"
       className={cn('flex items-center gap-2 px-4 py-2', className)}
       role="status"
       aria-live="polite"
@@ -206,6 +208,7 @@ export interface DateSeparatorProps {
 function DateSeparator({ label, className }: DateSeparatorProps) {
   return (
     <div
+      data-slot="date-separator"
       className={cn('flex items-center justify-center py-4', className)}
       role="separator"
       aria-label={label}
@@ -253,6 +256,7 @@ function EmptyState({
 }: EmptyStateProps) {
   return (
     <div
+      data-slot="message-empty-state"
       className={cn(
         'flex flex-1 flex-col items-center justify-center p-8 text-center',
         className
@@ -309,7 +313,10 @@ function LoadMoreButton({
   className,
 }: LoadMoreButtonProps) {
   return (
-    <div className={cn('flex justify-center py-4', className)}>
+    <div
+      data-slot="load-more-button"
+      className={cn('flex justify-center py-4', className)}
+    >
       <button
         type="button"
         onClick={onClick}
@@ -518,6 +525,7 @@ const MessageList = React.forwardRef<HTMLDivElement, MessageListProps>(
     return (
       <div
         ref={scrollContainerRef}
+        data-slot="message-list"
         className={cn(
           'flex flex-1 flex-col overflow-y-auto',
           'scroll-smooth',
@@ -534,7 +542,10 @@ const MessageList = React.forwardRef<HTMLDivElement, MessageListProps>(
         )}
 
         {/* Messages grouped by date */}
-        <div className="flex flex-col gap-1 p-4">
+        <div
+          data-slot="message-list-content"
+          className="flex flex-col gap-1 p-4"
+        >
           {messageGroups.map((group) => (
             <React.Fragment key={group.date}>
               {/* Date separator */}
diff --git a/src/components/Messaging/MessageThread.tsx b/src/components/Messaging/MessageThread.tsx
index b34d0290..5bef5873 100644
--- a/src/components/Messaging/MessageThread.tsx
+++ b/src/components/Messaging/MessageThread.tsx
@@ -55,6 +55,7 @@ function LightboxModal({ attachment, onClose }: LightboxModalProps) {
 
   return (
     <div
+      data-slot="message-lightbox"
       className={cn('fixed inset-0 z-50', 'flex items-center justify-center')}
       role="dialog"
       aria-modal="true"
@@ -286,6 +287,7 @@ const MessageThread = React.forwardRef<HTMLDivElement, MessageThreadProps>(
     return (
       <div
         ref={ref}
+        data-slot="message-thread"
         className={cn(
           'flex h-full flex-col',
           'bg-white dark:bg-neutral-900',
@@ -421,6 +423,7 @@ function MessagingSplitView({
 
   return (
     <div
+      data-slot="messaging-split-view"
       className={cn(
         'h-full w-full',
         breakpointClasses[mobileBreakpoint],
@@ -429,6 +432,7 @@ function MessagingSplitView({
     >
       {/* Conversation list */}
       <div
+        data-slot="messaging-list-pane"
         className={cn(
           'h-full w-full flex-shrink-0',
           'border-r border-neutral-200 dark:border-neutral-700',
@@ -443,6 +447,7 @@ function MessagingSplitView({
 
       {/* Message thread */}
       <div
+        data-slot="messaging-thread-pane"
         className={cn(
           'h-full min-w-0 flex-1',
           showMobileClasses[mobileBreakpoint]
diff --git a/src/styles/condensed-view.css b/src/styles/condensed-view.css
index df27c1f5..75f52d10 100644
--- a/src/styles/condensed-view.css
+++ b/src/styles/condensed-view.css
@@ -5595,3 +5595,318 @@ body.condensed [data-slot='sidebar-search'] input {
   padding: 0.25rem 0.75rem 0.25rem 2rem !important;
   font-size: 0.6875rem !important;
 }
+
+/* ============================================================================
+   Messaging – condensed overrides
+   ============================================================================ */
+
+/* --- Conversation Header --- */
+
+/* Header container – tighter padding */
+body.condensed [data-slot='conversation-header'] {
+  padding: 0.375rem 0.75rem !important;
+  gap: 0.5rem !important;
+}
+
+/* Header avatar – smaller */
+body.condensed [data-slot='conversation-header-avatar'] > div {
+  width: 2rem !important;
+  height: 2rem !important;
+  font-size: 0.6875rem !important;
+}
+
+/* Header online indicator – proportionally smaller */
+body.condensed [data-slot='conversation-header-avatar'] > span {
+  width: 0.5rem !important;
+  height: 0.5rem !important;
+}
+
+/* Header title */
+body.condensed [data-slot='conversation-header-info'] h2 {
+  font-size: 0.8125rem !important;
+  line-height: 1.125rem !important;
+}
+
+/* Header subtitle */
+body.condensed [data-slot='conversation-header-info'] p {
+  font-size: 0.625rem !important;
+  line-height: 0.875rem !important;
+}
+
+/* --- Conversation List Item --- */
+
+/* List item – tighter padding */
+body.condensed [data-slot='conversation-list-item'] {
+  padding: 0.375rem 0.75rem !important;
+  gap: 0.5rem !important;
+}
+
+/* List item avatar – smaller */
+body.condensed [data-slot='conversation-list-item-avatar'] > div {
+  width: 2.25rem !important;
+  height: 2.25rem !important;
+  font-size: 0.6875rem !important;
+}
+
+/* List item online indicator – smaller */
+body.condensed [data-slot='conversation-list-item-avatar'] > span {
+  width: 0.5rem !important;
+  height: 0.5rem !important;
+}
+
+/* List item title */
+body.condensed [data-slot='conversation-list-item-content'] h3 {
+  font-size: 0.75rem !important;
+  line-height: 1rem !important;
+}
+
+/* List item preview text & timestamp */
+body.condensed [data-slot='conversation-list-item-content'] p,
+body.condensed [data-slot='conversation-list-item-content'] span {
+  font-size: 0.625rem !important;
+  line-height: 0.875rem !important;
+}
+
+/* List item unread badge – smaller */
+body.condensed [data-slot='conversation-list-item-badge'] {
+  height: 1rem !important;
+  min-width: 1rem !important;
+  font-size: 0.5625rem !important;
+  padding: 0 0.25rem !important;
+}
+
+/* --- Message List --- */
+
+/* Message list content – tighter gap & padding */
+body.condensed [data-slot='message-list-content'] {
+  gap: 0.0625rem !important;
+  padding: 0.5rem !important;
+}
+
+/* Date separator – less vertical space */
+body.condensed [data-slot='date-separator'] {
+  padding: 0.375rem 0 !important;
+}
+
+body.condensed [data-slot='date-separator'] span {
+  font-size: 0.5625rem !important;
+  padding: 0.125rem 0.5rem !important;
+}
+
+/* Typing indicator – compact */
+body.condensed [data-slot='typing-indicator'] {
+  padding: 0.25rem 0.5rem !important;
+  gap: 0.375rem !important;
+}
+
+body.condensed [data-slot='typing-indicator'] > div {
+  padding: 0.375rem 0.75rem !important;
+}
+
+body.condensed [data-slot='typing-indicator'] > div > span {
+  width: 0.3125rem !important;
+  height: 0.3125rem !important;
+}
+
+body.condensed [data-slot='typing-indicator'] > span {
+  font-size: 0.5625rem !important;
+}
+
+/* Empty state – smaller text */
+body.condensed [data-slot='message-empty-state'] {
+  padding: 1.5rem !important;
+}
+
+/* Icon wrapper – smaller padding & margin */
+body.condensed [data-slot='message-empty-state'] > div:first-child {
+  padding: 0.5rem !important;
+  margin-bottom: 0.5rem !important;
+}
+
+/* Icon SVG – smaller */
+body.condensed [data-slot='message-empty-state'] > div:first-child svg {
+  width: 2rem !important;
+  height: 2rem !important;
+}
+
+body.condensed [data-slot='message-empty-state'] h3 {
+  font-size: 0.8125rem !important;
+  margin-bottom: 0.25rem !important;
+}
+
+body.condensed [data-slot='message-empty-state'] p {
+  font-size: 0.6875rem !important;
+  margin-bottom: 0.5rem !important;
+}
+
+/* Action button – smaller */
+body.condensed [data-slot='message-empty-state'] button,
+body.condensed [data-slot='message-empty-state'] a {
+  font-size: 0.6875rem !important;
+  padding: 0.375rem 0.75rem !important;
+}
+
+/* Load more button – smaller */
+body.condensed [data-slot='load-more-button'] {
+  padding: 0.375rem 0 !important;
+}
+
+body.condensed [data-slot='load-more-button'] button {
+  font-size: 0.625rem !important;
+  padding: 0.25rem 0.75rem !important;
+}
+
+/* --- Message Bubble --- */
+
+/* Bubble outer wrapper – tighter gap */
+body.condensed [data-slot='message-bubble'] {
+  gap: 0.25rem !important;
+}
+
+/* Avatar – smaller */
+body.condensed [data-slot='message-avatar'] > div {
+  width: 1.5rem !important;
+  height: 1.5rem !important;
+  font-size: 0.5625rem !important;
+}
+
+/* Sender name – smaller */
+body.condensed [data-slot='message-sender-name'] {
+  font-size: 0.5625rem !important;
+  margin-bottom: 0.0625rem !important;
+}
+
+/* Reply preview – compact */
+body.condensed [data-slot='message-reply-preview'] {
+  padding: 0.1875rem 0.5rem !important;
+  font-size: 0.5625rem !important;
+  margin-bottom: 0.0625rem !important;
+}
+
+/* Bubble content – tighter padding, smaller text */
+body.condensed [data-slot='message-bubble-content'] {
+  padding: 0.25rem 0.625rem !important;
+  font-size: 0.75rem !important;
+  line-height: 1.125rem !important;
+  border-radius: 1rem !important;
+}
+
+/* Footer row – smaller timestamps */
+body.condensed [data-slot='message-footer'] {
+  margin-top: 0.125rem !important;
+  gap: 0.25rem !important;
+}
+
+body.condensed [data-slot='message-footer'] span {
+  font-size: 0.5625rem !important;
+}
+
+/* Reactions – smaller pills */
+body.condensed [data-slot='message-reactions'] {
+  gap: 0.125rem !important;
+}
+
+body.condensed [data-slot='message-reactions'] span {
+  font-size: 0.5625rem !important;
+  padding: 0.0625rem 0.25rem !important;
+}
+
+/* System message – smaller */
+body.condensed [data-slot='message-system'] {
+  font-size: 0.625rem !important;
+  padding: 0.125rem 0.25rem !important;
+}
+
+/* Skeleton messages – smaller placeholders */
+body.condensed [data-slot='skeleton-message'] > div:first-child {
+  width: 1.5rem !important;
+  height: 1.5rem !important;
+}
+
+body.condensed [data-slot='skeleton-message'] > div:last-child {
+  height: 1.75rem !important;
+  width: 10rem !important;
+}
+
+/* --- Message Composer --- */
+
+/* Reply preview – compact */
+body.condensed [data-slot='composer-reply-preview'] {
+  padding: 0.25rem 0.75rem !important;
+  gap: 0.375rem !important;
+}
+
+body.condensed [data-slot='composer-reply-preview'] span {
+  font-size: 0.5625rem !important;
+}
+
+body.condensed [data-slot='composer-reply-preview'] p {
+  font-size: 0.6875rem !important;
+}
+
+/* Attachment previews – smaller thumbnails */
+body.condensed [data-slot='composer-attachments'] {
+  padding: 0.375rem !important;
+  gap: 0.25rem !important;
+}
+
+/* Input area – flex centered with breathing room */
+body.condensed [data-slot='composer-input-area'] {
+  display: flex !important;
+  align-items: center !important;
+  padding: 0.5rem 0.375rem !important;
+  gap: 0.25rem !important;
+}
+
+/* Attachment picker button – smaller */
+body.condensed [data-slot='attachment-picker-button'] {
+  padding: 0.25rem !important;
+}
+
+body.condensed [data-slot='attachment-picker-button'] svg {
+  width: 1rem !important;
+  height: 1rem !important;
+}
+
+/* Textarea wrapper – eliminate baseline gap */
+body.condensed [data-slot='composer-input-area'] > .relative {
+  display: flex !important;
+  align-items: center !important;
+}
+
+/* Character counter – vertically centered */
+body.condensed [data-slot='composer-input-area'] > .relative > #char-count {
+  top: 50% !important;
+  bottom: auto !important;
+  transform: translateY(-50%) !important;
+  right: 0.5rem !important;
+}
+
+body.condensed
+  [data-slot='composer-input-area']
+  > .relative
+  > #char-count
+  span {
+  font-size: 0.5625rem !important;
+}
+
+/* Textarea – smaller, vertically centered text */
+body.condensed [data-slot='composer-input'] {
+  font-size: 0.75rem !important;
+  line-height: 1.125rem !important;
+  padding: 0.25rem 0.75rem 0.125rem !important;
+  min-height: 0 !important;
+  height: auto !important;
+  border-radius: 1rem !important;
+}
+
+/* Send button – smaller */
+body.condensed [data-slot='composer-send-button'] {
+  padding: 0.25rem !important;
+  align-self: center !important;
+}
+
+body.condensed [data-slot='composer-send-button'] svg {
+  width: 0.875rem !important;
+  height: 0.875rem !important;
+}

From 075d8a1b9f542ba1c150d05e5728ed8896dd30d0 Mon Sep 17 00:00:00 2001
From: william garrity <wgarrity@mieweb.com>
Date: Tue, 31 Mar 2026 21:29:12 -0400
Subject: [PATCH 2/4] feat(condensed): add condensed view support for Toast
 component

---
 src/components/Toast/Toast.tsx | 20 ++++++++++---
 src/styles/condensed-view.css  | 53 ++++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx
index a332543c..b8ef494d 100644
--- a/src/components/Toast/Toast.tsx
+++ b/src/components/Toast/Toast.tsx
@@ -141,6 +141,7 @@ export function Toast({
   return (
     <div
       role="alert"
+      data-slot="toast"
       className={cn(
         'flex items-start gap-3 rounded-lg border p-4 shadow-lg',
         'max-w-[420px] min-w-[300px]',
@@ -149,15 +150,24 @@ export function Toast({
       )}
     >
       {/* Icon */}
-      <div className={cn('flex-shrink-0', styles.icon)}>{displayIcon}</div>
+      <div data-slot="toast-icon" className={cn('flex-shrink-0', styles.icon)}>
+        {displayIcon}
+      </div>
 
       {/* Content */}
-      <div className="min-w-0 flex-1">
-        {title && <p className="mb-1 text-sm font-semibold">{title}</p>}
-        <div className="text-sm opacity-90">{message}</div>
+      <div data-slot="toast-content" className="min-w-0 flex-1">
+        {title && (
+          <p data-slot="toast-title" className="mb-1 text-sm font-semibold">
+            {title}
+          </p>
+        )}
+        <div data-slot="toast-message" className="text-sm opacity-90">
+          {message}
+        </div>
         {action && (
           <button
             onClick={action.onClick}
+            data-slot="toast-action"
             className="mt-2 rounded text-sm font-medium underline hover:no-underline focus:ring-2 focus:ring-current focus:ring-offset-2 focus:outline-none"
           >
             {action.label}
@@ -169,6 +179,7 @@ export function Toast({
       {dismissible && (
         <button
           onClick={onClose}
+          data-slot="toast-dismiss"
           className="flex-shrink-0 rounded p-1 transition-colors hover:bg-black/10 focus:ring-2 focus:ring-current focus:outline-none dark:hover:bg-white/10"
           aria-label="Dismiss notification"
         >
@@ -214,6 +225,7 @@ export function ToastContainer({
 
   return (
     <div
+      data-slot="toast-container"
       className={cn(
         'pointer-events-none fixed z-50 flex flex-col gap-2',
         positionStyles[position]
diff --git a/src/styles/condensed-view.css b/src/styles/condensed-view.css
index 75f52d10..9a709abe 100644
--- a/src/styles/condensed-view.css
+++ b/src/styles/condensed-view.css
@@ -5910,3 +5910,56 @@ body.condensed [data-slot='composer-send-button'] svg {
   width: 0.875rem !important;
   height: 0.875rem !important;
 }
+
+/* ============================================================================
+   Toast – condensed overrides
+   ============================================================================ */
+
+/* Toast container – tighter gap */
+body.condensed [data-slot='toast-container'] {
+  gap: 0.25rem !important;
+}
+
+/* Toast root – compact padding & gap, narrower */
+body.condensed [data-slot='toast'] {
+  padding: 0.5rem 0.75rem !important;
+  gap: 0.375rem !important;
+  min-width: 220px !important;
+  max-width: 320px !important;
+  border-radius: 0.375rem !important;
+}
+
+/* Toast icon – smaller */
+body.condensed [data-slot='toast-icon'] svg {
+  width: 1rem !important;
+  height: 1rem !important;
+}
+
+/* Toast title – smaller */
+body.condensed [data-slot='toast-title'] {
+  font-size: 0.75rem !important;
+  line-height: 1rem !important;
+  margin-bottom: 0.125rem !important;
+}
+
+/* Toast message – smaller */
+body.condensed [data-slot='toast-message'] {
+  font-size: 0.6875rem !important;
+  line-height: 0.9375rem !important;
+}
+
+/* Toast action – smaller */
+body.condensed [data-slot='toast-action'] {
+  font-size: 0.6875rem !important;
+  margin-top: 0.25rem !important;
+}
+
+/* Toast dismiss – smaller */
+body.condensed [data-slot='toast-dismiss'] {
+  padding: 0.125rem !important;
+}
+
+body.condensed [data-slot='toast-dismiss'] svg {
+  width: 0.75rem !important;
+  height: 0.75rem !important;
+}

From 708593b637a032687741d66b2f4c3104fc21202e Mon Sep 17 00:00:00 2001
From: william garrity <wgarrity@mieweb.com>
Date: Tue, 31 Mar 2026 21:33:52 -0400
Subject: [PATCH 3/4] fix(condensed): replace fragile CSS selectors with
 dedicated data-slots

Address Copilot review comments on PR #156:
- Reaction pills: use > span direct child selector
- Skeleton messages: add skeleton-avatar/bubble/spacer data-slots
- Empty state: add message-empty-state-icon data-slot
- Composer: add composer-input-wrapper and composer-char-count data-slots
---
 src/components/Messaging/MessageComposer.tsx |  8 ++++++--
 src/components/Messaging/MessageList.tsx     | 15 ++++++++++++---
 src/styles/condensed-view.css                | 20 ++++++++------------
 3 files changed, 26 insertions(+), 17 deletions(-)

diff --git a/src/components/Messaging/MessageComposer.tsx b/src/components/Messaging/MessageComposer.tsx
index b62f5fbc..b88bda0c 100644
--- a/src/components/Messaging/MessageComposer.tsx
+++ b/src/components/Messaging/MessageComposer.tsx
@@ -521,7 +521,7 @@ const MessageComposer = React.forwardRef<
             )}
 
             {/* Text input */}
-            <div className="relative flex-1">
+            <div data-slot="composer-input-wrapper" className="relative flex-1">
               <textarea
                 ref={textareaRef}
                 data-slot="composer-input"
@@ -547,7 +547,11 @@ const MessageComposer = React.forwardRef<
 
               {/* Character count */}
               {showCharacterCount && (
-                <div id="char-count" className="absolute right-3 bottom-1.5">
+                <div
+                  data-slot="composer-char-count"
+                  id="char-count"
+                  className="absolute right-3 bottom-1.5"
+                >
                   <CharacterCounter current={content.length} max={maxLength} />
                 </div>
               )}
diff --git a/src/components/Messaging/MessageList.tsx b/src/components/Messaging/MessageList.tsx
index 0b7a8c79..3668fad3 100644
--- a/src/components/Messaging/MessageList.tsx
+++ b/src/components/Messaging/MessageList.tsx
@@ -119,9 +119,13 @@ function SkeletonMessage({
       aria-hidden="true"
     >
       {showAvatar && !isOutgoing && (
-        <div className="h-8 w-8 animate-pulse rounded-full bg-neutral-200 dark:bg-neutral-700" />
+        <div
+          data-slot="skeleton-avatar"
+          className="h-8 w-8 animate-pulse rounded-full bg-neutral-200 dark:bg-neutral-700"
+        />
       )}
       <div
+        data-slot="skeleton-bubble"
         className={cn(
           'animate-pulse rounded-2xl',
           isOutgoing
@@ -130,7 +134,9 @@ function SkeletonMessage({
           'h-10 w-48'
         )}
       />
-      {showAvatar && isOutgoing && <div className="w-8" />}
+      {showAvatar && isOutgoing && (
+        <div data-slot="skeleton-spacer" className="w-8" />
+      )}
     </div>
   );
 }
@@ -265,7 +271,10 @@ function EmptyState({
       aria-label={title}
     >
       {icon || (
-        <div className="mb-4 rounded-full bg-neutral-100 p-4 dark:bg-neutral-800">
+        <div
+          data-slot="message-empty-state-icon"
+          className="mb-4 rounded-full bg-neutral-100 p-4 dark:bg-neutral-800"
+        >
           <svg
             className="h-12 w-12 text-neutral-500"
             fill="none"
diff --git a/src/styles/condensed-view.css b/src/styles/condensed-view.css
index 9a709abe..1ab294bc 100644
--- a/src/styles/condensed-view.css
+++ b/src/styles/condensed-view.css
@@ -5718,13 +5718,13 @@ body.condensed [data-slot='message-empty-state'] {
 }
 
 /* Icon wrapper – smaller padding & margin */
-body.condensed [data-slot='message-empty-state'] > div:first-child {
+body.condensed [data-slot='message-empty-state-icon'] {
   padding: 0.5rem !important;
   margin-bottom: 0.5rem !important;
 }
 
 /* Icon SVG – smaller */
-body.condensed [data-slot='message-empty-state'] > div:first-child svg {
+body.condensed [data-slot='message-empty-state-icon'] svg {
   width: 2rem !important;
   height: 2rem !important;
 }
@@ -5806,7 +5806,7 @@ body.condensed [data-slot='message-reactions'] {
   gap: 0.125rem !important;
 }
 
-body.condensed [data-slot='message-reactions'] span {
+body.condensed [data-slot='message-reactions'] > span {
   font-size: 0.5625rem !important;
   padding: 0.0625rem 0.25rem !important;
 }
@@ -5818,12 +5818,12 @@ body.condensed [data-slot='message-system'] {
 }
 
 /* Skeleton messages – smaller placeholders */
-body.condensed [data-slot='skeleton-message'] > div:first-child {
+body.condensed [data-slot='skeleton-avatar'] {
   width: 1.5rem !important;
   height: 1.5rem !important;
 }
 
-body.condensed [data-slot='skeleton-message'] > div:last-child {
+body.condensed [data-slot='skeleton-bubble'] {
   height: 1.75rem !important;
   width: 10rem !important;
 }
@@ -5869,24 +5869,20 @@ body.condensed [data-slot='attachment-picker-button'] svg {
 }
 
 /* Textarea wrapper – eliminate baseline gap */
-body.condensed [data-slot='composer-input-area'] > .relative {
+body.condensed [data-slot='composer-input-wrapper'] {
   display: flex !important;
   align-items: center !important;
 }
 
 /* Character counter – vertically centered */
-body.condensed [data-slot='composer-input-area'] > .relative > #char-count {
+body.condensed [data-slot='composer-char-count'] {
   top: 50% !important;
   bottom: auto !important;
   transform: translateY(-50%) !important;
   right: 0.5rem !important;
 }
 
-body.condensed
-  [data-slot='composer-input-area']
-  > .relative
-  > #char-count
-  span {
+body.condensed [data-slot='composer-char-count'] span {
   font-size: 0.5625rem !important;
 }
 

From 703b128811a44ebc5e95413697760532313211a4 Mon Sep 17 00:00:00 2001
From: william garrity <wgarrity@mieweb.com>
Date: Tue, 31 Mar 2026 21:41:03 -0400
Subject: [PATCH 4/4] fix(condensed): exclude badge from conversation list span
 selector

---
 src/styles/condensed-view.css | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/styles/condensed-view.css b/src/styles/condensed-view.css
index 1ab294bc..0770663f 100644
--- a/src/styles/condensed-view.css
+++ b/src/styles/condensed-view.css
@@ -5662,7 +5662,9 @@ body.condensed [data-slot='conversation-list-item-content'] h3 {
 
 /* List item preview text & timestamp */
 body.condensed [data-slot='conversation-list-item-content'] p,
-body.condensed [data-slot='conversation-list-item-content'] span {
+body.condensed
+  [data-slot='conversation-list-item-content']
+  span:not([data-slot='conversation-list-item-badge']) {
   font-size: 0.625rem !important;
   line-height: 0.875rem !important;
 }
