| /* ============================================================================ |
| Chat message body |
| ---------------------------------------------------------------------------- |
| The MessageBody surface: message row shell, assistant bubble shell, the |
| hover copy button, the streaming caret, and the user-turn meta row. |
| Astryx owns Markdown prose, code-block chrome, and syntax colors. |
| |
| Relocated from maka-tokens.css. Authored unlayered (the renderer styles/ |
| contract requires it) but pulled into @layer components by |
| `@import ... layer(components)` in styles.css, so the rules keep the exact |
| cascade precedence they had in tokens. Astryx now owns the sender-aware row |
| and bubble chrome; the remaining selectors are product content and actions. |
| ============================================================================ */ |
| |
| /* Astryx owns sender-aware row alignment; Maka only declares the product |
| reading geometry and the deliberately dense conversation type scale. */ |
| .maka-chat-message { |
| width: 100%; |
| opacity: 1; |
| } |
| |
| .maka-chat-message-list { |
| width: 100%; |
| } |
| |
| /* Astryx's ChatMessageList renders a `flex: 1` spacer before its rows so a |
| short conversation sits on the composer instead of hanging from the top. |
| That spacer is unconditional, so on the empty surface it splits the column |
| with the empty-state holder — which is itself `flex: 1` and centres its |
| content — and the hero lands centred in the BOTTOM half: measured 172px |
| below the reading column's centre, plus the list's 16px gap. |
| Collapsing the spacer when the last row is an empty state hands the whole |
| column back to the holder, and its own centring is then true to the |
| surface. Keyed on the empty-state content rather than on "no messages" |
| because a single-message conversation must keep the push-to-bottom. */ |
| .maka-chat-message-list |
| > *:has(> :last-child > :is(.maka-hero, .astryx-empty-state)) |
| > [aria-hidden]:empty { |
| display: none; |
| } |
| |
| /* The conversation reading tier is Astryx's body role: 14px with the 20px |
| 4px-grid leading the type scale computes for it. The leading used to be a |
| hand-rolled 1.6154em (21px) that matched neither Maka's --leading-* tiers |
| nor Astryx's. */ |
| .maka-chat-message-bubble { |
| font: var(--maka-text-body); |
| } |
| |
| /* Geometry (radius, padding, max-width) belongs to ChatMessageBubble: under |
| `density="compact"` it resolves --radius-container plus the compact padding |
| step, so re-declaring them here only forked the primitive. What stays is the |
| product tint and the verbatim-text rule — Maka renders user input as typed, |
| which the primitive has no opinion about. */ |
| .maka-chat-message-bubble-user { |
| background: var(--chat-user-bg); |
| color: var(--chat-user-foreground, var(--foreground)); |
| white-space: pre-wrap; |
| } |
| |
| .maka-chat-message-bubble-assistant { |
| width: 100%; |
| max-width: none; |
| padding: var(--space-0-5) 0 0; |
| } |
| |
| .maka-processing-sequence { |
| display: flex; |
| min-width: 0; |
| flex-direction: column; |
| gap: var(--space-1); |
| } |
| |
| /* Cursor for tool/reasoning disclosure triggers is owned by |
| styles/native-cursor.css (role=button + default !important). */ |
| |
| .maka-turn .astryx-chat-tool-calls [role="button"] > span:nth-child(2) { |
| font: var(--maka-text-label); |
| } |
| |
| /* Reasoning and tool-call rows read at BODY size, not supporting size. |
| Astryx puts the label and preview on inner spans styled as `supporting` |
| (12px on this scale). Measured on main they rendered at 9.75px — Astryx's |
| sm tier at 0.75rem against the old 13px root — which made them the least |
| legible text in the product, and they carry real content — what the agent is |
| thinking and which tool is running — not metadata. |
| Cursor makes the same call: its tool rows sit at body size and lean |
| entirely on --text-secondary/tertiary for de-emphasis. Weight and colour |
| already do that here, so size does not need to. |
| |
| Rebinding the role token rather than restyling the spans. Astryx's |
| supporting atoms are `font-size: var(--text-supporting-size)` and |
| `line-height: var(--text-supporting-leading)`, so redefining those two on |
| the trigger reaches every span that opts into the role, at any depth, |
| through inheritance — no !important, since nothing else declares them |
| here, and no dependency on child order. |
| The earlier form of this rule selected `> span:not(:last-child)`, which |
| read as "every span except the chevron". It was wrong for exactly the |
| reason positional selectors are: ChatReasoning wraps its label, duration |
| and preview in a `<div>` (packages/ui/src/astryx-chat-reasoning.tsx), so |
| the rule enlarged the leading icon wrapper and left the reasoning text at |
| supporting size — the one row the retune was for. */ |
| .maka-turn :is(.astryx-chat-reasoning, .astryx-chat-tool-calls) [role="button"] { |
| --text-supporting-size: var(--text-body-size); |
| --text-supporting-leading: var(--maka-line-body); |
| } |
| |
| /* These boxes need no !important to beat Astryx: its StyleX atoms live in the |
| earlier astryx-components layer, so product CSS in `components` wins on |
| cascade order alone, whatever the specificity — measured, not assumed. */ |
| .maka-turn .astryx-chat-reasoning [role="button"] > span:last-child, |
| .maka-turn .astryx-chat-tool-calls [role="button"] > span:last-child { |
| width: 14px; |
| height: 14px; |
| margin-inline-start: auto; |
| } |
| |
| /* Both chevrons are the same registry icon, which wraps its svg in a span — |
| so the wrapper (`> *`) needs sizing too, not just the svg. */ |
| .maka-turn :is(.astryx-chat-reasoning, .astryx-chat-tool-calls) [role="button"] > span:last-child svg, |
| .maka-turn :is(.astryx-chat-reasoning, .astryx-chat-tool-calls) [role="button"] > span:last-child > * { |
| width: 10px; |
| height: 10px; |
| } |
| |
| @media (hover: hover) { |
| .maka-turn .astryx-chat-tool-calls [role="button"]:hover, |
| .maka-turn .astryx-chat-reasoning [role="button"]:hover { |
| background-color: var(--color-overlay-hover); |
| border-radius: var(--radius-element); |
| } |
| } |
| |
| /* Row detail open: same 0fr→1fr medium motion as group/reasoning. |
| |
| The Astryx duration/ease below are deliberate, not a leak of the product |
| scale. Astryx ChatToolCalls animates its own group open with |
| `grid-template-rows --duration-medium --ease-standard`; a detail row that |
| expands beside one has to share that clock or the two disclosures visibly |
| desync. Product motion belongs on the maka-tokens scale — this is the |
| exception that follows a neighbouring Astryx component, so retune it only |
| when ChatToolCalls moves. */ |
| @keyframes maka-chat-tool-detail-open { |
| from { grid-template-rows: 0fr; } |
| to { grid-template-rows: 1fr; } |
| } |
| |
| .maka-chat-tool-detail { |
| display: grid; |
| grid-template-rows: 1fr; |
| animation: maka-chat-tool-detail-open var(--duration-medium, 300ms) var(--ease-standard, cubic-bezier(0.24, 1, 0.4, 1)) both; |
| } |
| |
| .maka-chat-tool-detail-inner { |
| overflow: hidden; |
| min-height: 0; |
| } |
| |
| @media (prefers-reduced-motion: reduce) { |
| .maka-chat-tool-detail { |
| animation: none; |
| } |
| } |
| |
| /* --- Assistant bubble shell ----------------------------------------------- */ |
| /* User message: a tinted Astryx bubble anchored to the right, sized by the |
| primitive's own compact-density geometry. */ |
| /* Assistant: no bubble — just text on background, like an editor. The |
| Markdown typography and block rhythm belong to Astryx, so the shell keeps |
| only container geometry. |
| The reading measure is inherited from the conversation surface shared with |
| the composer, so the assistant block matches the composer width. */ |
| /* The streaming ▎ caret + its trailing-<p> inline hack were retired by the |
| streaming UI rework. Chat smooths the visible text before it reaches |
| Markdown; PR 8 owns the remaining streaming and scroll-anchoring boundary. */ |
| |
| /* --- User-turn meta row (relocated from tool-output.css) ----------------- */ |
| /* PR-CHAT-CHROME-FOLLOWUP-0 / PR5 CHAT-MESSAGE-TIME-0: the relative time is |
| always visible now (it was `opacity: 0` until hover, which hid it on |
| touch + from assistive tech). It stays quiet — caption tier at |
| normal weight, muted — and rides in the user turn's meta row beneath |
| the block. Aligned to the same tier the footer-action cva uses (text-xs). */ |
| /* #1879: height off the control ruler, not off the line box. `inline-block` |
| let the leading decide the box — measured, bumping this element's own |
| leading to 40px took the box from 20px to 40px and carried the meta row |
| with it. `--h-control-xs` is 20px, the value the supporting leading |
| already resolves to, so the pin is pixel-neutral today and load-bearing |
| the next time a leading moves. Safe to pin because `white-space: nowrap` |
| below makes single-line a property of this box rather than an assumption. */ |
| .maka-message-time-inline { |
| font: var(--maka-text-supporting); |
| height: var(--h-control-xs); |
| display: inline-flex; |
| align-items: center; |
| color: var(--muted-foreground); |
| font-variant-numeric: tabular-nums; |
| white-space: nowrap; |
| } |
| |
| /* User turn meta row: quiet time + a copy affordance, right-aligned |
| beneath the message block (the parent `Message role="user"` aligns its |
| items to flex-end, so this row shrink-wraps and hugs the right edge). */ |
| .maka-message-meta { |
| display: inline-flex; |
| align-items: center; |
| gap: var(--space-1-5); |
| } |
| |
| /* Turn summary / lineage rows / footer measure column folded into the |
| `@maka/ui` Marker chat primitive's own variants (issue #332 PR2 — |
| packages/ui/src/primitives/chat.tsx); the layout is now location- |
| independent instead of keyed on a `[data-role="assistant"]` descendant. */ |
| |
| /* --- Conversation heading scale ------------------------------------------- */ |
| /* Astryx's heading ladder (20/18/16/14…) is a DOCUMENT scale, and the |
| transcript is not a document. An agent turn emits `##` freely, so a ladder |
| that steps 1.4× per level shreds one reply into visually unrelated slabs. |
| Flatten it to two steps — h1 for a genuine section break, everything below |
| at body size — and let weight 600 plus colour carry the hierarchy, which is |
| what the rest of the conversation already does. |
| Cursor (1.43/1.32/1.21), Claude Code (1.14/1.07/1.0) and Codex |
| (1.71/1.43/1.21) all flatten here relative to their own document styles; |
| Claude Code goes furthest and merges h4–h6 into `strong`. Two steps sits |
| inside that range. |
| Every level shares the transcript baseline so headings do not add rhythm of |
| their own. |
| Scoped to `.maka-turn`, not to the Markdown contract alone. The contract is |
| emitted by the shared MarkdownBody, whose other caller is the Daily Review |
| panel — and a review report IS a document, so the argument above does not |
| apply to it. It keeps Astryx's full ladder. |
| Bare tags are the whole story: Astryx's Markdown renderer emits plain |
| h1–h6 with stylex props (Markdown.js `const Tag = \`h${level}\``). The |
| .astryx-heading.level-N classes come from the standalone Heading |
| component, which Markdown never mounts. */ |
| /* The one heading in a turn that is not at the body size, and so the one that |
| cannot take the body baseline with it: at 16px, --maka-line-body's 20px is |
| on the 4px grid but is the 14px tier's line box, one step tighter than the |
| tier this heading actually sits at. It takes its own tier's leading (24px); |
| the turn still reads as one rhythm because 24 is the next step of the same |
| grid, not a different one. */ |
| .maka-turn [data-maka-contract="markdown"] h1 { |
| font: var(--maka-text-heading-3); |
| } |
| |
| .maka-turn [data-maka-contract="markdown"] :is(h2, h3, h4, h5, h6) { |
| font: var(--maka-text-heading-4); |
| } |
| |
| .maka-chat-message pre:not([data-maka-contract="markdown"] pre) { |
| margin: 0; |
| font: inherit; |
| } |
| |
| /* System-note pill. Markdown descendants are excluded through Maka's product |
| boundary so raw-pre treatment cannot become a second code-block authority. */ |
| .maka-chat-message[data-sender="system"] |
| pre:not([data-maka-contract="markdown"] pre) { |
| font: var(--maka-text-supporting); |
| display: inline-flex; |
| border-radius: var(--radius-pill); |
| background: oklch(from var(--info) l c h / 0.08); |
| color: var(--info-text); |
| padding: var(--space-0-5) var(--space-1-5); |
| } |