refactor(ui): hand the chat meta row back to Astryx primitives - #2358

Merged
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives
Aug 7, 2026
Merged

refactor(ui): hand the chat meta row back to Astryx primitives#2358
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The row under a chat message reimplemented parts Astryx already ships, and every visible defect in it traced back to one of those reimplementations.

  • Time. A hand-rolled <small> carried a native title and aria-hidden, so the absolute reading was mouse-only and the visible one was hidden from assistive tech. It is now Timestamp — a semantic <time datetime> with a copyable hover card, and exactly what ChatMessageMetadata's own docs put in that slot. formatClockTime had no other caller and is deleted.
  • Icons. Four separate 12 literals became one named constant at 14. --icon-size reserves 12–14 for "dense meta" and this row is dense meta; 14 is also where the same position already sits most often across the app. A size must be passed at all because Astryx's sm button bounds the icon slot at 16px without resizing the glyph — measured, an unsized lucide icon renders 16×24, squashed.
  • Tooltips. Each button was wrapped in a <Tooltip> element. Button takes a tooltip prop and attaches the same behaviour through a hook, which is what its source recommends. The display: contents wrapper is gone and aria-describedby now resolves — previously it computed to nothing, which is why call sites carried hand-written aria-label fallbacks.
  • Hover gate. It sat on the whole row and so covered the time too, contrary to what the comment directly above it claimed. It moves to the footer slot, so the time stays visible. Astryx's · separator is dropped rather than gated: with the time always present and the actions fading in beside it, the bullet added a mark without adding a distinction.
  • Spacing. The row's top margin goes 4px → 6px. Its box is the 28px height of the buttons, not the 12px of the time, so it sat tighter against the bubble than its margin suggested.

Behaviour change worth knowing: the hour cycle now follows the host locale instead of a hardcoded hour12: false. Most locales are unaffected (14:32 in zh / en-GB / ja / de / fr); en-US renders 2:32 PM. The hour also loses its leading zero (9:05, not 09:05), which is why tabular-nums stays on that element.

Refs #2359 — the icon sizing policy this lands against is unenforced repo-wide (145 hardcoded sizes across 11 values). This PR deliberately does not attempt that sweep.

Verification

  • npm run -w @maka/ui test — 442 passing
  • npm run -w @maka/ui typecheck, npm --workspace @maka/desktop run typecheck — clean
  • npm run lint, npm run format:check — clean
  • node scripts/check-dead-css.mjs --check, node scripts/check-a11y.mjs — clean
  • Not run: Playwright E2E and the desktop main-process suite — this change is renderer-only presentation with no journey or IPC surface.

Checked against computed styles in Storybook (Product/Shell Official AppShell → Native Conversation) rather than by eye:

result
time element<time datetime="…">, no aria-hidden, opacity 1 at rest
separatordisplay: none
icon14×14 in a 16px slot, 28px button, stroke 1.75 (the globally governed value)
tooltip wiringbutton is a direct child of the meta row, aria-describedby resolves
spacingrow margin-top: 6px

Review focus

pointer-events: none was tried on the transparent actions — a transparent button still takes clicks — and reverted. It makes the pointer fall through to the ancestor whose :hover is what restores the button, so being clickable again depends on the browser redoing hit-testing on a later mouse event. Continuous movement gets there; a pointer that jumps onto the button and stops could not be reproduced on this surface, so the fix could not be demonstrated safe. The pre-existing behaviour is left untouched and the reasoning is recorded in the CSS.

The row below a message reimplemented parts Astryx already ships, and
each visible defect traced to one of those reimplementations.
- The time was a hand-rolled `<small>` carrying a native `title` and
`aria-hidden`, so the absolute reading was mouse-only and the visible
one was hidden from assistive tech. It is now `Timestamp`, which is
what `ChatMessageMetadata`'s own docs put in that slot: a semantic
`<time datetime>` with a copyable hover card. `formatClockTime` had no
other caller and is gone; its `hour12: false` was the app overriding a
preference that belongs to the reader's system, so the hour cycle now
follows the host locale (`14:32` in zh/en-GB/ja/de/fr, `2:32 PM` in
en-US) and the hour loses its leading zero.
- Footer icons were four separate `12` literals. They are now one named
constant at 14 — the top of the range `--icon-size` reserves for "dense
meta", and where the same position already sits most often across the
app. A size has to be passed at all because Astryx's `sm` button bounds
the icon slot at 16px without resizing the glyph: measured, an unsized
lucide icon renders 16×24, squashed.
- Tooltips wrapped each button in a `<Tooltip>` element. `Button` takes a
`tooltip` prop and attaches the same behaviour through a hook, which is
what its source recommends — the `display: contents` wrapper is gone
and `aria-describedby` now resolves.
- The hover gate sat on the row and so covered the time too, contrary to
what the comment above it claimed. It moves to the footer slot, so the
time stays put. Astryx's `·` separator between the slots is dropped
outright: with the time always present and the actions fading in beside
it, the two already read as separate and the bullet only adds a mark.
- The row's top margin goes 4px → 6px. Its box is the 28px height of the
buttons, not of the 12px time, so it sat tighter against the bubble
than the margin suggested.
Verified in Storybook against computed styles: `<time>` with a datetime,
no `aria-hidden`, opacity 1 at rest; separator `display: none`; icons
14×14 in a 16px slot at the globally governed 1.75 stroke; buttons direct
children of the meta row with `aria-describedby` set.
A transparent action is still clickable. `pointer-events: none` was tried
and reverted: it depends on the browser redoing hit-testing after the
ancestor `:hover` restores the button, and the jump-and-stop case could
not be reproduced on this surface to prove it safe.
@Astro-Han
Astro-Han marked this pull request as ready for review August 7, 2026 03:35
@Astro-Han
Astro-Han merged commit e4168f2 into mainAug 7, 2026
19 of 23 checks passed
@Astro-Han
Astro-Han deleted the refactor/chat-meta-astryx-primitives branch August 7, 2026 03:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

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

