@starwish-ai/xingyuan-dsh 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +194 -0
- package/README.en-US.md +1 -1
- package/README.md +1 -1
- package/lib/client/api.js +9 -1
- package/lib/client/cards.js +4 -1
- package/lib/client/i18n.js +55 -42
- package/lib/client/pages/categories.js +30 -2
- package/lib/client/pages/detail.js +9 -3
- package/lib/client/pages/growth.js +7 -3
- package/lib/client/pages/memory.js +59 -16
- package/lib/client/pages/quick-create.js +19 -6
- package/lib/client/pages/task-line.js +35 -12
- package/lib/client/pages/tasks.js +7 -2
- package/lib/client/pages/today.js +6 -1
- package/lib/client/pages/wishes.js +6 -3
- package/lib/client/styles.js +53 -22
- package/lib/client/ui.js +5 -2
- package/lib/client.js +324 -104
- package/lib/domain.js +68 -1
- package/lib/growth.js +9 -9
- package/lib/opportunity.js +36 -17
- package/lib/preset/hitl.js +20 -5
- package/lib/preset/prompts.js +19 -11
- package/lib/preset/tools.js +17 -11
- package/lib/routes/api.js +6 -10
- package/lib/sqlite.js +10 -2
- package/lib/store.js +36 -15
- package/lib/types/client/api.d.ts +2 -2
- package/lib/types/client/i18n.d.ts +29 -20
- package/lib/types/client/pages/task-line.d.ts +4 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/client/ui.d.ts +3 -1
- package/lib/types/domain.d.ts +9 -0
- package/lib/types/opportunity.d.ts +8 -1
- package/lib/types/preset/hitl.d.ts +5 -3
- package/lib/types/routes/errors.d.ts +1 -1
- package/lib/types/store.d.ts +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,200 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.7] - 2026-08-29
|
|
9
|
+
|
|
10
|
+
> **Note:** this release is the third six-perspective quality pass (product,
|
|
11
|
+
> design, end-user, dsh-plugin engineering, architecture, UX). It closes the
|
|
12
|
+
> last honesty gap in the write paths, makes tab switching lossless for
|
|
13
|
+
> in-progress input, hardens the opportunity-date calculator against a
|
|
14
|
+
> process-hanging edge case, and lands the accessibility/contrast leftovers.
|
|
15
|
+
> No data format changes; existing databases and recorded sessions replay as before.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Draft snapshots across tab switches**: typing a memory (up to 1000 chars)
|
|
19
|
+
or filling the quick-create forms used to be silently lost when switching
|
|
20
|
+
conversation view tabs (pages unmount on switch). Form drafts now survive tab
|
|
21
|
+
switches and panel collapse; successful saves clear them.
|
|
22
|
+
- **Instant growth feedback on check-in**: the check-in tool reply appends the
|
|
23
|
+
current level and experience progress, closing the "check in → grow" loop at
|
|
24
|
+
the moment of the action instead of deferring it to the Growth page.
|
|
25
|
+
- **Non-preset session hint on the chat-directed pages**: in "always show" mode the six tabs
|
|
26
|
+
render in every session, but wishes/tasks/memory empty states directed users
|
|
27
|
+
to a chat that has no XingYuan tools there. The Today page's "preset not
|
|
28
|
+
enabled" hint row now also renders on the other three pages.
|
|
29
|
+
- **HTTP shell tests**: route dispatch (status-code mapping, the 64 KB limit,
|
|
30
|
+
JSON parsing, the `ToolError.code` passthrough that powers client-side error
|
|
31
|
+
localization) had zero coverage; locked with mocked `IncomingMessage`/
|
|
32
|
+
`ServerResponse` in `test/routes-shell.test.ts`.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **Claiming an overdue unclaimed task is rejected, not silently closed**
|
|
36
|
+
(`claim_expired`): the old flow claimed it and immediately expired-closed it
|
|
37
|
+
while the tool reply asserted "claimed, now in progress" and the page toast
|
|
38
|
+
said "go check in" — both false, and the task-line button kept offering an
|
|
39
|
+
action that always fails. The row shows a guidance hint instead, the chat
|
|
40
|
+
task card hides its claim button for the same state, and the detail panel's
|
|
41
|
+
revive row (extend due date) now also covers overdue-unclaimed tasks.
|
|
42
|
+
- **The early check-in confirms no longer assert a falsehood**: the task-row,
|
|
43
|
+
card and detail dialogs said "today is not a check-in day" — false when today
|
|
44
|
+
was already checked in (the natural path: check in via chat, then tap the
|
|
45
|
+
button on the updated card); the calendar dialog said "{date} is not a
|
|
46
|
+
check-in day" for a day the panel only lists because it IS one. All four now
|
|
47
|
+
state only what is true: the target day is after today, and an early check-in
|
|
48
|
+
commits to finishing that day.
|
|
49
|
+
- **Growth-ladder and completed rows dim by text color, not row opacity**:
|
|
50
|
+
whole-row `opacity` (0.65-0.78) blended 12 px text below the WCAG AA 4.5:1
|
|
51
|
+
line (≈2.6-3.1:1 measured in both themes); the hierarchy now comes from the
|
|
52
|
+
secondary text color (4.76/5.15:1) while transparency stays on decorative
|
|
53
|
+
elements only.
|
|
54
|
+
- **Danger icon buttons share one quiet tier**: the memory-row delete joined
|
|
55
|
+
the wish-card delete at 0.78 idle opacity with hover/focus restore (long
|
|
56
|
+
lists stop glowing red; non-text contrast ≥3:1, the stale "≥4.5:1" comment
|
|
57
|
+
corrected to the non-text standard).
|
|
58
|
+
- **Growth chart stops squeezing its columns on narrow panels**: below the
|
|
59
|
+
520 px breakpoint the 30 daily columns scroll horizontally at a minimum
|
|
60
|
+
width (~13 px per column, up from ~10 px) instead of being flex-crushed;
|
|
61
|
+
a deliberate trade-off against the 24 px touch-target ideal, whose full
|
|
62
|
+
width would make the chart scroll several screens.
|
|
63
|
+
- **HITL confirm sorts callers by whether any confirm UI exists**: callers
|
|
64
|
+
with no confirm UI at all (`NO_PROVIDER`, `CALLER_NOT_LIVE` — both raised
|
|
65
|
+
before `NO_PROVIDER` in rc.2) are allowed, as before for headless; a
|
|
66
|
+
delegated caller (`DELEGATED_CALLER`, e.g. a session-spawned subagent) is
|
|
67
|
+
now failed closed with actionable guidance ("confirm in the main
|
|
68
|
+
conversation, then run it there") instead of the raw platform error —
|
|
69
|
+
keeping the always-on delete confirmation intact on delegated paths.
|
|
70
|
+
Recorded in the engineering doc's HITL contract section.
|
|
71
|
+
- **weekly/monthly expectation management**: tool descriptions and the task
|
|
72
|
+
guide now state that opportunity days run from the claim date (every 7 days /
|
|
73
|
+
monthly by claim date) and never bind to a weekday — the model must say so
|
|
74
|
+
instead of silently accepting "every Wednesday" requests. Recorded in Known
|
|
75
|
+
Limitations.
|
|
76
|
+
- **The recommendation tool states its ordering**: `get_recommended_tasks` is
|
|
77
|
+
a fixed "top 5 by historical check-in count" ranking, not curation;
|
|
78
|
+
description and reply now say so.
|
|
79
|
+
- **Dedup guidance covers paraphrase**: the wish/task guides instruct the model
|
|
80
|
+
to sweep the existing list semantically when substring dedup finds no hit but
|
|
81
|
+
the request is loosely phrased.
|
|
82
|
+
- **Prompt/UI drift sync**: the Tasks page has four status groups (not three)
|
|
83
|
+
and two more settings entries (confirm-card language, tab visibility) — the
|
|
84
|
+
capabilities section now matches what users see.
|
|
85
|
+
- **Copy/wording**: user-facing strings unify "机会日" to "打卡日" (the check-in
|
|
86
|
+
tool guidance already used the plain term; leftover error strings swept
|
|
87
|
+
too); the bare-wish card points to the
|
|
88
|
+
chat path for task suggestions; the task-created toast explains "claim it to
|
|
89
|
+
start checking in"; five English strings polished ("To check in", "entries",
|
|
90
|
+
"Check-ins · last 30 days", "saved when you leave the field", the grid
|
|
91
|
+
summary).
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
- **Opportunity-date loops can no longer hang the process**: termination used
|
|
95
|
+
ISO string comparison, which silently assumes four-digit years — a sequence
|
|
96
|
+
crossing 9999-12-31 (only reachable via hand-edited sqlite) compared
|
|
97
|
+
`'10000-xx' <= '9999-xx'` as true forever, hanging daily/weekly/monthly in an
|
|
98
|
+
allocating loop. Termination is now numeric (UTC day numbers; the monthly
|
|
99
|
+
branch via `Date.UTC`, since `Date.parse('10000-…')` returns `NaN`). Locked
|
|
100
|
+
by termination tests including the year-boundary crossing.
|
|
101
|
+
- **Toast glyph semantics & screen readers**: the info glyph was "★" — the star
|
|
102
|
+
is the high-importance memory marker (`.xy-star-hi`); info is now "i", and
|
|
103
|
+
toast glyphs are `aria-hidden` so decorative symbols are not announced inside
|
|
104
|
+
the live region.
|
|
105
|
+
- **Centered header baselines**: the `.xy-meta` top margin leaked into centered
|
|
106
|
+
card/page heads, sinking secondary text by 2 px; the treatment the detail
|
|
107
|
+
area already had now covers the heads too.
|
|
108
|
+
- **Focus management**: closing the category rename editor returns focus (to
|
|
109
|
+
the row's rename button, or the page title when the row was rebuilt by a
|
|
110
|
+
rename); finishing the last "load more" page moves focus to the "all shown"
|
|
111
|
+
line and announces it (`role="status"`).
|
|
112
|
+
- **Docs**: reference links pointed at the nonexistent `main` branch of the
|
|
113
|
+
upstream repository (the default branch is `master`) and at a cookbook page
|
|
114
|
+
that does not exist (the conversation-node contract lives in
|
|
115
|
+
`subsystems/conversation`); a dsh upgrade checklist now records the two
|
|
116
|
+
patch-DSL hazards (schedule insert id collision, storage-domain whole-line
|
|
117
|
+
replacement).
|
|
118
|
+
|
|
119
|
+
## [0.5.6] - 2026-08-29
|
|
120
|
+
|
|
121
|
+
> **Note:** this release is the follow-up to the v0.5.5 six-perspective quality
|
|
122
|
+
> pass: the same six reviews re-ran against 0.5.5 and every accepted finding is
|
|
123
|
+
> fixed here — one data-safety gate, honesty fixes in the growth ladder, focus
|
|
124
|
+
> management on the highest-frequency action, and a batch of contrast, copy and
|
|
125
|
+
> localization closures. No data format changes; existing databases and recorded
|
|
126
|
+
> sessions replay as before.
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
- **Write-path schema gate** (`makeXingyuanStore`): the storage domain validates
|
|
130
|
+
records only when cold-opening the medium — a write path that skipped a field
|
|
131
|
+
check (e.g. an over-long task name from the model) used to persist silently and
|
|
132
|
+
then brick the next startup (`invalid-record` refuses to open, plugin dead until
|
|
133
|
+
manual surgery). Every `put`/`update`/`global.set` is now guarded by the declared
|
|
134
|
+
zod schema at the single store assembly point and rejected at write time with a
|
|
135
|
+
stable `invalid_record` code; valid records parse as identity, reads pay nothing.
|
|
136
|
+
Locked by `test/domain-guard.test.ts`.
|
|
137
|
+
- **Network-failure copy**: connection-level fetch failures (dsh restarting) no
|
|
138
|
+
longer surface the browser's raw English (`Failed to fetch` / `Load failed`) in
|
|
139
|
+
the Chinese UI; they map to a localized "network request failed, retry" message.
|
|
140
|
+
- **Task rows keep keyboard focus after group-migrating actions**: a successful
|
|
141
|
+
check-in moves the row between the Today page's open/done lists and destroyed the
|
|
142
|
+
pressed button, dropping focus to `<body>`; focus now falls back to the page
|
|
143
|
+
title (the same pattern as undo). The same treatment covers a check-in that
|
|
144
|
+
closes a task anywhere task rows render, and claiming a task on the Tasks page,
|
|
145
|
+
where the row moves between status groups.
|
|
146
|
+
- **Memory page**: the search empty-state no longer flashes between keystrokes
|
|
147
|
+
while the debounce is pending, a settled zero-hit search now shows the "no
|
|
148
|
+
matches" copy instead of the "no memories yet" empty-library one (the server's
|
|
149
|
+
`total` is query-filtered, so the two were indistinguishable before), and the
|
|
150
|
+
key field's server minimum (2 characters) is validated inline with its guidance
|
|
151
|
+
message instead of a generic error.
|
|
152
|
+
|
|
153
|
+
### Changed
|
|
154
|
+
- **Level rewards describe what exists**: the growth ladder no longer promises
|
|
155
|
+
avatar frames, task templates or style gating that the product does not have
|
|
156
|
+
(coach styles were always free in Settings); rewards are now the honorary title
|
|
157
|
+
itself, capped by the Lv.10 pinnacle. Server table and both dictionaries updated
|
|
158
|
+
together (the Chinese dictionary is locked to the server table by
|
|
159
|
+
`test/growth.test.ts`).
|
|
160
|
+
- **Levels read "荣誉" not "权益"** on the Growth page hero and in `get_growth_stats`
|
|
161
|
+
output, matching the honorary rewards.
|
|
162
|
+
- **Today plan keeps the achieved-closing row**: checking the last opportunity day
|
|
163
|
+
closes the task, which used to remove the row from the Today page entirely (the
|
|
164
|
+
counter shrank right after checking in). A closed-achieved task with a check-in
|
|
165
|
+
on that date stays in the done group with its undo entry; cancelling the check-in
|
|
166
|
+
revives the task through the existing freshness path.
|
|
167
|
+
- **Calendar hover preserves state colors**: hovering a partially/fully-completed
|
|
168
|
+
day no longer washes the semantic amber/green chip with the accent hover tint —
|
|
169
|
+
the state color is exactly what the hover was inspecting.
|
|
170
|
+
- **Contrast closures**: micro-action card secondary text and skipped steps use the
|
|
171
|
+
`label-on-2` pair and a higher muted opacity so both themes stay above the
|
|
172
|
+
4.5:1 line (the same lift applies to deleted wish cards in chat); calendar/
|
|
173
|
+
check-in-cell state foreground colors moved into the theme token region as
|
|
174
|
+
paired `--xyd-on-c2` / `--xyd-on-c3` / `--xyd-on-dcell` tokens.
|
|
175
|
+
- **Date validation accepts only real calendar dates**: `2026-02-30` used to pass
|
|
176
|
+
the format check (`Date.parse` rolls it over instead of returning NaN) and then
|
|
177
|
+
silently produce a task with an empty opportunity sequence — or land in the
|
|
178
|
+
check-in table as a fake day that skews the streak replay. Task due dates, wish
|
|
179
|
+
estimated dates and check-in target dates (including the any-date back-fill on
|
|
180
|
+
no-deadline tasks) now share the opportunity calculator's semantic `isIsoDate`
|
|
181
|
+
round-trip check, and the routes' duplicate implementation converges onto it.
|
|
182
|
+
- **Prompt coherence**: the check-in example moved out of the "no-confirm mode"
|
|
183
|
+
section into confirm mode (check-in is a confirm-gated write) and no longer
|
|
184
|
+
demonstrates a streak number the tool does not return; list-tool outputs tell the
|
|
185
|
+
model to use bracketed IDs only for follow-up calls, not to render them to the
|
|
186
|
+
user; `generate_chart`'s `wishId` parameter names the camelCase chart keys.
|
|
187
|
+
- **Memory search bar wraps** so the saved notice is not clipped off narrow panels.
|
|
188
|
+
- **Copy/wording**: the Settings preference-unavailable hint no longer hardcodes a
|
|
189
|
+
stale count; the English "clear all memories" dialog is grammatical for one item;
|
|
190
|
+
the Growth page EXP/experience units are consistent in Chinese; an unknown level
|
|
191
|
+
falls back to a localized name instead of an empty label; the 404 API error and
|
|
192
|
+
the wish-not-found tool/route error carry localizable codes; the English
|
|
193
|
+
check-in grid summary is grammatical for one cell; README wording aligned with
|
|
194
|
+
the UI ("连续加成", straight quotes in English).
|
|
195
|
+
|
|
196
|
+
### Fixed
|
|
197
|
+
- **sqlite backend**: a corrupted table row now reports `malformed-medium` like the
|
|
198
|
+
global slot does, instead of a raw `SyntaxError` with a split troubleshooting path.
|
|
199
|
+
- **Mock generator**: the detail-panel fixture in `debug/gen-mock.ts` takes an id
|
|
200
|
+
parameter so the wish/task scenarios no longer emit duplicate DOM ids.
|
|
201
|
+
|
|
8
202
|
## [0.5.5] - 2026-08-28
|
|
9
203
|
|
|
10
204
|
> **Note:** this release is a six-perspective quality pass (product, visual design,
|
package/README.en-US.md
CHANGED
|
@@ -28,7 +28,7 @@ Tools are only mounted on sessions using the XingYuan preset; other sessions are
|
|
|
28
28
|
dsh plugin --profile web add @starwish-ai/xingyuan-dsh
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
After starting the Web GUI, installation succeeded when
|
|
31
|
+
After starting the Web GUI, installation succeeded when "星愿" (XingYuan) appears in the agent picker.
|
|
32
32
|
|
|
33
33
|
## Data & Backup
|
|
34
34
|
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- **对话驱动**:自然语言创建愿望 / 任务,自动查重相似项
|
|
14
14
|
- **机会日打卡**:周期任务打卡、补卡、取消、未来预勾,进度跨天恒为最新
|
|
15
15
|
- **微行动拆解**:把无从下手的目标拆成 3–7 个小步,引导式逐个完成或跳过
|
|
16
|
-
- **成长体系**:等级 Lv.1–Lv.10
|
|
16
|
+
- **成长体系**:等级 Lv.1–Lv.10、连续加成、统计卡与近 30 天柱状图
|
|
17
17
|
- **会话视图页**:今日 / 愿望 / 任务 / 日历 / 成长 / 记忆 六个标签页,页面按钮直连动作接口;
|
|
18
18
|
默认仅星愿预设的会话显示,可在设置里切换「始终显示 / 始终隐藏」并按标签勾选
|
|
19
19
|
- **记忆**:重要记忆自动注入上下文(上限可配),支持增删改查
|
package/lib/client/api.js
CHANGED
|
@@ -22,6 +22,7 @@ const ERROR_KEY = {
|
|
|
22
22
|
already_checked: 'err.already_checked',
|
|
23
23
|
not_opportunity_day: 'err.not_opportunity_day',
|
|
24
24
|
already_claimed: 'err.already_claimed',
|
|
25
|
+
claim_expired: 'err.claim_expired',
|
|
25
26
|
task_closed: 'err.task_closed',
|
|
26
27
|
due_past: 'err.due_past',
|
|
27
28
|
due_too_far: 'err.due_too_far',
|
|
@@ -40,8 +41,15 @@ const ERROR_KEY = {
|
|
|
40
41
|
bad_coach_style: 'err.bad_coach_style',
|
|
41
42
|
bad_interests: 'err.bad_interests',
|
|
42
43
|
};
|
|
43
|
-
/**
|
|
44
|
+
/** 网络层失败的浏览器原文是英文技术串(Chrome "Failed to fetch"、Safari "Load failed"、
|
|
45
|
+
* Firefox "NetworkError…"、undici "fetch failed"),直出会打破本地化承诺——识别后统一
|
|
46
|
+
* 转本地化文案。仅匹配 TypeError(fetch 在请求发出前失败的规范类型)中的已知模式,
|
|
47
|
+
* 其余 TypeError(代码缺陷)仍原样抛出便于排障。 */
|
|
48
|
+
const NETWORK_ERROR_PATTERN = /failed to fetch|load failed|networkerror|fetch failed/i;
|
|
49
|
+
/** 把任意抛错转成用户可读文案:网络失败/ActionError 按键本地化;其余直出消息。 */
|
|
44
50
|
export function describeError(e) {
|
|
51
|
+
if (e instanceof TypeError && NETWORK_ERROR_PATTERN.test(e.message))
|
|
52
|
+
return t('common.networkError');
|
|
45
53
|
if (!(e instanceof ActionError))
|
|
46
54
|
return e instanceof Error ? e.message : String(e);
|
|
47
55
|
// 服务端新增未知 code 时回落原始消息,避免渲染 undefined 或插值崩溃
|
package/lib/client/cards.js
CHANGED
|
@@ -93,7 +93,10 @@ function TaskView(props) {
|
|
|
93
93
|
perform();
|
|
94
94
|
};
|
|
95
95
|
const deleted = event.op === 'deleted';
|
|
96
|
-
|
|
96
|
+
// 截止日已过的待领取任务写路径拒绝领取(store.claim_expired):卡片按钮同口径隐藏,
|
|
97
|
+
// 避免点按必失败(卡片是快照,today 取渲染当时——与写路径校验同一新鲜口径)
|
|
98
|
+
const claimExpired = task.status === 'pending' && task.dueDate !== undefined && task.dueDate < localYmd(new Date());
|
|
99
|
+
const showClaim = !deleted && phase === 'idle' && task.status === 'pending' && !claimExpired;
|
|
97
100
|
const showCheckIn = !deleted && phase !== 'done' && (task.status === 'in_progress' || phase === 'claimed');
|
|
98
101
|
const preview = event.opportunityPreview.length > 0
|
|
99
102
|
? t('task.upcoming', { dates: event.opportunityPreview.map((date) => formatShortDate(date)).join(activeLocale() === 'en' ? ', ' : '、') })
|
package/lib/client/i18n.js
CHANGED
|
@@ -42,6 +42,7 @@ export const ZH_DICT = {
|
|
|
42
42
|
'common.requestFailed': '请求失败',
|
|
43
43
|
'common.actionFailed': '操作失败',
|
|
44
44
|
'common.requestTimeout': '请求超时,请重试',
|
|
45
|
+
'common.networkError': '网络连接失败,请确认星愿服务已启动后重试',
|
|
45
46
|
'common.dayUnit': '{n} 天',
|
|
46
47
|
'common.countUnit': '{n} 次',
|
|
47
48
|
'common.countUnitOne': '{n} 次',
|
|
@@ -58,7 +59,7 @@ export const ZH_DICT = {
|
|
|
58
59
|
// --- 愿望卡 / 愿望页 ---
|
|
59
60
|
'wish.progress': '进度 {percent}%',
|
|
60
61
|
'wish.eta': '预计完成 {date}',
|
|
61
|
-
'wish.noTasks': '
|
|
62
|
+
'wish.noTasks': '暂无下属任务——在对话里告诉我,让我为它推荐打卡任务',
|
|
62
63
|
'wish.pageTitle': '我的愿望',
|
|
63
64
|
'wish.summary': '{active} 个进行中{achieved}', 'wish.achievedSuffix': ' · {n} 个已达成',
|
|
64
65
|
'wish.sectionAchieved': '已达成',
|
|
@@ -77,6 +78,7 @@ export const ZH_DICT = {
|
|
|
77
78
|
'task.group.pending': '待领取',
|
|
78
79
|
'task.group.achieved': '已达成',
|
|
79
80
|
'task.group.expired': '已过期',
|
|
81
|
+
'task.claimExpiredHint': '该任务截止日已过,无法领取——可展开详情延长截止日重新开始',
|
|
80
82
|
'task.expiredHint': '截止日已过——在下方延长截止日即可重新开始,也可以在对话里让我处理。',
|
|
81
83
|
'task.pageTitle': '全部任务',
|
|
82
84
|
'task.totalCount': '共 {n} 个',
|
|
@@ -106,17 +108,17 @@ export const ZH_DICT = {
|
|
|
106
108
|
'toast.undone': '打卡已撤销',
|
|
107
109
|
'toast.undoneAt': '已撤销{date}的打卡',
|
|
108
110
|
'toast.wishCreated': '已创建愿望「{title}」',
|
|
109
|
-
'toast.taskCreated': '已创建任务「{name}
|
|
111
|
+
'toast.taskCreated': '已创建任务「{name}」,领取后开始打卡',
|
|
110
112
|
'toast.deleted': '已删除「{name}」',
|
|
111
113
|
'toast.categoryRenamed': '分类已改名为「{name}」',
|
|
112
114
|
'toast.categoryColored': '「{name}」配色已更新',
|
|
113
115
|
'toast.categoryColorReset': '「{name}」配色已重置,跟随愿望默认色',
|
|
114
116
|
'toast.categoryDeleted': '已清除「{name}」的自定义颜色',
|
|
115
117
|
// --- 确认弹窗 ---
|
|
116
|
-
'confirm.futureCheckin': '「{name}
|
|
118
|
+
'confirm.futureCheckin': '「{name}」的打卡日 {date} 在今天之后,提前打卡表示承诺当天完成。确认?',
|
|
117
119
|
'confirm.undoToday': '确定撤销「{name}」今天的打卡吗?进度将回退。',
|
|
118
120
|
'confirm.undoAt': '确定撤销「{name}」在{date}的打卡吗?进度将回退。',
|
|
119
|
-
'confirm.futureAt': '「{name}
|
|
121
|
+
'confirm.futureAt': '「{name}」的打卡日 {date} 在今天之后,提前打卡表示承诺当天完成。确认?',
|
|
120
122
|
'confirm.deleteTask': '确定删除任务「{name}」吗?其打卡记录与微行动拆解将一并删除,不可恢复。',
|
|
121
123
|
'confirm.deleteWish': '确定删除愿望「{name}」吗?下属任务、打卡记录与微行动拆解将一并删除,不可恢复。',
|
|
122
124
|
'confirm.renameCategory': '把分类「{old}」下的全部愿望改名为「{new}」吗?(共 {count} 个)', 'confirm.deleteEmptyCategory': '删除空分类「{name}」的自定义颜色吗?',
|
|
@@ -158,9 +160,9 @@ export const ZH_DICT = {
|
|
|
158
160
|
'growth.levelLabel': '当前等级',
|
|
159
161
|
'growth.levelFallback': '初心者',
|
|
160
162
|
'growth.maxed': '已满级',
|
|
161
|
-
'growth.expFormat': '{cur} / {next}
|
|
163
|
+
'growth.expFormat': '{cur} / {next} 经验',
|
|
162
164
|
'growth.levelRequire': '需要 {exp} 经验',
|
|
163
|
-
'growth.rewardPrefix': '
|
|
165
|
+
'growth.rewardPrefix': '当前等级荣誉:{reward}',
|
|
164
166
|
'growth.stat.checkinDays': '累计打卡天数',
|
|
165
167
|
'growth.stat.streak': '连续坚持',
|
|
166
168
|
'growth.stat.maxStreak': '最长连续坚持',
|
|
@@ -181,23 +183,23 @@ export const ZH_DICT = {
|
|
|
181
183
|
'growth.lv.1.name': '初心者',
|
|
182
184
|
'growth.lv.1.reward': '开启星愿之旅',
|
|
183
185
|
'growth.lv.2.name': '探索者',
|
|
184
|
-
'growth.lv.2.reward': '
|
|
186
|
+
'growth.lv.2.reward': '晋升「探索者」称号',
|
|
185
187
|
'growth.lv.3.name': '实践者',
|
|
186
|
-
'growth.lv.3.reward': '
|
|
188
|
+
'growth.lv.3.reward': '晋升「实践者」称号',
|
|
187
189
|
'growth.lv.4.name': '坚持者',
|
|
188
|
-
'growth.lv.4.reward': '
|
|
190
|
+
'growth.lv.4.reward': '晋升「坚持者」称号',
|
|
189
191
|
'growth.lv.5.name': '奋斗者',
|
|
190
|
-
'growth.lv.5.reward': '
|
|
192
|
+
'growth.lv.5.reward': '晋升「奋斗者」称号',
|
|
191
193
|
'growth.lv.6.name': '进取者',
|
|
192
|
-
'growth.lv.6.reward': '
|
|
194
|
+
'growth.lv.6.reward': '晋升「进取者」称号',
|
|
193
195
|
'growth.lv.7.name': '成就者',
|
|
194
|
-
'growth.lv.7.reward': '
|
|
196
|
+
'growth.lv.7.reward': '晋升「成就者」称号',
|
|
195
197
|
'growth.lv.8.name': '卓越者',
|
|
196
|
-
'growth.lv.8.reward': '
|
|
198
|
+
'growth.lv.8.reward': '晋升「卓越者」称号',
|
|
197
199
|
'growth.lv.9.name': '领航者',
|
|
198
|
-
'growth.lv.9.reward': '
|
|
200
|
+
'growth.lv.9.reward': '晋升「领航者」称号',
|
|
199
201
|
'growth.lv.10.name': '星愿大师',
|
|
200
|
-
'growth.lv.10.reward': '
|
|
202
|
+
'growth.lv.10.reward': '晋升「星愿大师」——星愿之旅的最高荣誉',
|
|
201
203
|
// --- 记忆页 ---
|
|
202
204
|
'memory.pageTitle': '记忆',
|
|
203
205
|
'memory.summary': '共 {total} 条 · 对话时按上限自动注入',
|
|
@@ -218,6 +220,7 @@ export const ZH_DICT = {
|
|
|
218
220
|
'memory.footNote': '删除与清空不可恢复;注入条数上限在 设置 → 星愿 调整。',
|
|
219
221
|
'memory.clearAll': '清空全部记忆',
|
|
220
222
|
'memory.needKeyAndValue': '键名和内容都要填写。',
|
|
223
|
+
'memory.keyTooShort': '键名至少需要 2 个字符。',
|
|
221
224
|
'memory.overwriteAsk': '「{key}」已存在,覆盖原内容吗?',
|
|
222
225
|
'memory.confirmDelete': '删除记忆「{key}:{value}」?删除后不可恢复。',
|
|
223
226
|
'memory.confirmClear': '清空全部 {total} 条记忆?此操作不可恢复!',
|
|
@@ -259,7 +262,7 @@ export const ZH_DICT = {
|
|
|
259
262
|
'settings.profile.saved': '画像已保存',
|
|
260
263
|
'settings.profile.sharedHint': '与对话侧共享同一份档案;对话里说「叫我小星」也会更新。',
|
|
261
264
|
'settings.pref.title': '对话偏好',
|
|
262
|
-
'settings.pref.unavailable': '
|
|
265
|
+
'settings.pref.unavailable': '当前连接不支持持久化偏好(远程或临时模式),以下选项不可用。',
|
|
263
266
|
'settings.pref.loading': '偏好加载中…',
|
|
264
267
|
'settings.pref.notRegistered': '偏好暂不可用(组件未就绪),请稍后重试。',
|
|
265
268
|
'settings.pref.readOnly': '当前设置为只读,无法保存修改。',
|
|
@@ -354,7 +357,7 @@ export const ZH_DICT = {
|
|
|
354
357
|
'detail.grid.checked': '{date} 已打卡',
|
|
355
358
|
'detail.grid.missed': '{date} 未打卡',
|
|
356
359
|
'detail.grid.future': '{date} 待打卡',
|
|
357
|
-
'detail.next.title': '
|
|
360
|
+
'detail.next.title': '接下来的打卡日:{dates}',
|
|
358
361
|
'detail.revive.label': '延长截止日(重新开始)',
|
|
359
362
|
'detail.revive.action': '延长并重新开始',
|
|
360
363
|
'detail.revive.done': '截止日已延长,任务重新开始',
|
|
@@ -423,8 +426,9 @@ export const ZH_DICT = {
|
|
|
423
426
|
'err.missing_field': '缺少必填字段:{field}',
|
|
424
427
|
'err.not_found': '目标不存在或已被删除',
|
|
425
428
|
'err.already_checked': '该日期已打卡;如需修改,请先撤销当日打卡',
|
|
426
|
-
'err.not_opportunity_day': '
|
|
429
|
+
'err.not_opportunity_day': '该日期不是打卡日;可在日历页选择最近的打卡日',
|
|
427
430
|
'err.already_claimed': '该任务已领取过',
|
|
431
|
+
'err.claim_expired': '该任务截止日已过,无法领取——请先延长截止日使任务重新开始',
|
|
428
432
|
'err.task_closed': '任务已完结,无法再打卡',
|
|
429
433
|
'err.due_past': '截止日期不能早于今天',
|
|
430
434
|
'err.due_too_far': '截止日期不能超过今天起 10 年',
|
|
@@ -433,7 +437,7 @@ export const ZH_DICT = {
|
|
|
433
437
|
'err.bad_date': '日期格式或取值不合法(yyyy-MM-dd,且不得早于今天)',
|
|
434
438
|
'err.overwrite_required': '已存在同名记录,需确认覆盖',
|
|
435
439
|
'err.not_claimed': '任务尚未领取,请先领取',
|
|
436
|
-
'err.no_opportunity_left': '
|
|
440
|
+
'err.no_opportunity_left': '没有可勾选的打卡日:应打卡的日期已全部完成或截止',
|
|
437
441
|
'err.no_checkins': '该任务暂无打卡记录',
|
|
438
442
|
'err.title_too_long': '标题不能超过 50 字符',
|
|
439
443
|
'err.name_too_long': '任务名不能超过 100 字符',
|
|
@@ -461,6 +465,7 @@ const EN_DICT = {
|
|
|
461
465
|
'common.requestFailed': 'Request failed',
|
|
462
466
|
'common.actionFailed': 'Action failed',
|
|
463
467
|
'common.requestTimeout': 'Request timed out, please retry',
|
|
468
|
+
'common.networkError': 'Network request failed. Make sure the XingYuan service is running, then try again',
|
|
464
469
|
'common.dayUnit': '{n} days',
|
|
465
470
|
'common.countUnit': '{n} times',
|
|
466
471
|
'common.countUnitOne': '{n} time',
|
|
@@ -475,7 +480,7 @@ const EN_DICT = {
|
|
|
475
480
|
'state.done': 'Done',
|
|
476
481
|
'wish.progress': 'Progress {percent}%',
|
|
477
482
|
'wish.eta': 'Target date {date}',
|
|
478
|
-
'wish.noTasks': 'No tasks yet',
|
|
483
|
+
'wish.noTasks': 'No tasks yet — tell me in chat and I will suggest check-in tasks for it',
|
|
479
484
|
'wish.pageTitle': 'My Wishes',
|
|
480
485
|
'wish.summary': '{active} in progress{achieved}',
|
|
481
486
|
'wish.achievedSuffix': ' · {n} achieved',
|
|
@@ -494,6 +499,7 @@ const EN_DICT = {
|
|
|
494
499
|
'task.group.pending': 'Unclaimed',
|
|
495
500
|
'task.group.achieved': 'Achieved',
|
|
496
501
|
'task.group.expired': 'Expired',
|
|
502
|
+
'task.claimExpiredHint': 'Past its due date and unclaimed — open the task detail to extend the due date and restart it',
|
|
497
503
|
'task.expiredHint': 'The due date has passed — extend it below to restart the task, or just ask me in chat.',
|
|
498
504
|
'task.pageTitle': 'All Tasks',
|
|
499
505
|
'task.totalCount': '{n} in total',
|
|
@@ -521,16 +527,16 @@ const EN_DICT = {
|
|
|
521
527
|
'toast.undone': 'Check-in reverted',
|
|
522
528
|
'toast.undoneAt': 'Reverted the check-in on {date}',
|
|
523
529
|
'toast.wishCreated': 'Wish “{title}” created',
|
|
524
|
-
'toast.taskCreated': 'Task “{name}” created',
|
|
530
|
+
'toast.taskCreated': 'Task “{name}” created — claim it to start checking in',
|
|
525
531
|
'toast.deleted': '“{name}” deleted',
|
|
526
532
|
'toast.categoryRenamed': 'Category renamed to “{name}”',
|
|
527
533
|
'toast.categoryColored': 'Color updated for “{name}”',
|
|
528
534
|
'toast.categoryColorReset': 'Color of “{name}” reset to follow wish colors',
|
|
529
535
|
'toast.categoryDeleted': 'Custom color of “{name}” cleared',
|
|
530
|
-
'confirm.futureCheckin': '“{name}” is
|
|
536
|
+
'confirm.futureCheckin': 'The check-in day {date} for “{name}” is after today. Checking in early means committing to finish it that day. Continue?',
|
|
531
537
|
'confirm.undoToday': "Undo today’s check-in for “{name}”? Progress will roll back.",
|
|
532
538
|
'confirm.undoAt': 'Undo the check-in of “{name}” on {date}? Progress will roll back.',
|
|
533
|
-
'confirm.futureAt': '{date}
|
|
539
|
+
'confirm.futureAt': 'The check-in day {date} for “{name}” is after today. Checking in early means committing to finish it that day. Continue?',
|
|
534
540
|
'confirm.deleteTask': 'Delete task “{name}”? Its check-ins and micro-step plan will be removed too. This cannot be undone.',
|
|
535
541
|
'confirm.deleteWish': 'Delete wish “{name}”? Its tasks, check-ins and micro-step plans will be removed too. This cannot be undone.',
|
|
536
542
|
'confirm.renameCategory': 'Rename category “{old}” to “{new}” everywhere? ({count} affected)',
|
|
@@ -581,7 +587,7 @@ const EN_DICT = {
|
|
|
581
587
|
'growth.stat.taskTotal': 'Tasks created',
|
|
582
588
|
'growth.stat.taskDone': 'Tasks achieved',
|
|
583
589
|
'growth.stat.none': 'None',
|
|
584
|
-
'growth.chart.title': '
|
|
590
|
+
'growth.chart.title': 'Check-ins · last 30 days',
|
|
585
591
|
'growth.chart.empty': 'No data yet. Your trend chart grows after the first check-in.',
|
|
586
592
|
'growth.chart.hint': 'Blue = checked in, striped = missed gap, green = full day. Hover or focus a bar for daily details.',
|
|
587
593
|
'growth.chart.tooltip': '{date}: checked {checked}/{total}',
|
|
@@ -591,23 +597,23 @@ const EN_DICT = {
|
|
|
591
597
|
'growth.lv.1.name': 'Novice',
|
|
592
598
|
'growth.lv.1.reward': 'Begin your journey',
|
|
593
599
|
'growth.lv.2.name': 'Explorer',
|
|
594
|
-
'growth.lv.2.reward': '
|
|
600
|
+
'growth.lv.2.reward': 'Earn the “Explorer” title',
|
|
595
601
|
'growth.lv.3.name': 'Practitioner',
|
|
596
|
-
'growth.lv.3.reward': 'Earn
|
|
602
|
+
'growth.lv.3.reward': 'Earn the “Practitioner” title',
|
|
597
603
|
'growth.lv.4.name': 'Perseverer',
|
|
598
|
-
'growth.lv.4.reward': '
|
|
604
|
+
'growth.lv.4.reward': 'Earn the “Perseverer” title',
|
|
599
605
|
'growth.lv.5.name': 'Striver',
|
|
600
|
-
'growth.lv.5.reward': 'Earn
|
|
606
|
+
'growth.lv.5.reward': 'Earn the “Striver” title',
|
|
601
607
|
'growth.lv.6.name': 'Go-getter',
|
|
602
|
-
'growth.lv.6.reward': '
|
|
608
|
+
'growth.lv.6.reward': 'Earn the “Go-getter” title',
|
|
603
609
|
'growth.lv.7.name': 'Achiever',
|
|
604
|
-
'growth.lv.7.reward': 'Earn
|
|
610
|
+
'growth.lv.7.reward': 'Earn the “Achiever” title',
|
|
605
611
|
'growth.lv.8.name': 'Paragon',
|
|
606
|
-
'growth.lv.8.reward': '
|
|
612
|
+
'growth.lv.8.reward': 'Earn the “Paragon” title',
|
|
607
613
|
'growth.lv.9.name': 'Pathfinder',
|
|
608
|
-
'growth.lv.9.reward': 'Earn
|
|
614
|
+
'growth.lv.9.reward': 'Earn the “Pathfinder” title',
|
|
609
615
|
'growth.lv.10.name': 'XingYuan Master',
|
|
610
|
-
'growth.lv.10.reward': '
|
|
616
|
+
'growth.lv.10.reward': 'Earn the “XingYuan Master” title — the highest honor of the journey',
|
|
611
617
|
'memory.pageTitle': 'Memories',
|
|
612
618
|
'memory.summary': '{total} in total · auto-injected up to the limit during chats',
|
|
613
619
|
'memory.editing': 'Editing “{key}” (key is fixed)',
|
|
@@ -618,7 +624,7 @@ const EN_DICT = {
|
|
|
618
624
|
'memory.importanceLabel': 'Importance: {level}',
|
|
619
625
|
'memory.add': '+ Add',
|
|
620
626
|
'memory.saveEdit': 'Save changes',
|
|
621
|
-
'memory.capNote': '{total} in total, showing the latest {shown}; narrow down with search.',
|
|
627
|
+
'memory.capNote': '{total} in total, showing the latest {shown} entries; narrow down with search.',
|
|
622
628
|
'memory.more': 'Load more',
|
|
623
629
|
'memory.loadedAll': 'All {n} shown',
|
|
624
630
|
'memory.empty.title': 'No memories yet',
|
|
@@ -627,9 +633,10 @@ const EN_DICT = {
|
|
|
627
633
|
'memory.footNote': 'Deletions are permanent; adjust the injection limit under Settings → XingYuan.',
|
|
628
634
|
'memory.clearAll': 'Clear all memories',
|
|
629
635
|
'memory.needKeyAndValue': 'Both key and content are required.',
|
|
636
|
+
'memory.keyTooShort': 'Key must be at least 2 characters.',
|
|
630
637
|
'memory.overwriteAsk': '“{key}” already exists. Overwrite it?',
|
|
631
638
|
'memory.confirmDelete': 'Delete memory “{key}: {value}”? This cannot be undone.',
|
|
632
|
-
'memory.confirmClear': 'Clear all {total}
|
|
639
|
+
'memory.confirmClear': 'Clear all memories ({total})? This cannot be undone!',
|
|
633
640
|
'memory.savedNew': 'Added “{key}”',
|
|
634
641
|
'memory.savedOverwrite': 'Saved “{key}”',
|
|
635
642
|
'memory.deletedOne': '“{key}” deleted',
|
|
@@ -667,14 +674,14 @@ const EN_DICT = {
|
|
|
667
674
|
'settings.profile.saved': 'Profile saved',
|
|
668
675
|
'settings.profile.sharedHint': 'Shared with the chat side; saying “call me Star” in chat updates it too.',
|
|
669
676
|
'settings.pref.title': 'Chat preferences',
|
|
670
|
-
'settings.pref.unavailable': 'This connection cannot persist preferences (remote or temporary mode); the
|
|
677
|
+
'settings.pref.unavailable': 'This connection cannot persist preferences (remote or temporary mode); the options below are disabled.',
|
|
671
678
|
'settings.pref.loading': 'Loading preferences…',
|
|
672
679
|
'settings.pref.notRegistered': 'Preferences are unavailable right now (component not ready); please retry later.',
|
|
673
680
|
'settings.pref.readOnly': 'Settings are read-only; changes cannot be saved.',
|
|
674
681
|
'settings.pref.confirmWrites': 'Confirm write actions',
|
|
675
682
|
'settings.pref.confirmWritesHint': 'Show an in-app confirmation before creating wishes/tasks, checking in or canceling (deletes always confirm). Turn off to execute directly.',
|
|
676
683
|
'settings.pref.memoryLimit': 'Memory injection limit',
|
|
677
|
-
'settings.pref.memoryLimitHint': 'Max memories auto-injected per chat (5-200, default 40); saved
|
|
684
|
+
'settings.pref.memoryLimitHint': 'Max memories auto-injected per chat (5-200, default 40); saved when you leave the field.',
|
|
678
685
|
'settings.pref.limitInvalid': 'Enter an integer between 5 and 200',
|
|
679
686
|
'settings.pref.writeFailed': 'The change did not save; please try again.',
|
|
680
687
|
'settings.pref.confirmLang': 'Confirm card language',
|
|
@@ -749,10 +756,10 @@ const EN_DICT = {
|
|
|
749
756
|
'chart.subtitle.daysRatio': '{done}/{due} days',
|
|
750
757
|
'chart.subtitle.streak': 'Current {current}d / best {best}d',
|
|
751
758
|
'detail.grid.title': 'Check-in record',
|
|
752
|
-
'detail.grid.summary': 'Last {total}
|
|
759
|
+
'detail.grid.summary': 'Last {total} shown: {checked} checked, {missed} missed, {future} to check in',
|
|
753
760
|
'detail.legend.checked': 'Checked in',
|
|
754
761
|
'detail.legend.missed': 'Missed',
|
|
755
|
-
'detail.legend.future': '
|
|
762
|
+
'detail.legend.future': 'To check in',
|
|
756
763
|
'detail.grid.checked': '{date} checked in',
|
|
757
764
|
'detail.grid.missed': '{date} missed',
|
|
758
765
|
'detail.grid.future': '{date} pending check-in',
|
|
@@ -820,8 +827,9 @@ const EN_DICT = {
|
|
|
820
827
|
'err.missing_field': 'Missing required field: {field}',
|
|
821
828
|
'err.not_found': 'Not found, or already deleted',
|
|
822
829
|
'err.already_checked': 'Already checked in on this date — undo it first to change',
|
|
823
|
-
'err.not_opportunity_day': 'Not
|
|
830
|
+
'err.not_opportunity_day': 'Not a check-in day — pick the nearest one in the Calendar tab',
|
|
824
831
|
'err.already_claimed': 'Already claimed',
|
|
832
|
+
'err.claim_expired': 'This task is past its due date — extend the due date first to restart it',
|
|
825
833
|
'err.task_closed': 'Task is closed and cannot be checked in',
|
|
826
834
|
'err.due_past': 'Due date cannot be before today',
|
|
827
835
|
'err.due_too_far': 'Due date cannot be more than 10 years ahead',
|
|
@@ -830,7 +838,7 @@ const EN_DICT = {
|
|
|
830
838
|
'err.bad_date': 'Invalid date (yyyy-MM-dd, not before today)',
|
|
831
839
|
'err.overwrite_required': 'Already exists; confirmation required to overwrite',
|
|
832
840
|
'err.not_claimed': 'Task must be claimed first',
|
|
833
|
-
'err.no_opportunity_left': 'No check-in day left to tick — all
|
|
841
|
+
'err.no_opportunity_left': 'No check-in day left to tick — all scheduled days are done or past the deadline',
|
|
834
842
|
'err.no_checkins': 'No check-in records for this task',
|
|
835
843
|
'err.title_too_long': 'Title exceeds 50 characters',
|
|
836
844
|
'err.name_too_long': 'Task name exceeds 100 characters',
|
|
@@ -905,7 +913,12 @@ export function useXyT() {
|
|
|
905
913
|
return xyTranslateStable;
|
|
906
914
|
}
|
|
907
915
|
const xyTranslateStable = (key, params) => t(key, params);
|
|
908
|
-
/** 当前语言(供调试/埋点类场景;无 face 时返回 'zh' 兜底口径)。
|
|
916
|
+
/** 当前语言(供调试/埋点类场景;无 face 时返回 'zh' 兜底口径)。
|
|
917
|
+
* 平台事实(rc.2 实测):宿主发布的 LocaleFace 契约(dsh-client-ui-slots
|
|
918
|
+
* renderer.d.ts)只声明 getSnapshot(): { revision } 与 bind(ns),快照的 `active`
|
|
919
|
+
* 字段不在类型面内但运行时存在——本函数依赖它做语言判定,读取处已做 unknown
|
|
920
|
+
* 收窄,字段缺席时静默回落 zh(en 用户会看到英文正文配中文日期,而非崩溃)。
|
|
921
|
+
* 上游若把 active 纳入正式契约,此处无需改动。 */
|
|
909
922
|
export function activeLocale() {
|
|
910
923
|
if (localeFace === undefined)
|
|
911
924
|
return 'zh';
|
|
@@ -7,7 +7,7 @@ import { createElement, useEffect, useRef, useState } from 'react';
|
|
|
7
7
|
import { getJson, postAction, describeError } from '../api.js';
|
|
8
8
|
import { useXyT } from '../i18n.js';
|
|
9
9
|
import { softConfirm, softConfirmDanger, useActionGuard } from '../hooks.js';
|
|
10
|
-
import { toast } from '../ui.js';
|
|
10
|
+
import { toast, focusPageTitle } from '../ui.js';
|
|
11
11
|
import { categoryVars } from '../../category-color.js';
|
|
12
12
|
import { SwatchRow } from './color-swatch.js';
|
|
13
13
|
import { countKey } from './format.js';
|
|
@@ -82,6 +82,29 @@ export function CategoryManager(props) {
|
|
|
82
82
|
const [renaming, setRenaming] = useState(undefined);
|
|
83
83
|
const [coloring, setColoring] = useState(undefined);
|
|
84
84
|
const { busy, guard } = useActionGuard();
|
|
85
|
+
// 改名编辑器随关闭(提交/取消/Esc)整体卸载,焦点会落空:记下触发行内「改名」
|
|
86
|
+
// 按钮,关闭后焦点归还。仅在「打开→关闭」迁移时执行(面板挂载的那次 effect
|
|
87
|
+
// 不动焦点——否则每次打开面板焦点就被拽到页面标题);改名成功后行按新名
|
|
88
|
+
// re-key、旧触发按钮随之销毁,且 effect 执行时 reload 数据未回、旧按钮还在
|
|
89
|
+
// DOM(归还焦点会在 re-key 中落空到 body)——成功路径显式清空触发引用,令
|
|
90
|
+
// effect 直接走页面标题兜底
|
|
91
|
+
const renameTriggerRef = useRef(null);
|
|
92
|
+
const renameOpenRef = useRef(false);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (renaming !== undefined) {
|
|
95
|
+
renameOpenRef.current = true;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!renameOpenRef.current)
|
|
99
|
+
return;
|
|
100
|
+
renameOpenRef.current = false;
|
|
101
|
+
const trigger = renameTriggerRef.current;
|
|
102
|
+
renameTriggerRef.current = null;
|
|
103
|
+
if (trigger !== null && document.contains(trigger))
|
|
104
|
+
trigger.focus();
|
|
105
|
+
else
|
|
106
|
+
focusPageTitle();
|
|
107
|
+
}, [renaming]);
|
|
85
108
|
const load = () => {
|
|
86
109
|
setError(undefined);
|
|
87
110
|
getJson('/xingyuan/api/categories')
|
|
@@ -94,6 +117,7 @@ export function CategoryManager(props) {
|
|
|
94
117
|
const commitRename = (oldName, newName) => {
|
|
95
118
|
guard(() => postAction('category-rename', { oldName, newName }).then(() => {
|
|
96
119
|
toast(t('toast.categoryRenamed', { name: newName }), 'ok');
|
|
120
|
+
renameTriggerRef.current = null;
|
|
97
121
|
setRenaming(undefined);
|
|
98
122
|
reload();
|
|
99
123
|
}));
|
|
@@ -114,7 +138,11 @@ export function CategoryManager(props) {
|
|
|
114
138
|
}, row.name), createElement('span', { className: 'xy-meta xy-catcount' }, t(countKey(row.wishCount, 'catmgr.countOne', 'catmgr.count'), { n: row.wishCount })), createElement('span', { className: 'xy-catops' }, createElement('button', {
|
|
115
139
|
className: 'xy-btn',
|
|
116
140
|
disabled: busy,
|
|
117
|
-
onClick: () => {
|
|
141
|
+
onClick: (e) => {
|
|
142
|
+
renameTriggerRef.current = e.currentTarget;
|
|
143
|
+
setRenaming(row.name);
|
|
144
|
+
setColoring(undefined);
|
|
145
|
+
},
|
|
118
146
|
}, t('catmgr.rename')), createElement('button', {
|
|
119
147
|
className: 'xy-btn',
|
|
120
148
|
disabled: busy,
|