Skip to content

refactor: adopt official Astryx desktop shell - #1732

Merged
jackwener merged 5 commits into
mainfrom
refactor/slice11-official-astryx-shell
Aug 1, 2026
Merged

refactor: adopt official Astryx desktop shell#1732
jackwener merged 5 commits into
mainfrom
refactor/slice11-official-astryx-shell

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace the custom two-column desktop frame and duplicate sidebar resize path with Astryx AppShell and SideNav public composition
  • render shell navigation and session history with SideNavItem, List, and TreeList while preserving product routing, session actions, and native rename-input keyboard behavior
  • reclassify the workspace workbar with Astryx Toolbar and TabList, and update the real shell Storybook and Electron geometry contracts
  • make the existing reminder-menu deferred edit path seed Astryx Dialog focus capture in the same frame, without adding a parallel focus-restoration authority
  • leave shared Tabs, Item, Empty, Spinner, and package index authorities unchanged

Verification

  • npm --workspace @maka/ui test — 296 passed
  • npm --workspace @maka/desktop run typecheck
  • npm --workspace @maka/desktop test — 2014 passed
  • npm --workspace @maka/desktop run build-storybook
  • npm --workspace @maka/desktop run smoke:storybook — 27 story render/play checks passed
  • npm --workspace @maka/desktop run e2e — 93 passed
  • GitHub CI — typecheck, test, E2E, and alignment audit passed

Review focus

  • SideNav owns collapse, scrolling, and the 180–480 px resize boundary; Maka retains only the controlled persisted width and collapsed state.
  • TreeList owns project expansion. Active project roots start expanded and the archived-project root starts collapsed; the removed four-item preview path is not retained in parallel.
  • The browser workbar tab is omitted while unavailable because Astryx 0.1.9 Tab has no disabled public prop; browser runtime state remains Maka-owned.
  • Linux gives the official SideNav and detail surface independent 0–10 px bottom insets; the computed-style contract checks containment and product-sized gaps instead of forcing the removed custom shell geometry.

