feat(account): define post-release notification preferences
This commit is contained in:
parent
d2c92a980f
commit
66ec4c7d1c
|
|
@ -6,6 +6,11 @@ export type AccountAnswerDepth = "concise" | "balanced" | "deep";
|
||||||
export type AccountEvidenceDisplay = "summary" | "inline" | "full";
|
export type AccountEvidenceDisplay = "summary" | "inline" | "full";
|
||||||
export type AccountNotificationKey = "pendingConfirmation" | "executionWriteback" | "releaseGate" | "failureAlert";
|
export type AccountNotificationKey = "pendingConfirmation" | "executionWriteback" | "releaseGate" | "failureAlert";
|
||||||
export type AccountSettingsSaveScope = "local" | "account";
|
export type AccountSettingsSaveScope = "local" | "account";
|
||||||
|
export type AccountPostReleaseNotificationEvent =
|
||||||
|
| "feedback_received"
|
||||||
|
| "revenue_signal"
|
||||||
|
| "compliance_blocked"
|
||||||
|
| "iteration_recommendation";
|
||||||
export type AccountReleaseNotificationEvent =
|
export type AccountReleaseNotificationEvent =
|
||||||
| "release_gate_blocked"
|
| "release_gate_blocked"
|
||||||
| "release_gate_ready"
|
| "release_gate_ready"
|
||||||
|
|
@ -59,6 +64,22 @@ export interface AccountReleaseGateNotificationPreference {
|
||||||
reason: string;
|
reason: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AccountPostReleaseNotificationPreference {
|
||||||
|
event: AccountPostReleaseNotificationEvent;
|
||||||
|
notificationKey: AccountNotificationKey;
|
||||||
|
surface: "release_growth";
|
||||||
|
spaceReadable: boolean;
|
||||||
|
inAppEnabled: boolean;
|
||||||
|
showInAppReminder: boolean;
|
||||||
|
desktopPreference: boolean;
|
||||||
|
emailDigestPreference: boolean;
|
||||||
|
title: string;
|
||||||
|
reason: string;
|
||||||
|
evidencePolicy: "preserve_business_evidence";
|
||||||
|
reminderPolicy: "page_reminder_only";
|
||||||
|
mutedDetail: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AccountSettings {
|
export interface AccountSettings {
|
||||||
profile: AccountProfileSettings;
|
profile: AccountProfileSettings;
|
||||||
aiPreferences: AccountAiPreferenceSettings;
|
aiPreferences: AccountAiPreferenceSettings;
|
||||||
|
|
@ -165,6 +186,28 @@ const releaseNotificationEventMeta: Record<
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const postReleaseNotificationEventMeta: Record<
|
||||||
|
AccountPostReleaseNotificationEvent,
|
||||||
|
{ notificationKey: AccountNotificationKey; reason: string }
|
||||||
|
> = {
|
||||||
|
feedback_received: {
|
||||||
|
notificationKey: "releaseGate",
|
||||||
|
reason: "Release/Growth 收到发布后用户反馈时使用发布门禁偏好控制页面内提醒;反馈证据本身不受偏好隐藏。",
|
||||||
|
},
|
||||||
|
revenue_signal: {
|
||||||
|
notificationKey: "releaseGate",
|
||||||
|
reason: "Release/Growth 收到收入、转化或增长信号时使用发布门禁偏好控制页面内提醒;收入证据本身不受偏好隐藏。",
|
||||||
|
},
|
||||||
|
compliance_blocked: {
|
||||||
|
notificationKey: "releaseGate",
|
||||||
|
reason: "合规、风控或发布后审核阻塞下一步时使用发布门禁偏好控制页面内提醒;阻塞状态与证据仍必须展示在业务页面。",
|
||||||
|
},
|
||||||
|
iteration_recommendation: {
|
||||||
|
notificationKey: "pendingConfirmation",
|
||||||
|
reason: "AI 生成下一轮迭代建议、需要人类拍板时使用待确认偏好控制页面内提醒;建议对象和证据仍保留。",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export function createDefaultAccountSettings(seed: AccountSettingsSeed = {}): AccountSettings {
|
export function createDefaultAccountSettings(seed: AccountSettingsSeed = {}): AccountSettings {
|
||||||
return {
|
return {
|
||||||
profile: {
|
profile: {
|
||||||
|
|
@ -303,6 +346,33 @@ export function getReleaseGateNotificationPreference(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPostReleaseNotificationPreference(
|
||||||
|
settings: AccountSettings | Partial<AccountSettings> | null | undefined,
|
||||||
|
event: AccountPostReleaseNotificationEvent
|
||||||
|
): AccountPostReleaseNotificationPreference {
|
||||||
|
const normalized = mergeAccountSettings(createDefaultAccountSettings(), settings || null);
|
||||||
|
const meta = postReleaseNotificationEventMeta[event];
|
||||||
|
const preference = normalized.notifications[meta.notificationKey];
|
||||||
|
const enabled = Boolean(preference.enabled);
|
||||||
|
return {
|
||||||
|
event,
|
||||||
|
notificationKey: meta.notificationKey,
|
||||||
|
surface: "release_growth",
|
||||||
|
spaceReadable: true,
|
||||||
|
inAppEnabled: enabled,
|
||||||
|
showInAppReminder: enabled,
|
||||||
|
desktopPreference: enabled && Boolean(preference.desktop),
|
||||||
|
emailDigestPreference: enabled && Boolean(preference.emailDigest),
|
||||||
|
title: accountNotificationLabels[meta.notificationKey].title,
|
||||||
|
reason: meta.reason,
|
||||||
|
evidencePolicy: "preserve_business_evidence",
|
||||||
|
reminderPolicy: "page_reminder_only",
|
||||||
|
mutedDetail: enabled
|
||||||
|
? "Release/Growth 或 Space 可显示页面内提醒;桌面/邮件只记录偏好,不代表系统级推送已接入。"
|
||||||
|
: "仅静默页面内提醒;反馈、收入、合规、迭代建议及其证据仍必须按业务页面展示。",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAccountSettings(seed?: AccountSettingsSeed): Promise<AccountSettings> {
|
export async function getAccountSettings(seed?: AccountSettingsSeed): Promise<AccountSettings> {
|
||||||
const defaults = createDefaultAccountSettings(seed);
|
const defaults = createDefaultAccountSettings(seed);
|
||||||
const localSettings = mergeAccountSettings(defaults, readStoredAccountSettings());
|
const localSettings = mergeAccountSettings(defaults, readStoredAccountSettings());
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
createDefaultAccountSettings,
|
createDefaultAccountSettings,
|
||||||
getAccountNotificationConsumption,
|
getAccountNotificationConsumption,
|
||||||
getAccountSettings,
|
getAccountSettings,
|
||||||
|
getPostReleaseNotificationPreference,
|
||||||
getReleaseGateNotificationPreference,
|
getReleaseGateNotificationPreference,
|
||||||
shouldShowAccountNotification,
|
shouldShowAccountNotification,
|
||||||
updateAccountSettings,
|
updateAccountSettings,
|
||||||
|
|
@ -132,6 +133,42 @@ describe("account settings api", () => {
|
||||||
expect(failedEvent.reason).toContain("failed event");
|
expect(failedEvent.reason).toContain("failed event");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("maps post-release feedback, revenue, compliance, and iteration events to local reminder preferences", () => {
|
||||||
|
const defaults = createDefaultAccountSettings();
|
||||||
|
const settings = {
|
||||||
|
...defaults,
|
||||||
|
notifications: {
|
||||||
|
...defaults.notifications,
|
||||||
|
releaseGate: { enabled: true, desktop: false, emailDigest: true },
|
||||||
|
pendingConfirmation: { enabled: false, desktop: true, emailDigest: true },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const feedback = getPostReleaseNotificationPreference(settings, "feedback_received");
|
||||||
|
const revenue = getPostReleaseNotificationPreference(settings, "revenue_signal");
|
||||||
|
const compliance = getPostReleaseNotificationPreference(settings, "compliance_blocked");
|
||||||
|
const iteration = getPostReleaseNotificationPreference(settings, "iteration_recommendation");
|
||||||
|
|
||||||
|
expect(feedback.notificationKey).toBe("releaseGate");
|
||||||
|
expect(feedback.surface).toBe("release_growth");
|
||||||
|
expect(feedback.spaceReadable).toBe(true);
|
||||||
|
expect(feedback.showInAppReminder).toBe(true);
|
||||||
|
expect(feedback.desktopPreference).toBe(false);
|
||||||
|
expect(feedback.emailDigestPreference).toBe(true);
|
||||||
|
expect(feedback.reminderPolicy).toBe("page_reminder_only");
|
||||||
|
expect(feedback.evidencePolicy).toBe("preserve_business_evidence");
|
||||||
|
expect(feedback.mutedDetail).toContain("不代表系统级推送已接入");
|
||||||
|
expect(revenue.notificationKey).toBe("releaseGate");
|
||||||
|
expect(revenue.reason).toContain("收入");
|
||||||
|
expect(compliance.notificationKey).toBe("releaseGate");
|
||||||
|
expect(compliance.reason).toContain("合规");
|
||||||
|
expect(iteration.notificationKey).toBe("pendingConfirmation");
|
||||||
|
expect(iteration.showInAppReminder).toBe(false);
|
||||||
|
expect(iteration.desktopPreference).toBe(false);
|
||||||
|
expect(iteration.emailDigestPreference).toBe(false);
|
||||||
|
expect(iteration.mutedDetail).toContain("证据仍必须");
|
||||||
|
});
|
||||||
|
|
||||||
it("falls back missing notification fields to safe defaults", async () => {
|
it("falls back missing notification fields to safe defaults", async () => {
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
"pmflow:account-settings:v1",
|
"pmflow:account-settings:v1",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue