Skip to content

refactor(ui): finish the Astryx frontend migration - #1853

Merged
Astro-Han merged 7 commits into
mainfrom
refactor/1565-finish-astryx-migration
Aug 1, 2026
Merged

refactor(ui): finish the Astryx frontend migration#1853
Astro-Han merged 7 commits into
mainfrom
refactor/1565-finish-astryx-migration

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1565.

  • make Astryx plus product-owned semantic CSS the sole renderer styling path: replace the remaining Tailwind utility strings, remove CVA and tailwind-merge, and collapse the cascade to Astryx reset/components plus Maka base/components
  • remove Tailwind packages, Vite/Storybook plugins, the @theme/@source bridge, stale configuration, and regenerated notice entries
  • retire the migration-only visual/hit-test harness, route manifests, scope declarations, and components.json; update frontend governance to describe the final ownership model
  • preserve native [hidden] semantics against the Astryx 0.2.0 reset, restore exact onboarding spacing and chat-marker typography, and lock those contracts with Electron and Storybook tests
  • remove the remaining unused utility recipes instead of retaining a second styling vocabulary

No product behavior or intentional visual redesign is introduced. Exact former 4px-ruler values are preserved in semantic classes, while interaction chrome remains owned by Astryx primitives.

Verification

  • npm run format:check
  • npm run lint
  • npm run build
  • npm run typecheck
  • npm run check:release
  • npm test -w @maka/ui — 235 passed
  • npm test -w @maka/desktop — 1340 passed on the rebased head
  • npm run build-storybook -w @maka/desktop
  • npm run smoke:storybook -w @maka/desktop — 51 manifest checks and 53 catalog renders across wide/compact/floor and both themes
  • npx playwright test --config e2e/playwright.config.ts e2e/mcp.spec.ts e2e/chat-chrome-style.spec.ts e2e/disclosure-output.spec.ts e2e/settings.spec.ts --workers=1 — 18 passed
  • manual Storybook checks at desktop and 390px widths confirmed tool activity and memory settings remain contained with no horizontal overflow

Review focus

Three narrow compatibility rules remain because the published Astryx package is still 0.2.0:

  • the global [hidden] invariant overrides the zero-specificity Astryx reset until Astryx preserves native hidden semantics
  • the ChatLayout child flex contract works around the dock-height phantom scroll fixed upstream by Astryx fix(ui): expose archived conversations in the sidebar #2573 but not yet published
  • the SideNav resize-handle transform correction works around an unfixed 0.2.0 hit-area bug

They are isolated, documented product CSS overrides with explicit upstream removal conditions; none preserves a parallel Maka primitive or styling system.

@Astro-Han
Astro-Han force-pushed the refactor/1565-finish-astryx-migration branch from ecf74df to 2557601 Compare August 1, 2026 15:56
@Astro-Han
Astro-Han marked this pull request as draft August 1, 2026 15:59
@Astro-Han
Astro-Han force-pushed the refactor/1565-finish-astryx-migration branch from 2557601 to a2d5719 Compare August 1, 2026 16:29
@Astro-Han
Astro-Han marked this pull request as ready for review August 1, 2026 16:37
@Astro-Han
Astro-Han merged commit 51f0479 into main Aug 1, 2026
4 checks passed
@Astro-Han
Astro-Han deleted the refactor/1565-finish-astryx-migration branch August 1, 2026 16:38
Astro-Han added a commit that referenced this pull request Aug 1, 2026
#1853 asserted the automation-origin marker's line height inside a story
`play` function. Play functions never execute in this Storybook — no test
addon is configured, and a deliberately throwing `play` still ships green
through scripts/storybook-visual-smoke.mjs — so that contract never ran.

Keep the automation-triggered turn as visual coverage by folding it into
the transcript scaffold, and move the contract into the seam that runs:
the existing origin-presentation test, widened from Goal alone to all
three Host origins, each of which is a separate branch with its own copy.
Astro-Han added a commit that referenced this pull request Aug 1, 2026
#1853 asserted the automation-origin marker's line height inside a story
`play` function. Play functions never execute in this Storybook — no test
addon is configured, and a deliberately throwing `play` still ships green
through scripts/storybook-visual-smoke.mjs — so that contract never ran.