refactor(ui): hand the chat meta row back to Astryx primitives - #2358

Merged
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives
Aug 7, 2026
Merged

refactor(ui): hand the chat meta row back to Astryx primitives#2358
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The row under a chat message reimplemented parts Astryx already ships, and every visible defect in it traced back to one of those reimplementations.

  • Time. A hand-rolled <small> carried a native title and aria-hidden, so the absolute reading was mouse-only and the visible one was hidden from assistive tech. It is now Timestamp — a semantic <time datetime> with a copyable hover card, and exactly what ChatMessageMetadata's own docs put in that slot. formatClockTime had no other caller and is deleted.
  • Icons. Four separate 12 literals became one named constant at 14. --icon-size reserves 12–14 for "dense meta" and this row is dense meta; 14 is also where the same position already sits most often across the app. A size must be passed at all because Astryx's sm button bounds the icon slot at 16px without resizing the glyph — measured, an unsized lucide icon renders 16×24, squashed.
  • Tooltips. Each button was wrapped in a <Tooltip> element. Button takes a tooltip prop and attaches the same behaviour through a hook, which is what its source recommends. The display: contents wrapper is gone and aria-describedby now resolves — previously it computed to nothing, which is why call sites carried hand-written aria-label fallbacks.
  • Hover gate. It sat on the whole row and so covered the time too, contrary to what the comment directly above it claimed. It moves to the footer slot, so the time stays visible. Astryx's · separator is dropped rather than gated: with the time always present and the actions fading in beside it, the bullet added a mark without adding a distinction.
  • Spacing. The row's top margin goes 4px → 6px. Its box is the 28px height of the buttons, not the 12px of the time, so it sat tighter against the bubble than its margin suggested.

Behaviour change worth knowing: the hour cycle now follows the host locale instead of a hardcoded hour12: false. Most locales are unaffected (14:32 in zh / en-GB / ja / de / fr); en-US renders 2:32 PM. The hour also loses its leading zero (9:05, not 09:05), which is why tabular-nums stays on that element.

Refs #2359 — the icon sizing policy this lands against is unenforced repo-wide (145 hardcoded sizes across 11 values). This PR deliberately does not attempt that sweep.

Verification

  • npm run -w @maka/ui test — 442 passing
  • npm run -w @maka/ui typecheck, npm --workspace @maka/desktop run typecheck — clean
  • npm run lint, npm run format:check — clean
  • node scripts/check-dead-css.mjs --check, node scripts/check-a11y.mjs — clean
  • Not run: Playwright E2E and the desktop main-process suite — this change is renderer-only presentation with no journey or IPC surface.

Checked against computed styles in Storybook (Product/Shell Official AppShell → Native Conversation) rather than by eye:

result
time element<time datetime="…">, no aria-hidden, opacity 1 at rest
separatordisplay: none
icon14×14 in a 16px slot, 28px button, stroke 1.75 (the globally governed value)
tooltip wiringbutton is a direct child of the meta row, aria-describedby resolves
spacingrow margin-top: 6px

Review focus

pointer-events: none was tried on the transparent actions — a transparent button still takes clicks — and reverted. It makes the pointer fall through to the ancestor whose :hover is what restores the button, so being clickable again depends on the browser redoing hit-testing on a later mouse event. Continuous movement gets there; a pointer that jumps onto the button and stops could not be reproduced on this surface, so the fix could not be demonstrated safe. The pre-existing behaviour is left untouched and the reasoning is recorded in the CSS.

The row below a message reimplemented parts Astryx already ships, and
each visible defect traced to one of those reimplementations.
- The time was a hand-rolled `<small>` carrying a native `title` and
`aria-hidden`, so the absolute reading was mouse-only and the visible
one was hidden from assistive tech. It is now `Timestamp`, which is
what `ChatMessageMetadata`'s own docs put in that slot: a semantic
`<time datetime>` with a copyable hover card. `formatClockTime` had no
other caller and is gone; its `hour12: false` was the app overriding a
preference that belongs to the reader's system, so the hour cycle now
follows the host locale (`14:32` in zh/en-GB/ja/de/fr, `2:32 PM` in
en-US) and the hour loses its leading zero.
- Footer icons were four separate `12` literals. They are now one named
constant at 14 — the top of the range `--icon-size` reserves for "dense
meta", and where the same position already sits most often across the
app. A size has to be passed at all because Astryx's `sm` button bounds
the icon slot at 16px without resizing the glyph: measured, an unsized
lucide icon renders 16×24, squashed.
- Tooltips wrapped each button in a `<Tooltip>` element. `Button` takes a
`tooltip` prop and attaches the same behaviour through a hook, which is
what its source recommends — the `display: contents` wrapper is gone
and `aria-describedby` now resolves.
- The hover gate sat on the row and so covered the time too, contrary to
what the comment above it claimed. It moves to the footer slot, so the
time stays put. Astryx's `·` separator between the slots is dropped
outright: with the time always present and the actions fading in beside
it, the two already read as separate and the bullet only adds a mark.
- The row's top margin goes 4px → 6px. Its box is the 28px height of the
buttons, not of the 12px time, so it sat tighter against the bubble
than the margin suggested.
Verified in Storybook against computed styles: `<time>` with a datetime,
no `aria-hidden`, opacity 1 at rest; separator `display: none`; icons
14×14 in a 16px slot at the globally governed 1.75 stroke; buttons direct
children of the meta row with `aria-describedby` set.
A transparent action is still clickable. `pointer-events: none` was tried
and reverted: it depends on the browser redoing hit-testing after the
ancestor `:hover` restores the button, and the jump-and-stop case could
not be reproduced on this surface to prove it safe.
@Astro-Han
Astro-Han marked this pull request as ready for review August 7, 2026 03:35
@Astro-Han
Astro-Han merged commit e4168f2 into mainAug 7, 2026
19 of 23 checks passed
@Astro-Han
Astro-Han deleted the refactor/chat-meta-astryx-primitives branch August 7, 2026 03:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

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

