feat(ui): Item row primitive + selection-led first-run & 模型 polish - #162

Merged
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive
Jun 24, 2026
Merged

feat(ui): Item row primitive + selection-led first-run & 模型 polish#162
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Started as a first-run overflow fix and grew into a focused polish of the model-connection surfaces (first-run hero + Settings → 模型). The root overflow bug was a 32px provider logo dropped into an 18px grid track. Rather than patch the bespoke grid, the rows were rebuilt on two new governed @maka/ui primitives (Item, Accordion), and the surrounding first-run page and the Settings model surfaces were tidied up in the same vein.

Changes

Shared primitives (@maka/ui)

  • item.tsx (new): Item / ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions on Base UI useRender + our cn/cva. The media slot is shrink-0 with no fixed width, so it sizes to whatever icon it wraps and the icon-track mismatch cannot recur. Clickable-row hover stays neutral (faint foreground wash), reserving brand accent for active rows. Sub-slots default to span (not div/p) so a row rendered as={button} stays valid HTML.
  • accordion.tsx (new): governed Base UI Accordion, used by the model-connections list.

First-run hero (needs_connection), selection-led redesign

  • Title leads, the three setup steps compress into one quiet numbered stepper, and a scrollable provider list (Item rows) is the subject of the page.
  • The dead .maka-onboarding-card / .maka-onboarding-grid grid CSS (and its orphaned keyframes) is removed.

Settings → 模型, model-connections section

  • Rebuilt the strip as a provider→connection accordion of Item rows (was a horizontal scroller that clipped chips mid-word).
  • Renamed the band from「已启用模型」to「模型连接」. The band groups and counts every configured connection, so "enabled" was a lie: the only real source of enabled:false is an OAuth subscription whose login lapsed (stored as enabled:false + lastTestStatus:'needs_reauth'; there is no user enable/disable toggle). Such a connection now reads「需要重新登录」instead of「已禁用」, and the group rollup spans the full group so a group holding only a lapsed login raises a warning header and auto-expands (it previously read idle/green). Status + rollup logic moved to a pure provider-connection-status.ts helper with direct behavioural tests.
  • Converged the provider catalog cards onto the same Item primitive; piloted Base UI Field on the model config sheet; restored the config-sheet close button to the governed quiet icon Button and cleaned up the header alignment.

Real brand icons + de-versioned copy (first-run + Settings 模型)

  • Vendored official brand SVGs from @lobehub/icons-static-svg (MIT) in provider-brand-marks.tsx; ProviderLogoMark delegates to them, so both surfaces show real marks (Claude / OpenAI / Gemini / DeepSeek / Moonshot / Zhipu / Ollama) on a neutral, theme-aware plate.
  • Copy highlights the provider, not model versions (no "GPT/Responses", no "V3"), so it does not go stale as models advance.

Verification

  • Desktop test suite: 1480 pass, 1 environmental flake. The single failure (rive-workflow-tool child-process reap) is a timing race under the parallel isolated runner; it passes 10/10 in isolation. Suite covers Item button-safety, the 模型连接 status/rollup behaviour, copy/contract, hover-neutral, and shared-primitive assertions.
  • @maka/ui build + renderer typecheck: pass.
  • Headless screenshot harness (no foreground window): first-run selection-led layout + real icons + no overflow; provider-workspace strip wraps with all fixtures visible, light/dark × 1280/990. The lapsed-login status/rollup states are covered by the behavioural unit tests rather than screenshots.

Follow-up (not in this PR)

  • Decouple the enabled field in core. Today enabled:false overloads "user-disabled", "not usable for chat", and "OAuth login lapsed". The section-level honesty is fixed here, but the same "请到设置启用" framing is still wrong in chat-readiness / onboarding / health, and connection-status.ts still maps a lapsed login to a "已禁用" presentation. Splitting enabled from auth/readiness status (and fixing the matching copy across the chain) is a separate, larger change. Reviewed from first principles via codex + an independent agent.
  • Provider catalog cards → rows. The catalog still uses a marketplace grid; converting it to Item rows is a different UX (browse vs pick-one) and deserves its own mock/review.

The first-run provider tiles dropped a 32px provider logo into an 18px
grid track, clipping the logo and overlapping the title. Rebuild the
list on a new shared `Item` primitive (shadcn Item rewritten onto Base
UI `useRender`) whose media slot is `shrink-0` with no fixed width, so
it sizes to the icon it wraps and that class of mismatch cannot recur.
- packages/ui: new primitives/item.tsx (Item / ItemMedia / ItemContent /
ItemTitle / ItemDescription / ItemActions); exported from index. The
clickable row hover stays neutral (a faint foreground wash), reserving
the brand `accent` for active/selected rows.
- OnboardingHero: provider list now renders through Item; the dead
.maka-onboarding-card grid CSS that caused the clip is removed.
- tests: re-target the hover-neutral and shared-primitive contracts to
the new primitive instead of the deleted CSS class.
Redesign the needs_connection first-run hero around picking a provider:
title leads, the three setup steps compress into one quiet numbered
stepper, and a scrollable provider list (Item rows) is the subject.
- Vendor official brand SVGs from @lobehub/icons-static-svg (MIT) in
provider-brand-marks.tsx; ProviderLogoMark now delegates to them, so
Settings 模型 rows get the same real icons (Claude/OpenAI/Gemini/
DeepSeek/Moonshot/Zhipu/Ollama) instead of hand-drawn SVGs.
- Neutral, theme-aware provider plate (drops hardcoded light tints).
- De-version provider copy: highlight the provider, not model versions
(no "GPT/Responses", no "V3"); update the OAuth-section contract test
expectations to match.
- Remove now-dead .maka-onboarding-grid block and the orphaned
maka-card-enter / maka-agents-fade-in keyframes.
Brand marks ship under their owners' trademarks; SVGs are MIT-licensed.
The enabled-models band was a horizontal scroller; once a few models
were enabled the chips ran off the right edge and got cut mid-word.
Put the header on its own row and let the chips wrap below, with a
max-height + vertical scroll for long lists, so every enabled model
stays fully visible.
@Astro-HanAstro-Han changed the title feat(ui): Item row primitive; fix first-run provider list overflowfeat(ui): Item row primitive + selection-led first-run & 模型 polishJun 23, 2026
- Enabled-models strip: a flex-wrap left every chip a different width
(sized to its own text) so rows never lined up. Lay the chips on an
even auto-fill grid and truncate long names, so columns align and the
band reads as one tidy group.
- Catalog pill tabs: hide Base UI's sliding rounded-md indicator. Our
pills supply their own fill, and the indicator's square corners poked
out from behind the 999px pill, making the selected state look warped.
Wrap Base UI's Accordion (Root / Item / Header / Trigger / Panel) the
same way the Tabs primitive is wrapped (cn + data-slot), and export the
Primitive-prefixed aliases from @maka/ui. Gives the design system a
single reusable, governed disclosure component instead of hand-rolled
expand/collapse.
The enabled-models list flattened providers and connections into one
peer level, which read wrong (a connection and its provider looked like
siblings) and felt cramped. Rebuild it as a Base UI Accordion: each
provider is a collapsible row that owns its connections, with the worst
connection status rolled up to the header so problems show while
collapsed. The provider holding the default connection and any failing /
needs-relogin provider start expanded; healthy ones stay compact.
Connection rows render on the shared Item primitive; the old bespoke
chip grid and its CSS are removed. Contract tests updated: the @maka/ui
import check tolerates multi-line formatting and now also pins the
Accordion + Item primitives, and the no-raw-button rule allows Base UI's
`render={<button/>}` polymorphic target.
The zai-coding-plan provider reused the legacy 智谱 (Zhipu) molecule mark,
but the product surfaced here ('Z.AI Coding Plan') carries the Z.ai
wordmark — a stylized 'Z' of three slanted strokes. Vendor the official
Z.ai mark (monochrome, currentColor) from @lobehub/icons-static-svg and
point the zai-coding-plan case at it.
The enabled-models accordion sat in a bordered, filled card — the
heaviest element on the 模型 page, clashing with the catalog grid below
and reading as 'two visual languages'. Drop the card box for a seamless
hairline-bounded list (whitespace + logo plates carry the structure),
and align the columns: connection rows now sit directly under the
provider name (pl 49 = pad-x 6 + logo 32 + gap 11) with their status
right edge aligned to the provider rollup (pr 32 = pad-x 6 + chevron 15
+ meta gap 11). Healthy statuses stay quiet; only problems take color.
The 模型 catalog (国内/海外/本地 cards) and the OAuth login cards were
hand-written: a Button (or, when gated, a raw <div>) wrapping <span>
copy styled by bespoke .providerCatalogCard CSS. They were the last part
of the page not on the shared shadcn Item primitive, so the page still
mixed two component languages.
Render every catalog + OAuth entry as a governed Item (ItemMedia ·
ItemContent[ItemTitle/ItemDescription] · ItemActions) — the same
primitive the enabled connections use — in a seamless single-column
hairline list. Gated providers become an inert div Item (no render prop)
with a real trailing state badge instead of an absolute ::after, so the
provider-surface gate still keeps them unclickable. Removes the dead
.providerCatalogCard CSS skin.
The two contract tests that pinned the old card geometry (8px radius,
fixed 172px height, auto-fill 198px grid, .providerCatalogCard/Copy
class names, ::after badges) are repointed to the new intent: governed
.providerCatalogRow chrome shared across all tabs, single-column
seamless list, squared (non-pill) badges. Typecheck + all 1467 desktop
tests pass.
The category / preview / login badge sat inside ItemTitle (the row's
first line), so it pinned to the title baseline instead of the row
center on two-line rows. Move it into ItemActions next to the chevron —
a direct child of the row's items-center flex — so badge and chevron
share the row's vertical center.
Two papercuts found while testing the running app:
- The model page opened with a '.settingsPageIntro' row that paired a
non-actionable hint ('如果配置遇到问题,可以查看配置指南。' — plain
text, no link) with a 'N 个模型' badge that just restated the count
already shown by the 已启用模型 header right below it. Removed the
whole intro row (and its now-orphan CSS); the page starts at the
enabled-models list.
- The unified row lists carried BOTH a container top+bottom border AND
inner row separators, so the catalog stacked a line under the tabs and
doubled a line against the 自定义供应商 border. Drop the container
borders on all three lists (enabled / market / oauth); inner hairlines
between rows now carry the separation on their own.
Convert ConnectionDetail's 连接标识 / 服务地址 / 模型密钥 rows from the
hand-written <label><span/> markup to the governed Base UI Field
primitive (FieldRoot + Label + FieldDescription from @maka/ui), which
already existed but had no consumers. The label tone is pinned to the
established Settings caption (text-xs / foreground-60) so the rows stay
visually identical; the parenthetical state hints (OAuth 固定 / 已设置…)
now render on their own FieldDescription line under the label.
Scoped to this one sheet as a pilot: AddProviderForm and every other
Settings form stay on the legacy markup. The model-oauth copy contract
is repointed to the new Field structure while keeping the copy
Chinese-first.
The provider config sheet's close button is absolutely positioned in the
top-right corner (.providerConfigSheetClose, 30px at right:14px), but the
ConnectionDetail header's right-aligned status badges (默认 / 国内) were
laid out flush to the content edge and slid underneath the X. Reserve the
corner with a 36px right padding on .providerEditor header so the badges
(and any long provider title) clear the close affordance.
…buttons
Every governed @maka/ui <Button> in ProvidersPanel also carried a
className="maka-button". That legacy class is defined in maka-tokens.css
under @layer components, but the Button primitive's own styling lives in
@layer utilities, which wins under Tailwind v4 layer ordering — so the
maka-button class was inert dead weight on these buttons (colors, size,
border, radius, hover, focus and disabled all came from the Button
variant, not maka-button).
Remove all 18 occurrences. Repoint the three model-oauth contract
assertions that pinned the class, and add an a11y-contract guard so a
governed Button can't silently regrow the legacy class. The legacy
maka-button system stays in place for the raw <button> holdouts in other
surfaces (composer, memory, network gateway) that still rely on it.
…tton
Three layout fixes on the provider config sheet, verified live via CDP:
- Refresh button stranded ~36px from the card's right edge. Root cause:
the close-button clearance rule was written as the descendant selector
`.providerEditor header`, which also matched the nested
`.modelTableHeader` and leaked its `padding-right: 36px` onto the model
card, capping the right-aligned button short of the edge. Scope it to
the direct child `.providerEditor > header` so only the sheet's own
title/badges header reserves the corner.
- Model table header switched to `align-items: flex-start` so the refresh
button sits on the title line instead of floating mid-block, and the
grid gets an explicit `minmax(0, 1fr)` column so the header spans the
full card width.
- Close button raised to `top: 10px` so it centers on the header's first
line, lining up with the title and the right-aligned status badges.
.providerConfigSheetClose hand-rolled rest/hover/focus styles on top of the
governed Button but leaned on --settings-surface-* / --text* tokens that were
never defined, so hovering the X did nothing. Strip the class down to position
+ circular shape and let the Button's variant="quiet" size="icon-sm" own the
rest/hover/focus states.
Update the contract test to assert the close button reuses the quiet icon
Button, replacing the assertion on the now-removed hand-written focus outline.
…m-primitive
# Conflicts:
#	apps/desktop/src/renderer/OnboardingHero.tsx
…safe
Item is routinely rendered as a <button> (provider rows, OAuth cards,
connection rows), but ItemMedia/ItemContent/ItemTitle/ItemActions defaulted to
<div> and ItemDescription to <p> — flow content that is invalid inside a button
and trips React DOM-nesting checks. Default the sub-slots to <span>; the
flex/line-clamp classes lay them out identically, and a span is valid inside
both a button and a div, so call sites never need render={<span/>}.
Add a renderToStaticMarkup regression test asserting a clickable Item emits a
<button> with no <div>/<p> inside.
…logins
The Settings → 模型 band was titled "已启用模型" but grouped and counted
every connection, and labeled OAuth subscription logins that had lapsed
(stored as enabled:false + needs_reauth) as "已禁用". A connection that
only needed re-login read as user-disabled, and the enabled-only rollup
left a group holding only a lapsed login showing idle at the header.
- Rename the band to「模型连接」(it is the configured-connection list);
count and empty state stay on the full list, now honestly named.
- chipStatusText: needs_reauth wins over the disabled check (需要重新登录);
a bare enabled:false (legacy migration only) reads 暂不可用, never 已禁用.
- rollupForGroup spans the full group, so a lapsed-only group raises a
warn header and auto-expands instead of reading idle.
- Extract chipStatusText/rollupForGroup into a pure provider-connection-status
module with direct behavioural tests; relax the contract test to verify the
dedicated helper import.
The deeper enabled/auth state-model decoupling and the chat-readiness /
health copy stay as the documented follow-up.
@Astro-Han
Astro-Han merged commit 3044f9a into mainJun 24, 2026
@Astro-Han
Astro-Han deleted the claude/onboarding-item-primitive branch June 24, 2026 07:01
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.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(ui): Item row primitive + selection-led first-run & 模型 polish - #162

