Commit Graph
41 Commits
Author SHA1 Message Date
WorkBuddy 3c2b967274 release: freeze 2026-10-15 validated release baseline
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Canceled after 0s
CI/CD Pipeline / E2E (live stack) (push) Canceled after 0s
CI/CD Pipeline / Backend Build & Test (push) Canceled after 0s
CI/CD Pipeline / Admin Web Build (push) Canceled after 0s
CI/CD Pipeline / Mini Program Compile Check (push) Canceled after 0s
CI/CD Pipeline / Migration Validation (push) Canceled after 0s
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Canceled after 0s
2026-10-15 09:47:31 +08:00
WorkBuddy d3735e3afa feat(matchmaker,contactauth): 受控联系授权机制立项落地(OQ-MATCHMAKER-003 / DEC-MATCHMAKER-003 B1 例外通道)
后端:
- V056 matchmaker_contact_authorizations 表:channel CHECK(phone/offline_meeting/full_contact)、六态状态机(pending_approval→pending_consent→active→expired/revoked, →rejected)、部分唯一索引 uq_mm_contact_auth_active 防同红娘-同用户-同渠道多 ACTIVE、双态惰性收敛(active 过期 + pending_consent 超同意窗口)
- ContactAuthorizationConfig 五 canonical key(matchmaker.contact.*):enabled 代码缺省 false fail-safe(yml 显式 true)/approver-authorities 双岗并列/consent-deadline-hours 72/default-validity-days 7/retention-days 365,全部 DEFAULT+CONFIG 待产品+法务终审
- ContactAuthorizationPolicy.assertContactAllowed(404 化 deny 先审计后 404) + canContact 徽标查询
- ContactAuthorizationService:request(须有效分配+purpose 必填)/approve(权限码动态判定∈配置+组长组织范围)/consent(用户本人,仅 pending_consent)/decline/revoke(reason 必填)/disclose(唯一披露出口:enabled fail-safe→assertActiveAssignment→assertContactAllowed→仅返回 phone/disclosedAt/authorizationId 三键,每次落 SensitiveAccessLog user_phone_disclosure,phone 不落应用日志)
- 双控制器:运营端 /operator/matchmaker/contact-authorizations + C 端 /contact-authorizations(UserPrincipal 归属校验,独立端点无预同意参数),不碰 SecurityConfig

守卫翻转(机械锁强制复审机制按设计生效):
- MatchmakerContactGuardTest 由「锁不存在」翻转为「锁新契约」五条:回显白名单类收敛/端点前缀收敛(受治理三包)/到期语义/consent 独立性三端扫描(后端+miniapp+admin-web,修复原只扫后端的预同意参数逃逸缺口)/ReferralService 白名单与无 @Scheduled 保留
- run-backend-contracts CURATED 18→20

前端:
- miniapp 新页 pages/contact-consent(目的/渠道/窗口明示+独立同意拒绝按钮无预勾选,app.json 第 64 页) + 红娘工作台申请/列表/撤销
- admin-web「联系授权审批」tab(审批岗队列+本人授权撤销,枚举码中文映射)

验证:mvn verify 895/895(3 skip)、UI 契约 24/24(64 页)、后端契约门禁 228/CURATED 20、小程序真编译 87 WXML 0 失败;负向注入 5(工程师)+2(QA 不同落点)判红→还原复绿;QA 抓 disclose 键数收敛断言缺口已补并回归;QA fresh eyes PASS 0 P0/P1
2026-10-11 13:08:02 +08:00
WorkBuddy 861c78efb5 feat(matchmaker): ZC-MM-002 A1 服务对象分配建模(DEC-MATCHMAKER-002 LOCKED 落地)
数据库(V053/V054):
- 新表 matchmaker_assignments:独立分配关系(assigned_by/reason/scope/
  expires_at/ended_at/end_reason),状态机 active/ended/revoked/transferred/
  expired(CHECK 白名单),部分唯一索引防同用户多 ACTIVE 主责分配,
  存量进行中服务单幂等回填
- 新角色 matchmaker_leader(014) + 新权限 matchmaker.team.assign(10000000-…-023);
  组长授 view/service/team.assign(不给平台级 matchmaker.assign)

后端:
- ServiceAssignmentPolicy 有效分配判定器(读前惰性过期,失效分配即时拒绝)
- 分配/转分配/撤销/结束四操作:状态机非法转换拒绝、理由必填、
  同用户主责冲突拒绝;组织范围校验(team.assign 同组织,NULL=NULL 视为
  平台直属,技术默认安全值待治理登记);transfer 同时校验原持有人与新目标范围
- 全操作审计 MATCHMAKER_ASSIGNMENT_CREATE/TRANSFER/REVOKE/END,
  operatorId=当前操作者,afterData 仅记摘要(无敏感内容)
- 删除认领端点 POST /services/{id}/claim(A1 禁止普通红娘认领,MM-AUTH-004=404)
- GET /pending 收敛为分配权持有者(assign or team.assign);
  dashboard today/completed 改本人口径,inProgress 口径修正 'processing'→'in_service'
- recommend/followup/complete/detail 归属判定接入判定器,拒绝走
  DataScopeGuard(自动 DATA_SCOPE_DENIED 审计),不再裸 IllegalStateException;
  assignService 与创建分配同事务联动;删除死查询 findAssignedTo

前端:
- admin-web 分配弹窗去硬编码假数据(operator_wang/chen)→ assignable-matchmakers
  真实接口(仅 id/name/organizationId,无联系方式)+ 理由必填;
  队列 tab 按 assign/team.assign 显隐;新增「服务对象分配」tab(转分配/撤销/结束)
- 小程序红娘工作台:删除接单入口,数据源切 GET /operator/matchmaker/assignments/my,
  枚举码不上屏(中文映射),空 serviceId 不跳转

测试:
- MatchmakerAssignmentA1Test 14 用例(MM-AUTH-001~004、MM-AUDIT-001、
  MM-LIFECYCLE-001、组长跨组织拒绝、NULL=NULL 平台直属边界、transfer 原持有人
  范围负向、/pending 403+审计),负向注入自证有效
- MatchmakerAssignmentPermissionTest 3 用例(端点权限契约 + claim 残留扫描)
- MatchmakerServiceTest 补 assignment 状态/scope 白名单守卫;迁移基线 LATEST=54
- 验证:定向 38/38 全绿;UI 契约 24/24;小程序 wcc/wcsc 真编译 0 失败
2026-10-09 22:46:32 +08:00
WorkBuddy ca0db57f24 chore(release): 冻结 v1.1.0 版本
- 同步版本号:VERSION / backend pom / admin-web / miniapp 均升至 1.1.0
- 合并本轮代码审查修复(详见 docs/CODE-REVIEW-2026-09-24.md):
  - 后端:微信登录真实实现 RealWechatLoginProvider、实名核验真实实现 RealIdentityVerificationProvider(本地强校验 + 可配置第三方 + fail-closed/fail-fast)
  - 照片直传落库端点 POST /me/photos/confirm(fileKey 归属校验 + 对象存在性校验后 savePhoto)
  - 刷新 Token Redis TTL 修正(getExpirationMs()*2 的 4h → refreshExpirationMs 的 14 天)
  - 管理端 token 改为 HttpOnly Cookie + silent-refresh,移除 localStorage 明文存储与 30+ 处手工注入
  - 小程序:红娘接单鉴权头错位修复、serviceType 中文映射、生产域名可配置