refactor(ui): hand the chat meta row back to Astryx primitives - #2358

Merged
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives
Aug 7, 2026
Merged

refactor(ui): hand the chat meta row back to Astryx primitives#2358
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The row under a chat message reimplemented parts Astryx already ships, and every visible defect in it traced back to one of those reimplementations.

  • Time. A hand-rolled <small> carried a native title and aria-hidden, so the absolute reading was mouse-only and the visible one was hidden from assistive tech. It is now Timestamp — a semantic <time datetime> with a copyable hover card, and exactly what ChatMessageMetadata's own docs put in that slot. formatClockTime had no other caller and is deleted.
  • Icons. Four separate 12 literals became one named constant at 14. --icon-size reserves 12–14 for "dense meta" and this row is dense meta; 14 is also where the same position already sits most often across the app. A size must be passed at all because Astryx's sm button bounds the icon slot at 16px without resizing the glyph — measured, an unsized lucide icon renders 16×24, squashed.
  • Tooltips. Each button was wrapped in a <Tooltip> element. Button takes a tooltip prop and attaches the same behaviour through a hook, which is what its source recommends. The display: contents wrapper is gone and aria-describedby now resolves — previously it computed to nothing, which is why call sites carried hand-written aria-label fallbacks.
  • Hover gate. It sat on the whole row and so covered the time too, contrary to what the comment directly above it claimed. It moves to the footer slot, so the time stays visible. Astryx's · separator is dropped rather than gated: with the time always present and the actions fading in beside it, the bullet added a mark without adding a distinction.
  • Spacing. The row's top margin goes 4px → 6px. Its box is the 28px height of the buttons, not the 12px of the time, so it sat tighter against the bubble than its margin suggested.

Behaviour change worth knowing: the hour cycle now follows the host locale instead of a hardcoded hour12: false. Most locales are unaffected (14:32 in zh / en-GB / ja / de / fr); en-US renders 2:32 PM. The hour also loses its leading zero (9:05, not 09:05), which is why tabular-nums stays on that element.

Refs #2359 — the icon sizing policy this lands against is unenforced repo-wide (145 hardcoded sizes across 11 values). This PR deliberately does not attempt that sweep.

Verification

  • npm run -w @maka/ui test — 442 passing
  • npm run -w @maka/ui typecheck, npm --workspace @maka/desktop run typecheck — clean
  • npm run lint, npm run format:check — clean
  • node scripts/check-dead-css.mjs --check, node scripts/check-a11y.mjs — clean
  • Not run: Playwright E2E and the desktop main-process suite — this change is renderer-only presentation with no journey or IPC surface.

Checked against computed styles in Storybook (Product/Shell Official AppShell → Native Conversation) rather than by eye:

result
time element<time datetime="…">, no aria-hidden, opacity 1 at rest
separatordisplay: none
icon14×14 in a 16px slot, 28px button, stroke 1.75 (the globally governed value)
tooltip wiringbutton is a direct child of the meta row, aria-describedby resolves
spacingrow margin-top: 6px

Review focus

pointer-events: none was tried on the transparent actions — a transparent button still takes clicks — and reverted. It makes the pointer fall through to the ancestor whose :hover is what restores the button, so being clickable again depends on the browser redoing hit-testing on a later mouse event. Continuous movement gets there; a pointer that jumps onto the button and stops could not be reproduced on this surface, so the fix could not be demonstrated safe. The pre-existing behaviour is left untouched and the reasoning is recorded in the CSS.

The row below a message reimplemented parts Astryx already ships, and
each visible defect traced to one of those reimplementations.
- The time was a hand-rolled `<small>` carrying a native `title` and
`aria-hidden`, so the absolute reading was mouse-only and the visible
one was hidden from assistive tech. It is now `Timestamp`, which is
what `ChatMessageMetadata`'s own docs put in that slot: a semantic
`<time datetime>` with a copyable hover card. `formatClockTime` had no
other caller and is gone; its `hour12: false` was the app overriding a
preference that belongs to the reader's system, so the hour cycle now
follows the host locale (`14:32` in zh/en-GB/ja/de/fr, `2:32 PM` in
en-US) and the hour loses its leading zero.
- Footer icons were four separate `12` literals. They are now one named
constant at 14 — the top of the range `--icon-size` reserves for "dense
meta", and where the same position already sits most often across the
app. A size has to be passed at all because Astryx's `sm` button bounds
the icon slot at 16px without resizing the glyph: measured, an unsized
lucide icon renders 16×24, squashed.
- Tooltips wrapped each button in a `<Tooltip>` element. `Button` takes a
`tooltip` prop and attaches the same behaviour through a hook, which is
what its source recommends — the `display: contents` wrapper is gone
and `aria-describedby` now resolves.
- The hover gate sat on the row and so covered the time too, contrary to
what the comment above it claimed. It moves to the footer slot, so the
time stays put. Astryx's `·` separator between the slots is dropped
outright: with the time always present and the actions fading in beside
it, the two already read as separate and the bullet only adds a mark.
- The row's top margin goes 4px → 6px. Its box is the 28px height of the
buttons, not of the 12px time, so it sat tighter against the bubble
than the margin suggested.
Verified in Storybook against computed styles: `<time>` with a datetime,
no `aria-hidden`, opacity 1 at rest; separator `display: none`; icons
14×14 in a 16px slot at the globally governed 1.75 stroke; buttons direct
children of the meta row with `aria-describedby` set.
A transparent action is still clickable. `pointer-events: none` was tried
and reverted: it depends on the browser redoing hit-testing after the
ancestor `:hover` restores the button, and the jump-and-stop case could
not be reproduced on this surface to prove it safe.
@Astro-Han
Astro-Han marked this pull request as ready for review August 7, 2026 03:35
@Astro-Han
Astro-Han merged commit e4168f2 into mainAug 7, 2026
19 of 23 checks passed
@Astro-Han
Astro-Han deleted the refactor/chat-meta-astryx-primitives branch August 7, 2026 03:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

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

