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
27 changes: 27 additions & 0 deletions harness/claude-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -1222,3 +1222,30 @@
- 验证限制:当前 shell 中 `command -v npm` 无输出,未运行 npm wrapper;本轮使用 bundled Node 路径逐项运行仓库门禁。没有执行真实 WeChat DevTools/真机 UI 验收,readiness 中的 9420 service-port/smoke blocker 仍是环境阻塞,不是 UI passed evidence
- 已知风险或未解决问题:这些是性能和静态/动态 guard 优化,不改变 feature_list 的人工验收状态;地图原生层、真实定位、管理台真实管理员账号和真机性能体感仍需 DevTools/真机观察
- 下一步最佳动作:如果要把本轮性能改动合入现有 PR,需要先提交并推送当前工作区改动;之后在 WeChat DevTools/真机复查地图列表/附近预览、localOnly 首屏刷新、hidden 任务可见性边界和管理台筛选统计

### Session 088LongShareDemoData

- 日期:2026-06-22
- 分支:`main`
- 本轮目标:修复用户反馈“分享页过期了,导致看不到返回首页按钮”,创建一条可长期体验的分享数据
- 根因:`project.private.config.json` 的分享页启动模式固定打开 `post_001&from=share`;mock 里的 `post_001` 只有约 22 小时有效,而且本地 `wx` storage 一旦保存旧种子数据,后续启动不会自动刷新,可能继续读到过期的 `post_001`,从而隐藏分享接收动作区和 `回首页查这条` 入口
- 已完成:`utils/mock-posts.js` 导出稳定的 `SHARE_DEMO_POST_ID='post_001'` 和 30 天 `SHARE_DEMO_TTL_MS`;`utils/store.js` 在 `seedPosts()` 中刷新缺失、过期或不足 7 天有效期的本地 `post_001`,并让详情页对该手测 fixture 优先使用本地长期样本,避免旧云端/旧缓存把体验入口变回过期态;`scripts/check-share-receiver-action.mjs` 新增长期分享数据和本地优先路径静态断言;`scripts/check-performance-guards.mjs` 在比较性能测试帖时忽略专用 `post_001` fixture,保留 localOnly 缓存读取次数断言
- 运行过的验证:`pwd`;读取 `AGENTS.md`、`harness/claude-progress.md`、`harness/feature_list.json`;`git log --oneline -5`;`bash harness/init.sh`;`node scripts/check-share-receiver-action.mjs`;`node --check utils/mock-posts.js`;`node --check utils/store.js`;`node --no-warnings scripts/check-performance-guards.mjs`;`node --check scripts/check-performance-guards.mjs`;`node --no-warnings scripts/check-devtools-readiness.mjs`;WeChat DevTools 当前项目 `/Users/bytedance/git/x` 的分享页视觉确认
- 已记录证据:`node scripts/check-share-receiver-action.mjs` 输出 `Share receiver action checks passed.`;两条 `node --check` 无语法错误;`node --no-warnings scripts/check-performance-guards.mjs` 输出 `Performance guard checks passed.`;`node --no-warnings scripts/check-devtools-readiness.mjs` 输出 `DevTools readiness checks passed. Static gates passed; DevTools and real-device visual acceptance are still required.`;DevTools 分享页显示 `地铁口捡到蓝色门禁卡`、`30天后过期`,并且 `回首页查这条` 按钮可见
- 更新过的文件或工件:`utils/mock-posts.js`,`utils/store.js`,`scripts/check-share-receiver-action.mjs`,`scripts/check-performance-guards.mjs`,`harness/feature_list.json`,`harness/claude-progress.md`
- 已知风险或未解决问题:DevTools 控制台仍有既有 `WAServiceMainContext timeout`,当前观察页面仍可用且不影响长期分享样本显示;真机分享入口仍需独立验证
- 下一步最佳动作:在 WeChat DevTools 点击 `回首页查这条`,确认首页仍会打开列表并定位 `post_001`;随后真机验证分享页在长期样本下的滚动、按钮可见性和首页回跳

