面向在 Lovable 上构建的应用
你的应用没有服务器。它照样有 bug。
你的应用做的每件事,都发生在两个普通监控工具看不到的地方:浏览器,和 Supabase。Sixty 从页面内部同时测量这两处 —— 查询以及它们返回了什么、实时通道、人们点了什么以及有没有生效,还有这些各自有多快 —— 然后把每次发布和上一次作比较,并把找到的东西交给写这段代码的助手。
它测量什么
- 页面发出的每一条 Supabase 查询
- 每次交互跑了几条、每条返回多少行、响应有多大、花了多久。「变慢了」几乎总是最后落在这里。
- 页面有多快,按路由统计
- 基于所有加载过它的人的真实 p75 的 LCP、INP、CLS 和 TTFB —— 不是某台机器上的实验室分数。并按国家拆开,因为一个在你这儿没问题的页面,在延迟更差的地方可能根本用不了。
- 人们点了什么,以及有没有生效
- 什么都没发生的点击、在同一个控件上反复点击,以及会把页面重绘几百次的交互。
- 认证、存储、Edge Function 和实时
- 一个 Supabase 应用是四个服务,不是一个。助手理解每个端点是做什么的,而不只是它花了多少时间 —— 所以一次被拒绝的登录、一个不存在的列、一次被驳回的写入、一个订阅失败的通道,都会作为它们本身抵达,而不是变成一次匿名的「请求失败」。
- 代码,在它运行之前
- 一个没有处理函数的按钮、一个依赖注定让它永远重跑的副作用、一个从不清理的订阅。这些在运行时什么都不会发出 —— 一个从未被挂上的处理函数是没法测量的 —— 所以它们是在构建时从源码里读出来的,并按这个文件实际承接多少流量来排序。
它能找到什么
这里的每一项都会和上一个版本作比较,所以发现指向的是那次改动,而不是某一天。
一条开始返回全部数据的查询
重写的过程中丢了一个过滤条件,一条原本返回 30 行的查询现在返回 30,000 行。对着你项目里那几行数据它是瞬间完成的,所以在真实数据进来之前,什么都不像出了问题。
诞生在一次渲染内部的 N+1
一次取数被搬进了列表里,一条查询变成四十条。每一条都很快,所以单看任何一次请求都没问题 —— 错的只有次数,而次数正是你能装的其他任何东西都不会告诉你的。
用户看到别人的行 —— 或者谁的都看不到
当行级安全策略缺失或写错时,Postgres 不会报错。它要么返回本不该出现的行,要么一行都不返回,页面就渲染成空白。Sixty 两边都看得到:数据库在策略下拒绝的语句,以及它放行、却从有数据变成空的那些语句。
一个永远加载不完的页面
一个出现十二秒后还挂在屏幕上的加载动画 —— 通常是一次在 catch 里失败的请求,或者一个被置为 true 却再没被置回去的加载标志。
结构在代码脚下漂移
代码期待、而数据库已经没有的某个列、表或关联。它在运行时失败,在浏览器里,落在加载了那个页面的人身上。
没人报告过的错误
按它们在代码中发生的位置归类,包括那些不会让任何东西崩溃的 React 失败 —— 错误边界接住它,显示一块空白区域,然后记录下来。
同一个实时通道,被订阅了三遍
一个副作用打开了订阅却从不清理,于是每次渲染都再加一个。什么都不抛,什么都不慢:用户只是看着每条新消息出现三遍,与此同时连接数正朝着那个会让整个功能对所有人关闭的上限爬。
一个测出来什么都没做的按钮
点了,却没有请求发出、没有数据变化、没有页面跳转 —— 通常是一个从未接上的处理函数。再加上随后而来的连点,那是人们在第一次点击看起来没反应时会做的事。
一次点击把页面重绘几百遍
一个渲染循环。它从不报错,也从不让请求失败;它只是把拿着手机那个人的电耗干。
被拒绝的会话
发往某个端点的调用里有一部分返回 401 或 403,或者在需要令牌的地方令牌已经过期。在一个没有服务器的应用里,这是唯一能看见「被拒绝」的地方。
你的动态里会出现什么
不是一块需要你去读的仪表盘。每条发现一张卡片,上面有数字、有改变了它们的那个版本,还有你的编码助手写出修复所需要的证据。
这是复原出来的。这些确实是检测器会从一个已发布的 Supabase 应用中产出的故障 —— 但和 Node 的例子不同,它们不是某个你能跑起来的脚本的输出,所以是画出来的,而不是引用来的。
select:orders这条查询会返回表里的所有订单,然后在浏览器里过滤。它是对着一个只有四十行的项目写出来的,那时候它是瞬间完成的。
在 /orders 上,自上上次发布起
select:invoices昨天还会返回发票的同一条查询,今天一条都不返回。什么都没失败:某条策略开始把所有东西都过滤掉了,页面就渲染成空白。
在 /invoices 上,自上次发布起
如何安装
这东西不用你手动装。往 Lovable 的聊天框里粘一段提示词,它就会把 agent 接进去 —— 一个 Vite 插件、一次 init 调用,以及一条转发遥测的路由,好让任何密钥都不会进到你的打包产物里。登录之后,这段提示词会连同你的密钥一起替你写好。
npm install @sixty-sh/supabase
// vite.config.ts
import drift from '@sixty-sh/supabase/vite'
export default defineConfig({ plugins: [react(), drift()] })
// src/integrations/supabase/client.ts — the file every Lovable app has
import { withSixty } from '@sixty-sh/supabase'
export const supabase = withSixty(createClient(URL, ANON_KEY), {
key: 'sixty_pk_…',
service: 'my-app',
endpoint: 'https://ingest.sixty.sh',
})然后发布两次。比较锚定的是发布而不是时钟,所以第一条发现会在你下一次发布之后到来 —— 只有一个版本时,没有东西可比。
它不做什么
- 来自已发布构建的发现指向的是查询和路由,而不是你源码里的某一行。生产构建是压缩过的,而能还原它的 sourcemap 目前还没被读取,所以你拿到的是「这个页面上的这条查询」,而不是「这个文件,第 40 行」。
- Lovable 的预览不够。它跑的是没有带指纹资源的开发服务器,所以没有任何版本可以让比较锚定在上面 —— 比较需要两次真实的发布。
- 这里不监视任何服务器,因为你没有服务器。如果你之后加了自己的 edge function,它们需要 Node agent 才能被看见。
如果你是因为下面某一条来的
看看你的上一次改动做了什么。
试试看!在做别的东西?可以看看 Node 服务、React Native 应用 —— 那些页面不一样,我们能看到的东西也不一样。