| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| @import 'katex/dist/katex.min.css'; |
| |
| .maka-model-wheel-anchor { display: inline-flex; min-width: 0; max-width: 100%; } |
| .maka-model-wheel-anchor > button { min-width: 0; max-width: 100%; } |
| .maka-model-wheel-anchor[data-open='true'] > button { visibility: hidden; } |
| .maka-model-wheel-expanded { border-radius: 10px; background: color-mix(in oklch, var(--background-elevated) 94%, transparent); backdrop-filter: blur(12px); } |
| .maka-model-wheel { position: relative; width: min(264px, 55vw); max-width: 100%; } |
| .maka-model-wheel::before { content: ''; position: absolute; inset: 50% 0 auto; height: 44px; translate: 0 -50%; border-radius: 10px; background: color-mix(in oklch, var(--foreground) 6%, transparent); pointer-events: none; } |
| .maka-model-wheel-viewport { position: relative; box-sizing: border-box; overflow-y: auto; overscroll-behavior: contain; scroll-snap-type: y mandatory; scrollbar-width: none; mask-image: linear-gradient(transparent, black 32%, black 68%, transparent); outline: none; } |
| .maka-model-wheel-viewport:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: calc(-1 * var(--focus-ring-width)); } |
| .maka-model-wheel-viewport[data-dragging='true'] { scroll-snap-type: none; cursor: grabbing; } |
| .maka-model-wheel-viewport[data-dragging='true'] .maka-model-wheel-option { cursor: grabbing; } |
| .maka-model-wheel-option { position: relative; display: flex; flex-direction: column; justify-content: center; padding: 0 28px 0 12px; scroll-snap-align: center; scroll-snap-stop: always; cursor: pointer; user-select: none; } |
| .maka-model-wheel-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; color: var(--muted-foreground); transform: scale(0.96); transform-origin: left center; transition: transform 180ms ease-out, color 180ms ease-out; } |
| .maka-model-wheel-option[data-active='true'] .maka-model-wheel-label { font-weight: 600; color: var(--foreground); transform: scale(1); } |
| .maka-model-wheel-viewport[aria-disabled='true'] { opacity: 0.5; overflow-y: hidden; } |
| @media (prefers-reduced-motion: reduce) { .maka-model-wheel-label { transition: none; } } |
| .maka-model-wheel-provider { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; color: var(--muted-foreground); } |
| .maka-model-wheel-check { position: absolute; right: 8px; top: 50%; translate: 0 -50%; color: var(--muted-foreground); } |
| |
| /* Maka product compositions shared across desktop renderer surfaces. Astryx |
| owns generic component chrome; these selectors own only product semantics. */ |
| |
| /* Retained tool evidence is not proof of current execution. Hide activity via |
| Astryx's public spinner target without inventing a completed/failed tool. */ |
| .maka-tool-activity-card[data-activity-observed='false'] .astryx-spinner { display: none; } |
| |
| /* An Item row draws a focus ring for ITS OWN tab stop, never for a control |
| that already draws one. |
| |
| Astryx's Item (and everything built on it — ListItem, and so every settings |
| row) applies `outline: 2px solid accent` at `:has(:focus-visible)`, |
| unconditionally. That is right for an interactive row: `onClick`/`href` |
| makes Item render an INVISIBLE `<button>`/`<a>` as a direct child, and the |
| row's ring is that button's only visible focus indicator. It is wrong for |
| every other row, where the focusable thing is a real control in the end slot |
| that rings itself — the row ring is then a second outline around a whole |
| label + description + control band. |
| |
| Two ways this showed up, both measured in a real window: |
| |
| 1. Tabbing onto any settings-row control — a Selector trigger, a Switch, a |
| SegmentedControl, a ghost Button — lit the control AND the full row. |
| 2. Opening the 默认模型 picker outlined the entire row and nothing else. |
| A Selector popup is a native `popover` element: the top layer moves where |
| it PAINTS, not where it sits in the DOM, so it is still a descendant of |
| the row. Focus goes to the popup's search input on open, `:has()` walks |
| up to the row, and the row rings — while the trigger, whose own wrapper |
| is a sibling of the popup, does not. Reported as "点开下拉框时外面整行 |
| 出现一个蓝色的框". |
| |
| `:not(:has(> :is(a, button):focus-visible))` is what separates the two: the |
| only `<a>`/`<button>` that is a DIRECT child of an Item root is that |
| invisible tab stop, since start/end/label content each sit inside their own |
| span. So an interactive row keeps its ring when its own tab stop has focus, |
| and loses it only when focus is on a control that owns its indicator — which |
| is also the case for a row carrying both. |
| |
| Product CSS is the last cascade layer (`components`, see |
| cascade-layers.css), so this wins over the compiled StyleX atom regardless |
| of that atom's `:not(#\#)` specificity padding. |
| |
| Gated on `forced-colors: none` because the premise — "the control already |
| draws one" — is only true when the page controls its own colors. Under |
| Windows High Contrast the control's affordance is gone: forced-colors drops |
| `box-shadow` entirely and repaints every border in the same system color, so |
| an Astryx field looks identical resting and focused, and its trigger button |
| sets `outline: none` on itself (Selector.tsx: the wrapper is supposed to |
| carry the ring). The row outline is then the ONLY indicator that survives — |
| outline is the one focus property forced-colors preserves — and taking it |
| away leaves a keyboard user with no visible focus at all. So there, the |
| doubled ring is the lesser problem and the row keeps it. |
| |
| Fixing it at the control instead was the other option and is not better: |
| it would mean product CSS reaching past the design system into the internals |
| of Selector, Switch, SegmentedControl and Button, one forced-colors rule per |
| control, to restore what each was supposed to draw itself. */ |
| @media (forced-colors: none) { |
| .astryx-item:has(:focus-visible):not(:has(> :is(a, button):focus-visible)) { |
| outline: none; |
| } |
| } |
| |
| .maka-quote-chip-remove { display: inline-flex; flex: 0 0 auto; align-items: center; justify-content: center; border: 0; color: var(--muted-foreground); background: transparent; transition: color var(--duration-quick) var(--ease-out-strong), background var(--duration-quick) var(--ease-out-strong); } |
| .maka-quote-chip-remove:hover { color: var(--foreground); background: var(--state-hover-bg); } |
| .maka-quote-chip-remove:focus-visible { color: var(--foreground); background: var(--state-hover-bg); outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: 1px; } |
| |
| |
| /* One shape. The tone and emphasis modifiers that used to sit here — a filled |
| plate, four tinted borders, four value inks, a dimmed zero — were reachable |
| only from this package's own story; the product's single call site renders |
| the plain outlined tile. The tinted borders were also the least defensible of |
| them: the value ink already carried the status, and a second tinted edge |
| repeating it is DESIGN.md §8's One Means Rule. |
| |
| Visual System 2.0 (T4): flat. DESIGN.md §5 says default surfaces are flat and |
| depth comes from the ladder, then a line, then shadow only for something that |
| genuinely floats — a stat tile sits on the plate. It already states its edge |
| with a border, so the resting shadow was also a second separator on the same |
| edge (§4's One Means Rule). Dropping it retired --card-shadow's last consumer |
| rather than promoting it to --elevation-raised, which would have made a |
| stationary tile float harder than it did before. |
| |
| `.maka-stat-tile-value` is declared after the tile, not before: the `font:` |
| shorthand resets `font-variant-numeric` along with the four axes the role |
| names, so the tabular figures these tiles exist to align have to be set on |
| the element that carries the role, after it. */ |
| .maka-stat-tile { |
| display: flex; |
| min-width: 0; |
| flex-direction: column; |
| align-items: flex-start; |
| gap: 4px; |
| padding: 12px; |
| border: 1px solid var(--card-border-color, var(--border)); |
| border-radius: var(--radius-surface); |
| background: var(--card-bg, var(--background)); |
| } |
| .maka-stat-tile-value { |
| font: var(--maka-text-heading-1); |
| display: block; min-width: 0; max-width: 100%; overflow-wrap: anywhere; color: var(--foreground); font-variant-numeric: tabular-nums; white-space: normal; } |
| /* Label and detail share one size, so ink is the only thing ranking them: the |
| label names the metric, the detail qualifies it. Both on muted was one row |
| with no rank at all. */ |
| .maka-stat-tile-label { |
| font: var(--maka-text-supporting); |
| color: var(--foreground); letter-spacing: 0.025em; } |
| .maka-stat-tile-detail { |
| font: var(--maka-text-supporting); |
| color: var(--muted-foreground); } |
| |
| .maka-quote-chip { display: inline-flex; gap: 4px; padding-left: 8px; background: var(--foreground-alpha-6); box-shadow: inset 0 0 0 1px var(--foreground-alpha-12); } |
| .maka-quote-chip-expanded { width: 100%; max-width: 100%; align-items: flex-start; padding-block: 4px; border-radius: var(--radius-surface); } |
| /* #1879: measured 24px natural — a 20px line box plus 2 x 2px padding — and |
| `--h-control-sm` IS 24, so the pin is pixel-neutral today and load-bearing |
| the next time a leading moves. This is the collapsed state only; the |
| expanded twin above wraps to many lines and must keep growing. The chip's |
| type lives on `.maka-quote-chip-text`, which is why the derived scan needed |
| two arms rather than three to see it at all. */ |
| .maka-quote-chip-collapsed { height: var(--h-control-sm); max-width: 240px; align-items: center; padding-block: 2px; border-radius: var(--radius-pill); } |
| .maka-quote-chip-removable { padding-right: 2px; } |
| .maka-quote-chip-readonly { padding-right: 8px; } |
| .maka-quote-chip-icon { width: var(--icon-meta); height: var(--icon-meta); flex: 0 0 auto; color: var(--muted-foreground); } |
| .maka-quote-chip-icon-expanded { margin-top: 2px; } |
| .maka-quote-chip-text { |
| font: var(--maka-text-supporting); |
| min-width: 0; |
| min-height: 0; |
| flex: 1; |
| height: auto; |
| border: 0; |
| padding: 0; |
| color: var(--muted-foreground); |
| background: transparent; |
| text-align: left; |
| box-shadow: none; |
| } |
| .maka-quote-chip-text-body { |
| display: block; |
| min-width: 0; |
| max-width: 100%; |
| } |
| .maka-quote-chip-text-expanded { max-height: 128px; overflow-y: auto; overflow-wrap: break-word; white-space: pre-wrap; } |
| .maka-quote-chip-text-clipped { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| .maka-quote-chip-label { color: var(--muted-foreground); } |
| .maka-quote-chip-remove { |
| width: 16px; |
| height: 16px; |
| min-width: 16px; |
| min-height: 16px; |
| border-radius: 50%; |
| padding: 0; |
| } |
| .maka-quote-chip-remove > svg, |
| .maka-quote-chip-remove svg { width: var(--icon-meta); height: var(--icon-meta); } |
| |
| .maka-session-todo-empty { |
| margin: 0; |
| } |
| |
| .maka-message-delivery { |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| color: var(--muted-foreground); |
| font-size: 0.75rem; |
| } |
| |
| /* Under both senders, the time and delivery status are content and stay put; |
| the actions and the turn meta are chrome and surface on hover or focus of |
| the message they belong to. A live footer shows its activity row outright. |
| |
| A transparent action still takes clicks. `pointer-events: none` looks like |
| the fix and is not obviously safe: it makes the pointer fall through to the |
| ancestor whose `:hover` is what restores the button, so being clickable |
| again depends on the browser redoing hit-testing on a later mouse event. */ |
| .maka-turn-footer-action, |
| .maka-turn-footer-meta { |
| opacity: 0; |
| transition: opacity var(--duration-quick) var(--ease-out-strong); |
| } |
| .maka-chat-message:hover .maka-turn-footer-action, |
| .maka-chat-message:focus-within .maka-turn-footer-action, |
| .maka-chat-message:hover .maka-turn-footer-meta, |
| .maka-chat-message:focus-within .maka-turn-footer-meta { |
| opacity: 1; |
| } |
| .maka-user-quotes { display: flex; max-width: 100%; flex-wrap: wrap; align-items: flex-start; gap: 4px; } |
| .maka-assistant-answer-content { display: flex; width: 100%; min-width: 0; flex-direction: column; gap: 8px; } |
| .maka-subagent-session-label, |
| .maka-subagent-session-end { |
| display: flex; |
| align-items: center; |
| gap: var(--space-2); |
| } |
| .maka-subagent-session-label { width: 100%; min-width: 0; } |
| .maka-subagent-session-summary { min-width: 0; flex: 1 1 auto; } |
| .maka-subagent-session-end { color: var(--muted-foreground); } |
| .maka-turn-processing, |
| .maka-turn-status { |
| font: var(--maka-text-body); |
| display: flex; align-items: center; gap: 8px; padding-block: 2px; color: var(--muted-foreground); } |
| /* The live status is the one thing in the turn that is about to happen rather |
| than something that already did, so it gets air the blocks above it do not |
| (they sit on the column's own 8px). Hugging the last tool card read as part |
| of that card. */ |
| .maka-turn-processing { margin-top: 8px; } |
| .maka-turn-provider-retry { margin-top: 8px; } |
| .maka-turn-indicator-text { display: inline-flex; min-width: 0; align-items: center; gap: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| .maka-turn-status-label { font-weight: var(--font-weight-medium); } |
| .maka-turn-status-separator { color: var(--foreground-alpha-16); } |
| /* Tabular figures so a ticking clock changes glyphs without changing width. */ |
| .maka-turn-elapsed { display: inline-flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; } |
| .maka-turn-elapsed:empty { display: none; } |
| .maka-compaction-status { display: inline-flex; align-items: center; gap: 8px; font: var(--maka-text-body); vertical-align: top; } |
| /* #1879: an Astryx `Badge variant="yellow"` now — the warning tone it used to |
| draw by hand (hairline + 5% wash) is a variant, and the box comes with it. |
| Product CSS keeps only what is layout: it is a block-level note under the |
| bubble, so it needs its own line and the gap above it. */ |
| .maka-turn-truncation-badge { display: flex; width: fit-content; margin-top: 6px; cursor: help; } |
| /* One quiet disclosure above the answer, with no card chrome. Keep children |
| mounted so tool disclosures and streamed text retain their identities. */ |
| .maka-processing-sequence { min-width: 0; } |
| .maka-processing-summary { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| padding-block: 8px; |
| border-bottom: 1px solid var(--border); |
| color: var(--muted-foreground); |
| font: var(--maka-text-body); |
| list-style: none; |
| cursor: pointer; |
| } |
| .maka-processing-summary::-webkit-details-marker { display: none; } |
| .maka-processing-summary:hover { color: var(--foreground); } |
| .maka-processing-summary:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: calc(-1 * var(--focus-ring-width)); } |
| .maka-processing-summary > svg { flex-shrink: 0; } |
| .maka-processing-sequence[open] > .maka-processing-summary > svg { transform: rotate(90deg); } |
| .maka-processing-content { display: flex; min-width: 0; flex-direction: column; gap: 8px; padding-block: 12px 4px; } |
| .maka-deep-thinking { min-width: 0; } |
| /* Reasoning uses the same safe Markdown pipeline as answers, then lowers its |
| visual weight inside the disclosure. Body layout stays with the upstream |
| Astryx atoms (secondary color, 22px inline-start indent, no rail): the |
| #2645 inset added a 2px link-tinted border that visually outweighed the |
| muted text it framed, and its padding-top/inline-start were dead code — |
| the ejected component's higher-specificity atoms already own both. */ |
| .maka-chat-reasoning-content { white-space: pre-wrap; word-break: break-word; } |
| .maka-deep-thinking [data-maka-contract="markdown"] .astryx-markdown { |
| color: var(--muted-foreground); |
| font: var(--maka-text-body); |
| } |
| .maka-deep-thinking [data-maka-contract="markdown"] .astryx-markdown-heading, |
| .maka-deep-thinking [data-maka-contract="markdown"] strong { |
| color: color-mix(in oklch, var(--link) 28%, var(--muted-foreground)); |
| } |
| .maka-deep-thinking [data-maka-contract="markdown"] .astryx-markdown-heading { |
| font: var(--maka-text-label); |
| } |
| .maka-math { |
| max-width: 100%; |
| color: inherit; |
| } |
| .maka-math-inline { |
| display: inline; |
| } |
| .maka-math-display { |
| display: block; |
| overflow-x: auto; |
| overflow-y: hidden; |
| padding-block: 2px; |
| } |
| |
| @keyframes maka-spin { to { transform: rotate(360deg); } } |
| .maka-spin { animation: maka-spin 1s linear infinite; } |
| |
| /* --------------------------------------------------------------------------- |
| Transcript markdown rhythm — the single authority for compact prose spacing. |
| |
| Astryx's `density` only reaches the blocks Markdown renders itself. Lists are |
| delegated to the List control, and Markdown hands it a hardcoded |
| `density="compact"` in BOTH modes — so a markdown list is spaced as a |
| clickable row (4px block padding, the RFC #839 control value), not as prose. |
| The result under `density="compact"` was a broken order: list items sat 10px |
| apart while paragraphs sat 4px apart, so same-level items read as further |
| apart than different paragraphs. |
| |
| This table restores one rule — visual distance rises with semantic distance — |
| by owning every compact prose gap in one place: |
| |
| 4px list items (same level) |
| 12px blocks (paragraph, list, quote, table, code) |
| 16px section (h3-h6, and either side of an `hr`) |
| 24px chapter (h1, h2) |
| |
| Why the block rung is 12px and not the 8px this table first shipped: 8px |
| restored the ORDER the table exists to fix, but never argued its value. |
| Body leading here is 20px, so 8px put two paragraphs 0.4 of a line apart — |
| a paragraph break narrower than the line break inside a paragraph, which is |
| the one distance a reader already knows. 12px is Astryx's own paragraph |
| rhythm at document density (spacingParagraphDefault, `--spacing-3`), so this |
| is the block step returning to the design system rather than a Maka number: |
| the transcript keeps its own HEADING scale and its own list rung, and only |
| stops being denser than the design system between two paragraphs. Measured |
| in Storybook (Product/Markdown → TranscriptTurn) rather than estimated. |
| |
| It costs contrast at the top of the ladder — the section step falls from 2x |
| the block gap to 1.33x, so a heading now separates mainly on weight, size |
| and colour rather than on space. That is the price of leaving the heading |
| rungs alone, which is deliberate: heading spacing is what #1857 and this |
| table already decided, and this change is about paragraphs only. |
| |
| What holds this: __tests__/markdown-rhythm-contract.test.tsx, and it pins |
| only the two ways this table can break SILENTLY — the runtime hooks these |
| selectors need still being emitted (an Astryx rename kills every rule at |
| once, and a selector that matches nothing never complains), and the |
| ListItem control padding still being neutralized (the mechanism that |
| inverted the ladder in the first place). The values below are NOT pinned, |
| deliberately: a wrong number is visible the moment anyone looks at a |
| transcript, so it does not need a test to be noticed — which is not true of |
| either of the other two. |
| |
| Three deliberate choices: |
| |
| - Only `[data-density="compact"]`. The document mode is not broken (its |
| lists sit 10px apart against 12px paragraphs, which is already in order), |
| and the Daily Review renders through it. Fixing only the broken half keeps |
| that surface out of the blast radius. Compact is not the transcript |
| alone, though — the desktop Artifact Preview asks for it too, so it takes |
| these gaps as well. For the block rung that is the point rather than a |
| cost: 12px IS the document rhythm, so the preview moves toward Astryx's |
| default rather than further from it. The heading rules below are the ones |
| where sharing is arguable, and they say so. |
| - Scoped to Astryx's own `data-density`, not to a `.maka-turn` ancestor. |
| `themeProps()` reflects every visual prop as a data attribute for exactly |
| this ("consumers target stable data-attribute selectors"), so the rhythm |
| follows the `density` prop instead of where the markdown happens to sit. |
| Storybook then reproduces the transcript by passing the prop, which the |
| old ancestor-scoped rules could not. |
| - Top level only. `>` keeps every gap on the document's own children, so |
| blocks nested inside a list item or a blockquote keep Astryx's own compact |
| 4px instead of the block gap. That tier difference is the point, not an |
| oversight: a list item should read as one unit, so two paragraphs inside |
| it belong closer together than two paragraphs in the transcript. The |
| ladder extends downward (4px nested < 12px block) rather than inverting, |
| which is the invariant that matters. |
| |
| A gap is a relation BETWEEN two blocks, so every gap rule is an adjacent |
| sibling rule. Two consequences, both load-bearing: |
| |
| - The first block matches no gap rule at all, so it needs no reset. The |
| reset form (`> *` for the gap, `> :first-child` to take it back) looks |
| equivalent and is not: `:first-child` scores (0,4,0) against the heading |
| rules' (0,5,0), so a turn that OPENS with a heading — which is most of |
| them — kept the 24px chapter gap above its first line and pushed itself |
| off the top of the bubble. Expressing the relation directly is what makes |
| that unrepresentable rather than merely fixed. |
| - Gaps are margin-top only, on top of a blanket `margin-block: 0` that |
| clears Astryx's own margins. Nothing collapses, so the distance between |
| two blocks is the value declared here. */ |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] { |
| --md-gap-list: var(--space-1); |
| --md-gap-block: var(--space-3); |
| --md-gap-section: var(--space-4); |
| --md-gap-chapter: var(--space-6); |
| } |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] > * { |
| margin-block: 0; |
| } |
| /* `:not(dialog)` because a gap is a relation between two blocks in the document |
| flow, and a dialog is not one. Astryx's `Dialog` does not portal, so the |
| Mermaid fullscreen overlay renders as a direct child of the markdown document |
| while being `position: fixed; inset: 0` — an adjacent-sibling margin there |
| pushes the whole viewport-filling overlay off the top of the screen rather |
| than spacing anything (caught by the Mermaid E2E journey, 8px off). The |
| exclusion belongs on the side that RECEIVES the margin; an overlay sitting |
| between two blocks is fine as the `+` antecedent. */ |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] > * + *:not(dialog) { |
| margin-block-start: var(--md-gap-block); |
| } |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] > * + .astryx-markdown-heading:is([data-level="1"], [data-level="2"]) { |
| margin-block-start: var(--md-gap-chapter); |
| } |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] > * + .astryx-markdown-heading:not([data-level="1"], [data-level="2"]) { |
| margin-block-start: var(--md-gap-section); |
| } |
| /* An `hr` is the only break the author typed by hand. Left on the generic block |
| step it reads no wider than the paragraph boundary above it, so writing one |
| changes nothing — which is the same failure as the flattened heading scale, |
| in a different place. It sits at the section step because that is what it |
| separates. A following h1/h2 still wins on specificity ((0,5,0) beats |
| (0,3,1)) and keeps the chapter gap, so `hr` before a major heading is |
| unaffected. */ |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] > * + hr, |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] > hr + *:not(dialog) { |
| margin-block-start: var(--md-gap-section); |
| } |
| /* Prose list, not a control list: drop the ListItem row padding and express the |
| row rhythm as a gap, so it stays below the block gap above. */ |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] .astryx-list-item { |
| padding-block: 0; |
| } |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] .astryx-list { |
| gap: var(--md-gap-list); |
| } |
| /* Prose reads differently from chrome. Han glyphs fill the whole em box, so |
| the 20px body leading that suits a control row leaves CJK paragraphs with |
| no air between lines; 1.6 is the common CJK reading ratio. Inherited from |
| the document root: headings and fenced code declare their own leading and |
| keep it, inline code follows the line it sits in, which is what keeps that |
| line's box even. `text-autospace` puts the conventional hair space between |
| Han and Latin or digit runs when the model did not type one. */ |
| [data-maka-contract="markdown"] .astryx-markdown { |
| line-height: 1.6; |
| text-autospace: normal; |
| } |
| /* Han faces have no italic, so `*emphasis*` in Chinese renders as a synthetic |
| shear of PingFang. Chinese typography marks emphasis with a dot under each |
| character instead. Keyed on the script of the turn's own prose (MarkdownBody |
| sets `data-maka-script`), not on the document `lang`: that is the UI locale, |
| and a model answers in whatever language it was asked in. Latin prose keeps |
| its italic. */ |
| [data-maka-contract="markdown"][data-maka-script="han"] .astryx-markdown em { |
| font-style: normal; |
| text-emphasis: filled dot; |
| text-emphasis-position: under right; |
| } |
| /* Heading scale: size says "this is a heading", colour says which level. |
| Astryx's document ladder (20/18/16/14) is a page scale and an agent turn |
| emits `##` and `###` every few lines, so the transcript keeps ONE size step |
| above body — 16 for h1/h2 — and everything below at body size. Chat-first |
| clients sit exactly here: Claude Code (1.07/1.0), opencode (15/13 with h4+ |
| muted), Lody (16/14); only the document-leaning ones (Cursor 1.32/1.21, |
| Codex 1.43/1.21) go bigger, and a transcript at those sizes reads as a |
| stack of slabs rather than an answer. |
| h3 cannot be a body-size bold line in CJK, though: PingFang's Regular and |
| Semibold are close enough that it is indistinguishable from a bold |
| sentence, and a Chinese turn lost a whole level of structure. Size would |
| bring the slabs back, so h3 steps down in ink instead and shares the muted |
| tier with h4-h6 — the same axis opencode uses one level lower. `####` is |
| rare in a turn, so h3 and h4 sharing a style costs less than h3 and |
| `<strong>` sharing one. |
| |
| RISK, not just a choice: these are the only rules here that are typography |
| rather than spacing, and Astryx's density RFC (facebook/astryx#839) draws |
| the line at "density shifts heights and spacing, not typography". Keying |
| them on `data-density` is honest only while `compact` and "transcript" name |
| the same set, and they DO NOT. Two surfaces ask for compact: the transcript |
| (chat-turn.tsx) and the desktop Artifact Preview |
| (apps/desktop/src/renderer/artifact-preview.tsx, since #2506), so the |
| preview pane has been rendering `.md` files at transcript heading sizes |
| with dimmed h4-h6 ever since. Nothing scopes it out: the |
| `[data-maka-contract="markdown"]` prefix is on every MarkdownBody, so |
| `density` is the whole key. |
| |
| Left shared on purpose, not by omission. The preview is a narrow side pane |
| showing a document a few lines at a time, which is the shape the flattening |
| was designed for, and no report has called it wrong. Splitting the key — |
| moving these three rules onto a surface attribute the transcript sets and |
| the preview does not — is a visible change to the preview's typography and |
| belongs in its own change, argued and looked at on that surface rather |
| than smuggled in under a spacing token. |
| |
| So: a THIRD compact surface inherits transcript typography silently, and |
| nothing catches it. That is unguarded, and it stays unguarded knowingly — |
| the caller set spans workspaces, so the only test that could hold it is a |
| library test grepping application source. One was written and it was wrong |
| on its first commit, passing while already false. A comment that is checked |
| when these rules are read beats an assertion that lies. */ |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] .astryx-markdown-heading:is([data-level="1"], [data-level="2"]) { |
| font: var(--maka-text-heading-3); |
| } |
| [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] .astryx-markdown-heading:not([data-level="1"], [data-level="2"]) { |
| font: var(--maka-text-heading-4); |
| color: var(--muted-foreground); |
| } |
| |
| /* Astryx hands custom images no inline/block placement or spacing wrapper. |
| Session artifacts are content previews rather than sentence badges, so they |
| own a bounded block presentation; compact transcript rhythm resets the |
| top-level margin above through the shared adjacency rules. */ |
| .maka-markdown-attachment-image { |
| display: block; |
| max-width: 100%; |
| height: auto; |
| margin-block: var(--space-3) var(--space-4); |
| } |
| |
| /* Markdown code renderers are custom components so Mermaid can be loaded only |
| for settled Mermaid fences. Astryx skips its own spacing wrapper when |
| `components.code` is set, so the custom block sits directly in the document |
| flow — the compact rhythm above already carries it, and only the document |
| mode needs its margins declared here. */ |
| .maka-markdown-code { min-width: 0; } |
| /* Markdown always gives CodeBlock a structural header, keeping its copy button |
| in a real toolbar instead of overlaying the horizontal scroll viewport. */ |
| .maka-markdown-code .astryx-codeblock-header { |
| min-height: 32px; |
| padding-block: 2px; |
| padding-inline: var(--spacing-2); |
| border-block-end: 1px solid var(--border); |
| } |
| /* Astryx pulls a headered code body upward by spacing-2. That is useful when |
| the header is only a language caption, but our header is a distinct toolbar: |
| the pull clips the top padding to 4px while leaving 12px below the line. */ |
| .maka-markdown-code .astryx-codeblock [role="group"] > div { |
| margin-block-start: 0; |
| } |
| /* A one-line command, key, or token keeps Astryx's native scrolling viewport, |
| selection, keyboard behavior, and platform scrollbar. */ |
| .maka-markdown-code[data-maka-code-layout="single-line"] .astryx-codeblock [role="group"] { |
| overflow-x: auto; |
| overflow-y: hidden; |
| } |
| .maka-markdown-code[data-maka-code-layout="single-line"] .astryx-codeblock [role="group"] > div > code { |
| box-sizing: border-box; |
| display: flex; |
| align-items: center; |
| min-block-size: 40px; |
| padding-block: 0; |
| } |
| /* The CodeBlock scroll viewport is focusable but Astryx leaves it on the UA |
| outline. Give it one clipped-safe Maka ring, then stop the non-interactive |
| prose ListItem's generic focus-within ring from drawing a second outline |
| around the entire numbered-list row. */ |
| .maka-markdown-code .astryx-codeblock [role="group"]:focus-visible { |
| outline: none; |
| box-shadow: inset 0 0 0 var(--focus-ring-width) var(--focus-ring); |
| } |
| [data-maka-contract="markdown"] .astryx-list-item:has(.maka-markdown-code :focus-visible) { |
| outline: none; |
| } |
| .maka-markdown-code-default { margin-block: var(--space-3) var(--space-4); } |
| [role="document"] > .maka-markdown-code:first-child { margin-block-start: 0; } |
| [role="document"] > .maka-markdown-code:last-child { margin-block-end: 0; } |
| .maka-mermaid-diagram { |
| display: flex; |
| max-width: 100%; |
| min-height: 0; |
| flex-direction: column; |
| overflow: hidden; |
| container-type: inline-size; |
| border: 1px solid var(--border); |
| border-radius: var(--radius-surface); |
| background: var(--background); |
| } |
| .maka-mermaid-toolbar { |
| min-height: 36px; |
| flex: 0 0 auto; |
| border-bottom: 1px solid var(--border); |
| background: var(--foreground-3); |
| } |
| .maka-mermaid-title { font: var(--maka-text-supporting); color: var(--muted-foreground); } |
| .maka-mermaid-actions { display: flex; align-items: center; gap: 2px; } |
| .maka-mermaid-zoom-actions { display: flex; align-items: center; gap: 2px; } |
| .maka-mermaid-zoom-level { |
| font: var(--maka-text-code); |
| min-width: 4.5ch; |
| color: var(--muted-foreground); |
| text-align: center; |
| font-variant-numeric: tabular-nums; |
| } |
| .maka-mermaid-viewport { |
| position: relative; |
| display: grid; |
| min-width: 0; |
| align-items: center; |
| overflow: auto; |
| padding: var(--space-3); |
| cursor: default; |
| outline: none; |
| overscroll-behavior: auto; |
| touch-action: pan-y; |
| } |
| .maka-mermaid-viewport:focus-visible { box-shadow: inset 0 0 0 2px var(--focus-ring); } |
| .maka-mermaid-viewport[data-maka-mermaid-pannable="horizontal"] { cursor: grab; overscroll-behavior-x: contain; } |
| .maka-mermaid-viewport[data-maka-mermaid-pannable="vertical"], |
| .maka-mermaid-viewport[data-maka-mermaid-pannable="both"] { |
| cursor: grab; |
| overscroll-behavior: contain; |
| touch-action: none; |
| } |
| .maka-mermaid-viewport[data-maka-mermaid-panning="true"] { cursor: grabbing; user-select: none; } |
| .maka-mermaid-canvas { min-width: 0; margin-inline: auto; } |
| .maka-mermaid-svg { display: flex; width: 100%; height: 100%; min-width: 0; justify-content: center; } |
| .maka-mermaid-svg > svg { |
| display: block; |
| width: 100%; |
| max-width: none !important; |
| height: 100%; |
| overflow: visible; |
| } |
| /* Legal square (DESIGN.md §6): the expanded diagram fills its viewport with |
| margin 0 on every side, so it is full-bleed rather than a card that lost its |
| corners. The collapsed state above keeps its radius. */ |
| .maka-mermaid-diagram-expanded { |
| width: 100%; |
| height: 100%; |
| max-width: none; |
| margin: 0 !important; |
| border: 0; |
| border-radius: 0; |
| } |
| .maka-mermaid-diagram-expanded .maka-mermaid-toolbar { |
| padding-left: max(var(--space-3), var(--maka-titlebar-area-x, 0px)); |
| padding-right: calc(var(--space-1) + var(--maka-titlebar-overlay-right-width, 0px)); |
| } |
| .maka-mermaid-diagram-expanded .maka-mermaid-actions { -webkit-app-region: no-drag; } |
| .maka-mermaid-diagram-expanded .maka-mermaid-viewport { min-height: 0; flex: 1 1 auto; } |
| .maka-mermaid-dialog { -webkit-app-region: no-drag; } |
| .maka-mermaid-source { |
| max-height: min(40%, 320px); |
| flex: 0 1 auto; |
| overflow: auto; |
| padding: var(--space-1) var(--space-3) var(--space-2); |
| border-top: 1px solid var(--border); |
| } |
| .maka-mermaid-fallback { display: grid; gap: var(--space-1-5); } |
| .maka-mermaid-fallback-actions { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| gap: var(--space-2); |
| } |
| .maka-mermaid-status { |
| font: var(--maka-text-supporting); |
| color: var(--muted-foreground); |
| } |
| |
| @container (max-width: 520px) { |
| .maka-mermaid-title { display: none; } |
| } |
| |
| @container (max-width: 360px) { |
| .maka-mermaid-diagram:not(.maka-mermaid-diagram-expanded) .maka-mermaid-zoom-actions { display: none; } |
| } |
| |
| .maka-tool-output-panel { display: grid; gap: 8px; margin-top: 4px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-surface); background: var(--foreground-3); } |
| .maka-tool-output-command { |
| font: var(--maka-text-code); |
| display: block; min-width: 0; color: var(--foreground); font-variant-ligatures: none; white-space: pre-wrap; word-break: break-word; } |
| /* The command reads as the surface's header, not as the first line of the |
| output: it keeps the foreground colour the body gives up, and a hairline |
| separates the two. The rule is on the row rather than the panel so a |
| command with nothing under it (a failed launch) does not end in a stray |
| line — the panel's own 8px gap supplies the space below. */ |
| .maka-tool-output-command-row { display: flex; min-width: 0; align-items: flex-start; gap: 8px; } |
| .maka-tool-output-command-row:not(:last-child) { padding-bottom: 8px; border-bottom: 1px solid var(--border); } |
| .maka-tool-output-command-row .maka-tool-output-command { flex: 1 1 auto; } |
| .maka-tool-output-command-actions { display: flex; flex: 0 0 auto; align-items: center; gap: 2px; } |
| .maka-tool-output-command-copy { flex: 0 0 auto; } |
| .maka-tool-output-command-copy[data-copy-feedback="pending"] { cursor: progress; } |
| .maka-tool-output-command-copy[data-copy-feedback="copied"] { color: var(--link); } |
| .maka-tool-output-command-copy[data-copy-feedback="failed"] { color: var(--destructive); } |
| .maka-tool-output-body { |
| font: var(--maka-text-code); |
| max-height: 256px; margin: 0; overflow-y: auto; color: var(--muted-foreground); font-variant-ligatures: none; scroll-behavior: auto; white-space: pre-wrap; word-break: break-word; } |
| .maka-tool-output-note { |
| font: var(--maka-text-body); |
| margin: 0; color: var(--muted-foreground); } |
| .maka-tool-output-wrap { min-width: 0; overflow-wrap: anywhere; } |
| .maka-tool-output-destructive { color: var(--destructive); } |
| /* No tinted edge on the panel: the note text above already states the status in |
| red or amber, and a second tinted edge repeats it (DESIGN.md §8). |
| `.maka-tool-output-panel` keeps its --border hairline. */ |
| .maka-tool-output-warning { color: var(--warning-text); } |
| .maka-tool-output-stack { display: flex; min-width: 0; flex-direction: column; gap: 6px; margin-top: 4px; } |
| .maka-pty-shell { gap: 0; overflow: hidden; padding: 0; } |
| .maka-pty-shell-header { display: flex; min-width: 0; align-items: center; padding: 10px 12px 4px; } |
| .maka-pty-shell-title { |
| font: var(--maka-text-label); |
| color: var(--muted-foreground); } |
| .maka-pty-shell-body { display: grid; min-width: 0; gap: 8px; padding: 6px 12px; } |
| .maka-pty-shell-footer { |
| font: var(--maka-text-body); |
| display: flex; min-height: 32px; align-items: center; justify-content: flex-end; gap: 6px; padding: 4px 12px 10px; color: var(--muted-foreground); } |
| .maka-pty-terminal-body { overflow: auto; white-space: pre; word-break: normal; } |
| |
| .maka-web-result { display: grid; gap: 8px; } |
| .maka-web-result-header { display: grid; gap: 2px; } |
| .maka-web-result-header strong { |
| font: var(--maka-text-heading-4); |
| color: var(--foreground); } |
| .maka-web-result-header small, |
| .maka-web-result-list small { |
| font: var(--maka-text-body); |
| color: var(--muted-foreground); } |
| .maka-web-result-list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; } |
| .maka-web-result-list li { display: grid; gap: 2px; } |
| .maka-web-result-list a { |
| font: var(--maka-text-label); |
| color: var(--link); } |
| /* The snippet is what a reader scans; the <small> beside it is the source |
| line. Same size, so ink is the rank. */ |
| .maka-web-result-list p { |
| font: var(--maka-text-body); |
| margin: 0; color: var(--foreground); } |
| .maka-web-result-error { gap: 6px; } |
| .maka-web-result-error-message, |
| .maka-web-result-repair { |
| font: var(--maka-text-body); |
| margin: 0; } |
| .maka-web-result-error-message { color: var(--destructive); } |
| .maka-web-result-repair { color: var(--muted-foreground); } |
| .maka-tool-call-detail { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; } |
| /* A CodeBlock title (a file path, a result headline) sits at the same tier as |
| the command header beside it. Astryx hard-codes that title to the supporting |
| tier, which is 12px against the block's own 14px code — rebinding the role |
| token reaches it through inheritance, the same move chat-message.css makes |
| on the tool and reasoning rows. Only the size: `medium` on a header is |
| Astryx's intent and the surface command's foreground colour carries the same |
| weight of emphasis without it. */ |
| .maka-tool-call-detail .astryx-codeblock { |
| /* Geist Mono ligates `--`, `!==` and friends into single glyphs that sit |
| outside the cell they started in, so a `---` line looked like it had |
| escaped the block's left edge — measured, its box starts on the same x as |
| every other line. The product's own wells all disable this; Astryx's |
| CodeBlock does not, and tool output is transcript text where the literal |
| characters are the point. */ |
| font-variant-ligatures: none; |
| --text-supporting-size: var(--text-code-size); |
| /* Unitless, so it has to move with the size: left at the supporting leading |
| it would multiply the larger size into a 23px line box and give the header |
| a rhythm no other row in the panel has. */ |
| --text-supporting-leading: var(--text-code-leading); |
| } |
| /* A headerless CodeBlock floats its copy button over the viewport's top-right |
| corner, and a long unbroken first line — a maka://archive URI, a long |
| command — runs straight under it (markdown avoids this by always giving the |
| block a header toolbar; tool wells stay quiet on purpose). Reserve the |
| button's footprint with a float on the first line so wrapped text flows |
| around the corner instead of colliding. On the line div, not <code>: 100+ |
| line blocks wrap lines in content-visibility chunks whose layout containment |
| would keep an outer float from shortening their line boxes. The lengths are |
| the button's geometry, not tunables: the 28px button inset spacing-2 (8px) |
| ends 20px past the code's spacing-4 (16px) inline padding — plus an 8px gap |
| — and its bottom edge sits 36px − spacing-3 (12px) = 24px below the first |
| line's top. Tool wells never enable line numbers, so the numbered-gutter |
| ::before cannot collide here. */ |
| .maka-tool-call-detail .astryx-codeblock:not(:has(.astryx-codeblock-header)) [data-line="1"]::before { |
| content: ''; |
| float: inline-end; |
| width: 28px; |
| height: 24px; |
| } |
| /* The headered variant has the same collision inside its header row: the |
| title (often a long unbroken maka://archive URI headline) is a flex span |
| with white-space: pre and visible overflow, so it runs straight under the |
| header's copy button — and headerCompact draws no divider, so the title |
| reads as the well's first content line while colliding like an overlay. |
| Ellipsize it inside the title slot the header already sizes (flex: 1, |
| min-width: 0). display: block because a flex container will not ellipsize |
| its anonymous text; tool wells never render the collapsible chevron that |
| the flex display exists for. white-space stays `pre`, not nowrap: both |
| satisfy text-overflow's no-wrap requirement, but nowrap would collapse |
| runs of spaces — and a title is often a literal path or command where |
| `report final.txt` and `report final.txt` are different names, with the |
| title as their only display (the copy button copies the body). */ |
| .maka-tool-call-detail .astryx-codeblock-header .astryx-codeblock-title { |
| display: block; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: pre; |
| } |
| .maka-tool-output-chunk { display: contents; } |
| .maka-tool-output-chunk-stderr { color: var(--destructive); } |
| .maka-tool-output-chunk-redacted { opacity: 0.65; } |
| .maka-tool-output-redacted { display: inline; margin-left: 2px; color: var(--warning-text); } |
| .maka-sandbox-blocked-banner, |
| .maka-requires-bypass-banner { margin-bottom: 10px; } |
| .maka-sandbox-blocked-description { |
| font: var(--maka-text-supporting); |
| display: flex; flex-direction: column; gap: 4px; white-space: pre-wrap; word-break: break-word; } |
| .maka-sandbox-blocked-error { |
| font: var(--maka-text-supporting); |
| } |
| .maka-sandbox-blocked-copy { align-self: start; } |
| .maka-sandbox-blocked-copy[data-pending="true"] { cursor: progress; } |
| .maka-sandbox-blocked-copy[data-copy-feedback="copied"] { color: var(--link); } |
| /* The failed state is stated by the label ink, so the edge stays --border — |
| one status, one means (DESIGN.md §8). */ |
| .maka-sandbox-blocked-copy[data-copy-feedback="failed"] { color: var(--destructive); } |
| .maka-turn-status-icon { flex: 0 0 auto; } |
| |
| .maka-turn-host-origin { |
| font: var(--maka-text-supporting); |
| display: inline-flex; |
| width: fit-content; |
| align-items: center; |
| margin-inline: 0; |
| border-radius: var(--radius-control); |
| } |
| |
| .maka-turn-host-origin { |
| align-self: flex-end; |
| gap: var(--space-1); |
| margin-bottom: var(--space-1); |
| padding: var(--space-0-5) var(--space-1-5); |
| background: var(--foreground-5); |
| color: var(--muted-foreground); |
| } |
| |
| /* Astryx `Banner` paints the failed-turn surface; this only sets the block |
| rhythm between it and the timeline entry above it. */ |
| .maka-turn-failure-unavailable { display: block; } |
| /* Keep Banner's immediate height change and built-in chevron rotation. |
| Animating height repeatedly lays out the transcript and triggers its |
| ResizeObserver-driven scroll following; that cost grows with history. |
| Only fade the newly mounted detail, without delaying layout or removal. */ |
| .maka-turn-failure-detail { |
| margin: 0; |
| white-space: pre-wrap; |
| overflow-wrap: anywhere; |
| font: inherit; |
| color: var(--foreground); |
| animation: maka-turn-failure-enter var(--duration-fast) var(--ease-standard); |
| } |
| @keyframes maka-turn-failure-enter { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| @media (prefers-reduced-motion: reduce) { |
| .maka-turn-failure-detail { animation: none; } |
| } |
| .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); |
| opacity: 0.82; |
| } |
| |
| .maka-turn-lineage-row-reverse { margin-top: var(--space-1); } |
| |
| .maka-turn-lineage-badge { |
| border: 0; |
| 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"] { |
| background: var(--color-accent-muted); |
| color: var(--foreground); |
| } |
| |
| .maka-turn-lineage-badge[data-direction="reverse"] { |
| background: oklch(from var(--brand-deep) l c h / 0.06); |
| color: oklch(from var(--brand-deep) calc(l - 0.04) c h); |
| } |
| |
| .maka-turn-footer { |
| min-height: var(--size-element-sm, 28px); |
| margin-top: var(--space-0-5); |
| } |
| |
| .maka-turn-footer .maka-turn-processing, |
| .maka-turn-footer .maka-turn-provider-retry { margin-top: 0; } |
| |
| .maka-overlay-preview { |
| font: var(--maka-text-supporting); |
| max-height: 180px; |
| margin: var(--space-1) 0 0; |
| overflow: auto; |
| font-variant-ligatures: none; |
| white-space: pre-wrap; |
| word-break: break-word; |
| } |
| .maka-overlay-close { justify-self: end; } |
| .maka-tool-diff, |
| .maka-tool-terminal { |
| display: grid; |
| gap: 0; |
| padding: 0; |
| border-radius: var(--radius-surface); |
| background: var(--background); |
| box-shadow: var(--ring-soft); |
| white-space: normal; |
| } |
| .maka-tool-diff-paths, |
| .maka-tool-terminal-head { |
| font: var(--maka-text-supporting); |
| display: flex; |
| flex-wrap: wrap; |
| gap: var(--space-1-5); |
| padding: var(--space-1) var(--space-2); |
| border-bottom: 1px solid var(--border); |
| background: var(--foreground-2); |
| } |
| .maka-tool-terminal-head { align-items: center; font-variant-ligatures: none; } |
| .maka-tool-diff-paths code { color: var(--muted-foreground); background: transparent; } |
| .maka-tool-diff-body { |
| font: var(--maka-text-supporting); |
| max-height: 320px; |
| margin: 0; |
| padding-block: var(--space-1); |
| overflow: auto; |
| font-variant-ligatures: none; |
| white-space: pre; |
| word-break: normal; |
| } |
| /* Only the chat consumer moved into `.maka-tool-output-panel`, where the diff |
| sits beside a command on the code tier and a 12px body next to 14px reads as |
| a different kind of text. `apps/desktop`'s artifact pane still draws this as |
| a standalone card at the supporting tier its container asks for, so the tier |
| belongs to the panel, not to the shared class. */ |
| .maka-tool-output-panel .maka-tool-diff-body { |
| font: var(--maka-text-code); |
| /* The `font` shorthand resets `font-variant-ligatures`, and this rule |
| outranks the `none` the shared class declares — so the panel's diff was |
| the one code surface in the app rendering `===` as `⩶` and `=>` as `⇒`. |
| Every other `font: var(--maka-text-code)` rule restates it for the same |
| reason; this one had not. */ |
| padding-block: 0; |
| font-variant-ligatures: none; |
| } |
| .maka-tool-diff-line { display: block; padding-inline: var(--space-2); white-space: pre; } |
| .maka-tool-diff-gutter { |
| display: inline-block; |
| box-sizing: content-box; |
| text-align: right; |
| padding-right: var(--space-2); |
| margin-right: var(--space-1); |
| border-right: 1px solid var(--border); |
| color: var(--muted-foreground); |
| user-select: none; |
| } |
| /* The marker column: two character cells, the sign and a blank, so an |
| addition's code starts in the same place as the context line above it and |
| the sign is not crowding the first token. `ch` rather than a px gap because |
| this sits in a monospace body — one more cell keeps the code on the same |
| grid the file itself is written on, where 4px would leave it between two. |
| The column keeps the line's tint colour — the sign is what the colour is |
| naming — while `.maka-tool-diff-code` hands the code back to syntax |
| colouring. Every row gets the cell, including the markerless ones: a |
| `\ No newline at end of file` that hugged column 0 while the code beside it |
| did not was the same misalignment on a different row. */ |
| .maka-tool-diff-marker { |
| display: inline-block; |
| width: 2ch; |
| } |
| /* Neutral base under the `astryx-token-*` spans. Without it an added line's |
| uncoloured text would stay green and a deleted line's red, which is the |
| flat single-tint reading the tokens exist to break up; the row's background |
| tint and its marker carry add/del on their own. |
| |
| Context lines read at full foreground, which is deliberate rather than |
| collateral: once a context line's keywords and strings are syntax-coloured, |
| dimming only the text between them reads as damage, not as de-emphasis. The |
| row's flat background is what says "unchanged" now. */ |
| .maka-tool-diff-code { color: var(--foreground); } |
| .maka-tool-diff-line[data-line="meta"] .maka-tool-diff-code { color: inherit; } |
| .maka-tool-diff-line[data-line="add"] { background: var(--color-success-muted); color: var(--success-text); } |
| .maka-tool-diff-line[data-line="del"] { background: var(--color-error-muted); color: var(--destructive); } |
| .maka-tool-diff-line[data-line="hunk"] { |
| font: var(--maka-text-heading-5); |
| background: oklch(from var(--accent) l c h / 0.08); color: var(--muted-foreground); } |
| .maka-tool-diff-line[data-line="meta"] { color: var(--muted-foreground); } |
| /* Full ink, matching `.maka-tool-diff-code` above: a context line is the code |
| the change is read against, and the marker column is the only part this rule |
| still paints on its own. Meta and hunk rows are the ones that recede. */ |
| .maka-tool-diff-line[data-line="ctx"] { color: var(--foreground); } |
| .maka-tool-terminal-cwd { color: var(--muted-foreground); background: transparent; } |
| .maka-tool-terminal-cmd { |
| font: var(--maka-text-heading-5); |
| min-width: 0; flex: 1 1 auto; overflow: hidden; background: transparent; color: var(--foreground); text-overflow: ellipsis; white-space: nowrap; } |
| /* Pinned like every other pill in the scan, even though the whole |
| `.maka-tool-terminal-*` family is currently unreachable: |
| `previewVariants({part: 'terminal-*'})` has no call site — `TerminalPreview` |
| renders `maka-tool-output-stack` / `ShellOutputBody` / `ToolCodeBlock` |
| instead. Deleting the family is its own change. */ |
| .maka-tool-terminal-exit { |
| font: var(--maka-text-heading-5); |
| height: var(--h-control-sm); display: inline-flex; align-items: center; white-space: nowrap; padding: 1px var(--space-1-5); border-radius: var(--radius-pill); background: var(--foreground-5); color: var(--muted-foreground); letter-spacing: var(--tracking-wide); } |
| .maka-tool-terminal-exit[data-ok="true"] { background: var(--color-success-muted); color: var(--success); } |
| .maka-tool-terminal-exit[data-ok="false"] { background: var(--color-error-muted); color: var(--destructive); } |
| .maka-tool-terminal-empty { |
| font: var(--maka-text-supporting); |
| margin: 0; padding: var(--space-2); color: var(--muted-foreground); font-style: italic; } |
| .maka-tool-terminal-stream { |
| font: var(--maka-text-supporting); |
| max-height: 180px; margin: 0; padding: var(--space-1-5) var(--space-2); overflow: auto; font-variant-ligatures: none; white-space: pre-wrap; word-break: break-word; } |
| .maka-tool-terminal-stream[data-stream="stdout"] { color: var(--foreground); } |
| .maka-tool-terminal-stream[data-stream="stderr"] { border-top: 1px solid var(--border); background: var(--color-error-muted); color: var(--destructive); } |
| .maka-tool-terminal-truncated-note { |
| font: var(--maka-text-supporting); |
| display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: var(--space-1-5) var(--space-2); border-top: 1px solid var(--border); background: var(--color-warning-muted); color: var(--foreground); } |
| .maka-tool-terminal-truncated-note > span { min-width: 0; } |
| .maka-tool-terminal-copy { flex: 0 0 auto; } |
| .maka-tool-terminal-copy[data-pending="true"] { cursor: progress; } |
| .maka-tool-terminal-copy[data-copy-error="true"] { border-color: var(--color-error-muted); color: var(--destructive); } |
| |
| .maka-web-search-preview { display: grid; gap: var(--space-2); padding: var(--space-2-5) var(--space-3); border: 1px solid var(--foreground-10); border-radius: var(--radius-surface); background: var(--foreground-3); } |
| .maka-web-search-preview > header { display: flex; flex-direction: column; gap: var(--space-0-5); padding-bottom: var(--space-1-5); border-bottom: 1px solid var(--foreground-10); } |
| .maka-web-search-preview > header strong { |
| font: var(--maka-text-heading-4); |
| color: var(--foreground); } |
| .maka-web-search-preview > header small, |
| .maka-web-search-preview li small { |
| font: var(--maka-text-supporting); |
| color: var(--muted-foreground); letter-spacing: var(--tracking-wider); text-transform: uppercase; } |
| .maka-web-search-preview ul { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; } |
| .maka-web-search-preview li { display: grid; gap: var(--space-0-5); padding-block: var(--space-2); border-top: 1px solid var(--foreground-5); } |
| .maka-web-search-preview li:first-child { padding-top: 0; border-top: 0; } |
| .maka-web-search-preview a { |
| font: var(--maka-text-heading-4); |
| color: var(--foreground); text-decoration: none; } |
| .maka-web-search-preview a:hover, |
| .maka-web-search-preview a:focus-visible { text-decoration: underline; } |
| .maka-web-search-preview li p { |
| font: var(--maka-text-supporting); |
| margin: 0; color: var(--muted-foreground); white-space: pre-wrap; word-break: break-word; } |
| /* The tinted status surface is Astryx's token; the edge around it is --border, |
| because the fill and the message ink already state the error (DESIGN.md §8). |
| Its copy sits on the tint, so it reads at primary ink (§3). */ |
| .maka-web-search-error { background: var(--color-error-muted); border-color: var(--border); } |
| .maka-web-search-error-message, |
| .maka-web-search-error-repair { |
| font: var(--maka-text-supporting); |
| margin: 0; white-space: pre-wrap; word-break: break-word; } |
| .maka-web-search-error-message { color: var(--destructive-text); } |
| .maka-web-search-error-repair { color: var(--foreground); } |
| |
| .maka-load-tool-preview { |
| display: grid; |
| grid-template-columns: var(--space-8) minmax(0, 1fr); |
| gap: var(--space-2-5); |
| align-items: start; |
| margin: var(--space-1) 0 0; |
| padding: var(--space-2) var(--space-2) var(--space-2-5); |
| } |
| .maka-load-tool-preview p { margin: 0; } |
| .maka-load-tool-icon { |
| display: grid; |
| width: var(--space-8); |
| height: var(--space-8); |
| place-items: center; |
| border-radius: var(--radius-element); |
| color: var(--muted-foreground); |
| background: var(--foreground-5); |
| } |
| .maka-load-tool-summary { |
| min-width: 0; |
| padding-top: var(--space-0-5); |
| } |
| .maka-load-tool-title { |
| font: var(--maka-text-heading-5); |
| } |
| .maka-load-tool-description { |
| font: var(--maka-text-supporting); |
| margin-top: var(--space-0-5) !important; |
| color: var(--muted-foreground); |
| overflow-wrap: anywhere; |
| } |
| .maka-load-tool-count { |
| font: var(--maka-text-supporting); |
| display: flex; |
| flex-wrap: wrap; |
| align-items: center; |
| gap: var(--space-1); |
| margin-top: var(--space-1) !important; |
| color: var(--muted-foreground); |
| } |
| .maka-load-tool-separator { |
| width: 3px; |
| height: 3px; |
| border-radius: 50%; |
| background: currentColor; |
| opacity: 0.55; |
| } |
| .maka-load-tool-technical { |
| grid-column: 1 / -1; |
| margin-left: calc(var(--space-8) + var(--space-2-5)); |
| color: var(--muted-foreground); |
| } |
| .maka-load-tool-technical > summary { |
| width: fit-content; |
| padding: var(--space-0-5) 0; |
| font: var(--maka-text-supporting); |
| cursor: pointer; |
| } |
| .maka-load-tool-technical > summary:focus-visible { |
| outline: none; |
| box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring); |
| border-radius: var(--radius-control); |
| } |
| .maka-load-tool-technical dl { |
| display: grid; |
| grid-template-columns: max-content minmax(0, 1fr); |
| gap: var(--space-1) var(--space-2); |
| margin: var(--space-1) 0 0; |
| padding-top: var(--space-2); |
| border-top: 1px solid var(--border-soft); |
| font: var(--maka-text-supporting); |
| } |
| .maka-load-tool-technical:not([open]) > dl { |
| display: none; |
| } |
| .maka-load-tool-technical dt { |
| color: var(--muted-foreground); |
| } |
| .maka-load-tool-technical dd { |
| min-width: 0; |
| margin: 0; |
| color: var(--muted-foreground); |
| } |
| .maka-load-tool-technical code { |
| font: var(--maka-text-code); |
| white-space: pre-wrap; |
| overflow-wrap: anywhere; |
| } |
| |
| /* Turn-footer action rings go inset (T5 geometry probe): the Astryx chat |
| layout's scrollport clips an outward ring on the first visible row's |
| actions. Same idiom as the titlebar strip and workbar launcher. The |
| layout's own scroll-to-bottom control has the same clip inside vendor |
| DOM — upstream item, not fixable from product CSS. */ |
| .maka-turn-footer-action:focus-visible { |
| outline: none; |
| box-shadow: inset 0 0 0 var(--focus-ring-width) var(--focus-ring); |
| } |
| |
| .maka-message-status-time { display: inline-flex; align-items: center; gap: 4px; } |
| |
| .maka-choice-panel { outline: none; } |
| |
| .maka-choice-hint { margin: 8px 0; } |