From 2ab9eae2b212580dea6601ec982041943afa2f0c Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Sat, 2 May 2026 12:53:02 +0800 Subject: [PATCH 1/3] fix(app): show all update notice bullets --- packages/app/src/context/highlights.test.ts | 55 +++++++++++++++++++-- packages/app/src/context/highlights.tsx | 38 +++++++++----- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/packages/app/src/context/highlights.test.ts b/packages/app/src/context/highlights.test.ts index 596e26abd..cfcb4b1c1 100644 --- a/packages/app/src/context/highlights.test.ts +++ b/packages/app/src/context/highlights.test.ts @@ -18,7 +18,7 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { }) }) - test("prefers the Chinese update notice for zh locale", () => { + test("prefers all Chinese update notice bullets for zh locale", () => { const payload = [ { tag_name: "v0.2.10", @@ -37,14 +37,18 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { }, ] const highlights = loadReleaseHighlights(payload, "0.2.10", "0.2.9", "zh") - expect(highlights).toHaveLength(1) + expect(highlights).toHaveLength(2) expect(highlights[0]).toMatchObject({ title: "爪印 v0.2.10", description: "修复首条消息崩溃", }) + expect(highlights[1]).toMatchObject({ + title: "爪印 v0.2.10", + description: "调整更新提示", + }) }) - test("falls back to bullets directly under 中文版本 when 主要更新 is absent", () => { + test("falls back to all bullets directly under 中文版本 when 主要更新 is absent", () => { const payload = [ { tag_name: "v0.2.10", @@ -52,11 +56,15 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { }, ] const highlights = loadReleaseHighlights(payload, "0.2.10", "0.2.9", "zh") - expect(highlights).toHaveLength(1) + expect(highlights).toHaveLength(2) expect(highlights[0]).toMatchObject({ title: "爪印 v0.2.10", description: "修复首条消息崩溃", }) + expect(highlights[1]).toMatchObject({ + title: "爪印 v0.2.10", + description: "调整更新提示", + }) }) test("falls back to the English update notice when Chinese summary is missing", () => { @@ -82,7 +90,44 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { }, ] const highlights = loadReleaseHighlights(payload, "0.3.0", "0.2.3", "en") - expect(highlights[0].description).toBe("Added dark theme") + expect(highlights.map((highlight) => highlight.description)).toEqual(["Added dark theme", "Fixed dock icon"]) + }) + + test("keeps all localized update notice bullets", () => { + const payload = [ + { + tag_name: "v2026.4.29", + body: [ + "## App Update Notice", + "", + "PawWork refreshes the desktop.", + "", + "## 中文版本", + "", + "### 主要更新", + "", + "PawWork 2026.4.29 刷新桌面界面。", + "", + "- 刷新桌面界面", + "- 修复首次进入 Home 时左右侧栏默认打开的问题", + "- 移除内置 Trash 工具", + "- 提升 session 稳定性", + "- 新增前台 subagent 生命周期支持", + "- 默认启用 open permissions", + "- 修复 Windows 拖拽上传", + ].join("\n"), + }, + ] + const highlights = loadReleaseHighlights(payload, "2026.4.29", "2026.4.28", "zh") + expect(highlights.map((highlight) => highlight.description)).toEqual([ + "刷新桌面界面", + "修复首次进入 Home 时左右侧栏默认打开的问题", + "移除内置 Trash 工具", + "提升 session 稳定性", + "新增前台 subagent 生命周期支持", + "默认启用 open permissions", + "修复 Windows 拖拽上传", + ]) }) test("truncates long summaries with an ellipsis", () => { diff --git a/packages/app/src/context/highlights.tsx b/packages/app/src/context/highlights.tsx index 86536edd5..f5112e8b8 100644 --- a/packages/app/src/context/highlights.tsx +++ b/packages/app/src/context/highlights.tsx @@ -88,24 +88,36 @@ function findChineseUpdateNotice(body: string) { return findHeadingSection(chinese, /^#{3,6}\s+主要更新\s*$/) ?? chinese } -function summarizeNotice(notice: string | undefined): string | undefined { - if (!notice) return +function trimNoticeItem(value: string) { + const text = value.trim() + return text.length > 200 ? text.slice(0, 200).trimEnd() + "…" : text +} + +function parseNoticeDescriptions(notice: string | undefined): string[] { + if (!notice) return [] const lines = notice .split(/\r?\n/) .map((line) => line.trim()) .filter((line) => line.length > 0 && !line.startsWith("#")) - if (lines.length === 0) return - const first = lines[0].replace(/^[-*+]\s+/, "").replace(/^\d+\.\s+/, "") - return first.length > 200 ? first.slice(0, 200).trimEnd() + "…" : first + + const bullets = lines.flatMap((line) => { + const match = line.match(/^(?:[-*+]\s+|\d+\.\s+)(.+)$/) + if (!match) return [] + const item = trimNoticeItem(match[1]) + return item ? [item] : [] + }) + if (bullets.length > 0) return bullets + + return lines.length > 0 ? [trimNoticeItem(lines[0])] : [] } -function summarizeReleaseBody(body: string, locale: ReleaseLocale) { +function parseReleaseBodyDescriptions(body: string, locale: ReleaseLocale) { if (locale === "zh") { - const chinese = summarizeNotice(findChineseUpdateNotice(body)) - if (chinese) return chinese + const chinese = parseNoticeDescriptions(findChineseUpdateNotice(body)) + if (chinese.length > 0) return chinese } - return summarizeNotice(findAppUpdateNotice(body)) + return parseNoticeDescriptions(findAppUpdateNotice(body)) } function releaseTitle(tag: string, locale: ReleaseLocale) { @@ -138,11 +150,11 @@ function parseRelease(value: unknown, locale: ReleaseLocale): ParsedRelease | un const body = getText(value.body) if (tag && body) { - const summary = summarizeReleaseBody(body, locale) - if (summary) { + const descriptions = parseReleaseBodyDescriptions(body, locale) + if (descriptions.length > 0) { return { tag, - highlights: [{ title: releaseTitle(tag, locale), description: summary }], + highlights: descriptions.map((description) => ({ title: releaseTitle(tag, locale), description })), } } } @@ -190,7 +202,7 @@ function sliceHighlights(input: { releases: ParsedRelease[]; current?: string; p seen.add(key) return true }) - return unique.slice(0, 5) + return unique } function dedupeKey(highlight: Highlight) { From 886447276b38e1d95e6b4b38c5c2d1099452647a Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Sat, 2 May 2026 15:04:21 +0800 Subject: [PATCH 2/3] fix(app): address update notice review feedback --- packages/app/src/context/highlights.test.ts | 47 +++++++++++++++++++++ packages/app/src/context/highlights.tsx | 6 ++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/app/src/context/highlights.test.ts b/packages/app/src/context/highlights.test.ts index cfcb4b1c1..34912cf68 100644 --- a/packages/app/src/context/highlights.test.ts +++ b/packages/app/src/context/highlights.test.ts @@ -82,6 +82,18 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { }) }) + test("keeps hard-wrapped paragraph notices as one card", () => { + const payload = [ + { + tag_name: "v0.2.10", + body: ["## App Update Notice", "", "Fixed first-message crash and improved", "the update notice parser."].join("\n"), + }, + ] + const highlights = loadReleaseHighlights(payload, "0.2.10", "0.2.9", "en") + expect(highlights).toHaveLength(1) + expect(highlights[0].description).toBe("Fixed first-message crash and improved the update notice parser.") + }) + test("skips markdown headings and strips bullet markers inside the app update notice section", () => { const payload = [ { @@ -130,6 +142,41 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { ]) }) + test("keeps skipped-version highlights beyond the old five item cap", () => { + const payload = [ + { + tag_name: "v2026.4.29", + body: "## App Update Notice\n\n- A\n- B\n- C\n", + }, + { + tag_name: "v2026.4.28", + body: "## App Update Notice\n\n- D\n- E\n- F\n", + }, + ] + + expect(loadReleaseHighlights(payload, "2026.4.29", "2026.4.27", "en").map((highlight) => highlight.description)).toEqual([ + "A", + "B", + "C", + "D", + "E", + "F", + ]) + }) + + test("limits long skipped-version highlight ranges", () => { + const payload = [ + { + tag_name: "v2026.4.29", + body: ["## App Update Notice", "", ...Array.from({ length: 20 }, (_, index) => `- Item ${index + 1}`)].join("\n"), + }, + ] + + const highlights = loadReleaseHighlights(payload, "2026.4.29", "2026.4.28", "en") + expect(highlights).toHaveLength(15) + expect(highlights.at(-1)?.description).toBe("Item 15") + }) + test("truncates long summaries with an ellipsis", () => { const long = "a".repeat(300) const payload = [{ tag_name: "v1.0.0", body: `## App Update Notice\n\n${long}` }] diff --git a/packages/app/src/context/highlights.tsx b/packages/app/src/context/highlights.tsx index f5112e8b8..4bf41f53a 100644 --- a/packages/app/src/context/highlights.tsx +++ b/packages/app/src/context/highlights.tsx @@ -9,6 +9,7 @@ import { persisted } from "@/utils/persist" import { DialogReleaseNotes, type Highlight } from "@/components/dialog-release-notes" const CHANGELOG_URL = "https://api.github.com/repos/Astro-Han/pawwork/releases" +const MAX_RELEASE_HIGHLIGHTS = 15 type Store = { version?: string @@ -109,7 +110,8 @@ function parseNoticeDescriptions(notice: string | undefined): string[] { }) if (bullets.length > 0) return bullets - return lines.length > 0 ? [trimNoticeItem(lines[0])] : [] + const summary = trimNoticeItem(lines.join(" ")) + return summary ? [summary] : [] } function parseReleaseBodyDescriptions(body: string, locale: ReleaseLocale) { @@ -202,7 +204,7 @@ function sliceHighlights(input: { releases: ParsedRelease[]; current?: string; p seen.add(key) return true }) - return unique + return unique.slice(0, MAX_RELEASE_HIGHLIGHTS) } function dedupeKey(highlight: Highlight) { From 1afe5eae6962ba97ec74cd2e31d63bcad958ed03 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Sat, 2 May 2026 15:13:39 +0800 Subject: [PATCH 3/3] fix(app): preserve wrapped update notice bullets --- packages/app/src/context/highlights.test.ts | 14 ++++++++++++++ packages/app/src/context/highlights.tsx | 17 ++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/app/src/context/highlights.test.ts b/packages/app/src/context/highlights.test.ts index 34912cf68..08fa19b9f 100644 --- a/packages/app/src/context/highlights.test.ts +++ b/packages/app/src/context/highlights.test.ts @@ -105,6 +105,20 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { expect(highlights.map((highlight) => highlight.description)).toEqual(["Added dark theme", "Fixed dock icon"]) }) + test("keeps wrapped bullet continuation lines", () => { + const payload = [ + { + tag_name: "v0.3.0", + body: ["## App Update Notice", "", "- Fixed first-message crash", " when startup takes longer", "- Added update notice parser"].join("\n"), + }, + ] + const highlights = loadReleaseHighlights(payload, "0.3.0", "0.2.3", "en") + expect(highlights.map((highlight) => highlight.description)).toEqual([ + "Fixed first-message crash when startup takes longer", + "Added update notice parser", + ]) + }) + test("keeps all localized update notice bullets", () => { const payload = [ { diff --git a/packages/app/src/context/highlights.tsx b/packages/app/src/context/highlights.tsx index 4bf41f53a..af18b7aea 100644 --- a/packages/app/src/context/highlights.tsx +++ b/packages/app/src/context/highlights.tsx @@ -102,12 +102,19 @@ function parseNoticeDescriptions(notice: string | undefined): string[] { .map((line) => line.trim()) .filter((line) => line.length > 0 && !line.startsWith("#")) - const bullets = lines.flatMap((line) => { + const bullets: string[] = [] + let currentBullet: string | undefined + for (const line of lines) { const match = line.match(/^(?:[-*+]\s+|\d+\.\s+)(.+)$/) - if (!match) return [] - const item = trimNoticeItem(match[1]) - return item ? [item] : [] - }) + if (match) { + if (currentBullet) bullets.push(trimNoticeItem(currentBullet)) + currentBullet = match[1].trim() + continue + } + if (currentBullet) currentBullet += ` ${line}` + } + if (currentBullet) bullets.push(trimNoticeItem(currentBullet)) + if (bullets.length > 0) return bullets const summary = trimNoticeItem(lines.join(" "))