看起来像产品
页面、卡片、假球员、几个按钮。它能解释想法,但按钮背后没有身份、数据和规则。适合演示,不适合把朋友真的叫进来。
一开始我问的是一个很大的问题:这个 site 到底是不是 web app,它能承载多少人,距离成熟的网球工具还有多远?真正让产品开始前进的,却是后来那句小得多的话:不用做到第三层,就是一个 East Auckland 的网球约球工具。这句话没有少做一点工作。它只是把工作从「造平台」改成了「让下一场球真的发生」。两晚之后,Tennis Buddy 上线:能找到附近球友、邀请或开 Open Game、加入比赛、看参与者、在比赛里聊天。也留下一个比技术更值钱的结论:MVP 不是缩水版大产品,是一个小承诺从头到尾都是真的。
「网球工具」这个词太大了。它可以只是一个好看的页面,也可以是一套真的能约到人的产品,还可以继续长成俱乐部、赛事、排名、场地和社交网络。最危险的做法是三层都沾一点,最后每一层都不完整。
页面、卡片、假球员、几个按钮。它能解释想法,但按钮背后没有身份、数据和规则。适合演示,不适合把朋友真的叫进来。
真实登录、真实资料、真实比赛、真实参加者。用户能发现人,发邀请或开局,对方能加入,双方在同一场比赛里继续沟通。
排名、比分、赛事、教练、订场、通知、俱乐部后台、审核与运营。它们都合理,但不是第一批朋友测试「能不能约到一场球」所必需的。
不用做到第三层。就是一个 East Auckland 的网球约球工具。
范围砍下来之后,产品就不该按页面来想,而该按动作来想。首页、资料页、比赛页只是容器;真正的骨架是一个人从「今晚想打球」走到「两个人都知道几点、在哪儿见」的路径。
动作需要真实身份,但没必要再造一套密码系统。产品用 ChatGPT 登录识别人;邮件只留在服务端,不会出现在公开球员资料里。
D1 里只有 clubs、profiles、games、game_players、game_messages。没有 feed、follow、ranking。数据模型和产品承诺一样小。
不能邀请自己,比赛不能超员,没加入的人不能读比赛聊天。按钮只是入口;真正决定「可不可以」的是后端条件写入与权限检查。
旧方向依赖另一套托管和数据库组合。重建时我没有试图逐页搬家,而是把核心闭环重新落在 Sites + D1 上。迁移不是把旧房子的家具全搬进来,而是趁搬家重新问一次:哪些东西真的是家?
第一版首页放过一张生成的红土球场照片。它有气氛,但像赛事海报,不像一个每天打开的本地工具。最后照片被整张删掉,换成纯 CSS 画出的抽象红土球场。
森林绿、红土橙、白 / 薄荷 / 桃色分区保留了 Roland-Garros 的身份;球场本身则按 23.77m × 10.97m 的双打尺寸重画,补齐单打边线、发球线、中心线和网。视觉不再借一张图讲「网球」,它自己就是产品结构的一部分。
页面能 build、按钮都在,不代表用户走得通。第一轮真实使用暴露的不是架构大事故,而是三处「设计时觉得可以,用户一走就卡住」的小地方。它们很不起眼,但每一个都挡在下一场球前面。
Onboarding 原本要求从固定列表选 Home Club。真实用户输入 Koru Tennis Club 时无处可去。修复后,Level 可以留空,Home Club 可以主动新增并保存;如果名字已经存在,会忽略大小写复用,不重复造一间俱乐部。
自己的卡片不该出现 Invite——邀请自己没有意义,也被后端明确禁止。所以自己的动作是 Edit profile;其他人的卡片才出现 Invite。这不是文案差异,是产品规则在 UI 上说人话。
只有日期和球场还不够。卡片补上了 Host、已加入的人、club、准确时间、View Game 和 Join Game。用户在按下 Join 前,应该知道自己要加入的是一场什么球。
测试账号 A 不填 Level,新建 Koru Tennis Club;账号 B 完成资料并在同一间 club 开一场 Open Game。A 的 People Nearby 里能看到 B,比赛卡能看到 B 是 Host、时间和 club;A 按下 Join 后,D1 里的两名参加者都变成 joined。不是组件存在,是整条路真的走完。
第一批朋友不需要一百个功能,但他们需要同一场比赛不会多出第五个人、旧比赛不会永远挂在首页、打开页面不会偷偷改写自己的资料。这些工作不适合做发布海报,却决定产品是不是一场演示。
Open Game 用条件写入防重复 Join 和超容量竞态。不是先在前端数人数再祈祷没人同时点击。
过期但没凑齐的比赛自动取消;已经确认或满员的比赛自动完成。列表呈现的是现在,不是数据库的化石层。
公开比赛和个人比赛各自按 24 场分页,参加者查询限制批次。East Auckland 的工具不需要假装服务百万用户,但也不该随着第 25 场球突然变形。
回访用户打开页面时,不再更新 profile 时间戳,也不反复重写 seed club。一次普通的 GET 不该制造新的事实。
Tennis Buddy 现在仍然不是一个成熟的全栈网球平台。它没有以下这些东西——而且第一版不该有。朋友测试的任务只有一个:这条本地约球闭环,能不能比群聊里喊一声更清楚、更容易发生?
如果第一批人愿意用它约到球,下一步由真实摩擦决定:也许是提醒,也许是搜索筛选,也许是取消与候补。如果没人用,排名和赛事系统只会把一个错误答案做得更完整。
产品范围不是「我们还没来得及做什么」,而是「这一版明确答应了什么」。
两晚里最重要的工作不是写出了多少代码,而是不断把问题缩小、把证据做实:从「成熟网球工具」缩到 East Auckland 的下一场球;从「页面能打开」推进到两个账号真的能相遇;从「按钮在那里」推进到权限、容量和生命周期都说得通。
AI 让建造速度变快之后,真正稀缺的反而更清楚了:不是生成更多功能,而是决定什么值得成为一个完整承诺,再给自己一条能看见它是否兑现的路。
—— AI 学习记录 #08 · 一次从产品边界到公开上线的真实建造复盘。
打开 Tennis Buddy →