Merged
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive
Jun 24, 2026
Merged

feat(ui): Item row primitive + selection-led first-run & 模型 polish#162
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Started as a first-run overflow fix and grew into a focused polish of the model-connection surfaces (first-run hero + Settings → 模型). The root overflow bug was a 32px provider logo dropped into an 18px grid track. Rather than patch the bespoke grid, the rows were rebuilt on two new governed @maka/ui primitives (Item, Accordion), and the surrounding first-run page and the Settings model surfaces were tidied up in the same vein.

Changes

Shared primitives (@maka/ui)

  • item.tsx (new): Item / ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions on Base UI useRender + our cn/cva. The media slot is shrink-0 with no fixed width, so it sizes to whatever icon it wraps and the icon-track mismatch cannot recur. Clickable-row hover stays neutral (faint foreground wash), reserving brand accent for active rows. Sub-slots default to span (not div/p) so a row rendered as={button} stays valid HTML.
  • accordion.tsx (new): governed Base UI Accordion, used by the model-connections list.

First-run hero (needs_connection), selection-led redesign

  • Title leads, the three setup steps compress into one quiet numbered stepper, and a scrollable provider list (Item rows) is the subject of the page.
  • The dead .maka-onboarding-card / .maka-onboarding-grid grid CSS (and its orphaned keyframes) is removed.

Settings → 模型, model-connections section

  • Rebuilt the strip as a provider→connection accordion of Item rows (was a horizontal scroller that clipped chips mid-word).
  • Renamed the band from「已启用模型」to「模型连接」. The band groups and counts every configured connection, so "enabled" was a lie: the only real source of enabled:false is an OAuth subscription whose login lapsed (stored as enabled:false + lastTestStatus:'needs_reauth'; there is no user enable/disable toggle). Such a connection now reads「需要重新登录」instead of「已禁用」, and the group rollup spans the full group so a group holding only a lapsed login raises a warning header and auto-expands (it previously read idle/green). Status + rollup logic moved to a pure provider-connection-status.ts helper with direct behavioural tests.
  • Converged the provider catalog cards onto the same Item primitive; piloted Base UI Field on the model config sheet; restored the config-sheet close button to the governed quiet icon Button and cleaned up the header alignment.

Real brand icons + de-versioned copy (first-run + Settings 模型)

  • Vendored official brand SVGs from @lobehub/icons-static-svg (MIT) in provider-brand-marks.tsx; ProviderLogoMark delegates to them, so both surfaces show real marks (Claude / OpenAI / Gemini / DeepSeek / Moonshot / Zhipu / Ollama) on a neutral, theme-aware plate.
  • Copy highlights the provider, not model versions (no "GPT/Responses", no "V3"), so it does not go stale as models advance.

Verification

  • Desktop test suite: 1480 pass, 1 environmental flake. The single failure (rive-workflow-tool child-process reap) is a timing race under the parallel isolated runner; it passes 10/10 in isolation. Suite covers Item button-safety, the 模型连接 status/rollup behaviour, copy/contract, hover-neutral, and shared-primitive assertions.
  • @maka/ui build + renderer typecheck: pass.
  • Headless screenshot harness (no foreground window): first-run selection-led layout + real icons + no overflow; provider-workspace strip wraps with all fixtures visible, light/dark × 1280/990. The lapsed-login status/rollup states are covered by the behavioural unit tests rather than screenshots.

Follow-up (not in this PR)

  • Decouple the enabled field in core. Today enabled:false overloads "user-disabled", "not usable for chat", and "OAuth login lapsed". The section-level honesty is fixed here, but the same "请到设置启用" framing is still wrong in chat-readiness / onboarding / health, and connection-status.ts still maps a lapsed login to a "已禁用" presentation. Splitting enabled from auth/readiness status (and fixing the matching copy across the chain) is a separate, larger change. Reviewed from first principles via codex + an independent agent.
  • Provider catalog cards → rows. The catalog still uses a marketplace grid; converting it to Item rows is a different UX (browse vs pick-one) and deserves its own mock/review.

The first-run provider tiles dropped a 32px provider logo into an 18px
grid track, clipping the logo and overlapping the title. Rebuild the
list on a new shared `Item` primitive (shadcn Item rewritten onto Base
UI `useRender`) whose media slot is `shrink-0` with no fixed width, so
it sizes to the icon it wraps and that class of mismatch cannot recur.
- packages/ui: new primitives/item.tsx (Item / ItemMedia / ItemContent /
ItemTitle / ItemDescription / ItemActions); exported from index. The
clickable row hover stays neutral (a faint foreground wash), reserving
the brand `accent` for active/selected rows.
- OnboardingHero: provider list now renders through Item; the dead
.maka-onboarding-card grid CSS that caused the clip is removed.
- tests: re-target the hover-neutral and shared-primitive contracts to
the new primitive instead of the deleted CSS class.
Redesign the needs_connection first-run hero around picking a provider:
title leads, the three setup steps compress into one quiet numbered
stepper, and a scrollable provider list (Item rows) is the subject.
- Vendor official brand SVGs from @lobehub/icons-static-svg (MIT) in
provider-brand-marks.tsx; ProviderLogoMark now delegates to them, so
Settings 模型 rows get the same real icons (Claude/OpenAI/Gemini/
DeepSeek/Moonshot/Zhipu/Ollama) instead of hand-drawn SVGs.
- Neutral, theme-aware provider plate (drops hardcoded light tints).
- De-version provider copy: highlight the provider, not model versions
(no "GPT/Responses", no "V3"); update the OAuth-section contract test
expectations to match.
- Remove now-dead .maka-onboarding-grid block and the orphaned
maka-card-enter / maka-agents-fade-in keyframes.
Brand marks ship under their owners' trademarks; SVGs are MIT-licensed.
The enabled-models band was a horizontal scroller; once a few models
were enabled the chips ran off the right edge and got cut mid-word.
Put the header on its own row and let the chips wrap below, with a
max-height + vertical scroll for long lists, so every enabled model
stays fully visible.
@Astro-HanAstro-Han changed the title feat(ui): Item row primitive; fix first-run provider list overflowfeat(ui): Item row primitive + selection-led first-run & 模型 polishJun 23, 2026
- Enabled-models strip: a flex-wrap left every chip a different width
(sized to its own text) so rows never lined up. Lay the chips on an
even auto-fill grid and truncate long names, so columns align and the
band reads as one tidy group.
- Catalog pill tabs: hide Base UI's sliding rounded-md indicator. Our
pills supply their own fill, and the indicator's square corners poked
out from behind the 999px pill, making the selected state look warped.
Wrap Base UI's Accordion (Root / Item / Header / Trigger / Panel) the
same way the Tabs primitive is wrapped (cn + data-slot), and export the
Primitive-prefixed aliases from @maka/ui. Gives the design system a
single reusable, governed disclosure component instead of hand-rolled
expand/collapse.
The enabled-models list flattened providers and connections into one
peer level, which read wrong (a connection and its provider looked like
siblings) and felt cramped. Rebuild it as a Base UI Accordion: each
provider is a collapsible row that owns its connections, with the worst
connection status rolled up to the header so problems show while
collapsed. The provider holding the default connection and any failing /
needs-relogin provider start expanded; healthy ones stay compact.
Connection rows render on the shared Item primitive; the old bespoke
chip grid and its CSS are removed. Contract tests updated: the @maka/ui
import check tolerates multi-line formatting and now also pins the
Accordion + Item primitives, and the no-raw-button rule allows Base UI's
`render={<button/>}` polymorphic target.
The zai-coding-plan provider reused the legacy 智谱 (Zhipu) molecule mark,
but the product surfaced here ('Z.AI Coding Plan') carries the Z.ai
wordmark — a stylized 'Z' of three slanted strokes. Vendor the official
Z.ai mark (monochrome, currentColor) from @lobehub/icons-static-svg and
point the zai-coding-plan case at it.
The enabled-models accordion sat in a bordered, filled card — the
heaviest element on the 模型 page, clashing with the catalog grid below
and reading as 'two visual languages'. Drop the card box for a seamless
hairline-bounded list (whitespace + logo plates carry the structure),
and align the columns: connection rows now sit directly under the
provider name (pl 49 = pad-x 6 + logo 32 + gap 11) with their status
right edge aligned to the provider rollup (pr 32 = pad-x 6 + chevron 15
+ meta gap 11). Healthy statuses stay quiet; only problems take color.
The 模型 catalog (国内/海外/本地 cards) and the OAuth login cards were
hand-written: a Button (or, when gated, a raw <div>) wrapping <span>
copy styled by bespoke .providerCatalogCard CSS. They were the last part
of the page not on the shared shadcn Item primitive, so the page still
mixed two component languages.
Render every catalog + OAuth entry as a governed Item (ItemMedia ·
ItemContent[ItemTitle/ItemDescription] · ItemActions) — the same
primitive the enabled connections use — in a seamless single-column
hairline list. Gated providers become an inert div Item (no render prop)
with a real trailing state badge instead of an absolute ::after, so the
provider-surface gate still keeps them unclickable. Removes the dead
.providerCatalogCard CSS skin.
The two contract tests that pinned the old card geometry (8px radius,
fixed 172px height, auto-fill 198px grid, .providerCatalogCard/Copy
class names, ::after badges) are repointed to the new intent: governed
.providerCatalogRow chrome shared across all tabs, single-column
seamless list, squared (non-pill) badges. Typecheck + all 1467 desktop
tests pass.
The category / preview / login badge sat inside ItemTitle (the row's
first line), so it pinned to the title baseline instead of the row
center on two-line rows. Move it into ItemActions next to the chevron —
a direct child of the row's items-center flex — so badge and chevron
share the row's vertical center.
Two papercuts found while testing the running app:
- The model page opened with a '.settingsPageIntro' row that paired a
non-actionable hint ('如果配置遇到问题,可以查看配置指南。' — plain
text, no link) with a 'N 个模型' badge that just restated the count
already shown by the 已启用模型 header right below it. Removed the
whole intro row (and its now-orphan CSS); the page starts at the
enabled-models list.
- The unified row lists carried BOTH a container top+bottom border AND
inner row separators, so the catalog stacked a line under the tabs and
doubled a line against the 自定义供应商 border. Drop the container
borders on all three lists (enabled / market / oauth); inner hairlines
between rows now carry the separation on their own.
Convert ConnectionDetail's 连接标识 / 服务地址 / 模型密钥 rows from the
hand-written <label><span/> markup to the governed Base UI Field
primitive (FieldRoot + Label + FieldDescription from @maka/ui), which
already existed but had no consumers. The label tone is pinned to the
established Settings caption (text-xs / foreground-60) so the rows stay
visually identical; the parenthetical state hints (OAuth 固定 / 已设置…)
now render on their own FieldDescription line under the label.
Scoped to this one sheet as a pilot: AddProviderForm and every other
Settings form stay on the legacy markup. The model-oauth copy contract
is repointed to the new Field structure while keeping the copy
Chinese-first.
The provider config sheet's close button is absolutely positioned in the
top-right corner (.providerConfigSheetClose, 30px at right:14px), but the
ConnectionDetail header's right-aligned status badges (默认 / 国内) were
laid out flush to the content edge and slid underneath the X. Reserve the
corner with a 36px right padding on .providerEditor header so the badges
(and any long provider title) clear the close affordance.
…buttons
Every governed @maka/ui <Button> in ProvidersPanel also carried a
className="maka-button". That legacy class is defined in maka-tokens.css
under @layer components, but the Button primitive's own styling lives in
@layer utilities, which wins under Tailwind v4 layer ordering — so the
maka-button class was inert dead weight on these buttons (colors, size,
border, radius, hover, focus and disabled all came from the Button
variant, not maka-button).
Remove all 18 occurrences. Repoint the three model-oauth contract
assertions that pinned the class, and add an a11y-contract guard so a
governed Button can't silently regrow the legacy class. The legacy
maka-button system stays in place for the raw <button> holdouts in other
surfaces (composer, memory, network gateway) that still rely on it.
…tton
Three layout fixes on the provider config sheet, verified live via CDP:
- Refresh button stranded ~36px from the card's right edge. Root cause:
the close-button clearance rule was written as the descendant selector
`.providerEditor header`, which also matched the nested
`.modelTableHeader` and leaked its `padding-right: 36px` onto the model
card, capping the right-aligned button short of the edge. Scope it to
the direct child `.providerEditor > header` so only the sheet's own
title/badges header reserves the corner.
- Model table header switched to `align-items: flex-start` so the refresh
button sits on the title line instead of floating mid-block, and the
grid gets an explicit `minmax(0, 1fr)` column so the header spans the
full card width.
- Close button raised to `top: 10px` so it centers on the header's first
line, lining up with the title and the right-aligned status badges.
.providerConfigSheetClose hand-rolled rest/hover/focus styles on top of the
governed Button but leaned on --settings-surface-* / --text* tokens that were
never defined, so hovering the X did nothing. Strip the class down to position
+ circular shape and let the Button's variant="quiet" size="icon-sm" own the
rest/hover/focus states.
Update the contract test to assert the close button reuses the quiet icon
Button, replacing the assertion on the now-removed hand-written focus outline.
…m-primitive
# Conflicts:
#	apps/desktop/src/renderer/OnboardingHero.tsx
…safe
Item is routinely rendered as a <button> (provider rows, OAuth cards,
connection rows), but ItemMedia/ItemContent/ItemTitle/ItemActions defaulted to
<div> and ItemDescription to <p> — flow content that is invalid inside a button
and trips React DOM-nesting checks. Default the sub-slots to <span>; the
flex/line-clamp classes lay them out identically, and a span is valid inside
both a button and a div, so call sites never need render={<span/>}.
Add a renderToStaticMarkup regression test asserting a clickable Item emits a
<button> with no <div>/<p> inside.
…logins
The Settings → 模型 band was titled "已启用模型" but grouped and counted
every connection, and labeled OAuth subscription logins that had lapsed
(stored as enabled:false + needs_reauth) as "已禁用". A connection that
only needed re-login read as user-disabled, and the enabled-only rollup
left a group holding only a lapsed login showing idle at the header.
- Rename the band to「模型连接」(it is the configured-connection list);
count and empty state stay on the full list, now honestly named.
- chipStatusText: needs_reauth wins over the disabled check (需要重新登录);
a bare enabled:false (legacy migration only) reads 暂不可用, never 已禁用.
- rollupForGroup spans the full group, so a lapsed-only group raises a
warn header and auto-expands instead of reading idle.
- Extract chipStatusText/rollupForGroup into a pure provider-connection-status
module with direct behavioural tests; relax the contract test to verify the
dedicated helper import.
The deeper enabled/auth state-model decoupling and the chat-readiness /
health copy stay as the documented follow-up.
@Astro-Han
Astro-Han merged commit 3044f9a into mainJun 24, 2026
@Astro-Han
Astro-Han deleted the claude/onboarding-item-primitive branch June 24, 2026 07:01
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.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(ui): Item row primitive + selection-led first-run & 模型 polish - #162