- 隔离本地产物(.workbuddy/、*.bundle、*.dump、secrets、application-dev.yml 等已被 .gitignore 覆盖)

未验证项(打标签 ≠ 生产发布):
- 后端编译 / 单测未在本环境执行(Maven 慢、Docker DNS 受限)
- 管理端 登录→刷新→登出 端到端、小程序真机走查待人工验收
- 生产部署需注入 WECHAT_APP_ID / WECHAT_APP_SECRET、VERIFICATION_API_URL / VERIFICATION_API_KEY 与小程序生产域名
2026-09-30 12:10:00 +08:00
WorkBuddy f4e00b4d95 feat(miniapp): 首页 4×2 入口宫格图标改版为参考图 2 的渐变砖 + 白色图形
参考图 2 的视觉语言是「高饱和渐变圆角砖 + 白色描画图形 + 主标题/副标题双层文字」,
原实现是「浅色底砖 + 彩色线性 PNG + 单行标题」,两者差异较大,本轮按参考图重做。

- app.wxss:新增 8 个 --gradient-entry-* 渐变 token 与 --mini-entry-shadow。
  白图形的判据取**非文本图形 3:1**(WCAG 1.4.11);渐变是斜向的、图形又跨在两色之间,
  故以「较亮端」为准逐色实算(3.00~3.02:1,较暗端 3.54~4.08:1),实算值写在 token 注释里。
- index.wxss:.entry-icon 由 72rpx 浅色砖改为 96rpx 渐变砖(radius 28rpx + 落影);
  图形改用 data-uri SVG 背景层(几何图形统一 24×24 viewBox),砖面走 --gradient-entry-*;
  新增 .entry-sub(12px muted 副标题);删除已无用的 .entry-icon-image。
  小屏 @media(≤340px) 同步收紧,副标题不降到 micro —— micro 在本项目是「仅角标/装饰」档位,
  这行字承载语义,不属于装饰。
- index.wxml:宫格项去掉 <image>,图标改为纯 CSS 砖;补 aria-label 承接图形语义。
- index.ts:tone 名与色相语义对齐(rose/orange/violet/blue/purple/pink/green/amber),
  并补 subtitle;entry 不再持有 iconAsset(图标不再是图片资源)。
- preview-miniapp.mjs:首页 mock 同步 tone/subtitle,预览才能看到真实砖面与副标题。

渲染态实测(无头浏览器 + CDP,非真机渲染):
- 8 个 .entry-item 均渲染出 label + subtitle;.entry-icon 计算样式 48×48px / radius 14px /
  background-image 含 svg 且为两层(图形 26px + 渐变铺满);label 14px/600;sub 12px #7D839B。
- 证据截图:artifacts/ui-redesign/miniapp/home-entries-page.png 与 home-entries-grid-zoom.png。

门禁:UI 契约 23/23;语种审计 0 命中;tsc --noEmit 通过;wcc/wcsc 真编译 88 WXSS + 86 WXML / 0 失败。

未改(需产品确认):第 8 项业务名仍是「善缘服务站」(参考图 2 写作「万善缘地」),
副标题自拟为「就在身边」;渐变整体比参考图略深,以保证白图形对比度达标。
2026-09-23 15:22:23 +08:00
WorkBuddy 44f75acebe fix(uat): 闭环 UAT 4 项硬阻塞(推荐恒空/保存即失败/越权端点/文档不一致)+ 小程序字段契约
UAT 走查(docs/UAT-WALKTHROUGH-2026-09-23.md)发现 4 项硬阻塞,本次闭环其中 3 项
(#2 微信登录、#5 照片直传不在本轮范围),并顺带修掉走查中新发现的跨端字段契约缺陷。

后端
- #6 推荐恒空:新增 MatchScoringService 作为唯一评分事实源(复用 UserPreference 既有
  10 维权重,零新造常量);RecommendationEngineService.calculateScore/generateReasons 由
  桩(return 50.0)改为委托;MatchEligibilityService 3 处桩全部去雷。此前 50 < 阈值 60
  → GET /recommendations 恒返回空数组。
- #4 保存即失败:updateProfile / updateBasicProfile / savePreference 改为
  「无则插入、有则更新」。注意 UserPreference 实体 @TableId=id 与库主键 user_id 不一致
  (V037 后补的可空列),故按 user_id 定位更新而非 updateById(后者会静默不更新)。
  gender / birth_date 为 NOT NULL 且向导不采集,缺失时从已通过实名认证的 user_identities
  补齐,仍缺则抛 400 业务拒绝,不再让 DataIntegrityViolationException 渲染成 500。
  GET /me/profile 的 preferences 补回 5 个此前未返回字段(regionPriority / maritalPriority /
  minEducationCode / educationPriority / childrenPriority)。
