# Project folder and config

Squiggle keeps everything for a project in a .squiggle folder at the project root: your flows, your settings, and what each run records. Here is what each part holds, what to commit, and every setting in config.jsonc.

## What’s in .squiggle

Pick an entry to see what it holds and whether it belongs in git. Studio, the `squiggle` command and the MCP server all use the same folder.

## What to commit

Commit `flows/`, `interfaces/` and `config.jsonc`. Everything else is recorded on your Mac by use, and can grow to several gigabytes. `squiggle init` writes `.squiggle/.gitignore`, which ignores everything except those three and itself. You can edit it: Squiggle never rewrites it.

A `.gitignore` rule doesn’t apply to files git already tracks. If you committed part of the folder before, untrack it:

```shell
git rm -r --cached .squiggle/runs
```

## Declare a screen’s interface

Before you build a screen, you can write down the controls it will have, one file per screen in `.squiggle/interfaces/`. The [report card](https://squiggle.sh/docs/run/card) then checks the built screen against it, and `squiggle agent validate` accepts a flow that targets the declared controls before the screen exists.

```
{
  "route": "settings",
  "path": "/settings",
  "controls": [
    { "testID": "settings-save", "role": "button", "leadsTo": "back" },
    { "testID": "settings-delete-account", "role": "button", "destructive": true }
  ]
}
```

| Key | What it holds |
| --- | --- |
| `route` | The screen’s route name, the name flows and screen memory use: settings for app/settings.tsx. The navigator chain, such as (tabs)/explore, works too. |
| `path` | The path that opens it, when it differs from the route. |
| `controls[].testID` | Required. The testID the control renders with. |
| `controls[].role` | Its accessibilityRole: button, link, switch, textbox and so on. |
| `controls[].label` | Its accessibility label. |
| `controls[].leadsTo` | The route a press opens, or back for a control that returns to the screen before. |
| `controls[].destructive` | true for a control that does something irreversible. Mark it in your code with agentHint({ destructive: true }) too. |

The card fails a row for a declared control the screen doesn’t render, a testID it renders that isn’t declared, a control that opens a different screen, and a different role. A file that isn’t JSON, has no `route`, or has a control with no `testID` fails a row too.

## How Squiggle finds the folder

The `squiggle` command and the MCP server look in the current directory, then each parent in turn, so a command run from a subdirectory uses the same folder. Studio uses the project you opened in it.

## config.jsonc

Settings for everyone who works on the project. Comments are allowed. Flags on the command line override these settings for one run.

```ts
{
  // the app under test, when this repo has one
  "bundleId": "com.acme.shop",
  "shards": 4,
  // --profile ci applies profiles.ci on top of the settings above
  "profiles": {
    "ci": { "relay": false, "shards": 2 }
  }
}
```

| Key | What it sets |
| --- | --- |
| `bundleId` | The app under test, when the project has one. |
| `budgets` | Performance limits per screen, keyed by route name, with `"*"` for every screen. `squiggle init` writes a default set. See [Set a budget per screen](https://squiggle.sh/docs/performance/baselines#set-a-budget-per-screen). A screen’s `sizes` say how big it must get without dropping frames; see [Keep a screen off its cliff](https://squiggle.sh/docs/performance#keep-a-screen-off-its-cliff). |
| `apps` | The app under test on each platform, for a project that builds both, for example { "ios": "com.acme.shop", "macos": "com.acme.shop.mac" }. The platform picks which; bundleId is the fallback. squiggle init fills it in from your ios/ and macos/ projects. |
| `platform` | ios or macos, the platform commands use when you don’t pass --platform. squiggle init sets macos for an app with no ios/ folder. |
| `flowsDir` | Where flows live, if not .squiggle/flows. |
| `shards` | How many simulators squiggle flows uses at once. |
| `udid` | The simulator to use when several are running. |
| `port` | The port squiggle waits on for the app (default 7777). SQUIGGLE_PORT overrides it for one shell, and squiggle flow honours both. The app must connect to the same port: set EXPO_PUBLIC_SQUIGGLE_WS_URL=ws://localhost:<port> in its .env. |
| `relay` | Connect through Studio instead of directly, so Studio keeps showing the app during a run. |
| `relayUrl` | The address to connect through. Setting it turns relay on. |
| `profiles` | Named sets of the settings above. --profile ci applies profiles.ci on top. |
| `telemetry` | false turns off anonymous usage events for this project. |
| `crashReports` | false turns off crash reports for this project. |
| `prEvidence` | true lets your agent attach a recording or screenshot to a pull request on its own. Off by default; squiggle flow --attach-pr never needs it. |
| `leaderboard` | true lets a benchmark score be published to the public leaderboard. Off by default. |
| `updateChannel` | The release channel squiggle update follows. squiggle update --channel sets it. |

## Telemetry

Squiggle sends anonymous usage events, never before it has shown you a one-time notice. To see exactly what is sent, run `squiggle telemetry`. To turn it off, run `squiggle telemetry off`, or set `DO_NOT_TRACK=1` or `SQUIGGLE_TELEMETRY=0`.