refactor(ui): hand the chat meta row back to Astryx primitives - #2358

Merged
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives
Aug 7, 2026
Merged

refactor(ui): hand the chat meta row back to Astryx primitives#2358
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The row under a chat message reimplemented parts Astryx already ships, and every visible defect in it traced back to one of those reimplementations.

  • Time. A hand-rolled <small> carried a native title and aria-hidden, so the absolute reading was mouse-only and the visible one was hidden from assistive tech. It is now Timestamp — a semantic <time datetime> with a copyable hover card, and exactly what ChatMessageMetadata's own docs put in that slot. formatClockTime had no other caller and is deleted.
  • Icons. Four separate 12 literals became one named constant at 14. --icon-size reserves 12–14 for "dense meta" and this row is dense meta; 14 is also where the same position already sits most often across the app. A size must be passed at all because Astryx's sm button bounds the icon slot at 16px without resizing the glyph — measured, an unsized lucide icon renders 16×24, squashed.
  • Tooltips. Each button was wrapped in a <Tooltip> element. Button takes a tooltip prop and attaches the same behaviour through a hook, which is what its source recommends. The display: contents wrapper is gone and aria-describedby now resolves — previously it computed to nothing, which is why call sites carried hand-written aria-label fallbacks.
  • Hover gate. It sat on the whole row and so covered the time too, contrary to what the comment directly above it claimed. It moves to the footer slot, so the time stays visible. Astryx's · separator is dropped rather than gated: with the time always present and the actions fading in beside it, the bullet added a mark without adding a distinction.
  • Spacing. The row's top margin goes 4px → 6px. Its box is the 28px height of the buttons, not the 12px of the time, so it sat tighter against the bubble than its margin suggested.

Behaviour change worth knowing: the hour cycle now follows the host locale instead of a hardcoded hour12: false. Most locales are unaffected (14:32 in zh / en-GB / ja / de / fr); en-US renders 2:32 PM. The hour also loses its leading zero (9:05, not 09:05), which is why tabular-nums stays on that element.

Refs #2359 — the icon sizing policy this lands against is unenforced repo-wide (145 hardcoded sizes across 11 values). This PR deliberately does not attempt that sweep.

Verification

  • npm run -w @maka/ui test — 442 passing
  • npm run -w @maka/ui typecheck, npm --workspace @maka/desktop run typecheck — clean
  • npm run lint, npm run format:check — clean
  • node scripts/check-dead-css.mjs --check, node scripts/check-a11y.mjs — clean
  • Not run: Playwright E2E and the desktop main-process suite — this change is renderer-only presentation with no journey or IPC surface.

Checked against computed styles in Storybook (Product/Shell Official AppShell → Native Conversation) rather than by eye:

result
time element<time datetime="…">, no aria-hidden, opacity 1 at rest
separatordisplay: none
icon14×14 in a 16px slot, 28px button, stroke 1.75 (the globally governed value)
tooltip wiringbutton is a direct child of the meta row, aria-describedby resolves
spacingrow margin-top: 6px

Review focus

pointer-events: none was tried on the transparent actions — a transparent button still takes clicks — and reverted. It makes the pointer fall through to the ancestor whose :hover is what restores the button, so being clickable again depends on the browser redoing hit-testing on a later mouse event. Continuous movement gets there; a pointer that jumps onto the button and stops could not be reproduced on this surface, so the fix could not be demonstrated safe. The pre-existing behaviour is left untouched and the reasoning is recorded in the CSS.

The row below a message reimplemented parts Astryx already ships, and
each visible defect traced to one of those reimplementations.
- The time was a hand-rolled `<small>` carrying a native `title` and
`aria-hidden`, so the absolute reading was mouse-only and the visible
one was hidden from assistive tech. It is now `Timestamp`, which is
what `ChatMessageMetadata`'s own docs put in that slot: a semantic
`<time datetime>` with a copyable hover card. `formatClockTime` had no
other caller and is gone; its `hour12: false` was the app overriding a
preference that belongs to the reader's system, so the hour cycle now
follows the host locale (`14:32` in zh/en-GB/ja/de/fr, `2:32 PM` in
en-US) and the hour loses its leading zero.
- Footer icons were four separate `12` literals. They are now one named
constant at 14 — the top of the range `--icon-size` reserves for "dense
meta", and where the same position already sits most often across the
app. A size has to be passed at all because Astryx's `sm` button bounds
the icon slot at 16px without resizing the glyph: measured, an unsized
lucide icon renders 16×24, squashed.
- Tooltips wrapped each button in a `<Tooltip>` element. `Button` takes a
`tooltip` prop and attaches the same behaviour through a hook, which is
what its source recommends — the `display: contents` wrapper is gone
and `aria-describedby` now resolves.
- The hover gate sat on the row and so covered the time too, contrary to
what the comment above it claimed. It moves to the footer slot, so the
time stays put. Astryx's `·` separator between the slots is dropped
outright: with the time always present and the actions fading in beside
it, the two already read as separate and the bullet only adds a mark.
- The row's top margin goes 4px → 6px. Its box is the 28px height of the
buttons, not of the 12px time, so it sat tighter against the bubble
than the margin suggested.
Verified in Storybook against computed styles: `<time>` with a datetime,
no `aria-hidden`, opacity 1 at rest; separator `display: none`; icons
14×14 in a 16px slot at the globally governed 1.75 stroke; buttons direct
children of the meta row with `aria-describedby` set.
A transparent action is still clickable. `pointer-events: none` was tried
and reverted: it depends on the browser redoing hit-testing after the
ancestor `:hover` restores the button, and the jump-and-stop case could
not be reproduced on this surface to prove it safe.
@Astro-Han
Astro-Han marked this pull request as ready for review August 7, 2026 03:35
@Astro-Han
Astro-Han merged commit e4168f2 into mainAug 7, 2026
19 of 23 checks passed
@Astro-Han
Astro-Han deleted the refactor/chat-meta-astryx-primitives branch August 7, 2026 03:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

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

refactor(ui): hand the chat meta row back to Astryx primitives - #2358

