what it does 2026-08-07 22:45

Next.js 15.5.0 · tidepool

Here is what your application can actually do.

28

ways in — pages people can open, endpoints anything can call, and actions your forms trigger.

Worth checking
5
Config-dependent
2
Import hops read
3
Scan time
147ms
Read this first This was read from your code without running it. A path chosen at runtime is invisible to it, so the absence of a finding is not proof that nothing is there.
If you don't read code — the nine words this report uses
Way in
A door into your application: a page someone opens, an address other software calls, or a form that submits.
Endpoint
A door for other programs rather than people. Anything that knows the address can knock — which is why who-is-asking checks matter.
GET · POST · DELETE
The kinds of knock: asking to see something, sending something in, asking to remove something.
Table
Where one kind of your data lives — users, projects, invoices. Losing one loses that data.
Outside service
Someone else's system your app relies on: Stripe moves the money, Resend sends the email. You cannot fix their outages.
Middleware
Code that runs before every request — often the doorman that checks who is asking.
Checks who is asking
The moment code verifies identity or permission before acting. Its absence before a deletion is what we flag.
Setting
A value that lives outside the code, like an API key. The same code can behave differently on your machine and in production.
file:line
An address in your code, like app/api/route.ts:12. Paste it into your AI assistant and ask.

What it can do

by ways in
  • Destroys something 5
  • Moves money 2
  • Looks something up 22
  • Saves or changes something 13
  • Emails someone 2
  • Talks to another company's service 1
Why these three come first Deleting data, moving money and changing who has access are the ones that cost real money or real trust when they go wrong. They sort above everything else on every screen. A filled mark means consequential — it does not mean broken.

What it depends on

15 things · widest reach first

Every table and outside service this application touches, and how much of it would notice if one changed.

See them as a map →  ·  or in depth →

This is the question behind "I'm afraid to touch it" Open any of these to see exactly what breaks if you rename it, change its shape, or lose it. The list is complete for names written literally in the code — anything reached through a name chosen at runtime is marked, because we cannot follow it.

5 things worth checking

most serious first
likely app/api/admin/users/[id]/route.ts:5

Deletes rows from members — with no visible check on who is asking

Nothing in the code we followed establishes the identity or permissions of whoever triggered this. This project has middleware, but its matcher does NOT cover this path — so nothing is checking upstream either.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/api/admin/users/[id]/route.ts, line 5
Behaviour: DELETE /api/admin/users/[id]
Finding: Deletes rows from `members` — with no visible check on who is asking

Before this code runs, verify who is making the request and that they are allowed to do this. Add the smallest check that achieves that, matching how the rest of this codebase already does authorisation. Keep every other behaviour exactly the same, and show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.
Walk through DELETE /api/admin/users/[id] →
likely app/api/billing/checkout/route.ts:4

Starts a Stripe checkout — this is where money is taken — with no visible check on who is asking

Nothing in the code we followed establishes the identity or permissions of whoever triggered this. This project has middleware, but its matcher does NOT cover this path — so nothing is checking upstream either.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/api/billing/checkout/route.ts, line 4
Behaviour: POST /api/billing/checkout
Finding: Starts a Stripe checkout — this is where money is taken — with no visible check on who is asking

Before this code runs, verify who is making the request and that they are allowed to do this. Add the smallest check that achieves that, matching how the rest of this codebase already does authorisation. Keep every other behaviour exactly the same, and show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.
Walk through POST /api/billing/checkout →
likely app/api/keys/route.ts:15

Deletes rows from api_keys — with no visible check on who is asking

Nothing in the code we followed establishes the identity or permissions of whoever triggered this. This project has middleware, but its matcher does NOT cover this path — so nothing is checking upstream either.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/api/keys/route.ts, line 15
Behaviour: DELETE /api/keys
Finding: Deletes rows from `api_keys` — with no visible check on who is asking

Before this code runs, verify who is making the request and that they are allowed to do this. Add the smallest check that achieves that, matching how the rest of this codebase already does authorisation. Keep every other behaviour exactly the same, and show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.
Walk through DELETE /api/keys →
likely app/api/projects/[id]/route.ts:13

Deletes rows from tasks — with no visible check on who is asking

Nothing in the code we followed establishes the identity or permissions of whoever triggered this. This project has middleware, but its matcher does NOT cover this path — so nothing is checking upstream either.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/api/projects/[id]/route.ts, line 13
Behaviour: DELETE /api/projects/[id]
Finding: Deletes rows from `tasks` — with no visible check on who is asking

Before this code runs, verify who is making the request and that they are allowed to do this. Add the smallest check that achieves that, matching how the rest of this codebase already does authorisation. Keep every other behaviour exactly the same, and show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.
Walk through DELETE /api/projects/[id] →
possible app/actions.ts:26

Named "sendOnboardingEmail", but we found nothing here that would send an email

We traced 2 other things this does, so we could read it — the promised step is not among them. Either it happens further away than we followed, or it was never wired up.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/actions.ts, line 26
Behaviour: A form calls sendOnboardingEmail()
Finding: Named "sendOnboardingEmail", but we found nothing here that would send an email

The name promises something the body never does. Either implement the promised step, or rename the function so it stops promising it. Show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.
Walk through A form calls sendOnboardingEmail() →
Middleware Middleware runs on 4 path patterns. Findings on paths it does not cover are stronger.

Every way in

7 groups · ranked by consequence

Grouped, and ranked by what a behaviour can do rather than by how big it is — a twelve-line delete endpoint outranks a four-hundred-line settings page.

API · projects 4 1 to check
API · admin 1 1 to check
API · billing 2 1 to check
API · keys 2 1 to check
Form actions 5 1 to check
API · tasks 2
Everything else (12 smaller areas) 12
2 with nothing found We found no effects in 2 of these. Some genuinely do nothing — a static page is just a page. The rest do their work further than 3 import hops away, or through code we cannot follow. Nothing found is not the same as nothing there.
← All behaviours

Endpoint · DELETE

DELETE /api/projects/[id]

Other software asks to remove something here.

app/api/projects/[id]/route.ts:13

likely

Deletes rows from tasks — with no visible check on who is asking

Nothing in the code we followed establishes the identity or permissions of whoever triggered this. This project has middleware, but its matcher does NOT cover this path — so nothing is checking upstream either.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/api/projects/[id]/route.ts, line 13
Behaviour: DELETE /api/projects/[id]
Finding: Deletes rows from `tasks` — with no visible check on who is asking

