From e747542836160b21851af36ddd089a9fc75b7806 Mon Sep 17 00:00:00 2001 From: timeStarry Date: Fri, 11 Sep 2026 23:04:12 +0800 Subject: [PATCH 1/3] release: prepare DualLane 0.19.0 UI/UX rollout --- .env.example | 2 +- .github/workflows/ci.yml | 4 +- .../workspace/echo/releases/service_test.go | 8 +-- apps/web/package.json | 2 +- apps/web/shared/echo-release-guides.json | 35 ++++++++++ apps/web/src/releases.test.ts | 7 +- apps/web/src/releases.ts | 13 ++++ apps/web/src/workspace-config-docs.test.js | 2 +- .../work-items/2026-09-11-ui-0190-release.md | 66 +++++++++++++++++++ docs/design/ui-ux-rewrite/RELEASE_NOTES.md | 4 +- e2e/workspace-echo-release.spec.ts | 17 ++--- package.json | 2 +- 12 files changed, 139 insertions(+), 23 deletions(-) create mode 100644 docs/backend/work-items/2026-09-11-ui-0190-release.md diff --git a/.env.example b/.env.example index 0f1d1512..c94bc2f7 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ DUALLANE_WEB_BIND=127.0.0.1 DUALLANE_WEB_PORT=8787 # Local/disposable Compose build labels. The guarded production coordinator # overrides these from the verified release version and full Git commit. -DUALLANE_APP_VERSION=0.18.0 +DUALLANE_APP_VERSION=0.19.0 DUALLANE_GIT_COMMIT=development # Absolute production checkout accepted by deploy/production/deploy.sh. When # empty, the guarded local default is $HOME/duallane. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f97649a3..32d970e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: env: CI: "true" TEST_DATABASE_URL: "" - DUALLANE_APP_VERSION: "0.18.0" + DUALLANE_APP_VERSION: "0.19.0" DUALLANE_GIT_COMMIT: ${{ github.sha }} POSTGRES_PASSWORD: duallane-ci-password @@ -176,7 +176,7 @@ jobs: - name: Validate production deployment config env: - DUALLANE_APP_VERSION: "0.18.0" + DUALLANE_APP_VERSION: "0.19.0" DUALLANE_GIT_COMMIT: ${{ github.sha }} POSTGRES_VOLUME_NAME: duallane-ci-postgres POSTGRES_PASSWORD: duallane-ci-password diff --git a/apps/backend/internal/workspace/echo/releases/service_test.go b/apps/backend/internal/workspace/echo/releases/service_test.go index 9ac7bf0c..cc91623c 100644 --- a/apps/backend/internal/workspace/echo/releases/service_test.go +++ b/apps/backend/internal/workspace/echo/releases/service_test.go @@ -53,15 +53,15 @@ func TestLoadGuideCatalogFromCanonicalSharedAsset(t *testing.T) { if catalog.Empty() { t.Fatal("canonical release catalog is empty") } - guide, ok := catalog.Guide("v0.18.0") + guide, ok := catalog.Guide("v0.19.0") if !ok { - t.Fatal("canonical catalog does not contain current release 0.18.0") + t.Fatal("canonical catalog does not contain current release 0.19.0") } - if guide.Version != "0.18.0" || guide.ReleasedAt != "2026-09-10" || guide.Title != "日常功能保持兼容" || guide.Summary != "本次为维护更新,保留聊天、文件、头像和个人设置;沿用原有入口,无需重新设置。" || len(guide.Sections) != 2 || guide.Sections[0].Items[0].Location == "" { + if guide.Version != "0.19.0" || guide.ReleasedAt != "2026-09-11" || guide.Title != "清晰双轨,全新一致体验" || guide.Summary != "统一聊天、话题和个人设置,提供五套主题,让阅读、输入与常用操作更加连贯。" || len(guide.Sections) != 4 || guide.Sections[0].Items[0].Location == "" { t.Fatalf("incomplete canonical guide: %#v", guide) } guide.Sections[0].Items[0].Title = "mutated test copy" - fresh, ok := catalog.Guide("0.18.0") + fresh, ok := catalog.Guide("0.19.0") if !ok || fresh.Sections[0].Items[0].Title == "mutated test copy" { t.Fatal("catalog returned mutable internal state") } diff --git a/apps/web/package.json b/apps/web/package.json index d6e22bea..268bcbad 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@duallane/web", - "version": "0.18.0", + "version": "0.19.0", "private": true, "type": "module", "scripts": { diff --git a/apps/web/shared/echo-release-guides.json b/apps/web/shared/echo-release-guides.json index 172e22c9..6b50e637 100644 --- a/apps/web/shared/echo-release-guides.json +++ b/apps/web/shared/echo-release-guides.json @@ -1,4 +1,39 @@ [ + { + "version": "0.19.0", + "releasedAt": "2026-09-11", + "title": "清晰双轨,全新一致体验", + "summary": "统一聊天、话题和个人设置,提供五套主题,让阅读、输入与常用操作更加连贯。", + "sections": [ + { + "title": "选择你的外观", + "items": [ + { "title": "五套主题与独立明暗模式", "description": "横向浏览原色、苔原、暮色、米色和极简主题预览,再选择系统、浅色或深色。密度、动态效果和透明效果可以分别调整,外观选择保存在本机。", "location": "个人 -> 外观" } + ] + }, + { + "title": "连贯的聊天与话题", + "items": [ + { "title": "在话题中使用完整消息", "description": "发送图片、文件和表情,回复消息或使用适用的消息操作。附件后台上传时可以继续发送下一条,失败内容从原消息重试,不覆盖新草稿。", "location": "话题 -> 选择话题 -> 输入消息;消息 -> 更多" }, + { "title": "按需打开详情", "description": "话题的参与、提醒和管理操作收进详情。聊天与话题中栏可以折叠,连续短消息保留连贯表面,阅读历史时保持正在看的位置。", "location": "话题 -> 话题详情;聊天或话题 -> 折叠中栏" } + ] + }, + { + "title": "更清楚的设置与成员操作", + "items": [ + { "title": "按类别找到个人设置", "description": "资料、外观、聊天、通知、隐私、表情和 Bot 分层展示,手机逐层进入。进入表情分类后,点击管理我的表情再打开工具,关闭后留在原页面。", "location": "个人;个人 -> 表情 -> 管理我的表情" }, + { "title": "搜索并邀请成员", "description": "在邀请窗口搜索并选择多名成员。部分邀请失败时保留未成功项供重试,已经加入的成员不会重复邀请。", "location": "聊天 -> 群聊 -> 会话详情 -> 邀请成员" }, + { "title": "使用一致的对象操作", "description": "右键、长按、更多和键盘打开同一组适用操作,消息正文仍可选择复制。文件列表与卡片视图用紧凑图文选择切换。", "location": "消息或成员 -> 更多;文件 -> 列表或卡片" } + ] + }, + { + "title": "已有内容与隐私", + "items": [ + { "title": "继续使用原有内容", "description": "已有消息、文件、邀请链接和明暗偏好继续可用。私密直连仍不保存聊天和文件内容,共享空间继续按成员权限保存与访问内容。", "location": "首页 -> 私密直连;首页 -> 共享空间" } + ] + } + ] + }, { "version": "0.18.0", "releasedAt": "2026-09-10", diff --git a/apps/web/src/releases.test.ts b/apps/web/src/releases.test.ts index c7a99d44..a718a65a 100644 --- a/apps/web/src/releases.test.ts +++ b/apps/web/src/releases.test.ts @@ -31,7 +31,7 @@ const INTERNAL_CHANGELOG_TERMS = [ describe("release history", () => { it("is unique, complete, and sorted newest first", () => { expect(new Set(DUAL_LANE_RELEASES.map((release) => release.version)).size).toBe(DUAL_LANE_RELEASES.length); - expect(DUAL_LANE_RELEASES.map((release) => release.version)).toEqual(["0.18.0", "0.17.0", "0.16.2", "0.16.1", "0.16.0", "0.15.5", "0.15.4", "0.15.3", "0.15.2", "0.15.1", "0.15.0", "0.14.3", "0.14.2", "0.14.1", "0.14.0", "0.13.2", "0.13.1", "0.13.0", "0.12.0", "0.11.0", "0.10.0", "0.9.0", "0.8.0", "0.7.0", "0.6.0", "0.5.0", "0.4.0", "0.3.0", "0.2.0", "0.1.0"]); + expect(DUAL_LANE_RELEASES.map((release) => release.version)).toEqual(["0.19.0", "0.18.0", "0.17.0", "0.16.2", "0.16.1", "0.16.0", "0.15.5", "0.15.4", "0.15.3", "0.15.2", "0.15.1", "0.15.0", "0.14.3", "0.14.2", "0.14.1", "0.14.0", "0.13.2", "0.13.1", "0.13.0", "0.12.0", "0.11.0", "0.10.0", "0.9.0", "0.8.0", "0.7.0", "0.6.0", "0.5.0", "0.4.0", "0.3.0", "0.2.0", "0.1.0"]); for (const release of DUAL_LANE_RELEASES) { expect(release.releasedAt).toMatch(/^\d{4}-\d{2}-\d{2}$/); expect(release.title.trim()).not.toBe(""); @@ -42,8 +42,8 @@ describe("release history", () => { }); it("matches both published package versions", () => { - expect(rootPackage.version).toBe("0.18.0"); - expect(webPackage.version).toBe("0.18.0"); + expect(rootPackage.version).toBe("0.19.0"); + expect(webPackage.version).toBe("0.19.0"); expect(DUAL_LANE_RELEASES[0].version).toBe(rootPackage.version); expect(DUAL_LANE_RELEASES[0].version).toBe(webPackage.version); }); @@ -108,6 +108,7 @@ describe("release history", () => { it("keeps every Echo usage guide aligned with the public release facts", () => { const releases = new Map(DUAL_LANE_RELEASES.map((release) => [release.version, release])); + expect(echoReleaseGuides.some((guide) => guide.version === rootPackage.version)).toBe(true); for (const guide of echoReleaseGuides) { const release = releases.get(guide.version); expect(release).toMatchObject({ diff --git a/apps/web/src/releases.ts b/apps/web/src/releases.ts index 438f121a..8075af87 100644 --- a/apps/web/src/releases.ts +++ b/apps/web/src/releases.ts @@ -12,6 +12,19 @@ export type DualLaneRelease = { }; export const DUAL_LANE_RELEASES: DualLaneRelease[] = [ + { + version: "0.19.0", + releasedAt: "2026-09-11", + title: "清晰双轨,全新一致体验", + summary: "统一聊天、话题和个人设置,提供五套主题,让阅读、输入与常用操作更加连贯。", + categories: [ + { title: "外观与选择", items: ["在个人设置的外观页选择原色、苔原、暮色、米色或极简主题,横向浏览预览,并独立选择系统、浅色或深色模式", "短选项使用清楚的图文单选,较长选项使用下拉;开关、滑块、菜单和按钮保持一致"] }, + { title: "聊天与话题", items: ["话题与群聊共用完整消息体验,支持图片、文件、表情、回复和适用的消息操作;附件上传时可以继续发送下一条", "本人消息保留独立底色,连续短消息连贯呈现;修复阅读历史时的位置跳动", "话题的参与、提醒和管理操作收进话题详情,聊天区更清爽"] }, + { title: "导航与个人设置", items: ["聊天和话题中栏可以折叠,文件与成员使用单一主列表;普通成员的日常入口更简洁", "个人设置按资料、外观、聊天、通知、隐私、表情及 Bot 分层,手机逐层进入", "表情设置正常显示页面,点击管理我的表情后再打开工具,关闭后留在原页面"] }, + { title: "常用操作", items: ["右键、长按、更多和键盘提供一致的对象操作,正文仍可选择复制", "群聊邀请成员使用带搜索框的多选窗口,部分失败可以只重试未成功成员", "文件列表与卡片视图使用紧凑的图文切换,成员与设置页面的对齐和布局更清晰"] }, + { title: "已有内容与隐私", items: ["已有消息、文件、邀请链接和明暗偏好继续可用,外观偏好保存在本机", "私密直连仍不保存聊天和文件内容,共享空间继续按成员权限保存与访问内容"] } + ] + }, { version: "0.18.0", releasedAt: "2026-09-10", diff --git a/apps/web/src/workspace-config-docs.test.js b/apps/web/src/workspace-config-docs.test.js index d072913a..0eb4da72 100644 --- a/apps/web/src/workspace-config-docs.test.js +++ b/apps/web/src/workspace-config-docs.test.js @@ -178,7 +178,7 @@ describe("workspace configuration docs", () => { expect(goCompose).toContain("DUALLANE_APP_VERSION: ${DUALLANE_APP_VERSION:?release version is required}"); expect(goCompose).toContain("DUALLANE_GIT_COMMIT: ${DUALLANE_GIT_COMMIT:?full release commit is required}"); expect(goCompose).toContain("image: duallane-go-workspace:${DUALLANE_GIT_COMMIT:?full release commit is required}"); - expect(envExample).toContain("DUALLANE_APP_VERSION=0.18.0"); + expect(envExample).toContain("DUALLANE_APP_VERSION=0.19.0"); expect(envExample).toContain("DUALLANE_GIT_COMMIT=development"); expect(readme).toMatch(/verified\s+release metadata/); expect(readme).toContain("/home/timestarry/duallane"); diff --git a/docs/backend/work-items/2026-09-11-ui-0190-release.md b/docs/backend/work-items/2026-09-11-ui-0190-release.md new file mode 100644 index 00000000..10bc88d0 --- /dev/null +++ b/docs/backend/work-items/2026-09-11-ui-0190-release.md @@ -0,0 +1,66 @@ +# 0.19.0 清晰双轨发布记录 + +日期:2026-09-11。状态:发布准备与生产预检已开展,**尚未激活 0.19.0**。 + +## 范围与提交 + +- UI/UX 重构 PR #12 已由 `timeStarry` 合并,合并提交为 `b07d5bfe7f0bc623bc57934866e3175d9adea829`;四项 PR CI 通过。 +- 发布准备将根与 Web 版本升至 0.19.0,补齐应用内更新历史、Echo 功能位置引导、示例与 CI 版本标签,以及相应一致性验证。 +- 既有 0.18.0 发布历史、SDK 与离线工具独立版本保持不变;没有 schema 变更,仍为 34。新 Web 与同版 Go 镜像一起激活。 +- 维护者已明确授权本次上线及远程生产主机访问;按检出所有者更新 Git,以具备所需权限的会话运行协调器。本次访问指令不改变仓库默认部署规则。 + +最终 UI/UX 规则见 [规范入口](../../design/ui-ux-rewrite/README.md),用户可见变化见 +[版本说明](../../design/ui-ux-rewrite/RELEASE_NOTES.md)。本记录不重新定义组件或交互规则。 + +## 已观察的生产基线 + +只读检查生产检出 `/home/timestarry/duallane`:干净 `main`,提交 +`ef5187348f8b180fa572ae301039c1487496b8c5`,当前运行版本 0.18.0。 +P2P、Workspace、worker、Web 四个 owner 均健康,镜像与上一成功快照一致,重启策略为 `always`。 + +上一成功恢复快照为: + +```text +backups/production/duallane-20260910T102919Z-ef5187348f8b.recovery.go-compose.snapshot.json +``` + +该文件及 `.compose.json`、`.external.json`、`.volumes.json` 配套文件均为 root 持有的 +普通 0600 文件。已验证格式、全部回滚镜像仍存在、外部文件指纹和当前实际卷归属; +此处只证明当前基线,激活脚本仍须比较目标版本的实际 authority 并重新校验。 +未导出生产消息、令牌、配置正文或私有快照内容。 + +## 验证与发布边界 + +UI 重构的完整回归及其修复见 [PR 验证记录](../../design/ui-ux-rewrite/PR_VERIFICATION.md)。 +发布准备的检查包括根/Web/Echo 版本一致性、Go 发布目录、真实 Go Echo 引导与公开关于页, +以及类型、构建和既有发布约束;具体结果随发布 PR 记录,不把测试环境当作线上。 + +本地发布准备检查:Web 333、SDK 8、离线兼容 20 项通过(该离线包原 PostgreSQL 项 1 项按配置跳过); +lint/build 通过;Go 发布目录、配置与 Workspace 启动专项及 Echo PostgreSQL race 通过; +真实 Go 新版 Echo 引导与公开关于页 2/2 通过,桌面/390px 关于页人工查看无横向溢出。 +同一工作树的 Linux Node 22.23.2/Compose 2.40.3 发布契约 34/34 通过、无跳过; +Windows 首次运行缺少原生 Docker/Bash 的环境失败没有通过修改断言掩盖。 + +真机软键盘、安全区域、辅助技术和完整性能基准仍是已披露的验证边界,不能据合并或自动化 +结果宣称这些人工检查完成。原有主 JS 超过 500 kB 的构建提示保留。 + +## 激活与回退 + +1. 发布准备 PR 由 `timeStarry` 合并并确认最终 CI 后,以检出所有者执行 `git pull --ff-only`。 +2. 确认生产检出为干净 `main`,HEAD 与最新 `origin/main` 完全一致。使用**发布准备合并后的最终 40 位 SHA**,不能继续使用 UI PR 的合并 SHA。 +3. 使用 root 执行正式协调器,设置 `GIT_OPTIONAL_LOCKS=0`;必要的 Git trust 例外仅作用于该进程与准确生产目录。 + +```sh +deploy/production/deploy.sh --expected-commit \ + --release-profile go-full --go-upgrade \ + --previous-release-snapshot /home/timestarry/duallane/backups/production/duallane-20260910T102919Z-ef5187348f8b.recovery.go-compose.snapshot.json +``` + +协调器负责新逻辑备份、固定镜像与 SQL 兼容验证、只读候选健康、旧 owner 停写与 drain、 +新 owner 激活及最后替换网关。成功后核对公开 Web/静态资源/健康/鉴权边界、四个 owner、 +版本与提交号、重启策略、新私有恢复快照和备份校验。 + +失败只按已验证快照恢复整版 Go 镜像并保持同一数据归属;不自动恢复旧数据库、不反向迁移、 +不恢复退休 Node 服务。不得以裸 `docker compose up`、删除现有卷或修改健康条件替代正式流程。 + +最终激活结果、镜像身份、安全产物名称和开放项记录在发布 PR 的上线记录;未取得这些证据前保持“尚未激活”。 diff --git a/docs/design/ui-ux-rewrite/RELEASE_NOTES.md b/docs/design/ui-ux-rewrite/RELEASE_NOTES.md index c8d5e366..ceb74818 100644 --- a/docs/design/ui-ux-rewrite/RELEASE_NOTES.md +++ b/docs/design/ui-ux-rewrite/RELEASE_NOTES.md @@ -1,6 +1,6 @@ -# 待发布:清晰双轨 UI/UX 重构 +# 0.19.0 待上线:清晰双轨 UI/UX 重构 -这是当前开发分支的发布说明草稿,包含首轮重构后的真实页面结构精修,尚未分配正式版本或发布日期。具体修正和证据见 [页面精修](PAGE_REFINEMENT.md)。准备产品发布时,将审核后的内容纳入既有 release catalog,并同步 Web 与根版本;不把未发布功能写入当前 0.18.0 的历史记录。 +本次发布编号为 0.19.0,发布条目日期为 2026-09-11,包含首轮重构后的真实页面结构精修。UI/UX 实现已通过 PR #12 合并;发布准备同步根与 Web 版本、应用内版本历史和 Echo 使用引导,保留 0.18.0 历史条目。具体修正和证据见 [页面精修](PAGE_REFINEMENT.md);合并发布准备不等于已上线,实际激活与恢复证据以 [0.19.0 发布记录](../../backend/work-items/2026-09-11-ui-0190-release.md) 为准。 - 入口、聊天、话题、文件、成员、个人与空间设置采用统一的清晰双轨语言。细线选中标记与圆角边框形成同一套导航规则。 - 提供原色、苔原、暮色、米色、极简五组主题及其浅深模式。主题预览只保留标题,可横向滑动或拖动浏览,也支持左右按钮和键盘选择。密度、动态效果和透明材质可独立设置;此前明暗偏好自动迁移,本机偏好读取失败会安全回退。 diff --git a/e2e/workspace-echo-release.spec.ts b/e2e/workspace-echo-release.spec.ts index 98d040b5..1a0c48f5 100644 --- a/e2e/workspace-echo-release.spec.ts +++ b/e2e/workspace-echo-release.spec.ts @@ -25,23 +25,24 @@ test("space owner broadcasts one detailed mobile release guide through Echo", as const chat = page.getByRole("region", { name: "回声" }); const composer = chat.getByLabel("输入消息"); - await composer.fill("/release 0.15.1"); + await composer.fill("/release 0.19.0"); await chat.locator("form.workspace-composer button.workspace-send-button").click(); const interaction = chat.getByRole("region", { name: "回声交互" }); - await expect(interaction.getByText("v0.15.1 更新已发布", { exact: true })).toBeVisible(); - const card = chat.getByRole("group", { name: /DualLane v0\.15\.1 版本更新/ }); + await expect(interaction.getByText("v0.19.0 更新已发布", { exact: true })).toBeVisible(); + const card = chat.getByRole("group", { name: /DualLane v0\.19\.0 版本更新/ }); await expect(card).toBeVisible(); - await expect(card.getByText("表情合集订阅", { exact: true })).toBeVisible(); - await expect(card.getByText(/个人设置 -> 我的表情 -> 打开合集详情/)).toBeVisible(); + await expect(card.getByText("五套主题与独立明暗模式", { exact: true })).toBeVisible(); + await expect(card.getByText("在话题中使用完整消息", { exact: true })).toBeVisible(); + await expect(card.getByText("个人 -> 外观", { exact: true })).toBeVisible(); const bounds = await card.boundingBox(); expect(bounds).not.toBeNull(); expect(bounds!.x).toBeGreaterThanOrEqual(0); expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(390); await interaction.getByRole("button", { name: "完成" }).click(); - await composer.fill("/release v0.15.1"); + await composer.fill("/release v0.19.0"); await chat.locator("form.workspace-composer button.workspace-send-button").click(); - await expect(interaction.getByText("v0.15.1 更新已发布", { exact: true })).toBeVisible(); - await expect(chat.getByRole("group", { name: /DualLane v0\.15\.1 版本更新/ })).toHaveCount(1); + await expect(interaction.getByText("v0.19.0 更新已发布", { exact: true })).toBeVisible(); + await expect(chat.getByRole("group", { name: /DualLane v0\.19\.0 版本更新/ })).toHaveCount(1); }); diff --git a/package.json b/package.json index 7726dcb7..bd45ee6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "duallane", - "version": "0.18.0", + "version": "0.19.0", "private": true, "description": "DualLane self-hosted split-lane communication tool.", "type": "module", From 26372fcb775d6b4aff4728206d3560fe1a0d0257 Mon Sep 17 00:00:00 2001 From: timeStarry Date: Fri, 11 Sep 2026 23:12:13 +0800 Subject: [PATCH 2/3] test: validate CI labels against the current release version --- .github/tests/ci-go-p2p-privacy.test.mjs | 4 ++-- docs/backend/work-items/2026-09-11-ui-0190-release.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/tests/ci-go-p2p-privacy.test.mjs b/.github/tests/ci-go-p2p-privacy.test.mjs index 37cf1f06..9d9ceae8 100644 --- a/.github/tests/ci-go-p2p-privacy.test.mjs +++ b/.github/tests/ci-go-p2p-privacy.test.mjs @@ -4,7 +4,7 @@ import test from "node:test"; test("Go P2P browser gate owns its server and never uploads private failure output", async () => { const root = new URL("../../", import.meta.url); - const read = (name) => readFile(new URL(name, root), "utf8"); + const read = async (name) => (await readFile(new URL(name, root), "utf8")).replace(/\r\n?/g, "\n"); const [workflow, config, nodeConfig, server, manifest] = await Promise.all([ read(".github/workflows/ci.yml"), read("playwright.p2p-go.config.ts"), read("playwright.config.ts"), read("e2e/support/test-server.mjs"), read("package.json") ]); @@ -22,7 +22,7 @@ test("Go P2P browser gate owns its server and never uploads private failure outp assert.doesNotMatch(server, /apps\/web\/server|openTestDatabase|createApp/); assert.match(server, /go-p2p-server\.mjs/); assert.match(qualityGate, /- name: Set up Go\n uses: actions\/setup-go@v7\n with:\n go-version-file: apps\/backend\/go\.mod\n cache-dependency-path: apps\/backend\/go\.sum/); - assert.match(qualityGate, /DUALLANE_APP_VERSION: "0\.18\.0"/); + assert.equal(qualityGate.match(/DUALLANE_APP_VERSION: "([^"]+)"/)?.[1], JSON.parse(manifest).version); assert.match(qualityGate, /docker compose --profile storage-migration build storage-provision/); assert.match(config, /testMatch:\s*\[\s*"p2p-go-privacy\.spec\.ts",\s*"p2p-ime\.spec\.ts"\s*\]/); assert.match(config, /outputDir: "\.private-test-results\/p2p-go-browser"/); diff --git a/docs/backend/work-items/2026-09-11-ui-0190-release.md b/docs/backend/work-items/2026-09-11-ui-0190-release.md index 10bc88d0..4c4e27a7 100644 --- a/docs/backend/work-items/2026-09-11-ui-0190-release.md +++ b/docs/backend/work-items/2026-09-11-ui-0190-release.md @@ -8,6 +8,7 @@ - 发布准备将根与 Web 版本升至 0.19.0,补齐应用内更新历史、Echo 功能位置引导、示例与 CI 版本标签,以及相应一致性验证。 - 既有 0.18.0 发布历史、SDK 与离线工具独立版本保持不变;没有 schema 变更,仍为 34。新 Web 与同版 Go 镜像一起激活。 - 维护者已明确授权本次上线及远程生产主机访问;按检出所有者更新 Git,以具备所需权限的会话运行协调器。本次访问指令不改变仓库默认部署规则。 +- 维护者随后明确授权代理自行检查发布 PR #13 的 CI,全部通过后使用其 GitHub 身份合并并部署;必须核对最终 head,不绕过检查或分支保护。此授权仅适用于本次发布。 最终 UI/UX 规则见 [规范入口](../../design/ui-ux-rewrite/README.md),用户可见变化见 [版本说明](../../design/ui-ux-rewrite/RELEASE_NOTES.md)。本记录不重新定义组件或交互规则。 @@ -46,7 +47,7 @@ Windows 首次运行缺少原生 Docker/Bash 的环境失败没有通过修改 ## 激活与回退 -1. 发布准备 PR 由 `timeStarry` 合并并确认最终 CI 后,以检出所有者执行 `git pull --ff-only`。 +1. 发布准备 PR 的最终 CI 全部通过后,按维护者本次明确授权以 `timeStarry` 身份合并,确认合并提交,再以检出所有者执行 `git pull --ff-only`。 2. 确认生产检出为干净 `main`,HEAD 与最新 `origin/main` 完全一致。使用**发布准备合并后的最终 40 位 SHA**,不能继续使用 UI PR 的合并 SHA。 3. 使用 root 执行正式协调器,设置 `GIT_OPTIONAL_LOCKS=0`;必要的 Git trust 例外仅作用于该进程与准确生产目录。 From 2a70bb5b1b2930d3bd4ae2352513df815ef504a3 Mon Sep 17 00:00:00 2001 From: timeStarry Date: Fri, 11 Sep 2026 23:57:16 +0800 Subject: [PATCH 3/3] test: exercise IME mentions with real keyboard input --- e2e/workspace-ime.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/e2e/workspace-ime.spec.ts b/e2e/workspace-ime.spec.ts index 0b8b4602..912aa15c 100644 --- a/e2e/workspace-ime.spec.ts +++ b/e2e/workspace-ime.spec.ts @@ -125,7 +125,11 @@ test("Workspace chat and topic editors keep IME confirmation out of send and men await expect(chatInput).toHaveText(""); expect(workspaceMessageWrites).toHaveLength(1); - await chatInput.fill("@"); + // The IME scenario starts with a user typing at the cleared editor's caret. + // fill() instead selects all and inserts text through a separate DOM path. + await chatInput.click(); + await chatInput.pressSequentially("@"); + await expect(chatInput).toHaveText("@"); const mentionPicker = page.getByRole("dialog", { name: "提及成员" }); await expect(mentionPicker).toBeVisible(); const mentionKey = await dispatchSafariImeEnter(chatInput); @@ -169,7 +173,9 @@ test("Workspace chat and topic editors keep IME confirmation out of send and men } }); - await topicInput.fill("@"); + await topicInput.click(); + await topicInput.pressSequentially("@"); + await expect(topicInput).toHaveText("@"); const topicMentionMenu = topicRegion.getByRole("dialog", { name: "提及成员", exact: true }); await expect(topicMentionMenu).toBeVisible(); const topicImeKey = await dispatchSafariImeEnter(topicInput);