Merged
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive
Jun 24, 2026
Merged

feat(ui): Item row primitive + selection-led first-run & 模型 polish#162
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Started as a first-run overflow fix and grew into a focused polish of the model-connection surfaces (first-run hero + Settings → 模型). The root overflow bug was a 32px provider logo dropped into an 18px grid track. Rather than patch the bespoke grid, the rows were rebuilt on two new governed @maka/ui primitives (Item, Accordion), and the surrounding first-run page and the Settings model surfaces were tidied up in the same vein.

Changes

Shared primitives (@maka/ui)

  • item.tsx (new): Item / ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions on Base UI useRender + our cn/cva. The media slot is shrink-0 with no fixed width, so it sizes to whatever icon it wraps and the icon-track mismatch cannot recur. Clickable-row hover stays neutral (faint foreground wash), reserving brand accent for active rows. Sub-slots default to span (not div/p) so a row rendered as={button} stays valid HTML.
  • accordion.tsx (new): governed Base UI Accordion, used by the model-connections list.

First-run hero (needs_connection), selection-led redesign

  • Title leads, the three setup steps compress into one quiet numbered stepper, and a scrollable provider list (Item rows) is the subject of the page.
  • The dead .maka-onboarding-card / .maka-onboarding-grid grid CSS (and its orphaned keyframes) is removed.

Settings → 模型, model-connections section

  • Rebuilt the strip as a provider→connection accordion of Item rows (was a horizontal scroller that clipped chips mid-word).
  • Renamed the band from「已启用模型」to「模型连接」. The band groups and counts every configured connection, so "enabled" was a lie: the only real source of enabled:false is an OAuth subscription whose login lapsed (stored as enabled:false + lastTestStatus:'needs_reauth'; there is no user enable/disable toggle). Such a connection now reads「需要重新登录」instead of「已禁用」, and the group rollup spans the full group so a group holding only a lapsed login raises a warning header and auto-expands (it previously read idle/green). Status + rollup logic moved to a pure provider-connection-status.ts helper with direct behavioural tests.
  • Converged the provider catalog cards onto the same Item primitive; piloted Base UI Field on the model config sheet; restored the config-sheet close button to the governed quiet icon Button and cleaned up the header alignment.

Real brand icons + de-versioned copy (first-run + Settings 模型)

  • Vendored official brand SVGs from @lobehub/icons-static-svg (MIT) in provider-brand-marks.tsx; ProviderLogoMark delegates to them, so both surfaces show real marks (Claude / OpenAI / Gemini / DeepSeek / Moonshot / Zhipu / Ollama) on a neutral, theme-aware plate.
  • Copy highlights the provider, not model versions (no "GPT/Responses", no "V3"), so it does not go stale as models advance.

Verification

  • Desktop test suite: 1480 pass, 1 environmental flake. The single failure (rive-workflow-tool child-process reap) is a timing race under the parallel isolated runner; it passes 10/10 in isolation. Suite covers Item button-safety, the 模型连接 status/rollup behaviour, copy/contract, hover-neutral, and shared-primitive assertions.
  • @maka/ui build + renderer typecheck: pass.
  • Headless screenshot harness (no foreground window): first-run selection-led layout + real icons + no overflow; provider-workspace strip wraps with all fixtures visible, light/dark × 1280/990. The lapsed-login status/rollup states are covered by the behavioural unit tests rather than screenshots.

Follow-up (not in this PR)

  • Decouple the enabled field in core. Today enabled:false overloads "user-disabled", "not usable for chat", and "OAuth login lapsed". The section-level honesty is fixed here, but the same "请到设置启用" framing is still wrong in chat-readiness / onboarding / health, and connection-status.ts still maps a lapsed login to a "已禁用" presentation. Splitting enabled from auth/readiness status (and fixing the matching copy across the chain) is a separate, larger change. Reviewed from first principles via codex + an independent agent.
  • Provider catalog cards → rows. The catalog still uses a marketplace grid; converting it to Item rows is a different UX (browse vs pick-one) and deserves its own mock/review.

The first-run provider tiles dropped a 32px provider logo into an 18px
grid track, clipping the logo and overlapping the title. Rebuild the
list on a new shared `Item` primitive (shadcn Item rewritten onto Base
UI `useRender`) whose media slot is `shrink-0` with no fixed width, so
it sizes to the icon it wraps and that class of mismatch cannot recur.
- packages/ui: new primitives/item.tsx (Item / ItemMedia / ItemContent /
ItemTitle / ItemDescription / ItemActions); exported from index. The
clickable row hover stays neutral (a faint foreground wash), reserving
the brand `accent` for active/selected rows.
- OnboardingHero: provider list now renders through Item; the dead
.maka-onboarding-card grid CSS that caused the clip is removed.
- tests: re-target the hover-neutral and shared-primitive contracts to
the new primitive instead of the deleted CSS class.
Redesign the needs_connection first-run hero around picking a provider:
title leads, the three setup steps compress into one quiet numbered
stepper, and a scrollable provider list (Item rows) is the subject.
- Vendor official brand SVGs from @lobehub/icons-static-svg (MIT) in
provider-brand-marks.tsx; ProviderLogoMark now delegates to them, so
Settings 模型 rows get the same real icons (Claude/OpenAI/Gemini/
DeepSeek/Moonshot/Zhipu/Ollama) instead of hand-drawn SVGs.
- Neutral, theme-aware provider plate (drops hardcoded light tints).
- De-version provider copy: highlight the provider, not model versions
(no "GPT/Responses", no "V3"); update the OAuth-section contract test
expectations to match.
- Remove now-dead .maka-onboarding-grid block and the orphaned
maka-card-enter / maka-agents-fade-in keyframes.
Brand marks ship under their owners' trademarks; SVGs are MIT-licensed.
The enabled-models band was a horizontal scroller; once a few models
were enabled the chips ran off the right edge and got cut mid-word.
Put the header on its own row and let the chips wrap below, with a
max-height + vertical scroll for long lists, so every enabled model
stays fully visible.
@Astro-HanAstro-Han changed the title feat(ui): Item row primitive; fix first-run provider list overflowfeat(ui): Item row primitive + selection-led first-run & 模型 polishJun 23, 2026
- Enabled-models strip: a flex-wrap left every chip a different width
(sized to its own text) so rows never lined up. Lay the chips on an
even auto-fill grid and truncate long names, so columns align and the
band reads as one tidy group.
- Catalog pill tabs: hide Base UI's sliding rounded-md indicator. Our
pills supply their own fill, and the indicator's square corners poked
out from behind the 999px pill, making the selected state look warped.
Wrap Base UI's Accordion (Root / Item / Header / Trigger / Panel) the
same way the Tabs primitive is wrapped (cn + data-slot), and export the
Primitive-prefixed aliases from @maka/ui. Gives the design system a
single reusable, governed disclosure component instead of hand-rolled
expand/collapse.
The enabled-models list flattened providers and connections into one
peer level, which read wrong (a connection and its provider looked like
siblings) and felt cramped. Rebuild it as a Base UI Accordion: each
provider is a collapsible row that owns its connections, with the worst
connection status rolled up to the header so problems show while
collapsed. The provider holding the default connection and any failing /
needs-relogin provider start expanded; healthy ones stay compact.
Connection rows render on the shared Item primitive; the old bespoke
chip grid and its CSS are removed. Contract tests updated: the @maka/ui
import check tolerates multi-line formatting and now also pins the
Accordion + Item primitives, and the no-raw-button rule allows Base UI's
`render={<button/>}` polymorphic target.
The zai-coding-plan provider reused the legacy 智谱 (Zhipu) molecule mark,
but the product surfaced here ('Z.AI Coding Plan') carries the Z.ai
wordmark — a stylized 'Z' of three slanted strokes. Vendor the official
Z.ai mark (monochrome, currentColor) from @lobehub/icons-static-svg and
point the zai-coding-plan case at it.
The enabled-models accordion sat in a bordered, filled card — the
heaviest element on the 模型 page, clashing with the catalog grid below
and reading as 'two visual languages'. Drop the card box for a seamless
hairline-bounded list (whitespace + logo plates carry the structure),
and align the columns: connection rows now sit directly under the
provider name (pl 49 = pad-x 6 + logo 32 + gap 11) with their status
right edge aligned to the provider rollup (pr 32 = pad-x 6 + chevron 15
+ meta gap 11). Healthy statuses stay quiet; only problems take color.
The 模型 catalog (国内/海外/本地 cards) and the OAuth login cards were
hand-written: a Button (or, when gated, a raw <div>) wrapping <span>
copy styled by bespoke .providerCatalogCard CSS. They were the last part
of the page not on the shared shadcn Item primitive, so the page still
mixed two component languages.
Render every catalog + OAuth entry as a governed Item (ItemMedia ·
ItemContent[ItemTitle/ItemDescription] · ItemActions) — the same
primitive the enabled connections use — in a seamless single-column
hairline list. Gated providers become an inert div Item (no render prop)
with a real trailing state badge instead of an absolute ::after, so the
provider-surface gate still keeps them unclickable. Removes the dead
.providerCatalogCard CSS skin.
The two contract tests that pinned the old card geometry (8px radius,
fixed 172px height, auto-fill 198px grid, .providerCatalogCard/Copy
class names, ::after badges) are repointed to the new intent: governed
.providerCatalogRow chrome shared across all tabs, single-column
seamless list, squared (non-pill) badges. Typecheck + all 1467 desktop
tests pass.
The category / preview / login badge sat inside ItemTitle (the row's
first line), so it pinned to the title baseline instead of the row
center on two-line rows. Move it into ItemActions next to the chevron —
a direct child of the row's items-center flex — so badge and chevron
share the row's vertical center.
Two papercuts found while testing the running app:
- The model page opened with a '.settingsPageIntro' row that paired a
non-actionable hint ('如果配置遇到问题,可以查看配置指南。' — plain
text, no link) with a 'N 个模型' badge that just restated the count
already shown by the 已启用模型 header right below it. Removed the
whole intro row (and its now-orphan CSS); the page starts at the
enabled-models list.
- The unified row lists carried BOTH a container top+bottom border AND
inner row separators, so the catalog stacked a line under the tabs and
doubled a line against the 自定义供应商 border. Drop the container
borders on all three lists (enabled / market / oauth); inner hairlines
between rows now carry the separation on their own.
Convert ConnectionDetail's 连接标识 / 服务地址 / 模型密钥 rows from the
hand-written <label><span/> markup to the governed Base UI Field
primitive (FieldRoot + Label + FieldDescription from @maka/ui), which
already existed but had no consumers. The label tone is pinned to the
established Settings caption (text-xs / foreground-60) so the rows stay
visually identical; the parenthetical state hints (OAuth 固定 / 已设置…)
now render on their own FieldDescription line under the label.
Scoped to this one sheet as a pilot: AddProviderForm and every other
Settings form stay on the legacy markup. The model-oauth copy contract
is repointed to the new Field structure while keeping the copy
Chinese-first.
The provider config sheet's close button is absolutely positioned in the
top-right corner (.providerConfigSheetClose, 30px at right:14px), but the
ConnectionDetail header's right-aligned status badges (默认 / 国内) were
laid out flush to the content edge and slid underneath the X. Reserve the
corner with a 36px right padding on .providerEditor header so the badges
(and any long provider title) clear the close affordance.
…buttons
Every governed @maka/ui <Button> in ProvidersPanel also carried a
className="maka-button". That legacy class is defined in maka-tokens.css
under @layer components, but the Button primitive's own styling lives in
@layer utilities, which wins under Tailwind v4 layer ordering — so the
maka-button class was inert dead weight on these buttons (colors, size,
border, radius, hover, focus and disabled all came from the Button
variant, not maka-button).
Remove all 18 occurrences. Repoint the three model-oauth contract
assertions that pinned the class, and add an a11y-contract guard so a
governed Button can't silently regrow the legacy class. The legacy
maka-button system stays in place for the raw <button> holdouts in other
surfaces (composer, memory, network gateway) that still rely on it.
…tton
Three layout fixes on the provider config sheet, verified live via CDP:
- Refresh button stranded ~36px from the card's right edge. Root cause:
the close-button clearance rule was written as the descendant selector
`.providerEditor header`, which also matched the nested
`.modelTableHeader` and leaked its `padding-right: 36px` onto the model
card, capping the right-aligned button short of the edge. Scope it to
the direct child `.providerEditor > header` so only the sheet's own
title/badges header reserves the corner.
- Model table header switched to `align-items: flex-start` so the refresh
button sits on the title line instead of floating mid-block, and the
grid gets an explicit `minmax(0, 1fr)` column so the header spans the
full card width.
- Close button raised to `top: 10px` so it centers on the header's first
line, lining up with the title and the right-aligned status badges.
.providerConfigSheetClose hand-rolled rest/hover/focus styles on top of the
governed Button but leaned on --settings-surface-* / --text* tokens that were
never defined, so hovering the X did nothing. Strip the class down to position
+ circular shape and let the Button's variant="quiet" size="icon-sm" own the
rest/hover/focus states.
Update the contract test to assert the close button reuses the quiet icon
Button, replacing the assertion on the now-removed hand-written focus outline.
…m-primitive
# Conflicts:
#	apps/desktop/src/renderer/OnboardingHero.tsx
…safe
Item is routinely rendered as a <button> (provider rows, OAuth cards,
connection rows), but ItemMedia/ItemContent/ItemTitle/ItemActions defaulted to
<div> and ItemDescription to <p> — flow content that is invalid inside a button
and trips React DOM-nesting checks. Default the sub-slots to <span>; the
flex/line-clamp classes lay them out identically, and a span is valid inside
both a button and a div, so call sites never need render={<span/>}.
Add a renderToStaticMarkup regression test asserting a clickable Item emits a
<button> with no <div>/<p> inside.
…logins
The Settings → 模型 band was titled "已启用模型" but grouped and counted
every connection, and labeled OAuth subscription logins that had lapsed
(stored as enabled:false + needs_reauth) as "已禁用". A connection that
only needed re-login read as user-disabled, and the enabled-only rollup
left a group holding only a lapsed login showing idle at the header.
- Rename the band to「模型连接」(it is the configured-connection list);
count and empty state stay on the full list, now honestly named.
- chipStatusText: needs_reauth wins over the disabled check (需要重新登录);
a bare enabled:false (legacy migration only) reads 暂不可用, never 已禁用.
- rollupForGroup spans the full group, so a lapsed-only group raises a
warn header and auto-expands instead of reading idle.
- Extract chipStatusText/rollupForGroup into a pure provider-connection-status
module with direct behavioural tests; relax the contract test to verify the
dedicated helper import.
The deeper enabled/auth state-model decoupling and the chat-readiness /
health copy stay as the documented follow-up.
@Astro-Han
Astro-Han merged commit 3044f9a into mainJun 24, 2026
@Astro-Han
Astro-Han deleted the claude/onboarding-item-primitive branch June 24, 2026 07:01
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.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(ui): Item row primitive + selection-led first-run & 模型 polish - #162