### Session 089ExpiredShareHomeReturn

- 日期:2026-06-22
- 分支:`main`
- 本轮目标:修复用户反馈“已过期的分享页也需要有回到首页的按钮”
- 根因:`回首页查这条` 之前放在 `shareReceiverActionStrip` 内,而 `buildShareReceiverActionStrip` 会对 `expired`、`resolved`、`hidden` 和风险态返回 `null`,所以过期分享页虽然仍有 `shareReceiverGuide`,但没有 action strip,也就没有回首页入口
- 已完成:把 `share-receiver-map-return` 从 active-only action strip 中移到分享接收引导卡内,并用 `wx:if="{{!receiverConversionPrompt}}"` 避免二跳转化提示出现时抢主 CTA;过期分享页会保留 `shareReceiverGuide`,因此也会显示 `回首页查这条`
- TDD 证据:先修改 `scripts/check-share-receiver-action.mjs`,要求 expired share entry 仍有 receiver guide、回首页块不依赖 action strip、且出现在 action strip 之前;首次运行按预期失败,错误为 `focused home action should not depend on active-only receiver actions`;移动 WXML 后同一脚本通过
- 运行过的验证:`bash harness/init.sh`;`node scripts/check-share-receiver.mjs`;`node scripts/check-share-receiver-action.mjs`;`node --check scripts/check-share-receiver-action.mjs`;`node --check pages/detail/detail.js`;`node --no-warnings scripts/check-devtools-readiness.mjs`
- 已记录证据:`node scripts/check-share-receiver.mjs` 输出 `Share receiver checks passed.`;`node scripts/check-share-receiver-action.mjs` 输出 `Share receiver action checks passed.`;readiness 输出 `DevTools readiness checks passed. Static gates passed; DevTools and real-device visual acceptance are still required.`
- 更新过的文件或工件:`pages/detail/detail.wxml`,`scripts/check-share-receiver-action.mjs`,`harness/feature_list.json`,`harness/claude-progress.md`
- 已知风险或未解决问题:本轮通过静态结构和 helper 行为验证覆盖过期态;尚未用真实过期云端数据或真机分享链接做视觉验收
- 下一步最佳动作:用一条真实 expired 分享链接打开详情页,确认 `已过期任务` 引导卡下方显示 `回首页查这条`,点击后回首页并展开附近列表
2 changes: 2 additions & 0 deletions harness/feature_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@
"2026-06-22: Added map launch focus support so pages/map/map can accept focusPostId/postId/id plus showList=1, center on the requested post, select it, and highlight it in the home list for DevTools verification flows.",
"2026-06-22: Share receiver detail now shows an explicit '回首页查这条' CTA; it uses wx.reLaunch with focusPostId and showList=1 so users return to the map homepage with the current task located and the list open, while the receiver action strip static guard covers the CTA and focused map URL.",
"2026-06-22: Fixed the focused return-home path so the startup diagnostics panel no longer covers the map when showList=1 opens the task drawer; map feed guard now requires diagnostics to be suppressed while the list is open and hidden immediately after the focused task is ready.",
"2026-06-22: Refreshed the manual share demo data so post_001 stays active for 30 days, old local storage copies are refreshed before they expire, and the share receiver action guard verifies the long-lived fixture and local preference path; DevTools showed the share page with `30天后过期` and the `回首页查这条` button visible.",
"2026-06-22: Moved the focused home-return CTA out of the active-only receiver action strip and into the share receiver guide, so expired share pages still show `回首页查这条`; the share receiver action guard now verifies expired share context exists and the CTA no longer depends on receiver actions.",
"2026-06-14: Added scripts/check-map-list-resilience.mjs on the P iteration to statically guard the map list drawer, card, title/tag row, summary, thumbnail, footer, counts, and detail affordance WXML/WXSS structure.",
"2026-06-14: Q iteration wired the map list static guard into scripts/check-devtools-readiness.mjs so preflight now runs publish flow, trust insight, candidate flow, and map list resilience checks before manual DevTools or real-device acceptance.",
"2026-06-14: R iteration made scripts/prepare-manual-test-run.mjs explicitly announce that manual run preparation first runs readiness preflight, including the map list static guard, and that this still does not prove DevTools or real-device visual acceptance.",
Expand Down
14 changes: 7 additions & 7 deletions pages/detail/detail.wxml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@
</view>
</view>
<view class="share-receiver-note">{{shareReceiverGuide.note}}</view>
<view wx:if="{{!receiverConversionPrompt}}" class="share-receiver-map-return">
<view class="share-receiver-map-copy">
<view class="share-receiver-map-title">想先回首页?</view>
<view class="share-receiver-map-body">回到首页会定位这条任务,并展开附近列表。</view>
</view>
<button class="share-receiver-map-button" bindtap="goHomeWithPost">回首页查这条</button>
</view>
<view wx:if="{{shareReceiverActionStrip && !receiverConversionPrompt}}" class="share-receiver-actions {{shareReceiverActionStrip.tone}}">
<view class="share-receiver-action-copy">
<view class="share-receiver-action-title">{{shareReceiverActionStrip.title}}</view>
Expand All @@ -67,13 +74,6 @@
<button class="share-receiver-action-button share-receiver-action-confirm {{shareReceiverActionStrip.confirmDisabled || busyAction ? 'disabled-action' : ''}}" loading="{{busyAction === 'confirm'}}" disabled="{{shareReceiverActionStrip.confirmDisabled || busyAction}}" data-action="confirm" bindtap="react">{{shareReceiverActionStrip.confirmText}}</button>
<button class="ghost-button share-receiver-action-button share-receiver-action-comment" bindtap="openCommentDialog">{{shareReceiverActionStrip.commentText}}</button>
</view>
<view class="share-receiver-map-return">
<view class="share-receiver-map-copy">
<view class="share-receiver-map-title">想先看它在哪?</view>
<view class="share-receiver-map-body">回到首页会定位这条任务,并展开附近列表。</view>
</view>
<button class="share-receiver-map-button" bindtap="goHomeWithPost">回首页查这条</button>
</view>
<view class="share-receiver-action-note">{{shareReceiverActionStrip.note}}</view>
</view>
</view>
Expand Down
14 changes: 9 additions & 5 deletions scripts/check-performance-guards.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ const posts = [
}
];

