diff --git a/client/electron/test/harmony-arkts.test.mjs b/client/electron/test/harmony-arkts.test.mjs
index 8fe0874..0654246 100644
--- a/client/electron/test/harmony-arkts.test.mjs
+++ b/client/electron/test/harmony-arkts.test.mjs
@@ -193,6 +193,28 @@ test('★ 图标不许用 Unicode 符号充当(`Text(\'✉\')` 会被系统渲
* `!EMOJI_ISH.test('→')` 不成立 —— 断言写错就是断言写错,不能靠放宽它来「修」。)
*/
const EMOJI_ISH = /^[\u2600-\u27BF\u2B00-\u2BFF\uFE0F]$/;
+ /*
+ * ★★ 2026-09-20 补第二类:**排版符号当图标**(上面那一段只覆盖 emoji 那一类)。
+ *
+ * 实测漏网 4 处:`Text('‹')`(两处返回键)、`Text('›')`(会话别名前的小箭头)、
+ * `Text(' ▾')`(账号下拉指示)—— 全都在上面那三个区间**之外**,所以一条都没抓到。
+ *
+ * 它们的问题与 emoji 不同、但同样是"看着像图标其实不是":
+ * · `‹`/`›` 的字形宽窄**由字体决定**,与旁边 20 的 `AmIcon` 对不齐;
+ * 而且它们**不是** WebUI 用的东西 —— WebUI 那里是
+ * `ChevronLeftIcon` / `ChevronRightIcon`(SVG,见 `icons.tsx:135`),
+ * 所以这属于"两端不一致",不只是"字形不好看"。
+ * · `▾`(U+25BE)同理,WebUI `AccountSwitcher.tsx:84` 用的是
+ * 同一个 `ChevronRightIcon` **转 90°**(不是一个专门的"下箭头")。
+ *
+ * 覆盖 U+2039/203A(single/double guillemets)、U+25B2–U+25CF(几何图形:
+ * ▴▾◂▪● 这一类常被拿来当箭头/圆点)、U+2190–U+21FF 里的**单字符箭头**
+ * 仍**不**扫(`→`/`←` 在正文里是标点,上面那段注释已经说明为什么不能扫)。
+ * ★ 注意 `→` 在 `Text('a → b')` 这种**多条字符**里本来就不匹配
+ * (正则要求 1–3 个字符**整体**是个符号),所以这里加 `←→` 也不会误伤;
+ * 但为了不与上面那段注释自相矛盾,这一段仍然只管"几何/引号类"。
+ */
+ const GLYPH_ISH = /^[\u2039\u203A\u00AB\u00BB\u25A0-\u25CF\u25B2\u25BC\u25C0\u25B6]$/;
const hits = [];
const walkDir = (dir) => {
for (const e of readdirSync(dir, { withFileTypes: true })) {
@@ -202,7 +224,8 @@ test('★ 图标不许用 Unicode 符号充当(`Text(\'✉\')` 会被系统渲
const src = code(full);
for (const m of src.matchAll(/Text\(\s*'([^']{1,3})'\s*\)/g)) {
const ch = m[1];
- if (EMOJI_ISH.test(ch)) {
+ const trimmed = ch.trim();
+ if (EMOJI_ISH.test(ch) || GLYPH_ISH.test(trimmed)) {
hits.push(`${e.name}: Text('${ch}')(U+${ch.codePointAt(0).toString(16).toUpperCase()})`);
}
}
diff --git a/client/harmony/entry/src/main/ets/pages/ComposePage.ets b/client/harmony/entry/src/main/ets/pages/ComposePage.ets
index ddb9c93..7d02f1f 100644
--- a/client/harmony/entry/src/main/ets/pages/ComposePage.ets
+++ b/client/harmony/entry/src/main/ets/pages/ComposePage.ets
@@ -309,7 +309,15 @@ export struct ComposeView {
.fontSize(13).fontColor(Theme.accentFor())
.layoutWeight(1)
.onClick(() => { this.showAccountPicker = !this.showAccountPicker; })
- Text(' ▾').fontSize(12).fontColor(Theme.textSubtleFor())
+ /*
+ * 下拉指示 —— 与 WebUI `AccountSwitcher.tsx:84` 逐项对齐:
+ *
+ * 即**同一个右尖括号图标,展开时转 90°**(不是一个专门的"下箭头")。
+ * 原来这里写的是 `Text(' ▾')` —— 一个 Unicode 字符(本仓禁用当图标)。
+ */
+ AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.textSubtleFor() })
+ .rotate({ angle: this.showAccountPicker ? 90 : 0 })
.onClick(() => { this.showAccountPicker = !this.showAccountPicker; })
}
.width('100%').height(40).padding({ left: 12, right: 12 })
diff --git a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets
index 4bffde5..2b74b7f 100644
--- a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets
+++ b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets
@@ -16,7 +16,7 @@ import { MailDetail, SendMailRequest, ForwardMailRequest, Address } from '../mod
import { MailDetailParams } from '../model/RouteParams';
import { AmIcon } from '../common/Icons';
import { permissionLabel } from '../model/MailGrouping';
-import { LIST_FADE_LENGTH } from '../model/NavItems';
+import { LIST_FADE_LENGTH, HEADER_BACK_HIT } from '../model/NavItems';
import { LengthMetrics } from '@kit.ArkUI';
import {
participantAddress,
@@ -651,11 +651,21 @@ export struct MailDetailView {
*/
Column() {
Row() {
- Text('‹')
- .fontSize(24).fontColor(Theme.accentFor())
- .width(36).height(36)
- .textAlign(TextAlign.Center)
- .onClick(() => { this.goBack(); })
+ /*
+ * 返回键 —— **用图标,不用 `‹` 这个 Unicode 字符**。
+ *
+ * ★ 本仓明令禁止把 Unicode 符号当图标(渲染依赖字体、各家字形宽窄不一,
+ * 还会被部分主题替换成 emoji 字形)。全仓已统一走 `AmIcon` + `ICON_PATHS`,
+ * 这里是**最后一处漏网**(其它页面 2026-09-19 那批已经换过)。
+ *
+ * 尺寸按 `NAV_ITEM_MIN_HIT` 的口径给足 44vp 可点区 ——
+ * `AppHeader` 的返回键就是这个尺寸,两处要对齐。
+ */
+ Stack({ alignContent: Alignment.Center }) {
+ AmIcon({ iconName: 'chevronLeft', iconSize: 20, iconColor: Theme.accentFor() })
+ }
+ .width(HEADER_BACK_HIT).height(HEADER_BACK_HIT)
+ .onClick(() => { this.goBack(); })
Row() {
Text(this.subject)
diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets
index 2d7cfb9..1d8c2fc 100644
--- a/client/harmony/entry/src/main/ets/pages/MainPage.ets
+++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets
@@ -88,6 +88,7 @@ import {
NAV_CONTENT_RESERVE,
NAV_ITEM_MIN_HIT,
NAV_ITEMS,
+ HEADER_BACK_HIT,
TAB_BAR_RADIUS,
TAB_BAR_SIDE,
TAB_BAR_TOP,
@@ -2198,9 +2199,12 @@ struct ContactsTab {
Column() {
/* 返回行:与 WebUI 的 `BackButton label="会话"` 同一语义 */
Row() {
- Text('‹').fontSize(24).fontColor(Theme.accentFor()).width(36).height(36)
- .textAlign(TextAlign.Center)
- .onClick(() => { this.closeSession(); })
+ /* 返回键用图标,不用 `‹`(本仓禁 Unicode 符号当图标 —— 见 Icons.ets) */
+ Stack({ alignContent: Alignment.Center }) {
+ AmIcon({ iconName: 'chevronLeft', iconSize: 20, iconColor: Theme.accentFor() })
+ }
+ .width(HEADER_BACK_HIT).height(HEADER_BACK_HIT)
+ .onClick(() => { this.closeSession(); })
Text(this.openSessionTitle)
.fontSize(14).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
@@ -2292,7 +2296,9 @@ struct ContactsTab {
.width('100%')
Row() {
- Text('›').fontSize(12).fontColor(Theme.accentFor())
+ /* WebUI `ContactPanel.tsx:247` 这里是 ``
+ —— 一个 SVG 图标,不是 `›` 这个字符。逐项对齐。 */
+ AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.accentFor() })
Text(c.session_alias.length > 0 ? c.session_alias : '(未命名会话)')
.fontSize(11).fontColor(Theme.accentFor())
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
diff --git a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets
index 5810634..2d7f140 100644
--- a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets
+++ b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets
@@ -1188,7 +1188,9 @@ export struct SettingsPane {
Row() {
Text('管理').fontSize(Theme.fontBody).fontColor(Theme.textPrimary)
.layoutWeight(1)
- Text('›').fontSize(Theme.fontBody).fontColor(Theme.textSubtleFor())
+ /* 「进入下一级」指示:用图标不用 `›`(本仓禁 Unicode 符号当图标;
+ 口径与 `MailList`/`ContactPanel` 的 chevron 一致) */
+ AmIcon({ iconName: 'chevronRight', iconSize: 14, iconColor: Theme.textSubtleFor() })
}
.width('100%')
Text('用户管理(新建 / 编辑 / 启停 / 重置密码)')