From eb618af80a2200315df2d4f46da65bea045160a2 Mon Sep 17 00:00:00 2001 From: tianma-if Date: Tue, 13 Oct 2026 13:49:49 +0800 Subject: [PATCH] feat: merge red and orange prose palettes into vermilion orange --- packages/shared/src/i18n/en-US.ts | 3 +-- packages/shared/src/i18n/ja.ts | 3 +-- packages/shared/src/i18n/pl.ts | 3 +-- packages/shared/src/i18n/zh-CN.ts | 3 +-- packages/shared/src/note-prose.test.mjs | 25 +++++++++++++++++---- packages/shared/src/note-prose.ts | 29 ++++++++----------------- packages/shared/src/schemas.ts | 3 ++- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts index 0f39ca29..f476fc88 100644 --- a/packages/shared/src/i18n/en-US.ts +++ b/packages/shared/src/i18n/en-US.ts @@ -1022,8 +1022,7 @@ export const enUS = { editorBodyPaletteTitle: "Text colors", editorBodyPalettes: { native: "Native", - clay: "Red", - orange: "Orange", + clay: "Vermilion orange", dawn: "Gold", teal: "Teal", azure: "Blue", diff --git a/packages/shared/src/i18n/ja.ts b/packages/shared/src/i18n/ja.ts index 1ae913ea..cc5bf5e3 100644 --- a/packages/shared/src/i18n/ja.ts +++ b/packages/shared/src/i18n/ja.ts @@ -1011,8 +1011,7 @@ export const ja = { editorBodyPaletteTitle: "本文の配色", editorBodyPalettes: { native: "標準", - clay: "赤", - orange: "オレンジ", + clay: "朱橙", dawn: "金", teal: "青緑", azure: "青", diff --git a/packages/shared/src/i18n/pl.ts b/packages/shared/src/i18n/pl.ts index b8a43c08..c5e444b8 100644 --- a/packages/shared/src/i18n/pl.ts +++ b/packages/shared/src/i18n/pl.ts @@ -1011,8 +1011,7 @@ export const pl = { editorBodyPaletteTitle: "Kolory tekstu", editorBodyPalettes: { native: "Natywne", - clay: "Czerwony", - orange: "Pomarańczowy", + clay: "Cynobrowy pomarańcz", dawn: "Złoty", teal: "Morski", azure: "Niebieski", diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts index 6e05f3f7..a0373c62 100644 --- a/packages/shared/src/i18n/zh-CN.ts +++ b/packages/shared/src/i18n/zh-CN.ts @@ -1022,8 +1022,7 @@ export const zhCN = { editorBodyPaletteTitle: "正文配色", editorBodyPalettes: { native: "原生", - clay: "红色", - orange: "橙色", + clay: "朱橙", dawn: "金色", teal: "青色", azure: "蓝色", diff --git a/packages/shared/src/note-prose.test.mjs b/packages/shared/src/note-prose.test.mjs index 457ff864..84c3628a 100644 --- a/packages/shared/src/note-prose.test.mjs +++ b/packages/shared/src/note-prose.test.mjs @@ -2,7 +2,10 @@ import { describe, expect, test } from "bun:test"; import { MAX_NOTE_PROSE_CSS_BYTES, NOTE_PROSE_PALETTES, + NOTE_PROSE_PALETTE_CHOICES, NOTE_PROSE_DARK_ACCENTS, + accountNoteProseFromRow, + publicNoteProseFromAccount, noteProseCssVariables, NoteProseUpdateSchema, noteProseCodeFontSize, @@ -24,7 +27,21 @@ const emptyAccount = () => ({ }); describe("note prose", () => { - test("checks reading contrast with an intentional bright teal emphasis exception", () => { + test("merges old red and orange choices without dropping account or public share preferences", () => { + expect(NOTE_PROSE_PALETTE_CHOICES).toContain("clay"); + expect(NOTE_PROSE_PALETTE_CHOICES).not.toContain("orange"); + for (const oldPalette of ["clay", "orange"]) { + const account = accountNoteProseFromRow({ note_prose_palette: oldPalette }); + expect(account.palette).toBe("clay"); + expect(resolveNoteProse(account).palette).toBe("clay"); + expect(publicNoteProseFromAccount(account).palette).toBe("clay"); + expect(NoteProseUpdateSchema.parse({ palette: oldPalette }).palette).toBe("clay"); + } + expect(noteProseMigrationPatch(emptyAccount(), { palette: "orange", customCss: null, customColors: null })) + .toEqual({ palette: "clay" }); + }); + + test("checks reading contrast with intentional bright teal and vermilion emphasis exceptions", () => { const luminance = (hex) => { const channels = [1, 3, 5].map((offset) => { const value = parseInt(hex.slice(offset, offset + 2), 16) / 255; @@ -38,9 +55,9 @@ describe("note prose", () => { }; for (const [id, colors] of Object.entries(NOTE_PROSE_PALETTES)) { for (const background of ["#FFFFFF", "#F8FAFB"]) { - // Teal emphasis intentionally stays close to the original bright hue. - // It does not meet normal-text AA; links and code retain the 4.5 minimum. - expect(contrast(colors.accent, background)).toBeGreaterThanOrEqual(id === "teal" ? 2.5 : 4.5); + // Bright emphasis is intentional; links and code retain normal-text AA. + const minimumAccentContrast = id === "teal" ? 2.5 : id === "clay" ? 3 : 4.5; + expect(contrast(colors.accent, background)).toBeGreaterThanOrEqual(minimumAccentContrast); for (const foreground of [colors.link, colors.codeText]) { expect(contrast(foreground, background)).toBeGreaterThanOrEqual(4.5); } diff --git a/packages/shared/src/note-prose.ts b/packages/shared/src/note-prose.ts index 2fd1c7df..16788dca 100644 --- a/packages/shared/src/note-prose.ts +++ b/packages/shared/src/note-prose.ts @@ -8,7 +8,6 @@ export const DEFAULT_NOTE_PROSE_LINE_HEIGHT: NoteProseLineHeight = 1.65; export const NOTE_PROSE_PALETTE_IDS = [ "clay", - "orange", "dawn", "teal", "azure", @@ -110,25 +109,14 @@ const palette = (colors: NoteProsePaletteColors): NoteProsePaletteColors => colo export const NOTE_PROSE_PALETTES: Record = { clay: palette({ - accent: "#C13C42", - text: "#374151", - muted: "#374151", - surface: "#FFF7F7", - divider: "#FFD6D6", - link: "#B42318", - codeBackground: "#FFF1F1", - codeText: "#B42318", - background: "#FFFFFF", - }), - orange: palette({ - accent: "#A65B24", + accent: "#FF4C01", text: "#3A2E28", muted: "#746055", - surface: "#FFF4EA", - divider: "#F6D7BE", - link: "#B45309", - codeBackground: "#FFF4EA", - codeText: "#B45309", + surface: "#FFF4EE", + divider: "#F6D5C6", + link: "#C63C00", + codeBackground: "#FFF4EE", + codeText: "#C63C00", background: "#FFFFFF", }), dawn: palette({ @@ -201,8 +189,7 @@ export const NOTE_PROSE_PALETTES: Record = { - clay: "#F08B8E", - orange: "#E7A16A", + clay: "#FF996E", dawn: "#D9B65C", teal: "#70C7B8", azure: "#8CB4F0", @@ -262,6 +249,8 @@ export const parseNoteProseLineHeight = (value: unknown): NoteProseLineHeight | }; export const parseNoteProsePalette = (value: unknown): NoteProsePaletteChoice | null => { + // Red and orange now share the existing red identifier for older clients. + if (value === "orange") return "clay"; // Removed palettes and the custom color editor read as native. if (value === "emerald" || value === "green" || value === "plum" || value === "custom") return "native"; return typeof value === "string" && (NOTE_PROSE_PALETTE_CHOICES as readonly string[]).includes(value) diff --git a/packages/shared/src/schemas.ts b/packages/shared/src/schemas.ts index c4d3ebf1..e8517a61 100644 --- a/packages/shared/src/schemas.ts +++ b/packages/shared/src/schemas.ts @@ -444,7 +444,8 @@ const noteProseCustomColorSetSchema = z.object({ export const NoteProseUpdateSchema = z.object({ fontSize: z.union([z.literal(14), z.literal(16), z.literal(18), z.literal(20)]).nullable().optional(), lineHeight: z.union([z.literal(1.5), z.literal(1.65), z.literal(2)]).nullable().optional(), - palette: z.enum(NOTE_PROSE_PALETTE_CHOICES).nullable().optional(), + palette: z.union([z.enum(NOTE_PROSE_PALETTE_CHOICES), z.literal("orange")]) + .transform((value) => value === "orange" ? "clay" as const : value).nullable().optional(), customCss: z.string().refine((value) => new TextEncoder().encode(value).byteLength <= MAX_NOTE_PROSE_CSS_BYTES).nullable().optional(), customColors: z.object({ light: noteProseCustomColorSetSchema,