- #8 越权端点:POST /activities/{id}/interests/confirm 不再直调「建 Match 原语」
  RelationshipService.confirmActivityInterest,改为与 /activities/{id}/mutual/* 同一条
  二阶段同意链路(createMutualConsent + confirmConnection),单方调用不再可能建 Match
  (INV-014 / INV-015)。
- #10 文档不一致:MatchmakerController 类注释更正(operator 端点实际在 /operator/matchmaker)。
- 存量 500 家族:RuntimeExceptionThrowGuardTest 的正则只认 throw new RuntimeException(,
  而 17 处裸抛写作 orElseThrow(() -> new RuntimeException(...)),整体绕过守卫(CI 全绿却
  仍有 500 家族缺陷)→ 17 处全量改为 GlobalExceptionHandler.BusinessException;
  守卫扩为「整段代码 + 该形态」,并加自检断言防止守卫退化成恒真空断言。

小程序(跨端字段名 / 取值契约)
- 字段名对齐后端规范名:hasChildren→childrenStatus、occupationCategory→occupationCode、
  workRegion→workRegionCode、ageMin/ageImportance→minAge/agePriority、
  educationAccepted/maritalStatusAccepted→minEducationCode/maritalStatuses。
  此前字段名对不上,后端读不到 → 字段被静默丢弃。
- 取值修 4 类违反 DB CHECK(写入直接 500)的项:has→yes;high_school→senior_high、
  phd→doctor(chk_profile_edu);occasional→sometimes(chk_profile_smoking/drinking)。
- 预填改读服务端真实字段:此前读 profile.hasChildren / occupationCategory / workRegion,
  而服务端返回的是 childrenStatus / occupationCode / workRegionCode → 表单永远回填不上。
- profile-confirm 完成度改为直接采用服务端 profileCompletion(AGENTS 第 9 条:DB 是
  Source of Truth),废弃原先 22 条「读服务端不存在字段」的本地猜测式断言。
- 择偶条件页新增「展示形态 ↔ 服务端规范形态」适配器(toApiPayload / fromApiPayload)。

测试与门禁
- 新增 MatchScoringServiceTest、RecommendationEngineServiceBehaviorTest、
  ProfileSaveUpsertBehaviorTest、ActivityInterestConfirmConsentBehaviorTest 与
  ui-tests/miniapp-profile-contracts.mjs(桩 stores,真跑三个页面的事件处理与 onNext,
  断言提交负载用规范字段名与枚举码)。
- MIN_GUARDS 21→23(并修正上一轮漏改:该值当时仍为 21,与守卫数不符,使「数量下限」
  保护形同虚设)。
- 负向注入实测:把 #6 评分改回桩 50.0、#4 改回恒 insert、#8 改回绕开二阶段、并注入
  orElseThrow 形态裸异常 → 4 个守卫全部变红(含精确定位到 RecommendationEngineService.java:282)。
- 后端:795 tests / 0 failures / 3 skipped;UI 守卫 23/23;语种审计 0 命中;tsc 通过;
  wcc/wcsc 真编译 88 WXSS + 86 WXML / 0 失败。

仍未修(需产品/数据决策,见报告 8.3)
- regionScope 需 GB/T 2260 单值区划码,而小程序选项含「镇江其他辖区」这类非单值项 →
  不臆造区划码(该约束默认 preferred 软约束,不误过滤);
- occupationPreference 后端无对应列;settlementPreference 后端无取值字典。
2026-09-23 14:33:43 +08:00
WorkBuddy c0298c27a5 fix(miniapp): 小程序界面全量中文化,消除英文/非中文枚举码上屏
- 新增 miniapp/constants/profileLabels.js 作为「枚举码→中文简体」唯一字典
  (婚姻/子女/学历/职业/吸烟饮酒/择偶子女/红娘状态),覆盖表单值与种子
  实际值两套并集,保证详情页无论后端返回哪种码都显示中文
- 修复「择偶条件-子女」下拉英文选项(any/accept/reject→不限/可接受/不接受),
  改用 value/label 数组 + range-key="label"
- 修复 recommend-card / user-card / profile-detail / profile-confirm 直接渲染
  后端枚举码(educationCode/maritalStatus/occupationCode…)的问题,改由
  observers 派生中文 label 上屏
- 清理残留英文文案:Required 条件→必须符合的条件、喜欢TA/向Ta→喜欢对方/向对方、
  U51 服务预约→服务预约、U64 公益服务记录→公益服务记录
- 修复 7 个英文窗口标题与 3 个英文占位页正文
- matchmaker 历史服务状态消除原始码兜底,统一走字典
- 加固 scripts/audit-miniapp-language.mjs(A 整行先剥离 mustache、B 排除
  bind:action 处理器、F 窗口标题、JPG/PNG 入白名单)
- 新增 ui-tests/miniapp-language-contracts.mjs 语种守卫(复用审计脚本,断言 0 命中),
  UI 契约门禁 MIN_GUARDS 21→22;已做负向注入验证判别力
2026-09-23 11:19:17 +08:00
WorkBuddy 1fc34f1bcd fix(miniapp): 修复导航页与微信胶囊重合,并把首页两个栏目卡缩到 1/3
问题 1:五个导航页顶部与右上角胶囊按钮(··· ⊙)重合
- 根因:自定义导航用 env(safe-area-inset-top) 取的是设备物理安全区,
  在 Android 上常为 0,完全不反映胶囊位置;且右对齐元素从未为胶囊让出宽度。
- 新增 miniapp/utils/nav-metrics.js 作为唯一事实源:以
  wx.getMenuButtonBoundingClientRect() 实测胶囊矩形,反推并导出四个 CSS 变量
  --nav-status / --nav-h / --nav-right / --nav-top,并提供无接口时的兜底值。
- nav-bar 组件改为自行获取导航度量(attached + observer 双触发),
  标题按胶囊垂直居中并收窄 max-width;外部传参降级为兜底。
- index / discover / messages / activity / mine 五页根结点注入同一组变量,
  WXSS 里的 env(safe-area-inset-top)、硬编码 88rpx 与右对齐 right:0 全量替换。

问题 2:首页「善缘公益服务」「善缘安全提醒」两个栏目过宽
- 根因:封面 <image> 只声明 width 未声明 height,小程序默认按 240px 计算,
  实际把卡片撑到约 480rpx 高。
- 按「整体等比缩到 1/3」重构:两栏改为 mini-card 小卡,宽度
  calc((100% - 2*gap)/3)、高度写死 168rpx,封面 height:100%;
  公益服务一行三卡横向排布,安全提醒为「标题小卡 + 右侧文案条」,
  长文案收进详情页,安全提示原文保留。

守卫与验证
- 新增 ui-tests/miniapp-capsule-safe-contracts.mjs:
  A 层多机型断言导航行与胶囊垂直居中、右对齐让位、navTop 与兜底值;
  B 层纯函数审计五个自带导航行页面(页面清单由文件系统推导,失配直接报红);
  C 层断言首页小卡宽为 1/3 且显式声明高度,并钉住旧 service-/safety- 选择器回归。
  审计前剥离注释,避免守卫被自身示例文本误伤。
- 新增 scripts/audit-image-height.mjs:按属性名精确比对,审计全仓
  「有 width 无 height」的 <image>,当前 30 个类 0 命中。
- 负向注入 N1~N7(含 nav-safe 未接入、旧选择器回归、极小屏幕、无胶囊兜底等)
  全部被守卫咬住并 exit 1,还原后复跑通过。
- 真编译(wcc/wcsc):WXSS 88 / WXML 86,失败 0;
  tsc --noEmit 通过;UI 契约门控 21/21 PASS(新增第 21 个守卫并抬高 MIN_GUARDS)。
- 渲染探针实测:单卡 213rpx x 168rpx;五页右对齐元素右缘 423~528px,
  均在胶囊右缘 365px 之外。

未纳入本次提交:miniapp/project.config.json(微信开发者工具自动改写,
仅追加若干布尔开关并删掉文件末换行)与生成的预览页 artifacts preview.html
(约 7900 行生成产物),以免淹没代码评审视线。
2026-09-23 09:55:40 +08:00
WorkBuddy 269adf1fed 小程序端:设计系统收敛与排版层级修复
问题:88 个 wxss 里散落大量硬编码颜色/字号/字重,无法统一调整;
部分正文对比度不达 WCAG AA(4.5:1);组件触摸目标偏小。

改动:
- 硬编码颜色收敛到语义 token(颜色统一走 design token,不再散落字面值)
- 修正全站字号阶梯:正文 ≥12px、角标 ≥11px;字重只用 400/500/600/700
- 组件触摸目标提升到 ≥44px,并补交互反馈
- 首页补氛围层次、强化排版层级

验证(本轮实测):
- UI 契约门禁 18/18 PASS(含 wxml 结构、wxss 组件选择器、token 显式值断言)
- 声明级体检 88/88 通过(全部 wxss 声明合法)

交付物:artifacts/miniapp-design-v2-preview.html
2026-09-23 01:06:46 +08:00
WorkBuddy 825fd41ee4 fix(auth): 修复刷新令牌链路三层断点,并把 121 处裸异常收敛为携带 HTTP 语义的异常
用户在微信开发者工具里看到首页全站 401,追下去发现「刷新」这条路整条是断的,
且断在三个互不相干的地方 —— 每修好一层,下一层才显形:

1) 前端漏传 userId → /auth/refresh 直接 400(上一笔 63c18d8 已修)
2) 后端把 refresh token 交给 validateToken 校验,而那是用 access 密钥验签的,
   refresh token 由 refresh 密钥签发 → 必然失败。这是本次的核心修复。
3) 失败被抛成裸 RuntimeException → HTTP 500,客户端当作服务端故障重试。
4) 换发出来的新 refresh token 与旧 token **逐字节相同** —— JWT 的 iat 只有秒级精度、
   且未设 jti,同一用户同一秒内签发的令牌完全一致,「刷新即轮换、旧令牌作废」
   这条安全属性形同不存在(Redis 比对旧值自然相等)。三处签发补 UUID jti。

第 2 层此前被两层掩盖:前端漏传 userId 使请求先 400 走不到验签;且全仓
没有任何 /auth/refresh 用例。第 4 层则只在「同一秒内签发两次」时才暴露,
是新增 E2E 用例(换发后立刻用旧令牌再刷一次)把它逼出来的。

同时修复审计中发现的同族问题:
- AnalyticsService 用的是 shared.exception.BusinessException,而全局处理器只映射
  嵌套类 GlobalExceptionHandler.BusinessException —— 该异常无任何 handler,直落 500。
  现让旧类继承被映射的类,从结构上杜绝「未映射的业务异常静默 500」。
- 小程序 authStore 读 result.needPhone,后端返回 isNewUser → `!undefined === true`,
  每个用户一登录就被标记成已绑手机号。同族的第三个字段名不一致缺陷。
- 小程序 api.js 的 401 分支会无条件触发 onSessionExpired→refresh;后端把刷新失败
  语义修正为 401 后,这条路径会自我递归。补 /auth/* 守卫。
- 登录页只认 instanceof Error,把服务端文案(如「账号已被封禁」)丢了。

后端异常收敛(121 处 → 0):
- 业务拒绝改抛 BusinessException + ApiResponseCode(404 不存在 / 403 无权与状态不允许 /
  409 冲突重复 / 400 参数非法),文案一字未改,避免测试与前端文案契约漂移
- 真正的服务端故障(加解密、文件头、上传、签名、实名 Provider)改为显式
  ServerErrorException:仍是 500,但意图明确、ERROR 记全栈、不向客户端泄露内部细节
- 用带显式映射表的脚本执行(fail-closed,遇未知文案即中止),不用启发式规则

守卫(先有判别力再谈全绿):
- 新增 JwtTokenServiceRefreshTokenTest:钉住「refresh 用 refresh 密钥验签」与
  「两类令牌不得互相顶替」(含两把密钥被配成同值时的 type 声明兜底),
  以及「同一秒内签发的两个令牌必须逐字节不同」
- 新增 RuntimeExceptionThrowGuardTest:扫源码断言 main 零裸抛(无白名单副本)
- GlobalExceptionHandlerTest 补三条:别名的实例必须按其业务码回 403 而非 500、
  无法识别的字符串码 fail-closed 到 500(不得退化成无信息的 400)、
  以及继承关系守卫(把别名类改回 RuntimeException 会静默恢复该缺陷,只有它能咬住)
- 新增 tests/e2e/auth_refresh_test.py:唯一覆盖 /auth/refresh 的用例,
  断言换发的 accessToken 真能过鉴权、旧令牌轮换失效、无效凭据 401 而非 500
- http_semantics_test.py 补「Service 层拒绝必须是 4xx 而非 5xx」
- miniapp-auth-contracts.mjs 补 isNewUser 映射(双向)与 /auth/* 401 不递归

验证:
- 定向后端测试 9/9
- 全量后端测试 113 个测试类、0 失败、BUILD SUCCESS
- 本机真实栈 E2E 72 passed(含 7 个新增刷新用例)
- miniapp tsc 0 错误、UI 契约门控 18/18
- 负向注入两处精准变红(RuntimeExceptionThrowGuardTest 与
  JwtTokenServiceRefreshTokenTest 各 1 例),BUILD FAILURE,
  注入前后工作区 SHA256 逐字节一致(零污染)
2026-09-22 21:41:30 +08:00
WorkBuddy 63c18d89c2 fix(miniapp): 修复全站 401 —— 认证链字段名不一致 + 首页 onRetry 缺失
根因(读码 + 本机后端实测坐实):
- 后端登录/刷新返回 accessToken,前端 authStore 却读 result.token →
  accessToken 从未落库 → 每个请求都不带 Authorization → 全站 401(首页无法加载)。
- /auth/refresh 少传 userId:后端以 userId 定位 Redis refresh token,
  缺失即 BAD_REQUEST → HTTP 400(实测 {refreshToken} 体 → 400)。
- 首页 index.wxml 绑定 bind:retry="onRetry"(ui-error-state 触发 retry 事件),
  但 index.ts 无该方法 → 控制台报错、错误页「重新加载」失效。
- app.globalData.token 从未赋值,多页直读它拼 Authorization → Bearer undefined。

修复:
- authStore:登录/刷新改用 result.accessToken;刷新传 this.state.userId。
- authService.refresh(userId):发送 { userId, refreshToken }。
- pages/index:新增 onRetry;onLoad 订阅 authStore(expired → 回登录引导;
  刷新成功且停在错误态 → 自动重载);onUnload 退订。
- utils/api:saveToken/clearToken/getToken 同步 app.globalData.token,兼容旧页面。

守卫补强:
- ui-tests/miniapp-auth-contracts.mjs 原仅覆盖 hydrate,对字段名缺陷零覆盖。
  新增行为断言:登录后 storage.accessToken 必须等于后端 accessToken;
  刷新请求体必须含 userId + refreshToken 并持久化新 accessToken。
  已双向负向注入验证判别力(改回 result.token / 去掉 userId 均 exit 1)。

验证:
- tsc --noEmit -p miniapp/tsconfig.json → exit 0,零错误。
- scripts/run-ui-contracts.mjs → 18/18 PASS。
- 本机 8085 /auth/refresh 三态实测:{} → 400;{refreshToken} → 400;
  {userId,refreshToken} → 500(进入服务校验,非 400)。
- 未验证:开发者工具内实际登录→首页链路(本机无法驱动 devtools,待用户复测)。
2026-09-22 19:08:16 +08:00
WorkBuddy 438b38d121 V1.0.1 配置与规则纠偏:照片上限配置化+强校验 / 活体开关闭环 / 推荐阈值快照+规则版本化 / governance 三表+漂移审计
根治三类工程治理问题(技术默认值≠产品规则、配置存在但未闭环、文档代码漂移):
- PROFILE-001:profile.photo.maxCount(默认6未确认)+ ProfilePolicy + 服务器端 FOR UPDATE
  并发强校验(PHOTO_LIMIT_EXCEEDED,拒绝先于 S3 不留孤儿)+ 前端 mergeAppConfig 嵌套键修复
- CFG-001/IDENTITY-001:IdentityPolicy + GET /verification/liveness-status 真实判定点
  (默认关不阻断,开则 enhancedVerified 变化,消除假开关)+ 删死 seed livenessVerification.enabled
- MATCH-001:阈值60定为技术默认(消解 PENDING 矛盾)+ V050 recommendation_records.threshold_snapshot
  规则版本化 + 删死重复配置类 MatchConfig
- DOC-001:新增 docs/governance/(config-register/open-questions/decision-register/
  hardcoded-rule-audit/config-drift-audit)
- TEST-001:新增守卫 PhotoLimitGuardTest(8)/IdentityLivenessGuardTest(5)/
  RecommendationThresholdSnapshotGuardTest(4),均经负向注入可红

全量回归 733/0/0/3(基线717+17新增-1删死测试);UI 契约门禁 18/18;STATUS 升 rc.21
2026-09-21 02:05:15 +08:00
WorkBuddy 08b9cb2468 fix(p21-2e): 红娘服务拦截接线(blocker 首次生效 + 竞态兜底);2d 确认 Onboarding 本地资格已实现;文档回写
P16 遗留 2e(红娘服务拦截):
- checkCanWithdraw 硬编码 0 → 接 MatchmakerServiceMapper.countActiveByUserId(非终态口径
  submitted/assigned/in_service/waiting_user,与 findActiveByOperatorAndUser 一致),blocker 首次真正生效
- notifyMatchmaker TODO 空转 → terminateActiveMatchmakerServices 竞态兜底(残留非终态置 cancelled);
  不发通知:Phase 18 D2 的 notifications.user_id→users(id) FK 教训
- 守卫 MatchmakerWithdrawBlockerGuardTest 4 用例全绿;首跑暴露测试基建坑:@Transactional Service
  是 CGLIB 代理,ReflectionTestUtils 直调私有方法拿到的是代理实例的 null 字段 → 须先
  AopTestUtils.getTargetObject 解包(已在测试内注释说明)
- 另发现历史遗留:countInProgressByOperator 使用白名单外的 'processing'(已登记)

P04-T10:复核确认 Onboarding 本地资格步骤早已实现(onboarding 消息/动作 + 路由 + /me/status 字段),
JIRA ⏳ → ✅;清除首页语义错位的死状态 isVerified(miniapp tsc EXIT=0)

文档:JIRA 收尾行/计划文档 Task 3.1/3.3 勾选
2026-09-19 19:30:38 +08:00
WorkBuddy d036c9b986 fix(miniapp): 修复 2 个页面 WXML 结构缺陷,并补第 15 项门禁(WXML 结构守卫)
现象:微信开发者工具报
  [ WXML 文件编译错误] ./pages/service-history/service-history.wxml
  get tag end without start, near `</`

真实缺陷(实测 2 处,非 1 处):
- pages/service-history/service-history.wxml 多 1 个 </view>
- pages/service-program/service-program.wxml  多 1 个 </view>
(均为单行压缩写法,随更早提交入库;全仓另外 84 个 WXML 结构自洽)

根因不是这两个文件写错,而是**门禁覆盖盲区**:
既有 14 项 UI 契约全是「源码内容 / 清单断言」,没有任何一条会解析 WXML,
所以「14/14 全绿」与「小程序能不能编译」之间没有因果关系。

修复:
1. 删掉两处多余闭合标签 → 86 个 WXML 结构全部自洽。
2. 新增 scripts/lib/wxml-structure.mjs:按**引号状态机**逐字符扫描标签。
   不能用懒惰正则 —— 属性值里可以合法出现 `>`(`subtitle="{{unreadTotal > 0 ? ...}}"`),
   正则会在引号内的 `>` 处提前收尾,把自闭合标签的 `/` 读掉,报出假阳性。
   (首版实现即踩此坑,曾把合法的 messages.wxml 误判为坏文件;已复核排除。)
3. 新增 scripts/check-wxml-structure.mjs(CLI,退出码 0/1/2)。
4. 新增 ui-tests/wxml-structure-contracts.mjs:全量扫描 + **3 组内置自检样例**
   (正向样例故意在属性值内放 `>`,把「假阳性」这个坑钉进门禁)。
5. scripts/run-ui-contracts.mjs:MIN_GUARDS 14 → 15(守卫被删/改名仍会拒绝报绿)。
6. 文档口径同步:AGENTS.md、STATUS.md(4 处当前口径 + 新增 rc.17 版本行);
   ci.yml ui-contracts job 注释 14 → 15。历史时点行保留原值。

负向验证(三项,全部通过):
- 注入「多余闭合 / 未闭合」→ 退出 1,且只报坏文件、合法的 `>` 属性文件未被误报;
- UI_GUARDS_MIN=99 → 拒绝报绿(数量下限分支可自检);
- CLI 直扫坏目录 → 退出 1。

验证:node scripts/run-ui-contracts.mjs → PASS 15 / 15,0 失败,退出码 0。
未跑:微信开发者工具真实编译(本机无该环境),结论仅由结构判据支撑。
2026-09-19 11:25:05 +08:00
WorkBuddy 8dee67040c fix(miniapp): 幂等键改为按操作稳定;删除指向 404 的互选死代码
1) 幂等键(配合后端机制)

原写法把 Date.now() 拼进幂等键,每次调用都生成新键,即使后端实现了幂等
也永远不会命中 —— 声明端与实现端各自都坏。

api.idempotencyKey(scope, subject):键缓存在 _pendingIdempotency(TTL 10min),
同一逻辑操作在成功之前复用同一把键,收到 200+success 才释放;失败(含网络错误)
不释放,下次重试沿用同一把键。25 处调用点(8 个 service + 2 个 TS 页面)全部替换,
Date.now 幂等键写法残留经 grep 为 0。

2) 删除死代码 submitInterests

它请求 POST /activities/{id}/interests —— 后端从未实现该端点
(Phase 16 已把表达意向合并进 /activities/{id}/feedback 的
interestTargetUserIds)。全仓确认无任何页面/组件调用方。
两个文件各留注释说明删除原因与恢复条件:若产品确认需要独立的
「提交互选」端点,应先定义语义并补后端实现,再恢复前端方法。

store 头部关于 AGENTS 第 6 条(单向意向保密)的注释同步改为引用
submitFeedback 返回的 newMutuals。
2026-09-18 16:37:33 +08:00
WorkBuddy 72b4e9b846 fix(ops-backend/service-station): close Phase 19 KEEP-VERIFY — 25 defects (unreachable announcements + station write privilege escalation; banner 500 + soft-delete leak; content publish dead-end; program approval dead-end/draft leak/zero counts/missing withdraw; zero-coverage test schema) ; add V042 announcement chain + 34 guard tests; full regression 602 green
Backend
- Service Station: expose the 「临时公告」 chain end-to-end (V042 table/service existed
  but had ZERO endpoints -> both admin and miniapp could never reach it): 5 operator
  endpoints (list/create/update/publish/archive) + 1 user-facing read endpoint.
- Fix privilege escalation: create/update were guarded only by the class-level
  service_location.view, making V015's service_location.manage a never-used dead
  permission -> read-only roles could edit stations. Permissions moved to method level.
- Add ServiceLocationOperatorController to OperatorEndpointPermissionCoverageTest
  (it was NOT scanned, which is why the escalation stayed invisible).
- Add create/update validation (address NOT NULL + status CHECK only blew up on PG).
- Banner: validation failures threw bare RuntimeException -> 500; now VALIDATION_ERROR
  -> 400. Add deleted_at IS NULL to both home queries (soft-deleted banners stayed live).
- Content: operator list/detail split (admin «publish» was a dead end because it reused
  the published-only public API); category deactivate/reactivate; article body on edit.
- Service Program: operator member list now includes pending (approval reachable);
  active-only public visibility (draft leak); memberCount/pendingCount aggregation;
  withdraw/removeMember; reuse row on re-apply (UNIQUE(program_id,user_id) crash).
- MeController: add the missing GET /me/service-history route.

Schema / guards
- schema-h2.sql: add service_location_announcements (+ chk_announcement_status) and
  content_categories (+ uk_content_category_code, chk_category_status); move both into
  REQUIRED_IN_TEST_SCHEMA. MigrationValidationTest baseline 41 -> 42.
- V11PermissionMatrixContractTest now scans ALL migrations instead of hardcoding V015+V029.
- New: ServiceLocationAnnouncementPhase19IntegrationTest (24),
  ServiceLocationControllerPermissionContractTest (5),
  ServiceLocationAnnouncementConstraintParityTest (5, prod DDL <-> test schema <->
  backend whitelist <-> admin labels).

Frontend
- admin-web: ServiceStationView gains announcement management (statuses, create/edit,
  publish/archive with confirm, expiry); write buttons gated by service_location.manage.
- miniapp: U50 renders the 临时公告 block via new serviceStationService; page no longer
  talks to utils/api directly; announcement failure degrades to [] instead of erroring.
- ui-tests: U37 four-group + U50 announcement contracts; /home assertion retargeted to
  the service layer.

Verification
- Negative verification: 3 injected defects (drop soft-delete filter / drop test-schema
  CHECK / downgrade write endpoint to view) -> 4 named tests fail precisely, no
  collateral failures; restored, NEG-VAL residue = 0.
- Backend full regression: 602 tests, 0 failures, 0 errors, 3 skipped (baseline 420).
- admin-web: vue-tsc --noEmit EXIT=0, vite build EXIT=0. Miniapp: tsc --noEmit EXIT=0.
- ui-tests 14/14 PASS.
2026-09-17 22:21:31 +08:00
WorkBuddy 755a570849 fix(activity): close Phase 14 KEEP-VERIFY/EXTEND — 11 backend + 3 frontend-contract + 2 test-fidelity defects
Phase 14 (multi-type public-welfare activities) verification closure.

Backend (D1-D11):
- D1 CRITICAL: ActivityMapper.countRemainingCapacity had no @Select and no XML
  -> activity detail / register / approve all threw Invalid bound statement.
  Removed the shell; unified on ActivityRegistrationMapper.
- D2 CRITICAL: register & approve were read-then-write -> oversold under
  concurrency. Replaced with single atomic SQL (insertIfCapacityAvailable /
  approveIfCapacityAvailable, affectedRows==0 => full).
- D3 HIGH: activityType / orgRole never validated against production
  chk_activity_type / chk_org_role -> 500 only on PostgreSQL.
- D4 HIGH: ActivityListItemDTO had no activityType -> miniapp category tabs
  always empty (Gate failure).
- D5: male/female counts were fabricated as approvedCount/2; now real
  aggregation over user_profiles.gender.
- D6: minAge/maxAge was a TODO no-op; added assertAgeInRange.
- D7: registration_policy write-only; per-type eligibility now enforced
  (identity / local eligibility / gender quota).
- D8: POST /operator/activities dropped type, multi-org and capacity groups
  and set organizer_org_id to the operator user id; now delegates to
  createActivityFromAdmin (single implementation).
- D9: three operator write endpoints were mounted on the user-side
  ActivityController (dual implementation, zero callers) -> moved.
- D10: NPE on soft-deleted activity in getMyActivities; NPE/500 on missing
  admin form fields (Instant.parse(null)); tolerant time parsing added.
- D11: DTO lacked registeredCount / status / coverFileId / description
  -> card always showed 0 registered, "view detail", no cover, no summary.

Frontend contract (F1-F3):
- F1: miniapp category code "classroom" is not in the production whitelist
  (should be relationship_education); three types had no tab at all.
- F2: ui-activity-card read 7 fields that do not exist on the backend;
  now reads only real DTO fields, plus per-type badge and eligibility hint.
- F3: admin-web had no activity management page at all; added
  ActivityManagementView.vue (type-driven dynamic form, multi-org,
  capacity groups, publish/cancel) + /activities route and nav entry.

Test fidelity (T1-T2):
- T1: deleted two assertTrue(true) placeholder tests (testNoOverbooking,
  testNoDuplicateRegistration) that covered the exact broken invariants.
- T2: test schema lacked V023 columns/constraints and three tables; the
  activity tables were only in the OPTIONAL set (zero coverage) -> schema
  completed and moved to REQUIRED_IN_TEST_SCHEMA.

Tests: ActivityPhase14Test (23 cases), ActivityConstraintGuardTest (9 cases,
parses real db/migration/V*.sql plus miniapp source). Two negative-verification
rounds proved both guard families actually turn red.

Regression: 450 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS
(baseline 420). admin-web vue-tsc 0 error, vite build OK.
2026-09-17 14:02:00 +08:00
WorkBuddy 7a2693b239 fix(counseling): close Phase 13A KEEP-VERIFY/EXTEND — 9 real defects + 4 test-infra defects
Phase 13A(婚恋咨询与婚前辅导)复验关闭。历史 ✅ 不可信:counseling 模块存在双实现,
线上权威 CounselingService(167 行)零测试,唯一被测的 CounselingServiceApp(280 行)
是孤儿类(无注入点、无 HTTP 端点可达,生产永不执行)→ 已合并并删除。

生产缺陷(D1~D9):
- D1 Critical 生产故障 · INV-013 泄露:咨询正文 userRequestSummary 原文被写入 WARN 日志
  (同 Phase 10 D2 一族)→ 改为只记 serviceId + riskLevel,绝不记正文。
- D2 High 越权:申请 userId 取自请求体而非认证主体 → Controller 强制 principal.id()。
- D3 High 并发:分配用「先读后写」→ 两管理员并发分配静默覆盖;
  改 assignIfPending 条件更新(WHERE status='pending' AND assigned_operator_id IS NULL),
  affectedRows=0 明确报错。
- D4 High 越权读:counseling_manager 经 getDetail 可读 followups 正文 → INV-013 绕过通道;
  manager 只加回 userRequestSummary,不装配 followups。
- D5 Medium:无状态机 → 「已完成」可被再取消;新增 ALLOWED_TRANSITIONS + assertTransition。
- D6 Medium 生产故障:GET /counseling/services/intro 后端不存在(且被 /services/{id} 吞掉)
  → 小程序 intro 页 100% 无数据;补端点并声明在变量路径之前。
- D7 Medium 契约:Admin 调用 5 条不存在的 /services/operator/*(404)+ 缺四态
  → 后端补 8 个运营端端点(@PreAuthorize),前端整体重写 + 注册 /counseling 路由。
- D8 Medium 契约:小程序字段名错(userRequestSummary/preferredServiceMode)+ 无幂等
  + my-counseling 未检查 success(403 静默空列表)+ 跳转不存在的页 + WXML 内调用方法。
- D9 Medium 测试保真度:测试库 counseling 两表零 CHECK 约束,
  counseling_services 未列入 EntityMigrationParityTest 任何集合(= 零覆盖)。

测试基建缺陷(T1~T4,不修即恒真假绿):
- T1 守卫找不到 DDL:surefire workingDirectory = ${project.basedir}/.. 使 CWD 为仓库根,
  且 backend/ 是子目录非祖先 → 改为候选后缀 + 逐级向上定位,失败大声抛异常。
- T2 全限定 @com.baomidou...TableField(exist=false) 漏解析(假漂移警报)→ 正则放宽 + 回归守卫。
- T3 OperatorEndpointPermissionCoverageTest 扫到私有辅助方法(假阳性)→ 只扫描真实端点 + 自检。
- T4 两处测试自身逻辑缺陷:陈旧内存快照比较、Instant 纳秒 vs DB 微秒精度。

迁移:新增 V039(counseling 幂等键 + 三个 CHECK 白名单显式固化),基线 38 → 39。

负向验证两轮(均已还原,grep -c NEG-VAL = 0):
- ① DDL 常量守卫:从 V039 移除 'scheduled' → 常量守卫精准变红
  (证明解析的是真实 DDL 而非副本)。
- ② 行为守卫:注入「并发回退先读后写 / manager 读 followups / 状态机守卫失效」
  → 三个用例各自精准变红。

测试:新增 CounselingPhase13ATest(25)+ CounselingConstraintGuardTest(5,解析真实 DDL);
删除孤儿类及其 3 个 DTO、删除测死代码的 CounselingDataScopeTest。
全量回归:Tests run: 420, Failures: 0, Errors: 0, Skipped: 3 / BUILD SUCCESS(4:49)。
基线 391 → 420(净 +29)。admin-web vue-tsc EXIT=0;miniapp tsc EXIT=0。

Tag: phase13a-recovery-2026-09-17
Report: docs/Phase-13A-V11-Recovery-Completion-Report-2026-09-17.md
2026-09-17 01:25:12 +08:00
Codex ce172ffd4f feat(ui): activity 页迁 TS + 修复过期契约 + 新增 11 页静态预览器
## 背景
2026-09-13 UI 重设计方案的 13 个契约门禁中 3 个为红,逐一查明性质后分类处理。

## 1. activity 页迁移 TypeScript(11/11 页现已全 TS)
- activity.js → activity.ts(139 行),业务逻辑零改动,仅补类型
- 此前 STATUS.md 记「9/11 TS-only、3 页待迁」,但 messages/matchmaker
  已在 4f91be0 迁完,实际只剩 activity 一页,本提交后清零

## 2. miniapp-reference-contracts 契约自身过期(非代码问题)
- 契约引用 messages.js / matchmaker.js / activity.js,但页面已 TS 化,
  readFileSync 抛裸 ENOENT 栈,看不出「契约引用了陈旧文件名」这一真实原因
- 改为统一走 readPageLogic(page):断言 .ts 存在 + .js 不存在(锁单一运行源),
  找不到文件时给出可读断言而非崩溃
- 核心 11 页循环内统一加 TS-only 校验,防止 JS 回流

## 3. phase07-home-contracts 契约断言架构写反(非代码问题)
- 契约要求 HomeApplicationService 字面出现 userIdentityMapper.findByUserId,
  但实际是 capabilityService.isIdentityVerified(userId)(HomeApplicationService:116)
- 这符合 AGENTS.md #26「前端入口可见性由服务端 CapabilityService 返回决定」,
  让首页穿透 Capability 直接持有 mapper 反而是架构重复
- 且契约自身另一条已断言 CapabilityService 必须有 isIdentityVerified,
  两条要求互相矛盾
- 改为:查库断言落在 caps 侧;首页断言必须委托 capabilityService.isIdentityVerified;
  新增 doesNotMatch(home, /userIdentityMapper/) 禁止首页绕过 Capability
- 保留原有 doesNotMatch(home, /setVerified\(true\)/) 防硬编码断言

## 4. 新增 11 页静态预览器(本机无微信开发者工具)
- scripts/preview-miniapp.mjs:把 WXML + WXSS 转成可浏览器打开的静态 HTML
- 能力:wx:if/wx:elif/wx:else 链、wx:for 列表、嵌套三元、比较/取反运算、
  自定义组件按属性展开、slot 替换、rpx→px 换算、组件样式收集
- mock 数据严格对齐 types/index.d.ts 与各页 WXML 实际读取字段
  (如 regionName/heightCm/educationCode、status: registration_open),
  不用想象字段,避免误导验收判断
- 产出 artifacts/ui-redesign/miniapp/preview.html(11 页可切换,284K)
- 明确边界:非真机渲染、不执行页面 JS、不连后端,
  只用于视觉走查,不得作为功能验收证据

## 验证
- 契约门禁 12/13 通过(唯一红的 ui-document-baseline-contracts 涉及
  STATUS.md 验收口径,待用户看真机效果后裁决,本轮不动)
- 11 页渲染体量:116/70/47/56/94/68/39/39/41/37/38 个类名元素,
  零残留 {{}} 插值、零残留原始组件标签
- 小程序 tsc --noEmit 通过
2026-09-16 12:33:48 +08:00
Codex 687a156350 fix(chat): 会话/消息响应契约对齐小程序,修复聊天页字段断链
## 问题(三处真实断链)
后端 chat 模块字段名与小程序契约不一致,但自动化测试此前未覆盖,故一直未被发现:

1. 会话 DTO 用 otherUserId/otherDisplayName/otherAvatarUrl,
   而前端(types/index.d.ts + pages/chat/chat.ts + components/conversation-item)
   只读 targetUserId/targetDisplayName/targetAvatarUrl。
   → chat.ts 取不到对方 userId(无兜底,落到空串),
     导致"查看资料 / 拉黑 / 举报"点击后提示"暂无更多信息"并失效。
2. 消息响应只给 createdAt(ISO 字符串),前端读 Message.sentAt: number(epoch 毫秒)。
   → chat.ts 时间分隔 ts 恒为 0,消息时间分隔与时间文案不显示。
3. 会话对象缺 relationStatus,前端 conversation-item 依赖它做灰显与"已拉黑"提示。

## 裁决依据
按"以文件修改时间为准"的裁决统一到前端约定:
- 前端 types/index.d.ts 被 86 个页面引用,是声明式契约
- pages/chat/chat.ts(09-04)是最新的真实消费者
- targetUserId 已是项目内既有惯例(ActivityMatchItemDTO)
- admin-web 不消费 /conversations,无第二方受影响

## 变更
- ConversationDTO:字段改名 target*;新增 relationStatus;lastMessageAt 改 epoch 毫秒
- ChatService.toConversationDTO:同步映射;relationStatus 按 Block > unmatched > mutual_match > neutral 推导
- Constants:新增 RELATION_STATUS_NEUTRAL / MUTUAL_MATCH / UNMATCHED(不硬编码)
- MessageResponse:新增 sentAt(epoch 毫秒)
- miniapp/utils/datetime.js(新增):收敛时间格式化,
  补上模板依赖但全仓从未计算的 lastMessageAtText
- messageStore:会话进入 store 时统一装饰 lastMessageAtText
- chat.ts:_formatTime 改为复用共享工具;移除已失效的 otherDisplayName 兜底

## 验证
- 新增 ChatContractJsonTest(4 例)锁定字段名、sentAt 数值类型、
  并断言 other*/status 旧字段名不再出现