Merged
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive
Jun 24, 2026
Merged

feat(ui): Item row primitive + selection-led first-run & 模型 polish#162
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Started as a first-run overflow fix and grew into a focused polish of the model-connection surfaces (first-run hero + Settings → 模型). The root overflow bug was a 32px provider logo dropped into an 18px grid track. Rather than patch the bespoke grid, the rows were rebuilt on two new governed @maka/ui primitives (Item, Accordion), and the surrounding first-run page and the Settings model surfaces were tidied up in the same vein.

Changes

Shared primitives (@maka/ui)

  • item.tsx (new): Item / ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions on Base UI useRender + our cn/cva. The media slot is shrink-0 with no fixed width, so it sizes to whatever icon it wraps and the icon-track mismatch cannot recur. Clickable-row hover stays neutral (faint foreground wash), reserving brand accent for active rows. Sub-slots default to span (not div/p) so a row rendered as={button} stays valid HTML.
  • accordion.tsx (new): governed Base UI Accordion, used by the model-connections list.

First-run hero (needs_connection), selection-led redesign

  • Title leads, the three setup steps compress into one quiet numbered stepper, and a scrollable provider list (Item rows) is the subject of the page.
  • The dead .maka-onboarding-card / .maka-onboarding-grid grid CSS (and its orphaned keyframes) is removed.

Settings → 模型, model-connections section

  • Rebuilt the strip as a provider→connection accordion of Item rows (was a horizontal scroller that clipped chips mid-word).
  • Renamed the band from「已启用模型」to「模型连接」. The band groups and counts every configured connection, so "enabled" was a lie: the only real source of enabled:false is an OAuth subscription whose login lapsed (stored as enabled:false + lastTestStatus:'needs_reauth'; there is no user enable/disable toggle). Such a connection now reads「需要重新登录」instead of「已禁用」, and the group rollup spans the full group so a group holding only a lapsed login raises a warning header and auto-expands (it previously read idle/green). Status + rollup logic moved to a pure provider-connection-status.ts helper with direct behavioural tests.
  • Converged the provider catalog cards onto the same Item primitive; piloted Base UI Field on the model config sheet; restored the config-sheet close button to the governed quiet icon Button and cleaned up the header alignment.

Real brand icons + de-versioned copy (first-run + Settings 模型)

  • Vendored official brand SVGs from @lobehub/icons-static-svg (MIT) in provider-brand-marks.tsx; ProviderLogoMark delegates to them, so both surfaces show real marks (Claude / OpenAI / Gemini / DeepSeek / Moonshot / Zhipu / Ollama) on a neutral, theme-aware plate.
  • Copy highlights the provider, not model versions (no "GPT/Responses", no "V3"), so it does not go stale as models advance.

Verification

  • Desktop test suite: 1480 pass, 1 environmental flake. The single failure (rive-workflow-tool child-process reap) is a timing race under the parallel isolated runner; it passes 10/10 in isolation. Suite covers Item button-safety, the 模型连接 status/rollup behaviour, copy/contract, hover-neutral, and shared-primitive assertions.
  • @maka/ui build + renderer typecheck: pass.
  • Headless screenshot harness (no foreground window): first-run selection-led layout + real icons + no overflow; provider-workspace strip wraps with all fixtures visible, light/dark × 1280/990. The lapsed-login status/rollup states are covered by the behavioural unit tests rather than screenshots.

Follow-up (not in this PR)

  • Decouple the enabled field in core. Today enabled:false overloads "user-disabled", "not usable for chat", and "OAuth login lapsed". The section-level honesty is fixed here, but the same "请到设置启用" framing is still wrong in chat-readiness / onboarding / health, and connection-status.ts still maps a lapsed login to a "已禁用" presentation. Splitting enabled from auth/readiness status (and fixing the matching copy across the chain) is a separate, larger change. Reviewed from first principles via codex + an independent agent.
  • Provider catalog cards → rows. The catalog still uses a marketplace grid; converting it to Item rows is a different UX (browse vs pick-one) and deserves its own mock/review.

The first-run provider tiles dropped a 32px provider logo into an 18px
grid track, clipping the logo and overlapping the title. Rebuild the
list on a new shared `Item` primitive (shadcn Item rewritten onto Base
UI `useRender`) whose media slot is `shrink-0` with no fixed width, so
it sizes to the icon it wraps and that class of mismatch cannot recur.
- packages/ui: new primitives/item.tsx (Item / ItemMedia / ItemContent /
ItemTitle / ItemDescription / ItemActions); exported from index. The
clickable row hover stays neutral (a faint foreground wash), reserving
the brand `accent` for active/selected rows.
- OnboardingHero: provider list now renders through Item; the dead
.maka-onboarding-card grid CSS that caused the clip is removed.
- tests: re-target the hover-neutral and shared-primitive contracts to
the new primitive instead of the deleted CSS class.
Redesign the needs_connection first-run hero around picking a provider:
title leads, the three setup steps compress into one quiet numbered
stepper, and a scrollable provider list (Item rows) is the subject.
- Vendor official brand SVGs from @lobehub/icons-static-svg (MIT) in
provider-brand-marks.tsx; ProviderLogoMark now delegates to them, so
Settings 模型 rows get the same real icons (Claude/OpenAI/Gemini/
DeepSeek/Moonshot/Zhipu/Ollama) instead of hand-drawn SVGs.
- Neutral, theme-aware provider plate (drops hardcoded light tints).
- De-version provider copy: highlight the provider, not model versions
(no "GPT/Responses", no "V3"); update the OAuth-section contract test
expectations to match.
- Remove now-dead .maka-onboarding-grid block and the orphaned
maka-card-enter / maka-agents-fade-in keyframes.
Brand marks ship under their owners' trademarks; SVGs are MIT-licensed.
The enabled-models band was a horizontal scroller; once a few models
were enabled the chips ran off the right edge and got cut mid-word.
Put the header on its own row and let the chips wrap below, with a
max-height + vertical scroll for long lists, so every enabled model
stays fully visible.
@Astro-HanAstro-Han changed the title feat(ui): Item row primitive; fix first-run provider list overflowfeat(ui): Item row primitive + selection-led first-run & 模型 polishJun 23, 2026
- Enabled-models strip: a flex-wrap left every chip a different width
(sized to its own text) so rows never lined up. Lay the chips on an
even auto-fill grid and truncate long names, so columns align and the
band reads as one tidy group.
- Catalog pill tabs: hide Base UI's sliding rounded-md indicator. Our
pills supply their own fill, and the indicator's square corners poked
out from behind the 999px pill, making the selected state look warped.
Wrap Base UI's Accordion (Root / Item / Header / Trigger / Panel) the
same way the Tabs primitive is wrapped (cn + data-slot), and export the
Primitive-prefixed aliases from @maka/ui. Gives the design system a
single reusable, governed disclosure component instead of hand-rolled
expand/collapse.
The enabled-models list flattened providers and connections into one
peer level, which read wrong (a connection and its provider looked like
siblings) and felt cramped. Rebuild it as a Base UI Accordion: each
provider is a collapsible row that owns its connections, with the worst
connection status rolled up to the header so problems show while
collapsed. The provider holding the default connection and any failing /
needs-relogin provider start expanded; healthy ones stay compact.
Connection rows render on the shared Item primitive; the old bespoke
chip grid and its CSS are removed. Contract tests updated: the @maka/ui
import check tolerates multi-line formatting and now also pins the
Accordion + Item primitives, and the no-raw-button rule allows Base UI's
`render={<button/>}` polymorphic target.
The zai-coding-plan provider reused the legacy 智谱 (Zhipu) molecule mark,
but the product surfaced here ('Z.AI Coding Plan') carries the Z.ai
wordmark — a stylized 'Z' of three slanted strokes. Vendor the official
Z.ai mark (monochrome, currentColor) from @lobehub/icons-static-svg and
point the zai-coding-plan case at it.
The enabled-models accordion sat in a bordered, filled card — the
heaviest element on the 模型 page, clashing with the catalog grid below
and reading as 'two visual languages'. Drop the card box for a seamless
hairline-bounded list (whitespace + logo plates carry the structure),
and align the columns: connection rows now sit directly under the
provider name (pl 49 = pad-x 6 + logo 32 + gap 11) with their status
right edge aligned to the provider rollup (pr 32 = pad-x 6 + chevron 15
+ meta gap 11). Healthy statuses stay quiet; only problems take color.
The 模型 catalog (国内/海外/本地 cards) and the OAuth login cards were
hand-written: a Button (or, when gated, a raw <div>) wrapping <span>
copy styled by bespoke .providerCatalogCard CSS. They were the last part
of the page not on the shared shadcn Item primitive, so the page still
mixed two component languages.
Render every catalog + OAuth entry as a governed Item (ItemMedia ·
ItemContent[ItemTitle/ItemDescription] · ItemActions) — the same
primitive the enabled connections use — in a seamless single-column
hairline list. Gated providers become an inert div Item (no render prop)
with a real trailing state badge instead of an absolute ::after, so the
provider-surface gate still keeps them unclickable. Removes the dead
.providerCatalogCard CSS skin.
The two contract tests that pinned the old card geometry (8px radius,
fixed 172px height, auto-fill 198px grid, .providerCatalogCard/Copy
class names, ::after badges) are repointed to the new intent: governed
.providerCatalogRow chrome shared across all tabs, single-column
seamless list, squared (non-pill) badges. Typecheck + all 1467 desktop
tests pass.
The category / preview / login badge sat inside ItemTitle (the row's
first line), so it pinned to the title baseline instead of the row
center on two-line rows. Move it into ItemActions next to the chevron —
a direct child of the row's items-center flex — so badge and chevron
share the row's vertical center.
Two papercuts found while testing the running app:
- The model page opened with a '.settingsPageIntro' row that paired a
non-actionable hint ('如果配置遇到问题,可以查看配置指南。' — plain
text, no link) with a 'N 个模型' badge that just restated the count
already shown by the 已启用模型 header right below it. Removed the
whole intro row (and its now-orphan CSS); the page starts at the
enabled-models list.
- The unified row lists carried BOTH a container top+bottom border AND
inner row separators, so the catalog stacked a line under the tabs and
doubled a line against the 自定义供应商 border. Drop the container
borders on all three lists (enabled / market / oauth); inner hairlines
between rows now carry the separation on their own.
Convert ConnectionDetail's 连接标识 / 服务地址 / 模型密钥 rows from the
hand-written <label><span/> markup to the governed Base UI Field
primitive (FieldRoot + Label + FieldDescription from @maka/ui), which
already existed but had no consumers. The label tone is pinned to the
established Settings caption (text-xs / foreground-60) so the rows stay
visually identical; the parenthetical state hints (OAuth 固定 / 已设置…)
now render on their own FieldDescription line under the label.
Scoped to this one sheet as a pilot: AddProviderForm and every other
Settings form stay on the legacy markup. The model-oauth copy contract
is repointed to the new Field structure while keeping the copy
Chinese-first.
The provider config sheet's close button is absolutely positioned in the
top-right corner (.providerConfigSheetClose, 30px at right:14px), but the
ConnectionDetail header's right-aligned status badges (默认 / 国内) were
laid out flush to the content edge and slid underneath the X. Reserve the
corner with a 36px right padding on .providerEditor header so the badges
(and any long provider title) clear the close affordance.
…buttons
Every governed @maka/ui <Button> in ProvidersPanel also carried a
className="maka-button". That legacy class is defined in maka-tokens.css
under @layer components, but the Button primitive's own styling lives in
@layer utilities, which wins under Tailwind v4 layer ordering — so the
maka-button class was inert dead weight on these buttons (colors, size,
border, radius, hover, focus and disabled all came from the Button
variant, not maka-button).
Remove all 18 occurrences. Repoint the three model-oauth contract
assertions that pinned the class, and add an a11y-contract guard so a
governed Button can't silently regrow the legacy class. The legacy
maka-button system stays in place for the raw <button> holdouts in other
surfaces (composer, memory, network gateway) that still rely on it.
…tton
Three layout fixes on the provider config sheet, verified live via CDP:
- Refresh button stranded ~36px from the card's right edge. Root cause:
the close-button clearance rule was written as the descendant selector
`.providerEditor header`, which also matched the nested
`.modelTableHeader` and leaked its `padding-right: 36px` onto the model
card, capping the right-aligned button short of the edge. Scope it to
the direct child `.providerEditor > header` so only the sheet's own
title/badges header reserves the corner.
- Model table header switched to `align-items: flex-start` so the refresh
button sits on the title line instead of floating mid-block, and the
grid gets an explicit `minmax(0, 1fr)` column so the header spans the
full card width.
- Close button raised to `top: 10px` so it centers on the header's first
line, lining up with the title and the right-aligned status badges.
.providerConfigSheetClose hand-rolled rest/hover/focus styles on top of the
governed Button but leaned on --settings-surface-* / --text* tokens that were
never defined, so hovering the X did nothing. Strip the class down to position
+ circular shape and let the Button's variant="quiet" size="icon-sm" own the
rest/hover/focus states.
Update the contract test to assert the close button reuses the quiet icon
Button, replacing the assertion on the now-removed hand-written focus outline.
…m-primitive
# Conflicts:
#	apps/desktop/src/renderer/OnboardingHero.tsx
…safe
Item is routinely rendered as a <button> (provider rows, OAuth cards,
connection rows), but ItemMedia/ItemContent/ItemTitle/ItemActions defaulted to
<div> and ItemDescription to <p> — flow content that is invalid inside a button
and trips React DOM-nesting checks. Default the sub-slots to <span>; the
flex/line-clamp classes lay them out identically, and a span is valid inside
both a button and a div, so call sites never need render={<span/>}.
Add a renderToStaticMarkup regression test asserting a clickable Item emits a
<button> with no <div>/<p> inside.
…logins
The Settings → 模型 band was titled "已启用模型" but grouped and counted
every connection, and labeled OAuth subscription logins that had lapsed
(stored as enabled:false + needs_reauth) as "已禁用". A connection that
only needed re-login read as user-disabled, and the enabled-only rollup
left a group holding only a lapsed login showing idle at the header.
- Rename the band to「模型连接」(it is the configured-connection list);
count and empty state stay on the full list, now honestly named.
- chipStatusText: needs_reauth wins over the disabled check (需要重新登录);
a bare enabled:false (legacy migration only) reads 暂不可用, never 已禁用.
- rollupForGroup spans the full group, so a lapsed-only group raises a
warn header and auto-expands instead of reading idle.
- Extract chipStatusText/rollupForGroup into a pure provider-connection-status
module with direct behavioural tests; relax the contract test to verify the
dedicated helper import.
The deeper enabled/auth state-model decoupling and the chat-readiness /
health copy stay as the documented follow-up.
@Astro-Han
Astro-Han merged commit 3044f9a into mainJun 24, 2026
@Astro-Han
Astro-Han deleted the claude/onboarding-item-primitive branch June 24, 2026 07:01
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.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(ui): Item row primitive + selection-led first-run & 模型 polish - #162

