Skip to content

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) - #554

Merged
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge
Jul 5, 2026
Merged

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9)#554
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

#520 PR9: converge the hand-written settings card surfaces onto a Card primitive, and collapse the four coexisting badge surfaces onto two role-split primitives — pill Badge (emphasis markers) and squared Chip (dense settings status rows).

A review-driven correction: an initial Table primitive was retired before merge — with a single HTML <table> consumer and no second one in sight (the model "table" is a div-based list, not an HTML table), it was premature. The usage stats table now stays a local native <table> in usage-settings-page.tsx.

Why

Refs #520. The renderer had four parallel badge implementations and three hand-written settings card CSS surfaces, each drifting from the shared token system. The convergence goal is one primitive per UI role, governed by contracts instead of ad-hoc CSS.

A key finding drove the badge split: Base UI (@base-ui/react 1.5.0/1.6.0) ships no Badge/Chip/Tag component, and shadcn/ui ships Badge but noChip. So both primitives are self-built (cva + data-slot + Base UI useRender), same pattern as the existing Card/Alert primitives. Settings status rows need compact squared chips (radius-control) for information density — the settings-form-a11y contract explicitly locks "not pills". Forcing them onto the pill Badge would regress that intent and turn the destructive tone into a solid red block. The correct convergence is 4 surfaces → 2 primitives by role, not 4 → 1.

The Table primitive was added in an earlier commit but removed per review: the only HTML <table> consumer is the usage stats table, and the model "table" (.modelTable) is a div-based list with radio + chips, not an HTML table, so it will never migrate onto a Table primitive. By Occam's razor the primitive is lifted only when a second real consumer appears; the stats table keeps its styles inline.

Scope

Changed:

  • New packages/ui/src/primitives/card.tsx — thin Card (data-slot="card", radius-surface); caller keeps layout/visual CSS.
  • New packages/ui/src/primitives/chip.tsx — squared Chip (data-slot="chip", radius-control), 5 status-tone variants mirroring StatusTone, sm/default sizes. cva reproduces the retired CSS oklch alphas (success/12, info/14, warning/18, destructive/15) and the neutral foreground-5 base; sm reproduces .settingsBadge (18px / font-normal / 0-6px padding) and default reproduces .settingsConnectionBadge (20px / semibold / 2-8px padding).
  • packages/ui/src/primitives/badge.tsx is now the canonical pill Badge; the PrimitiveBadge alias and the legacy ui.tsxBadge (raw emerald/amber variants) are gone.
  • Migrations onto Card: settingsRows + 3 consumer pages, settingsMetricCard, maka-error-card. Onto Badge: health-center, permission-center, artifact-pane, plan-reminder, permission-dialog. Onto Chip: provider-connection-detail (size="sm" for the generic label), provider-add-form (size="sm"), web-search, memory-settings, account-settings, provider-oauth (8 sites).
  • Usage stats table: stays a local native <table>/<thead>/<tbody>/<tr>/<th scope>/<td> in usage-settings-page.tsx (SimpleStatsTable), with the same Tailwind classes the retired Table primitive applied — no public Table primitive.
  • CSS retired: settings card/table radius rows, .settingsStatsTable, .settingsBadge, .settingsConnectionBadge + data-tone variants.
  • Contracts: new card-converge-contract, badge-converge-contract, chip-converge-contract; updated radius-converge (drop stale .settingsRows tier + ui.tsx badgeVariants entry), tabular-nums (drop .settingsStatsTable), settings-usage (regex matches native <table>/<th scope> a11y), web-search-boundary (regex matches <SettingsRows>), settings-form-a11y (lock point moved from CSS class to Chip primitive).

