技术日志

面向开发的变更记录(技术细节)。群友版更新请看 更新公告。

[Unreleased]

[2.4.7] - 2026-09-15

Fixed

  • 终轮独立复审 5 低危修复(2026-09-15):① 盘点回退路径 restaurants 查询失败(瞬时 5xx)时不再把全部行误标孤儿红底(restsOk 降级为"未知");② _dup_override 剥离回写条件修正为"键原存在即回写"(原 delete 后判断恒 false 致永不回写、非 true 形态键会透传致未知列 400 自伤;单测抓出并补 _dup_override:false 用例);③ 上传 60s timer 改 try/finally 清理(abort/断网抛错路径不再残留 timer,两流程一致);④ 客户端查重与服务端语义对齐:同名不同区=分店不弹重复警告(blur/submit 传区域);⑤ 放大角标加 aria-hidden(读屏不重复播报)。另修自查抓到的既有 bug:submit 查重命中时 dupMatchName 误赋对象致显示 [object Object](改 .name)。单测 90 项全过。真机冒烟(软删/建店/写评价)通过。
  • Security

  • 生产库权限加固闭环(DB 侧操作,2026-09-15,无代码改动):① F1 表权限收窄——anon/authenticated 收业务最小集(restaurants/reviews 仅 SELECT+INSERT+列级 UPDATE(deleted)、edit_log 仅 SELECT、contributions/likes 零权限),与 RLS 软删策略(qual deleted=false/with_check deleted=true)+列级 GRANT 三层一致;② F9 闭环——6 个 admin_* SECURITY DEFINER 函数 anon 均不可执行,代码零调用保留不删;③ CF Rate Limiting 定案 A 不建(WAF 限流为 zone 级、pages.dev 无 zone;威胁模型匹配现有应用内限流);④ ADMIN_PASSWORD 强度残余风险经用户评估接受(standing offer:登录失败锁定);⑤ 新缺口入档:Supabase 免费档无自动备份,例行导出机制待建。
  • [2.4.6] - 2026-09-15

    Changed

  • 图片放大提示改为缩略图角标(2026-09-15 用户提议+竞品调研,真机确认通过):原「点击放大」游离在区块标题行右侧、与图片分离(非标准形态)。调研结论:大众点评/美团商家页、Airbnb(Show all photos)、小红书/朋友圈、以及本站灯箱参照的 PhotoSwipe/lightgallery 类画廊组件,均**不用游离文字提示**——放大 affordance 跟随图片本身,典型为图角半透明角标(放大镜图标或图标+短字)/计数角标/点按习惯。据此改为每张缩略图右下角半透明黑底角标(放大镜图标+「放大」,pointer-events-none 不挡点击),标题行只留「群友实拍 (N 张)」。
  • [2.4.5] - 2026-09-15

    Fixed

  • "是,不同门店"确认后仍添加失败(2026-09-15 用户反馈,确认链路断链):根因=服务端代理对 POST restaurants 有**独立同名去重(409)**,而前端确认结果(dupConfirmed)只绕过客户端检查、**不传服务端**,重试必再被 409(提示语"确认后重试"实为死路)。修:① 确认时 body 携带 _dup_override:true,代理见标记跳过去重并**转发前剥离该键**(防未知列插入);② 新增分店语义:**同名但区域不同直接放行**(select 增 district 比对),同店名不同区域不再误拦;③ 模糊包含匹配仍须确认(防手滑重复)。新增 tests/supabase-dedup.test.mjs 4 用例(同名同区 409 / override 放行且剥离键 / 同名不同区放行 / 模糊仍 409);顺带修路由 import proxy-guard 缺 .ts 扩展名致 Node 类型剥离模式无法解析。
  • 移动端选图后页面冻住(2026-09-15 用户反馈,新建餐厅/写评价 Both):根因=图片预览用 FileReader.readAsDataURL——对数 MB 相册图在**主线程同步 base64 编码**并生成数 MB 字符串,移动端低内存设备选图即冻住(与 CSP 无关,自查已排除:生产 /api/upload 带同源 Origin 返回 200、CSP img-src 已含 blob:/data:)。修:预览改 URL.createObjectURL(blob: 零拷贝引用),换图/移除/提交成功时 revokeObjectURL 防泄漏。顺带修两个自查发现的真缺陷:① 新建页上传失败原被**静默吞掉**(console.warn 后继续提交→图片丢失却提示成功),改为报错并中止提交;② 上传无进度无超时,慢网络表现为无限"提交中"——加按钮阶段文案「上传中...」+ 60s AbortController 超时(超时提示换小图/查网络),两流程(新建/评价)一致。
  • 追加生命周期与店删除联动(2026-09-15 用户反馈"删了 temp 店,最近追加没跟着删"):三层修复——① **硬删连带**:delete-restaurant 硬删成功后连带物理删除该店 contributions(contributions 建表无外键,须代码级联;评价由 DB 外键 ON DELETE CASCADE 连带);② **软删隐藏**:盘点列表不再列入软删店的追加(店还原后追加自然回来,语义与"删除=隐藏"一致);③ **孤儿兜底**:餐厅行已不存在的孤儿行标 orphan、后台红底标识待清理残留,店名从硬删审计快照(edit_log 的 hard_delete_restaurant 行 old_value)捞回**按真店名显示**(用户要求不叫"孤儿店"),支持手动批量删除(硬删连带清理万一失败的兜底)。单测 +2(生命周期过滤/孤儿标记/店名回收、硬删级联)。
  • Security

  • 保守 CSP 上线(审计 F11 后半):default-src 'self'; script-src/style-src 'self' 'unsafe-inline'(内联脚本/样式存量起步,nonce 改造另列专项);connect-src 'self'(站点无外部运行时资源,生产 Supabase 全走同源 /api/supabase 代理);img-src 'self' blob: data:(上传预览 object URL);object-src 'none'、base-uri 'self'、form-action 'self'、frame-ancestors 'none'。双处生效:SSR middleware(仅 PROD 构建——astro dev 前端直连 Supabase 属跨域,加 CSP 会误伤开发链路)+ 静态 _headers(预渲染路由)。离线验证:SSR/预渲染两类路由 CSP 头均在、四类页面 200;**待真机冒烟**(首页/详情/后台/添加/图片上传预览/编辑弹窗全功能过一遍)。
  • Changed

  • 审计 #10 后半·constants.ts 公共模块抽取:分类/标签/标准区域/推荐菜拆分正则(CATEGORIES/TAG_OPTIONS/STANDARD_DISTRICTS/DISH_SEP_RE/splitDishes)收敛到 src/lib/constants.ts 唯一事实源;edit-restaurant(再导出保持 import 路径兼容)、EditRestaurantModal、add.astro、server-data、contributions 的私留副本全部移除。顺带删除 validateField 中 recommended_dishes/notes 死分支("只增不改"后二者已不在 EditableFieldName,比较恒不成立,校验由 validateContribution 承担)。添加表单区域选项顺序统一为 constants 序(标准区 + 全区域)。
  • 审计 #9 后半·CI 加 astro check 类型门禁:历史 260 个类型错误归零——逻辑层 3 个真错误实修(ts2367 死分支、ts2345 Set 泛型与 slug undefined 兜底);三页 6 个内联 DOM 胶水脚本块(add×1、admin×1、[slug]×4)以 @ts-nocheck **显式登记类型债**(债清单见 HANDOVER,待脚本模块化后统一移除,非静默忽略);deploy.yml 新增 Type check 步骤(Test 后、Build 前)。新增 devDeps:@astrojs/check、typescript。
  • [2.4.4] - 2026-09-12

    Security

  • 审计遗留一批收口(F4/F6/F8/F11,2026-09-12):**F4** /img Content-Type 白名单收敛——不再优先信任桶内 httpMetadata.contentType(历史迁移对象若含 SVG/HTML 即同源存储型 XSS),仅 4 种图片类型可信(declared 去参数优先、扩展名兜底),其余降级 application/octet-stream + Content-Disposition: attachment;离线五形态探针全符合。**F6** 代理转发前无条件剥离客户端自带 apikey/Authorization/Accept-Profile/Content-Profile 再重注入 anon key(原"仅缺失时注入"透传伪造鉴权头/schema 头);真链路验证恶意头被替换/剥离。**F8** 旧管理密码明文从 HANDOVER 删除(口令早已轮换,git 历史字面量随之失效)。**F11** HSTS max-age=31536000; includeSubDomains 双处生效(静态 _headers + SSR middleware;不加 preload,注册不可逆)。**CSP 缓办**:大量内联脚本需 unsafe-inline 起步或 nonce 改造,单独一轮评估+真机冒烟后再上。
  • 独立复审一轮(2026-09-11,无高危;2 中 5 低,修 6 留 1):① **中**·去重自排除类型错位(c.id !== excludeId,bigint 字符串 vs Number 恒真)致本人仅大小写/空格差异的自改被自己旧值误判重复 → String 化比较 + 回归单测;② **中**·硬删(餐厅/评价/贡献)无审计,批量硬删不可追查 → 加快照审计(删前 fetchSnap 取快照、删后写 edit_log approved,best-effort 不阻断已执行的物理删除);③ **低**·追加可向不存在餐厅 id 写孤儿行 → 追加前校验存在(软删隐藏店仍放行,保生产自测约定);④ **低**·GET /api/contribution 已无调用方且 deviceToken 走查询串有日志泄露面 → 端点下线、限流简化为仅写;⑤ **低**·sf_device_token cookie 缺 Secure(现承担 SSR 归属判定)→ 补;⑥ **低**·admin/auth/edit-restaurant 同源端点多余 ACAO:* → 移除(F7 收口)。**留**:审计写失败但主写成功的半提交提示文案(上轮已改清晰,可接受)。白名单核对:本会话无新增未登记浏览器端 Supabase 调用;XSS/鉴权/注入面复核闭环。
  • Added

  • 后台多选批量操作:「已隐藏餐厅」「已隐藏评价」「最近追加」三个列表加多选 checkbox 与批量条(全选/已选计数/批量还原/批量彻底删除/批量删除),记录多时可统一操作。批量串行调用既有单条管理端点(复用鉴权与审计),汇总成功/失败后刷新列表;checkbox 的 change 走 document 委托,列表重渲染免重绑。
  • 追加内容发现机制(审计留痕 + 后台「最近追加」模块):此前群友追加只进 contributions 表——后台不可见、无审计、无提醒,发现全靠人工撞见。本次:① 追加成功后 addContribution 写一行 edit_log(status=approved 表示自动生效,不进「待处理」列表,不改变产品行为);② 新增管理端盘点端点 /api/admin/list-contributions(POST 读、token 鉴权、service_role join 店名;contributions 不在代理读白名单内,盘点必须走服务端);③ 后台新增「最近追加(近 20 条)」模块:店名/字段/内容/署名/时间列表,疑似测试内容(temp/test/纯数字/超长)标黄,每行一键删除(复用 /api/admin/delete-contribution)。新增端点单测与追加端点级单测;离线 e2e:未登录盘点 401、登录盘点返回追加+店名、追加留 approved 痕。
  • Fixed

  • 盘点店名/加载失败三连修(Prefer: undefined → #id → 加载失败):list-contributions 店名获取历经三次真机事故:① 两请求 join 时 postgrestHeaders 缺省 prefer 写入 'Prefer: undefined' 头(已修:有值才写);② 真机店名全显示 #id(join 失败);③ 改嵌入式查询(select=*,restaurant:restaurants(id,name))后,因**生产库 contributions 无外键**嵌入 400 → 整模块"加载失败"。终态为**三级容错**:嵌入单请求(需 FK)→ 回退顺序两请求(先 contributions 再 id=in.(...) 取店名,String 键 join 防 bigint 字符串错位)→ 店名取不到回退 #id,列表永远可加载。单测覆盖嵌入成功与嵌入 400 回退两路径。另提供可选外键 DDL sql/add_contributions_fk.sql(含孤儿预检语句、ON DELETE CASCADE 顺带清孤儿追加;执行后嵌入路径生效,非必需)。
  • 追加审计缺口填补 + 死代码 applyAppend 移除:CHANGELOG 2.4.3 所述"edit_log 审计"实际指向死代码 applyAppend(自 acdbee8 改走 contributions 后生产链路无人调用),其单测造成"追加有审计"的假象,掩盖了生产链路完全无留痕的缺口。本次审计改为在 addContribution 内真实写入;applyAppend 及其专属符号(APPENDABLE_FIELDS/AppendableFieldName/normalizeEntry)移除,墓碑注释指向真实链路;其单测改端点级(tests/append-restaurant.test.mjs,直接断言生产链路 /api/append-restaurant → contributions + edit_log)。
  • [2.4.3] - 2026-09-11

    Fixed

  • 本人补充条目改 SSR 判定归属(根治真机"无法改/删自己补充的内容"):此前弹窗 chip 的本人/他人区分依赖客户端拉取 GET /api/contribution 后重建 DOM,真机该拉取偶发失败即退化为服务端兜底的全灰只读 chip,本人条目恒不可改/删(服务端 curl 带同 token 返回 mine:true 正常,根因在客户端拉取环节)。现改为 **SSR 读同源 cookie sf_device_token → LIKE_SALT 加盐哈希 → 与 contributions.device_hash 比对**,弹窗在服务端即区分本人/他人;客户端不拉取归属,彻底移除 loadContribs / window.__loadContribs。归属判定失败(无 cookie / 无盐)时全部按他人只读渲染,不影响详情页主体。真机验收同期两处修正:①**"点不了"**——改/删绑定脚本位于弹窗 DOM 之前,按容器绑定时容器尚不存在致按钮无事件,改 **document 级事件委托**(与 DOM 顺序无关);②**视觉**——按反馈弃橙色填充 chip(突兀),补充内容改**列表行**(内容 + 署名),本人行右侧低调灰色下划线「改/删」、他人行无按钮。改/删成功后整页 reload 由 SSR 重渲染。
  • 弹窗贡献条目可见性加固(1a850bb、d63bcbb):真机发现打开编辑弹窗时补充条目偶发不显示(客户端拉取归属失败即空白)。改为**服务端先把贡献渲染成灰 chip**(客户端拉取成功后再升级为"本人橙色+改/删",失败也保留灰 chip);并将 /api/contribution 读/写限流分桶(读 30/分、写 10/分),降低打开弹窗的读被写流量误限 429 的概率。
  • Added

  • 追加贡献独立成行 + 设备归属(acdbee8):响应群友"手滑打错不能全靠管理员"。补充的推荐菜/备注不再并入基线文本,而是存入 contributions 表(sql/add_contributions.sql:同店同字段内容唯一 + RLS deny-by-default)并记录追加设备的加盐哈希。**已上线并验证**:追加、去重、详情/首页合并展示、弹窗服务端灰 chip 可见(1a850bb)、读/写限流分桶(d63bcbb);生产 e2e(隐藏店 388,curl 层)追加→重复拒→本人改→他人改拒→他人删拒→本人删→列表空 全符合。**本人改/删自己那条**:初版依赖客户端拉取归属升级 chip,真机不可用;已改 SSR 判定归属修复(见本节 Fixed「本人补充条目改 SSR 判定归属」),待真机验收。
  • 推荐菜/备注改「只增不改」(群友反馈 Wooden_Fish:人多后覆盖式编辑易互相破坏内容)**:EDITABLE_FIELDS 移除这两项(服务端强制不可整段覆盖);新增 applyAppend(追加 + 归一化去重 + 一次一道菜/一行备注 + 长度上限)与 /api/append-restaurant(每 IP 限流 + service_role + edit_log 审计)。编辑弹窗中两字段改为「现有内容展示(推荐菜 chips / 备注行)+ 补充输入」,不再整段 textarea。管理后台新增「整段编辑推荐菜/备注」(搜索→载入→保存,/api/admin/edit-fields,token 鉴权 + 审计 approved),保证管理员仍具整段整理/纠错能力。新增 tests/append-restaurant.test.mjs(5 用例),全套 78 单测过。**生产 e2e(对已软删隐藏测试店):追加生效、重复追加被拒、备注行追加、edit_log 留痕;群友覆盖式提交被服务端拒绝("没有要修改的字段")。
  • [2.4.2] - 2026-09-11

    Changed

  • ⓘ 说明改为行内展开(移动端友好):弃用侧弹深色气泡——窄屏朝右弹被右缘裁切(表单那条截断)、触控目标过小、hover 不适用触摸端。改为点 ⓘ 在**卡片内行内展开**一条全宽浅灰说明(点按切换、点别处或点说明本身关闭、同一时刻只开一条),触控目标放大(p-1.5 + w-5/w-4 图标,≈32px)。三处:吃过 / 评价区平均分 / 写评价表单评分。依据移动端 tooltip 最佳实践:tap-to-toggle 为主触发、目标 ≥24px、避免裁切与遮挡、一句话级说明用行内展开而非底部半屏弹层(后者对单句过重)。
  • 评价区平均分行对齐修正(333840e):星星 / 分数 / ⓘ /(N 人评分)改 flex items-center 同一排垂直居中 + leading-none 收紧(原 inline 拼接致 ⓘ 基线下沉错位)。
  • [2.4.1] - 2026-09-11

    Changed

  • 点赞更名「吃过」+ ✓ 图标 + 说明拆为双 ⓘ 气泡:按钮由「👍 赞过」改为「✓ 吃过」(语义更贴"去过/认可",计数读作"N 人吃过");原单条常驻说明移除,拆为两条 ⓘ 点按/悬停气泡——点赞旁「吃过 = 去过并认可这家店的人数」、信息卡评分旁「评分 = 群友打星(1–5 星)的平均分」,默认界面零常驻说明文字(NN/g info-tip 准则)。首页卡片角标(✓N 吃过)、添加页重复出口(去标「吃过」)、群友公告措辞同步更新。
  • ⓘ 气泡视觉定稿(6cee85e→1064dbb):气泡内容改**单行**(whitespace-nowrap+w-auto,去掉固定宽度导致的折行);共**三处** ⓘ(吃过 / 评价区平均分 / 写评价表单"点击评分"旁)且**统一朝右弹**(left-full 垂直居中、左箭头);评价区平均分 ⓘ 置于分数紧后、表单 ⓘ 紧跟"点击评分"——触发器贴屏幕右缘时朝右弹会溢出手机屏,故调整位置;平均分 ⓘ 的注入在评价区客户端渲染的平均分标记内(生产 restaurants.rating 列全空、信息卡分标签不渲染,故挂评价区才是群友真能看到的位置)。
  • [2.4.0] - 2026-09-11

    Added

  • 店铺点赞(认可/去过)上线(335bfb4):无账号"一设备一票"点赞。新增 likes 表(sql/add_likes.sql:unique(restaurant_id, device_hash) + RLS deny-by-default + 索引)与 /api/like 端点(服务端 LIKE_SALT 加盐哈希设备 token、service_role 写、点赞 Prefer: resolution=ignore-duplicates 幂等、每 IP 限流、缺盐 fail-closed 500);详情页点赞按钮(可撤销)+ 计数 + 注明("赞过=认可或去过人数;打分看评价星级"),首页卡片 meta 行加「👍N 赞过」(SSR service_role group-by 实时计数,不存易漂移列)。点赞**纯展示,不进任何排序/筛选/榜单**(用户明确拒绝排行榜)。重复提交(409/名称预检)新增「去点赞这家 / 去写评价」出口,把被拦下的重复推荐转成对已有店的支持。新增 tests/like.test.mjs(7 用例)。**生产端到端已验**:基线 200、点赞 count+1、同设备重复点不重复计数(唯一约束生效)、取消赞回基线、首页角标渲染、测试数据净零。
  • Security

  • 代理 PATCH 加 query 过滤白名单(复审 F5,e34e6c3):B3 白名单此前只校验 PATCH body 为 {deleted:true},未校验过滤条件——构造 PATCH /rest/v1/reviews?id=gt.0 + {deleted:true} 一条请求即可经代理软删全站评价/餐厅。现要求 PATCH 的 query 恰为单主键等值过滤(id=eq.<整数>,或删店级联软删评价用的 restaurant_id=eq.<整数>),其余(gt/gte/无过滤/多条件/or=(...)/非整数/非主键列)一律 403。核实前端三处 update 调用的过滤列后才收紧,不误伤正常删除;新增 F5 拦截用例,全套 66 单测通过。线上回归:合法过滤 204(0 行受影响)、批量过滤 403、未删数据完好、删除功能正常。
  • Changed

  • ai_note 解析/合成统一(审计 #10 前半):新建 src/lib/ai-note.ts(parseAiNote/mergeAiNote + AiNote 类型),消除此前 4 份重复实现——edit-restaurant.parseAiNote(改为再导出,对外 import 路径不变)、server-data.parseNote(变体,返回 {}/undefined 且 p 未校验有限性)、[slug].parseDetailNote(与规范版逐字节相同)、index.astro 内联 delivery 解析。规范行为:解析失败/缺字段一律回落具体默认值 {tags:[],avgPrice:null,delivery:false};p 仅接受有限数字(NaN/Infinity→null,比旧 server-data 变体更严);d 仅接受布尔。新增 tests/ai-note.test.mjs(10 用例),全套 65 单测通过。**#10 后半(constants.ts:CATEGORIES/区域清单/去重归一化)待下一轮。**
  • 更新公告页内新增「技术日志」入口模块:/changelog 顶部说明语下方、时间线上方加一张与版本卡片同款风格的入口卡(代码专属图标 +「技术日志」+「面向开发的技术变更记录」+ 进入箭头),从群友版更新公告可直达开发版技术日志。同步移除首页页脚的「技术日志」链接,避免入口冗余(index.astro)。
  • CI 加单测门禁(审计 #9 前半,dbc7252):deploy.yml 在 build 前新增 npm test 步骤,55 用例任一失败即中断部署防回归;node-version 20 → 24(测试直接 import .ts,依赖 Node 默认类型剥离 23.6+,与本地一致);package.json 补 engines.node >=23.6.0。已验:Actions run 全绿(Test/Build/Deploy 均 success)。astro check 门禁暂缓——会暴露既有类型缺口(如 RestaurantCard props)致 CI 红,需单独一轮类型修复后再上(HANDOVER 待办)。
  • [2.3.1] - 2026-09-10

    Security

  • Supabase 反向代理加最小权限白名单 + 限流(审计 #2/#3 之 B3,363b61e+9a6cd50):此前 /api/supabase/[...path].ts 透传任意路径 + 注入 anon key,等同开放中继,任何人可匿名调 admin_* rpc、访问 /auth、/storage、任意表或 DELETE。新增 src/lib/proxy-guard.ts(decideProxyAccess 纯函数)收口为白名单:仅放行 GET restaurants/reviews/edit_log、POST restaurants/reviews + /rest/v1/rpc/increment_review_count、PATCH restaurants/reviews(仅软删 {deleted:true});其余(admin_* rpc、/auth/*、/storage/*、其它表/rpc、DELETE/PUT、非软删 PATCH)一律 403。判定前置于 env 检查,未配上游也不沦为开放中继。加每-IP 限流(读 120/分、写 30/分,超限 429 + Retry-After;内存级,强限流仍需 #5 的 CF Rate Limiting)。新增 tests/proxy-guard.test.mjs(13 用例覆盖放行/拦截矩阵),全套 55 单测通过。
  • **修正**:supabase-js 的 rpc 实际请求路径为 /rest/v1/rpc/(非 /rpc/);首版白名单误写导致 increment_review_count 被拦成 403(写评价后计数 +1 静默失败;因首页评价数已改实时聚合,用户可见数不受影响)。线上回归发现后即修正,并补测试守卫两种路径形式。
  • [2.3.0] - 2026-09-10

    > 本版本为一次安全/缺陷修复批次(对应 HANDOVER「技术债与改进审计」)+ 图片显示根治。回退基线 tag v2.2.0,每条修复独立 commit、可单独 git revert。

    Security

  • 管理后台回收站收口服务端(审计 #2/#3,方案 B 之 B1+B2):还原/彻底删除改走服务端 /api/admin/restore-restaurant、/api/admin/restore-review(service_role + HMAC token);admin.astro 移除客户端口令 / sessionStorage / admin_* RPC 调用(anon 可执行、返回布尔的口令 oracle 客户端入口已除);delete-restaurant/delete-review 复用为硬删并清理过期 Storage 逻辑。线上已验:4 个端点无 token 一律 401。(B3 代理白名单尚未做,见审计待办)
  • /api/upload 加固(审计 #7):同 IP 限流(10 次/分)+ 文件头魔数校验,不再轻信客户端声明的 MIME。
  • /api/auth 登录限流(审计 #3 部分):同 IP 5 次/分,收敛在线爆破面。
  • 安全响应头(审计 #15):public/_headers(静态页)+ src/middleware.ts(SSR 页),补齐 CSP/X-Frame-Options 等,/admin 不再裸奔于点击劫持。
  • Fixed

  • 详情页数据源方向修正为 DB 优先(审计 #1):JSON 仅兜底,查询加 deleted=eq.false,notFound 返回真 404。根治「群友改动被静态 JSON 旧值静默盖回」及「编辑任意字段即清掉已标可外卖 d:true」。
  • 编辑弹窗可清空字段(审计 #8):推荐菜/备注/人均价可清空(区域必填除外),改为「与当前值不同即提交」。
  • 图片显示根治(同源代理):改走本站 /img/ 代理,直接从 R2 binding(IMAGES)读对象返回、Cloudflare 边缘缓存;弃用 pub-*.r2.dev 公共开发域名直链(curl 200 但浏览器裂图)。新增 src/pages/img/[...key].ts(key 扁平校验防路径穿越、X-Content-Type-Options: nosniff、immutable 长缓存);upload.ts 返回 /img/、[slug].astro 的 proxyImage/proxyImg 统一改写,兼容历史四种来源,无需数据迁移。
  • 「西塔」区域归属修正(审计 #13):经调研确认属和平区(原误标铁西区),server-data.ts SYNONYMS 及对应单测同步。
  • Changed

  • 详情页 ai_note 展示优化(审计 #12):原始 JSON → 分类 chips + 🛵 可外卖徽标。
  • 死代码清理(审计 #11):移除未接线的批量删除防线(checkDeleteAllowed/incrementDeleteCount/DEL_THRESHOLD)、decodeServerError、window.__applyFilters、恒等 catMap、过期注释,收敛需维护的鉴权面。
  • **后台「待处理修改」自动隐藏已删除餐厅**(软删/硬删/孤儿)的记录,避免对不存在店做采纳/撤销。
  • .gitignore 忽略 .wrangler/(本地 miniflare/wrangler 开发状态目录)。
  • [2.2.0] - 2026-09-09

    Added

  • 新增「可外卖」独立筛选:首页筛选条新增「🛵 可外卖」开关,作为独立维度与分类(多选 OR)/区域做 **AND 叠加**(可筛"能外卖的烧烤店"),刻意不并入分类多选;可外卖店卡片显示绿色「🛵 外卖」角标。/add 添加餐厅可直接勾选"支持外卖",详情页"编辑店信息"可修改,改动同样走 edit_log 审计、后台可采纳/撤销。外卖标记存于 ai_note.d(布尔),无需数据库迁移。
  • 新增「酒吧」分类:添加餐厅、编辑店信息、首页分类筛选、卡片 emoji(🍸)全链路支持"酒吧"分类。
  • 人人可改餐厅信息 + 审计:详情页新增"编辑店信息",普通群友可改主分类/区域/人均价/推荐菜/备注;每笔改动写入 edit_log 审计(改前/改后值、改动者、时间),管理后台 /admin 按餐厅分组逐笔"采纳/撤销"。后台仅在有待处理改动时显示修改记录,平时保持干净。
  • 分类多选:首页分类筛选与编辑弹窗均改为多选勾选(第一个为主分类),可同时归入多个分类筛选(如 火锅+自助)。
  • 人均价显示统一:新增 formatPrice,卡片与详情页一律显示"人均X元",兼容纯数字/区间/文案等旧数据。
  • Changed

  • 更新日志重构为独立「更新公告」模块:新建 src/data/updates.json(面向群友的友好文案,结构化)+ src/components/UpdateTimeline.astro(时间线 + 版本卡片 + 新增/优化/修复彩色标签);changelog.astro 改为渲染该模块,不再正则解析 CHANGELOG.md 原文(路由仍 /changelog,标题改「更新公告」)。首页头部新增「📢 更新公告」入口按钮 + 未读红点(localStorage.sf_updates_seen 记录已读版本,进入页面即标记已读)。**用户侧文案维护在 updates.json,技术细节仍记于本文件。**
  • 新增「技术日志」页(/tech-log):首页页脚入口,渲染 CHANGELOG.md 的技术变更记录;与群友版「更新公告」(/changelog,首页头部 📢)分层——一个给开发看技术细节,一个给群友看友好更新。
  • 添加餐厅分类改为多选勾选:/add 页分类由单选下拉改为多选勾选(第一个为主分类),与编辑弹窗/首页筛选一致。
  • 卡片分类标签统一:首页卡片上主分类与附加分类统一为浅灰 pill(主分类加粗),消除多选后"灰色 + 橙色"混杂,视觉更协调。
  • 删除餐厅弹窗文案改为群友友好:确认与结果弹窗去掉"软删除/管理后台处理"等内部措辞,改为"要删除「XX」吗?/已删除,我们会尽快处理"。
  • 移除编辑弹窗的"属性标签"编辑项与人机(算术)校验;保留每 IP 限流防刷。
  • Fixed

  • 为 5 家店名铁证复合店补多标签:三姐炸串/淇淇炸串QQ鸡架(+小吃快餐)、今江烧烤冷面/金福顺烤肉冷面店/韩风烤牛肉冷面店(+面食粉类),保留原人均价,并写 edit_log 审计(后台可撤销)。
  • [2.1.2] - 2026-08-14

    Fixed

  • 修复添加餐厅 / 写评价提交失败:表单图片上传分支曾因局部变量名遮蔽外层表单引用,导致提交时读不到表单数据而失败;已修正,添加餐厅与写评价均可正常提交。
  • 添加餐厅分类补上"自助":分类下拉补齐"自助",可在分类里直接选自助。
  • Security

  • 管理后台密码轮换:admin_* 数据库函数密码已更换(旧值作废),新密码不落仓库明文,存于 Cloudflare secret / 安全记录。
  • [2.1.1] - 2026-08-14

    Fixed

  • 首页区域 tag 归一化:区域筛选只保留 9 个标准区 + 全区域 + 其他;多区店同时计入其所涵盖的每个区;口语/垃圾值(随便/全球/有很多店 可以就近吃/西塔/全沈阳 等)归入其他/全区域/实际区,不再出现脏 tag。
  • 图片改由 Cloudflare R2 托管:27 张历史图片从 Supabase Storage 迁至 R2 公开桶,详情页与评论图片统一改写为 R2 直链,绕开 supabase.co 国内不通与 bot 拦截;新增/评价上传改走 R2,新增图片同样进 R2。
  • Added

  • src/pages/api/upload.ts:服务端 R2 图片上传端点(校验类型/≤8MB,返回公开直链)。
  • [2.1.0] - 2026-08-14

    Changed

  • 首页彻底改为数据库驱动(SSR):首页由静态烘焙改为 SSR,每次请求实时从 Supabase 拉取全部未删餐厅与未删评价,分类/区域/Tag 计数、评价数、收录总数全部以数据库为准;新增餐厅/评价/改标签后刷新即更新,无需重新部署。静态 restaurants.json 仅作数据库不可达时的首屏兜底。
  • 评价数改用真实数据:首页评价徽章与「评价最多」排序改为按 reviews 未删记录的实时聚合计数,不再读取可能失效的 review_count 字段。
  • Added

  • 软删除 + 回收站:删除餐厅/评价不再物理删除,仅前台隐藏、数据保留;管理后台 /admin 可查看被隐藏数据,支持还原或彻底删除。
  • 管理后台密码登录:进入后台需输入管理密码,通过后操作无需再输密码。
  • 详情页平均分:有带星评价的餐厅显示"平均 X.X 星(N 人评分)",无评分的不显示。
  • Fixed

  • 评价徽章:此前静态 reviews.length 恒为 0 导致徽章不显示,已随首页 SSR 根治。
  • 首页卡片统一尺寸:卡片内容固定等宽高并 overflow-hidden,消除长短不齐。
  • 详情页图片:统一处理相对路径(/storage/...)与完整 URL 走代理,修复部分门店图片 404。
  • 详情页不存在 slug:返回友好"这家店不见了"而非报错。
  • 数据:天天鲜小肥羊标注为 火锅 + 自助(JSON 与数据库同步)。
  • Security

  • **删除/编辑收口到服务端** /api/admin/*:HMAC token(HttpOnly cookie)+ service_role + 每 IP 限流。
  • 前端密钥收敛:统一走 src/lib/supabase.ts 的 PUBLIC_* 注入;移除各页硬编码 publishable key。
  • RLS 收紧:撤销 restaurants/reviews 公共 UPDATE/DELETE 策略;普通用户仅可软删。
  • Storage 上传加固:前端校验 JPG/PNG/WebP/GIF、≤8MB、安全文件名。
  • 新增 CI(构建 → 密钥扫描 → 部署)、scripts/scan-secrets.sh、.env.example。
  • [2.0.0] - 2026-08-14

    Added

  • 多标签分类系统:每家餐厅支持主分类 + 跨分类属性标签(自助/清真/炸鸡),筛选面板按标签过滤。
  • 人均价格展示:调研美团/大众点评数据,为 123 家餐厅添加 avgPrice,卡片显示"人均 XX 元"。
  • 重复门店防重:添加页面实时查重 + 提交前二次校验;API 代理层 POST 拦截返回 409。
  • 评分功能(代码就绪,暂未启用):详情页星级评分交互 + 表单集成。
  • Changed

  • 分类体系精简:18 类 → 12 类,遵循"按吃法/场景分、不按菜品分"原则(烤串/烤肉/烧烤 → 烧烤,小吃/快餐简餐 → 小吃快餐,东北菜/铁锅炖 → 东北菜,东南亚/新疆并入西餐/烧烤/小吃快餐)。
  • 卡片 UI 统一:grid 等高 + 附加标签 pill 展示。
  • 数字同步:header / 筛选区 / footer 三处收录数实时同步。
  • Security

  • XSS 防护:所有动态内容经 esc() 转义(index / admin / 详情页)。
  • 管理密码改为服务端校验(/api/auth 端点),移除前端明文密码。
  • CORS 白名单限制为项目域名。
  • 评论删除增加频率计数器保护。
  • 内联 onclick 改为 data-attribute 事件绑定。
  • Fixed

  • 图片代理:Supabase Storage 请求补充 Auth 头,修复 403。
  • **41 家重复餐厅合并**(含评论迁移),保留 3 家合理分店。
  • loadDynamic() 动态加载不再覆盖静态卡片的分类数据。
  • PASTA BAR 手工意面分类修正(其他菜系 → 西餐)。
  • Changed

  • 数据:210 家餐厅完成新分类重映射;123 家餐厅有人均价格数据(中位数 50 元);Supabase ai_note 列存储结构化 tags + avgPrice JSON。
  • Removed

  • src/styles/global.css(未引用的死代码)。
  • src/lib/supabase.ts(未引用的死代码)。
  • [1.0.0] - 2026-08-11

    Added

  • 首页静态渲染 + Supabase 动态增量加载。
  • 餐厅详情页 SSR + 评论系统。
  • 管理后台(登录/删除餐厅/删除评论)。
  • 添加餐厅表单 + 图片上传。
  • Cloudflare Pages 部署 + Supabase PostgreSQL。
  • 筛选面板(分类/区域/排序)+ 搜索。
  • 状态持久化(sessionStorage + URL 参数)。
  • [Unreleased]: https://github.com/voidleap-dev/xiaozhong-dianping/compare/v2.1.2...HEAD

    [2.1.2]: https://github.com/voidleap-dev/xiaozhong-dianping/compare/v2.1.1...v2.1.2

    [2.1.1]: https://github.com/voidleap-dev/xiaozhong-dianping/compare/v2.1.0...v2.1.1

    [2.1.0]: https://github.com/voidleap-dev/xiaozhong-dianping/compare/v2.0.0...v2.1.0

    [2.0.0]: https://github.com/voidleap-dev/xiaozhong-dianping/compare/v1.0.0...v2.0.0

    [1.0.0]: https://github.com/voidleap-dev/xiaozhong-dianping/releases/tag/v1.0.0