Merged
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive
Jun 24, 2026
Merged

feat(ui): Item row primitive + selection-led first-run & 模型 polish#162
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Started as a first-run overflow fix and grew into a focused polish of the model-connection surfaces (first-run hero + Settings → 模型). The root overflow bug was a 32px provider logo dropped into an 18px grid track. Rather than patch the bespoke grid, the rows were rebuilt on two new governed @maka/ui primitives (Item, Accordion), and the surrounding first-run page and the Settings model surfaces were tidied up in the same vein.

Changes

Shared primitives (@maka/ui)

  • item.tsx (new): Item / ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions on Base UI useRender + our cn/cva. The media slot is shrink-0 with no fixed width, so it sizes to whatever icon it wraps and the icon-track mismatch cannot recur. Clickable-row hover stays neutral (faint foreground wash), reserving brand accent for active rows. Sub-slots default to span (not div/p) so a row rendered as={button} stays valid HTML.
  • accordion.tsx (new): governed Base UI Accordion, used by the model-connections list.

First-run hero (needs_connection), selection-led redesign

  • Title leads, the three setup steps compress into one quiet numbered stepper, and a scrollable provider list (Item rows) is the subject of the page.
  • The dead .maka-onboarding-card / .maka-onboarding-grid grid CSS (and its orphaned keyframes) is removed.

Settings → 模型, model-connections section

  • Rebuilt the strip as a provider→connection accordion of Item rows (was a horizontal scroller that clipped chips mid-word).
  • Renamed the band from「已启用模型」to「模型连接」. The band groups and counts every configured connection, so "enabled" was a lie: the only real source of enabled:false is an OAuth subscription whose login lapsed (stored as enabled:false + lastTestStatus:'needs_reauth'; there is no user enable/disable toggle). Such a connection now reads「需要重新登录」instead of「已禁用」, and the group rollup spans the full group so a group holding only a lapsed login raises a warning header and auto-expands (it previously read idle/green). Status + rollup logic moved to a pure provider-connection-status.ts helper with direct behavioural tests.
  • Converged the provider catalog cards onto the same Item primitive; piloted Base UI Field on the model config sheet; restored the config-sheet close button to the governed quiet icon Button and cleaned up the header alignment.

Real brand icons + de-versioned copy (first-run + Settings 模型)

  • Vendored official brand SVGs from @lobehub/icons-static-svg (MIT) in provider-brand-marks.tsx; ProviderLogoMark delegates to them, so both surfaces show real marks (Claude / OpenAI / Gemini / DeepSeek / Moonshot / Zhipu / Ollama) on a neutral, theme-aware plate.
  • Copy highlights the provider, not model versions (no "GPT/Responses", no "V3"), so it does not go stale as models advance.

Verification

  • Desktop test suite: 1480 pass, 1 environmental flake. The single failure (rive-workflow-tool child-process reap) is a timing race under the parallel isolated runner; it passes 10/10 in isolation. Suite covers Item button-safety, the 模型连接 status/rollup behaviour, copy/contract, hover-neutral, and shared-primitive assertions.
  • @maka/ui build + renderer typecheck: pass.
  • Headless screenshot harness (no foreground window): first-run selection-led layout + real icons + no overflow; provider-workspace strip wraps with all fixtures visible, light/dark × 1280/990. The lapsed-login status/rollup states are covered by the behavioural unit tests rather than screenshots.

Follow-up (not in this PR)

  • Decouple the enabled field in core. Today enabled:false overloads "user-disabled", "not usable for chat", and "OAuth login lapsed". The section-level honesty is fixed here, but the same "请到设置启用" framing is still wrong in chat-readiness / onboarding / health, and connection-status.ts still maps a lapsed login to a "已禁用" presentation. Splitting enabled from auth/readiness status (and fixing the matching copy across the chain) is a separate, larger change. Reviewed from first principles via codex + an independent agent.
  • Provider catalog cards → rows. The catalog still uses a marketplace grid; converting it to Item rows is a different UX (browse vs pick-one) and deserves its own mock/review.

The first-run provider tiles dropped a 32px provider logo into an 18px
grid track, clipping the logo and overlapping the title. Rebuild the
list on a new shared `Item` primitive (shadcn Item rewritten onto Base
UI `useRender`) whose media slot is `shrink-0` with no fixed width, so
it sizes to the icon it wraps and that class of mismatch cannot recur.
- packages/ui: new primitives/item.tsx (Item / ItemMedia / ItemContent /
ItemTitle / ItemDescription / ItemActions); exported from index. The
clickable row hover stays neutral (a faint foreground wash), reserving
the brand `accent` for active/selected rows.
- OnboardingHero: provider list now renders through Item; the dead
.maka-onboarding-card grid CSS that caused the clip is removed.
- tests: re-target the hover-neutral and shared-primitive contracts to
the new primitive instead of the deleted CSS class.
Redesign the needs_connection first-run hero around picking a provider:
title leads, the three setup steps compress into one quiet numbered
stepper, and a scrollable provider list (Item rows) is the subject.
- Vendor official brand SVGs from @lobehub/icons-static-svg (MIT) in
provider-brand-marks.tsx; ProviderLogoMark now delegates to them, so
Settings 模型 rows get the same real icons (Claude/OpenAI/Gemini/
DeepSeek/Moonshot/Zhipu/Ollama) instead of hand-drawn SVGs.
- Neutral, theme-aware provider plate (drops hardcoded light tints).
- De-version provider copy: highlight the provider, not model versions
(no "GPT/Responses", no "V3"); update the OAuth-section contract test
expectations to match.
- Remove now-dead .maka-onboarding-grid block and the orphaned
maka-card-enter / maka-agents-fade-in keyframes.
Brand marks ship under their owners' trademarks; SVGs are MIT-licensed.
The enabled-models band was a horizontal scroller; once a few models
were enabled the chips ran off the right edge and got cut mid-word.
Put the header on its own row and let the chips wrap below, with a
max-height + vertical scroll for long lists, so every enabled model
stays fully visible.
@Astro-HanAstro-Han changed the title feat(ui): Item row primitive; fix first-run provider list overflowfeat(ui): Item row primitive + selection-led first-run & 模型 polishJun 23, 2026
- Enabled-models strip: a flex-wrap left every chip a different width
(sized to its own text) so rows never lined up. Lay the chips on an
even auto-fill grid and truncate long names, so columns align and the
band reads as one tidy group.
- Catalog pill tabs: hide Base UI's sliding rounded-md indicator. Our
pills supply their own fill, and the indicator's square corners poked
out from behind the 999px pill, making the selected state look warped.
Wrap Base UI's Accordion (Root / Item / Header / Trigger / Panel) the
same way the Tabs primitive is wrapped (cn + data-slot), and export the
Primitive-prefixed aliases from @maka/ui. Gives the design system a
single reusable, governed disclosure component instead of hand-rolled
expand/collapse.
The enabled-models list flattened providers and connections into one
peer level, which read wrong (a connection and its provider looked like
siblings) and felt cramped. Rebuild it as a Base UI Accordion: each
provider is a collapsible row that owns its connections, with the worst
connection status rolled up to the header so problems show while
collapsed. The provider holding the default connection and any failing /
needs-relogin provider start expanded; healthy ones stay compact.
Connection rows render on the shared Item primitive; the old bespoke
chip grid and its CSS are removed. Contract tests updated: the @maka/ui
import check tolerates multi-line formatting and now also pins the
Accordion + Item primitives, and the no-raw-button rule allows Base UI's
`render={<button/>}` polymorphic target.
The zai-coding-plan provider reused the legacy 智谱 (Zhipu) molecule mark,
but the product surfaced here ('Z.AI Coding Plan') carries the Z.ai
wordmark — a stylized 'Z' of three slanted strokes. Vendor the official
Z.ai mark (monochrome, currentColor) from @lobehub/icons-static-svg and
point the zai-coding-plan case at it.
The enabled-models accordion sat in a bordered, filled card — the
heaviest element on the 模型 page, clashing with the catalog grid below
and reading as 'two visual languages'. Drop the card box for a seamless
hairline-bounded list (whitespace + logo plates carry the structure),
and align the columns: connection rows now sit directly under the
provider name (pl 49 = pad-x 6 + logo 32 + gap 11) with their status
right edge aligned to the provider rollup (pr 32 = pad-x 6 + chevron 15
+ meta gap 11). Healthy statuses stay quiet; only problems take color.
The 模型 catalog (国内/海外/本地 cards) and the OAuth login cards were
hand-written: a Button (or, when gated, a raw <div>) wrapping <span>
copy styled by bespoke .providerCatalogCard CSS. They were the last part
of the page not on the shared shadcn Item primitive, so the page still
mixed two component languages.
Render every catalog + OAuth entry as a governed Item (ItemMedia ·
ItemContent[ItemTitle/ItemDescription] · ItemActions) — the same
primitive the enabled connections use — in a seamless single-column
hairline list. Gated providers become an inert div Item (no render prop)
with a real trailing state badge instead of an absolute ::after, so the
provider-surface gate still keeps them unclickable. Removes the dead
.providerCatalogCard CSS skin.
The two contract tests that pinned the old card geometry (8px radius,
fixed 172px height, auto-fill 198px grid, .providerCatalogCard/Copy
class names, ::after badges) are repointed to the new intent: governed
.providerCatalogRow chrome shared across all tabs, single-column
seamless list, squared (non-pill) badges. Typecheck + all 1467 desktop
tests pass.
The category / preview / login badge sat inside ItemTitle (the row's
first line), so it pinned to the title baseline instead of the row
center on two-line rows. Move it into ItemActions next to the chevron —
a direct child of the row's items-center flex — so badge and chevron
share the row's vertical center.
Two papercuts found while testing the running app:
- The model page opened with a '.settingsPageIntro' row that paired a
non-actionable hint ('如果配置遇到问题,可以查看配置指南。' — plain
text, no link) with a 'N 个模型' badge that just restated the count
already shown by the 已启用模型 header right below it. Removed the
whole intro row (and its now-orphan CSS); the page starts at the
enabled-models list.
- The unified row lists carried BOTH a container top+bottom border AND
inner row separators, so the catalog stacked a line under the tabs and
doubled a line against the 自定义供应商 border. Drop the container
borders on all three lists (enabled / market / oauth); inner hairlines
between rows now carry the separation on their own.
Convert ConnectionDetail's 连接标识 / 服务地址 / 模型密钥 rows from the
hand-written <label><span/> markup to the governed Base UI Field
primitive (FieldRoot + Label + FieldDescription from @maka/ui), which
already existed but had no consumers. The label tone is pinned to the
established Settings caption (text-xs / foreground-60) so the rows stay
visually identical; the parenthetical state hints (OAuth 固定 / 已设置…)
now render on their own FieldDescription line under the label.
Scoped to this one sheet as a pilot: AddProviderForm and every other
Settings form stay on the legacy markup. The model-oauth copy contract
is repointed to the new Field structure while keeping the copy
Chinese-first.
The provider config sheet's close button is absolutely positioned in the
top-right corner (.providerConfigSheetClose, 30px at right:14px), but the
ConnectionDetail header's right-aligned status badges (默认 / 国内) were
laid out flush to the content edge and slid underneath the X. Reserve the
corner with a 36px right padding on .providerEditor header so the badges
(and any long provider title) clear the close affordance.
…buttons
Every governed @maka/ui <Button> in ProvidersPanel also carried a
className="maka-button". That legacy class is defined in maka-tokens.css
under @layer components, but the Button primitive's own styling lives in
@layer utilities, which wins under Tailwind v4 layer ordering — so the
maka-button class was inert dead weight on these buttons (colors, size,
border, radius, hover, focus and disabled all came from the Button
variant, not maka-button).
Remove all 18 occurrences. Repoint the three model-oauth contract
assertions that pinned the class, and add an a11y-contract guard so a
governed Button can't silently regrow the legacy class. The legacy
maka-button system stays in place for the raw <button> holdouts in other
surfaces (composer, memory, network gateway) that still rely on it.
…tton
Three layout fixes on the provider config sheet, verified live via CDP:
- Refresh button stranded ~36px from the card's right edge. Root cause:
the close-button clearance rule was written as the descendant selector
`.providerEditor header`, which also matched the nested
`.modelTableHeader` and leaked its `padding-right: 36px` onto the model
card, capping the right-aligned button short of the edge. Scope it to
the direct child `.providerEditor > header` so only the sheet's own
title/badges header reserves the corner.
- Model table header switched to `align-items: flex-start` so the refresh
button sits on the title line instead of floating mid-block, and the
grid gets an explicit `minmax(0, 1fr)` column so the header spans the
full card width.
- Close button raised to `top: 10px` so it centers on the header's first
line, lining up with the title and the right-aligned status badges.
.providerConfigSheetClose hand-rolled rest/hover/focus styles on top of the
governed Button but leaned on --settings-surface-* / --text* tokens that were
never defined, so hovering the X did nothing. Strip the class down to position
+ circular shape and let the Button's variant="quiet" size="icon-sm" own the
rest/hover/focus states.
Update the contract test to assert the close button reuses the quiet icon
Button, replacing the assertion on the now-removed hand-written focus outline.
…m-primitive
# Conflicts:
#	apps/desktop/src/renderer/OnboardingHero.tsx
…safe
Item is routinely rendered as a <button> (provider rows, OAuth cards,
connection rows), but ItemMedia/ItemContent/ItemTitle/ItemActions defaulted to
<div> and ItemDescription to <p> — flow content that is invalid inside a button
and trips React DOM-nesting checks. Default the sub-slots to <span>; the
flex/line-clamp classes lay them out identically, and a span is valid inside
both a button and a div, so call sites never need render={<span/>}.
Add a renderToStaticMarkup regression test asserting a clickable Item emits a
<button> with no <div>/<p> inside.
…logins
The Settings → 模型 band was titled "已启用模型" but grouped and counted
every connection, and labeled OAuth subscription logins that had lapsed
(stored as enabled:false + needs_reauth) as "已禁用". A connection that
only needed re-login read as user-disabled, and the enabled-only rollup
left a group holding only a lapsed login showing idle at the header.
- Rename the band to「模型连接」(it is the configured-connection list);
count and empty state stay on the full list, now honestly named.
- chipStatusText: needs_reauth wins over the disabled check (需要重新登录);
a bare enabled:false (legacy migration only) reads 暂不可用, never 已禁用.
- rollupForGroup spans the full group, so a lapsed-only group raises a
warn header and auto-expands instead of reading idle.
- Extract chipStatusText/rollupForGroup into a pure provider-connection-status
module with direct behavioural tests; relax the contract test to verify the
dedicated helper import.
The deeper enabled/auth state-model decoupling and the chat-readiness /
health copy stay as the documented follow-up.
@Astro-Han
Astro-Han merged commit 3044f9a into mainJun 24, 2026
@Astro-Han
Astro-Han deleted the claude/onboarding-item-primitive branch June 24, 2026 07:01
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.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(ui): Item row primitive + selection-led first-run & 模型 polish - #162