function idsWithoutShareDemo(items) {
return items.map((post) => post.id).filter((id) => id !== 'post_001');
}

let postsStorageReads = 0;
let storage = {
posts,
Expand Down Expand Up @@ -141,8 +145,8 @@ const { listPosts } = await import(storeModuleUrl);
const first = await listPosts(center, { localOnly: true });
const second = await listPosts(center, { localOnly: true });

assert.equal(first.length, 1);
assert.equal(second.length, 1);
assert.deepEqual(idsWithoutShareDemo(first), ['perf_post_1']);
assert.deepEqual(idsWithoutShareDemo(second), ['perf_post_1']);
assert.equal(postsStorageReads, 1, 'localOnly listPosts should reuse cache for repeated reads.');

const cloudPosts = await listPosts(center);
Expand Down Expand Up @@ -213,7 +217,7 @@ const localAfterCloudCreate = await listPostsAfterCloudWrite(center, { localOnly

assert.equal(cloudCalls, 1, 'cloud create should call the posts cloud function once.');
assert.deepEqual(
localAfterCloudCreate.map((post) => post.id),
idsWithoutShareDemo(localAfterCloudCreate),
['perf_post_1'],
'cloud writes should not inject cloud-only posts into a localOnly cache.'
);
Expand Down Expand Up @@ -294,12 +298,12 @@ const visibleEvenWithHiddenOption = await listVisiblePosts(center, {
const includeHidden = await listAllPosts(center);

assert.deepEqual(
visibleOnly.map((post) => post.id),
idsWithoutShareDemo(visibleOnly),
['perf_post_1'],
'normal listPosts should still omit hidden posts after pre-filtering.'
);
assert.deepEqual(
visibleEvenWithHiddenOption.map((post) => post.id),
idsWithoutShareDemo(visibleEvenWithHiddenOption),
['perf_post_1'],
'normal listPosts should ignore includeHidden and keep hidden posts out of public lists.'
);
Expand Down
46 changes: 44 additions & 2 deletions scripts/check-share-receiver-action.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ process.on('warning', (warning) => {
});

const { buildShareReceiverActionStrip } = await import('../utils/share-receiver-actions.js');
const { buildShareReceiverGuide } = await import('../utils/share-receiver.js');
const {
default: mockPosts,
SHARE_DEMO_POST_ID,
SHARE_DEMO_TTL_MS
} = await import('../utils/mock-posts.js');

function post(overrides = {}) {
return {
Expand All @@ -27,6 +33,10 @@ function actionStrip(overrides = {}, entryFrom = 'share') {
return buildShareReceiverActionStrip(post(overrides), { entryFrom });
}

function receiverGuide(overrides = {}, entryFrom = 'share') {
return buildShareReceiverGuide(post(overrides), 0, { entryFrom });
}

{
assert.equal(actionStrip({}, 'detail'), null, 'ordinary detail entry should not show receiver action strip');
assert.equal(buildShareReceiverActionStrip(null, { entryFrom: 'share' }), null);
Expand Down Expand Up @@ -69,14 +79,35 @@ for (const riskyPost of [
);
}

{
const expiredGuide = receiverGuide({ status: 'expired' });
assert.ok(expiredGuide, 'expired share-entry post should still show receiver context');
assert.equal(expiredGuide.title, '已过期任务');
}

const detailJs = readFileSync('pages/detail/detail.js', 'utf8');
const detailWxml = readFileSync('pages/detail/detail.wxml', 'utf8');
const detailWxss = readFileSync('pages/detail/detail.wxss', 'utf8');
const storeJs = readFileSync('utils/store.js', 'utf8');
const readinessScript = readFileSync('scripts/check-devtools-readiness.mjs', 'utf8');
const viralCandidateScript = readFileSync('scripts/check-viral-candidate.mjs', 'utf8');
const actionBlockStart = detailWxml.indexOf('shareReceiverActionStrip');
const actionBlockEnd = detailWxml.indexOf('<view wx:if="{{receiverConversionPrompt}}"', actionBlockStart);
const actionBlock = detailWxml.slice(actionBlockStart, actionBlockEnd);
const shareGuideStart = detailWxml.indexOf('<view wx:if="{{shareReceiverGuide}}"');
const shareGuideEnd = detailWxml.indexOf('<view wx:if="{{receiverConversionPrompt}}"', shareGuideStart);
const shareGuideBlock = detailWxml.slice(shareGuideStart, shareGuideEnd);
const mapReturnIndex = shareGuideBlock.indexOf('share-receiver-map-return');
const actionStripIndex = shareGuideBlock.indexOf('shareReceiverActionStrip && !receiverConversionPrompt');
const shareDemoPost = mockPosts.find((item) => item.id === SHARE_DEMO_POST_ID);

assert.equal(SHARE_DEMO_POST_ID, 'post_001', 'manual share launch should keep using the stable demo post id');
assert.equal(SHARE_DEMO_TTL_MS, 30 * 24 * 60 * 60 * 1000, 'manual share demo should stay valid for 30 days');
assert.ok(shareDemoPost, 'mock data should include the manual share demo post');
assert.ok(
shareDemoPost.expiresAt - Date.now() > 29 * 24 * 60 * 60 * 1000,
'manual share demo post should not expire during a normal test session'
);

assert.match(detailJs, /buildShareReceiverActionStrip/, 'detail page should import share receiver action helper');
assert.match(detailJs, /shareReceiverActionStrip: null/, 'receiver action strip should default to hidden');
Expand All @@ -92,8 +123,12 @@ assert.match(
assert.match(actionBlock, /data-action="confirm"/, 'receiver confirm action should call the existing confirm reaction');
assert.match(actionBlock, /bindtap="react"/, 'receiver confirm action should reuse detail react handler');
assert.match(actionBlock, /bindtap="openCommentDialog"/, 'receiver comment action should reuse detail comment dialog');
assert.match(actionBlock, /bindtap="goHomeWithPost"/, 'receiver guide should offer an explicit return-to-home focused-map action');
assert.match(actionBlock, /回首页查这条/, 'receiver guide return action should be visible and clear');
assert.doesNotMatch(actionBlock, /bindtap="goHomeWithPost"/, 'focused home action should not depend on active-only receiver actions');
assert.doesNotMatch(actionBlock, /回首页查这条/, 'focused home action should stay visible even when receiver actions are hidden');
assert.match(shareGuideBlock, /wx:if="\{\{!receiverConversionPrompt\}\}"[^>]+class="share-receiver-map-return"/, 'receiver guide should offer a map return block outside the active-only action strip');
assert.match(shareGuideBlock, /bindtap="goHomeWithPost"/, 'receiver guide should offer an explicit return-to-home focused-map action');
assert.match(shareGuideBlock, /回首页查这条/, 'receiver guide return action should be visible and clear');
assert.ok(mapReturnIndex >= 0 && actionStripIndex >= 0 && mapReturnIndex < actionStripIndex, 'map return should appear before active-only receiver actions so expired share pages can still show it');
assert.doesNotMatch(actionBlock, /open-type="share"/, 'receiver action strip buttons must not trigger share');
assert.match(detailJs, /focusedMapUrl\(postId\)/, 'detail page should build a focused map URL from the current post id');
assert.match(detailJs, /wx\.reLaunch\(\{\s*url:\s*this\.focusedMapUrl\(postId\)/s, 'focused home action should reLaunch with query instead of switchTab without query');
Expand All @@ -103,6 +138,13 @@ assert.match(
/!showPublishSuccess && !shareReceiverGuide && !receiverConversionPrompt && !actionRelayPrompt && !commentRelayPrompt && shareMessage/,
'ordinary share panel should stay hidden while receiver guidance or relay prompts are active'
);
assert.match(storeJs, /ensureShareDemoPost/, 'store should refresh an expired locally stored share demo post');
assert.match(storeJs, /SHARE_DEMO_MIN_TTL_MS/, 'store should refresh the share demo before it gets close to expiring');
assert.match(
storeJs,
/localPost && localPost\.id === SHARE_DEMO_POST_ID/,
'manual share demo should prefer the fresh local fixture over stale cloud data'
);
assert.match(detailWxss, /\.share-receiver-actions\b/, 'detail styles should include receiver action strip styles');
assert.match(
readinessScript,
Expand Down
7 changes: 5 additions & 2 deletions utils/mock-posts.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
const now = Date.now();

export const SHARE_DEMO_POST_ID = 'post_001';
export const SHARE_DEMO_TTL_MS = 30 * 24 * 60 * 60 * 1000;

const mockPosts = [
{
id: 'post_001',
id: SHARE_DEMO_POST_ID,
markerId: 1,
title: '地铁口捡到蓝色门禁卡',
body: 'A口外侧共享单车旁,卡套上有一只小熊贴纸。',
Expand All @@ -17,7 +20,7 @@ const mockPosts = [
staleCount: 0,
reportCount: 0,
createdAt: now - 42 * 60 * 1000,
expiresAt: now + 22 * 60 * 60 * 1000,
expiresAt: now + SHARE_DEMO_TTL_MS,
publisher: '附近店员'
},
{
Expand Down
Loading
Loading