- 后端 294 tests, 0 failures, 0 errors(原 290 + 新增 4)
- 小程序 tsc --noEmit 通过

遗留(未在本轮处理):平台尚未实现头像 URL 解析(user_photos 只存 fileKey),
targetAvatarUrl 保持 null,前端 ui-avatar 回退昵称首字,不影响展示。
2026-09-15 22:45:46 +08:00
Codex 4f91be0cab feat(miniapp): final 3 pages migrated to TypeScript (matchmaker/messages/mine)
D1 debt cleared: all 11 miniapp core pages now TS-only.
tsc --noEmit -> 0 errors. JS sources deleted.
2026-09-14 14:59:28 +08:00
Codex 22325ec2b0 feat(discover): chip-targeted filter panel — scroll to + highlight target section
- Each filter chip (年龄/学历/婚姻) carries data-target pointing to its section
- openFilterPanel receives TouchEvent, maps target to section id, sets panelScrollTo + panelHighlight
- Panel uses scroll-view scroll-into-view with scroll-with-animation for smooth scroll
- Targeted section gets pink outline highlight via .hl class
- Non-targeted sections remain normally visible for context
2026-09-14 11:27:52 +08:00
Codex adfa4ff31e fix(discover): filter panel as bottom sheet with mask, add debug log
- Changed filter panel from in-flow to fixed bottom drawer with semi-transparent mask
- Mask click closes panel (closeFilterPanel)
- Added console.log in openFilterPanel for debugging event firing
- Panel now z-index:11 above mask z-index:10, guaranteeing visibility
2026-09-14 11:19:25 +08:00
Codex 3e5d2314aa fix(discover): restore discover-filter-chips class for contract 2026-09-14 11:09:53 +08:00
Codex 146ebab61d feat(discover): rewrite filter UI to align with backend DiscoverController 5 params
- Top chips: 推荐(重置) / 丹阳本地(regionCode=321181) / 年龄 / 学历 / 婚姻
- Each chip has real active state bound to applied filter value
- Panel expanded to 4 sections: age slider + region + education + maritalStatus
- Param names fixed: minAge/maxAge/regionCode/educationCode/maritalStatus
- Data flow: panel temp -> onApplyFilter -> applied -> _buildParams -> API
- Panel opens with current applied values, closes with openFilterPanel/closeFilterPanel
2026-09-14 11:09:23 +08:00
Codex 0f06d82a92 fix(discover): filter chips use applied state and apply immediately on top-chip tap
- Top chip '推荐' now active only when no active filter (was hardcoded)
- Top chip '丹阳本地' uses applied.region (was tempRegion)
- Top chip '年龄' uses applied age range
- New onTopRegionTap applies region filter immediately and refreshes results
- Panel onRegionTap preserved for filter-panel multi-select flow
2026-09-14 10:59:43 +08:00
Codex 751f14d5ee chore: remove obsolete .js sources for migrated C-group pages 2026-09-14 10:14:27 +08:00
Codex 93fc9b0d88 feat(miniapp): migrate C-group pages to TypeScript, update contracts 2026-09-14 10:14:05 +08:00
Codex 63cbbff2de feat: implement miniapp C-group pages with real API calls and design tokens 2026-09-14 10:02:22 +08:00
Codex 1aea6f7791 feat: redesign public relationship services 2026-09-14 08:51:26 +08:00
Codex a6037ded59 feat: redesign activity and profile tabs 2026-09-14 07:05:20 +08:00
Codex b7dc52b517 feat: redesign discovery and messages tabs 2026-09-14 06:49:49 +08:00
Codex 36b15d9ca8 feat: rebuild miniapp reference home 2026-09-14 06:45:36 +08:00
Codex df47543501 feat: add miniapp reference assets and primitives 2026-09-14 06:38:44 +08:00
Codex 311f251609 test: lock miniapp reference page scope 2026-09-14 06:19:11 +08:00
Codex 097c4c4778 chore: finalize miniapp page source cleanup 2026-09-13 21:25:50 +08:00
Codex eed4400611 refactor: remove duplicate miniapp page sources 2026-09-13 21:19:05 +08:00
admin ac2327b499 chore: checkpoint before switching to main 2026-09-11 09:07:41 +08:00
admin 76a10e5de0 fix: resolve miniapp compile errors 2026-09-10 18:37:53 +08:00
admin 45ca118453 feat: deliver V1.1 dual-client UI fidelity milestone 2026-09-10 16:06:11 +08:00
admin b46b48eb33 chore: establish V1.1 recovery baseline 2026-09-09 22:05:00 +08:00