Merged
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive
Jun 24, 2026
Merged

feat(ui): Item row primitive + selection-led first-run & 模型 polish#162
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Started as a first-run overflow fix and grew into a focused polish of the model-connection surfaces (first-run hero + Settings → 模型). The root overflow bug was a 32px provider logo dropped into an 18px grid track. Rather than patch the bespoke grid, the rows were rebuilt on two new governed @maka/ui primitives (Item, Accordion), and the surrounding first-run page and the Settings model surfaces were tidied up in the same vein.

Changes

Shared primitives (@maka/ui)

  • item.tsx (new): Item / ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions on Base UI useRender + our cn/cva. The media slot is shrink-0 with no fixed width, so it sizes to whatever icon it wraps and the icon-track mismatch cannot recur. Clickable-row hover stays neutral (faint foreground wash), reserving brand accent for active rows. Sub-slots default to span (not div/p) so a row rendered as={button} stays valid HTML.
  • accordion.tsx (new): governed Base UI Accordion, used by the model-connections list.

First-run hero (needs_connection), selection-led redesign

  • Title leads, the three setup steps compress into one quiet numbered stepper, and a scrollable provider list (Item rows) is the subject of the page.
  • The dead .maka-onboarding-card / .maka-onboarding-grid grid CSS (and its orphaned keyframes) is removed.

Settings → 模型, model-connections section

  • Rebuilt the strip as a provider→connection accordion of Item rows (was a horizontal scroller that clipped chips mid-word).
  • Renamed the band from「已启用模型」to「模型连接」. The band groups and counts every configured connection, so "enabled" was a lie: the only real source of enabled:false is an OAuth subscription whose login lapsed (stored as enabled:false + lastTestStatus:'needs_reauth'; there is no user enable/disable toggle). Such a connection now reads「需要重新登录」instead of「已禁用」, and the group rollup spans the full group so a group holding only a lapsed login raises a warning header and auto-expands (it previously read idle/green). Status + rollup logic moved to a pure provider-connection-status.ts helper with direct behavioural tests.
  • Converged the provider catalog cards onto the same Item primitive; piloted Base UI Field on the model config sheet; restored the config-sheet close button to the governed quiet icon Button and cleaned up the header alignment.

Real brand icons + de-versioned copy (first-run + Settings 模型)

  • Vendored official brand SVGs from @lobehub/icons-static-svg (MIT) in provider-brand-marks.tsx; ProviderLogoMark delegates to them, so both surfaces show real marks (Claude / OpenAI / Gemini / DeepSeek / Moonshot / Zhipu / Ollama) on a neutral, theme-aware plate.
  • Copy highlights the provider, not model versions (no "GPT/Responses", no "V3"), so it does not go stale as models advance.

Verification

  • Desktop test suite: 1480 pass, 1 environmental flake. The single failure (rive-workflow-tool child-process reap) is a timing race under the parallel isolated runner; it passes 10/10 in isolation. Suite covers Item button-safety, the 模型连接 status/rollup behaviour, copy/contract, hover-neutral, and shared-primitive assertions.
  • @maka/ui build + renderer typecheck: pass.
  • Headless screenshot harness (no foreground window): first-run selection-led layout + real icons + no overflow; provider-workspace strip wraps with all fixtures visible, light/dark × 1280/990. The lapsed-login status/rollup states are covered by the behavioural unit tests rather than screenshots.

Follow-up (not in this PR)

  • Decouple the enabled field in core. Today enabled:false overloads "user-disabled", "not usable for chat", and "OAuth login lapsed". The section-level honesty is fixed here, but the same "请到设置启用" framing is still wrong in chat-readiness / onboarding / health, and connection-status.ts still maps a lapsed login to a "已禁用" presentation. Splitting enabled from auth/readiness status (and fixing the matching copy across the chain) is a separate, larger change. Reviewed from first principles via codex + an independent agent.
  • Provider catalog cards → rows. The catalog still uses a marketplace grid; converting it to Item rows is a different UX (browse vs pick-one) and deserves its own mock/review.

The first-run provider tiles dropped a 32px provider logo into an 18px
grid track, clipping the logo and overlapping the title. Rebuild the
list on a new shared `Item` primitive (shadcn Item rewritten onto Base
UI `useRender`) whose media slot is `shrink-0` with no fixed width, so
it sizes to the icon it wraps and that class of mismatch cannot recur.
- packages/ui: new primitives/item.tsx (Item / ItemMedia / ItemContent /
ItemTitle / ItemDescription / ItemActions); exported from index. The
clickable row hover stays neutral (a faint foreground wash), reserving
the brand `accent` for active/selected rows.
- OnboardingHero: provider list now renders through Item; the dead
.maka-onboarding-card grid CSS that caused the clip is removed.
- tests: re-target the hover-neutral and shared-primitive contracts to
the new primitive instead of the deleted CSS class.
Redesign the needs_connection first-run hero around picking a provider:
title leads, the three setup steps compress into one quiet numbered
stepper, and a scrollable provider list (Item rows) is the subject.
- Vendor official brand SVGs from @lobehub/icons-static-svg (MIT) in
provider-brand-marks.tsx; ProviderLogoMark now delegates to them, so
Settings 模型 rows get the same real icons (Claude/OpenAI/Gemini/
DeepSeek/Moonshot/Zhipu/Ollama) instead of hand-drawn SVGs.
- Neutral, theme-aware provider plate (drops hardcoded light tints).
- De-version provider copy: highlight the provider, not model versions
(no "GPT/Responses", no "V3"); update the OAuth-section contract test
expectations to match.
- Remove now-dead .maka-onboarding-grid block and the orphaned
maka-card-enter / maka-agents-fade-in keyframes.
Brand marks ship under their owners' trademarks; SVGs are MIT-licensed.
The enabled-models band was a horizontal scroller; once a few models
were enabled the chips ran off the right edge and got cut mid-word.
Put the header on its own row and let the chips wrap below, with a
max-height + vertical scroll for long lists, so every enabled model
stays fully visible.
@Astro-HanAstro-Han changed the title feat(ui): Item row primitive; fix first-run provider list overflowfeat(ui): Item row primitive + selection-led first-run & 模型 polishJun 23, 2026
- Enabled-models strip: a flex-wrap left every chip a different width
(sized to its own text) so rows never lined up. Lay the chips on an
even auto-fill grid and truncate long names, so columns align and the
band reads as one tidy group.
- Catalog pill tabs: hide Base UI's sliding rounded-md indicator. Our
pills supply their own fill, and the indicator's square corners poked
out from behind the 999px pill, making the selected state look warped.
Wrap Base UI's Accordion (Root / Item / Header / Trigger / Panel) the
same way the Tabs primitive is wrapped (cn + data-slot), and export the
Primitive-prefixed aliases from @maka/ui. Gives the design system a
single reusable, governed disclosure component instead of hand-rolled
expand/collapse.
The enabled-models list flattened providers and connections into one
peer level, which read wrong (a connection and its provider looked like
siblings) and felt cramped. Rebuild it as a Base UI Accordion: each
provider is a collapsible row that owns its connections, with the worst
connection status rolled up to the header so problems show while
collapsed. The provider holding the default connection and any failing /
needs-relogin provider start expanded; healthy ones stay compact.
Connection rows render on the shared Item primitive; the old bespoke
chip grid and its CSS are removed. Contract tests updated: the @maka/ui
import check tolerates multi-line formatting and now also pins the
Accordion + Item primitives, and the no-raw-button rule allows Base UI's
`render={<button/>}` polymorphic target.
The zai-coding-plan provider reused the legacy 智谱 (Zhipu) molecule mark,
but the product surfaced here ('Z.AI Coding Plan') carries the Z.ai
wordmark — a stylized 'Z' of three slanted strokes. Vendor the official
Z.ai mark (monochrome, currentColor) from @lobehub/icons-static-svg and
point the zai-coding-plan case at it.
The enabled-models accordion sat in a bordered, filled card — the
heaviest element on the 模型 page, clashing with the catalog grid below
and reading as 'two visual languages'. Drop the card box for a seamless
hairline-bounded list (whitespace + logo plates carry the structure),
and align the columns: connection rows now sit directly under the
provider name (pl 49 = pad-x 6 + logo 32 + gap 11) with their status
right edge aligned to the provider rollup (pr 32 = pad-x 6 + chevron 15
+ meta gap 11). Healthy statuses stay quiet; only problems take color.
The 模型 catalog (国内/海外/本地 cards) and the OAuth login cards were
hand-written: a Button (or, when gated, a raw <div>) wrapping <span>
copy styled by bespoke .providerCatalogCard CSS. They were the last part
of the page not on the shared shadcn Item primitive, so the page still
mixed two component languages.
Render every catalog + OAuth entry as a governed Item (ItemMedia ·
ItemContent[ItemTitle/ItemDescription] · ItemActions) — the same
primitive the enabled connections use — in a seamless single-column
hairline list. Gated providers become an inert div Item (no render prop)
with a real trailing state badge instead of an absolute ::after, so the
provider-surface gate still keeps them unclickable. Removes the dead
.providerCatalogCard CSS skin.
The two contract tests that pinned the old card geometry (8px radius,
fixed 172px height, auto-fill 198px grid, .providerCatalogCard/Copy
class names, ::after badges) are repointed to the new intent: governed
.providerCatalogRow chrome shared across all tabs, single-column
seamless list, squared (non-pill) badges. Typecheck + all 1467 desktop
tests pass.
The category / preview / login badge sat inside ItemTitle (the row's
first line), so it pinned to the title baseline instead of the row
center on two-line rows. Move it into ItemActions next to the chevron —
a direct child of the row's items-center flex — so badge and chevron
share the row's vertical center.
Two papercuts found while testing the running app:
- The model page opened with a '.settingsPageIntro' row that paired a
non-actionable hint ('如果配置遇到问题,可以查看配置指南。' — plain
text, no link) with a 'N 个模型' badge that just restated the count
already shown by the 已启用模型 header right below it. Removed the
whole intro row (and its now-orphan CSS); the page starts at the
enabled-models list.
- The unified row lists carried BOTH a container top+bottom border AND
inner row separators, so the catalog stacked a line under the tabs and
doubled a line against the 自定义供应商 border. Drop the container
borders on all three lists (enabled / market / oauth); inner hairlines
between rows now carry the separation on their own.
Convert ConnectionDetail's 连接标识 / 服务地址 / 模型密钥 rows from the
hand-written <label><span/> markup to the governed Base UI Field
primitive (FieldRoot + Label + FieldDescription from @maka/ui), which
already existed but had no consumers. The label tone is pinned to the
established Settings caption (text-xs / foreground-60) so the rows stay
visually identical; the parenthetical state hints (OAuth 固定 / 已设置…)
now render on their own FieldDescription line under the label.
Scoped to this one sheet as a pilot: AddProviderForm and every other
Settings form stay on the legacy markup. The model-oauth copy contract
is repointed to the new Field structure while keeping the copy
Chinese-first.
The provider config sheet's close button is absolutely positioned in the
top-right corner (.providerConfigSheetClose, 30px at right:14px), but the
ConnectionDetail header's right-aligned status badges (默认 / 国内) were
laid out flush to the content edge and slid underneath the X. Reserve the
corner with a 36px right padding on .providerEditor header so the badges
(and any long provider title) clear the close affordance.
…buttons
Every governed @maka/ui <Button> in ProvidersPanel also carried a
className="maka-button". That legacy class is defined in maka-tokens.css
under @layer components, but the Button primitive's own styling lives in
@layer utilities, which wins under Tailwind v4 layer ordering — so the
maka-button class was inert dead weight on these buttons (colors, size,
border, radius, hover, focus and disabled all came from the Button
variant, not maka-button).
Remove all 18 occurrences. Repoint the three model-oauth contract
assertions that pinned the class, and add an a11y-contract guard so a
governed Button can't silently regrow the legacy class. The legacy
maka-button system stays in place for the raw <button> holdouts in other
surfaces (composer, memory, network gateway) that still rely on it.
…tton
Three layout fixes on the provider config sheet, verified live via CDP:
- Refresh button stranded ~36px from the card's right edge. Root cause:
the close-button clearance rule was written as the descendant selector
`.providerEditor header`, which also matched the nested
`.modelTableHeader` and leaked its `padding-right: 36px` onto the model
card, capping the right-aligned button short of the edge. Scope it to
the direct child `.providerEditor > header` so only the sheet's own
title/badges header reserves the corner.
- Model table header switched to `align-items: flex-start` so the refresh
button sits on the title line instead of floating mid-block, and the
grid gets an explicit `minmax(0, 1fr)` column so the header spans the
full card width.
- Close button raised to `top: 10px` so it centers on the header's first
line, lining up with the title and the right-aligned status badges.
.providerConfigSheetClose hand-rolled rest/hover/focus styles on top of the
governed Button but leaned on --settings-surface-* / --text* tokens that were
never defined, so hovering the X did nothing. Strip the class down to position
+ circular shape and let the Button's variant="quiet" size="icon-sm" own the
rest/hover/focus states.
Update the contract test to assert the close button reuses the quiet icon
Button, replacing the assertion on the now-removed hand-written focus outline.
…m-primitive
# Conflicts:
#	apps/desktop/src/renderer/OnboardingHero.tsx
…safe
Item is routinely rendered as a <button> (provider rows, OAuth cards,
connection rows), but ItemMedia/ItemContent/ItemTitle/ItemActions defaulted to
<div> and ItemDescription to <p> — flow content that is invalid inside a button
and trips React DOM-nesting checks. Default the sub-slots to <span>; the
flex/line-clamp classes lay them out identically, and a span is valid inside
both a button and a div, so call sites never need render={<span/>}.
Add a renderToStaticMarkup regression test asserting a clickable Item emits a
<button> with no <div>/<p> inside.
…logins
The Settings → 模型 band was titled "已启用模型" but grouped and counted
every connection, and labeled OAuth subscription logins that had lapsed
(stored as enabled:false + needs_reauth) as "已禁用". A connection that
only needed re-login read as user-disabled, and the enabled-only rollup
left a group holding only a lapsed login showing idle at the header.
- Rename the band to「模型连接」(it is the configured-connection list);
count and empty state stay on the full list, now honestly named.
- chipStatusText: needs_reauth wins over the disabled check (需要重新登录);
a bare enabled:false (legacy migration only) reads 暂不可用, never 已禁用.
- rollupForGroup spans the full group, so a lapsed-only group raises a
warn header and auto-expands instead of reading idle.
- Extract chipStatusText/rollupForGroup into a pure provider-connection-status
module with direct behavioural tests; relax the contract test to verify the
dedicated helper import.
The deeper enabled/auth state-model decoupling and the chat-readiness /
health copy stay as the documented follow-up.
@Astro-Han
Astro-Han merged commit 3044f9a into mainJun 24, 2026
@Astro-Han
Astro-Han deleted the claude/onboarding-item-primitive branch June 24, 2026 07:01
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.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(ui): Item row primitive + selection-led first-run & 模型 polish - #162

Merged
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive
Jun 24, 2026
Merged

feat(ui): Item row primitive + selection-led first-run & 模型 polish#162
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Started as a first-run overflow fix and grew into a focused polish of the model-connection surfaces (first-run hero + Settings → 模型). The root overflow bug was a 32px provider logo dropped into an 18px grid track. Rather than patch the bespoke grid, the rows were rebuilt on two new governed @maka/ui primitives (Item, Accordion), and the surrounding first-run page and the Settings model surfaces were tidied up in the same vein.

Changes

Shared primitives (@maka/ui)

  • item.tsx (new): Item / ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions on Base UI useRender + our cn/cva. The media slot is shrink-0 with no fixed width, so it sizes to whatever icon it wraps and the icon-track mismatch cannot recur. Clickable-row hover stays neutral (faint foreground wash), reserving brand accent for active rows. Sub-slots default to span (not div/p) so a row rendered as={button} stays valid HTML.
  • accordion.tsx (new): governed Base UI Accordion, used by the model-connections list.

First-run hero (needs_connection), selection-led redesign

  • Title leads, the three setup steps compress into one quiet numbered stepper, and a scrollable provider list (Item rows) is the subject of the page.
  • The dead .maka-onboarding-card / .maka-onboarding-grid grid CSS (and its orphaned keyframes) is removed.

Settings → 模型, model-connections section

  • Rebuilt the strip as a provider→connection accordion of Item rows (was a horizontal scroller that clipped chips mid-word).
  • Renamed the band from「已启用模型」to「模型连接」. The band groups and counts every configured connection, so "enabled" was a lie: the only real source of enabled:false is an OAuth subscription whose login lapsed (stored as enabled:false + lastTestStatus:'needs_reauth'; there is no user enable/disable toggle). Such a connection now reads「需要重新登录」instead of「已禁用」, and the group rollup spans the full group so a group holding only a lapsed login raises a warning header and auto-expands (it previously read idle/green). Status + rollup logic moved to a pure provider-connection-status.ts helper with direct behavioural tests.
  • Converged the provider catalog cards onto the same Item primitive; piloted Base UI Field on the model config sheet; restored the config-sheet close button to the governed quiet icon Button and cleaned up the header alignment.

Real brand icons + de-versioned copy (first-run + Settings 模型)

  • Vendored official brand SVGs from @lobehub/icons-static-svg (MIT) in provider-brand-marks.tsx; ProviderLogoMark delegates to them, so both surfaces show real marks (Claude / OpenAI / Gemini / DeepSeek / Moonshot / Zhipu / Ollama) on a neutral, theme-aware plate.
  • Copy highlights the provider, not model versions (no "GPT/Responses", no "V3"), so it does not go stale as models advance.

Verification

  • Desktop test suite: 1480 pass, 1 environmental flake. The single failure (rive-workflow-tool child-process reap) is a timing race under the parallel isolated runner; it passes 10/10 in isolation. Suite covers Item button-safety, the 模型连接 status/rollup behaviour, copy/contract, hover-neutral, and shared-primitive assertions.
  • @maka/ui build + renderer typecheck: pass.
  • Headless screenshot harness (no foreground window): first-run selection-led layout + real icons + no overflow; provider-workspace strip wraps with all fixtures visible, light/dark × 1280/990. The lapsed-login status/rollup states are covered by the behavioural unit tests rather than screenshots.

Follow-up (not in this PR)

  • Decouple the enabled field in core. Today enabled:false overloads "user-disabled", "not usable for chat", and "OAuth login lapsed". The section-level honesty is fixed here, but the same "请到设置启用" framing is still wrong in chat-readiness / onboarding / health, and connection-status.ts still maps a lapsed login to a "已禁用" presentation. Splitting enabled from auth/readiness status (and fixing the matching copy across the chain) is a separate, larger change. Reviewed from first principles via codex + an independent agent.
  • Provider catalog cards → rows. The catalog still uses a marketplace grid; converting it to Item rows is a different UX (browse vs pick-one) and deserves its own mock/review.

The first-run provider tiles dropped a 32px provider logo into an 18px
grid track, clipping the logo and overlapping the title. Rebuild the
list on a new shared `Item` primitive (shadcn Item rewritten onto Base
UI `useRender`) whose media slot is `shrink-0` with no fixed width, so
it sizes to the icon it wraps and that class of mismatch cannot recur.
- packages/ui: new primitives/item.tsx (Item / ItemMedia / ItemContent /
ItemTitle / ItemDescription / ItemActions); exported from index. The
clickable row hover stays neutral (a faint foreground wash), reserving
the brand `accent` for active/selected rows.
- OnboardingHero: provider list now renders through Item; the dead
.maka-onboarding-card grid CSS that caused the clip is removed.
- tests: re-target the hover-neutral and shared-primitive contracts to
the new primitive instead of the deleted CSS class.
Redesign the needs_connection first-run hero around picking a provider:
title leads, the three setup steps compress into one quiet numbered
stepper, and a scrollable provider list (Item rows) is the subject.
- Vendor official brand SVGs from @lobehub/icons-static-svg (MIT) in
provider-brand-marks.tsx; ProviderLogoMark now delegates to them, so
Settings 模型 rows get the same real icons (Claude/OpenAI/Gemini/
DeepSeek/Moonshot/Zhipu/Ollama) instead of hand-drawn SVGs.
- Neutral, theme-aware provider plate (drops hardcoded light tints).
- De-version provider copy: highlight the provider, not model versions
(no "GPT/Responses", no "V3"); update the OAuth-section contract test
expectations to match.
- Remove now-dead .maka-onboarding-grid block and the orphaned
maka-card-enter / maka-agents-fade-in keyframes.
Brand marks ship under their owners' trademarks; SVGs are MIT-licensed.
The enabled-models band was a horizontal scroller; once a few models
were enabled the chips ran off the right edge and got cut mid-word.
Put the header on its own row and let the chips wrap below, with a
max-height + vertical scroll for long lists, so every enabled model
stays fully visible.
@Astro-HanAstro-Han changed the title feat(ui): Item row primitive; fix first-run provider list overflowfeat(ui): Item row primitive + selection-led first-run & 模型 polishJun 23, 2026
- Enabled-models strip: a flex-wrap left every chip a different width
(sized to its own text) so rows never lined up. Lay the chips on an
even auto-fill grid and truncate long names, so columns align and the
band reads as one tidy group.
- Catalog pill tabs: hide Base UI's sliding rounded-md indicator. Our
pills supply their own fill, and the indicator's square corners poked
out from behind the 999px pill, making the selected state look warped.
Wrap Base UI's Accordion (Root / Item / Header / Trigger / Panel) the
same way the Tabs primitive is wrapped (cn + data-slot), and export the
Primitive-prefixed aliases from @maka/ui. Gives the design system a
single reusable, governed disclosure component instead of hand-rolled
expand/collapse.
The enabled-models list flattened providers and connections into one
peer level, which read wrong (a connection and its provider looked like
siblings) and felt cramped. Rebuild it as a Base UI Accordion: each
provider is a collapsible row that owns its connections, with the worst
connection status rolled up to the header so problems show while
collapsed. The provider holding the default connection and any failing /
needs-relogin provider start expanded; healthy ones stay compact.
Connection rows render on the shared Item primitive; the old bespoke
chip grid and its CSS are removed. Contract tests updated: the @maka/ui
import check tolerates multi-line formatting and now also pins the
Accordion + Item primitives, and the no-raw-button rule allows Base UI's
`render={<button/>}` polymorphic target.
The zai-coding-plan provider reused the legacy 智谱 (Zhipu) molecule mark,
but the product surfaced here ('Z.AI Coding Plan') carries the Z.ai
wordmark — a stylized 'Z' of three slanted strokes. Vendor the official
Z.ai mark (monochrome, currentColor) from @lobehub/icons-static-svg and
point the zai-coding-plan case at it.
The enabled-models accordion sat in a bordered, filled card — the
heaviest element on the 模型 page, clashing with the catalog grid below
and reading as 'two visual languages'. Drop the card box for a seamless
hairline-bounded list (whitespace + logo plates carry the structure),
and align the columns: connection rows now sit directly under the
provider name (pl 49 = pad-x 6 + logo 32 + gap 11) with their status
right edge aligned to the provider rollup (pr 32 = pad-x 6 + chevron 15
+ meta gap 11). Healthy statuses stay quiet; only problems take color.
The 模型 catalog (国内/海外/本地 cards) and the OAuth login cards were
hand-written: a Button (or, when gated, a raw <div>) wrapping <span>
copy styled by bespoke .providerCatalogCard CSS. They were the last part
of the page not on the shared shadcn Item primitive, so the page still
mixed two component languages.
Render every catalog + OAuth entry as a governed Item (ItemMedia ·
ItemContent[ItemTitle/ItemDescription] · ItemActions) — the same
primitive the enabled connections use — in a seamless single-column
hairline list. Gated providers become an inert div Item (no render prop)
with a real trailing state badge instead of an absolute ::after, so the
provider-surface gate still keeps them unclickable. Removes the dead
.providerCatalogCard CSS skin.
The two contract tests that pinned the old card geometry (8px radius,
fixed 172px height, auto-fill 198px grid, .providerCatalogCard/Copy
class names, ::after badges) are repointed to the new intent: governed
.providerCatalogRow chrome shared across all tabs, single-column
seamless list, squared (non-pill) badges. Typecheck + all 1467 desktop
tests pass.
The category / preview / login badge sat inside ItemTitle (the row's
first line), so it pinned to the title baseline instead of the row
center on two-line rows. Move it into ItemActions next to the chevron —
a direct child of the row's items-center flex — so badge and chevron
share the row's vertical center.
Two papercuts found while testing the running app:
- The model page opened with a '.settingsPageIntro' row that paired a
non-actionable hint ('如果配置遇到问题,可以查看配置指南。' — plain
text, no link) with a 'N 个模型' badge that just restated the count
already shown by the 已启用模型 header right below it. Removed the
whole intro row (and its now-orphan CSS); the page starts at the
enabled-models list.
- The unified row lists carried BOTH a container top+bottom border AND
inner row separators, so the catalog stacked a line under the tabs and
doubled a line against the 自定义供应商 border. Drop the container
borders on all three lists (enabled / market / oauth); inner hairlines
between rows now carry the separation on their own.
Convert ConnectionDetail's 连接标识 / 服务地址 / 模型密钥 rows from the
hand-written <label><span/> markup to the governed Base UI Field
primitive (FieldRoot + Label + FieldDescription from @maka/ui), which
already existed but had no consumers. The label tone is pinned to the
established Settings caption (text-xs / foreground-60) so the rows stay
visually identical; the parenthetical state hints (OAuth 固定 / 已设置…)
now render on their own FieldDescription line under the label.
Scoped to this one sheet as a pilot: AddProviderForm and every other
Settings form stay on the legacy markup. The model-oauth copy contract
is repointed to the new Field structure while keeping the copy
Chinese-first.
The provider config sheet's close button is absolutely positioned in the
top-right corner (.providerConfigSheetClose, 30px at right:14px), but the
ConnectionDetail header's right-aligned status badges (默认 / 国内) were
laid out flush to the content edge and slid underneath the X. Reserve the
corner with a 36px right padding on .providerEditor header so the badges
(and any long provider title) clear the close affordance.
…buttons
Every governed @maka/ui <Button> in ProvidersPanel also carried a
className="maka-button". That legacy class is defined in maka-tokens.css
under @layer components, but the Button primitive's own styling lives in
@layer utilities, which wins under Tailwind v4 layer ordering — so the
maka-button class was inert dead weight on these buttons (colors, size,
border, radius, hover, focus and disabled all came from the Button
variant, not maka-button).
Remove all 18 occurrences. Repoint the three model-oauth contract
assertions that pinned the class, and add an a11y-contract guard so a
governed Button can't silently regrow the legacy class. The legacy
maka-button system stays in place for the raw <button> holdouts in other
surfaces (composer, memory, network gateway) that still rely on it.
…tton
Three layout fixes on the provider config sheet, verified live via CDP:
- Refresh button stranded ~36px from the card's right edge. Root cause:
the close-button clearance rule was written as the descendant selector
`.providerEditor header`, which also matched the nested
`.modelTableHeader` and leaked its `padding-right: 36px` onto the model
card, capping the right-aligned button short of the edge. Scope it to
the direct child `.providerEditor > header` so only the sheet's own
title/badges header reserves the corner.
- Model table header switched to `align-items: flex-start` so the refresh
button sits on the title line instead of floating mid-block, and the
grid gets an explicit `minmax(0, 1fr)` column so the header spans the
full card width.
- Close button raised to `top: 10px` so it centers on the header's first
line, lining up with the title and the right-aligned status badges.
.providerConfigSheetClose hand-rolled rest/hover/focus styles on top of the
governed Button but leaned on --settings-surface-* / --text* tokens that were
never defined, so hovering the X did nothing. Strip the class down to position
+ circular shape and let the Button's variant="quiet" size="icon-sm" own the
rest/hover/focus states.
Update the contract test to assert the close button reuses the quiet icon
Button, replacing the assertion on the now-removed hand-written focus outline.
…m-primitive
# Conflicts:
#	apps/desktop/src/renderer/OnboardingHero.tsx
…safe
Item is routinely rendered as a <button> (provider rows, OAuth cards,
connection rows), but ItemMedia/ItemContent/ItemTitle/ItemActions defaulted to
<div> and ItemDescription to <p> — flow content that is invalid inside a button
and trips React DOM-nesting checks. Default the sub-slots to <span>; the
flex/line-clamp classes lay them out identically, and a span is valid inside
both a button and a div, so call sites never need render={<span/>}.
Add a renderToStaticMarkup regression test asserting a clickable Item emits a
<button> with no <div>/<p> inside.
…logins
The Settings → 模型 band was titled "已启用模型" but grouped and counted
every connection, and labeled OAuth subscription logins that had lapsed
(stored as enabled:false + needs_reauth) as "已禁用". A connection that
only needed re-login read as user-disabled, and the enabled-only rollup
left a group holding only a lapsed login showing idle at the header.
- Rename the band to「模型连接」(it is the configured-connection list);
count and empty state stay on the full list, now honestly named.
- chipStatusText: needs_reauth wins over the disabled check (需要重新登录);
a bare enabled:false (legacy migration only) reads 暂不可用, never 已禁用.
- rollupForGroup spans the full group, so a lapsed-only group raises a
warn header and auto-expands instead of reading idle.
- Extract chipStatusText/rollupForGroup into a pure provider-connection-status
module with direct behavioural tests; relax the contract test to verify the
dedicated helper import.
The deeper enabled/auth state-model decoupling and the chat-readiness /
health copy stay as the documented follow-up.
@Astro-Han
Astro-Han merged commit 3044f9a into mainJun 24, 2026
@Astro-Han
Astro-Han deleted the claude/onboarding-item-primitive branch June 24, 2026 07:01
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.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(ui): Item row primitive + selection-led first-run & 模型 polish - #162

Merged
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive
Jun 24, 2026
Merged