Keep the automation-triggered turn as visual coverage by folding it into
the transcript scaffold, and move the contract into the seam that runs:
the existing origin-presentation test, widened from Goal alone to all
three Host origins, each of which is a separate branch with its own copy.
Astro-Han added a commit that referenced this pull request Aug 1, 2026
…scaffolding (#1849)

* refactor(stories): derive bot brand icons from BOT_BRAND

The catalog kept its own list of the eight IM channels, so the story was
satisfied by any subset of the registry BotBrandLogo actually reads — a
newly supported channel would simply never render. Read the keys instead,
matching how LucideIcons already tracks the icon barrel, and drop the empty
omit-list and the always-true component guard it filtered through.

* refactor(stories): drop the ask-user-question review scaffold

The story built its own two-column board with a hand-written fake transcript
and 60 lines of story-only CSS, then hardcoded 760px and 390px chat columns.
The prompt already caps itself at --maka-chat-measure and its production slot
is display:contents, so those widths were the story's invention rather than
the product's — the retyped chain FIDELITY.md warns about, and the transcript
above it was a screen no user reaches.

Mount the prompt the way its sibling sandbox-boundary-prompt story does, and
give the manifest the narrow column back as compact and floor viewports,
where it is the real thing instead of a fixed div.

* refactor(stories): fold the plan-reminder variants into one list

Plan reminders held five of the manifest's surfaces while every neighbouring
page held one, and two of the five paid for it without showing anything new.

The attention state is a single blocked row, which is exactly the kind of
thing a scanning page only proves it surfaces when the row has healthy
neighbours — so it joins the configured list, the way ExtensionsMcpConfigured
already pairs one healthy server with one failed one.

Keep-awake renders nothing at all: plan-reminder-panel.test.tsx asserts the
enabled page stays visually identical and keeps the state inside the settings
menu, so the story smoked pixels identical to the configured one and its play
function re-tested a prop the unit test already covers.

Also drop the "~130 stories" figure from the catalog-pass comment; the count
has been wrong since #1783 cut it, and a live number there earns nothing.

* refactor(stories): host the chat transcript in the real shell

Product/Chat Surface mounted ChatView and Composer inside a 960x760 centred
card with a border and a radius — a frame the product does not have. In the
app the transcript lives in the full-height maka-panel-detail pane that
app-shell.stories.tsx already composes from the real AstryxAppShell, so every
geometry comparison against the old story was measuring its own scaffolding.
Two of its six stories were also the shell stories' states in that wrong
frame: StreamingResponse against StreamingTurn, and the two empty chats
against EmptyHome, which is the only empty home the app renders.

Move the states the shell did not have — the reasoning-and-tool-call
transcript and the session context layer — onto ComposedShell, and delete the
parallel file with its second copy of the session, message and composer
fixtures. Widening the session overlay to Partial<SessionSummary> keeps the
one-active-session rule while letting a story set the name and labels the
context layer reads.

SessionContextNarrow is not carried over: it faked a narrow column with a
360px div, and the manifest now renders the same story at compact and floor.

* test: assert the keep-awake state on its settings menu item

The panel renders identically whether keep-awake is on or off, so the
settings checkbox is the only thing carrying the state — and the existing
test only checked that the trigger exists. Deleting the story that opened
the menu therefore left aria-checked unverified. Astryx renders the menu
in static markup, so the contract lands in the same suite rather than
needing a browser.

* refactor(stories): derive the shell's chat state from production helpers

Review found the migrated stories still short of what this PR claimed. The
detail pane retyped AppShellDetailPanel's classes and dropped the two wrappers
below it, so .mainColumn never applied and the composer measured its own box;
the composer omitted onModelChange, which renders ChatModelSwitcher disabled;
and NativeConversation lost the footer actions the old file hardcoded.

Mount the real AppShellDetailPanel with the production wrappers, and derive
what the renderer derives: deriveAppShellTurnViewModel for footer actions,
deriveBranchBanner and deriveSessionRevisionNavigation for lineage. Deriving
is the point — SessionContextLayer previously hand-wrote a banner and a
revision counter for a session catalog containing neither a parent nor a
revision family, and asserted fromAbortedTurn, which app-shell.tsx documents
it deliberately never passes. The story now supplies the relatives and gets
the banner only if the real rules still produce one.

A story that renders no lineage while claiming to show it is the failure this
should catch, so sessionContext gets a surface check for the breadcrumbs, the
revision counter and horizontal overflow; removing the fixture makes it fail
at all three viewports.

Excluding id from the session overlay closes the one field that would have
desynchronized the sidebar from the chat rather than projecting onto it.

* fix(stories): make the shell fixtures states the app can actually produce

Second review round found the lineage fixture from the first one was itself
impossible. The store's isValidRevisionLineage accepts the five revision
fields together or not at all and rejects any index below 2; the fixture set
revisionRootSessionId and revisionIndex alone, and made the root its own
revision. reviseBeforeTurn shows the real shape: the root carries no revision
fields and each revision carries all five, keeping the branch lineage it was
revised from. The family is now built that way, and the sidebar runs through
projectRevisionLinkedSessionTree, because production collapses a family to one
row and the story was about to show three.

WaitingForPermission forced disabled: true, but app-shell.tsx never passes
disabled to ChatComposerRegion — the textarea stays usable while a tool waits,
so the story was inventing a locked input.

Restore the no-active-session composer as NewChatComposer. Deleting EmptyChat
took the only coverage of Composer's other branch with it: without an active
session it renders NewChatModelPicker instead of ChatModelSwitcher and drops
the thinking selector. EmptyHome's comment claimed that state differed only in
the sidebar, which was wrong.

The ask-user-question decorator now reproduces the two wrappers around the
composer slot. The prompt's root carries the composer class and .mainColumn
zeroes its top padding, so the bare canvas rendered it 8px low; measured at
0px in the built story afterwards.

Guards, all three verified by making them fail:
- sessionContext joins REQUIRED_PRODUCT_SURFACES, so deleting its manifest
  entry now fails the validator instead of silently retiring its check.
- The check reads every OverflowList copy rather than the first, which is the
  hidden measurement clone, and asserts goal and revision are visible or in
  the overflow menu — not merely present in the DOM.
- The countdown spacing loop ran zero times if countdowns disappeared; it now
  requires one when a scheduled row exists.
- check-story-annotations skipped `export` followed by a newline, so a story
  in that shape passed unread while the check reported success.

* test(ui): cover every Host origin marker where the assertion runs

#1853 asserted the automation-origin marker's line height inside a story
`play` function. Play functions never execute in this Storybook — no test
addon is configured, and a deliberately throwing `play` still ships green
through scripts/storybook-visual-smoke.mjs — so that contract never ran.

Keep the automation-triggered turn as visual coverage by folding it into
the transcript scaffold, and move the contract into the seam that runs:
the existing origin-presentation test, widened from Goal alone to all
three Host origins, each of which is a separate branch with its own copy.

* docs(stories): record that play functions do not run, and stop restating the checker

Two edits to the fidelity convention:

- Add the rule the commit before this one discovered: `play` is inert here,
  so behavioural and computed-style contracts belong in a packages/ui test
  or the smoke script's checks, not in a story.
- Cut three paragraphs that re-derive check-story-annotations.mjs's design
  — its fail-closed rationale, #1724's history, and why a source scanner
  only approximates the story set. All of it already lives in that script's
  header, which is where someone editing the guard will read it. The doc
  keeps the rule authors need: annotate every story, and write it as
  `export const Name: Story = …`.
- Drop the trailing `# Sandbox boundary prompt` section. It described one
  story's two variants, in an H1 that broke the document's structure, and
  those variants carry the same description as their own `// Real path:`.
Sign up for free to 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.

refactor(ui): make Astryx the sole renderer design system

1 participant