← Gimbo's Universe / Ideas / Tennis Buddy 建造复盘
建造复盘 · AI 学习记录 #08 · 2026.07

我没造一个网球平台,
只把东区的下一场球
做成了.

一开始我问的是一个很大的问题:这个 site 到底是不是 web app,它能承载多少人,距离成熟的网球工具还有多远?真正让产品开始前进的,却是后来那句小得多的话:不用做到第三层,就是一个 East Auckland 的网球约球工具。这句话没有少做一点工作。它只是把工作从「造平台」改成了「让下一场球真的发生」。两晚之后,Tennis Buddy 上线:能找到附近球友、邀请或开 Open Game、加入比赛、看参与者、在比赛里聊天。也留下一个比技术更值钱的结论:MVP 不是缩水版大产品,是一个小承诺从头到尾都是真的。

范围
East Auckland · 找人约球
建造
2 晚 · 8 次可追溯提交
验证
双账号 · 本地 D1 · 真实 Join 流程

成熟有三层,
我只拿第二层

「网球工具」这个词太大了。它可以只是一个好看的页面,也可以是一套真的能约到人的产品,还可以继续长成俱乐部、赛事、排名、场地和社交网络。最危险的做法是三层都沾一点,最后每一层都不完整。

1
Demo layer

看起来像产品

页面、卡片、假球员、几个按钮。它能解释想法,但按钮背后没有身份、数据和规则。适合演示,不适合把朋友真的叫进来。

3
Platform layer

完整网球平台

排名、比分、赛事、教练、订场、通知、俱乐部后台、审核与运营。它们都合理,但不是第一批朋友测试「能不能约到一场球」所必需的。

那句真正的产品需求

不用做到第三层。就是一个 East Auckland 的网球约球工具。

不是功能清单,
是一场球怎么发生

范围砍下来之后,产品就不该按页面来想,而该按动作来想。首页、资料页、比赛页只是容器;真正的骨架是一个人从「今晚想打球」走到「两个人都知道几点、在哪儿见」的路径。

发现附近球友 邀请 / Host Join / Accept 比赛内聊天 上场打球
Identity

先知道谁是谁

动作需要真实身份,但没必要再造一套密码系统。产品用 ChatGPT 登录识别人;邮件只留在服务端,不会出现在公开球员资料里。

World state

五张表够了

D1 里只有 clubs、profiles、games、game_players、game_messages。没有 feed、follow、ranking。数据模型和产品承诺一样小。

Rules

规则放在服务端

不能邀请自己,比赛不能超员,没加入的人不能读比赛聊天。按钮只是入口;真正决定「可不可以」的是后端条件写入与权限检查。

旧方向依赖另一套托管和数据库组合。重建时我没有试图逐页搬家,而是把核心闭环重新落在 Sites + D1 上。迁移不是把旧房子的家具全搬进来,而是趁搬家重新问一次:哪些东西真的是家?

一张生成图被删掉后,
球场反而更像自己

Roland-Garros identity · code-native court

不是贴一张网球照片,
是让页面长出球场.

第一版首页放过一张生成的红土球场照片。它有气氛,但像赛事海报,不像一个每天打开的本地工具。最后照片被整张删掉,换成纯 CSS 画出的抽象红土球场。

森林绿、红土橙、白 / 薄荷 / 桃色分区保留了 Roland-Garros 的身份;球场本身则按 23.77m × 10.97m 的双打尺寸重画,补齐单打边线、发球线、中心线和网。视觉不再借一张图讲「网球」,它自己就是产品结构的一部分。

三个很小的洞,
足以让整条闭环漏水

页面能 build、按钮都在,不代表用户走得通。第一轮真实使用暴露的不是架构大事故,而是三处「设计时觉得可以,用户一走就卡住」的小地方。它们很不起眼,但每一个都挡在下一场球前面。

01

列表里没有我的俱乐部

Onboarding 原本要求从固定列表选 Home Club。真实用户输入 Koru Tennis Club 时无处可去。修复后,Level 可以留空,Home Club 可以主动新增并保存;如果名字已经存在,会忽略大小写复用,不重复造一间俱乐部。

02

People Nearby 里只有我自己

自己的卡片不该出现 Invite——邀请自己没有意义,也被后端明确禁止。所以自己的动作是 Edit profile;其他人的卡片才出现 Invite。这不是文案差异,是产品规则在 UI 上说人话。

03

Open Game 看不出我在加入谁

只有日期和球场还不够。卡片补上了 Host、已加入的人、club、准确时间、View Game 和 Join Game。用户在按下 Join 前,应该知道自己要加入的是一场什么球。

Way to see · 不靠想象验收

两个账号,一间新 club,一场真实 Join.

测试账号 A 不填 Level,新建 Koru Tennis Club;账号 B 完成资料并在同一间 club 开一场 Open Game。A 的 People Nearby 里能看到 B,比赛卡能看到 B 是 Host、时间和 club;A 按下 Join 后,D1 里的两名参加者都变成 joined。不是组件存在,是整条路真的走完。

功能会让人试用,
边界才让人敢用

第一批朋友不需要一百个功能,但他们需要同一场比赛不会多出第五个人、旧比赛不会永远挂在首页、打开页面不会偷偷改写自己的资料。这些工作不适合做发布海报,却决定产品是不是一场演示。

Capacity

加入必须抗重复与超员

Open Game 用条件写入防重复 Join 和超容量竞态。不是先在前端数人数再祈祷没人同时点击。

Lifecycle

比赛会自然退场

过期但没凑齐的比赛自动取消;已经确认或满员的比赛自动完成。列表呈现的是现在,不是数据库的化石层。

Scale

列表从第一天就有边界

公开比赛和个人比赛各自按 24 场分页,参加者查询限制批次。East Auckland 的工具不需要假装服务百万用户,但也不该随着第 25 场球突然变形。

Quiet reads

读取就是读取

回访用户打开页面时,不再更新 profile 时间戳,也不反复重写 seed club。一次普通的 GET 不该制造新的事实。

边界不是欠债清单,
是这版产品的形状

Tennis Buddy 现在仍然不是一个成熟的全栈网球平台。它没有以下这些东西——而且第一版不该有。朋友测试的任务只有一个:这条本地约球闭环,能不能比群聊里喊一声更清楚、更容易发生?

Community feedRankingsScoringCourt bookingCoachesTournamentsNotificationsClub admin

如果第一批人愿意用它约到球,下一步由真实摩擦决定:也许是提醒,也许是搜索筛选,也许是取消与候补。如果没人用,排名和赛事系统只会把一个错误答案做得更完整。

产品范围不是「我们还没来得及做什么」,而是「这一版明确答应了什么」。

MVP 不是
缩水版大产品.
是一个小承诺,
从头到尾都是真的.

两晚里最重要的工作不是写出了多少代码,而是不断把问题缩小、把证据做实:从「成熟网球工具」缩到 East Auckland 的下一场球;从「页面能打开」推进到两个账号真的能相遇;从「按钮在那里」推进到权限、容量和生命周期都说得通。

AI 让建造速度变快之后,真正稀缺的反而更清楚了:不是生成更多功能,而是决定什么值得成为一个完整承诺,再给自己一条能看见它是否兑现的路。

—— AI 学习记录 #08 · 一次从产品边界到公开上线的真实建造复盘。

打开 Tennis Buddy →