Merged
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives
Aug 7, 2026
Merged

refactor(ui): hand the chat meta row back to Astryx primitives#2358
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The row under a chat message reimplemented parts Astryx already ships, and every visible defect in it traced back to one of those reimplementations.

  • Time. A hand-rolled <small> carried a native title and aria-hidden, so the absolute reading was mouse-only and the visible one was hidden from assistive tech. It is now Timestamp — a semantic <time datetime> with a copyable hover card, and exactly what ChatMessageMetadata's own docs put in that slot. formatClockTime had no other caller and is deleted.
  • Icons. Four separate 12 literals became one named constant at 14. --icon-size reserves 12–14 for "dense meta" and this row is dense meta; 14 is also where the same position already sits most often across the app. A size must be passed at all because Astryx's sm button bounds the icon slot at 16px without resizing the glyph — measured, an unsized lucide icon renders 16×24, squashed.
  • Tooltips. Each button was wrapped in a <Tooltip> element. Button takes a tooltip prop and attaches the same behaviour through a hook, which is what its source recommends. The display: contents wrapper is gone and aria-describedby now resolves — previously it computed to nothing, which is why call sites carried hand-written aria-label fallbacks.
  • Hover gate. It sat on the whole row and so covered the time too, contrary to what the comment directly above it claimed. It moves to the footer slot, so the time stays visible. Astryx's · separator is dropped rather than gated: with the time always present and the actions fading in beside it, the bullet added a mark without adding a distinction.
  • Spacing. The row's top margin goes 4px → 6px. Its box is the 28px height of the buttons, not the 12px of the time, so it sat tighter against the bubble than its margin suggested.

Behaviour change worth knowing: the hour cycle now follows the host locale instead of a hardcoded hour12: false. Most locales are unaffected (14:32 in zh / en-GB / ja / de / fr); en-US renders 2:32 PM. The hour also loses its leading zero (9:05, not 09:05), which is why tabular-nums stays on that element.

Refs #2359 — the icon sizing policy this lands against is unenforced repo-wide (145 hardcoded sizes across 11 values). This PR deliberately does not attempt that sweep.

Verification

  • npm run -w @maka/ui test — 442 passing
  • npm run -w @maka/ui typecheck, npm --workspace @maka/desktop run typecheck — clean
  • npm run lint, npm run format:check — clean
  • node scripts/check-dead-css.mjs --check, node scripts/check-a11y.mjs — clean
  • Not run: Playwright E2E and the desktop main-process suite — this change is renderer-only presentation with no journey or IPC surface.

Checked against computed styles in Storybook (Product/Shell Official AppShell → Native Conversation) rather than by eye:

result
time element<time datetime="…">, no aria-hidden, opacity 1 at rest
separatordisplay: none
icon14×14 in a 16px slot, 28px button, stroke 1.75 (the globally governed value)
tooltip wiringbutton is a direct child of the meta row, aria-describedby resolves
spacingrow margin-top: 6px

Review focus

pointer-events: none was tried on the transparent actions — a transparent button still takes clicks — and reverted. It makes the pointer fall through to the ancestor whose :hover is what restores the button, so being clickable again depends on the browser redoing hit-testing on a later mouse event. Continuous movement gets there; a pointer that jumps onto the button and stops could not be reproduced on this surface, so the fix could not be demonstrated safe. The pre-existing behaviour is left untouched and the reasoning is recorded in the CSS.

The row below a message reimplemented parts Astryx already ships, and
each visible defect traced to one of those reimplementations.
- The time was a hand-rolled `<small>` carrying a native `title` and
`aria-hidden`, so the absolute reading was mouse-only and the visible
one was hidden from assistive tech. It is now `Timestamp`, which is
what `ChatMessageMetadata`'s own docs put in that slot: a semantic
`<time datetime>` with a copyable hover card. `formatClockTime` had no
other caller and is gone; its `hour12: false` was the app overriding a
preference that belongs to the reader's system, so the hour cycle now
follows the host locale (`14:32` in zh/en-GB/ja/de/fr, `2:32 PM` in
en-US) and the hour loses its leading zero.
- Footer icons were four separate `12` literals. They are now one named
constant at 14 — the top of the range `--icon-size` reserves for "dense
meta", and where the same position already sits most often across the
app. A size has to be passed at all because Astryx's `sm` button bounds
the icon slot at 16px without resizing the glyph: measured, an unsized
lucide icon renders 16×24, squashed.
- Tooltips wrapped each button in a `<Tooltip>` element. `Button` takes a
`tooltip` prop and attaches the same behaviour through a hook, which is
what its source recommends — the `display: contents` wrapper is gone
and `aria-describedby` now resolves.
- The hover gate sat on the row and so covered the time too, contrary to
what the comment above it claimed. It moves to the footer slot, so the
time stays put. Astryx's `·` separator between the slots is dropped
outright: with the time always present and the actions fading in beside
it, the two already read as separate and the bullet only adds a mark.
- The row's top margin goes 4px → 6px. Its box is the 28px height of the
buttons, not of the 12px time, so it sat tighter against the bubble
than the margin suggested.
Verified in Storybook against computed styles: `<time>` with a datetime,
no `aria-hidden`, opacity 1 at rest; separator `display: none`; icons
14×14 in a 16px slot at the globally governed 1.75 stroke; buttons direct
children of the meta row with `aria-describedby` set.
A transparent action is still clickable. `pointer-events: none` was tried
and reverted: it depends on the browser redoing hit-testing after the
ancestor `:hover` restores the button, and the jump-and-stop case could
not be reproduced on this surface to prove it safe.
@Astro-Han
Astro-Han marked this pull request as ready for review August 7, 2026 03:35
@Astro-Han
Astro-Han merged commit e4168f2 into mainAug 7, 2026
19 of 23 checks passed
@Astro-Han
Astro-Han deleted the refactor/chat-meta-astryx-primitives branch August 7, 2026 03:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

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

refactor(ui): hand the chat meta row back to Astryx primitives - #2358

