sobriety-tools-guardian
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSobriety Tools Guardian
Sobriety Tools 守护工具
Mission: Keep sobriety.tools fast enough to save lives. A fentanyl addict in crisis has seconds, not minutes. The app must load instantly, work offline, and surface help before they ask.
使命:确保sobriety.tools足够快,以挽救生命。处于危机中的芬太尼成瘾者只有几秒时间,而非几分钟。应用必须即时加载、支持离线使用,并在用户求助前主动提供帮助。
Why Performance Is Life-or-Death
为什么性能关乎生死
CRISIS TIMELINE:
0-30 seconds: User opens app in distress
30-60 seconds: Looking for sponsor number or meeting
60-120 seconds: Decision point - call someone or use
2+ minutes: If still searching, may give up
EVERY SECOND OF LOAD TIME = LIVES AT RISKCore truth: This isn't a business app. Slow performance isn't "bad UX" - it's abandonment during crisis. The user staring at a spinner might be deciding whether to live or die.
CRISIS TIMELINE:
0-30 seconds: User opens app in distress
30-60 seconds: Looking for sponsor number or meeting
60-120 seconds: Decision point - call someone or use
2+ minutes: If still searching, may give up
EVERY SECOND OF LOAD TIME = LIVES AT RISK核心事实:这不是一款商业应用。缓慢的性能不是“糟糕的用户体验”——而是危机中的放弃。盯着加载动画的用户可能正在决定生死。
Stack-Specific Optimization Knowledge
特定技术栈的优化知识
Architecture (Know This Cold)
架构(必须熟练掌握)
Next.js 15 (static export) → Cloudflare Pages
↓
Supabase (PostgREST + PostGIS)
↓
Cloudflare Workers:
- meeting-proxy (KV cached, geohash-based)
- meeting-harvester (hourly cron)
- claude-api (AI features)Next.js 15 (static export) → Cloudflare Pages
↓
Supabase (PostgREST + PostGIS)
↓
Cloudflare Workers:
- meeting-proxy (KV cached, geohash-based)
- meeting-harvester (hourly cron)
- claude-api (AI features)Critical Performance Paths
关键性能路径
1. Meeting Search (MUST be <500ms)
User location → Geohash (3-char ~150km cell)
→ KV cache lookup (edge, ~5ms)
→ Cache HIT: Return immediately
→ Cache MISS: Supabase RPC find_current_meetings
→ PostGIS ST_DWithin query
→ Store in KV, returnBottleneck: Cold Supabase queries. Fix: Pre-warm top 30 metros via /warm endpoint.
2. Sponsor/Contact List (MUST be <200ms)
User opens contacts → Local IndexedDB first
→ Show cached contacts instantly
→ Background sync with Supabase
→ Update UI if changesAnti-pattern: Waiting for network before showing contacts. In crisis, show stale data immediately.
3. Check-in Flow (MUST be <100ms to first input)
Open check-in → Pre-rendered form shell
→ Load previous patterns async
→ Submit optimistically1. 会议搜索(必须<500ms)
User location → Geohash (3-char ~150km cell)
→ KV cache lookup (edge, ~5ms)
→ Cache HIT: Return immediately
→ Cache MISS: Supabase RPC find_current_meetings
→ PostGIS ST_DWithin query
→ Store in KV, return瓶颈:Supabase冷查询。解决方案:通过/warm端点预加载前30个大都市的数据。
2. 赞助人/联系人列表(必须<200ms)
User opens contacts → Local IndexedDB first
→ Show cached contacts instantly
→ Background sync with Supabase
→ Update UI if changes反模式:等待网络请求完成后再显示联系人。在危机中,应立即显示缓存数据。
3. 签到流程(首次输入必须<100ms)
Open check-in → Pre-rendered form shell
→ Load previous patterns async
→ Submit optimisticallyOffline-First Requirements (NON-NEGOTIABLE)
离线优先要求(不可协商)
typescript
// Service Worker must cache:
const CRISIS_CRITICAL = [
'/contacts', // Sponsor phone numbers
'/safety-plan', // User's safety plan
'/meetings?saved=true', // Saved meetings list
'/crisis', // Crisis resources page
];
// These MUST work with zero network:
// 1. View sponsor contacts
// 2. View safety plan
// 3. View saved meetings (even if stale)
// 4. Record check-in (sync when online)typescript
// Service Worker must cache:
const CRISIS_CRITICAL = [
'/contacts', // Sponsor phone numbers
'/safety-plan', // User's safety plan
'/meetings?saved=true', // Saved meetings list
'/crisis', // Crisis resources page
];
// These MUST work with zero network:
// 1. View sponsor contacts
// 2. View safety plan
// 3. View saved meetings (even if stale)
// 4. Record check-in (sync when online)Crisis Detection Patterns
危机检测模式
Journal Sentiment Signals
日志情绪信号
typescript
// RED FLAGS (surface help proactively):
const CRISIS_INDICATORS = {
anger_spike: 'HALT angry score jumps 3+ points',
ex_mentions: 'Mentions ex-partner 3+ times in week',
isolation: 'No check-ins for 3+ days after daily streak',
time_distortion: 'Check-ins at unusual hours (2-5am)',
negative_spiral: 'Consecutive declining mood scores',
};
// When detected: Surface sponsor contact, safety plan link
// DO NOT: Be preachy or alarming. Gentle nudge only.typescript
// RED FLAGS (surface help proactively):
const CRISIS_INDICATORS = {
anger_spike: 'HALT angry score jumps 3+ points',
ex_mentions: 'Mentions ex-partner 3+ times in week',
isolation: 'No check-ins for 3+ days after daily streak',
time_distortion: 'Check-ins at unusual hours (2-5am)',
negative_spiral: 'Consecutive declining mood scores',
};
// When detected: Surface sponsor contact, safety plan link
// DO NOT: Be preachy or alarming. Gentle nudge only.Check-in Analysis
签到分析
sql
-- Detect concerning patterns
SELECT user_id,
AVG(angry_score) as avg_anger,
AVG(angry_score) FILTER (WHERE created_at > NOW() - INTERVAL '3 days') as recent_anger,
COUNT(*) FILTER (WHERE EXTRACT(HOUR FROM created_at) BETWEEN 2 AND 5) as late_night_checkins
FROM daily_checkins
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY user_id
HAVING AVG(angry_score) FILTER (WHERE created_at > NOW() - INTERVAL '3 days') >
AVG(angry_score) + 2;sql
-- Detect concerning patterns
SELECT user_id,
AVG(angry_score) as avg_anger,
AVG(angry_score) FILTER (WHERE created_at > NOW() - INTERVAL '3 days') as recent_anger,
COUNT(*) FILTER (WHERE EXTRACT(HOUR FROM created_at) BETWEEN 2 AND 5) as late_night_checkins
FROM daily_checkins
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY user_id
HAVING AVG(angry_score) FILTER (WHERE created_at > NOW() - INTERVAL '3 days') >
AVG(angry_score) + 2;Performance Monitoring & Logging
性能监控与日志
Key Metrics to Track
需要跟踪的关键指标
typescript
// Client-side (log to analytics)
const PERF_METRICS = {
ttfb: 'Time to First Byte',
fcp: 'First Contentful Paint',
lcp: 'Largest Contentful Paint',
tti: 'Time to Interactive',
// App-specific critical paths
contacts_visible: 'Time until sponsor list renders',
meeting_results: 'Time until first meeting card shows',
checkin_interactive: 'Time until check-in form accepts input',
};
// Log slow paths
if (contactsVisibleTime > 500) {
logPerf('contacts_slow', { duration: contactsVisibleTime, network: navigator.connection?.effectiveType });
}typescript
// Client-side (log to analytics)
const PERF_METRICS = {
ttfb: 'Time to First Byte',
fcp: 'First Contentful Paint',
lcp: 'Largest Contentful Paint',
tti: 'Time to Interactive',
// App-specific critical paths
contacts_visible: 'Time until sponsor list renders',
meeting_results: 'Time until first meeting card shows',
checkin_interactive: 'Time until check-in form accepts input',
};
// Log slow paths
if (contactsVisibleTime > 500) {
logPerf('contacts_slow', { duration: contactsVisibleTime, network: navigator.connection?.effectiveType });
}Automated Performance Regression Detection
自动化性能回归检测
bash
undefinedbash
undefinedscripts/perf-audit.sh - Run in CI
scripts/perf-audit.sh - Run in CI
lighthouse https://sobriety.tools/meetings --output=json --output-path=./perf.json
SCORE=$(jq '.categories.performance.score' perf.json)
if (( $(echo "$SCORE < 0.9" | bc -l) )); then
echo "Performance regression: $SCORE"
Create GitHub issue automatically
fi
undefinedlighthouse https://sobriety.tools/meetings --output=json --output-path=./perf.json
SCORE=$(jq '.categories.performance.score' perf.json)
if (( $(echo "$SCORE < 0.9" | bc -l) )); then
echo "Performance regression: $SCORE"
Create GitHub issue automatically
fi
undefinedAutomated Issue Detection & Filing
自动化问题检测与提交
Background Performance Scanner
后台性能扫描器
typescript
// Run hourly via Cloudflare Worker cron
async function performanceAudit() {
const checks = [
checkMeetingCacheHealth(),
checkSupabaseQueryTimes(),
checkStaticAssetSizes(),
checkServiceWorkerCoverage(),
];
const issues = await Promise.all(checks);
const problems = issues.flat().filter(i => i.severity === 'high');
for (const problem of problems) {
await createGitHubIssue({
title: `[Auto] Perf: ${problem.title}`,
body: problem.description + '\n\n' + problem.suggestedFix,
labels: ['performance', 'automated'],
});
}
}typescript
// Run hourly via Cloudflare Worker cron
async function performanceAudit() {
const checks = [
checkMeetingCacheHealth(),
checkSupabaseQueryTimes(),
checkStaticAssetSizes(),
checkServiceWorkerCoverage(),
];
const issues = await Promise.all(checks);
const problems = issues.flat().filter(i => i.severity === 'high');
for (const problem of problems) {
await createGitHubIssue({
title: `[Auto] Perf: ${problem.title}`,
body: problem.description + '\n\n' + problem.suggestedFix,
labels: ['performance', 'automated'],
});
}
}Common Anti-Patterns
常见反模式
1. Network-Blocking Contact Display
1. 阻塞网络的联系人显示
Symptom: Contacts page shows spinner while fetching
Problem: User in crisis sees loading state instead of sponsor number
Solution:
typescript
// WRONG
const { data: contacts } = useQuery(['contacts'], fetchContacts);
// RIGHT
const { data: contacts } = useQuery(['contacts'], fetchContacts, {
initialData: () => getCachedContacts(), // IndexedDB
staleTime: Infinity, // Never refetch automatically
});症状:联系人页面在获取数据时显示加载动画
问题:处于危机中的用户看到的是加载状态而非赞助人号码
解决方案:
typescript
// WRONG
const { data: contacts } = useQuery(['contacts'], fetchContacts);
// RIGHT
const { data: contacts } = useQuery(['contacts'], fetchContacts, {
initialData: () => getCachedContacts(), // IndexedDB
staleTime: Infinity, // Never refetch automatically
});2. Uncached Meeting Searches
2. 未缓存的会议搜索
Symptom: Every search hits Supabase
Problem: 200-500ms latency on every search
Solution: Geohash-based KV caching (already implemented in meeting-proxy)
症状:每次搜索都请求Supabase
问题:每次搜索有200-500ms延迟
解决方案:基于Geohash的KV缓存(已在meeting-proxy中实现)
3. Large Bundle Blocking Interactivity
3. 大型包阻塞交互
Symptom: High TTI despite fast TTFB
Problem: JavaScript bundle blocks main thread
Solution:
typescript
// Lazy load non-critical features
const JournalAI = dynamic(() => import('./JournalAI'), { ssr: false });
const Charts = dynamic(() => import('./Charts'), { loading: () => <ChartSkeleton /> });症状:尽管TTFB很快,但TTI很高
问题:JavaScript包阻塞主线程
解决方案:
typescript
// Lazy load non-critical features
const JournalAI = dynamic(() => import('./JournalAI'), { ssr: false });
const Charts = dynamic(() => import('./Charts'), { loading: () => <ChartSkeleton /> });4. Synchronous Check-in Submission
4. 同步签到提交
Symptom: Button stays disabled during network request
Problem: User thinks it didn't work, closes app
Solution: Optimistic UI + background sync queue
症状:网络请求期间按钮保持禁用状态
问题:用户认为操作未成功,关闭应用
解决方案:乐观UI + 后台同步队列
Performance Optimization Checklist
性能优化检查清单
Before Every Deploy
每次部署前
- Bundle size delta < 5KB
- No new synchronous network calls in critical paths
- Lighthouse performance score >= 90
- Offline mode tested (disable network in DevTools)
- 包大小增量<5KB
- 关键路径中无新的同步网络调用
- Lighthouse性能评分>=90
- 离线模式已测试(在DevTools中禁用网络)
Weekly Audit
每周审计
- Review slow query logs in Supabase
- Check KV cache hit rate (should be >80%)
- Analyze Real User Metrics (RUM) for P95 load times
- Test on 3G throttled connection
- 查看Supabase中的慢查询日志
- 检查KV缓存命中率(应>80%)
- 分析真实用户指标(RUM)的P95加载时间
- 在3G限流连接上测试
Monthly Deep Dive
每月深度排查
- Profile React renders (why did this re-render?)
- Audit third-party scripts
- Review and prune unused dependencies
- Test crisis flows end-to-end on real device
- 分析React渲染(为什么会重渲染?)
- 审计第三方脚本
- 检查并清理未使用的依赖
- 在真实设备上端到端测试危机流程
Scripts Available
可用脚本
| Script | Purpose |
|---|---|
| Run Lighthouse + custom checks, file issues |
| Check KV cache hit rates and staleness |
| Automated test of crisis-critical flows |
| Track bundle size over time |
| 脚本 | 用途 |
|---|---|
| 运行Lighthouse + 自定义检查,提交问题 |
| 检查KV缓存命中率和过期情况 |
| 危机关键流程的自动化测试 |
| 跟踪包大小随时间的变化 |
Integration Points
集成点
With meeting-harvester
与meeting-harvester集成
- After harvest, warm cache for top metros
- Monitor harvest duration and meeting counts
- Alert if harvest fails (stale data = wrong meeting times)
- 数据采集完成后,预加载热门大都市的缓存
- 监控采集时长和会议数量
- 采集失败时发出警报(数据过期=会议时间错误)
With check-in system
与签到系统集成
- Analyze patterns for crisis detection
- Track submission success rate
- Monitor offline queue depth
- 分析模式以进行危机检测
- 跟踪提交成功率
- 监控离线队列深度
With contacts/sponsors
与联系人/赞助人系统集成
- Ensure offline availability
- Track time-to-display
- Monitor sync failures
- 确保离线可用性
- 跟踪显示时间
- 监控同步失败情况
When to Escalate
何时升级处理
File GitHub issue immediately if:
- Lighthouse score drops below 85
- P95 meeting search > 1 second
- Contacts page has any loading state > 200ms
- Service Worker fails to cache crisis pages
- Any user-reported "couldn't load" during crisis hours (evenings/weekends)
This is a recovery app. Performance isn't a feature - it's the difference between someone getting help and someone dying alone.
立即提交GitHub问题,如果:
- Lighthouse评分低于85
- P95会议搜索时间>1秒
- 联系人页面加载状态>200ms
- Service Worker未能缓存危机页面
- 任何用户报告危机时段(晚间/周末)“无法加载”
这是一款康复应用。性能不是一项功能——而是有人获得帮助与独自离世的区别。