You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by co...@apache.org on 2026/10/02 12:29:17 UTC

(maka) branch main updated: refactor(ui): move turn chrome onto Astryx layout (#5911)

This is an automated email from the ASF dual-hosted git repository.

Colafornia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/main by this push:
     new 887924e14a refactor(ui): move turn chrome onto Astryx layout (#5911)
887924e14a is described below

commit 887924e14aa3fe78b746fd88a43548d7335b9573
Author: Colafornia <co...@gmail.com>
AuthorDate: Fri Oct 2 20:29:11 2026 +0800

    refactor(ui): move turn chrome onto Astryx layout (#5911)
    
    Generated-by: Devin
---
 apps/desktop/src/renderer/styles/chat-detail.css |   7 +-
 apps/desktop/stories/app-shell.stories.tsx       |  43 +++++++++
 docs/astryx-surface-file-inventory.md            |   4 +-
 packages/ui/src/chat-turn.tsx                    | 115 +++++++++++++++--------
 packages/ui/src/chat-view.tsx                    |   9 +-
 packages/ui/src/primitives/chat.tsx              |  48 ++--------
 packages/ui/src/styles.css                       |  29 ++----
 7 files changed, 149 insertions(+), 106 deletions(-)

diff --git a/apps/desktop/src/renderer/styles/chat-detail.css b/apps/desktop/src/renderer/styles/chat-detail.css
index 8e9bb9c265..cb76ec561b 100644
--- a/apps/desktop/src/renderer/styles/chat-detail.css
+++ b/apps/desktop/src/renderer/styles/chat-detail.css
@@ -20,14 +20,11 @@
 /* A turn reads as one unit because its internal gap is well under the
    between-turn gap, which the transcript row in chat-view.tsx owns. */
 .maka-turn {
-  display: flex;
-  flex-direction: column;
+  /* local: VStack owns display/direction/gap/width; the reading measure and
+     flex-item behavior have no props to move onto. */
   flex: 0 0 auto;
-  gap: var(--space-3);
   max-width: var(--maka-reading-measure);
   margin: 0 auto;
-  width: 100%;
-  box-sizing: border-box;
 }
 
 /* Search-highlight ring on the turn the search panel just navigated to.
diff --git a/apps/desktop/stories/app-shell.stories.tsx b/apps/desktop/stories/app-shell.stories.tsx
index c14f69b4bd..a936a81aff 100644
--- a/apps/desktop/stories/app-shell.stories.tsx
+++ b/apps/desktop/stories/app-shell.stories.tsx
@@ -583,6 +583,49 @@ export const UpdateDownloaded: Story = {
   render: () => <ComposedShell updateReminder={{ state: 'downloaded', latestVersion: '0.1.7' }} />,
 };
 
+const onLineageNavigation = fn();
+
+// Real path: open a session retaining an original reply and its regenerated
+// reply. Stored turn_state lineage is projected by ChatView and the production
+// deriveChatTurnPresentation, including the forward and reverse navigation.
+export const RegeneratedConversation: Story = {
+  render: () => <ComposedShell chat={{
+    scrollBehavior: 'auto',
+    onLineageBadgeClick: onLineageNavigation,
+    messages: [
+      user('lineage-original-user', 'lineage-original', 5, '解释一下这个方案。'),
+      assistant('lineage-original-answer', 'lineage-original', 4, '旧回答保留在原来的轮次中。'),
+      { type: 'turn_state', id: 'lineage-original-state', turnId: 'lineage-original', ts: NOW - 4 * 60_000, status: 'completed' },
+      user('lineage-new-user', 'lineage-new', 3, '解释一下这个方案。'),
+      assistant('lineage-new-answer', 'lineage-new', 2, '重新生成的回答与旧回答通过来源标记相连。'),
+      { type: 'turn_state', id: 'lineage-new-state', turnId: 'lineage-new', ts: NOW - 2 * 60_000, status: 'completed', regeneratedFromTurnId: 'lineage-original' },
+    ],
+  }} />,
+  play: async ({ canvasElement }) => {
+    onLineageNavigation.mockClear();
+    const canvas = within(canvasElement);
+    const forward = await canvas.findByRole('button', { name: '重新生成自旧回答' });
+    const reverse = await canvas.findByRole('button', { name: '已重新生成 → 新回答' });
+    const original = canvasElement.querySelector<HTMLElement>('[data-turn-id="lineage-original"]')!;
+    const regenerated = canvasElement.querySelector<HTMLElement>('[data-turn-id="lineage-new"]')!;
+    const sourceRow = forward.closest('.maka-turn-lineage-row')!;
+    const derivativeRow = reverse.closest('.maka-turn-lineage-row')!;
+    await document.fonts.ready;
+    expect(sourceRow.getBoundingClientRect().bottom).toBeLessThanOrEqual(regenerated.querySelector('.maka-user-message')!.getBoundingClientRect().top);
+    expect(derivativeRow.getBoundingClientRect().bottom).toBeLessThanOrEqual(original.querySelector('.maka-turn-footer')!.getBoundingClientRect().top);
+    for (const [button, row] of [[forward, sourceRow], [reverse, derivativeRow]] as const) {
+      expect(button.getBoundingClientRect().left).toBeCloseTo(row.getBoundingClientRect().left, 1);
+      expect(button.getBoundingClientRect().right).toBeLessThanOrEqual(row.getBoundingClientRect().right + 1);
+    }
+    forward.focus();
+    await expect(forward).toHaveFocus();
+    await userEvent.keyboard('{Enter}');
+    await expect(onLineageNavigation).toHaveBeenLastCalledWith('lineage-original');
+    await userEvent.click(reverse);
+    await expect(onLineageNavigation).toHaveBeenLastCalledWith('lineage-new');
+  },
+};
+
 // Real path: the same download fails → same slot, muted variant, retry.
 export const UpdateFailed: Story = {
   render: () => <ComposedShell updateReminder={{ state: 'error', latestVersion: '0.1.7' }} />,
diff --git a/docs/astryx-surface-file-inventory.md b/docs/astryx-surface-file-inventory.md
index eb7ff9240c..e7a38a7c10 100644
--- a/docs/astryx-surface-file-inventory.md
+++ b/docs/astryx-surface-file-inventory.md
@@ -284,8 +284,8 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi
 | `packages/ui/src/chat-empty-hero.tsx` | shell-chrome-or-panel | none | aligned — no raw controls; no Astryx JSX usage | aligned |
 | `packages/ui/src/chat-model-switcher.tsx` | shell-chrome-or-panel | Button, Selector, SelectorOption | aligned — uses Astryx (Button, Selector, SelectorOption) | aligned |
 | `packages/ui/src/chat-surface-layout.tsx` | shell-chrome-or-panel | ChatLayout | aligned — uses Astryx (ChatLayout) | aligned |
-| `packages/ui/src/chat-turn.tsx` | shell-chrome-or-panel | Badge, Banner, Button, ChatMessage, ChatMessageBubble, ChatMessageMetadata, ChatSystemMessage, HStack, Icon, IconButton, Spinner, Thumbnail, Timestamp, Token, Tooltip | aligned — uses Astryx (Badge, Banner, Button, ChatMessage, ChatMessageBubble, ChatMessageMetadata, ChatSystemMessage, HStack) | aligned |
-| `packages/ui/src/chat-view.tsx` | shell-chrome-or-panel | Button, ButtonGroup, ChatMessageList, EmptyState, HStack, Spinner | aligned — uses Astryx (Button, ButtonGroup, ChatMessageList, EmptyState, HStack, Spinner) | aligned |
+| `packages/ui/src/chat-turn.tsx` | shell-chrome-or-panel | Badge, Banner, Button, ChatMessage, ChatMessageBubble, ChatMessageMetadata, ChatSystemMessage, HStack, Icon, IconButton, Spinner, Thumbnail, Timestamp, Token, Tooltip, VStack | aligned — uses Astryx (Badge, Banner, Button, ChatMessage, ChatMessageBubble, ChatMessageMetadata, ChatSystemMessage, HStack) | aligned |
+| `packages/ui/src/chat-view.tsx` | shell-chrome-or-panel | Button, ButtonGroup, ChatMessageList, EmptyState, HStack, Spinner, VStack | aligned — uses Astryx (Button, ButtonGroup, ChatMessageList, EmptyState, HStack, Spinner, VStack) | aligned |
 | `packages/ui/src/choice-panel.tsx` | shell-chrome-or-panel | Badge, Item | aligned — uses Astryx (Badge, Item) | aligned |
 | `packages/ui/src/client-capability-prompt.tsx` | ui-composition | Button | aligned — uses Astryx (Button) | aligned |
 | `packages/ui/src/client-plugin-runtime.tsx` | ui-composition | none | aligned — no raw controls; no Astryx JSX usage | aligned |
diff --git a/packages/ui/src/chat-turn.tsx b/packages/ui/src/chat-turn.tsx
index 9f29d6b572..c67f2954af 100644
--- a/packages/ui/src/chat-turn.tsx
+++ b/packages/ui/src/chat-turn.tsx
@@ -39,6 +39,7 @@ import {
   Thumbnail,
   Timestamp,
   Token,
+  VStack,
   useLightbox,
   useMediaQuery,
 } from '@astryxdesign/core';
@@ -61,7 +62,7 @@ import {
 import { foldTimeline, reconcileFoldedEntries, type FoldedTimelineChild, type FoldedTimelineEntry } from './timeline-fold.js';
 import { AttachmentKindIcon } from './attachment-kinds.js';
 import { QuoteRefChip } from './quote-ref-chip.js';
-import { Marker, markerVariants } from './primitives/chat.js';
+import { markerVariants } from './primitives/chat.js';
 import { ToolTrow } from './tool-activity.js';
 import { formatBytes } from './tool-activity/preview-utils.js';
 import { useUiLocale } from './locale-context.js';
@@ -361,6 +362,33 @@ function CopyButton(props: {
   );
 }
 
+/**
+ * Host-provenance note ("scheduled task", "agent graph", …): a non-interactive
+ * chip whose durable id stays in the tooltip instead of the transcript body.
+ * `aria-description` keeps that id reachable for assistive tech, matching the
+ * role `title` used to play before Astryx `Tooltip` took over the hover path.
+ */
+function HostOriginNote(props: { title: string; icon: ReactNode; label: string }) {
+  return (
+    <Tooltip content={props.title}>
+      <HStack
+        gap={1}
+        vAlign="center"
+        paddingBlock={0.5}
+        paddingInline={1.5}
+        width="fit-content"
+        className={markerVariants({ variant: 'host-origin' })}
+        data-slot="marker"
+        data-variant="host-origin"
+        role="note"
+        aria-description={props.title}
+      >
+        {props.icon}
+        <span>{props.label}</span>
+      </HStack>
+    </Tooltip>
+  );
+}
 
 /**
  * Renders one conversational turn: user message → tools used → assistant
@@ -495,7 +523,10 @@ export const TurnView = memo(function TurnView(props: {
     [foldedTimeline, showAssistantMessage],
   );
   return (
-    <section
+    <VStack
+      as="section"
+      gap={3}
+      width="100%"
       className="maka-turn"
       data-maka-contract="markdown-flow"
       data-turn-id={turn.turnId}
@@ -503,7 +534,16 @@ export const TurnView = memo(function TurnView(props: {
       tabIndex={props.searchHighlighted ? -1 : undefined}
     >
       {forwardBadges.length > 0 && (
-        <Marker variant="lineage-row" aria-label={copy.sourceAriaLabel}>
+        <HStack
+          gap={0.5}
+          wrap="wrap"
+          vAlign="center"
+          width="100%"
+          className={markerVariants({ variant: 'lineage-row' })}
+          data-slot="marker"
+          data-variant="lineage-row"
+          aria-label={copy.sourceAriaLabel}
+        >
           {forwardBadges.map((badge) => (
             <UiButton
               key={badge.id}
@@ -517,39 +557,30 @@ export const TurnView = memo(function TurnView(props: {
               label={badge.label}
             />
           ))}
-        </Marker>
+        </HStack>
       )}
       {/* Host provenance keeps non-user prompts from impersonating the user.
           Durable ids stay in tooltips instead of the transcript body. */}
       {turn.user?.hostOrigin?.kind === 'scheduled_task' && (
-        <Marker
-          variant="host-origin"
-          role="note"
+        <HostOriginNote
           title={copy.scheduledTaskTitle(turn.user.hostOrigin.scheduledTaskId)}
-        >
-          <Timer size={ICON_SIZE.meta} aria-hidden="true" />
-          <span>{copy.scheduledTaskTriggered}</span>
-        </Marker>
+          icon={<Timer size={ICON_SIZE.meta} aria-hidden="true" />}
+          label={copy.scheduledTaskTriggered}
+        />
       )}
       {turn.user?.hostOrigin?.kind === 'legacy_automation' && (
-        <Marker
-          variant="host-origin"
-          role="note"
+        <HostOriginNote
           title={copy.legacyAutomationTitle(turn.user.hostOrigin.automationId)}
-        >
-          <Timer size={ICON_SIZE.meta} aria-hidden="true" />
-          <span>{copy.legacyAutomationTriggered}</span>
-        </Marker>
+          icon={<Timer size={ICON_SIZE.meta} aria-hidden="true" />}
+          label={copy.legacyAutomationTriggered}
+        />
       )}
       {turn.user?.hostOrigin?.kind === 'goal' && (
-        <Marker
-          variant="host-origin"
-          role="note"
+        <HostOriginNote
           title={copy.goalTitle(turn.user.hostOrigin.goalId)}
-        >
-          <RefreshCcw size={ICON_SIZE.meta} aria-hidden="true" />
-          <span>{copy.goalContinued}</span>
-        </Marker>
+          icon={<RefreshCcw size={ICON_SIZE.meta} aria-hidden="true" />}
+          label={copy.goalContinued}
+        />
       )}
       {turn.user?.hostOrigin?.kind === 'workhub_result' && (
         <ChatSystemMessage
@@ -561,14 +592,11 @@ export const TurnView = memo(function TurnView(props: {
         </ChatSystemMessage>
       )}
       {turn.user?.hostOrigin?.kind === 'agent_graph' && (
-        <Marker
-          variant="host-origin"
-          role="note"
+        <HostOriginNote
           title={copy.agentGraphTitle(turn.user.hostOrigin.graphId)}
-        >
-          <GitBranch size={ICON_SIZE.meta} aria-hidden="true" />
-          <span>{copy.agentGraphTriggered}</span>
-        </Marker>
+          icon={<GitBranch size={ICON_SIZE.meta} aria-hidden="true" />}
+          label={copy.agentGraphTriggered}
+        />
       )}
       {props.transientMessages?.map((message) => (
         <TransientUserMessage key={message.id} message={message} />
@@ -711,7 +739,7 @@ export const TurnView = memo(function TurnView(props: {
               sender="assistant"
               className="maka-chat-message maka-assistant-answer"
             >
-            <div className="maka-assistant-answer-content">
+            <VStack gap={2} width="100%" className="maka-assistant-answer-content">
               {props.messageRail}
               {/* The turn timeline is the rendering source of truth
                 (materialize.ts): each step's 深度思考 disclosure, answer bubble,
@@ -791,9 +819,18 @@ export const TurnView = memo(function TurnView(props: {
                   )}
                 </Banner>
               )}
-            </div>
+            </VStack>
             {ownsTurnChrome && reverseBadges.length > 0 && (
-              <Marker variant="lineage-row-reverse" aria-label={copy.derivativesAriaLabel}>
+              <HStack
+                gap={0.5}
+                wrap="wrap"
+                vAlign="center"
+                width="100%"
+                className={markerVariants({ variant: 'lineage-row-reverse' })}
+                data-slot="marker"
+                data-variant="lineage-row-reverse"
+                aria-label={copy.derivativesAriaLabel}
+              >
                 {reverseBadges.map((badge) => (
                   <UiButton
                     key={badge.id}
@@ -807,7 +844,7 @@ export const TurnView = memo(function TurnView(props: {
                     label={badge.label}
                   />
                 ))}
-              </Marker>
+              </HStack>
             )}
             {ownsTurnChrome && (
               <TurnFooter
@@ -833,7 +870,7 @@ export const TurnView = memo(function TurnView(props: {
           </Fragment>
         );
       })}
-    </section>
+    </VStack>
   );
 });
 
@@ -956,10 +993,10 @@ export function PendingTurnAnswer(props: {
       sender="assistant"
       className="maka-chat-message maka-assistant-answer"
     >
-      <div className="maka-assistant-answer-content">
+      <VStack gap={2} width="100%" className="maka-assistant-answer-content">
         <TurnStatusBar status="running" running={props.running} startedAt={startedAt} providerRetry={props.providerRetry} />
         {props.providerRetry && <ModelProviderRetryIndicator retry={props.providerRetry} />}
-      </div>
+      </VStack>
       <TurnFooter turnId={props.turnId} actions={[]} live context={context} />
     </LocalizedChatMessage>
   );
diff --git a/packages/ui/src/chat-view.tsx b/packages/ui/src/chat-view.tsx
index 824dfdd02e..9f1da9c58a 100644
--- a/packages/ui/src/chat-view.tsx
+++ b/packages/ui/src/chat-view.tsx
@@ -56,7 +56,7 @@ import type {
   QuoteRef,
   ShellRunUpdate,
 } from '@maka/core/events';
-import { Button, ButtonGroup, ChatMessageList, EmptyState, HStack, Spinner } from '@astryxdesign/core';
+import { Button, ButtonGroup, ChatMessageList, EmptyState, HStack, Spinner, VStack } from '@astryxdesign/core';
 import { useChatLayoutContext } from '@astryxdesign/core/Chat';
 import { useLayer } from '@astryxdesign/core/Layer';
 import { finalAssistantReplyText } from './materialize.js';
@@ -539,7 +539,10 @@ export function ChatView(props: {
   // Tail rows have no Turn ancestor, so the reading measure that `.maka-turn`
   // owns would not reach them: without it the bubble spans the full window.
   const tail = hasPendingAnswer || tailTransientMessages.length > 0 ? (
-    <section
+    <VStack
+      as="section"
+      gap={3}
+      width="100%"
       className={hasPendingAnswer ? 'maka-turn maka-pending-turn' : 'maka-turn'}
       data-awaiting-host={awaitingHost || undefined}
     >
@@ -557,7 +560,7 @@ export function ChatView(props: {
           providerRetry={activeContent?.providerRetry}
         />
       )}
-    </section>
+    </VStack>
   ) : null;
   const { startMargin, listRef, measureStartMargin } = useTranscriptStartMargin(scrollRef);
   const { highlightedTurnId, placed, commandTurnId, revealTurnAtStart, measurement } = useChatScroll({
diff --git a/packages/ui/src/primitives/chat.tsx b/packages/ui/src/primitives/chat.tsx
index eacc856277..4955d27110 100644
--- a/packages/ui/src/primitives/chat.tsx
+++ b/packages/ui/src/primitives/chat.tsx
@@ -19,13 +19,11 @@
 
 "use client";
 
-import type React from "react";
-import { cn } from "../utils.js";
-
 /**
- * `Marker` — the per-turn lineage / footer chrome (issue #332, PR2).
+ * `markerVariants` — the per-turn lineage / footer chrome classes
+ * (issue #332, PR2).
  *
- * Retires the bespoke `.maka-turn-summary*`, `.maka-turn-lineage-*`, and
+ * Retired the bespoke `.maka-turn-summary*`, `.maka-turn-lineage-*`, and
  * `.maka-turn-footer*` shell
  * CSS (spread across `maka-tokens.css`, `styles/settings/models.css`, and the
  * re-anchored measure-column block in `styles/tool-output.css`), moving each
@@ -33,12 +31,14 @@ import { cn } from "../utils.js";
  *
  * The measure-column geometry the old `tool-output.css` re-anchor applied to
  * the summary / lineage rows / footer is gone rather than moved: `.maka-turn`
- * is the column, and every `Marker` renders inside one, so a second cap on the
- * chrome could only ever be the same edge stated twice.
+ * is the column, and every marked element renders inside one, so a second cap
+ * on the chrome could only ever be the same edge stated twice.
  *
- * `markerVariants` is exported from THIS module as a local variant recipe
- * so the lineage badge + footer action — which render as `UiButton` and can't
- * be wrapped — apply the shell via `className`; `Button` runs it through
+ * Layout itself now rides Astryx primitives (`VStack` / `HStack` own the
+ * column, rows and chips); this recipe only maps each chrome slot to the
+ * semantic classes its remaining surface styling still selects on. The
+ * lineage badge + footer action — which render as `UiButton` and can't be
+ * wrapped — apply the shell via `className`; `Button` runs it through
  * `cn` last so consumers can append their own product hook.
  * It is intentionally kept OFF the `@maka/ui` package barrel (see `index.ts`):
  * the only consumers import it by relative path, so the variant table stays an
@@ -68,34 +68,6 @@ function markerVariants({ variant }: { variant: MarkerVariant }): string {
 
 export { markerVariants };
 
-export interface MarkerProps extends React.ComponentPropsWithoutRef<"div"> {
-  variant: MarkerVariant;
-  // The summary chips were authored as inline `<span>`s; the containers /
-  // markers as `<div>`s. Keep the original tag so the semantic-class
-  // conversion is structurally identical (zero behavioral change).
-  as?: "div" | "span";
-}
-
-export function Marker({
-  className,
-  variant,
-  as: Tag = "div",
-  ...props
-}: MarkerProps): React.ReactElement {
-  return (
-    // `{...props}` first so the `data-slot` / `data-variant` hooks land last and
-    // can't be clobbered by a consumer (mirrors Message / Bubble). The styling
-    // `data-kind` / `data-state` / `data-direction` etc. flow through `...props`
-    // and are read by the literalized `data-[…]:` variants above.
-    <Tag
-      {...props}
-      data-slot="marker"
-      data-variant={variant}
-      className={cn(markerVariants({ variant }), className)}
-    />
-  );
-}
-
 /**
  * Tool-result preview surfaces (issue #332, PR4) — the semantic classes
  * `DiffCodePreview` and the load-tool result card style through.
diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css
index 9b0439aee5..cbe5e05b59 100644
--- a/packages/ui/src/styles.css
+++ b/packages/ui/src/styles.css
@@ -252,7 +252,9 @@
   flex: 1;
   padding-block: 0 var(--space-1-5);
 }
-.maka-assistant-answer-content { display: flex; width: 100%; min-width: 0; flex-direction: column; gap: 8px; }
+/* local: VStack owns the column and gap; this is only the flex-child floor
+   that lets long diagnostics shrink inside the ChatMessage column. */
+.maka-assistant-answer-content { min-width: 0; }
 /* Holds the answer's place, unseen and unannounced, until the Host names the Turn. */
 .maka-pending-turn[data-awaiting-host] .maka-assistant-answer { visibility: hidden; }
 .maka-subagent-session-label,
@@ -905,16 +907,13 @@
 .maka-sandbox-blocked-copy[data-copy-feedback="failed"] { color: var(--destructive); }
 
 .maka-turn-host-origin {
+  /* local: HStack owns display/gap/padding/width; the chip surface (tone,
+     radius, right-edge self-alignment, rhythm) has no props to move onto. */
   font: var(--maka-text-supporting);
-  display: inline-flex;
-  width: fit-content;
-  align-items: center;
   align-self: flex-end;
-  gap: var(--space-1);
   margin-inline: 0;
   margin-bottom: var(--space-1);
   border-radius: var(--radius-control);
-  padding: var(--space-0-5) var(--space-1-5);
   background: var(--foreground-5);
   color: var(--muted-foreground);
 }
@@ -943,16 +942,6 @@
 }
 .maka-turn-failed-banner { margin-block: var(--space-1) var(--space-0-5); }
 
-.maka-turn-lineage-row,
-.maka-turn-footer {
-  display: flex;
-  width: 100%;
-  flex-wrap: wrap;
-  align-items: center;
-  justify-content: flex-start;
-  gap: var(--space-0-5);
-}
-
 .maka-turn-lineage-row {
   margin-top: var(--space-0-5);
   margin-bottom: var(--space-1);
@@ -962,10 +951,8 @@
 .maka-turn-lineage-row-reverse { margin-top: var(--space-1); }
 
 .maka-turn-lineage-badge {
-  border: 0;
+  /* local: lineage navigation keeps its pill silhouette; Button has no radius prop. */
   border-radius: var(--radius-pill);
-  background: oklch(from var(--foreground) l c h / 0.05);
-  color: var(--muted-foreground);
 }
 
 .maka-turn-lineage-badge[data-direction="forward"] {
@@ -979,6 +966,10 @@
 }
 
 .maka-turn-footer {
+  /* local: ChatMessageMetadata owns flex/centering, but has no width, wrap or gap props. */
+  width: 100%;
+  flex-wrap: wrap;
+  gap: var(--space-0-5);
   min-height: var(--size-element-sm, 28px);
   margin-top: var(--space-0-5);
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org