Merged
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives
Aug 7, 2026
Merged

refactor(ui): hand the chat meta row back to Astryx primitives#2358
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The row under a chat message reimplemented parts Astryx already ships, and every visible defect in it traced back to one of those reimplementations.

  • Time. A hand-rolled <small> carried a native title and aria-hidden, so the absolute reading was mouse-only and the visible one was hidden from assistive tech. It is now Timestamp — a semantic <time datetime> with a copyable hover card, and exactly what ChatMessageMetadata's own docs put in that slot. formatClockTime had no other caller and is deleted.
  • Icons. Four separate 12 literals became one named constant at 14. --icon-size reserves 12–14 for "dense meta" and this row is dense meta; 14 is also where the same position already sits most often across the app. A size must be passed at all because Astryx's sm button bounds the icon slot at 16px without resizing the glyph — measured, an unsized lucide icon renders 16×24, squashed.
  • Tooltips. Each button was wrapped in a <Tooltip> element. Button takes a tooltip prop and attaches the same behaviour through a hook, which is what its source recommends. The display: contents wrapper is gone and aria-describedby now resolves — previously it computed to nothing, which is why call sites carried hand-written aria-label fallbacks.
  • Hover gate. It sat on the whole row and so covered the time too, contrary to what the comment directly above it claimed. It moves to the footer slot, so the time stays visible. Astryx's · separator is dropped rather than gated: with the time always present and the actions fading in beside it, the bullet added a mark without adding a distinction.
  • Spacing. The row's top margin goes 4px → 6px. Its box is the 28px height of the buttons, not the 12px of the time, so it sat tighter against the bubble than its margin suggested.

Behaviour change worth knowing: the hour cycle now follows the host locale instead of a hardcoded hour12: false. Most locales are unaffected (14:32 in zh / en-GB / ja / de / fr); en-US renders 2:32 PM. The hour also loses its leading zero (9:05, not 09:05), which is why tabular-nums stays on that element.

Refs #2359 — the icon sizing policy this lands against is unenforced repo-wide (145 hardcoded sizes across 11 values). This PR deliberately does not attempt that sweep.

Verification

  • npm run -w @maka/ui test — 442 passing
  • npm run -w @maka/ui typecheck, npm --workspace @maka/desktop run typecheck — clean
  • npm run lint, npm run format:check — clean
  • node scripts/check-dead-css.mjs --check, node scripts/check-a11y.mjs — clean
  • Not run: Playwright E2E and the desktop main-process suite — this change is renderer-only presentation with no journey or IPC surface.

Checked against computed styles in Storybook (Product/Shell Official AppShell → Native Conversation) rather than by eye:

result
time element<time datetime="…">, no aria-hidden, opacity 1 at rest
separatordisplay: none
icon14×14 in a 16px slot, 28px button, stroke 1.75 (the globally governed value)
tooltip wiringbutton is a direct child of the meta row, aria-describedby resolves
spacingrow margin-top: 6px

Review focus

pointer-events: none was tried on the transparent actions — a transparent button still takes clicks — and reverted. It makes the pointer fall through to the ancestor whose :hover is what restores the button, so being clickable again depends on the browser redoing hit-testing on a later mouse event. Continuous movement gets there; a pointer that jumps onto the button and stops could not be reproduced on this surface, so the fix could not be demonstrated safe. The pre-existing behaviour is left untouched and the reasoning is recorded in the CSS.

The row below a message reimplemented parts Astryx already ships, and
each visible defect traced to one of those reimplementations.
- The time was a hand-rolled `<small>` carrying a native `title` and
`aria-hidden`, so the absolute reading was mouse-only and the visible
one was hidden from assistive tech. It is now `Timestamp`, which is
what `ChatMessageMetadata`'s own docs put in that slot: a semantic
`<time datetime>` with a copyable hover card. `formatClockTime` had no
other caller and is gone; its `hour12: false` was the app overriding a
preference that belongs to the reader's system, so the hour cycle now
follows the host locale (`14:32` in zh/en-GB/ja/de/fr, `2:32 PM` in
en-US) and the hour loses its leading zero.
- Footer icons were four separate `12` literals. They are now one named
constant at 14 — the top of the range `--icon-size` reserves for "dense
meta", and where the same position already sits most often across the
app. A size has to be passed at all because Astryx's `sm` button bounds
the icon slot at 16px without resizing the glyph: measured, an unsized
lucide icon renders 16×24, squashed.
- Tooltips wrapped each button in a `<Tooltip>` element. `Button` takes a
`tooltip` prop and attaches the same behaviour through a hook, which is
what its source recommends — the `display: contents` wrapper is gone
and `aria-describedby` now resolves.
- The hover gate sat on the row and so covered the time too, contrary to
what the comment above it claimed. It moves to the footer slot, so the
time stays put. Astryx's `·` separator between the slots is dropped
outright: with the time always present and the actions fading in beside
it, the two already read as separate and the bullet only adds a mark.
- The row's top margin goes 4px → 6px. Its box is the 28px height of the
buttons, not of the 12px time, so it sat tighter against the bubble
than the margin suggested.
Verified in Storybook against computed styles: `<time>` with a datetime,
no `aria-hidden`, opacity 1 at rest; separator `display: none`; icons
14×14 in a 16px slot at the globally governed 1.75 stroke; buttons direct
children of the meta row with `aria-describedby` set.
A transparent action is still clickable. `pointer-events: none` was tried
and reverted: it depends on the browser redoing hit-testing after the
ancestor `:hover` restores the button, and the jump-and-stop case could
not be reproduced on this surface to prove it safe.
@Astro-Han
Astro-Han marked this pull request as ready for review August 7, 2026 03:35
@Astro-Han
Astro-Han merged commit e4168f2 into mainAug 7, 2026
19 of 23 checks passed
@Astro-Han
Astro-Han deleted the refactor/chat-meta-astryx-primitives branch August 7, 2026 03:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

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

refactor(ui): hand the chat meta row back to Astryx primitives - #2358

