diff --git a/apps/desktop/src/renderer/maka-tokens.css b/apps/desktop/src/renderer/maka-tokens.css index 03881e213d..3c24b9b58e 100644 --- a/apps/desktop/src/renderer/maka-tokens.css +++ b/apps/desktop/src/renderer/maka-tokens.css @@ -1410,8 +1410,11 @@ font: var(--maka-text-body); background: var(--background); color: var(--foreground); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + /* Astryx's reset puts `antialiased` on `:where(body)`, which strips the + stroke dilation macOS applies and thins 14px Han glyphs. Maka is + CJK-first, so the platform default wins here, for the whole document. */ + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; overflow: hidden; /* main shell handles its own scrolling */ } diff --git a/packages/ui/src/__tests__/markdown-han-script.test.tsx b/packages/ui/src/__tests__/markdown-han-script.test.tsx new file mode 100644 index 0000000000..1f76792d6e --- /dev/null +++ b/packages/ui/src/__tests__/markdown-han-script.test.tsx @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { hasHanProse, MarkdownBody } from '../markdown-body.js'; + +describe('markdown Han-script marker', () => { + it('reads the prose, not the code', () => { + assert.equal(hasHanProse('按 `lastActiveAt` 排序。'), true); + assert.equal(hasHanProse('Sort by `lastActiveAt`.'), false); + assert.equal(hasHanProse('Sort by `排序键`.'), false); + assert.equal(hasHanProse('Example:\n\n```ts\n// 返回值为空时抛错\nreturn null;\n```\n'), false); + assert.equal(hasHanProse('说明:\n\n```ts\nreturn null;\n```\n'), true); + }); + + it('marks the markdown root so the emphasis rule can key on the script of the text', () => { + const han = renderToStaticMarkup(); + assert.match(han, /]*data-maka-contract="markdown"[^>]*data-maka-script="han"/); + + const latin = renderToStaticMarkup(); + assert.doesNotMatch(latin, /data-maka-script/); + }); +}); diff --git a/packages/ui/src/markdown-body.tsx b/packages/ui/src/markdown-body.tsx index e293b4b59e..2f3afdda3e 100644 --- a/packages/ui/src/markdown-body.tsx +++ b/packages/ui/src/markdown-body.tsx @@ -111,6 +111,17 @@ export function applyMermaidRenderBudget(source: string): string { return lines.join('\n'); } +// Whether the prose (not code) of a markdown source is written in Han script. +// The document `lang` is the UI locale, which says nothing about what the +// model wrote, so the CSS that styles Han-specific runs (emphasis has no +// italic in Han faces) keys on this instead. +export function hasHanProse(source: string): boolean { + const prose = source + .replace(/^( {0,3})(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n {0,3}\2[ \t]*$/gm, '') + .replace(/`[^`\n]*`/g, ''); + return /\p{Script=Han}/u.test(prose); +} + const MARKDOWN_COMPONENTS = { default: { ...BASE_MARKDOWN_COMPONENTS, @@ -152,6 +163,7 @@ export function MarkdownBody(props: { return (
` sharing one. RISK, not just a choice: these are the only rules here that are typography rather than spacing, and Astryx's density RFC (facebook/astryx#839) draws @@ -451,14 +478,6 @@ } [data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] .astryx-markdown-heading:not([data-level="1"], [data-level="2"]) { font: var(--maka-text-heading-4); -} -/* Below h3 the size ladder is spent — h4-h6 already sit at body size, and - growing them back would re-create the slabs the flattening exists to avoid. - Colour is the one axis left that costs no vertical space, so the deepest - levels step down in ink instead. opencode makes the same call at the same - point (13px h4-h6 dropped to muted); it is why a turn can carry four levels - of structure inside two type sizes. */ -[data-maka-contract="markdown"] .astryx-markdown[data-density="compact"] .astryx-markdown-heading:not([data-level="1"], [data-level="2"], [data-level="3"]) { color: var(--muted-foreground); } diff --git a/packages/ui/stories/markdown.stories.tsx b/packages/ui/stories/markdown.stories.tsx index b29b096e51..0a4f3b0c69 100644 --- a/packages/ui/stories/markdown.stories.tsx +++ b/packages/ui/stories/markdown.stories.tsx @@ -88,7 +88,7 @@ export const TranscriptTurn: Story = { '', '#### 边界情况', '', - '按项目视图仍然保留项目折叠,扁平化只作用于按时间视图。', + '按项目视图仍然保留项目折叠,扁平化*只作用于按时间视图*,按`lastActiveAt`排序,PR #4580 的 3 个 executor 不受影响。', '', '> 注意:新增状态只需补一个行图标。', '',