Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,22 @@ public engineering records with dated evidence, not live configuration.
cycle only through fresh valid consent and monotonically newer link clocks;
never use public signup to revive suppressed/test identities, historical hard
failures or unknown submissions. Do not reset quotas or purchase allowance.
Keep provider submission
distinct from delivery and inbox acceptance. Candidate details are in
[the subscription iteration](docs/iterations/2026-10-13-subscription-polish.md).
A separately authorized owner `mail-test release` may remove only the
server-configured test role after an exact normalized `expectedRecipient`
check. It sends no mail, preserves history and rotates signed-link clocks,
leaving an ordinary unsubscribed recipient who must sign up and confirm
again. It cannot lift suppression, bypass uncertain submissions, reset
quotas, automatically subscribe or choose a sending target. Public signup
cannot perform this release; editorial schedules must never call it.
Keep provider submission distinct from delivery and inbox acceptance.
- Current subscription layout is introduction, capacity, unified feedback,
action form, cancellation/unsubscribe, then privacy. Keep feedback above
the actions it explains across signup and signed management states.
Current candidate details are in
[the normal subscription iteration](docs/iterations/2026-10-13-normal-subscription.md);
[the v14 iteration](docs/iterations/2026-10-13-subscription-polish.md)
records the previous layout and deployment. Do not predeclare deployment,
recipient release or a v1.0.1 tag before their actual evidence/authorization.
- Preserve accurate tracking and network disclosures. Anonymous Brevo
tracking is not proof that pixels/rewritten links are disabled. Mainland
accessibility is not guaranteed.
Expand Down
28 changes: 13 additions & 15 deletions components/news/subscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
onManagementUrl: (id: string, token: string) => void;
}) {
const zh = locale === "zh";
const [confirmationLanding] = useState(initialLink?.action === "confirm");
const [autoAttempted, setAutoAttempted] = useState(false);
const [manage, setManage] = useState<ManageLink | null>(initialLink),
[subscriber, setSubscriber] = useState<SubscriberView | null>(null),
Expand Down Expand Up @@ -266,7 +265,18 @@
<h2>{manage ? zh ? "你的邮件订阅" : "Your email subscription" : zh ? "每周精选,送达邮箱。" : "A weekly perspective, by email."}</h2>
<p>{zh ? "选择语言与主题,精选事件与研究。仅在有实质更新时发送。" : "Choose a language and topics for selected events and research. No empty editions."}</p>
</header>
{!confirmationLanding && form}
<section className="n-subscription-capacity" aria-label={zh ? "订阅名额" : "Subscription availability"} aria-busy={capacityState === "loading"}>
{capacity ? <>
<div className="n-subscription-capacity-head">
<div><span className="n-subscription-capacity-label">{zh ? "已确认订阅 / 当前容量" : "Confirmed / capacity"}</span><strong>{capacity.confirmedCount}<span> / {capacity.capacity}</span></strong><span className="n-subscription-capacity-label">{zh ? "位订阅者 · 本站设置" : "subscribers · site limit"}</span></div>
<span className={`n-subscription-remaining${capacity.full ? " is-full" : ""}`}>{capacity.full ? zh ? "名额已满" : "Capacity reached" : zh ? `剩余 ${capacity.remaining} 个名额` : `${capacity.remaining} places remaining`}</span>
</div>
<progress max={capacity.capacity} value={Math.min(capacity.confirmedCount, capacity.capacity)} aria-label={zh ? "已确认订阅人数" : "Confirmed subscribers"} />
<p>{zh ? `仅确认成功占名额。免费限额 ${capacity.dailyLimit} 封/天,周报预算 ${capacity.weeklyLimit} 封/天;确认邮件共用额度,投递可能延迟。` : `Only confirmed subscriptions occupy places. Daily allowance: ${capacity.dailyLimit} emails, including confirmations; digest budget: ${capacity.weeklyLimit}/day. Delivery may be delayed.`}</p>
</> : <p>{capacityState === "loading" ? zh ? "正在读取已确认人数与剩余名额…" : "Loading confirmed subscribers and remaining places…" : zh ? "订阅人数暂时未知,请重试读取。" : "Subscriber counts are temporarily unknown. Please retry."}</p>}
{capacityState === "error" && <p className="n-subscription-inline-error" role="status">{zh ? "名额未能更新,数字为上次结果;仍可管理已生效订阅。" : "Availability failed to refresh; figures are from the last check. Active subscriptions remain manageable."}</p>}
<button type="button" className="n-subscription-secondary" disabled={busy !== null || capacityState === "loading"} onClick={() => { setCapacityState("loading"); refreshCapacity(); }}>{capacityState === "loading" ? zh ? "正在刷新…" : "Refreshing…" : zh ? "刷新名额" : "Refresh availability"}</button>
</section>
{display && (() => {
const [icon, title, body] = content(display);
return <section className={`n-subscription-notice n-subscription-${display.kind}`} ref={noticeRef} tabIndex={-1} role={errorCard ? "alert" : "status"} aria-live={errorCard ? "assertive" : "polite"} aria-atomic="true">
Expand All @@ -280,25 +290,13 @@
</div>
</section>;
})()}
{confirmationLanding && form}
{form}
{manage && !confirmationMode && (subscriber?.status === "confirmed" || subscriber?.status === "pending") && <section id="unsubscribe" className="n-subscription-form n-subscription-unsubscribe-area" aria-label={zh ? "结束邮件订阅" : "Stop email updates"}><div><h3>{zh ? "取消邮件订阅" : "Cancel email updates"}</h3><p>{zh ? "停止后续邮件;仍可使用 RSS。" : "Stop future email. RSS remains available."}</p></div><button type="button" className="n-subscription-secondary n-subscription-unsubscribe-button" disabled={formLocked} onClick={() => void submit("unsubscribe")}>{busy === "unsubscribe" ? zh ? "正在退订…" : "Unsubscribing…" : subscriber.status === "pending" ? zh ? "取消订阅申请" : "Cancel subscription request" : zh ? "退订所有邮件" : "Unsubscribe from all email"}</button></section>}
<section className="n-subscription-capacity" aria-label={zh ? "订阅名额" : "Subscription availability"} aria-busy={capacityState === "loading"}>
{capacity ? <>
<div className="n-subscription-capacity-head">
<div><span className="n-subscription-capacity-label">{zh ? "已确认订阅 / 当前容量" : "Confirmed / capacity"}</span><strong>{capacity.confirmedCount}<span> / {capacity.capacity}</span></strong><span className="n-subscription-capacity-label">{zh ? "位订阅者 · 本站设置" : "subscribers · site limit"}</span></div>
<span className={`n-subscription-remaining${capacity.full ? " is-full" : ""}`}>{capacity.full ? zh ? "名额已满" : "Capacity reached" : zh ? `剩余 ${capacity.remaining} 个名额` : `${capacity.remaining} places remaining`}</span>
</div>
<progress max={capacity.capacity} value={Math.min(capacity.confirmedCount, capacity.capacity)} aria-label={zh ? "已确认订阅人数" : "Confirmed subscribers"} />
<p>{zh ? `仅确认成功占名额。免费限额 ${capacity.dailyLimit} 封/天,周报预算 ${capacity.weeklyLimit} 封/天;确认邮件共用额度,投递可能延迟。` : `Only confirmed subscriptions occupy places. Daily allowance: ${capacity.dailyLimit} emails, including confirmations; digest budget: ${capacity.weeklyLimit}/day. Delivery may be delayed.`}</p>
</> : <p>{capacityState === "loading" ? zh ? "正在读取已确认人数与剩余名额…" : "Loading confirmed subscribers and remaining places…" : zh ? "订阅人数暂时未知,请重试读取。" : "Subscriber counts are temporarily unknown. Please retry."}</p>}
{capacityState === "error" && <p className="n-subscription-inline-error" role="status">{zh ? "名额未能更新,数字为上次结果;仍可管理已生效订阅。" : "Availability failed to refresh; figures are from the last check. Active subscriptions remain manageable."}</p>}
<button type="button" className="n-subscription-secondary" disabled={busy !== null || capacityState === "loading"} onClick={() => { setCapacityState("loading"); refreshCapacity(); }}>{capacityState === "loading" ? zh ? "正在刷新…" : "Refreshing…" : zh ? "刷新名额" : "Refresh availability"}</button>
</section>
<p className="n-small n-subscription-disclosure">{zh ? "邮件由 Brevo 投递。当前匿名打开与点击统计不关联具体联系人,但仍可能包含像素与链接跟踪;本站不依据这些统计决定发信或判断已读。" : "Email is delivered by Brevo. Anonymous open and click statistics are not associated with individual contacts, but tracking pixels and tracked links may remain. We do not use these statistics to decide whether to send email or as proof that an email was read."}</p>
</div>
<aside className="n-subscription-sidebar" aria-label={zh ? "RSS 阅读方式" : "Read with RSS"}>
<section className="n-rss-box">
<div className="n-rss-heading"><span className="n-kicker">OPEN FEEDS</span><img src="/brand/rss-feed.svg" width="38" height="38" alt="" aria-hidden="true" /></div>

Check warning on line 299 in components/news/subscription.tsx

View workflow job for this annotation

GitHub Actions / verify

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
<h2>{zh ? "阅读,不止一种方式。" : "Read on your terms."}</h2>
<p>{zh ? "全部已发布新闻,直接进入 RSS 阅读器。无需邮箱,不占名额。" : "All published stories in your RSS reader. No email or subscription place needed."}</p>
<div className="n-rss-feeds">{(["zh", "en"] as const).map(feedLanguage => {
Expand Down
66 changes: 66 additions & 0 deletions docs/CURRENT_STATE.json
Original file line number Diff line number Diff line change
Expand Up @@ -647,5 +647,71 @@
],
"productionBrowser": "Agent in-app browser rejected both custom and platform domain navigation; no production DOM acceptance claimed. Synthetic local browser acceptance passed.",
"immutableReleaseTagVerified": true
},
"normalSubscriptionIteration": {
"recordedOn": "2026-10-13",
"phase": "local-verification-complete-awaiting-required-CI-and-Sites",
"baseMain": "e3df53558f72534a7c6f43909b689b30e8ffb27b",
"notes": "docs/iterations/2026-10-13-normal-subscription.md",
"requestedBehavior": [
"Capacity card before status and interaction cards",
"All status/feedback before editable form and explicit cancellation/unsubscribe",
"Owner-authorized release of configured historical test identity to allow ordinary opt-in"
],
"productionOperationScope": "Only configured recipient matching explicit owner expectedRecipient; no send, no automatic confirmation, no quota reset, retain test history",
"releaseGate": {
"plannedTag": "v1.0.1",
"tagCreated": false,
"requiresUserAcceptance": true
},
"schemaChanged": false,
"secretsRead": false,
"realMailSent": false,
"validation": {
"tests": {
"base": 35,
"newsAndMail": 61,
"subscriptionUi": 11,
"localHttp": 8,
"total": 115
},
"typescript": "passed",
"contentDocuments": 24,
"lintErrors": 0,
"lintWarnings": 2,
"audit": {
"moderate": 8,
"high": 0,
"critical": 0
},
"build": "passed",
"bundle": {
"javascriptBytes": 919522,
"javascriptGzipBytes": 267400,
"cssBytes": 116185,
"cssGzipBytes": 23035
},
"browser": {
"syntheticLocal": true,
"signupUiOnlyMock": true,
"viewports": [
"1440x900",
"390x844"
],
"flows": [
"Visible automatic confirmation",
"Bilingual signed management links",
"Saved preferences feedback before form",
"Explicit unsubscribe and pending cancellation",
"Capacity before feedback and interactions",
"Retained signup form below check-inbox feedback; no return CTA"
],
"consoleErrors": 0
},
"testStability": "61-case suite passed 20 bounded runs after forced reverse-winner preference regression fix; first truncated 60/61 cause remains unconfirmed",
"httpStartupLimit": "Local ProxyWorker had one connection-lost 500 immediately after restart; direct owner-rejection 403 and subsequent ready 8/8 passed; no production inference"
},
"ownerStatusRead": "Authenticated sanitized status only; configured test role was unsubscribed, four historical deliveries delivered, no raw subscriber rows, email, signatures or secrets read",
"productionRoleRelease": "pending-deployment"
}
}
14 changes: 12 additions & 2 deletions docs/PROJECT_CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@

本说明用于在同一个项目中持续迭代。它仅保留可公开的工程事实,未复制私人运营审计、账户、验收邮箱、生产数据或秘密值。机器可读记录见 [CURRENT_STATE.json](CURRENT_STATE.json)。

## 最新迭代:订阅体验优化(2026-10-13)
## 当前迭代:普通订阅身份与统一卡片顺序(2026-10-13)

用户最新要求覆盖上一轮的表单/反馈位置:容量卡片靠上,状态反馈统一在操作表单与取消/退订卡片之前。当前候选基线 main `e3df53558f72534a7c6f43909b689b30e8ffb27b`。完成本地检查、受保护 PR 合并和既有 Sites 发布后,将单独记录公开凭据;下节 v14 仅为前一轮发布。

用户已明确授权让历史固定测试收件人通过正常读者流程订阅。采用仅 owner 可调用、精确匹配固定配置目标且无发信的角色释放:保留测试历史与安全抑制,使旧签名失效;释放后仅为普通退订身份,仍需用户自己申请并完成邮件确认。不能把确认过的测试身份自动计为公众订阅,不能匿名释放任意测试地址或清零配额。公共仓库不记录真人邮箱、秘密或签名。

本轮最终本地 35 基础 + 61 新闻/邮件 + 11 订阅 UI + 8 HTTP,共 115 项通过,TypeScript、内容、lint、审计、构建与原预算通过;JS 919522/gzip267400,CSS116185/gzip23035。双语桌面和手机验证统一卡片顺序;signup 布局用本地响应 mock,未发送邮件。补测强制反向赢家修复有效 pending 的语言/主题被重复申请覆盖问题,修复后 61 项连续 20 轮通过;初次截断失败与本地启动连接瞬断的界限保留在迭代记录。已读取获授权的脱敏 owner 状态,但生产角色释放仍需匹配版本部署后执行。

`v1.0.1` 标签须等待用户验收确认,本轮只部署供验收;不创建该标签、GitHub Release 或移动 `v1.0.0`。详细记录见 [本轮维护说明](iterations/2026-10-13-normal-subscription.md)。

## 历史:订阅体验优化 v14(2026-10-13)

在正式 v1.0.0 之后继续迭代,候选基线 main 为 `c67f707b9e6c7f9e9b297dbde72757a1b545ecc9`。[PR #7](https://github.com/AutoGenUI/ag-ui.ai/pull/7) 已正常合入 main `ce64f39c158e72e06549cde97c6810c77a2ff5d8`,[PR verify](https://github.com/AutoGenUI/ag-ui.ai/actions/runs/38390726523/job/115273567567) 与 [main verify](https://github.com/AutoGenUI/ag-ui.ai/actions/runs/38390910990/job/115274178851) 均通过。下节 v1.0.0 是不可变历史。

Expand Down Expand Up @@ -82,7 +92,7 @@ Sites 数字版本 **14** 于 11:34:36 UTC 部署成功,version ID `appgprj_6a
- 网站正式 confirmed 订阅策略容量为 250。每日发送额度 300、周报预算 250 是交接时的免费档与网站策略;不是联系人上限。后续是否变化需重新核验,不自动购买或升级。
- pending、test、unsubscribed、suppressed 不占正式 confirmed 容量。确认原子取得名额;满额返回 `capacity-full`,偏好与退订继续可用;退订释放名额。
- 普通 GET/HEAD/预取不确认或退订;当前版本在可见确认落地页自动 POST,退订仍需明确按钮操作。执行可见 JavaScript 的扫描器可能确认,不能宣称完全排除机器人。保留原始 URL/纯文本后备、状态反馈、请求 epoch 和防旧请求覆盖逻辑。
- 测试身份与正式订阅隔离,不能将测试收件人转为正式订阅。固定测试收件人只取服务器配置,不从工具参数指定。
- 公众申请不得自动转换测试身份;仅独立 owner 释放操作可将精确匹配的固定测试身份改为普通 unsubscribed,之后仍需新申请与邮件确认。测试发信目标只取服务器配置,release 的 expectedRecipient 仅校验该目标,不能指定新的发送地址。
- 保留 generation、最新 run 原子 claim、restart winner、过期检查、投递幂等和抑制机制;不确定提交不能自动重发。D1 字面前缀使用 `substr`,不要恢复曾触发长度限制的 LIKE prefix 实现。
- 交接记录两轮真实验收完成,合计 4 封测试邮件,用户已退订,鉴权 Delivered 回调与退订后停发通过。本轮没有重复此流程。没有真实 queued cancellation 证据;不宣称真实验证了全部退信情况。
- 服务接受请求、Delivered 回调和用户实际收件分别记录;不依打开/点击判定已读或决定发信。
Expand Down
Loading
Loading