Not included:

  • PR8 combobox migration (SearchModal + CommandPalette) — separate PR, runtime keyboard interaction risk.
  • PR10 input canonicalization (Add DeepSeek context budget diagnostics #22) — separate PR, implementation merge + contract rewrite.
  • The scattered composer-model-chip / prompt-chip / plan-card-chip CSS classes are not migrated to Chip in this PR; they are left for a future sweep now that Chip exists as the convergence target.
  • A public Table primitive is intentionally not shipped (see Why). Lift when a second HTML <table> consumer appears.

Verification

  • npm run typecheck — clean.
  • npm run -w @maka/desktop test — 2049/2049 pass (one fewer than the initial Table-inclusive state: the deleted "table sites import Table" case).
  • npm run -w @maka/ui build + npm run -w @maka/desktop build:main — clean.
  • Screenshot: settings-bots (8), settings-memory (8), settings-general (8), settings-data (8) captured (no crash).
  • Pixel diff vs main (ImageMagick compare): settings-bots and settings-general light/dark 1280 AE=0 (pixel-identical). settings-data 1280 shows ~4500 px diff (RMSE 0.001) in the data section, which is the main-branch composer/sidebar commits (feat(sidebar): add project-grouped session view mode #510/feat(composer): workspace picker, git branch switching, and composer defaults persistence #511), not this PR — the usage stats table lives in the usage section (not in the settings-data scenario) and its inline styles match the retired primitive exactly.

User-facing impact

None intended. Card/badge/chip visuals are preserved by reproducing the retired CSS token values inside the primitives (and inside SimpleStatsTable for the usage stats table). The settingsConnectionBadge warning tone no longer misuses --info (it now uses --warning, which aliases --info so the color is unchanged but the intent is correct). The .settingsBadge-derived generic labels keep 18px / font-normal via Chip size="sm".

Reviewer notes

  • The badge split (pill Badge + squared Chip) is the load-bearing decision. The alternative — collapsing all 4 surfaces onto pill Badge — was rejected because it regresses the settings-form-a11y "compact squared, not pills" lock and turns the destructive status into a solid red block. See chip-converge-contract for the role-split + token lock.
  • The Table primitive was removed per review (P3-1): single consumer, and the model "table" is a div surface so no second consumer is coming. SimpleStatsTable keeps the same styles inline.
  • Chip variants mirror StatusTone directly, so settings callers pass tone straight through. statusBadgeVariant is retained because health/permission center still use it with the pill Badge.
  • Card is intentionally thin (only data-slot + radius-surface); layout/visual CSS stays at call sites, matching the ChoiceCard philosophy.
  • Commits are independent revert targets; squash on merge.

Checklist

  • Scope matches the PR title and excludes unrelated changes
  • Verification lists commands/results, or explains why they were not run
  • User-facing impact, docs, changelog, migrations, and breaking changes are noted, or marked none
  • Risk, rollback, or review focus is called out for non-trivial changes
  • UI changes include screenshots/video, or explain why not applicable

…es (#520 PR9)
Card (packages/ui/src/primitives/card.tsx) — thin surface container
(data-slot="card" + radius-surface), following maka's ChoiceCard
philosophy: each call site keeps its own layout/visual CSS. settingsRows,
settingsMetricCard, and maka-error-card now route through Card; their CSS
drops the border-radius line (Card owns it), layout is byte-identical.
Table (packages/ui/src/primitives/table.tsx) — shadcn-style family
(Table/TableHeader/TableBody/TableRow/TableHead/TableCell) with data-slot.
settingsStatsTable retires entirely; the table chrome (border + radius +
caption font-size) and the cell chrome (tabular-nums + hairline row
separators + caption-tone color + semibold head) move into the primitive.
maka-error-card stays on Card (not Alert): it is a large crash surface
with shadow-modal + stack <pre>, not a small inline callout.
card-table-converge-contract locks the migration. Updated four existing
contracts whose selectors pinned the retired classes/structure:
radius-converge (drop .settingsRows tier — Card owns it now),
tabular-nums (drop .settingsStatsTable th/td — Table owns it now),
settings-usage (regex now matches the Table family + scoped heads/cells),
web-search-boundary (regex now matches <SettingsRows className=…>).
Token values verified equivalent: --font-weight-semibold=600 (font-semibold),
--space-1/2, --radius-surface=8px. Screenshot manifest passes (32/32, 0 fail).
#520 PR9 commit 2: collapse the four coexisting badge surfaces onto two
canonical primitives, split by UI role.
- pill Badge (primitives/badge.tsx): emphasis markers. Retire the
PrimitiveBadge alias + the legacy ui.tsx Badge (raw emerald/amber
variants). health/permission center, artifact-pane, plan-reminder, and
permission-dialog route through <Badge>.
- squared Chip (primitives/chip.tsx): dense status rows. Retire the
.settingsBadge + .settingsConnectionBadge CSS chips. settings
connection status / default / category markers route through <Chip>;
variants mirror StatusTone, so settings callers pass the tone directly
instead of going through statusBadgeVariant (which stays for the
health/permission pill Badge sites).
- contracts: badge-converge (pill track), chip-converge (squared track,
locks radius-control not pill), settings-form-a11y (lock point moved
from the CSS class to the Chip primitive), radius-converge (drop the
stale ui.tsx badgeVariants entry).
- visual zero-change: Chip cva reproduces the retired CSS oklch alphas
(success/12, info/14, warning/18, destructive/15) and the neutral
foreground-5 base. screenshot pixel diff vs main: settings-bots
light/dark 1280 AE=0.
…ontract, comment fix
#520 PR9 review fixes:
- P2: the three .settingsBadge migration sites (provider-connection-detail
x2, provider-add-form x1) now use Chip size="sm" to reproduce the retired
.settingsBadge geometry (18px / font-normal / 0-6px padding). Without
size="sm" they defaulted to the larger status-row size (20px / semibold /
2-8px padding) and drifted. The neutral background did not drift:
bg-secondary aliases --color-secondary = var(--foreground-5), so only
height/weight/padding moved.
- P3-2: chip-converge-contract now locks user-visible tokens (neutral
bg-secondary + foreground-secondary text, sm/default size geometry,
status-tone alphas /12 /14 /18 /15) so a cva class change that keeps the
import is still caught. Dropped the "no old span" migration-narrative
loop; kept import + role-split + token assertions.
- P3-3: bot.css / connection.css retire comments now say Chip
variant="neutral" (not Badge variant="secondary"), matching the actual
migration and not steering future migrants back to the pill Badge.
Verification: 2050/2050 contract pass, typecheck clean, screenshot
settings-bots light/dark 1280 + light 990 all AE=0 (pixel-identical to
main after the size=sm fix; the 990 variant's earlier 13380 px diff is gone).
#520 PR9 review P3-1: the Table primitive had a single consumer
(usage-settings-page SimpleStatsTable) and no second HTML <table> consumer
in sight — the model "table" is a div-based list (.modelTable), not an HTML
table, so it will never migrate onto Table. Per Occam's razor, lift the
primitive only when a second real consumer appears.
- Delete packages/ui/src/primitives/table.tsx and its barrel export.
- SimpleStatsTable in usage-settings-page now renders a native HTML
<table>/<thead>/<tbody>/<tr>/<th>/<td> with the same Tailwind classes the
primitive applied (w-full border-collapse rounded-[var(--radius-surface)]
border border-border text-caption + cell border-b/px/py/align/
tabular-nums). Styles stay inline so the stats surface is self-contained
until a second HTML <table> consumer justifies lifting it back.
- Rename card-table-converge-contract → card-converge-contract: drop the
Table data-slot / table-sites assertions, keep Card. Note the usage stats
table a11y semantics (aria-label + scope) now live in
settings-usage-contract, whose assertions are updated to match the native
<table>/<th scope> shape.
- settings-form-a11y and tabular-nums are untouched: they assert on the
.modelTable CSS class (a div surface), not on the Table primitive.
Verification: 2049/2049 contract pass (one fewer test — the deleted
"table sites import Table" case), typecheck clean. Screenshot pixel diff vs
main: settings-general light/dark 1280 AE=0; settings-data 1280 shows ~4500
px (RMSE 0.001) in the `data` section, which is main-branch composer/sidebar
drift (#510/#511), not this change — the usage stats table lives in the
`usage` section and its styles are identical to the retired primitive.
…impleStatsTable
Per PR9 review P3: the retire comment still referenced the deleted
packages/ui/src/primitives/table.tsx, which would mislead a future maintainer
into restoring a public Table primitive. Now points at the local native
SimpleStatsTable in usage-settings-page.tsx, matching the actual state.
@Astro-Han
Astro-Han merged commit 80cc73a into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the feat/ui-card-badge-converge branch July 14, 2026 05:05
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)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) by Astro-Han · Pull Request #554 · apache/maka · GitHub
Skip to content

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) - #554

Merged
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge
Jul 5, 2026
Merged

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9)#554
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

#520 PR9: converge the hand-written settings card surfaces onto a Card primitive, and collapse the four coexisting badge surfaces onto two role-split primitives — pill Badge (emphasis markers) and squared Chip (dense settings status rows).

A review-driven correction: an initial Table primitive was retired before merge — with a single HTML <table> consumer and no second one in sight (the model "table" is a div-based list, not an HTML table), it was premature. The usage stats table now stays a local native <table> in usage-settings-page.tsx.

Why

Refs #520. The renderer had four parallel badge implementations and three hand-written settings card CSS surfaces, each drifting from the shared token system. The convergence goal is one primitive per UI role, governed by contracts instead of ad-hoc CSS.

A key finding drove the badge split: Base UI (@base-ui/react 1.5.0/1.6.0) ships no Badge/Chip/Tag component, and shadcn/ui ships Badge but noChip. So both primitives are self-built (cva + data-slot + Base UI useRender), same pattern as the existing Card/Alert primitives. Settings status rows need compact squared chips (radius-control) for information density — the settings-form-a11y contract explicitly locks "not pills". Forcing them onto the pill Badge would regress that intent and turn the destructive tone into a solid red block. The correct convergence is 4 surfaces → 2 primitives by role, not 4 → 1.

The Table primitive was added in an earlier commit but removed per review: the only HTML <table> consumer is the usage stats table, and the model "table" (.modelTable) is a div-based list with radio + chips, not an HTML table, so it will never migrate onto a Table primitive. By Occam's razor the primitive is lifted only when a second real consumer appears; the stats table keeps its styles inline.

Scope

Changed:

  • New packages/ui/src/primitives/card.tsx — thin Card (data-slot="card", radius-surface); caller keeps layout/visual CSS.
  • New packages/ui/src/primitives/chip.tsx — squared Chip (data-slot="chip", radius-control), 5 status-tone variants mirroring StatusTone, sm/default sizes. cva reproduces the retired CSS oklch alphas (success/12, info/14, warning/18, destructive/15) and the neutral foreground-5 base; sm reproduces .settingsBadge (18px / font-normal / 0-6px padding) and default reproduces .settingsConnectionBadge (20px / semibold / 2-8px padding).
  • packages/ui/src/primitives/badge.tsx is now the canonical pill Badge; the PrimitiveBadge alias and the legacy ui.tsxBadge (raw emerald/amber variants) are gone.
  • Migrations onto Card: settingsRows + 3 consumer pages, settingsMetricCard, maka-error-card. Onto Badge: health-center, permission-center, artifact-pane, plan-reminder, permission-dialog. Onto Chip: provider-connection-detail (size="sm" for the generic label), provider-add-form (size="sm"), web-search, memory-settings, account-settings, provider-oauth (8 sites).
  • Usage stats table: stays a local native <table>/<thead>/<tbody>/<tr>/<th scope>/<td> in usage-settings-page.tsx (SimpleStatsTable), with the same Tailwind classes the retired Table primitive applied — no public Table primitive.
  • CSS retired: settings card/table radius rows, .settingsStatsTable, .settingsBadge, .settingsConnectionBadge + data-tone variants.
  • Contracts: new card-converge-contract, badge-converge-contract, chip-converge-contract; updated radius-converge (drop stale .settingsRows tier + ui.tsx badgeVariants entry), tabular-nums (drop .settingsStatsTable), settings-usage (regex matches native <table>/<th scope> a11y), web-search-boundary (regex matches <SettingsRows>), settings-form-a11y (lock point moved from CSS class to Chip primitive).

Not included:

  • PR8 combobox migration (SearchModal + CommandPalette) — separate PR, runtime keyboard interaction risk.
  • PR10 input canonicalization (Add DeepSeek context budget diagnostics #22) — separate PR, implementation merge + contract rewrite.
  • The scattered composer-model-chip / prompt-chip / plan-card-chip CSS classes are not migrated to Chip in this PR; they are left for a future sweep now that Chip exists as the convergence target.
  • A public Table primitive is intentionally not shipped (see Why). Lift when a second HTML <table> consumer appears.

Verification

  • npm run typecheck — clean.
  • npm run -w @maka/desktop test — 2049/2049 pass (one fewer than the initial Table-inclusive state: the deleted "table sites import Table" case).
  • npm run -w @maka/ui build + npm run -w @maka/desktop build:main — clean.
  • Screenshot: settings-bots (8), settings-memory (8), settings-general (8), settings-data (8) captured (no crash).
  • Pixel diff vs main (ImageMagick compare): settings-bots and settings-general light/dark 1280 AE=0 (pixel-identical). settings-data 1280 shows ~4500 px diff (RMSE 0.001) in the data section, which is the main-branch composer/sidebar commits (feat(sidebar): add project-grouped session view mode #510/feat(composer): workspace picker, git branch switching, and composer defaults persistence #511), not this PR — the usage stats table lives in the usage section (not in the settings-data scenario) and its inline styles match the retired primitive exactly.

User-facing impact

None intended. Card/badge/chip visuals are preserved by reproducing the retired CSS token values inside the primitives (and inside SimpleStatsTable for the usage stats table). The settingsConnectionBadge warning tone no longer misuses --info (it now uses --warning, which aliases --info so the color is unchanged but the intent is correct). The .settingsBadge-derived generic labels keep 18px / font-normal via Chip size="sm".

Reviewer notes

  • The badge split (pill Badge + squared Chip) is the load-bearing decision. The alternative — collapsing all 4 surfaces onto pill Badge — was rejected because it regresses the settings-form-a11y "compact squared, not pills" lock and turns the destructive status into a solid red block. See chip-converge-contract for the role-split + token lock.
  • The Table primitive was removed per review (P3-1): single consumer, and the model "table" is a div surface so no second consumer is coming. SimpleStatsTable keeps the same styles inline.
  • Chip variants mirror StatusTone directly, so settings callers pass tone straight through. statusBadgeVariant is retained because health/permission center still use it with the pill Badge.
  • Card is intentionally thin (only data-slot + radius-surface); layout/visual CSS stays at call sites, matching the ChoiceCard philosophy.
  • Commits are independent revert targets; squash on merge.

Checklist

  • Scope matches the PR title and excludes unrelated changes
  • Verification lists commands/results, or explains why they were not run
  • User-facing impact, docs, changelog, migrations, and breaking changes are noted, or marked none
  • Risk, rollback, or review focus is called out for non-trivial changes
  • UI changes include screenshots/video, or explain why not applicable

…es (#520 PR9)
Card (packages/ui/src/primitives/card.tsx) — thin surface container
(data-slot="card" + radius-surface), following maka's ChoiceCard
philosophy: each call site keeps its own layout/visual CSS. settingsRows,
settingsMetricCard, and maka-error-card now route through Card; their CSS
drops the border-radius line (Card owns it), layout is byte-identical.
Table (packages/ui/src/primitives/table.tsx) — shadcn-style family
(Table/TableHeader/TableBody/TableRow/TableHead/TableCell) with data-slot.
settingsStatsTable retires entirely; the table chrome (border + radius +
caption font-size) and the cell chrome (tabular-nums + hairline row
separators + caption-tone color + semibold head) move into the primitive.
maka-error-card stays on Card (not Alert): it is a large crash surface
with shadow-modal + stack <pre>, not a small inline callout.
card-table-converge-contract locks the migration. Updated four existing
contracts whose selectors pinned the retired classes/structure:
radius-converge (drop .settingsRows tier — Card owns it now),
tabular-nums (drop .settingsStatsTable th/td — Table owns it now),
settings-usage (regex now matches the Table family + scoped heads/cells),
web-search-boundary (regex now matches <SettingsRows className=…>).
Token values verified equivalent: --font-weight-semibold=600 (font-semibold),
--space-1/2, --radius-surface=8px. Screenshot manifest passes (32/32, 0 fail).
#520 PR9 commit 2: collapse the four coexisting badge surfaces onto two
canonical primitives, split by UI role.
- pill Badge (primitives/badge.tsx): emphasis markers. Retire the
PrimitiveBadge alias + the legacy ui.tsx Badge (raw emerald/amber
variants). health/permission center, artifact-pane, plan-reminder, and
permission-dialog route through <Badge>.
- squared Chip (primitives/chip.tsx): dense status rows. Retire the
.settingsBadge + .settingsConnectionBadge CSS chips. settings
connection status / default / category markers route through <Chip>;
variants mirror StatusTone, so settings callers pass the tone directly
instead of going through statusBadgeVariant (which stays for the
health/permission pill Badge sites).
- contracts: badge-converge (pill track), chip-converge (squared track,
locks radius-control not pill), settings-form-a11y (lock point moved
from the CSS class to the Chip primitive), radius-converge (drop the
stale ui.tsx badgeVariants entry).
- visual zero-change: Chip cva reproduces the retired CSS oklch alphas
(success/12, info/14, warning/18, destructive/15) and the neutral
foreground-5 base. screenshot pixel diff vs main: settings-bots
light/dark 1280 AE=0.
…ontract, comment fix
#520 PR9 review fixes:
- P2: the three .settingsBadge migration sites (provider-connection-detail
x2, provider-add-form x1) now use Chip size="sm" to reproduce the retired
.settingsBadge geometry (18px / font-normal / 0-6px padding). Without
size="sm" they defaulted to the larger status-row size (20px / semibold /
2-8px padding) and drifted. The neutral background did not drift:
bg-secondary aliases --color-secondary = var(--foreground-5), so only
height/weight/padding moved.
- P3-2: chip-converge-contract now locks user-visible tokens (neutral
bg-secondary + foreground-secondary text, sm/default size geometry,
status-tone alphas /12 /14 /18 /15) so a cva class change that keeps the
import is still caught. Dropped the "no old span" migration-narrative
loop; kept import + role-split + token assertions.
- P3-3: bot.css / connection.css retire comments now say Chip
variant="neutral" (not Badge variant="secondary"), matching the actual
migration and not steering future migrants back to the pill Badge.
Verification: 2050/2050 contract pass, typecheck clean, screenshot
settings-bots light/dark 1280 + light 990 all AE=0 (pixel-identical to
main after the size=sm fix; the 990 variant's earlier 13380 px diff is gone).
#520 PR9 review P3-1: the Table primitive had a single consumer
(usage-settings-page SimpleStatsTable) and no second HTML <table> consumer
in sight — the model "table" is a div-based list (.modelTable), not an HTML
table, so it will never migrate onto Table. Per Occam's razor, lift the
primitive only when a second real consumer appears.
- Delete packages/ui/src/primitives/table.tsx and its barrel export.
- SimpleStatsTable in usage-settings-page now renders a native HTML
<table>/<thead>/<tbody>/<tr>/<th>/<td> with the same Tailwind classes the
primitive applied (w-full border-collapse rounded-[var(--radius-surface)]
border border-border text-caption + cell border-b/px/py/align/
tabular-nums). Styles stay inline so the stats surface is self-contained
until a second HTML <table> consumer justifies lifting it back.
- Rename card-table-converge-contract → card-converge-contract: drop the
Table data-slot / table-sites assertions, keep Card. Note the usage stats
table a11y semantics (aria-label + scope) now live in
settings-usage-contract, whose assertions are updated to match the native
<table>/<th scope> shape.
- settings-form-a11y and tabular-nums are untouched: they assert on the
.modelTable CSS class (a div surface), not on the Table primitive.
Verification: 2049/2049 contract pass (one fewer test — the deleted
"table sites import Table" case), typecheck clean. Screenshot pixel diff vs
main: settings-general light/dark 1280 AE=0; settings-data 1280 shows ~4500
px (RMSE 0.001) in the `data` section, which is main-branch composer/sidebar
drift (#510/#511), not this change — the usage stats table lives in the
`usage` section and its styles are identical to the retired primitive.
…impleStatsTable
Per PR9 review P3: the retire comment still referenced the deleted
packages/ui/src/primitives/table.tsx, which would mislead a future maintainer
into restoring a public Table primitive. Now points at the local native
SimpleStatsTable in usage-settings-page.tsx, matching the actual state.
@Astro-Han
Astro-Han merged commit 80cc73a into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the feat/ui-card-badge-converge branch July 14, 2026 05:05
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)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) by Astro-Han · Pull Request #554 · apache/maka · GitHub
Skip to content

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) - #554

Merged
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge
Jul 5, 2026
Merged

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9)#554
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

#520 PR9: converge the hand-written settings card surfaces onto a Card primitive, and collapse the four coexisting badge surfaces onto two role-split primitives — pill Badge (emphasis markers) and squared Chip (dense settings status rows).

A review-driven correction: an initial Table primitive was retired before merge — with a single HTML <table> consumer and no second one in sight (the model "table" is a div-based list, not an HTML table), it was premature. The usage stats table now stays a local native <table> in usage-settings-page.tsx.

Why

Refs #520. The renderer had four parallel badge implementations and three hand-written settings card CSS surfaces, each drifting from the shared token system. The convergence goal is one primitive per UI role, governed by contracts instead of ad-hoc CSS.

A key finding drove the badge split: Base UI (@base-ui/react 1.5.0/1.6.0) ships no Badge/Chip/Tag component, and shadcn/ui ships Badge but noChip. So both primitives are self-built (cva + data-slot + Base UI useRender), same pattern as the existing Card/Alert primitives. Settings status rows need compact squared chips (radius-control) for information density — the settings-form-a11y contract explicitly locks "not pills". Forcing them onto the pill Badge would regress that intent and turn the destructive tone into a solid red block. The correct convergence is 4 surfaces → 2 primitives by role, not 4 → 1.

The Table primitive was added in an earlier commit but removed per review: the only HTML <table> consumer is the usage stats table, and the model "table" (.modelTable) is a div-based list with radio + chips, not an HTML table, so it will never migrate onto a Table primitive. By Occam's razor the primitive is lifted only when a second real consumer appears; the stats table keeps its styles inline.

Scope

Changed:

  • New packages/ui/src/primitives/card.tsx — thin Card (data-slot="card", radius-surface); caller keeps layout/visual CSS.
  • New packages/ui/src/primitives/chip.tsx — squared Chip (data-slot="chip", radius-control), 5 status-tone variants mirroring StatusTone, sm/default sizes. cva reproduces the retired CSS oklch alphas (success/12, info/14, warning/18, destructive/15) and the neutral foreground-5 base; sm reproduces .settingsBadge (18px / font-normal / 0-6px padding) and default reproduces .settingsConnectionBadge (20px / semibold / 2-8px padding).
  • packages/ui/src/primitives/badge.tsx is now the canonical pill Badge; the PrimitiveBadge alias and the legacy ui.tsxBadge (raw emerald/amber variants) are gone.
  • Migrations onto Card: settingsRows + 3 consumer pages, settingsMetricCard, maka-error-card. Onto Badge: health-center, permission-center, artifact-pane, plan-reminder, permission-dialog. Onto Chip: provider-connection-detail (size="sm" for the generic label), provider-add-form (size="sm"), web-search, memory-settings, account-settings, provider-oauth (8 sites).
  • Usage stats table: stays a local native <table>/<thead>/<tbody>/<tr>/<th scope>/<td> in usage-settings-page.tsx (SimpleStatsTable), with the same Tailwind classes the retired Table primitive applied — no public Table primitive.
  • CSS retired: settings card/table radius rows, .settingsStatsTable, .settingsBadge, .settingsConnectionBadge + data-tone variants.
  • Contracts: new card-converge-contract, badge-converge-contract, chip-converge-contract; updated radius-converge (drop stale .settingsRows tier + ui.tsx badgeVariants entry), tabular-nums (drop .settingsStatsTable), settings-usage (regex matches native <table>/<th scope> a11y), web-search-boundary (regex matches <SettingsRows>), settings-form-a11y (lock point moved from CSS class to Chip primitive).

Not included:

  • PR8 combobox migration (SearchModal + CommandPalette) — separate PR, runtime keyboard interaction risk.
  • PR10 input canonicalization (Add DeepSeek context budget diagnostics #22) — separate PR, implementation merge + contract rewrite.
  • The scattered composer-model-chip / prompt-chip / plan-card-chip CSS classes are not migrated to Chip in this PR; they are left for a future sweep now that Chip exists as the convergence target.
  • A public Table primitive is intentionally not shipped (see Why). Lift when a second HTML <table> consumer appears.

Verification

  • npm run typecheck — clean.
  • npm run -w @maka/desktop test — 2049/2049 pass (one fewer than the initial Table-inclusive state: the deleted "table sites import Table" case).
  • npm run -w @maka/ui build + npm run -w @maka/desktop build:main — clean.
  • Screenshot: settings-bots (8), settings-memory (8), settings-general (8), settings-data (8) captured (no crash).
  • Pixel diff vs main (ImageMagick compare): settings-bots and settings-general light/dark 1280 AE=0 (pixel-identical). settings-data 1280 shows ~4500 px diff (RMSE 0.001) in the data section, which is the main-branch composer/sidebar commits (feat(sidebar): add project-grouped session view mode #510/feat(composer): workspace picker, git branch switching, and composer defaults persistence #511), not this PR — the usage stats table lives in the usage section (not in the settings-data scenario) and its inline styles match the retired primitive exactly.

User-facing impact

None intended. Card/badge/chip visuals are preserved by reproducing the retired CSS token values inside the primitives (and inside SimpleStatsTable for the usage stats table). The settingsConnectionBadge warning tone no longer misuses --info (it now uses --warning, which aliases --info so the color is unchanged but the intent is correct). The .settingsBadge-derived generic labels keep 18px / font-normal via Chip size="sm".

Reviewer notes

  • The badge split (pill Badge + squared Chip) is the load-bearing decision. The alternative — collapsing all 4 surfaces onto pill Badge — was rejected because it regresses the settings-form-a11y "compact squared, not pills" lock and turns the destructive status into a solid red block. See chip-converge-contract for the role-split + token lock.
  • The Table primitive was removed per review (P3-1): single consumer, and the model "table" is a div surface so no second consumer is coming. SimpleStatsTable keeps the same styles inline.
  • Chip variants mirror StatusTone directly, so settings callers pass tone straight through. statusBadgeVariant is retained because health/permission center still use it with the pill Badge.
  • Card is intentionally thin (only data-slot + radius-surface); layout/visual CSS stays at call sites, matching the ChoiceCard philosophy.
  • Commits are independent revert targets; squash on merge.

Checklist

  • Scope matches the PR title and excludes unrelated changes
  • Verification lists commands/results, or explains why they were not run
  • User-facing impact, docs, changelog, migrations, and breaking changes are noted, or marked none
  • Risk, rollback, or review focus is called out for non-trivial changes
  • UI changes include screenshots/video, or explain why not applicable

…es (#520 PR9)
Card (packages/ui/src/primitives/card.tsx) — thin surface container
(data-slot="card" + radius-surface), following maka's ChoiceCard
philosophy: each call site keeps its own layout/visual CSS. settingsRows,
settingsMetricCard, and maka-error-card now route through Card; their CSS
drops the border-radius line (Card owns it), layout is byte-identical.
Table (packages/ui/src/primitives/table.tsx) — shadcn-style family
(Table/TableHeader/TableBody/TableRow/TableHead/TableCell) with data-slot.
settingsStatsTable retires entirely; the table chrome (border + radius +
caption font-size) and the cell chrome (tabular-nums + hairline row
separators + caption-tone color + semibold head) move into the primitive.
maka-error-card stays on Card (not Alert): it is a large crash surface
with shadow-modal + stack <pre>, not a small inline callout.
card-table-converge-contract locks the migration. Updated four existing
contracts whose selectors pinned the retired classes/structure:
radius-converge (drop .settingsRows tier — Card owns it now),
tabular-nums (drop .settingsStatsTable th/td — Table owns it now),
settings-usage (regex now matches the Table family + scoped heads/cells),
web-search-boundary (regex now matches <SettingsRows className=…>).
Token values verified equivalent: --font-weight-semibold=600 (font-semibold),
--space-1/2, --radius-surface=8px. Screenshot manifest passes (32/32, 0 fail).
#520 PR9 commit 2: collapse the four coexisting badge surfaces onto two
canonical primitives, split by UI role.
- pill Badge (primitives/badge.tsx): emphasis markers. Retire the
PrimitiveBadge alias + the legacy ui.tsx Badge (raw emerald/amber
variants). health/permission center, artifact-pane, plan-reminder, and
permission-dialog route through <Badge>.
- squared Chip (primitives/chip.tsx): dense status rows. Retire the
.settingsBadge + .settingsConnectionBadge CSS chips. settings
connection status / default / category markers route through <Chip>;
variants mirror StatusTone, so settings callers pass the tone directly
instead of going through statusBadgeVariant (which stays for the
health/permission pill Badge sites).
- contracts: badge-converge (pill track), chip-converge (squared track,
locks radius-control not pill), settings-form-a11y (lock point moved
from the CSS class to the Chip primitive), radius-converge (drop the
stale ui.tsx badgeVariants entry).
- visual zero-change: Chip cva reproduces the retired CSS oklch alphas
(success/12, info/14, warning/18, destructive/15) and the neutral
foreground-5 base. screenshot pixel diff vs main: settings-bots
light/dark 1280 AE=0.
…ontract, comment fix
#520 PR9 review fixes:
- P2: the three .settingsBadge migration sites (provider-connection-detail
x2, provider-add-form x1) now use Chip size="sm" to reproduce the retired
.settingsBadge geometry (18px / font-normal / 0-6px padding). Without
size="sm" they defaulted to the larger status-row size (20px / semibold /
2-8px padding) and drifted. The neutral background did not drift:
bg-secondary aliases --color-secondary = var(--foreground-5), so only
height/weight/padding moved.
- P3-2: chip-converge-contract now locks user-visible tokens (neutral
bg-secondary + foreground-secondary text, sm/default size geometry,
status-tone alphas /12 /14 /18 /15) so a cva class change that keeps the
import is still caught. Dropped the "no old span" migration-narrative
loop; kept import + role-split + token assertions.
- P3-3: bot.css / connection.css retire comments now say Chip
variant="neutral" (not Badge variant="secondary"), matching the actual
migration and not steering future migrants back to the pill Badge.
Verification: 2050/2050 contract pass, typecheck clean, screenshot
settings-bots light/dark 1280 + light 990 all AE=0 (pixel-identical to
main after the size=sm fix; the 990 variant's earlier 13380 px diff is gone).
#520 PR9 review P3-1: the Table primitive had a single consumer
(usage-settings-page SimpleStatsTable) and no second HTML <table> consumer
in sight — the model "table" is a div-based list (.modelTable), not an HTML
table, so it will never migrate onto Table. Per Occam's razor, lift the
primitive only when a second real consumer appears.
- Delete packages/ui/src/primitives/table.tsx and its barrel export.
- SimpleStatsTable in usage-settings-page now renders a native HTML
<table>/<thead>/<tbody>/<tr>/<th>/<td> with the same Tailwind classes the
primitive applied (w-full border-collapse rounded-[var(--radius-surface)]
border border-border text-caption + cell border-b/px/py/align/
tabular-nums). Styles stay inline so the stats surface is self-contained
until a second HTML <table> consumer justifies lifting it back.
- Rename card-table-converge-contract → card-converge-contract: drop the
Table data-slot / table-sites assertions, keep Card. Note the usage stats
table a11y semantics (aria-label + scope) now live in
settings-usage-contract, whose assertions are updated to match the native
<table>/<th scope> shape.
- settings-form-a11y and tabular-nums are untouched: they assert on the
.modelTable CSS class (a div surface), not on the Table primitive.
Verification: 2049/2049 contract pass (one fewer test — the deleted
"table sites import Table" case), typecheck clean. Screenshot pixel diff vs
main: settings-general light/dark 1280 AE=0; settings-data 1280 shows ~4500
px (RMSE 0.001) in the `data` section, which is main-branch composer/sidebar
drift (#510/#511), not this change — the usage stats table lives in the
`usage` section and its styles are identical to the retired primitive.
…impleStatsTable
Per PR9 review P3: the retire comment still referenced the deleted
packages/ui/src/primitives/table.tsx, which would mislead a future maintainer
into restoring a public Table primitive. Now points at the local native
SimpleStatsTable in usage-settings-page.tsx, matching the actual state.
@Astro-Han
Astro-Han merged commit 80cc73a into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the feat/ui-card-badge-converge branch July 14, 2026 05:05
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)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) by Astro-Han · Pull Request #554 · apache/maka · GitHub
Skip to content

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) - #554

Merged
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge
Jul 5, 2026
Merged

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9)#554
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

#520 PR9: converge the hand-written settings card surfaces onto a Card primitive, and collapse the four coexisting badge surfaces onto two role-split primitives — pill Badge (emphasis markers) and squared Chip (dense settings status rows).

A review-driven correction: an initial Table primitive was retired before merge — with a single HTML <table> consumer and no second one in sight (the model "table" is a div-based list, not an HTML table), it was premature. The usage stats table now stays a local native <table> in usage-settings-page.tsx.

Why

Refs #520. The renderer had four parallel badge implementations and three hand-written settings card CSS surfaces, each drifting from the shared token system. The convergence goal is one primitive per UI role, governed by contracts instead of ad-hoc CSS.

A key finding drove the badge split: Base UI (@base-ui/react 1.5.0/1.6.0) ships no Badge/Chip/Tag component, and shadcn/ui ships Badge but noChip. So both primitives are self-built (cva + data-slot + Base UI useRender), same pattern as the existing Card/Alert primitives. Settings status rows need compact squared chips (radius-control) for information density — the settings-form-a11y contract explicitly locks "not pills". Forcing them onto the pill Badge would regress that intent and turn the destructive tone into a solid red block. The correct convergence is 4 surfaces → 2 primitives by role, not 4 → 1.

The Table primitive was added in an earlier commit but removed per review: the only HTML <table> consumer is the usage stats table, and the model "table" (.modelTable) is a div-based list with radio + chips, not an HTML table, so it will never migrate onto a Table primitive. By Occam's razor the primitive is lifted only when a second real consumer appears; the stats table keeps its styles inline.

Scope

Changed:

  • New packages/ui/src/primitives/card.tsx — thin Card (data-slot="card", radius-surface); caller keeps layout/visual CSS.
  • New packages/ui/src/primitives/chip.tsx — squared Chip (data-slot="chip", radius-control), 5 status-tone variants mirroring StatusTone, sm/default sizes. cva reproduces the retired CSS oklch alphas (success/12, info/14, warning/18, destructive/15) and the neutral foreground-5 base; sm reproduces .settingsBadge (18px / font-normal / 0-6px padding) and default reproduces .settingsConnectionBadge (20px / semibold / 2-8px padding).
  • packages/ui/src/primitives/badge.tsx is now the canonical pill Badge; the PrimitiveBadge alias and the legacy ui.tsxBadge (raw emerald/amber variants) are gone.
  • Migrations onto Card: settingsRows + 3 consumer pages, settingsMetricCard, maka-error-card. Onto Badge: health-center, permission-center, artifact-pane, plan-reminder, permission-dialog. Onto Chip: provider-connection-detail (size="sm" for the generic label), provider-add-form (size="sm"), web-search, memory-settings, account-settings, provider-oauth (8 sites).
  • Usage stats table: stays a local native <table>/<thead>/<tbody>/<tr>/<th scope>/<td> in usage-settings-page.tsx (SimpleStatsTable), with the same Tailwind classes the retired Table primitive applied — no public Table primitive.
  • CSS retired: settings card/table radius rows, .settingsStatsTable, .settingsBadge, .settingsConnectionBadge + data-tone variants.
  • Contracts: new card-converge-contract, badge-converge-contract, chip-converge-contract; updated radius-converge (drop stale .settingsRows tier + ui.tsx badgeVariants entry), tabular-nums (drop .settingsStatsTable), settings-usage (regex matches native <table>/<th scope> a11y), web-search-boundary (regex matches <SettingsRows>), settings-form-a11y (lock point moved from CSS class to Chip primitive).

Not included:

  • PR8 combobox migration (SearchModal + CommandPalette) — separate PR, runtime keyboard interaction risk.
  • PR10 input canonicalization (Add DeepSeek context budget diagnostics #22) — separate PR, implementation merge + contract rewrite.
  • The scattered composer-model-chip / prompt-chip / plan-card-chip CSS classes are not migrated to Chip in this PR; they are left for a future sweep now that Chip exists as the convergence target.
  • A public Table primitive is intentionally not shipped (see Why). Lift when a second HTML <table> consumer appears.

Verification

  • npm run typecheck — clean.
  • npm run -w @maka/desktop test — 2049/2049 pass (one fewer than the initial Table-inclusive state: the deleted "table sites import Table" case).
  • npm run -w @maka/ui build + npm run -w @maka/desktop build:main — clean.
  • Screenshot: settings-bots (8), settings-memory (8), settings-general (8), settings-data (8) captured (no crash).
  • Pixel diff vs main (ImageMagick compare): settings-bots and settings-general light/dark 1280 AE=0 (pixel-identical). settings-data 1280 shows ~4500 px diff (RMSE 0.001) in the data section, which is the main-branch composer/sidebar commits (feat(sidebar): add project-grouped session view mode #510/feat(composer): workspace picker, git branch switching, and composer defaults persistence #511), not this PR — the usage stats table lives in the usage section (not in the settings-data scenario) and its inline styles match the retired primitive exactly.

User-facing impact

None intended. Card/badge/chip visuals are preserved by reproducing the retired CSS token values inside the primitives (and inside SimpleStatsTable for the usage stats table). The settingsConnectionBadge warning tone no longer misuses --info (it now uses --warning, which aliases --info so the color is unchanged but the intent is correct). The .settingsBadge-derived generic labels keep 18px / font-normal via Chip size="sm".

Reviewer notes

  • The badge split (pill Badge + squared Chip) is the load-bearing decision. The alternative — collapsing all 4 surfaces onto pill Badge — was rejected because it regresses the settings-form-a11y "compact squared, not pills" lock and turns the destructive status into a solid red block. See chip-converge-contract for the role-split + token lock.
  • The Table primitive was removed per review (P3-1): single consumer, and the model "table" is a div surface so no second consumer is coming. SimpleStatsTable keeps the same styles inline.
  • Chip variants mirror StatusTone directly, so settings callers pass tone straight through. statusBadgeVariant is retained because health/permission center still use it with the pill Badge.
  • Card is intentionally thin (only data-slot + radius-surface); layout/visual CSS stays at call sites, matching the ChoiceCard philosophy.
  • Commits are independent revert targets; squash on merge.

Checklist

  • Scope matches the PR title and excludes unrelated changes
  • Verification lists commands/results, or explains why they were not run
  • User-facing impact, docs, changelog, migrations, and breaking changes are noted, or marked none
  • Risk, rollback, or review focus is called out for non-trivial changes
  • UI changes include screenshots/video, or explain why not applicable

…es (#520 PR9)
Card (packages/ui/src/primitives/card.tsx) — thin surface container
(data-slot="card" + radius-surface), following maka's ChoiceCard
philosophy: each call site keeps its own layout/visual CSS. settingsRows,
settingsMetricCard, and maka-error-card now route through Card; their CSS
drops the border-radius line (Card owns it), layout is byte-identical.
Table (packages/ui/src/primitives/table.tsx) — shadcn-style family
(Table/TableHeader/TableBody/TableRow/TableHead/TableCell) with data-slot.
settingsStatsTable retires entirely; the table chrome (border + radius +
caption font-size) and the cell chrome (tabular-nums + hairline row
separators + caption-tone color + semibold head) move into the primitive.
maka-error-card stays on Card (not Alert): it is a large crash surface
with shadow-modal + stack <pre>, not a small inline callout.
card-table-converge-contract locks the migration. Updated four existing
contracts whose selectors pinned the retired classes/structure:
radius-converge (drop .settingsRows tier — Card owns it now),
tabular-nums (drop .settingsStatsTable th/td — Table owns it now),
settings-usage (regex now matches the Table family + scoped heads/cells),
web-search-boundary (regex now matches <SettingsRows className=…>).
Token values verified equivalent: --font-weight-semibold=600 (font-semibold),
--space-1/2, --radius-surface=8px. Screenshot manifest passes (32/32, 0 fail).
#520 PR9 commit 2: collapse the four coexisting badge surfaces onto two
canonical primitives, split by UI role.
- pill Badge (primitives/badge.tsx): emphasis markers. Retire the
PrimitiveBadge alias + the legacy ui.tsx Badge (raw emerald/amber
variants). health/permission center, artifact-pane, plan-reminder, and
permission-dialog route through <Badge>.
- squared Chip (primitives/chip.tsx): dense status rows. Retire the
.settingsBadge + .settingsConnectionBadge CSS chips. settings
connection status / default / category markers route through <Chip>;
variants mirror StatusTone, so settings callers pass the tone directly
instead of going through statusBadgeVariant (which stays for the
health/permission pill Badge sites).
- contracts: badge-converge (pill track), chip-converge (squared track,
locks radius-control not pill), settings-form-a11y (lock point moved
from the CSS class to the Chip primitive), radius-converge (drop the
stale ui.tsx badgeVariants entry).
- visual zero-change: Chip cva reproduces the retired CSS oklch alphas
(success/12, info/14, warning/18, destructive/15) and the neutral
foreground-5 base. screenshot pixel diff vs main: settings-bots
light/dark 1280 AE=0.
…ontract, comment fix
#520 PR9 review fixes:
- P2: the three .settingsBadge migration sites (provider-connection-detail
x2, provider-add-form x1) now use Chip size="sm" to reproduce the retired
.settingsBadge geometry (18px / font-normal / 0-6px padding). Without
size="sm" they defaulted to the larger status-row size (20px / semibold /
2-8px padding) and drifted. The neutral background did not drift:
bg-secondary aliases --color-secondary = var(--foreground-5), so only
height/weight/padding moved.
- P3-2: chip-converge-contract now locks user-visible tokens (neutral
bg-secondary + foreground-secondary text, sm/default size geometry,
status-tone alphas /12 /14 /18 /15) so a cva class change that keeps the
import is still caught. Dropped the "no old span" migration-narrative
loop; kept import + role-split + token assertions.
- P3-3: bot.css / connection.css retire comments now say Chip
variant="neutral" (not Badge variant="secondary"), matching the actual
migration and not steering future migrants back to the pill Badge.
Verification: 2050/2050 contract pass, typecheck clean, screenshot
settings-bots light/dark 1280 + light 990 all AE=0 (pixel-identical to
main after the size=sm fix; the 990 variant's earlier 13380 px diff is gone).
#520 PR9 review P3-1: the Table primitive had a single consumer
(usage-settings-page SimpleStatsTable) and no second HTML <table> consumer
in sight — the model "table" is a div-based list (.modelTable), not an HTML
table, so it will never migrate onto Table. Per Occam's razor, lift the
primitive only when a second real consumer appears.
- Delete packages/ui/src/primitives/table.tsx and its barrel export.
- SimpleStatsTable in usage-settings-page now renders a native HTML
<table>/<thead>/<tbody>/<tr>/<th>/<td> with the same Tailwind classes the
primitive applied (w-full border-collapse rounded-[var(--radius-surface)]
border border-border text-caption + cell border-b/px/py/align/
tabular-nums). Styles stay inline so the stats surface is self-contained
until a second HTML <table> consumer justifies lifting it back.
- Rename card-table-converge-contract → card-converge-contract: drop the
Table data-slot / table-sites assertions, keep Card. Note the usage stats
table a11y semantics (aria-label + scope) now live in
settings-usage-contract, whose assertions are updated to match the native
<table>/<th scope> shape.
- settings-form-a11y and tabular-nums are untouched: they assert on the
.modelTable CSS class (a div surface), not on the Table primitive.
Verification: 2049/2049 contract pass (one fewer test — the deleted
"table sites import Table" case), typecheck clean. Screenshot pixel diff vs
main: settings-general light/dark 1280 AE=0; settings-data 1280 shows ~4500
px (RMSE 0.001) in the `data` section, which is main-branch composer/sidebar
drift (#510/#511), not this change — the usage stats table lives in the
`usage` section and its styles are identical to the retired primitive.
…impleStatsTable
Per PR9 review P3: the retire comment still referenced the deleted
packages/ui/src/primitives/table.tsx, which would mislead a future maintainer
into restoring a public Table primitive. Now points at the local native
SimpleStatsTable in usage-settings-page.tsx, matching the actual state.
@Astro-Han
Astro-Han merged commit 80cc73a into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the feat/ui-card-badge-converge branch July 14, 2026 05:05
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)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) by Astro-Han · Pull Request #554 · apache/maka · GitHub
Skip to content

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) - #554

Merged
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge
Jul 5, 2026
Merged

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9)#554
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

#520 PR9: converge the hand-written settings card surfaces onto a Card primitive, and collapse the four coexisting badge surfaces onto two role-split primitives — pill Badge (emphasis markers) and squared Chip (dense settings status rows).

A review-driven correction: an initial Table primitive was retired before merge — with a single HTML <table> consumer and no second one in sight (the model "table" is a div-based list, not an HTML table), it was premature. The usage stats table now stays a local native <table> in usage-settings-page.tsx.

Why

Refs #520. The renderer had four parallel badge implementations and three hand-written settings card CSS surfaces, each drifting from the shared token system. The convergence goal is one primitive per UI role, governed by contracts instead of ad-hoc CSS.

A key finding drove the badge split: Base UI (@base-ui/react 1.5.0/1.6.0) ships no Badge/Chip/Tag component, and shadcn/ui ships Badge but noChip. So both primitives are self-built (cva + data-slot + Base UI useRender), same pattern as the existing Card/Alert primitives. Settings status rows need compact squared chips (radius-control) for information density — the settings-form-a11y contract explicitly locks "not pills". Forcing them onto the pill Badge would regress that intent and turn the destructive tone into a solid red block. The correct convergence is 4 surfaces → 2 primitives by role, not 4 → 1.

The Table primitive was added in an earlier commit but removed per review: the only HTML <table> consumer is the usage stats table, and the model "table" (.modelTable) is a div-based list with radio + chips, not an HTML table, so it will never migrate onto a Table primitive. By Occam's razor the primitive is lifted only when a second real consumer appears; the stats table keeps its styles inline.

Scope

Changed:

  • New packages/ui/src/primitives/card.tsx — thin Card (data-slot="card", radius-surface); caller keeps layout/visual CSS.
  • New packages/ui/src/primitives/chip.tsx — squared Chip (data-slot="chip", radius-control), 5 status-tone variants mirroring StatusTone, sm/default sizes. cva reproduces the retired CSS oklch alphas (success/12, info/14, warning/18, destructive/15) and the neutral foreground-5 base; sm reproduces .settingsBadge (18px / font-normal / 0-6px padding) and default reproduces .settingsConnectionBadge (20px / semibold / 2-8px padding).
  • packages/ui/src/primitives/badge.tsx is now the canonical pill Badge; the PrimitiveBadge alias and the legacy ui.tsxBadge (raw emerald/amber variants) are gone.
  • Migrations onto Card: settingsRows + 3 consumer pages, settingsMetricCard, maka-error-card. Onto Badge: health-center, permission-center, artifact-pane, plan-reminder, permission-dialog. Onto Chip: provider-connection-detail (size="sm" for the generic label), provider-add-form (size="sm"), web-search, memory-settings, account-settings, provider-oauth (8 sites).
  • Usage stats table: stays a local native <table>/<thead>/<tbody>/<tr>/<th scope>/<td> in usage-settings-page.tsx (SimpleStatsTable), with the same Tailwind classes the retired Table primitive applied — no public Table primitive.
  • CSS retired: settings card/table radius rows, .settingsStatsTable, .settingsBadge, .settingsConnectionBadge + data-tone variants.
  • Contracts: new card-converge-contract, badge-converge-contract, chip-converge-contract; updated radius-converge (drop stale .settingsRows tier + ui.tsx badgeVariants entry), tabular-nums (drop .settingsStatsTable), settings-usage (regex matches native <table>/<th scope> a11y), web-search-boundary (regex matches <SettingsRows>), settings-form-a11y (lock point moved from CSS class to Chip primitive).

Not included:

  • PR8 combobox migration (SearchModal + CommandPalette) — separate PR, runtime keyboard interaction risk.
  • PR10 input canonicalization (Add DeepSeek context budget diagnostics #22) — separate PR, implementation merge + contract rewrite.
  • The scattered composer-model-chip / prompt-chip / plan-card-chip CSS classes are not migrated to Chip in this PR; they are left for a future sweep now that Chip exists as the convergence target.
  • A public Table primitive is intentionally not shipped (see Why). Lift when a second HTML <table> consumer appears.

Verification

  • npm run typecheck — clean.
  • npm run -w @maka/desktop test — 2049/2049 pass (one fewer than the initial Table-inclusive state: the deleted "table sites import Table" case).
  • npm run -w @maka/ui build + npm run -w @maka/desktop build:main — clean.
  • Screenshot: settings-bots (8), settings-memory (8), settings-general (8), settings-data (8) captured (no crash).
  • Pixel diff vs main (ImageMagick compare): settings-bots and settings-general light/dark 1280 AE=0 (pixel-identical). settings-data 1280 shows ~4500 px diff (RMSE 0.001) in the data section, which is the main-branch composer/sidebar commits (feat(sidebar): add project-grouped session view mode #510/feat(composer): workspace picker, git branch switching, and composer defaults persistence #511), not this PR — the usage stats table lives in the usage section (not in the settings-data scenario) and its inline styles match the retired primitive exactly.

User-facing impact

None intended. Card/badge/chip visuals are preserved by reproducing the retired CSS token values inside the primitives (and inside SimpleStatsTable for the usage stats table). The settingsConnectionBadge warning tone no longer misuses --info (it now uses --warning, which aliases --info so the color is unchanged but the intent is correct). The .settingsBadge-derived generic labels keep 18px / font-normal via Chip size="sm".

Reviewer notes

  • The badge split (pill Badge + squared Chip) is the load-bearing decision. The alternative — collapsing all 4 surfaces onto pill Badge — was rejected because it regresses the settings-form-a11y "compact squared, not pills" lock and turns the destructive status into a solid red block. See chip-converge-contract for the role-split + token lock.
  • The Table primitive was removed per review (P3-1): single consumer, and the model "table" is a div surface so no second consumer is coming. SimpleStatsTable keeps the same styles inline.
  • Chip variants mirror StatusTone directly, so settings callers pass tone straight through. statusBadgeVariant is retained because health/permission center still use it with the pill Badge.
  • Card is intentionally thin (only data-slot + radius-surface); layout/visual CSS stays at call sites, matching the ChoiceCard philosophy.
  • Commits are independent revert targets; squash on merge.

Checklist

  • Scope matches the PR title and excludes unrelated changes
  • Verification lists commands/results, or explains why they were not run
  • User-facing impact, docs, changelog, migrations, and breaking changes are noted, or marked none
  • Risk, rollback, or review focus is called out for non-trivial changes
  • UI changes include screenshots/video, or explain why not applicable

…es (#520 PR9)
Card (packages/ui/src/primitives/card.tsx) — thin surface container
(data-slot="card" + radius-surface), following maka's ChoiceCard
philosophy: each call site keeps its own layout/visual CSS. settingsRows,
settingsMetricCard, and maka-error-card now route through Card; their CSS
drops the border-radius line (Card owns it), layout is byte-identical.
Table (packages/ui/src/primitives/table.tsx) — shadcn-style family
(Table/TableHeader/TableBody/TableRow/TableHead/TableCell) with data-slot.
settingsStatsTable retires entirely; the table chrome (border + radius +
caption font-size) and the cell chrome (tabular-nums + hairline row
separators + caption-tone color + semibold head) move into the primitive.
maka-error-card stays on Card (not Alert): it is a large crash surface
with shadow-modal + stack <pre>, not a small inline callout.
card-table-converge-contract locks the migration. Updated four existing
contracts whose selectors pinned the retired classes/structure:
radius-converge (drop .settingsRows tier — Card owns it now),
tabular-nums (drop .settingsStatsTable th/td — Table owns it now),
settings-usage (regex now matches the Table family + scoped heads/cells),
web-search-boundary (regex now matches <SettingsRows className=…>).
Token values verified equivalent: --font-weight-semibold=600 (font-semibold),
--space-1/2, --radius-surface=8px. Screenshot manifest passes (32/32, 0 fail).
#520 PR9 commit 2: collapse the four coexisting badge surfaces onto two
canonical primitives, split by UI role.
- pill Badge (primitives/badge.tsx): emphasis markers. Retire the
PrimitiveBadge alias + the legacy ui.tsx Badge (raw emerald/amber
variants). health/permission center, artifact-pane, plan-reminder, and
permission-dialog route through <Badge>.
- squared Chip (primitives/chip.tsx): dense status rows. Retire the
.settingsBadge + .settingsConnectionBadge CSS chips. settings
connection status / default / category markers route through <Chip>;
variants mirror StatusTone, so settings callers pass the tone directly
instead of going through statusBadgeVariant (which stays for the
health/permission pill Badge sites).
- contracts: badge-converge (pill track), chip-converge (squared track,
locks radius-control not pill), settings-form-a11y (lock point moved
from the CSS class to the Chip primitive), radius-converge (drop the
stale ui.tsx badgeVariants entry).
- visual zero-change: Chip cva reproduces the retired CSS oklch alphas
(success/12, info/14, warning/18, destructive/15) and the neutral
foreground-5 base. screenshot pixel diff vs main: settings-bots
light/dark 1280 AE=0.
…ontract, comment fix
#520 PR9 review fixes:
- P2: the three .settingsBadge migration sites (provider-connection-detail
x2, provider-add-form x1) now use Chip size="sm" to reproduce the retired
.settingsBadge geometry (18px / font-normal / 0-6px padding). Without
size="sm" they defaulted to the larger status-row size (20px / semibold /
2-8px padding) and drifted. The neutral background did not drift:
bg-secondary aliases --color-secondary = var(--foreground-5), so only
height/weight/padding moved.
- P3-2: chip-converge-contract now locks user-visible tokens (neutral
bg-secondary + foreground-secondary text, sm/default size geometry,
status-tone alphas /12 /14 /18 /15) so a cva class change that keeps the
import is still caught. Dropped the "no old span" migration-narrative
loop; kept import + role-split + token assertions.
- P3-3: bot.css / connection.css retire comments now say Chip
variant="neutral" (not Badge variant="secondary"), matching the actual
migration and not steering future migrants back to the pill Badge.
Verification: 2050/2050 contract pass, typecheck clean, screenshot
settings-bots light/dark 1280 + light 990 all AE=0 (pixel-identical to
main after the size=sm fix; the 990 variant's earlier 13380 px diff is gone).
#520 PR9 review P3-1: the Table primitive had a single consumer
(usage-settings-page SimpleStatsTable) and no second HTML <table> consumer
in sight — the model "table" is a div-based list (.modelTable), not an HTML
table, so it will never migrate onto Table. Per Occam's razor, lift the
primitive only when a second real consumer appears.
- Delete packages/ui/src/primitives/table.tsx and its barrel export.
- SimpleStatsTable in usage-settings-page now renders a native HTML
<table>/<thead>/<tbody>/<tr>/<th>/<td> with the same Tailwind classes the
primitive applied (w-full border-collapse rounded-[var(--radius-surface)]
border border-border text-caption + cell border-b/px/py/align/
tabular-nums). Styles stay inline so the stats surface is self-contained
until a second HTML <table> consumer justifies lifting it back.
- Rename card-table-converge-contract → card-converge-contract: drop the
Table data-slot / table-sites assertions, keep Card. Note the usage stats
table a11y semantics (aria-label + scope) now live in
settings-usage-contract, whose assertions are updated to match the native
<table>/<th scope> shape.
- settings-form-a11y and tabular-nums are untouched: they assert on the
.modelTable CSS class (a div surface), not on the Table primitive.
Verification: 2049/2049 contract pass (one fewer test — the deleted
"table sites import Table" case), typecheck clean. Screenshot pixel diff vs
main: settings-general light/dark 1280 AE=0; settings-data 1280 shows ~4500
px (RMSE 0.001) in the `data` section, which is main-branch composer/sidebar
drift (#510/#511), not this change — the usage stats table lives in the
`usage` section and its styles are identical to the retired primitive.
…impleStatsTable
Per PR9 review P3: the retire comment still referenced the deleted
packages/ui/src/primitives/table.tsx, which would mislead a future maintainer
into restoring a public Table primitive. Now points at the local native
SimpleStatsTable in usage-settings-page.tsx, matching the actual state.
@Astro-Han
Astro-Han merged commit 80cc73a into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the feat/ui-card-badge-converge branch July 14, 2026 05:05
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)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) by Astro-Han · Pull Request #554 · apache/maka · GitHub
Skip to content

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) - #554

Merged
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge
Jul 5, 2026
Merged

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9)#554
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

#520 PR9: converge the hand-written settings card surfaces onto a Card primitive, and collapse the four coexisting badge surfaces onto two role-split primitives — pill Badge (emphasis markers) and squared Chip (dense settings status rows).

A review-driven correction: an initial Table primitive was retired before merge — with a single HTML <table> consumer and no second one in sight (the model "table" is a div-based list, not an HTML table), it was premature. The usage stats table now stays a local native <table> in usage-settings-page.tsx.

Why

Refs #520. The renderer had four parallel badge implementations and three hand-written settings card CSS surfaces, each drifting from the shared token system. The convergence goal is one primitive per UI role, governed by contracts instead of ad-hoc CSS.

A key finding drove the badge split: Base UI (@base-ui/react 1.5.0/1.6.0) ships no Badge/Chip/Tag component, and shadcn/ui ships Badge but noChip. So both primitives are self-built (cva + data-slot + Base UI useRender), same pattern as the existing Card/Alert primitives. Settings status rows need compact squared chips (radius-control) for information density — the settings-form-a11y contract explicitly locks "not pills". Forcing them onto the pill Badge would regress that intent and turn the destructive tone into a solid red block. The correct convergence is 4 surfaces → 2 primitives by role, not 4 → 1.

The Table primitive was added in an earlier commit but removed per review: the only HTML <table> consumer is the usage stats table, and the model "table" (.modelTable) is a div-based list with radio + chips, not an HTML table, so it will never migrate onto a Table primitive. By Occam's razor the primitive is lifted only when a second real consumer appears; the stats table keeps its styles inline.

Scope

Changed:

  • New packages/ui/src/primitives/card.tsx — thin Card (data-slot="card", radius-surface); caller keeps layout/visual CSS.
  • New packages/ui/src/primitives/chip.tsx — squared Chip (data-slot="chip", radius-control), 5 status-tone variants mirroring StatusTone, sm/default sizes. cva reproduces the retired CSS oklch alphas (success/12, info/14, warning/18, destructive/15) and the neutral foreground-5 base; sm reproduces .settingsBadge (18px / font-normal / 0-6px padding) and default reproduces .settingsConnectionBadge (20px / semibold / 2-8px padding).
  • packages/ui/src/primitives/badge.tsx is now the canonical pill Badge; the PrimitiveBadge alias and the legacy ui.tsxBadge (raw emerald/amber variants) are gone.
  • Migrations onto Card: settingsRows + 3 consumer pages, settingsMetricCard, maka-error-card. Onto Badge: health-center, permission-center, artifact-pane, plan-reminder, permission-dialog. Onto Chip: provider-connection-detail (size="sm" for the generic label), provider-add-form (size="sm"), web-search, memory-settings, account-settings, provider-oauth (8 sites).
  • Usage stats table: stays a local native <table>/<thead>/<tbody>/<tr>/<th scope>/<td> in usage-settings-page.tsx (SimpleStatsTable), with the same Tailwind classes the retired Table primitive applied — no public Table primitive.
  • CSS retired: settings card/table radius rows, .settingsStatsTable, .settingsBadge, .settingsConnectionBadge + data-tone variants.
  • Contracts: new card-converge-contract, badge-converge-contract, chip-converge-contract; updated radius-converge (drop stale .settingsRows tier + ui.tsx badgeVariants entry), tabular-nums (drop .settingsStatsTable), settings-usage (regex matches native <table>/<th scope> a11y), web-search-boundary (regex matches <SettingsRows>), settings-form-a11y (lock point moved from CSS class to Chip primitive).

Not included:

  • PR8 combobox migration (SearchModal + CommandPalette) — separate PR, runtime keyboard interaction risk.
  • PR10 input canonicalization (Add DeepSeek context budget diagnostics #22) — separate PR, implementation merge + contract rewrite.
  • The scattered composer-model-chip / prompt-chip / plan-card-chip CSS classes are not migrated to Chip in this PR; they are left for a future sweep now that Chip exists as the convergence target.
  • A public Table primitive is intentionally not shipped (see Why). Lift when a second HTML <table> consumer appears.

Verification

  • npm run typecheck — clean.
  • npm run -w @maka/desktop test — 2049/2049 pass (one fewer than the initial Table-inclusive state: the deleted "table sites import Table" case).
  • npm run -w @maka/ui build + npm run -w @maka/desktop build:main — clean.
  • Screenshot: settings-bots (8), settings-memory (8), settings-general (8), settings-data (8) captured (no crash).
  • Pixel diff vs main (ImageMagick compare): settings-bots and settings-general light/dark 1280 AE=0 (pixel-identical). settings-data 1280 shows ~4500 px diff (RMSE 0.001) in the data section, which is the main-branch composer/sidebar commits (feat(sidebar): add project-grouped session view mode #510/feat(composer): workspace picker, git branch switching, and composer defaults persistence #511), not this PR — the usage stats table lives in the usage section (not in the settings-data scenario) and its inline styles match the retired primitive exactly.

User-facing impact

None intended. Card/badge/chip visuals are preserved by reproducing the retired CSS token values inside the primitives (and inside SimpleStatsTable for the usage stats table). The settingsConnectionBadge warning tone no longer misuses --info (it now uses --warning, which aliases --info so the color is unchanged but the intent is correct). The .settingsBadge-derived generic labels keep 18px / font-normal via Chip size="sm".

Reviewer notes

  • The badge split (pill Badge + squared Chip) is the load-bearing decision. The alternative — collapsing all 4 surfaces onto pill Badge — was rejected because it regresses the settings-form-a11y "compact squared, not pills" lock and turns the destructive status into a solid red block. See chip-converge-contract for the role-split + token lock.
  • The Table primitive was removed per review (P3-1): single consumer, and the model "table" is a div surface so no second consumer is coming. SimpleStatsTable keeps the same styles inline.
  • Chip variants mirror StatusTone directly, so settings callers pass tone straight through. statusBadgeVariant is retained because health/permission center still use it with the pill Badge.
  • Card is intentionally thin (only data-slot + radius-surface); layout/visual CSS stays at call sites, matching the ChoiceCard philosophy.
  • Commits are independent revert targets; squash on merge.

Checklist

  • Scope matches the PR title and excludes unrelated changes
  • Verification lists commands/results, or explains why they were not run
  • User-facing impact, docs, changelog, migrations, and breaking changes are noted, or marked none
  • Risk, rollback, or review focus is called out for non-trivial changes
  • UI changes include screenshots/video, or explain why not applicable

…es (#520 PR9)
Card (packages/ui/src/primitives/card.tsx) — thin surface container
(data-slot="card" + radius-surface), following maka's ChoiceCard
philosophy: each call site keeps its own layout/visual CSS. settingsRows,
settingsMetricCard, and maka-error-card now route through Card; their CSS
drops the border-radius line (Card owns it), layout is byte-identical.
Table (packages/ui/src/primitives/table.tsx) — shadcn-style family
(Table/TableHeader/TableBody/TableRow/TableHead/TableCell) with data-slot.
settingsStatsTable retires entirely; the table chrome (border + radius +
caption font-size) and the cell chrome (tabular-nums + hairline row
separators + caption-tone color + semibold head) move into the primitive.
maka-error-card stays on Card (not Alert): it is a large crash surface
with shadow-modal + stack <pre>, not a small inline callout.
card-table-converge-contract locks the migration. Updated four existing
contracts whose selectors pinned the retired classes/structure:
radius-converge (drop .settingsRows tier — Card owns it now),
tabular-nums (drop .settingsStatsTable th/td — Table owns it now),
settings-usage (regex now matches the Table family + scoped heads/cells),
web-search-boundary (regex now matches <SettingsRows className=…>).
Token values verified equivalent: --font-weight-semibold=600 (font-semibold),
--space-1/2, --radius-surface=8px. Screenshot manifest passes (32/32, 0 fail).
#520 PR9 commit 2: collapse the four coexisting badge surfaces onto two
canonical primitives, split by UI role.
- pill Badge (primitives/badge.tsx): emphasis markers. Retire the
PrimitiveBadge alias + the legacy ui.tsx Badge (raw emerald/amber
variants). health/permission center, artifact-pane, plan-reminder, and
permission-dialog route through <Badge>.
- squared Chip (primitives/chip.tsx): dense status rows. Retire the
.settingsBadge + .settingsConnectionBadge CSS chips. settings
connection status / default / category markers route through <Chip>;
variants mirror StatusTone, so settings callers pass the tone directly
instead of going through statusBadgeVariant (which stays for the
health/permission pill Badge sites).
- contracts: badge-converge (pill track), chip-converge (squared track,
locks radius-control not pill), settings-form-a11y (lock point moved
from the CSS class to the Chip primitive), radius-converge (drop the
stale ui.tsx badgeVariants entry).
- visual zero-change: Chip cva reproduces the retired CSS oklch alphas
(success/12, info/14, warning/18, destructive/15) and the neutral
foreground-5 base. screenshot pixel diff vs main: settings-bots
light/dark 1280 AE=0.
…ontract, comment fix
#520 PR9 review fixes:
- P2: the three .settingsBadge migration sites (provider-connection-detail
x2, provider-add-form x1) now use Chip size="sm" to reproduce the retired
.settingsBadge geometry (18px / font-normal / 0-6px padding). Without
size="sm" they defaulted to the larger status-row size (20px / semibold /
2-8px padding) and drifted. The neutral background did not drift:
bg-secondary aliases --color-secondary = var(--foreground-5), so only
height/weight/padding moved.
- P3-2: chip-converge-contract now locks user-visible tokens (neutral
bg-secondary + foreground-secondary text, sm/default size geometry,
status-tone alphas /12 /14 /18 /15) so a cva class change that keeps the
import is still caught. Dropped the "no old span" migration-narrative
loop; kept import + role-split + token assertions.
- P3-3: bot.css / connection.css retire comments now say Chip
variant="neutral" (not Badge variant="secondary"), matching the actual
migration and not steering future migrants back to the pill Badge.
Verification: 2050/2050 contract pass, typecheck clean, screenshot
settings-bots light/dark 1280 + light 990 all AE=0 (pixel-identical to
main after the size=sm fix; the 990 variant's earlier 13380 px diff is gone).
#520 PR9 review P3-1: the Table primitive had a single consumer
(usage-settings-page SimpleStatsTable) and no second HTML <table> consumer
in sight — the model "table" is a div-based list (.modelTable), not an HTML
table, so it will never migrate onto Table. Per Occam's razor, lift the
primitive only when a second real consumer appears.
- Delete packages/ui/src/primitives/table.tsx and its barrel export.
- SimpleStatsTable in usage-settings-page now renders a native HTML
<table>/<thead>/<tbody>/<tr>/<th>/<td> with the same Tailwind classes the
primitive applied (w-full border-collapse rounded-[var(--radius-surface)]
border border-border text-caption + cell border-b/px/py/align/
tabular-nums). Styles stay inline so the stats surface is self-contained
until a second HTML <table> consumer justifies lifting it back.
- Rename card-table-converge-contract → card-converge-contract: drop the
Table data-slot / table-sites assertions, keep Card. Note the usage stats
table a11y semantics (aria-label + scope) now live in
settings-usage-contract, whose assertions are updated to match the native
<table>/<th scope> shape.
- settings-form-a11y and tabular-nums are untouched: they assert on the
.modelTable CSS class (a div surface), not on the Table primitive.
Verification: 2049/2049 contract pass (one fewer test — the deleted
"table sites import Table" case), typecheck clean. Screenshot pixel diff vs
main: settings-general light/dark 1280 AE=0; settings-data 1280 shows ~4500
px (RMSE 0.001) in the `data` section, which is main-branch composer/sidebar
drift (#510/#511), not this change — the usage stats table lives in the
`usage` section and its styles are identical to the retired primitive.
…impleStatsTable
Per PR9 review P3: the retire comment still referenced the deleted
packages/ui/src/primitives/table.tsx, which would mislead a future maintainer
into restoring a public Table primitive. Now points at the local native
SimpleStatsTable in usage-settings-page.tsx, matching the actual state.
@Astro-Han
Astro-Han merged commit 80cc73a into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the feat/ui-card-badge-converge branch July 14, 2026 05:05
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)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) by Astro-Han · Pull Request #554 · apache/maka · GitHub
Skip to content

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9) - #554

Merged
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge
Jul 5, 2026
Merged

feat(ui): converge card/table + badge surfaces onto primitives (#520 PR9)#554
Astro-Han merged 5 commits into
mainfrom
feat/ui-card-badge-converge

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

#520 PR9: converge the hand-written settings card surfaces onto a Card primitive, and collapse the four coexisting badge surfaces onto two role-split primitives — pill Badge (emphasis markers) and squared Chip (dense settings status rows).

A review-driven correction: an initial Table primitive was retired before merge — with a single HTML <table> consumer and no second one in sight (the model "table" is a div-based list, not an HTML table), it was premature. The usage stats table now stays a local native <table> in usage-settings-page.tsx.

Why

Refs #520. The renderer had four parallel badge implementations and three hand-written settings card CSS surfaces, each drifting from the shared token system. The convergence goal is one primitive per UI role, governed by contracts instead of ad-hoc CSS.

A key finding drove the badge split: Base UI (@base-ui/react 1.5.0/1.6.0) ships no Badge/Chip/Tag component, and shadcn/ui ships Badge but noChip. So both primitives are self-built (cva + data-slot + Base UI useRender), same pattern as the existing Card/Alert primitives. Settings status rows need compact squared chips (radius-control) for information density — the settings-form-a11y contract explicitly locks "not pills". Forcing them onto the pill Badge would regress that intent and turn the destructive tone into a solid red block. The correct convergence is 4 surfaces → 2 primitives by role, not 4 → 1.

The Table primitive was added in an earlier commit but removed per review: the only HTML <table> consumer is the usage stats table, and the model "table" (.modelTable) is a div-based list with radio + chips, not an HTML table, so it will never migrate onto a Table primitive. By Occam's razor the primitive is lifted only when a second real consumer appears; the stats table keeps its styles inline.

Scope

Changed:

  • New packages/ui/src/primitives/card.tsx — thin Card (data-slot="card", radius-surface); caller keeps layout/visual CSS.
  • New packages/ui/src/primitives/chip.tsx — squared Chip (data-slot="chip", radius-control), 5 status-tone variants mirroring StatusTone, sm/default sizes. cva reproduces the retired CSS oklch alphas (success/12, info/14, warning/18, destructive/15) and the neutral foreground-5 base; sm reproduces .settingsBadge (18px / font-normal / 0-6px padding) and default reproduces .settingsConnectionBadge (20px / semibold / 2-8px padding).
  • packages/ui/src/primitives/badge.tsx is now the canonical pill Badge; the PrimitiveBadge alias and the legacy ui.tsxBadge (raw emerald/amber variants) are gone.
  • Migrations onto Card: settingsRows + 3 consumer pages, settingsMetricCard, maka-error-card. Onto Badge: health-center, permission-center, artifact-pane, plan-reminder, permission-dialog. Onto Chip: provider-connection-detail (size="sm" for the generic label), provider-add-form (size="sm"), web-search, memory-settings, account-settings, provider-oauth (8 sites).
  • Usage stats table: stays a local native <table>/<thead>/<tbody>/<tr>/<th scope>/<td> in usage-settings-page.tsx (SimpleStatsTable), with the same Tailwind classes the retired Table primitive applied — no public Table primitive.
  • CSS retired: settings card/table radius rows, .settingsStatsTable, .settingsBadge, .settingsConnectionBadge + data-tone variants.
  • Contracts: new card-converge-contract, badge-converge-contract, chip-converge-contract; updated radius-converge (drop stale .settingsRows tier + ui.tsx badgeVariants entry), tabular-nums (drop .settingsStatsTable), settings-usage (regex matches native <table>/<th scope> a11y), web-search-boundary (regex matches <SettingsRows>), settings-form-a11y (lock point moved from CSS class to Chip primitive).

Not included:

  • PR8 combobox migration (SearchModal + CommandPalette) — separate PR, runtime keyboard interaction risk.
  • PR10 input canonicalization (Add DeepSeek context budget diagnostics #22) — separate PR, implementation merge + contract rewrite.
  • The scattered composer-model-chip / prompt-chip / plan-card-chip CSS classes are not migrated to Chip in this PR; they are left for a future sweep now that Chip exists as the convergence target.
  • A public Table primitive is intentionally not shipped (see Why). Lift when a second HTML <table> consumer appears.

Verification

  • npm run typecheck — clean.
  • npm run -w @maka/desktop test — 2049/2049 pass (one fewer than the initial Table-inclusive state: the deleted "table sites import Table" case).
  • npm run -w @maka/ui build + npm run -w @maka/desktop build:main — clean.
  • Screenshot: settings-bots (8), settings-memory (8), settings-general (8), settings-data (8) captured (no crash).
  • Pixel diff vs main (ImageMagick compare): settings-bots and settings-general light/dark 1280 AE=0 (pixel-identical). settings-data 1280 shows ~4500 px diff (RMSE 0.001) in the data section, which is the main-branch composer/sidebar commits (feat(sidebar): add project-grouped session view mode #510/feat(composer): workspace picker, git branch switching, and composer defaults persistence #511), not this PR — the usage stats table lives in the usage section (not in the settings-data scenario) and its inline styles match the retired primitive exactly.

User-facing impact

None intended. Card/badge/chip visuals are preserved by reproducing the retired CSS token values inside the primitives (and inside SimpleStatsTable for the usage stats table). The settingsConnectionBadge warning tone no longer misuses --info (it now uses --warning, which aliases --info so the color is unchanged but the intent is correct). The .settingsBadge-derived generic labels keep 18px / font-normal via Chip size="sm".

Reviewer notes

  • The badge split (pill Badge + squared Chip) is the load-bearing decision. The alternative — collapsing all 4 surfaces onto pill Badge — was rejected because it regresses the settings-form-a11y "compact squared, not pills" lock and turns the destructive status into a solid red block. See chip-converge-contract for the role-split + token lock.
  • The Table primitive was removed per review (P3-1): single consumer, and the model "table" is a div surface so no second consumer is coming. SimpleStatsTable keeps the same styles inline.
  • Chip variants mirror StatusTone directly, so settings callers pass tone straight through. statusBadgeVariant is retained because health/permission center still use it with the pill Badge.
  • Card is intentionally thin (only data-slot + radius-surface); layout/visual CSS stays at call sites, matching the ChoiceCard philosophy.
  • Commits are independent revert targets; squash on merge.

Checklist

  • Scope matches the PR title and excludes unrelated changes
  • Verification lists commands/results, or explains why they were not run
  • User-facing impact, docs, changelog, migrations, and breaking changes are noted, or marked none
  • Risk, rollback, or review focus is called out for non-trivial changes
  • UI changes include screenshots/video, or explain why not applicable

…es (#520 PR9)
Card (packages/ui/src/primitives/card.tsx) — thin surface container
(data-slot="card" + radius-surface), following maka's ChoiceCard
philosophy: each call site keeps its own layout/visual CSS. settingsRows,
settingsMetricCard, and maka-error-card now route through Card; their CSS
drops the border-radius line (Card owns it), layout is byte-identical.
Table (packages/ui/src/primitives/table.tsx) — shadcn-style family
(Table/TableHeader/TableBody/TableRow/TableHead/TableCell) with data-slot.
settingsStatsTable retires entirely; the table chrome (border + radius +
caption font-size) and the cell chrome (tabular-nums + hairline row
separators + caption-tone color + semibold head) move into the primitive.
maka-error-card stays on Card (not Alert): it is a large crash surface
with shadow-modal + stack <pre>, not a small inline callout.
card-table-converge-contract locks the migration. Updated four existing
contracts whose selectors pinned the retired classes/structure:
radius-converge (drop .settingsRows tier — Card owns it now),
tabular-nums (drop .settingsStatsTable th/td — Table owns it now),
settings-usage (regex now matches the Table family + scoped heads/cells),
web-search-boundary (regex now matches <SettingsRows className=…>).
Token values verified equivalent: --font-weight-semibold=600 (font-semibold),
--space-1/2, --radius-surface=8px. Screenshot manifest passes (32/32, 0 fail).
#520 PR9 commit 2: collapse the four coexisting badge surfaces onto two
canonical primitives, split by UI role.
- pill Badge (primitives/badge.tsx): emphasis markers. Retire the
PrimitiveBadge alias + the legacy ui.tsx Badge (raw emerald/amber
variants). health/permission center, artifact-pane, plan-reminder, and
permission-dialog route through <Badge>.
- squared Chip (primitives/chip.tsx): dense status rows. Retire the
.settingsBadge + .settingsConnectionBadge CSS chips. settings
connection status / default / category markers route through <Chip>;
variants mirror StatusTone, so settings callers pass the tone directly
instead of going through statusBadgeVariant (which stays for the
health/permission pill Badge sites).
- contracts: badge-converge (pill track), chip-converge (squared track,
locks radius-control not pill), settings-form-a11y (lock point moved
from the CSS class to the Chip primitive), radius-converge (drop the
stale ui.tsx badgeVariants entry).
- visual zero-change: Chip cva reproduces the retired CSS oklch alphas
(success/12, info/14, warning/18, destructive/15) and the neutral
foreground-5 base. screenshot pixel diff vs main: settings-bots
light/dark 1280 AE=0.
…ontract, comment fix
#520 PR9 review fixes:
- P2: the three .settingsBadge migration sites (provider-connection-detail
x2, provider-add-form x1) now use Chip size="sm" to reproduce the retired
.settingsBadge geometry (18px / font-normal / 0-6px padding). Without
size="sm" they defaulted to the larger status-row size (20px / semibold /
2-8px padding) and drifted. The neutral background did not drift:
bg-secondary aliases --color-secondary = var(--foreground-5), so only
height/weight/padding moved.
- P3-2: chip-converge-contract now locks user-visible tokens (neutral
bg-secondary + foreground-secondary text, sm/default size geometry,
status-tone alphas /12 /14 /18 /15) so a cva class change that keeps the
import is still caught. Dropped the "no old span" migration-narrative
loop; kept import + role-split + token assertions.
- P3-3: bot.css / connection.css retire comments now say Chip
variant="neutral" (not Badge variant="secondary"), matching the actual
migration and not steering future migrants back to the pill Badge.
Verification: 2050/2050 contract pass, typecheck clean, screenshot
settings-bots light/dark 1280 + light 990 all AE=0 (pixel-identical to
main after the size=sm fix; the 990 variant's earlier 13380 px diff is gone).
#520 PR9 review P3-1: the Table primitive had a single consumer
(usage-settings-page SimpleStatsTable) and no second HTML <table> consumer
in sight — the model "table" is a div-based list (.modelTable), not an HTML
table, so it will never migrate onto Table. Per Occam's razor, lift the
primitive only when a second real consumer appears.
- Delete packages/ui/src/primitives/table.tsx and its barrel export.
- SimpleStatsTable in usage-settings-page now renders a native HTML
<table>/<thead>/<tbody>/<tr>/<th>/<td> with the same Tailwind classes the
primitive applied (w-full border-collapse rounded-[var(--radius-surface)]
border border-border text-caption + cell border-b/px/py/align/
tabular-nums). Styles stay inline so the stats surface is self-contained
until a second HTML <table> consumer justifies lifting it back.
- Rename card-table-converge-contract → card-converge-contract: drop the
Table data-slot / table-sites assertions, keep Card. Note the usage stats
table a11y semantics (aria-label + scope) now live in
settings-usage-contract, whose assertions are updated to match the native
<table>/<th scope> shape.
- settings-form-a11y and tabular-nums are untouched: they assert on the
.modelTable CSS class (a div surface), not on the Table primitive.
Verification: 2049/2049 contract pass (one fewer test — the deleted
"table sites import Table" case), typecheck clean. Screenshot pixel diff vs
main: settings-general light/dark 1280 AE=0; settings-data 1280 shows ~4500
px (RMSE 0.001) in the `data` section, which is main-branch composer/sidebar
drift (#510/#511), not this change — the usage stats table lives in the
`usage` section and its styles are identical to the retired primitive.
…impleStatsTable
Per PR9 review P3: the retire comment still referenced the deleted
packages/ui/src/primitives/table.tsx, which would mislead a future maintainer
into restoring a public Table primitive. Now points at the local native
SimpleStatsTable in usage-settings-page.tsx, matching the actual state.
@Astro-Han
Astro-Han merged commit 80cc73a into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the feat/ui-card-badge-converge branch July 14, 2026 05:05
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