feat(ui): Item row primitive + selection-led first-run & 模型 polish#162
Astro-Han merged 19 commits into
mainfrom
claude/onboarding-item-primitive

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Started as a first-run overflow fix and grew into a focused polish of the model-connection surfaces (first-run hero + Settings → 模型). The root overflow bug was a 32px provider logo dropped into an 18px grid track. Rather than patch the bespoke grid, the rows were rebuilt on two new governed @maka/ui primitives (Item, Accordion), and the surrounding first-run page and the Settings model surfaces were tidied up in the same vein.

Changes

Shared primitives (@maka/ui)

  • item.tsx (new): Item / ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions on Base UI useRender + our cn/cva. The media slot is shrink-0 with no fixed width, so it sizes to whatever icon it wraps and the icon-track mismatch cannot recur. Clickable-row hover stays neutral (faint foreground wash), reserving brand accent for active rows. Sub-slots default to span (not div/p) so a row rendered as={button} stays valid HTML.
  • accordion.tsx (new): governed Base UI Accordion, used by the model-connections list.

First-run hero (needs_connection), selection-led redesign

  • Title leads, the three setup steps compress into one quiet numbered stepper, and a scrollable provider list (Item rows) is the subject of the page.
  • The dead .maka-onboarding-card / .maka-onboarding-grid grid CSS (and its orphaned keyframes) is removed.

Settings → 模型, model-connections section

  • Rebuilt the strip as a provider→connection accordion of Item rows (was a horizontal scroller that clipped chips mid-word).
  • Renamed the band from「已启用模型」to「模型连接」. The band groups and counts every configured connection, so "enabled" was a lie: the only real source of enabled:false is an OAuth subscription whose login lapsed (stored as enabled:false + lastTestStatus:'needs_reauth'; there is no user enable/disable toggle). Such a connection now reads「需要重新登录」instead of「已禁用」, and the group rollup spans the full group so a group holding only a lapsed login raises a warning header and auto-expands (it previously read idle/green). Status + rollup logic moved to a pure provider-connection-status.ts helper with direct behavioural tests.
  • Converged the provider catalog cards onto the same Item primitive; piloted Base UI Field on the model config sheet; restored the config-sheet close button to the governed quiet icon Button and cleaned up the header alignment.

Real brand icons + de-versioned copy (first-run + Settings 模型)

  • Vendored official brand SVGs from @lobehub/icons-static-svg (MIT) in provider-brand-marks.tsx; ProviderLogoMark delegates to them, so both surfaces show real marks (Claude / OpenAI / Gemini / DeepSeek / Moonshot / Zhipu / Ollama) on a neutral, theme-aware plate.
  • Copy highlights the provider, not model versions (no "GPT/Responses", no "V3"), so it does not go stale as models advance.

Verification

  • Desktop test suite: 1480 pass, 1 environmental flake. The single failure (rive-workflow-tool child-process reap) is a timing race under the parallel isolated runner; it passes 10/10 in isolation. Suite covers Item button-safety, the 模型连接 status/rollup behaviour, copy/contract, hover-neutral, and shared-primitive assertions.
  • @maka/ui build + renderer typecheck: pass.
  • Headless screenshot harness (no foreground window): first-run selection-led layout + real icons + no overflow; provider-workspace strip wraps with all fixtures visible, light/dark × 1280/990. The lapsed-login status/rollup states are covered by the behavioural unit tests rather than screenshots.

Follow-up (not in this PR)

  • Decouple the enabled field in core. Today enabled:false overloads "user-disabled", "not usable for chat", and "OAuth login lapsed". The section-level honesty is fixed here, but the same "请到设置启用" framing is still wrong in chat-readiness / onboarding / health, and connection-status.ts still maps a lapsed login to a "已禁用" presentation. Splitting enabled from auth/readiness status (and fixing the matching copy across the chain) is a separate, larger change. Reviewed from first principles via codex + an independent agent.
  • Provider catalog cards → rows. The catalog still uses a marketplace grid; converting it to Item rows is a different UX (browse vs pick-one) and deserves its own mock/review.

The first-run provider tiles dropped a 32px provider logo into an 18px
grid track, clipping the logo and overlapping the title. Rebuild the
list on a new shared `Item` primitive (shadcn Item rewritten onto Base
UI `useRender`) whose media slot is `shrink-0` with no fixed width, so
it sizes to the icon it wraps and that class of mismatch cannot recur.
- packages/ui: new primitives/item.tsx (Item / ItemMedia / ItemContent /
ItemTitle / ItemDescription / ItemActions); exported from index. The
clickable row hover stays neutral (a faint foreground wash), reserving
the brand `accent` for active/selected rows.
- OnboardingHero: provider list now renders through Item; the dead
.maka-onboarding-card grid CSS that caused the clip is removed.
- tests: re-target the hover-neutral and shared-primitive contracts to
the new primitive instead of the deleted CSS class.
Redesign the needs_connection first-run hero around picking a provider:
title leads, the three setup steps compress into one quiet numbered
stepper, and a scrollable provider list (Item rows) is the subject.
- Vendor official brand SVGs from @lobehub/icons-static-svg (MIT) in
provider-brand-marks.tsx; ProviderLogoMark now delegates to them, so
Settings 模型 rows get the same real icons (Claude/OpenAI/Gemini/
DeepSeek/Moonshot/Zhipu/Ollama) instead of hand-drawn SVGs.
- Neutral, theme-aware provider plate (drops hardcoded light tints).
- De-version provider copy: highlight the provider, not model versions
(no "GPT/Responses", no "V3"); update the OAuth-section contract test
expectations to match.
- Remove now-dead .maka-onboarding-grid block and the orphaned
maka-card-enter / maka-agents-fade-in keyframes.
Brand marks ship under their owners' trademarks; SVGs are MIT-licensed.
The enabled-models band was a horizontal scroller; once a few models
were enabled the chips ran off the right edge and got cut mid-word.
Put the header on its own row and let the chips wrap below, with a
max-height + vertical scroll for long lists, so every enabled model
stays fully visible.
@Astro-HanAstro-Han changed the title feat(ui): Item row primitive; fix first-run provider list overflowfeat(ui): Item row primitive + selection-led first-run & 模型 polishJun 23, 2026
- Enabled-models strip: a flex-wrap left every chip a different width
(sized to its own text) so rows never lined up. Lay the chips on an
even auto-fill grid and truncate long names, so columns align and the
band reads as one tidy group.
- Catalog pill tabs: hide Base UI's sliding rounded-md indicator. Our
pills supply their own fill, and the indicator's square corners poked
out from behind the 999px pill, making the selected state look warped.
Wrap Base UI's Accordion (Root / Item / Header / Trigger / Panel) the
same way the Tabs primitive is wrapped (cn + data-slot), and export the
Primitive-prefixed aliases from @maka/ui. Gives the design system a
single reusable, governed disclosure component instead of hand-rolled
expand/collapse.
The enabled-models list flattened providers and connections into one
peer level, which read wrong (a connection and its provider looked like
siblings) and felt cramped. Rebuild it as a Base UI Accordion: each
provider is a collapsible row that owns its connections, with the worst
connection status rolled up to the header so problems show while
collapsed. The provider holding the default connection and any failing /
needs-relogin provider start expanded; healthy ones stay compact.
Connection rows render on the shared Item primitive; the old bespoke
chip grid and its CSS are removed. Contract tests updated: the @maka/ui
import check tolerates multi-line formatting and now also pins the
Accordion + Item primitives, and the no-raw-button rule allows Base UI's
`render={<button/>}` polymorphic target.
The zai-coding-plan provider reused the legacy 智谱 (Zhipu) molecule mark,
but the product surfaced here ('Z.AI Coding Plan') carries the Z.ai
wordmark — a stylized 'Z' of three slanted strokes. Vendor the official
Z.ai mark (monochrome, currentColor) from @lobehub/icons-static-svg and
point the zai-coding-plan case at it.
The enabled-models accordion sat in a bordered, filled card — the
heaviest element on the 模型 page, clashing with the catalog grid below
and reading as 'two visual languages'. Drop the card box for a seamless
hairline-bounded list (whitespace + logo plates carry the structure),
and align the columns: connection rows now sit directly under the
provider name (pl 49 = pad-x 6 + logo 32 + gap 11) with their status
right edge aligned to the provider rollup (pr 32 = pad-x 6 + chevron 15
+ meta gap 11). Healthy statuses stay quiet; only problems take color.
The 模型 catalog (国内/海外/本地 cards) and the OAuth login cards were
hand-written: a Button (or, when gated, a raw <div>) wrapping <span>
copy styled by bespoke .providerCatalogCard CSS. They were the last part
of the page not on the shared shadcn Item primitive, so the page still
mixed two component languages.
Render every catalog + OAuth entry as a governed Item (ItemMedia ·
ItemContent[ItemTitle/ItemDescription] · ItemActions) — the same
primitive the enabled connections use — in a seamless single-column
hairline list. Gated providers become an inert div Item (no render prop)
with a real trailing state badge instead of an absolute ::after, so the
provider-surface gate still keeps them unclickable. Removes the dead
.providerCatalogCard CSS skin.
The two contract tests that pinned the old card geometry (8px radius,
fixed 172px height, auto-fill 198px grid, .providerCatalogCard/Copy
class names, ::after badges) are repointed to the new intent: governed
.providerCatalogRow chrome shared across all tabs, single-column
seamless list, squared (non-pill) badges. Typecheck + all 1467 desktop
tests pass.
The category / preview / login badge sat inside ItemTitle (the row's
first line), so it pinned to the title baseline instead of the row
center on two-line rows. Move it into ItemActions next to the chevron —
a direct child of the row's items-center flex — so badge and chevron
share the row's vertical center.
Two papercuts found while testing the running app:
- The model page opened with a '.settingsPageIntro' row that paired a
non-actionable hint ('如果配置遇到问题,可以查看配置指南。' — plain
text, no link) with a 'N 个模型' badge that just restated the count
already shown by the 已启用模型 header right below it. Removed the
whole intro row (and its now-orphan CSS); the page starts at the
enabled-models list.
- The unified row lists carried BOTH a container top+bottom border AND
inner row separators, so the catalog stacked a line under the tabs and
doubled a line against the 自定义供应商 border. Drop the container
borders on all three lists (enabled / market / oauth); inner hairlines
between rows now carry the separation on their own.
Convert ConnectionDetail's 连接标识 / 服务地址 / 模型密钥 rows from the
hand-written <label><span/> markup to the governed Base UI Field
primitive (FieldRoot + Label + FieldDescription from @maka/ui), which
already existed but had no consumers. The label tone is pinned to the
established Settings caption (text-xs / foreground-60) so the rows stay
visually identical; the parenthetical state hints (OAuth 固定 / 已设置…)
now render on their own FieldDescription line under the label.
Scoped to this one sheet as a pilot: AddProviderForm and every other
Settings form stay on the legacy markup. The model-oauth copy contract
is repointed to the new Field structure while keeping the copy
Chinese-first.
The provider config sheet's close button is absolutely positioned in the
top-right corner (.providerConfigSheetClose, 30px at right:14px), but the
ConnectionDetail header's right-aligned status badges (默认 / 国内) were
laid out flush to the content edge and slid underneath the X. Reserve the
corner with a 36px right padding on .providerEditor header so the badges
(and any long provider title) clear the close affordance.
…buttons
Every governed @maka/ui <Button> in ProvidersPanel also carried a
className="maka-button". That legacy class is defined in maka-tokens.css
under @layer components, but the Button primitive's own styling lives in
@layer utilities, which wins under Tailwind v4 layer ordering — so the
maka-button class was inert dead weight on these buttons (colors, size,
border, radius, hover, focus and disabled all came from the Button
variant, not maka-button).
Remove all 18 occurrences. Repoint the three model-oauth contract
assertions that pinned the class, and add an a11y-contract guard so a
governed Button can't silently regrow the legacy class. The legacy
maka-button system stays in place for the raw <button> holdouts in other
surfaces (composer, memory, network gateway) that still rely on it.
…tton
Three layout fixes on the provider config sheet, verified live via CDP:
- Refresh button stranded ~36px from the card's right edge. Root cause:
the close-button clearance rule was written as the descendant selector
`.providerEditor header`, which also matched the nested
`.modelTableHeader` and leaked its `padding-right: 36px` onto the model
card, capping the right-aligned button short of the edge. Scope it to
the direct child `.providerEditor > header` so only the sheet's own
title/badges header reserves the corner.
- Model table header switched to `align-items: flex-start` so the refresh
button sits on the title line instead of floating mid-block, and the
grid gets an explicit `minmax(0, 1fr)` column so the header spans the
full card width.
- Close button raised to `top: 10px` so it centers on the header's first
line, lining up with the title and the right-aligned status badges.
.providerConfigSheetClose hand-rolled rest/hover/focus styles on top of the
governed Button but leaned on --settings-surface-* / --text* tokens that were
never defined, so hovering the X did nothing. Strip the class down to position
+ circular shape and let the Button's variant="quiet" size="icon-sm" own the
rest/hover/focus states.
Update the contract test to assert the close button reuses the quiet icon
Button, replacing the assertion on the now-removed hand-written focus outline.
…m-primitive
# Conflicts:
#	apps/desktop/src/renderer/OnboardingHero.tsx
…safe
Item is routinely rendered as a <button> (provider rows, OAuth cards,
connection rows), but ItemMedia/ItemContent/ItemTitle/ItemActions defaulted to
<div> and ItemDescription to <p> — flow content that is invalid inside a button
and trips React DOM-nesting checks. Default the sub-slots to <span>; the
flex/line-clamp classes lay them out identically, and a span is valid inside
both a button and a div, so call sites never need render={<span/>}.
Add a renderToStaticMarkup regression test asserting a clickable Item emits a
<button> with no <div>/<p> inside.
…logins
The Settings → 模型 band was titled "已启用模型" but grouped and counted
every connection, and labeled OAuth subscription logins that had lapsed
(stored as enabled:false + needs_reauth) as "已禁用". A connection that
only needed re-login read as user-disabled, and the enabled-only rollup
left a group holding only a lapsed login showing idle at the header.
- Rename the band to「模型连接」(it is the configured-connection list);
count and empty state stay on the full list, now honestly named.
- chipStatusText: needs_reauth wins over the disabled check (需要重新登录);
a bare enabled:false (legacy migration only) reads 暂不可用, never 已禁用.
- rollupForGroup spans the full group, so a lapsed-only group raises a
warn header and auto-expands instead of reading idle.
- Extract chipStatusText/rollupForGroup into a pure provider-connection-status
module with direct behavioural tests; relax the contract test to verify the
dedicated helper import.
The deeper enabled/auth state-model decoupling and the chat-readiness /
health copy stay as the documented follow-up.
@Astro-Han
Astro-Han merged commit 3044f9a into mainJun 24, 2026
@Astro-Han
Astro-Han deleted the claude/onboarding-item-primitive branch June 24, 2026 07:01
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.

1 participant

@Astro-Han