Before this code runs, verify who is making the request and that they are allowed to do this. Add the smallest check that achieves that, matching how the rest of this codebase already does authorisation. Keep every other behaviour exactly the same, and show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Deletes rows from tasks

    app/api/projects/[id]/route.ts:14

    app/api/projects/[id]/route.tsline 14
      12// GET during review and never made it down here.
      13export async function DELETE(request: Request, { params }: { params: { id: string } }) {
      14  await supabase.from('tasks').delete().eq('project_id', params.id);
      15  await supabase.from('comments').delete().eq('project_id', params.id);
      16  await supabase.from('projects').delete().eq('id', params.id);
  2. Changes something

    Deletes rows from comments

    app/api/projects/[id]/route.ts:15

    app/api/projects/[id]/route.tsline 15
      13export async function DELETE(request: Request, { params }: { params: { id: string } }) {
      14  await supabase.from('tasks').delete().eq('project_id', params.id);
      15  await supabase.from('comments').delete().eq('project_id', params.id);
      16  await supabase.from('projects').delete().eq('id', params.id);
      17  return new Response(null, { status: 204 });
  3. Changes something

    Deletes rows from projects

    app/api/projects/[id]/route.ts:16

    app/api/projects/[id]/route.tsline 16
      14  await supabase.from('tasks').delete().eq('project_id', params.id);
      15  await supabase.from('comments').delete().eq('project_id', params.id);
      16  await supabase.from('projects').delete().eq('id', params.id);
      17  return new Response(null, { status: 204 });
      18}
  4. Answers

    Answers the request

    app/api/projects/[id]/route.ts:17

    app/api/projects/[id]/route.tsline 17
      15  await supabase.from('comments').delete().eq('project_id', params.id);
      16  await supabase.from('projects').delete().eq('id', params.id);
      17  return new Response(null, { status: 204 });
      18}
← All behaviours

Endpoint · DELETE

DELETE /api/admin/users/[id]

Other software asks to remove something here.

app/api/admin/users/[id]/route.ts:5

likely

Deletes rows from members — with no visible check on who is asking

Nothing in the code we followed establishes the identity or permissions of whoever triggered this. This project has middleware, but its matcher does NOT cover this path — so nothing is checking upstream either.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/api/admin/users/[id]/route.ts, line 5
Behaviour: DELETE /api/admin/users/[id]
Finding: Deletes rows from `members` — with no visible check on who is asking

Before this code runs, verify who is making the request and that they are allowed to do this. Add the smallest check that achieves that, matching how the rest of this codebase already does authorisation. Keep every other behaviour exactly the same, and show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Deletes rows from members

    app/api/admin/users/[id]/route.ts:6

    app/api/admin/users/[id]/route.tsline 6
       4// /admin/:path* but not /api/admin/:path*.
       5export async function DELETE(request: Request, { params }: { params: { id: string } }) {
       6  await supabase.from('members').delete().eq('user_id', params.id);
       7  await supabase.from('users').delete().eq('id', params.id);
       8  return new Response(null, { status: 204 });
  2. Changes something

    Deletes rows from users

    app/api/admin/users/[id]/route.ts:7

    app/api/admin/users/[id]/route.tsline 7
       5export async function DELETE(request: Request, { params }: { params: { id: string } }) {
       6  await supabase.from('members').delete().eq('user_id', params.id);
       7  await supabase.from('users').delete().eq('id', params.id);
       8  return new Response(null, { status: 204 });
       9}
  3. Answers

    Answers the request

    app/api/admin/users/[id]/route.ts:8

    app/api/admin/users/[id]/route.tsline 8
       6  await supabase.from('members').delete().eq('user_id', params.id);
       7  await supabase.from('users').delete().eq('id', params.id);
       8  return new Response(null, { status: 204 });
       9}
      10 
← All behaviours

Endpoint · POST

POST /api/billing/checkout

Other software sends something in here.

app/api/billing/checkout/route.ts:4

likely

Starts a Stripe checkout — this is where money is taken — with no visible check on who is asking

Nothing in the code we followed establishes the identity or permissions of whoever triggered this. This project has middleware, but its matcher does NOT cover this path — so nothing is checking upstream either.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/api/billing/checkout/route.ts, line 4
Behaviour: POST /api/billing/checkout
Finding: Starts a Stripe checkout — this is where money is taken — with no visible check on who is asking