Merged
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives
Aug 7, 2026
Merged

refactor(ui): hand the chat meta row back to Astryx primitives#2358
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The row under a chat message reimplemented parts Astryx already ships, and every visible defect in it traced back to one of those reimplementations.

  • Time. A hand-rolled <small> carried a native title and aria-hidden, so the absolute reading was mouse-only and the visible one was hidden from assistive tech. It is now Timestamp — a semantic <time datetime> with a copyable hover card, and exactly what ChatMessageMetadata's own docs put in that slot. formatClockTime had no other caller and is deleted.
  • Icons. Four separate 12 literals became one named constant at 14. --icon-size reserves 12–14 for "dense meta" and this row is dense meta; 14 is also where the same position already sits most often across the app. A size must be passed at all because Astryx's sm button bounds the icon slot at 16px without resizing the glyph — measured, an unsized lucide icon renders 16×24, squashed.
  • Tooltips. Each button was wrapped in a <Tooltip> element. Button takes a tooltip prop and attaches the same behaviour through a hook, which is what its source recommends. The display: contents wrapper is gone and aria-describedby now resolves — previously it computed to nothing, which is why call sites carried hand-written aria-label fallbacks.
  • Hover gate. It sat on the whole row and so covered the time too, contrary to what the comment directly above it claimed. It moves to the footer slot, so the time stays visible. Astryx's · separator is dropped rather than gated: with the time always present and the actions fading in beside it, the bullet added a mark without adding a distinction.
  • Spacing. The row's top margin goes 4px → 6px. Its box is the 28px height of the buttons, not the 12px of the time, so it sat tighter against the bubble than its margin suggested.

Behaviour change worth knowing: the hour cycle now follows the host locale instead of a hardcoded hour12: false. Most locales are unaffected (14:32 in zh / en-GB / ja / de / fr); en-US renders 2:32 PM. The hour also loses its leading zero (9:05, not 09:05), which is why tabular-nums stays on that element.

Refs #2359 — the icon sizing policy this lands against is unenforced repo-wide (145 hardcoded sizes across 11 values). This PR deliberately does not attempt that sweep.

Verification

  • npm run -w @maka/ui test — 442 passing
  • npm run -w @maka/ui typecheck, npm --workspace @maka/desktop run typecheck — clean
  • npm run lint, npm run format:check — clean
  • node scripts/check-dead-css.mjs --check, node scripts/check-a11y.mjs — clean
  • Not run: Playwright E2E and the desktop main-process suite — this change is renderer-only presentation with no journey or IPC surface.

Checked against computed styles in Storybook (Product/Shell Official AppShell → Native Conversation) rather than by eye:

result
time element<time datetime="…">, no aria-hidden, opacity 1 at rest
separatordisplay: none
icon14×14 in a 16px slot, 28px button, stroke 1.75 (the globally governed value)
tooltip wiringbutton is a direct child of the meta row, aria-describedby resolves
spacingrow margin-top: 6px

Review focus

pointer-events: none was tried on the transparent actions — a transparent button still takes clicks — and reverted. It makes the pointer fall through to the ancestor whose :hover is what restores the button, so being clickable again depends on the browser redoing hit-testing on a later mouse event. Continuous movement gets there; a pointer that jumps onto the button and stops could not be reproduced on this surface, so the fix could not be demonstrated safe. The pre-existing behaviour is left untouched and the reasoning is recorded in the CSS.

The row below a message reimplemented parts Astryx already ships, and
each visible defect traced to one of those reimplementations.
- The time was a hand-rolled `<small>` carrying a native `title` and
`aria-hidden`, so the absolute reading was mouse-only and the visible
one was hidden from assistive tech. It is now `Timestamp`, which is
what `ChatMessageMetadata`'s own docs put in that slot: a semantic
`<time datetime>` with a copyable hover card. `formatClockTime` had no
other caller and is gone; its `hour12: false` was the app overriding a
preference that belongs to the reader's system, so the hour cycle now
follows the host locale (`14:32` in zh/en-GB/ja/de/fr, `2:32 PM` in
en-US) and the hour loses its leading zero.
- Footer icons were four separate `12` literals. They are now one named
constant at 14 — the top of the range `--icon-size` reserves for "dense
meta", and where the same position already sits most often across the
app. A size has to be passed at all because Astryx's `sm` button bounds
the icon slot at 16px without resizing the glyph: measured, an unsized
lucide icon renders 16×24, squashed.
- Tooltips wrapped each button in a `<Tooltip>` element. `Button` takes a
`tooltip` prop and attaches the same behaviour through a hook, which is
what its source recommends — the `display: contents` wrapper is gone
and `aria-describedby` now resolves.
- The hover gate sat on the row and so covered the time too, contrary to
what the comment above it claimed. It moves to the footer slot, so the
time stays put. Astryx's `·` separator between the slots is dropped
outright: with the time always present and the actions fading in beside
it, the two already read as separate and the bullet only adds a mark.
- The row's top margin goes 4px → 6px. Its box is the 28px height of the
buttons, not of the 12px time, so it sat tighter against the bubble
than the margin suggested.
Verified in Storybook against computed styles: `<time>` with a datetime,
no `aria-hidden`, opacity 1 at rest; separator `display: none`; icons
14×14 in a 16px slot at the globally governed 1.75 stroke; buttons direct
children of the meta row with `aria-describedby` set.
A transparent action is still clickable. `pointer-events: none` was tried
and reverted: it depends on the browser redoing hit-testing after the
ancestor `:hover` restores the button, and the jump-and-stop case could
not be reproduced on this surface to prove it safe.
@Astro-Han
Astro-Han marked this pull request as ready for review August 7, 2026 03:35
@Astro-Han
Astro-Han merged commit e4168f2 into mainAug 7, 2026
19 of 23 checks passed
@Astro-Han
Astro-Han deleted the refactor/chat-meta-astryx-primitives branch August 7, 2026 03:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

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

refactor(ui): hand the chat meta row back to Astryx primitives - #2358

