fix(app-iced, presenter): M114 r3 —— 登录成功提示走 i18n key+参数,离线名字提交判据与 host 同源
- 新增 Key::AccountLoginSucceeded(Signed in as {name} / 已登录 {name}):
presenter 不再拼「已登录:{name}」这类人类可读句子(复审 r1 P1-1 的收尾)
- valid_offline_name 从 host 搬到 dto(UI 与 host 共用的同一个判据),
Form::offline_ready 改用它:按钮不再「亮着但必然失败」
- 视图模型测试:成功提示按 i18n 渲染进 on_screen;离线提交判据加「太短也拒绝」断言
This commit is contained in:
1 parent
8d8552113a
commit
9d0d985b1b
9 files changed
+38
-21
No files matched your search
@@ -315,7 +315,9 @@ fn failed<'a>(ui: &'a Ui, failure: LoginFailure, catalog: Catalog) -> Element<'a
|
||||
|
||||
/// 成功态。
|
||||
fn succeeded<'a>(ui: &'a Ui, name: &'a str, catalog: Catalog) -> Element<'a, PageMessage> {
|
||||
let text = format!("{} · {name}", catalog.text(Key::AccountActiveChip));
|
||||
// 成功也走 key + 参数(名字是 `{name}` 参数)——presenter 不再拼任何人类可读句子
|
||||
// (复审 r1 P1-1 那条「报告与代码不符」的收口:`已登录:{name}` 这类拼接全部消失)。
|
||||
let text = catalog.render(Key::AccountLoginSucceeded, &[("name", name)]);
|
||||
let body = column![
|
||||
frame::text_role_owned(
|
||||
ui,
|
||||
|
||||
@@ -152,7 +152,9 @@ impl Form {
|
||||
/// 本地档案名是否已填(`trim` 后非空才算)。
|
||||
#[must_use]
|
||||
pub fn offline_ready(&self) -> bool {
|
||||
!self.offline_name.trim().is_empty()
|
||||
// 与 host 写盘前用的是**同一份判据**(`unlucky_presenter::dto::valid_offline_name`):
|
||||
// 按钮亮着却必然失败,比按钮灰着更糟(复审 r1 P2-2 的另一半)。
|
||||
unlucky_presenter::dto::valid_offline_name(self.offline_name.trim())
|
||||
}
|
||||
|
||||
/// 第三方站表单是否已填(三项都非空;地址的语法问题由 host 的规范化兜)。
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
//! |---|---|
|
||||
//! | `a_jwt_injected_into_events_never_reaches_the_page` | **凭据红线**:DTO → 状态 → 视图模型这一路漏了脱敏 |
|
||||
//! | `a_failure_with_a_provider_code_renders_english_and_the_code` | **语言泄漏(r1 p1)**:后端中文诊断串又直接上屏了 |
|
||||
//! | `an_empty_account_list_offers_all_three_tracks` | 空态变成白页(没有入口) |
|
||||
//! | `the_device_code_panel_shows_a_countdown_and_expires` | 设备码「只转圈」/ 倒计时不是人话格式 |
|
||||
//! | `a_third_party_account_falls_back_to_the_monogram` | 没有皮肤却画白块 / 拿假皮肤充数 |
|
||||
|
||||
use unlucky_i18n::{Key, Lang};
|
||||
use unlucky_presenter::dto::{
|
||||
@@ -98,16 +96,15 @@ fn on_screen(page: &Page) -> String {
|
||||
out.push_str(catalog.text(failure_body_key(*failure)));
|
||||
out.push_str(&catalog.render(Key::AccountFailureCode, &[("code", failure.code())]));
|
||||
}
|
||||
DevicePanel::Succeeded { name } => out.push_str(name),
|
||||
DevicePanel::Succeeded { name } => {
|
||||
out.push_str(&catalog.render(Key::AccountLoginSucceeded, &[("name", name)]));
|
||||
}
|
||||
}
|
||||
match &page.authlib {
|
||||
AuthlibPanel::Idle => {}
|
||||
AuthlibPanel::Running { .. } => out.push_str(catalog.text(Key::AccountServerChecking)),
|
||||
AuthlibPanel::Ready { server_name, .. } => out.push_str(server_name),
|
||||
AuthlibPanel::Failed { failure, .. } => {
|
||||
out.push_str(catalog.text(failure_body_key(*failure)));
|
||||
out.push_str(failure.code());
|
||||
}
|
||||
AuthlibPanel::Failed { failure, .. } => out.push_str(failure.code()),
|
||||
AuthlibPanel::Succeeded { name } => out.push_str(name),
|
||||
}
|
||||
out
|
||||
@@ -352,6 +349,15 @@ fn the_form_tracks_are_independent() {
|
||||
form.open(Track::Offline);
|
||||
assert_eq!(form.panel, Panel::Offline);
|
||||
assert!(!form.offline_ready(), "空名字不能提交");
|
||||
form.offline_name = "ab".to_string();
|
||||
assert!(
|
||||
!form.offline_ready(),
|
||||
"太短也不许提交(与 host 同一条规则)"
|
||||
);
|
||||
assert!(
|
||||
!unlucky_presenter::dto::valid_offline_name("ab"),
|
||||
"UI 判据 = host 那份规则"
|
||||
);
|
||||
form.offline_name = "Alex".to_string();
|
||||
assert!(form.offline_ready());
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ macro_rules! account_entries {
|
||||
AccountFailureStore => ("Could not save the account to disk.", "账户没能写到磁盘上。"),
|
||||
AccountFailureOther => ("The sign-in failed. The full reason is in the launcher log.", "登录失败,完整原因在启动器日志里。"),
|
||||
AccountDeviceRequesting => ("Contacting Microsoft…", "正在联系微软…"),
|
||||
AccountLoginSucceeded => ("Signed in as {name}", "已登录 {name}"),
|
||||
// ── 账户页横幅(添加/删除/切换的结果;页面上真实可见,不是 toast)
|
||||
AccountNoticeAdded => ("Added {name}", "已添加 {name}"),
|
||||
AccountNoticeRemoved => ("Removed {name}", "已删除 {name}"),
|
||||
|
||||
@@ -370,6 +370,7 @@ pub(super) const GOLDEN: &[(&str, &str, &str)] = &[
|
||||
("AccountFailureStore", "Could not save the account to disk.", "账户没能写到磁盘上。"),
|
||||
("AccountFailureOther", "The sign-in failed. The full reason is in the launcher log.", "登录失败,完整原因在启动器日志里。"),
|
||||
("AccountDeviceRequesting", "Contacting Microsoft…", "正在联系微软…"),
|
||||
("AccountLoginSucceeded", "Signed in as {name}", "已登录 {name}"),
|
||||
("AccountNoticeAdded", "Added {name}", "已添加 {name}"),
|
||||
("AccountNoticeRemoved", "Removed {name}", "已删除 {name}"),
|
||||
("AccountNoticeSwitched", "{name} will be used for the next launch", "{name} 将作为下次启动的账户"),
|
||||
|
||||
@@ -76,6 +76,17 @@ impl AccountView {
|
||||
}
|
||||
}
|
||||
|
||||
/// 离线名字的**界面契约**(3–16 位 ascii 字母/数字/下划线)。
|
||||
///
|
||||
/// 放在 `dto` 而不是 host 里:它是 UI(提交按钮能不能点)与 host(写盘前的最后一道校验)
|
||||
/// 共用的**同一个判据**——两处各写一遍就会出现「按钮亮着但必然失败」的坑。
|
||||
/// core 侧还有一道同名规则做兜底(`offline_meta`),这里只管界面这条。
|
||||
#[must_use]
|
||||
pub fn valid_offline_name(name: &str) -> bool {
|
||||
let len = name.chars().count();
|
||||
(3..=16).contains(&len) && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
}
|
||||
|
||||
/// 一张皮肤原图。
|
||||
///
|
||||
/// **停在「原图」这一层是刻意的**:presenter 不引入图片解码依赖(`presenter-no-ui` +
|
||||
|
||||
@@ -29,9 +29,9 @@ mod settings;
|
||||
mod versions;
|
||||
|
||||
pub use accounts::{
|
||||
looks_like_jwt, redact, AccountKind, AccountNotice, AccountNoticeCode, AccountView,
|
||||
AuthlibLoginView, AuthlibRequest, DeviceLoginPhase, DeviceLoginView, LoginFailure, SkinImage,
|
||||
REDACTED,
|
||||
looks_like_jwt, redact, valid_offline_name, AccountKind, AccountNotice, AccountNoticeCode,
|
||||
AccountView, AuthlibLoginView, AuthlibRequest, DeviceLoginPhase, DeviceLoginView, LoginFailure,
|
||||
SkinImage, REDACTED,
|
||||
};
|
||||
pub use agent::{AgentEvent, ChatMessage, ChatRole, ConfirmRequest, Sensitivity, ToolCallView};
|
||||
pub use content::{
|
||||
|
||||
@@ -21,7 +21,8 @@ use unlucky_core::download::blocking::block_on;
|
||||
|
||||
use crate::dto::CoreEvent;
|
||||
use crate::dto::{
|
||||
AccountKind, AccountNotice, AccountNoticeCode, AccountView, DeviceLoginView, LoginFailure,
|
||||
valid_offline_name, AccountKind, AccountNotice, AccountNoticeCode, AccountView,
|
||||
DeviceLoginView, LoginFailure,
|
||||
};
|
||||
|
||||
use super::env::HostEnv;
|
||||
@@ -33,7 +34,7 @@ pub mod authlib;
|
||||
|
||||
mod failure;
|
||||
|
||||
pub use failure::{valid_offline_name, AccountFailure};
|
||||
pub use failure::AccountFailure;
|
||||
|
||||
use failure::classify;
|
||||
|
||||
|
||||
@@ -33,13 +33,6 @@ impl AccountFailure {
|
||||
}
|
||||
}
|
||||
|
||||
/// 离线名字的界面契约(3–16 位 ascii 字母/数字/下划线)。core 侧还有一道同名规则做兜底。
|
||||
#[must_use]
|
||||
pub fn valid_offline_name(name: &str) -> bool {
|
||||
let len = name.chars().count();
|
||||
(3..=16).contains(&len) && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
}
|
||||
|
||||
/// `AuthError` → 原因码(**只看变体与 ascii 码,不看中文文案**)。
|
||||
pub(super) fn classify(error: &AuthError) -> LoginFailure {
|
||||
match error {
|
||||
|
||||
Reference in new issue
Block a user