Before this code runs, verify who is making the request and that they are allowed to do this. Add the smallest check that achieves that, matching how the rest of this codebase already does authorisation. Keep every other behaviour exactly the same, and show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out body

    app/api/billing/checkout/route.ts:5

    app/api/billing/checkout/route.tsline 5
       3 
       4export async function POST(request: Request) {
       5  const body = await request.json();
       6 
       7  const session = await stripe.checkout.sessions.create({
  2. Changes something

    Starts a Stripe checkout — this is where money is taken

    app/api/billing/checkout/route.ts:7

    app/api/billing/checkout/route.tsline 7
       5const body = await request.json();
       6 
       7const session = await stripe.checkout.sessions.create({
       8  mode: 'subscription',
       9  line_items: [{ price: PRICE_ID, quantity: 1 }],
  3. Changes something

    Adds rows to subscriptions

    app/api/billing/checkout/route.ts:14

    app/api/billing/checkout/route.tsline 14
      12});
      13 
      14await supabase.from('subscriptions').insert({
      15  user_id: body.userId,
      16  status: 'pending',
  4. Answers

    Answers the request

    app/api/billing/checkout/route.ts:19

    app/api/billing/checkout/route.tsline 19
      17  });
      18 
      19  return Response.json({ url: session.url });
      20}
      21 
← All behaviours

Endpoint · DELETE

DELETE /api/keys

Other software asks to remove something here.

app/api/keys/route.ts:15

likely

Deletes rows from api_keys — with no visible check on who is asking

Nothing in the code we followed establishes the identity or permissions of whoever triggered this. This project has middleware, but its matcher does NOT cover this path — so nothing is checking upstream either.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/api/keys/route.ts, line 15
Behaviour: DELETE /api/keys
Finding: Deletes rows from `api_keys` — with no visible check on who is asking

Before this code runs, verify who is making the request and that they are allowed to do this. Add the smallest check that achieves that, matching how the rest of this codebase already does authorisation. Keep every other behaviour exactly the same, and show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out body

    app/api/keys/route.ts:16

    app/api/keys/route.tsline 16
      14 
      15export async function DELETE(request: Request) {
      16  const body = await request.json();
      17  await supabase.from('api_keys').delete().eq('id', body.id);
      18  return new Response(null, { status: 204 });
  2. Changes something

    Deletes rows from api_keys

    app/api/keys/route.ts:17

    app/api/keys/route.tsline 17
      15export async function DELETE(request: Request) {
      16  const body = await request.json();
      17  await supabase.from('api_keys').delete().eq('id', body.id);
      18  return new Response(null, { status: 204 });
      19}
  3. Answers

    Answers the request

    app/api/keys/route.ts:18

    app/api/keys/route.tsline 18
      16  const body = await request.json();
      17  await supabase.from('api_keys').delete().eq('id', body.id);
      18  return new Response(null, { status: 204 });
      19}
      20 
← All behaviours

Form action

A form calls deleteWorkspace()

app/actions.ts:14

What it does

5 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out user

    app/actions.ts:15

    app/actions.tsline 15
      13 
      14export async function deleteWorkspace(workspaceId: string) {
      15  const { user, role } = await requireMember(workspaceId);
      16  if (role !== 'owner') throw new Error('Only the owner can do that');
      17 
  2. Stops here if this fails

    Checks role !== 'owner' — otherwise it throws an error

    app/actions.ts:16

    app/actions.tsline 16
      14export async function deleteWorkspace(workspaceId: string) {
      15  const { user, role } = await requireMember(workspaceId);
      16  if (role !== 'owner') throw new Error('Only the owner can do that');
      17 
      18  await supabase.from('members').delete().eq('workspace_id', workspaceId);
  3. Changes something

    Deletes rows from members

    app/actions.ts:18

    app/actions.tsline 18
      16if (role !== 'owner') throw new Error('Only the owner can do that');
      17 
      18await supabase.from('members').delete().eq('workspace_id', workspaceId);
      19await supabase.from('projects').delete().eq('workspace_id', workspaceId);
      20await supabase.from('workspaces').delete().eq('id', workspaceId);
  4. Changes something

    Deletes rows from projects

    app/actions.ts:19

    app/actions.tsline 19
      17 
      18await supabase.from('members').delete().eq('workspace_id', workspaceId);
      19await supabase.from('projects').delete().eq('workspace_id', workspaceId);
      20await supabase.from('workspaces').delete().eq('id', workspaceId);
      21await record('workspace.delete', user.id, workspaceId);
  5. Changes something

    Deletes rows from workspaces

    app/actions.ts:20

    app/actions.tsline 20
      18  await supabase.from('members').delete().eq('workspace_id', workspaceId);
      19  await supabase.from('projects').delete().eq('workspace_id', workspaceId);
      20  await supabase.from('workspaces').delete().eq('id', workspaceId);
      21  await record('workspace.delete', user.id, workspaceId);
      22}
← All behaviours

Form action

A form calls sendOnboardingEmail()

app/actions.ts:26

possible

Named "sendOnboardingEmail", but we found nothing here that would send an email

We traced 2 other things this does, so we could read it — the promised step is not among them. Either it happens further away than we followed, or it was never wired up.

Fix prompt — paste into your AI
A static-analysis scan of this codebase flagged something worth checking.

Where: app/actions.ts, line 26
Behaviour: A form calls sendOnboardingEmail()
Finding: Named "sendOnboardingEmail", but we found nothing here that would send an email

The name promises something the body never does. Either implement the promised step, or rename the function so it stops promising it. Show me the change as a diff.

Important: if the protection already exists somewhere the scan could not follow, do not change anything — just show me where it lives.

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Reads rows from users

    app/actions.ts:27

    app/actions.tsline 27
      25// left for later, and the name never changed.
      26export async function sendOnboardingEmail(userId: string) {
      27  const { data: user } = await supabase.from('users').select('email').eq('id', userId).single();
      28  await supabase.from('users').update({ onboarded_at: new Date().toISOString() }).eq('id', userId);
      29  return user;
  2. Changes something

    Changes rows in users

    app/actions.ts:28

    app/actions.tsline 28
      26export async function sendOnboardingEmail(userId: string) {
      27  const { data: user } = await supabase.from('users').select('email').eq('id', userId).single();
      28  await supabase.from('users').update({ onboarded_at: new Date().toISOString() }).eq('id', userId);
      29  return user;
      30}
  3. Answers

    Answers the request

    app/actions.ts:29

    app/actions.tsline 29
      27  const { data: user } = await supabase.from('users').select('email').eq('id', userId).single();
      28  await supabase.from('users').update({ onboarded_at: new Date().toISOString() }).eq('id', userId);
      29  return user;
      30}
      31 
← All behaviours

Endpoint · DELETE

DELETE /api/tasks/[id]

Other software asks to remove something here.

app/api/tasks/[id]/route.ts:13

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out body

    app/api/tasks/[id]/route.ts:14

    app/api/tasks/[id]/route.tsline 14
      12 
      13export async function DELETE(request: Request, { params }: { params: { id: string } }) {
      14  const body = await request.json();
      15  const { user } = await requireMember(body.workspaceId);
      16  await supabase.from('tasks').delete().eq('id', params.id);
  2. Works something out

    Works out user

    app/api/tasks/[id]/route.ts:15

    app/api/tasks/[id]/route.tsline 15
      13export async function DELETE(request: Request, { params }: { params: { id: string } }) {
      14  const body = await request.json();
      15  const { user } = await requireMember(body.workspaceId);
      16  await supabase.from('tasks').delete().eq('id', params.id);
      17  await record('task.delete', user.id, params.id);
  3. Changes something

    Deletes rows from tasks

    app/api/tasks/[id]/route.ts:16

    app/api/tasks/[id]/route.tsline 16
      14const body = await request.json();
      15const { user } = await requireMember(body.workspaceId);
      16await supabase.from('tasks').delete().eq('id', params.id);
      17await record('task.delete', user.id, params.id);
      18return new Response(null, { status: 204 });
  4. Answers

    Answers the request

    app/api/tasks/[id]/route.ts:18

    app/api/tasks/[id]/route.tsline 18
      16  await supabase.from('tasks').delete().eq('id', params.id);
      17  await record('task.delete', user.id, params.id);
      18  return new Response(null, { status: 204 });
      19}
      20 
← All behaviours

Endpoint · POST

POST /api/billing/portal

Other software sends something in here.

app/api/billing/portal/route.ts:5

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Checks who is asking, via requireUser()

    app/api/billing/portal/route.ts:6

    app/api/billing/portal/route.tsline 6
       4 
       5export async function POST() {
       6  const user = await requireUser();
       7  const { data } = await supabase
       8    .from('subscriptions')
  2. Changes something

    Reads rows from subscriptions

    app/api/billing/portal/route.ts:7

    app/api/billing/portal/route.tsline 7
       5export async function POST() {
       6  const user = await requireUser();
       7  const { data } = await supabase
       8    .from('subscriptions')
       9    .select('customer_id')
  3. Changes something

    Opens the Stripe billing portal, where a customer can change what they pay

    app/api/billing/portal/route.ts:13

    app/api/billing/portal/route.tsline 13
      11  .single();
      12 
      13const session = await stripe.billingPortal.sessions.create({
      14  customer: data!.customer_id,
      15  return_url: process.env.APP_URL + '/settings',
  4. Answers

    Answers the request

    app/api/billing/portal/route.ts:18

    app/api/billing/portal/route.tsline 18
      16  });
      17 
      18  return Response.json({ url: session.url });
      19}
      20 
← All behaviours

Endpoint · POST

POST /api/ai/summarise

Other software sends something in here.

app/api/ai/summarise/route.ts:6

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out body

    app/api/ai/summarise/route.ts:7

    app/api/ai/summarise/route.tsline 7
       5 
       6export async function POST(request: Request) {
       7  const body = await request.json();
       8  await requireMember(body.workspaceId);
       9 
  2. Changes something

    Reads rows from comments

    app/api/ai/summarise/route.ts:10

    app/api/ai/summarise/route.tsline 10
       8await requireMember(body.workspaceId);
       9 
      10const { data: comments } = await supabase
      11  .from('comments')
      12  .select('body')
  3. Changes something

    Asks a language model to write something, streaming the reply — this costs money per request

    This takes a different path depending on the setting OPENAI_MODEL, so it may not behave the same in production as it does locally.

    app/api/ai/summarise/route.ts:15

    app/api/ai/summarise/route.tsline 15
      13  .eq('project_id', body.projectId);
      14 
      15const result = streamText({
      16  model: openai(process.env.OPENAI_MODEL ?? 'gpt-4o-mini'),
      17  prompt: `Summarise this discussion:\n${(comments ?? []).map((c) => c.body).join('\n')}`,
  4. Answers

    Answers the request

    app/api/ai/summarise/route.ts:20

    app/api/ai/summarise/route.tsline 20
      18  });
      19 
      20  return result.toTextStreamResponse();
      21}
      22 

Depends on configuration

app/api/ai/summarise/route.ts:16 This takes a different path depending on the setting OPENAI_MODEL, so it may not behave the same in production as it does locally.
← All behaviours

Endpoint · GET

GET /api/export

Other software asks to see something here.

app/api/export/route.ts:4

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out workspaceId

    app/api/export/route.ts:5

    app/api/export/route.tsline 5
       3// Debug switch left in. In development it skips the ownership filter entirely.
       4export async function GET(request: Request) {
       5  const workspaceId = new URL(request.url).searchParams.get('workspace');
       6 
       7  const query = supabase.from('files').select('*');
  2. Changes something

    Reads rows from files

    app/api/export/route.ts:7

    app/api/export/route.tsline 7
       5const workspaceId = new URL(request.url).searchParams.get('workspace');
       6 
       7const query = supabase.from('files').select('*');
       8const { data } =
       9  process.env.NODE_ENV === 'development'
  3. Works something out

    Works out data

    This takes a different path depending on the setting NODE_ENV, so it may not behave the same in production as it does locally.

    app/api/export/route.ts:8

    app/api/export/route.tsline 8
       6 
       7const query = supabase.from('files').select('*');
       8const { data } =
       9  process.env.NODE_ENV === 'development'
      10    ? await query
  4. Answers

    Answers the request

    app/api/export/route.ts:13

    app/api/export/route.tsline 13
      11      : await query.eq('workspace_id', workspaceId);
      12 
      13  return Response.json(data);
      14}
      15 

Depends on configuration

app/api/export/route.ts:9 This takes a different path depending on the setting NODE_ENV, so it may not behave the same in production as it does locally.
← All behaviours

Endpoint · POST

POST /api/invite

Other software sends something in here.

app/api/invite/route.ts:5

What it does

5 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out body

    app/api/invite/route.ts:6

    app/api/invite/route.tsline 6
       4 
       5export async function POST(request: Request) {
       6  const body = await request.json();
       7  const { user } = await requireMember(body.workspaceId);
       8 
  2. Works something out

    Works out user

    app/api/invite/route.ts:7

    app/api/invite/route.tsline 7
       5export async function POST(request: Request) {
       6  const body = await request.json();
       7  const { user } = await requireMember(body.workspaceId);
       8 
       9  const { data: invitation } = await supabase
  3. Changes something

    Adds rows to invitations

    app/api/invite/route.ts:9

    app/api/invite/route.tsline 9
       7const { user } = await requireMember(body.workspaceId);
       8 
       9const { data: invitation } = await supabase
      10  .from('invitations')
      11  .insert({ email: body.email, workspace_id: body.workspaceId, invited_by: user.id })
  4. Changes something

    Sends an email

    app/api/invite/route.ts:15

    app/api/invite/route.tsline 15
      13    .single();
      14 
      15  await sendMail(body.email, 'You have been invited', 'Come and join us on Tidepool.');
      16  return Response.json(invitation);
      17}
  5. Answers

    Answers the request

    app/api/invite/route.ts:16

    app/api/invite/route.tsline 16
      14 
      15  await sendMail(body.email, 'You have been invited', 'Come and join us on Tidepool.');
      16  return Response.json(invitation);
      17}
      18 
← All behaviours

Endpoint · PATCH

PATCH /api/tasks/[id]

Other software changes something here.

app/api/tasks/[id]/route.ts:5

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out body

    app/api/tasks/[id]/route.ts:6

    app/api/tasks/[id]/route.tsline 6
       4 
       5export async function PATCH(request: Request, { params }: { params: { id: string } }) {
       6  const body = await request.json();
       7  const { user } = await requireMember(body.workspaceId);
       8  await supabase.from('tasks').update({ status: body.status }).eq('id', params.id);
  2. Works something out

    Works out user

    app/api/tasks/[id]/route.ts:7

    app/api/tasks/[id]/route.tsline 7
       5export async function PATCH(request: Request, { params }: { params: { id: string } }) {
       6  const body = await request.json();
       7  const { user } = await requireMember(body.workspaceId);
       8  await supabase.from('tasks').update({ status: body.status }).eq('id', params.id);
       9  await record('task.update', user.id, params.id);
  3. Changes something

    Changes rows in tasks

    app/api/tasks/[id]/route.ts:8

    app/api/tasks/[id]/route.tsline 8
       6const body = await request.json();
       7const { user } = await requireMember(body.workspaceId);
       8await supabase.from('tasks').update({ status: body.status }).eq('id', params.id);
       9await record('task.update', user.id, params.id);
      10return new Response(null, { status: 204 });
  4. Answers

    Answers the request

    app/api/tasks/[id]/route.ts:10

    app/api/tasks/[id]/route.tsline 10
       8  await supabase.from('tasks').update({ status: body.status }).eq('id', params.id);
       9  await record('task.update', user.id, params.id);
      10  return new Response(null, { status: 204 });
      11}
      12 
← All behaviours

Page

Someone opens /projects/[id]

app/projects/[id]/page.tsx:1

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Reads rows from tasks

    app/projects/[id]/page.tsx:6

    app/projects/[id]/page.tsxline 6
       4export default async function ProjectPage({ params }: { params: { id: string } }) {
       5  await requireMember(params.id);
       6  const { data: tasks } = await supabase.from('tasks').select('*').eq('project_id', params.id);
       7  const { data: comments } = await supabase.from('comments').select('*').eq('project_id', params.id);
       8  return <main><pre>{JSON.stringify({ tasks, comments })}</pre></main>;
  2. Changes something

    Reads rows from comments

    app/projects/[id]/page.tsx:7

    app/projects/[id]/page.tsxline 7
       5  await requireMember(params.id);
       6  const { data: tasks } = await supabase.from('tasks').select('*').eq('project_id', params.id);
       7  const { data: comments } = await supabase.from('comments').select('*').eq('project_id', params.id);
       8  return <main><pre>{JSON.stringify({ tasks, comments })}</pre></main>;
       9}
  3. Answers

    Answers the request

    app/projects/[id]/page.tsx:8

    app/projects/[id]/page.tsxline 8
       6  const { data: tasks } = await supabase.from('tasks').select('*').eq('project_id', params.id);
       7  const { data: comments } = await supabase.from('comments').select('*').eq('project_id', params.id);
       8  return <main><pre>{JSON.stringify({ tasks, comments })}</pre></main>;
       9}
      10 
← All behaviours

Form action

A form calls archiveProject()

app/actions.ts:8

What it does

2 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out user

    app/actions.ts:9

    app/actions.tsline 9
       7 
       8export async function archiveProject(projectId: string, workspaceId: string) {
       9  const { user } = await requireMember(workspaceId);
      10  await supabase.from('projects').update({ archived: true }).eq('id', projectId);
      11  await record('project.archive', user.id, projectId);
  2. Changes something

    Changes rows in projects

    app/actions.ts:10

    app/actions.tsline 10
       8export async function archiveProject(projectId: string, workspaceId: string) {
       9  const { user } = await requireMember(workspaceId);
      10  await supabase.from('projects').update({ archived: true }).eq('id', projectId);
      11  await record('project.archive', user.id, projectId);
      12}
← All behaviours

Form action

A form calls inviteTeammate()

app/actions.ts:32

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out user

    app/actions.ts:33

    app/actions.tsline 33
      31 
      32export async function inviteTeammate(email: string, workspaceId: string) {
      33  const { user } = await requireMember(workspaceId);
      34  await supabase.from('invitations').insert({ email, workspace_id: workspaceId, invited_by: user.id });
      35  await sendMail(email, 'Join the workspace', 'You have been invited to Tidepool.');
  2. Changes something

    Adds rows to invitations

    app/actions.ts:34

    app/actions.tsline 34
      32export async function inviteTeammate(email: string, workspaceId: string) {
      33  const { user } = await requireMember(workspaceId);
      34  await supabase.from('invitations').insert({ email, workspace_id: workspaceId, invited_by: user.id });
      35  await sendMail(email, 'Join the workspace', 'You have been invited to Tidepool.');
      36}
  3. Changes something

    Sends an email

    app/actions.ts:35

    app/actions.tsline 35
      33  const { user } = await requireMember(workspaceId);
      34  await supabase.from('invitations').insert({ email, workspace_id: workspaceId, invited_by: user.id });
      35  await sendMail(email, 'Join the workspace', 'You have been invited to Tidepool.');
      36}
      37 
← All behaviours

Endpoint · POST

POST /api/files

Other software sends something in here.

app/api/files/route.ts:4

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out body

    app/api/files/route.ts:5

    app/api/files/route.tsline 5
       3 
       4export async function POST(request: Request) {
       5  const body = await request.json();
       6  await requireMember(body.workspaceId);
       7  const { data } = await supabase
  2. Changes something

    Adds rows to files

    app/api/files/route.ts:7

    app/api/files/route.tsline 7
       5const body = await request.json();
       6await requireMember(body.workspaceId);
       7const { data } = await supabase
       8  .from('files')
       9  .insert({ name: body.name, workspace_id: body.workspaceId })
  3. Answers

    Answers the request

    app/api/files/route.ts:12

    app/api/files/route.tsline 12
      10    .select()
      11    .single();
      12  return Response.json(data);
      13}
      14 
← All behaviours

Endpoint · GET

GET /api/projects/[id]

Other software asks to see something here.

app/api/projects/[id]/route.ts:4

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out body

    app/api/projects/[id]/route.ts:5

    app/api/projects/[id]/route.tsline 5
       3 
       4export async function GET(request: Request, { params }: { params: { id: string } }) {
       5  const body = new URL(request.url).searchParams;
       6  await requireMember(body.get('workspaceId')!);
       7  const { data } = await supabase.from('projects').select('*').eq('id', params.id).single();
  2. Changes something

    Reads rows from projects

    app/api/projects/[id]/route.ts:7

    app/api/projects/[id]/route.tsline 7
       5  const body = new URL(request.url).searchParams;
       6  await requireMember(body.get('workspaceId')!);
       7  const { data } = await supabase.from('projects').select('*').eq('id', params.id).single();
       8  return Response.json(data);
       9}
  3. Answers

    Answers the request

    app/api/projects/[id]/route.ts:8

    app/api/projects/[id]/route.tsline 8
       6  await requireMember(body.get('workspaceId')!);
       7  const { data } = await supabase.from('projects').select('*').eq('id', params.id).single();
       8  return Response.json(data);
       9}
      10 
← All behaviours

Page

Someone opens /settings

app/settings/page.tsx:1

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Checks who is asking, via requireUser()

    app/settings/page.tsx:5

    app/settings/page.tsxline 5
       3 
       4export default async function Settings() {
       5  const user = await requireUser();
       6  const { data } = await supabase.from('users').select('*').eq('id', user.id).single();
       7  const { data: subscription } = await supabase
  2. Changes something

    Reads rows from users

    app/settings/page.tsx:6

    app/settings/page.tsxline 6
       4export default async function Settings() {
       5  const user = await requireUser();
       6  const { data } = await supabase.from('users').select('*').eq('id', user.id).single();
       7  const { data: subscription } = await supabase
       8    .from('subscriptions')
  3. Changes something

    Reads rows from subscriptions

    app/settings/page.tsx:7

    app/settings/page.tsxline 7
       5const user = await requireUser();
       6const { data } = await supabase.from('users').select('*').eq('id', user.id).single();
       7const { data: subscription } = await supabase
       8  .from('subscriptions')
       9  .select('*')
  4. Answers

    Answers the request

    app/settings/page.tsx:12

    app/settings/page.tsxline 12
      10    .eq('user_id', user.id)
      11    .single();
      12  return <main><pre>{JSON.stringify({ data, subscription })}</pre></main>;
      13}
      14 
← All behaviours

Form action

A form calls rotateApiKey()

app/actions.ts:38

What it does

2 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Checks who is asking, via requireUser()

    app/actions.ts:39

    app/actions.tsline 39
      37 
      38export async function rotateApiKey(keyId: string) {
      39  const user = await requireUser();
      40  await supabase.from('api_keys').update({ secret: crypto.randomUUID() }).eq('id', keyId);
      41  await record('key.rotate', user.id, keyId);
  2. Changes something

    Changes rows in api_keys

    app/actions.ts:40

    app/actions.tsline 40
      38export async function rotateApiKey(keyId: string) {
      39  const user = await requireUser();
      40  await supabase.from('api_keys').update({ secret: crypto.randomUUID() }).eq('id', keyId);
      41  await record('key.rotate', user.id, keyId);
      42}
← All behaviours

Endpoint · POST

POST /api/keys

Other software sends something in here.

app/api/keys/route.ts:4

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Checks who is asking, via requireUser()

    app/api/keys/route.ts:5

    app/api/keys/route.tsline 5
       3 
       4export async function POST(request: Request) {
       5  const user = await requireUser();
       6  const body = await request.json();
       7  const { data } = await supabase
  2. Works something out

    Works out body

    app/api/keys/route.ts:6

    app/api/keys/route.tsline 6
       4export async function POST(request: Request) {
       5  const user = await requireUser();
       6  const body = await request.json();
       7  const { data } = await supabase
       8    .from('api_keys')
  3. Changes something

    Adds rows to api_keys

    app/api/keys/route.ts:7

    app/api/keys/route.tsline 7
       5const user = await requireUser();
       6const body = await request.json();
       7const { data } = await supabase
       8  .from('api_keys')
       9  .insert({ user_id: user.id, label: body.label, scope: body.scope })
  4. Answers

    Answers the request

    app/api/keys/route.ts:12

    app/api/keys/route.tsline 12
      10    .select()
      11    .single();
      12  return Response.json(data);
      13}
      14 
← All behaviours

Endpoint · GET

GET /api/projects

Other software asks to see something here.

app/api/projects/route.ts:4

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Checks who is asking, via requireUser()

    app/api/projects/route.ts:5

    app/api/projects/route.tsline 5
       3 
       4export async function GET() {
       5  const user = await requireUser();
       6  const { data } = await supabase.from('projects').select('*').eq('owner_id', user.id);
       7  return Response.json(data);
  2. Changes something

    Reads rows from projects

    app/api/projects/route.ts:6

    app/api/projects/route.tsline 6
       4export async function GET() {
       5  const user = await requireUser();
       6  const { data } = await supabase.from('projects').select('*').eq('owner_id', user.id);
       7  return Response.json(data);
       8}
  3. Answers

    Answers the request

    app/api/projects/route.ts:7

    app/api/projects/route.tsline 7
       5  const user = await requireUser();
       6  const { data } = await supabase.from('projects').select('*').eq('owner_id', user.id);
       7  return Response.json(data);
       8}
       9 
← All behaviours

Endpoint · POST

POST /api/projects

Other software sends something in here.

app/api/projects/route.ts:10

What it does

4 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Checks who is asking, via requireUser()

    app/api/projects/route.ts:11

    app/api/projects/route.tsline 11
       9 
      10export async function POST(request: Request) {
      11  const user = await requireUser();
      12  const body = await request.json();
      13  const { data } = await supabase
  2. Works something out

    Works out body

    app/api/projects/route.ts:12

    app/api/projects/route.tsline 12
      10export async function POST(request: Request) {
      11  const user = await requireUser();
      12  const body = await request.json();
      13  const { data } = await supabase
      14    .from('projects')
  3. Changes something

    Adds rows to projects

    app/api/projects/route.ts:13

    app/api/projects/route.tsline 13
      11const user = await requireUser();
      12const body = await request.json();
      13const { data } = await supabase
      14  .from('projects')
      15  .insert({ name: body.name, owner_id: user.id })
  4. Answers

    Answers the request

    app/api/projects/route.ts:18

    app/api/projects/route.tsline 18
      16    .select()
      17    .single();
      18  return Response.json(data);
      19}
      20 
← All behaviours

Page

Someone opens /dashboard

app/dashboard/page.tsx:1

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Checks who is asking, via requireUser()

    app/dashboard/page.tsx:5

    app/dashboard/page.tsxline 5
       3 
       4export default async function Dashboard() {
       5  const user = await requireUser();
       6  const { data: projects } = await supabase
       7    .from('projects')
  2. Changes something

    Reads rows from projects

    app/dashboard/page.tsx:6

    app/dashboard/page.tsxline 6
       4export default async function Dashboard() {
       5  const user = await requireUser();
       6  const { data: projects } = await supabase
       7    .from('projects')
       8    .select('*')
  3. Answers

    Answers the request

    app/dashboard/page.tsx:11

    app/dashboard/page.tsxline 11
       9    .eq('owner_id', user.id);
      10 
      11  return <main><h1>Your projects</h1><pre>{JSON.stringify(projects)}</pre></main>;
      12}
      13 
← All behaviours

Endpoint · POST

POST /api/webhooks/stripe

Other software sends something in here.

app/api/webhooks/stripe/route.ts:4

What it does

5 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out signature

    app/api/webhooks/stripe/route.ts:5

    app/api/webhooks/stripe/route.tsline 5
       3 
       4export async function POST(request: Request) {
       5  const signature = request.headers.get('stripe-signature');
       6  const raw = await request.text();
       7 
  2. Works something out

    Works out raw

    app/api/webhooks/stripe/route.ts:6

    app/api/webhooks/stripe/route.tsline 6
       4export async function POST(request: Request) {
       5  const signature = request.headers.get('stripe-signature');
       6  const raw = await request.text();
       7 
       8  // Verifying the signature IS the authorisation here: it proves the request
  3. Changes something

    Verifies the request really came from Stripe

    app/api/webhooks/stripe/route.ts:10

    app/api/webhooks/stripe/route.tsline 10
       8// Verifying the signature IS the authorisation here: it proves the request
       9// came from Stripe and not from someone who guessed the URL.
      10const event = stripe.webhooks.constructEvent(
      11  raw,
      12  signature!,
  4. Changes something

    Adds or changes rows in subscriptions

    • Adds rows to invoices

    app/api/webhooks/stripe/route.ts:16

    app/api/webhooks/stripe/route.tsline 16
      14);
      15 
      16if (event.type === 'checkout.session.completed') {
      17  await supabase.from('subscriptions').upsert({ status: 'active' });
      18  await supabase.from('invoices').insert({ amount: 0 });
  5. Answers

    Answers the request

    app/api/webhooks/stripe/route.ts:21

    app/api/webhooks/stripe/route.tsline 21
      19  }
      20 
      21  return Response.json({ received: true });
      22}
      23 
← All behaviours

Page

Someone opens /admin

app/admin/page.tsx:1

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Changes something

    Reads rows from users

    app/admin/page.tsx:5

    app/admin/page.tsxline 5
       3export default async function AdminPage() {
       4  // Reached only through middleware, which does cover /admin.
       5  const { data: users } = await supabase.from('users').select('*');
       6  const { data: invoices } = await supabase.from('invoices').select('*');
       7  return <main><pre>{JSON.stringify({ users, invoices })}</pre></main>;
  2. Changes something

    Reads rows from invoices

    app/admin/page.tsx:6

    app/admin/page.tsxline 6
       4  // Reached only through middleware, which does cover /admin.
       5  const { data: users } = await supabase.from('users').select('*');
       6  const { data: invoices } = await supabase.from('invoices').select('*');
       7  return <main><pre>{JSON.stringify({ users, invoices })}</pre></main>;
       8}
  3. Answers

    Answers the request

    app/admin/page.tsx:7

    app/admin/page.tsxline 7
       5  const { data: users } = await supabase.from('users').select('*');
       6  const { data: invoices } = await supabase.from('invoices').select('*');
       7  return <main><pre>{JSON.stringify({ users, invoices })}</pre></main>;
       8}
       9 
← All behaviours

Every request

Every request to /dashboard/:path*, /projects/:path*, /settings/:path*, /admin/:path*, before anything else

middleware.ts:1

What it does

3 steps
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Works something out

    Works out token

    middleware.ts:5

    middleware.tsline 5
       3 
       4export function middleware(request: NextRequest) {
       5  const token = request.cookies.get('session');
       6  if (!token) {
       7    return NextResponse.redirect(new URL('/login', request.url));
  2. Stops here if this fails

    Checks !token — otherwise it stops here

    middleware.ts:6

    middleware.tsline 6
       4export function middleware(request: NextRequest) {
       5  const token = request.cookies.get('session');
       6  if (!token) {
       7    return NextResponse.redirect(new URL('/login', request.url));
       8  }
  3. Answers

    Answers the request

    middleware.ts:9

    middleware.tsline 9
       7    return NextResponse.redirect(new URL('/login', request.url));
       8  }
       9  return NextResponse.next();
      10}
      11 
← All behaviours

Page

Someone opens the home page

app/page.tsx:1

What it does

1 step
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Answers

    Answers the request

    app/page.tsx:2

    app/page.tsxline 2
       1export default function Home() {
       2  return <main><h1>Tidepool</h1><p>Somewhere for a team to keep its work.</p></main>;
       3}
       4 
← All behaviours

Page

Someone opens /pricing

app/pricing/page.tsx:1

What it does

1 step
Read top to bottom These are the statements of this behaviour in the order they are written. A step marked stops here ends everything below it when its check fails. Work reached through other files is listed but not placed in sequence — we can see that it happens, not exactly when.
  1. Answers

    Answers the request

    app/pricing/page.tsx:2

    app/pricing/page.tsxline 2
       1export default function Pricing() {
       2  return <main><h1>Pricing</h1><p>Free while we are small.</p></main>;
       3}
       4 
← All behaviours

table

members

What breaks if you change it

Rename or remove this table and 11 behaviours break.

2 places can delete from it. Data removed here is gone for everything above.

What happens next

If you remove or rename members…

12 behaviours affected in total

Left with nothing keeping it up to date worked out 3

Every behaviour that wrote to these is in the list above. They would still exist and still be readable — but nothing would be updating them any more. This is the failure with no error message.

Keeps running, on data that has gone stale worked out 1

These read the tables above. They would not crash and they would not warn you — they would carry on serving whatever was there when the writing stopped.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

projects

What breaks if you change it

Rename or remove this table and 7 behaviours break.

2 places can delete from it. Data removed here is gone for everything above.

2 places write to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename projects…

9 behaviours affected in total

Left with nothing keeping it up to date worked out 2

Every behaviour that wrote to these is in the list above. They would still exist and still be readable — but nothing would be updating them any more. This is the failure with no error message.

Keeps running, on data that has gone stale worked out 2

These read the tables above. They would not crash and they would not warn you — they would carry on serving whatever was there when the writing stopped.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

audit_log

What breaks if you change it

Rename or remove this table and 5 behaviours break.

5 places write to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename audit_log…

5 behaviours affected in total

Stops working straight away in the code 5

These name it directly, so they break the moment it is gone.

Left with nothing keeping it up to date worked out 1

Every behaviour that wrote to these is in the list above. They would still exist and still be readable — but nothing would be updating them any more. This is the failure with no error message.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

tasks

What breaks if you change it

Rename or remove this table and 4 behaviours break.

2 places can delete from it. Data removed here is gone for everything above.

1 place writes to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename tasks…

5 behaviours affected in total

Stops working straight away in the code 4

These name it directly, so they break the moment it is gone.

Left with nothing keeping it up to date worked out 1

Every behaviour that wrote to these is in the list above. They would still exist and still be readable — but nothing would be updating them any more. This is the failure with no error message.

Keeps running, on data that has gone stale worked out 1

These read the tables above. They would not crash and they would not warn you — they would carry on serving whatever was there when the writing stopped.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

users

What breaks if you change it

Rename or remove this table and 4 behaviours break.

1 place can delete from it. Data removed here is gone for everything above.

1 place writes to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename users…

4 behaviours affected in total

Stops working straight away in the code 4

These name it directly, so they break the moment it is gone.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

subscriptions

What breaks if you change it

Rename or remove this table and 4 behaviours break.

2 places write to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename subscriptions…

5 behaviours affected in total

Stops working straight away in the code 4

These name it directly, so they break the moment it is gone.

Left with nothing keeping it up to date worked out 1

Every behaviour that wrote to these is in the list above. They would still exist and still be readable — but nothing would be updating them any more. This is the failure with no error message.

Keeps running, on data that has gone stale worked out 1

These read the tables above. They would not crash and they would not warn you — they would carry on serving whatever was there when the writing stopped.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

comments

What breaks if you change it

Rename or remove this table and 3 behaviours break.

1 place can delete from it. Data removed here is gone for everything above.

What happens next

If you remove or rename comments…

3 behaviours affected in total

Stops working straight away in the code 3

These name it directly, so they break the moment it is gone.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

api_keys

What breaks if you change it

Rename or remove this table and 3 behaviours break.

1 place can delete from it. Data removed here is gone for everything above.

2 places write to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename api_keys…

3 behaviours affected in total

Stops working straight away in the code 3

These name it directly, so they break the moment it is gone.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

invoices

What breaks if you change it

Rename or remove this table and 2 behaviours break.

1 place writes to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename invoices…

2 behaviours affected in total

Stops working straight away in the code 2

These name it directly, so they break the moment it is gone.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

service

Stripe

What breaks if you change it

If Stripe is unavailable or changes its API, 2 behaviours stop working.

2 places send something to it. Those calls have effects outside your application that you cannot undo.

What happens next

If Stripe stops responding…

2 behaviours affected in total

Stops working straight away in the code 2

These call it directly. Whatever they were doing with it will fail.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

files

What breaks if you change it

Rename or remove this table and 2 behaviours break.

1 place writes to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename files…

2 behaviours affected in total

Stops working straight away in the code 2

These name it directly, so they break the moment it is gone.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

invitations

What breaks if you change it

Rename or remove this table and 2 behaviours break.

2 places write to it — change its shape and each of those needs updating too.

What happens next

If you remove or rename invitations…

2 behaviours affected in total

Stops working straight away in the code 2

These name it directly, so they break the moment it is gone.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

service

email

What breaks if you change it

If email is unavailable or changes its API, 2 behaviours stop working.

2 places send something to it. Those calls have effects outside your application that you cannot undo.

What happens next

If email stops responding…

2 behaviours affected in total

Stops working straight away in the code 2

These call it directly. Whatever they were doing with it will fail.

Left with nothing keeping it up to date worked out 1

Every behaviour that wrote to these is in the list above. They would still exist and still be readable — but nothing would be updating them any more. This is the failure with no error message.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

table

workspaces

What breaks if you change it

Rename or remove this table and 1 behaviour breaks.

1 place can delete from it. Data removed here is gone for everything above.

What happens next

If you remove or rename workspaces…

1 behaviour affected in total

Stops working straight away in the code 1

These name it directly, so they break the moment it is gone.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

service

language model

What breaks if you change it

If language model is unavailable or changes its API, 1 behaviour stops working.

1 place sends something to it. Those calls have effects outside your application that you cannot undo.

What happens next

If language model stops responding…

1 behaviour affected in total

Stops working straight away in the code 1

These call it directly. Whatever they were doing with it will fail.

How far to trust this Wave one is what the code literally references. Everything after it is worked out from the graph, so treat it as where to look rather than as what will happen.

Only names written literally in the code are here at all. Anything reached dynamically is missing from every wave.
← All behaviours

15 dependencies · 24 connections

Everything this application leans on.

Two things are joined when some behaviour reaches both — so a line means they get changed together, whether or not anyone meant them to.

Map of the tables and services this application depends on 15 resources. Larger means more behaviours reach it. Lines connect resources that some behaviour uses together. The same information is listed as text under "What it depends on". members projects audit_log tasks users subscriptions comments api_keys invoices Stripe files invitations email workspaces language model
something can delete from it read or written only outside service size = how much reaches it
How to read it members is the centre of gravity — 11 behaviours reach it. 7 of these are ones something can delete from. Click any node to see exactly what breaks if you change it.
What this picture leaves out Only names written literally in the code can be placed. Anything reached through a name chosen at runtime is missing from this map entirely — not drawn faintly, not drawn at all — so treat it as a floor on how connected your application is, never a ceiling.
← All behaviours

15 dependencies · in depth

The same map, with room to breathe.

Drag to turn it. Nearer means larger and brighter; the clusters are groups of tables your application almost always touches together.

See it flat instead →

Three-dimensional map of the tables and services this application depends on 15 resources arranged in depth. Larger and brighter means nearer and more depended upon. The same information is listed as text under "What it depends on". language model comments tasks invitations workspaces members projects files email audit_log users invoices subscriptions Stripe api_keys Drag to turn · scroll to zoom
something can delete from it read or written only outside service
Why depth rather than decoration Forced onto one plane, a densely-connected graph presses unrelated clusters into each other simply because there is nowhere else for them to go. A third axis gives them somewhere, so what you are looking at is closer to the shape of the data. Everything here is the same information as the flat map and the text list — no claim is made in this view that is not made in those.
What this picture leaves out Only names written literally in the code can be placed. Anything reached through a name chosen at runtime is absent entirely, so treat this as a floor on how connected your application is, never a ceiling.
← All behaviours

3 scans · 22:45:12 to 22:45:21

Your application, over time.

Move along the track to see what the system looked like at each scan, and what moved to get there.

2026-08-07 22:45:12

Ways in
28
Things it does
113
Worth checking
3
Moved
0
Where the record starts This is the first scan we have. There is nothing before it to compare against, so nothing is marked as changed — that is an absence of history, not an absence of change.

2026-08-07 22:45:16

Ways in
28
Things it does
112-1
Worth checking
4+1
Moved
1

Changed

A form calls sendOnboardingEmail()

No longer: Sends an email
!1 new thing worth checking

2026-08-07 22:45:21

Ways in
28
Things it does
108-4
Worth checking
5+1
Moved
1

Changed

DELETE /api/projects/[id]

No longer: Checks who is asking, via requireUser()
No longer: Reads rows from members
No longer: Checks the current session
No longer: Checks who is signed in
!1 new thing worth checking
What the line is The solid line is how many ways into the application there were at each scan. The dashed line is how many things were worth checking. Only the most recent scans are kept — history is pruned so the file cannot grow without limit.
← All behaviours

Compared with 2026-08-07 22:45

What moved since the last scan

Changed

DELETE /api/projects/[id]

No longer: Checks who is asking, via requireUser()
No longer: Reads rows from members
No longer: Checks the current session
No longer: Checks who is signed in
!1 new thing worth checking
27 unchanged, not listed Only what moved is shown. A behaviour losing an effect is ranked above one gaining a new one — an email that quietly stopped sending is the failure you cannot otherwise detect.