| /* Shell layout — app frame, panels, topbar rail, main column, workspace top actions. */ |
| .appFrame { |
| font: var(--maka-text-body); |
| position: relative; |
| height: 100dvh; |
| width: 100%; |
| padding: 0; |
| /* PR-APPFRAME-FILL-0: `#root` (index.html) is `display:flex; |
| align-items:center; justify-content:center` so the preload |
| skeleton (`.maka-preload`, rendered before React mounts) sits |
| centered. Once React mounts, `.appFrame` is that flex container's |
| only child, and without an explicit main-axis size a flex item |
| shrinks to its content width and gets centered — so on the |
| first-run hero (onboarding content max-content ~680px) the whole |
| app frame floated mid-window with large left/right gutters, while |
| long-message sessions filled the width. Pin `width:100%` so the |
| frame always spans the window regardless of inner content's |
| max-content. The preload centering is unaffected (it's the only |
| child at that point and still centers). */ |
| /* The backplate under the preload skeleton and behind the shell before it |
| mounts. AppShell paints both columns over this, so it is not a shell |
| surface — it is what the window shows when there is no shell yet. */ |
| background: var(--surface-canvas); |
| color: var(--foreground); |
| box-sizing: border-box; |
| } |
| |
| .app { |
| height: 100%; |
| } |
| |
| /* Astryx AppShell owns the column layout AND the column materials: on |
| `variant="elevated"` it paints the nav column --color-background-body and the |
| content column --color-background-surface, both of which makaTheme.ts points |
| back at the product palette. Window chrome is a transparent absolute |
| hit-test overlay on the frame — not an AppShell topNav row — so both columns |
| paint to the window top (Codex / Claude Desktop / Cursor). */ |
| .maka-shell-astryx { |
| width: 100%; |
| min-width: 0; |
| min-height: 0; |
| overflow: hidden; |
| } |
| |
| /* WAWQAQ msg `9f313715` (2026-08-03): the content column is a rounded floating |
| plate flush to the sidebar — its edge is the ONE boundary on that seam. The |
| theme's full-height sidenav hairline (`.astryx-app-shell-sidenav.elevated` |
| in the built maka.css) ran on the same seam, so beside the plate's top-left |
| corner radius both lines showed and read as a doubled border. Drop it in both |
| collapse states — column separation is tonal (the plate's fill against the |
| canvas), per DESIGN.md's One Working Plane rule. Override lives here because |
| maka.css is a generated artifact (`npm run astryx:theme`), not a hand-edited |
| file. |
| |
| Continuous Collapse used to repaint the collapsed rail with |
| --color-background-surface here as well, so the traffic lights would sit on |
| one surface instead of straddling a column edge narrower than they are. That |
| was written while AppShell's own surface was still the content column's |
| material; `.astryx-layout-content` below now paints --agents-layout-bg |
| (== --surface-canvas == --color-background-body), so the two columns already |
| share one material and the repaint was the only thing breaking the band. In |
| light mode --color-background-surface happens to equal the floating plate's |
| fill, which hid it; in dark mode the plate lifts 0.035L off --background and |
| the rail became a THIRD tone (measured L 0.205 against the canvas's 0.18), |
| painting a 49px slab that ran to the window top past the plate's 4px gutter |
| and under the light cluster. The rail now wears its column's material in both |
| states, which is what "one surface" meant in the first place. */ |
| .maka-shell-astryx .astryx-app-shell-sidenav { |
| border-inline-end-color: transparent; |
| } |
| |
| |
| /* The SIDENAV's resize handle is the one control positioned against the column |
| BOX rather than flowing inside it (Astryx gives it `position:absolute; top:0; |
| inset-block-end:0`), so the columns' `padding-top` clearance does not move it |
| and its top 36px lands under the chrome strip. The strip is the topmost hit |
| surface across the whole band, so that part of the handle cannot be grabbed at |
| all — pointer-down there reaches the OS as a window drag instead. Start the |
| handle below the strip; `inset-block-end: 0` still takes it to the bottom. |
| |
| Keyed on the sidenav handle's own testid, not on the class: every Astryx |
| ResizeHandle carries that class, and a page's own inspector handle is a |
| `position: relative` flex item, so the same `top` shoved its divider 36px |
| down the page — it started below the header's rule instead of meeting it. */ |
| .maka-shell-astryx [data-testid="astryx-sidenav-resize-handle"] { |
| top: var(--h-titlebar); |
| } |
| |
| /* The canvas the floating plate sits on. Keyed on the AppShell's own |
| `#astryx-app-shell-main`, NOT on `.astryx-layout-content`: that class is on |
| EVERY Astryx LayoutContent, so as a descendant selector it also reached the |
| one a page renders inside the plate and painted the darker canvas back over |
| it, edge to edge. In light mode that was a 2.5% step nobody noticed; in dark |
| it repainted the whole page the sidebar's colour and the two columns fused. |
| The id is the vendor's own hook for this region and cannot be nested. */ |
| .maka-shell-astryx #astryx-app-shell-main { |
| background: var(--agents-layout-bg); |
| } |
| |
| .maka-shell-astryx .maka-panel-detail { |
| width: calc(100% - var(--agents-content-area-gap)); |
| height: calc(100% - 2 * var(--agents-content-area-gap)); |
| margin: var(--agents-content-area-gap) var(--agents-content-area-gap) var(--agents-content-area-gap) 0; |
| min-width: 0; |
| min-height: 0; |
| display: flex; |
| flex-direction: column; |
| /* This element frames; it does not paint. What the user reads as "the content |
| area" is the column inside it, and beside that column the workbar is a |
| second plate of the same kind. The canvas showing between them is the seam, |
| which is the same seam this frame's own margin draws on its other three |
| sides — the shell has exactly one way of separating surfaces, and it is a |
| gap, not a tone step and not a rule. |
| |
| `clip`, not `hidden`: a frame is never a scrollport. Nothing overflows it |
| today, and the one thing that ever did — a column hung above this box to |
| reach its top edge — scrolled the whole frame up by the clearance the first |
| time anything inside took focus. */ |
| overflow: clip; |
| /* How far each plate inside has to start below the absolute chrome strip. |
| Declared here because the frame is what the strip sits over, consumed by |
| every plate that stands in it — the conversation column and the workbar |
| both. One number, no plate reaching outside its own box to get it. */ |
| --maka-plate-titlebar-clearance: calc(var(--h-titlebar) - var(--agents-content-area-gap)); |
| box-sizing: border-box; |
| } |
| |
| /* The conversation plate. Every view renders into this column, so this is where |
| the content surface actually is. */ |
| .maka-shell-astryx .mainColumn { |
| border-radius: var(--radius-modal); |
| background: var(--agents-content-area-bg); |
| padding-top: var(--maka-plate-titlebar-clearance); |
| box-sizing: border-box; |
| } |
| |
| /* Same top clear for the sidebar column so list/nav content sits below the |
| traffic-light / left chrome cluster while the transparent plate shows. */ |
| .maka-shell-astryx .maka-session-panel { |
| padding-top: var(--h-titlebar); |
| box-sizing: border-box; |
| } |
| |
| /* Collapse/expand easing. The declaration has to sit on an element that lives |
| across the toggle, and SideNav's own root is not one: expanded it wraps the |
| <nav> in a div for the overlay resize handle, collapsed it renders the bare |
| <nav>, so React unmounts and remounts that subtree. A transition on the nav |
| has nothing to interpolate from — measured live, the very first frame after |
| the click is already 48px across all 144 sampled frames. `.maka-sidenav-motion` |
| is the product's own wrapper outside SideNav, so it is the same node before |
| and after and its width really does animate. |
| |
| The wrapper is therefore the width authority while the ease runs, and its |
| child must follow rather than assert its own. `width: 100% !important` is |
| load-bearing in the COLLAPSED direction: SideNav's `rootCollapsed` is a |
| StyleX atom padded to specificity 0-4-0 (`.xhgvbfk:not(#\#)` four times over) |
| against this rule's 0-2-0, so without the flag the nav would snap to 48px |
| while the wrapper was still easing toward it. Expanded, SideNav's inline |
| width sits on the <nav>, which is wrapped one level deeper by the resize |
| handle's container, so it is never what this rule is competing with. |
| |
| Only a pointer drag suppresses the ease. A standing transition would catch the |
| width the resize handle feeds back on every pointer move, restarting the ease |
| each time so the rail trails the cursor. The gate asks the component that owns |
| the answer: Astryx marks its handle `data-resizing` while a drag is in flight |
| and clears it on pointer-up. The handle is a descendant of the wrapper (it is |
| the nav's sibling one level in), so plain `:has()` reads it. |
| |
| Separator focus is deliberately NOT part of the gate — it was, and it left |
| the ease dead for every collapse after the handle had once been touched. */ |
| .maka-shell-astryx .maka-sidenav-motion { |
| display: flex; |
| min-width: 0; |
| min-height: 0; |
| /* The nav sizes itself against a definite height (`.maka-session-panel` |
| is `height: 100%`); without one here the wrapper grows to the unclipped |
| content and the footer leaves the window — the geometry |
| e2e/sidebar-geometry.spec.ts locks. */ |
| height: 100%; |
| width: var(--maka-sidenav-width); |
| transition: width var(--duration-large) var(--ease-out-strong); |
| } |
| |
| /* Same source as Astryx's own `rootCollapsed` width, so the rail cannot drift |
| from the value SideNav lands on when the ease finishes. Keyed off the shell's |
| own collapsed flag — the wrapper carried a second copy of it until this rule |
| read the one `.appFrame` already publishes and the hairlines already use. */ |
| .appFrame[data-sidebar-state='collapsed'] .maka-sidenav-motion { |
| width: var(--spacing-12); |
| } |
| |
| .maka-shell-astryx .maka-sidenav-motion:has([data-resizing]) { |
| transition: none; |
| } |
| |
| .maka-shell-astryx .maka-sidenav-motion > * { |
| /* !important: beats SideNav's inline width — see the note above. */ |
| width: 100% !important; |
| min-width: 0; |
| } |
| |
| .maka-panel { |
| min-width: 0; |
| min-height: 0; |
| overflow: hidden; |
| } |
| |
| /* The window's only drag surface, and the only `-webkit-app-region: drag` in |
| the app. It is a transparent absolute overlay — pure hit-test chrome — so |
| column surfaces paint through it. Action clusters carve themselves out with |
| `no-drag`. Chromium builds drag regions in document order: this node must |
| precede the columns so later no-drag controls can subtract from it. |
| |
| This replaced an arrangement where every container that happened to overlap |
| the titlebar declared its own drag region and then reserved room for its |
| neighbours with a hand-summed `margin-left`/`margin-right` ruler. Those |
| rulers encoded "how many titlebar buttons exist right now" — state-dependent |
| when a third titlebar button (新任务) only appeared while the sidebar was |
| collapsed (that button is gone now; 新任务 stays on the SideNav rail). The |
| sidebar's strip reserved room for two, the third button landed in the space |
| nobody reserved, and clicks on it reached the OS as window drags. |
| |
| The inset keeps the drag rect off the window frame on the three sides it |
| touches, so the OS keeps its resize corridor there. It is DEFENSIVE, not |
| demonstrated: this area's other bug (`af681c1`, msg `5b85fdb1`) was a window |
| that could not be resized from its edges, and no automated check in this repo |
| can exercise the OS hit test to prove whether 0 would also be safe. 4px of a |
| 36px titlebar is not a cost worth arguing about; a P0 nobody can reproduce in |
| CI is. |
| |
| The gutters are applied as plain padding, NOT as `calc(gutter - margin)`. Both |
| are safe-AREA measurements, so the box's 4px inset just yields 4px more |
| clearance than the minimum. Subtracting it would restate the inset in two more |
| places to keep the clusters at an exact pixel — which is the habit this whole |
| change exists to remove. */ |
| .maka-window-titlebar { |
| position: absolute; |
| z-index: var(--z-titlebar); |
| top: var(--maka-window-resize-edge); |
| left: var(--maka-window-resize-edge); |
| right: var(--maka-window-resize-edge); |
| height: calc(var(--h-titlebar) - var(--maka-window-resize-edge)); |
| min-height: calc(var(--h-titlebar) - var(--maka-window-resize-edge)); |
| box-sizing: border-box; |
| display: flex; |
| align-items: center; |
| gap: var(--space-2); |
| /* No paint: column surfaces under this strip own the color (canvas left, |
| --background right). Windows titleBarOverlay still samples --background |
| for the OS caption strip on the content side. */ |
| background: transparent; |
| /* One gutter rule per side: our own `--space-6` of breathing room, or the |
| platform's native-control safe area when that is wider. Nothing here knows |
| which platform it is on or how wide any control is — `max()` picks whichever |
| the OS reports (macOS: traffic lights on the left; Windows: caption buttons |
| on the right; Linux: neither, so the design floor stands). */ |
| padding-left: max(var(--space-6), var(--maka-titlebar-area-x)); |
| padding-right: calc(var(--space-6) + var(--maka-titlebar-overlay-right-width)); |
| -webkit-app-region: drag; |
| } |
| |
| /* Drag regions are hit-tested from element rects, and the top layer is invisible |
| to that pass: a `showModal()` dialog paints over the titlebar, but its controls |
| inside the titlebar rect still reach the OS as window drags. */ |
| dialog:modal { |
| -webkit-app-region: no-drag; |
| } |
| |
| .maka-shell-topbar-rail { |
| display: flex; |
| align-items: center; |
| gap: var(--space-2); |
| opacity: 1; |
| pointer-events: auto; |
| transform: translateX(0); |
| transition: |
| opacity var(--duration-quick) var(--ease-out-strong), |
| transform var(--duration-base) var(--ease-out-strong); |
| -webkit-app-region: no-drag; |
| /* Match SideNav unselected icon ink (secondary), not ghost Button primary. */ |
| color: var(--foreground-secondary); |
| } |
| |
| .mainColumn { |
| min-width: 0; |
| min-height: 0; |
| height: 100%; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| background: transparent; |
| } |
| |
| .mainColumn[data-home-surface="true"] { |
| align-content: stretch; |
| padding-bottom: 0; |
| } |
| |
| /* Right-hand cluster of the titlebar row. `margin-left: auto` is the whole |
| positioning rule — it used to be absolutely positioned against the detail |
| panel, which is why the chat header had to reserve its width by hand. Which |
| views render it at all is decided in app-shell.tsx, not by a `display: none` |
| override reaching in from `[data-agents-view]`. */ |
| .maka-workspace-top-actions { |
| -webkit-app-region: no-drag; |
| margin-left: auto; |
| display: inline-flex; |
| align-items: center; |
| gap: var(--space-1-5); |
| color: var(--foreground-secondary); |
| } |
| |
| /* Titlebar chrome glyph ink is set where the glyph is: `ChromeIcon` |
| (app-shell-chrome-actions.tsx) renders Astryx Icon with `color="secondary"`, |
| and Icon puts `color: var(--color-icon-secondary)` on the svg ITSELF. A |
| button-level rule cannot reach past that — a child's own `color` always beats |
| an inherited one — so the ghost variant's primary text never applied to these |
| glyphs and the override that used to sit here corrected nothing. It also |
| over-reached onto button label text, which these buttons happen not to have. |
| |
| `.maka-titlebar-action` needs no `no-drag` of its own: every one of these |
| buttons lives inside `.maka-shell-topbar-rail` or |
| `.maka-workspace-top-actions`, and each of those carves out a rect covering |
| its children. A per-button rule (plus a `*` descendant rule for the icons) |
| only duplicated that. */ |