Merged
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives
Aug 7, 2026
Merged

refactor(ui): hand the chat meta row back to Astryx primitives#2358
Astro-Han merged 1 commit into
mainfrom
refactor/chat-meta-astryx-primitives

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The row under a chat message reimplemented parts Astryx already ships, and every visible defect in it traced back to one of those reimplementations.

  • Time. A hand-rolled <small> carried a native title and aria-hidden, so the absolute reading was mouse-only and the visible one was hidden from assistive tech. It is now Timestamp — a semantic <time datetime> with a copyable hover card, and exactly what ChatMessageMetadata's own docs put in that slot. formatClockTime had no other caller and is deleted.
  • Icons. Four separate 12 literals became one named constant at 14. --icon-size reserves 12–14 for "dense meta" and this row is dense meta; 14 is also where the same position already sits most often across the app. A size must be passed at all because Astryx's sm button bounds the icon slot at 16px without resizing the glyph — measured, an unsized lucide icon renders 16×24, squashed.
  • Tooltips. Each button was wrapped in a <Tooltip> element. Button takes a tooltip prop and attaches the same behaviour through a hook, which is what its source recommends. The display: contents wrapper is gone and aria-describedby now resolves — previously it computed to nothing, which is why call sites carried hand-written aria-label fallbacks.
  • Hover gate. It sat on the whole row and so covered the time too, contrary to what the comment directly above it claimed. It moves to the footer slot, so the time stays visible. Astryx's · separator is dropped rather than gated: with the time always present and the actions fading in beside it, the bullet added a mark without adding a distinction.
  • Spacing. The row's top margin goes 4px → 6px. Its box is the 28px height of the buttons, not the 12px of the time, so it sat tighter against the bubble than its margin suggested.

Behaviour change worth knowing: the hour cycle now follows the host locale instead of a hardcoded hour12: false. Most locales are unaffected (14:32 in zh / en-GB / ja / de / fr); en-US renders 2:32 PM. The hour also loses its leading zero (9:05, not 09:05), which is why tabular-nums stays on that element.

Refs #2359 — the icon sizing policy this lands against is unenforced repo-wide (145 hardcoded sizes across 11 values). This PR deliberately does not attempt that sweep.

Verification

  • npm run -w @maka/ui test — 442 passing
  • npm run -w @maka/ui typecheck, npm --workspace @maka/desktop run typecheck — clean
  • npm run lint, npm run format:check — clean
  • node scripts/check-dead-css.mjs --check, node scripts/check-a11y.mjs — clean
  • Not run: Playwright E2E and the desktop main-process suite — this change is renderer-only presentation with no journey or IPC surface.

Checked against computed styles in Storybook (Product/Shell Official AppShell → Native Conversation) rather than by eye:

result
time element<time datetime="…">, no aria-hidden, opacity 1 at rest
separatordisplay: none
icon14×14 in a 16px slot, 28px button, stroke 1.75 (the globally governed value)
tooltip wiringbutton is a direct child of the meta row, aria-describedby resolves
spacingrow margin-top: 6px

Review focus

pointer-events: none was tried on the transparent actions — a transparent button still takes clicks — and reverted. It makes the pointer fall through to the ancestor whose :hover is what restores the button, so being clickable again depends on the browser redoing hit-testing on a later mouse event. Continuous movement gets there; a pointer that jumps onto the button and stops could not be reproduced on this surface, so the fix could not be demonstrated safe. The pre-existing behaviour is left untouched and the reasoning is recorded in the CSS.

The row below a message reimplemented parts Astryx already ships, and
each visible defect traced to one of those reimplementations.
- The time was a hand-rolled `<small>` carrying a native `title` and
`aria-hidden`, so the absolute reading was mouse-only and the visible
one was hidden from assistive tech. It is now `Timestamp`, which is
what `ChatMessageMetadata`'s own docs put in that slot: a semantic
`<time datetime>` with a copyable hover card. `formatClockTime` had no
other caller and is gone; its `hour12: false` was the app overriding a
preference that belongs to the reader's system, so the hour cycle now
follows the host locale (`14:32` in zh/en-GB/ja/de/fr, `2:32 PM` in
en-US) and the hour loses its leading zero.
- Footer icons were four separate `12` literals. They are now one named
constant at 14 — the top of the range `--icon-size` reserves for "dense
meta", and where the same position already sits most often across the
app. A size has to be passed at all because Astryx's `sm` button bounds
the icon slot at 16px without resizing the glyph: measured, an unsized
lucide icon renders 16×24, squashed.
- Tooltips wrapped each button in a `<Tooltip>` element. `Button` takes a
`tooltip` prop and attaches the same behaviour through a hook, which is
what its source recommends — the `display: contents` wrapper is gone
and `aria-describedby` now resolves.
- The hover gate sat on the row and so covered the time too, contrary to
what the comment above it claimed. It moves to the footer slot, so the
time stays put. Astryx's `·` separator between the slots is dropped
outright: with the time always present and the actions fading in beside
it, the two already read as separate and the bullet only adds a mark.
- The row's top margin goes 4px → 6px. Its box is the 28px height of the
buttons, not of the 12px time, so it sat tighter against the bubble
than the margin suggested.
Verified in Storybook against computed styles: `<time>` with a datetime,
no `aria-hidden`, opacity 1 at rest; separator `display: none`; icons
14×14 in a 16px slot at the globally governed 1.75 stroke; buttons direct
children of the meta row with `aria-describedby` set.
A transparent action is still clickable. `pointer-events: none` was tried
and reverted: it depends on the browser redoing hit-testing after the
ancestor `:hover` restores the button, and the jump-and-stop case could
not be reproduced on this surface to prove it safe.
@Astro-Han
Astro-Han marked this pull request as ready for review August 7, 2026 03:35
@Astro-Han
Astro-Han merged commit e4168f2 into mainAug 7, 2026
19 of 23 checks passed
@Astro-Han
Astro-Han deleted the refactor/chat-meta-astryx-primitives branch August 7, 2026 03:36
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