@jackwener
jackwener merged commit 2770730 into mainAug 1, 2026
5 of 6 checks passed
@jackwener
jackwener deleted the refactor/slice11-official-astryx-shell branch August 1, 2026 03:40
Astro-Han added a commit that referenced this pull request Aug 2, 2026
Nothing draws a line between the session rail and the content column, by
design: Astryx ships a divider only on `AppShell variant="section"` and Maka
runs "surface" (#1732). The separation is meant to come from the material —
`--surface-canvas` behind the rail, `--background` on the content — which is
exactly what the token block has claimed since the gray-plate/white-card model
landed.
The step was too small to deliver it. `l - 0.024` resolves to #f7f7f7 against
white: six sRGB levels, below what reads as a second surface on most displays.
Until now nobody saw the miss, because Astryx painted its own opaque surface
over the frame plate; this branch makes the shell transparent so the plate
finally shows, and what showed was almost nothing.
0.045 is not a taste pick. It is the surface→body step Astryx itself ships
(#ffffff L1.000 → #f1f1f1 L0.958), and it puts light mode within a hair of the
dark step (0.065) this file has carried all along. Dark stays where it is —
it was tuned against a dark plate, reads fine, and is not the boundary anyone
reported losing.
Not variant="section": a hairline plus a wash is two mechanisms for one job,
and section's divider is unconditional, so it would draw a rule down the 48px
icon rail — the same orphan line this branch just removed above Settings.
Astro-Han added a commit that referenced this pull request Aug 2, 2026
Nothing draws a line between the session rail and the content column, by
design: Astryx ships a divider only on `AppShell variant="section"` and Maka
runs "surface" (#1732). The separation is meant to come from the material —
`--surface-canvas` behind the rail, `--background` on the content — which is
exactly what the token block has claimed since the gray-plate/white-card model
landed.
The step was too small to deliver it. `l - 0.024` resolves to #f7f7f7 against
white: six sRGB levels, below what reads as a second surface on most displays.
Until now nobody saw the miss, because Astryx painted its own opaque surface
over the frame plate; this branch makes the shell transparent so the plate
finally shows, and what showed was almost nothing.
0.045 is not a taste pick. It is the surface→body step Astryx itself ships
(#ffffff L1.000 → #f1f1f1 L0.958), and it puts light mode within a hair of the
dark step (0.065) this file has carried all along. Dark stays where it is —
it was tuned against a dark plate, reads fine, and is not the boundary anyone
reported losing.
Not variant="section": a hairline plus a wash is two mechanisms for one job,
and section's divider is unconditional, so it would draw a rule down the 48px
icon rail — the same orphan line this branch just removed above Settings.
Astro-Han added a commit that referenced this pull request Aug 2, 2026
Nothing draws a line between the session rail and the content column, by
design: Astryx ships a divider only on `AppShell variant="section"` and Maka
runs "surface" (#1732). The separation is meant to come from the material —
`--surface-canvas` behind the rail, `--background` on the content — which is
exactly what the token block has claimed since the gray-plate/white-card model
landed.
The step was too small to deliver it. `l - 0.024` resolves to #f7f7f7 against
white: six sRGB levels, below what reads as a second surface on most displays.
Until now nobody saw the miss, because Astryx painted its own opaque surface
over the frame plate; this branch makes the shell transparent so the plate
finally shows, and what showed was almost nothing.
0.045 is not a taste pick. It is the surface→body step Astryx itself ships
(#ffffff L1.000 → #f1f1f1 L0.958), and it puts light mode within a hair of the
dark step (0.065) this file has carried all along. Dark stays where it is —
it was tuned against a dark plate, reads fine, and is not the boundary anyone
reported losing.
Not variant="section": a hairline plus a wash is two mechanisms for one job,
and section's divider is unconditional, so it would draw a rule down the 48px
icon rail — the same orphan line this branch just removed above Settings.
Astro-Han added a commit that referenced this pull request Aug 2, 2026
…rail (#1876)
* fix(desktop): paint column surfaces through a transparent titlebar overlay
Stop using AppShell topNav as a full-width chrome row so sidebar canvas and
session --background extend to the window top (Codex/Claude/Cursor). Keep a
single absolute drag overlay with no-drag action clusters, Windows WCO gutters,
and 16px secondary ghost icons aligned with SideNav chrome.
* feat(desktop): collapse the session rail into a 48px icon rail
Continuous Collapse: the 48px rail stays in flow and paints ink only, with
`.appFrame` flipping to the plate background when collapsed, so the transparent
titlebar reads as one surface with the column instead of a chrome bar.
Session rows share the nav items' left edge: `.maka-list-stackContent` no longer
adds a second inline inset on top of SideNav's `scrollable` padding, and its grid
track is `minmax(0, 1fr)` so a long title cannot push the row past the rail and
get its end actions clipped.
`.maka-session-item-label` is block-level flex, not inline-flex. An inline box is
shrink-to-fit with a min-content floor, which for a nowrap title is the whole
string — the label then outgrew its slot and painted over the row meta instead of
ellipsizing.
Width easing is scoped to collapse/expand via `data-easing`. A standing width
transition also caught the per-pointer-move updates from the resize handle and
made the drag lag the cursor.
* fix(desktop): restore Astryx's icon scale under the 13px product root
Astryx sizes icons in rem so they track the root font-size, and documents the
scale as its px-equivalents at a 16px root: 12 / 16 / 20 / 24. Maka's root is
13px, so every icon Astryx renders lands at 81% of that — 13px sitting next to
the 16px lucide icons the product hands to Astryx slots by absolute size. The
nav rail showed it worst: 新任务 / 扩展 / 定时任务 read visibly smaller than the
same-family icons one settings page away.
The 13px type scale is deliberate; the icon scale was never meant to follow it,
so restate Astryx's own intent in px at the renderer mounts rather than move the
rem base. Class-on-class beats StyleX's single-class rules without !important.
* fix(desktop): drop the footer hairline on the collapsed rail
The rule that was supposed to do this targeted `.maka-session-panel-footer`,
whose border is already `0 !important` — it never had a border to clear. The
hairline is drawn one level up, on the sticky-bottom host that separates the
scrolling history from the footer.
At 48px there is no history column left to separate, so the line reads as an
orphan rule floating above the Settings icon on an otherwise ink-only rail.
Scope the collapsed override to the rule that owns the border in sidebar.css
rather than reaching for it from the shell.
* docs(desktop): stop citing contracts that no longer exist
maka-tokens.css claimed five static guards enforced its scales:
motion-token-converge, icon-system-contract, the spacing adjacency pin, and
governance contract #406. All of them went out with the 149 source-scanning
tests in #1724. A comment that promises a guard nobody runs is worse than no
comment — it reads as "the build will catch this" while nothing does.
The rules themselves stay; only the enforcement claims change to what actually
holds them: convention and review.
Also record why chat-message.css reaches for an Astryx duration instead of the
product scale. It is not a leak: Astryx ChatToolCalls opens its own group with
`grid-template-rows --duration-medium --ease-standard`, so a detail row that
expands beside one has to share that clock. Without the note the next reader
"fixes" it onto --duration-large and desyncs the two disclosures.
check-dead-css: allowlist astryx-icon and astryx-app-shell-sidenav, both
themeProps classes that exist only at runtime.
* chore(desktop): delete orphan tokens and let check-dead-css see them
Sixteen tokens in maka-tokens.css had no reader left anywhere: the --action
CTA pair, --toast-accent, --bot-brand-default, --brand-deep-hover,
--system-alert-accent, two --color-* alias tiers, --shadow-modal,
--focus-glow-width, --scale-hover, --ease-drawer, --font-serif, --h-toolbar,
--h-list-header, and --w-sessionlist (session width has been React state for a
while). Their surrounding notes claimed live consumers, so the comments went
with them.
Ten unused tokens stay. A rung of an ordered scale outliving its last consumer
is not rot — deleting the middle of the z-index or control-height ladder is
exactly what invites the next bare number. RESERVED_SCALE_TOKENS carries them
with the series each belongs to.
check-dead-css now sweeps tokens alongside classes, so this converges instead
of drifting back. Two consumer surfaces the class sweep never needed are in
scope for tokens: packages/ui stylesheets, and stories — a story composing a
surface out of the design vocabulary is a real reader, though a product class
only a story mentions is still dead. Verified both ways: an injected orphan
trips --check in either declaration style, and the live renderer resolves every
var() after the deletions.
* fix(desktop): give the session column a plate you can actually see
Nothing draws a line between the session rail and the content column, by
design: Astryx ships a divider only on `AppShell variant="section"` and Maka
runs "surface" (#1732). The separation is meant to come from the material —
`--surface-canvas` behind the rail, `--background` on the content — which is
exactly what the token block has claimed since the gray-plate/white-card model
landed.
The step was too small to deliver it. `l - 0.024` resolves to #f7f7f7 against
white: six sRGB levels, below what reads as a second surface on most displays.
Until now nobody saw the miss, because Astryx painted its own opaque surface
over the frame plate; this branch makes the shell transparent so the plate
finally shows, and what showed was almost nothing.
0.045 is not a taste pick. It is the surface→body step Astryx itself ships
(#ffffff L1.000 → #f1f1f1 L0.958), and it puts light mode within a hair of the
dark step (0.065) this file has carried all along. Dark stays where it is —
it was tuned against a dark plate, reads fine, and is not the boundary anyone
reported losing.
Not variant="section": a hairline plus a wash is two mechanisms for one job,
and section's divider is unconditional, so it would draw a rule down the 48px
icon rail — the same orphan line this branch just removed above Settings.
* refactor(desktop): let AppShell own the column materials
The column boundary was a product invention held together by two `!important`
rules: paint `--surface-canvas` on `.appFrame`, then force AppShell and its
sideNav slot transparent so the plate reads through them. Every layer in
between had to stay transparent forever, and Astryx's own material tokens sat
unused underneath.
Astryx ships this exact model. `variant="elevated"` — its default — paints the
nav column `--color-background-body` and the content column
`--color-background-surface`; with no topNav there is no radius or backdrop,
just the two materials. Maka ran `"surface"`, the one variant that paints both
columns the same and draws no divider, which is why there was nothing to see.
Taking Astryx's paint means fixing whose colors it paints with. Its neutral
pair is static light-dark(), so it cannot follow Maka's six switchable
palettes — and in dark mode the two systems disagree outright: Astryx's surface
is #262626 against the product's #171719, so the nav column rendered LIGHTER
than the content it navigates. makaTheme.ts now points both material tokens
back at the palette. Direction is deliberate and opposite to the type scale
above it: Astryx's scale covers everything Maka needs from type, while its
neutral pair is a fraction of a palette that also carries status, chat, and
per-theme colors.
Dark's plate step joins light's 0.045. The old 0.065 was never seen on the
shell — Astryx painted over the frame plate until now — and against #171719
content it renders a near-black #09090b rail.
Continuous Collapse survives as the one rule that overrides a column material:
collapsed, the 48px rail takes the content surface, because the traffic lights
are wider than the rail and a wash boundary would run a seam through them.
Measured in Storybook (Chromium, sampled pixels), both schemes, both states:
light expanded nav #f0f0f0 content #ffffff
light collapsed nav #ffffff content #ffffff
dark expanded nav #0d0d0f content #171819
dark collapsed nav #171719 content #171819
* fix(desktop): stop tooltips from swallowing clicks under them
Astryx tooltips are text popovers with no interactive content, but they leave
pointer-events at auto, so an open tooltip is a hit target. Anchored to a
control in the titlebar there is no room above it, so the layer flips below the
window's top edge and lands on the SideNav's first row — 86×28 of it, measured
live, directly over 新任务.
The E2E caught it deterministically: click 展开侧边栏, and the pointer stays
parked on its trigger, so the tooltip that opens never closes and the next
click has nowhere to land. A person hits the same edge more briefly on the way
down to the first nav row. It surfaced now because 新任务 moved into the rail
in this branch; before, the row under that tooltip was not a target.
A tooltip is an affordance, never a target. One unscoped rule — the popover
renders in the top layer, where a mount-scoped selector is not reliably in the
chain — and no !important, since StyleX declares no pointer-events of its own.
While here: keep the type-scale comment in makaTheme.ts adjacent to
`typography`. The materials block landed between them last commit.
* feat(desktop): split the columns with a hairline and a quieter wash
Sampled the two apps this shell is measured against, by scanning a pixel row
across their column seam:
Cursor nav #edeeef line 220 content #f6f6f6 ΔL 0.025
Codex nav ~#eeefef line 210 content #ffffff ΔL ~0.010
Maka nav #f0f0f0 (none) content #ffffff ΔL 0.045
Both draw a 1px rule, and both keep the wash far below what Maka was carrying.
Maka's sidebar was never the darker one — it was the lightest of the three —
but with no line it had to state the whole boundary through lightness, and a
wash that wide reads as a gray area rather than a second panel.
So: wash down to 0.025, plus the rule. The line says where the boundary is;
the wash only says the two columns are different material.
The rule is authored in makaTheme.ts, not as a product override, so AppShell
keeps one paint authority — the theme emits it into its own @scope, and the
color is the product's --border, the same product → Astryx direction as the
materials. Astryx's own divider is not usable here: it is hardcoded to
`variant === 'section'`, which also drops the wash entirely, and it is
unconditional — it would draw down the 48px icon rail.
Collapsed still drops both halves of the edge. The traffic lights measure
~62px against a 48px rail, so any column edge there cuts through the cluster.
Live measurements (2x capture, pixel row across the seam):
light nav 247 line 224 content 255
dark nav 17 line 38 content 23
* revert(desktop): drop the icon-scale pin, the 13px root is gone
This branch pinned Astryx's four icon sizes to px because the product root was
13px and every rem-based icon landed at 81% — 13px next to the 16px lucide
icons handed to the same slots. That was true when it was written and is not
true now: #1857 landed in the rebase and removed the root pin outright, saying
so in maka-tokens.css ("the root font-size stays at the browser default (16px)
ON PURPOSE") for exactly the same reason this commit existed.
So the pin restates what rem already computes. Measured in Storybook across
three stories, with the rule and with it reverted: xsm 12, sm 16, md 20, root
16px, identical either way.
Keeping it would leave 34 lines of CSS whose comment asserts a 13px root that
main's own token sheet denies — the precise shape of rot the rest of this
branch is removing.
* refactor(desktop): key the column edge on the elevated variant
`base` drew the rule under any variant, including ones that paint the columns
differently or draw their own divider. Astryx keys component overrides by prop
(`variant:elevated`), which emits `.astryx-app-shell-sidenav.elevated` —
verified on the live node, which carries both classes.
* test(e2e): lock the column edge in both sidebar states
The bug this branch was asked to fix — no visible boundary between the session
column and the content column — had nothing holding it. Neither half of the
edge is greppable: the material comes from a generated theme file, the rule
from a component override inside it, and a CSS grep passes on declarations that
never reach the element. So assert the rendered result.
Collapsed is asserted too, and asserts the opposite: both halves go away,
because the traffic lights are wider than the 48px rail and any column edge
there cuts through the cluster. Without that half a reviewer cannot tell the
collapsed rail from a regression.
Both halves ease between states, so the comparisons poll for the resting value
— sampled mid-transition the color comes back as oklab().
Mutation-checked, rebuilding between runs: divider width 1px→0px fails on the
border assertion, dropping the collapsed background override fails on the
material assertion.
* docs(desktop): stop describing tokens this branch deleted
The dead-token sweep removed the declarations and left their prose, so the
token sheet documented six things that no longer exist: the serif family with
its whole section header, a "these three geometry tokens" focus-ring recipe
that has two, a transform-amplitude note listing a hover scale nothing uses,
and the brand/system-banner tint aliases whose comments dangled above the next
section. Also drops the double blank line where --shadow-modal stood.
The lucide stroke note is corrected rather than deleted: it justified keeping
size per call site by pointing at icon rules in astryx-mount.css, and those
were reverted on this same branch when the 13px root that motivated them went
away. The reason still holds — Astryx sizes its slot icons through its own
props — so the sentence now names that instead of a file that no longer says it.
* fix(desktop): give the shell one paint authority and tests that can fail
Five independent reviews of this branch converged on two defects it had
introduced, both of the same kind: a comment asserting a guarantee the code
did not provide.
window-titlebar.spec.ts had stopped seeing the class of defect it exists for.
The strip became an absolute overlay with default pointer-events, so it is the
topmost hit at every point of the band and `elementFromPoint` returns it and
nothing else; every control beneath silently failed the "is this reachable"
precondition and was skipped. The assertion that used to catch this was
inverted in the same change (`band.bottom <= contentTop` became its converse),
leaving a form satisfied by exactly the geometry that creates the hazard.
Mutation-proved: deleting either column's `padding-top` put 新任务, the workbar
tab and 查看上一版本 under the overlay — real 30s click timeouts in three other
specs — while this spec passed. The sweep now probes with `elementsFromPoint`
minus the strip's own subtree, and splits the judgment: the strip's children
are reachable and keep the app-region rules, anything beneath it is a defect
outright, since no-drag shapes the OS rect while the click is already lost to
the strip's hit-testing. That immediately caught the resize handle, which is
positioned against the column box rather than flowing inside it and so kept its
top 36px under the strip; it now starts below it.
The Astryx neutral ramp was half-migrated. Remapping only the two tokens the
shell reads left card, popover, muted and the hairline on the static
light-dark() pair, and a ramp with one half palette-driven and the other frozen
does not merely drift — it inverts: stock muted is L 0.222 against a
--background of 0.18–0.24, so the recessive fill sat at or above the surface it
is recessed into, in 8 of 11 dark palettes. Card, Code, ChatToolCalls, Slider
and TableRow are the transcript, not chrome. The whole ramp now moves, onto the
product's own stated hierarchy; --muted lands on foreground-at-5%, defined
relative to whatever it sits on, so the inversion is unrepresentable rather
than merely corrected.
Also from the reviews, each verified before acting on it:
- The tooltip hit-test rule was global, disabling the hover bridge Astryx
documents as its WCAG 1.4.13 implementation for all 33 call sites. Scoped to
the titlebar, the one place the layer cannot flip into empty space, with the
concession stated.
- Both new `!important`s rested on "StyleX is unlayered", which this app
contradicts: astryx.css is one @layer astryx-base block imported into
astryx-components, which product `components` outranks outright. Dropped. The
`.astryx-button` colour rule went with them — Icon colours the glyph itself,
so a button-level rule never reached it.
- The content column had two paint authorities, and the new column-material
test sampled the product's copy, so it would have passed with AppShell
painting nothing. It samples AppShell's own element now, and the product's
background is gone.
- `.maka-sidenav-motion` mirrored a width SideNav already owns and then needed
`width: 100% !important` to beat it. Deleted; the transition lives on the nav.
Its React easing flag went too: cleared on transitionend, it never cleared
when no transition ran, leaving every later drag animated — the exact fault it
existed to prevent. The gate is now derived from the handle's own
`data-resizing`/focus, so it cannot desynchronize.
- `data-sidebar-state` had three writers and one reader; `data-modal-background-
hidden` had no readers at all. One writer, on the frame.
- The Storybook shell omitted `data-sidebar-state`, so the pixel-review surface
rendered a collapsed state the app does not have.
New locks, each mutation-verified to fail: the 48px collapsed rail (the number
the dropped column edge argues from), the transition suppression during resize,
and the column materials read off AppShell.
Not done, and deliberately: the generated edge selector is a bare `.elevated`
class, which Astryx's own themeProps notes advise against — the CLI emits it, so
it is an upstream report, not a local fix.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Astro-Han@jackwener