Metadata-Version: 2.5
Name: stackthread
Version: 0.1.0.dev4
Summary: Click any element in your running web app and follow it through your code: component, handler, request, endpoint, database.
Project-URL: Vite plugin, https://www.npmjs.com/package/@stackthread/vite
Author: The stackthread authors
License-Expression: MIT
License-File: LICENSE
Keywords: code-navigation,developer-tools,django,fastapi,react,static-analysis,vite
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Debuggers
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# stackthread

[![PyPI](https://img.shields.io/pypi/v/stackthread?include_prereleases&label=PyPI)](https://pypi.org/project/stackthread/)
[![npm](https://img.shields.io/npm/v/@stackthread/vite/next?label=npm%20%40stackthread%2Fvite)](https://www.npmjs.com/package/@stackthread/vite)
[![Python](https://img.shields.io/pypi/pyversions/stackthread)](https://pypi.org/project/stackthread/)
[![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue)](#licence)

**See what is behind anything in your web app, in plain words.**

More and more code is written with the help of AI, and much of it was never read line by line by anyone on the
team. That makes it hard to find your way around, to debug, and to be sure what the app really does.
stackthread opens your running app next to its code. Click a button, a field or an error message, and it shows
where it comes from and what happens next: the screen, the server, the rules it checks and the information it
saves. Everything is read straight from your code, exactly as it is.

- **Click and see.** Click anything in your running app and read, in plain sentences, what it does and which
  code is behind it.
- **Find your way in big code.** Search for words you saw on a screen, an error message or a field, and land in
  the right place, even with hundreds of screens.
- **Check the logic.** Read the server's rules in the order they run ("If the task is empty, stop: No such
  task"), the tests that reach them, and what changed recently, line by line.
- **Nothing to learn first.** The Guide button shows how to do one thing at a time, in two or three steps.

It never runs your code and never sends it anywhere: everything stays on your machine.

**Works today with** Python backends (FastAPI, Django, Django REST framework, django-ninja) and React apps on
Vite. **Support for more frameworks and languages is coming in upcoming releases.**

**Who it is for:** developers finding their way in a new or AI-written codebase, testers and product people who
want to see why the app behaved as it did and hand a precise note to a developer, and team leads who want to
know what changed and what no test covers.

For developers, the same click gives the whole thread with files and lines, from the component that drew
the element to the tables the backend touches. Open any of them at the right line, edit, save.

```
<button> Mark as paid                    OrderPage.tsx:22
  payNow()            onClick handler     OrderPage.tsx:12
  shop.pay()          used by payNow      shop.ts:25
POST /api/orders/{}/pay/                  sent by shop.pay
POST /api/orders/{pk}/pay/   drf endpoint urls.py:8
  OrderViewSet.pay()                      views.py:27
    services.pay()                        services.py:28
Order (shop_orders)  written by pay       models.py:13
```

- **Works on your machine only.** No account, no telemetry, no network calls beyond `127.0.0.1`.
- **Reads code, never runs it.** Your backend does not need to be running, and nothing is imported or executed.
- **Honest about certainty.** Every link is marked *exact*, *inferred* or *heuristic*.
- **Never makes words up.** Plain words come from your code, its comments and your team's own `[words]` list.
- **No Python dependencies.** Node.js reads the frontend with a bundled parser.

> **Status: early preview (development releases).** Install with `pip install --pre stackthread` and update the
> Python package and the Vite plugin together.

## Contents

- [Supported stacks](#supported-stacks)
- [Install](#install)
- [Quick start](#quick-start)
- [Updating](#updating)
- [Using the workbench](#using-the-workbench)
- [Command line](#command-line)
- [Configuration](#configuration)
- [Project layouts](#project-layouts)
- [Teaching stackthread your domain](#teaching-stackthread-your-domain)
- [Troubleshooting](#troubleshooting)
- [Security and privacy](#security-and-privacy)
- [Limitations](#limitations)

## Supported stacks

| Layer | Supported |
|---|---|
| Backend | FastAPI, Django (URLconf, function and class-based views), Django REST framework, django-ninja |
| Data | Django ORM, SQLAlchemy 2, SQLModel, Flask-SQLAlchemy queries, MongoDB collections (heuristic) |
| Frontend | React (JSX and TSX) on Vite 5 or later |
| Routing | React Router (route objects and `<Routes>`), TanStack Router (code-based and file-based) |
| HTTP | `fetch`, axios and axios instances, ky, ofetch, openapi-fetch, and your own request wrappers |

What each framework detector understands, in detail:

- **FastAPI:** `FastAPI()` and `APIRouter(prefix=...)` wherever they are created; `get`, `post`, `put`, `patch`,
  `delete`, `options`, `head` and `api_route`; `include_router` nested to any depth and `add_api_route`;
  `Depends` and `Security`, shown as "injected into"; `response_model=` and parameter and return types.
- **Django:** `ROOT_URLCONF` followed through `include()`; `path()` with converters (`<int:pk>` becomes `{pk}`)
  and `re_path()` with named groups; function views and class-based views through `as_view()`, one endpoint per
  HTTP method, including the generic views.
- **Django REST framework:** `SimpleRouter` and `DefaultRouter`; `ViewSet`, `GenericViewSet`, `ModelViewSet`,
  `ReadOnlyModelViewSet` and the mixins; `@action(detail=..., methods=[...], url_path=...)` and `lookup_field`;
  `APIView` methods and `@api_view`.
- **django-ninja:** `NinjaAPI()` mounted with `api.urls`; `Router()` with `add_router(prefix, router)`, as an
  object or an import string; route decorators and `api_operation`.
- **Data:** model classes and their tables, and which functions read or write them: `Model.objects` chains,
  `get_object_or_404`, `save()` and `delete()`; SQLAlchemy `select`, `insert`, `update`, `delete` and session
  calls; Flask-SQLAlchemy `Model.query`; MongoDB collection methods.
- **React:** components (functions, `memo`, `forwardRef`, higher-order components, classes), hooks, props,
  `useState`, `useReducer`, `useMemo`, `useCallback`, `useRef`, context; every JSX element with its handlers,
  shown values, render condition and the list it is rendered from. A submit button follows its form's `onSubmit`;
  a prop is followed to the parent that passed it.
- **HTTP:** literal, template and concatenated URLs; `axios.create({ baseURL })` instances defined anywhere;
  typed `openapi-fetch` paths; wrapper functions that pass their URL on; import aliases from `tsconfig.json`,
  `jsconfig.json` and `vite.config.*`; Vite `server.proxy` keys tell stackthread which requests reach the backend.

Python is parsed with the standard library `ast` (Python 3.14 syntax accepted on older interpreters).
JavaScript and TypeScript are parsed with a bundled Babel, so your project's compiler versions do not matter.

## Install

| You need | Version | Why |
|---|---|---|
| Python | 3.11 or later | runs the indexer and the local server |
| Node.js | 22.18 or later | reads JavaScript and TypeScript files |
| Vite | 5 or later | makes the elements of your app clickable |

Install the Python package where your backend's tools live: in the backend's virtual environment, as a dev
dependency, or globally.

```sh
pip install --pre stackthread          # or: uv add --dev --prerelease allow stackthread
                                       # or: pipx install --pip-args=--pre stackthread
npm install --save-dev @stackthread/vite@next
```

## Quick start

**1. Install both parts.** The Python package reads your code; the Vite plugin makes your app clickable.

```sh
pip install --pre stackthread                  # where your backend's tools live, e.g. its virtual environment
npm install --save-dev @stackthread/vite       # in your React app
```

**2. Add one line** to `vite.config.ts`, before the React plugin:

```ts
import stackthread from '@stackthread/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [stackthread(), react()],
})
```

**3. Run your app as usual.**

```sh
npm run dev
```

Vite prints one more link, `➜ stackthread: http://localhost:5173/__stackthread/`. Open it. That is all: the
plugin starts stackthread for you and stops it with the dev server. The first read of a big project takes a few
seconds; the page opens by itself when it is ready.

Not sure where to start? Press **Guide** in the top bar.

Good to know:

- The plugin finds the `stackthread` command in a virtual environment of your project (`.venv`, `venv` or `env`,
  also one folder down, such as `backend/.venv`) or on your PATH. If it is not installed, the page says how to
  install it.
- stackthread works out your backend and frontend by itself. To adjust anything, run `stackthread init` once in
  the folder that holds both; it writes `stackthread.toml` (see [Configuration](#configuration)). Do this too if
  the backend and the frontend are not in one git repository.
- Prefer to run it yourself? `stackthread serve` in the project folder; the plugin then uses that one. Turn the
  automatic start off with `stackthread({ server: false })`.

## Updating

Update both parts together, then restart `npm run dev`:

```sh
pip install --pre -U stackthread
npm install --save-dev @stackthread/vite@latest
```

There is nothing else to do:

- **Your files stay as they are.** `stackthread.toml`, the `.stackthread/` folder (cache, logs, problem reports)
  and the workbench's settings in your browser are kept. Settings are only ever added, never renamed, so an older
  `stackthread.toml` keeps working. The cache is read again from scratch when a new version reads code differently.
- **An old server is replaced.** If a `stackthread serve` from before the update is still running, the plugin
  restarts it with the new version; a server whose package was upgraded underneath it stops by itself, and the
  page reloads into the new version.
- **Mismatched versions are named.** If the Python package and the plugin are different versions, the Vite
  console says which one to update.
- **What is new is one click away.** After an update, a dot on **Guide** leads to what changed.

Coming from 0.1.0-dev.3 or earlier: you no longer need to run `stackthread serve` yourself; `npm run dev` starts
it.

## Using the workbench

The workbench is made for anyone who needs to understand the app, not only the people who wrote it. It speaks
**plain words** by default: "Complete a task" instead of `POST /api/tasks/{task_id}/complete`, "If the task is
empty, stop: No such task" instead of a block of Python. The **Developer** switch in the top bar shows the code's
own names, files and lines everywhere. **Light**, **Dark** or **Auto** sets the colours.

Not sure where to start? **Guide** in the top bar asks what you would like to do (find the code behind
something you saw, check a rule, see how parts connect, see what changed, tell a developer about a problem) and
shows it in two or three steps. It also has a one-minute look around, and after an update, what is new. Guides
start only when you ask; a small dot on the button marks something new.

Four places on the left, one explanation on the right:

| Place | What it is for |
|---|---|
| **Find** | search for anything you saw, or pick a screen from pictures of every screen |
| **Explore the app** | your running app; click anything to see what it does |
| **Map** | a mind map of the code around one thing: what leads to it, and what it does next |
| **To check** | problems found in the code, steps no test reaches, how much of the code is explained |

### Find

Type what you noticed: the name of a screen, words on a button, an error message, a field, or a name from the
code. Results are grouped (screens, text on screens, messages and errors, server actions, saved information, steps,
parts of screens). An error message leads straight to the rule that sends it, highlighted.

Below the search, **Screens of your app** shows every screen, grouped the way the app's addresses group them, so
it stays easy with hundreds of screens: large groups show their first screens and **Show all**, and the search
narrows everything at once. Screens come from your router and from `[[views]]` in the settings; each is named by
its own heading once you have visited it, and a small picture of it appears while you use the app.

### Explore the app

Your app runs inside the workbench at full width and works as usual: its own links and menus move between screens,
and **All screens** opens the picture gallery to jump anywhere. Turn on **Point and click** (or press
**Alt+Shift+S**) and click a button, a field, a heading or any text. A label shows what you are pointing at.

You can also do this without the workbench: inside your app, **Alt+Shift+S** turns on point and click and the same
explanation docks on the right side of the page.

### The explanation

Whatever you pick (something in the app, a search result, a card on the map) is explained in three tabs:

- **What happens**: one sentence, then the path from your screen through the server to the saved information,
  with the notes from the code's own comments. When a part of the screen only shows or is greyed out in some
  cases, it says when ("Greyed out when a request is being sent"). A link offers **Go to this screen**.
- **Logic**: the server steps on the path, in the order they run, each as plain rules read from the Python code:
  conditions, the messages it stops with, what it works out and what it saves. Any step opens on its own.
- **Check**: every rule that can refuse, with its condition, so you can judge whether it is right; the tests that
  reach the path; **when the code last changed**, one line per file, newest first, with who changed it and why;
  and a note for the developer. **See the change** shows the lines added and removed, and the file's earlier
  changes; in a git repository it also shows changes not committed yet. **Copy for developer** puts the
  explanation, the path you took and your note on the clipboard.

A **trail** of everything you opened runs along the top of the explanation, with back and forward
(**Alt+Left**, **Alt+Right**). **Save trail** keeps it, for example to show a new colleague how a refund works.

In **Developer** words, What happens shows the full thread in five layers (on screen, in the browser, over the
wire, on the server, data), every row with its file and line.

### Map

The map is a mind map with one thing in the middle and one rule: **left is what leads here, right is what happens
next**. Two steps each way show at once; a small **+** grows a branch, and **N more** shows the rest. Clicking any
card puts it in the middle and opens its rules in the explanation; hovering shows its note without a click. With
nothing picked, the middle is the whole app: screen areas on the left, server areas and tables on the right,
grouped the way the project is laid out. **Find a path from here** (in the explanation) draws the route between two
places. Scroll or pinch to zoom, drag to move, **Fit** to see everything.

### Code pane

Every file and line opens in the code pane under the explanation, with syntax highlighting for Python, TypeScript
and JavaScript. **Ctrl+S** (or **Cmd+S**) saves. stackthread:

- refuses the save if the file changed on disk since you opened it, so your editor's work is never overwritten;
- never writes secrets or lock files (`.env*`, `*.pem`, `*.key`, lock files, `.git/` and more);
- can run your formatter after each save (`server.format`).

The button named after your editor opens the file there instead (VS Code, Cursor, Windsurf, Zed, IntelliJ IDEA,
PyCharm or WebStorm; set `server.editor`).

### To check

What `stackthread check` reports, in plain sentences: requests that reach no server action, imports that break
your layer rules, files that could not be read. Also the steps with many rules that no test reaches, and how many
definitions have a note in the code (plain words are only as good as those notes).

### Status and problem reports

The status in the top bar always says what stackthread is doing:

| Status | Meaning |
|---|---|
| Indexing 48/132 files | the first full read, with a progress line under the top bar |
| Re-indexing OrderPage.tsx | a file changed and is being read again |
| Live · rev 3 | up to date; the number goes up with every change |
| Offline, reconnecting | the browser lost the stackthread server; it retries by itself |

If you restart `stackthread serve`, open pages reconnect and read everything again on their own.

Click the status for the activity panel: recent events, and a **Problems** tab with every warning and error.
**Report a problem**, there or in the status bar, makes a zip of the logs and diagnostics to attach when you ask
for help. It never contains source code.

### Keyboard

| Keys | Action |
|---|---|
| Ctrl+K / Cmd+K | search |
| Alt+Shift+S | point and click on or off, in the workbench and inside your app |
| Alt+Left, Alt+Right | back and forward along the trail |
| Ctrl+S / Cmd+S | save in the code pane |
| Esc | close search, a dialog, or point and click in your app |

## Command line

```
stackthread [--config PATH] [--log-level LEVEL | --verbose] <command> [options]
```

| Command | What it does |
|---|---|
| `init [--force]` | detect backends and frontends and write `stackthread.toml` |
| `serve` | serve the workbench on `127.0.0.1`, read the code in the background, follow changes |
| `scan` | index once and print a summary; `--json FILE` writes the whole graph |
| `explain FILE:LINE` | the thread through a code location, as text or `--json` |
| `check` | findings for CI; exits with 1 at or above `--fail-on` (default `error`) |
| `doctor` | check Node.js, the bundled parser, the UI and the config |
| `logs` | print, filter and follow the log |
| `report` | make a problem report zip |

`serve` options: `--port N` (default 7341; the next nine are tried when busy), `--no-open`, `--no-watch`,
`--read-only`.

`explain` shows who reaches a function from the screen, which requests lead to it and what data it touches:

```
$ stackthread explain backend/shop/services.py:21
reserve()  backend/shop/services.py:21

In the browser
  shop.placeOrder()  web/src/lib/shop.ts:23  calls POST /api/orders/

Over the wire
  POST /api/orders/  web/src/lib/shop.ts:24  sent by shop.placeOrder

On the server
  reserve()  backend/shop/services.py:21  this definition
  POST /api/orders/  backend/shop/urls.py:8  reaches this definition

Data
  Product (shop_product)  backend/shop/models.py:4  written by reserve
```

### In CI

```sh
pip install --pre stackthread
stackthread check                     # --fail-on warning is stricter; --json for machines
```

| Rule | Severity | Meaning |
|---|---|---|
| `unmatched-request` | warning | a frontend request matches no endpoint |
| `layers` | error | an import breaks a `[[rules.layers]]` order |
| `parse-error` | error | a file could not be parsed |
| `node-unavailable` | error | frontend sources exist but Node.js 22.18+ was not found |
| `adapter` | error | an adapter failed to load or raised |

Exit codes: 0 success, 1 findings at the threshold, 2 invalid configuration, 70 unexpected error (in the log),
130 interrupted.

## Configuration

stackthread reads `stackthread.toml` from the current folder or the nearest parent, or `[tool.stackthread]` in
`pyproject.toml`. Every key is optional. Unknown keys and wrong types are reported together, with suggestions for
misspellings. Paths are relative to the project root; `*` matches within a folder and `**` across folders.
`.gitignore`, `node_modules`, virtual environments and build output are always skipped.

```toml
[project]
name = "shop"                       # shown in the workbench title

[[sources]]                         # one per backend or frontend
name = "api"
lang = "python"                     # python | typescript | javascript
root = "backend"                    # the folder that contains your top-level package
exclude = ["**/migrations/**"]
# include = ["**/*.py"]            # default: every file of the language
# frameworks = ["drf", "django-orm"]  # default: detect everything

[[sources]]
name = "web"
lang = "typescript"
root = "web"
include = ["src/**/*"]
# alias = { "@" = "src" }           # default: from tsconfig.json and vite.config.*

[http]
# Requests from "web" starting with /api reach routes of "api" with the prefix replaced:
links = [{ from = "web", to = "api", client_prefix = "/api", server_prefix = "" }]
clients = ["request"]               # extra functions to treat as HTTP clients

[pages]
params = { orderId = "1" }          # sample values, so /orders/$orderId opens as /orders/1

[[views]]                           # named states your router does not express, shown with the screens
group = "Checkout"
name = "Payment step"
path = "/checkout?step=payment"

[words]                             # your team's words for names in the code, used everywhere in plain words
qty = "quantity"
OrderLine = "line of an order"
"busy" = "a payment is being sent"  # also for screen conditions: "Greyed out when a payment is being sent"

[server]
port = 7341
editor = "vscode"                   # vscode | cursor | windsurf | zed | idea | pycharm | webstorm | none
edit = true                         # allow saving; `serve --read-only` overrides it
edit_deny = ["config/*.yaml"]       # added to the built-in list of files that are never written
format = { python = ["ruff", "format", "{file}"], typescript = ["npx", "prettier", "--write", "{file}"] }
open_browser = true

[[rules.layers]]                    # for `stackthread check`: lower layers never import higher ones
name = "backend layers"
order = ["shop.core", "shop.services", "shop.api"]

[log]
level = "debug"                     # what the log file records
size_kb = 1024                      # size of one file before it is rotated
keep = 5                            # rotated files kept

[adapters]
use = ["tools.stackthread_adapter:Orders"]
```

Python test files (`test_*.py`, `*_test.py`, `tests/`) are indexed and tagged: they appear in search but never in a
server thread.

## Project layouts

- **Monorepo with `backend/` and `web/`:** run `init` and `serve` at the root. The plugin finds the root by
  looking upwards for `stackthread.toml`.
- **Several frontends:** one `[[sources]]` per frontend, and the plugin in each Vite app.
- **Backend and frontend in different repositories:** put both under one folder, or run stackthread in the
  frontend repository with a source `root = "../api"`.
- **Without the Vite plugin:** `http://127.0.0.1:7341/__stackthread/` serves the same workbench; Find, the map,
  the explanations and To check work, but there is no live app to click. Django projects that render templates
  get the map, endpoints, models, `explain` and `check`.

## Teaching stackthread your domain

Every application has meaning no framework detector can see: order stages kept in a database, feature flags,
translation keys. There are two ways to add it.

### Concepts: no code

A concept links a `data-*` attribute in your markup to where its value is defined:

```tsx
<button data-i18n="pay_button" onClick={pay}>{t('pay_button')}</button>
```

```toml
[[concepts]]
attribute = "data-i18n"
label = "Text {value}"
find = [{ file = "web/src/locales/en.json", pattern = '"{value}":' }]
mentions = true                     # also list up to 40 source lines that mention the value
```

Clicking the button, or anything inside an element with the attribute, adds a layer "Text pay_button" with the
line that defines it and the lines that use it.

### Adapters: a little Python

An adapter is a class in your own code; every method is optional.

```python
# tools/stackthread_adapter.py
from stackthread.adapters import Adapter, Context, View
from stackthread.model import Finding, Graph, Node


class Orders(Adapter):
    name = "orders"

    def views(self, ctx: Context) -> list[View]:
        """Named states shown with the screens: here, one per order stage."""
        stages = ["packing", "shipping", "delivered"]
        return [View(group="Order stages", name=s.title(), path=f"/orders/1?stage={s}") for s in stages]

    def extend(self, ctx: Context, graph: Graph) -> None:
        """Add nodes and edges after the built-in detectors ran."""
        graph.add_node(Node(id="stage:packing", kind="store", name="Packing stage", lang="py", source="api"))

    def lineage(self, ctx: Context, selection: dict, layers: list[dict]) -> None:
        """Add or change rows of a thread before it is shown."""
        layers.append({"id": "stage", "title": "Stage", "note": "from the stage table",
                       "rows": [{"label": "Packing", "kind": "stage"}]})

    def checks(self, ctx: Context, graph: Graph) -> list[Finding]:
        """Extra findings for `stackthread check` and the To check page."""
        return []
```

Enable it with `[adapters] use = ["tools.stackthread_adapter:Orders"]`, or publish it from a package through the
`stackthread.adapters` entry-point group:

```toml
[project.entry-points."stackthread.adapters"]
orders = "my_package.stackthread:Orders"
```

An adapter receives `ctx.config` (the parsed settings), `ctx.project` (with the graph and the facts of every file),
`graph` (nodes, edges, and `out`, `into`, `of_kind`, `at(file, line)`), `selection` (what was clicked) and `layers`
(the thread as JSON). An adapter that fails becomes an `adapter` finding; indexing carries on without it. Adapters
run with your permissions, like any dev dependency.

## Troubleshooting

Start with `stackthread doctor`: it checks Node.js, the bundled parser, the UI and the configuration, and says
where the logs are.

Every run writes JSON lines to `.stackthread/logs/stackthread.log` in your project (the folder ignores itself in
git). Read them with:

```sh
stackthread logs                    # the last 50 entries at info and above
stackthread logs --level warning    # only problems
stackthread logs --area http -f     # one part, following new entries
```

Areas: `cli`, `index`, `worker` (the Node process), `server`, `http`, `edit`, `adapters` and `client` (errors in
the workbench, the in-app panel and the Vite plugin). Use `--verbose` on any command for more on the console.

`stackthread report --note "what went wrong"` (or **Report a problem** in the workbench) makes a zip with the
logs, versions, `doctor` checks and summaries of the settings and the project. It never contains source code;
paths under your home folder are shown as `~`. Nothing is sent anywhere; you decide where the file goes.

**A request is not linked to its endpoint.** `stackthread check` lists it as `unmatched-request` with the URL it
saw. Usually the frontend calls `/api/...` while backend routes start at `/`; add an `[http] links` entry.

**Frontend files are missing.** Check Node.js 22.18 or later with `stackthread doctor`; the log area `worker`
shows what Node printed.

**The workbench says the server is not running.** Start `stackthread serve` in the project and reload.

## Security and privacy

stackthread is for one developer, on their own machine, with their own project.

- **Nothing leaves your machine.** No telemetry, no update checks, no accounts.
- **Loopback only.** The server binds `127.0.0.1`, with no option to change it.
- **Other websites cannot use it.** Requests must carry a loopback `Host`, a loopback `Origin` when present, and a
  random 256-bit session token created at start-up.
- **The Vite plugin adds no new door.** It only proxies same-origin requests from this machine, only in
  `vite dev`.
- **Edits are fenced.** Only existing files inside your sources; never secrets or lock files; a save is refused if
  the file changed on disk; files over 2 MB or not UTF-8 are refused; writes are atomic and keep line endings.
  `--read-only` turns saving off.
- **The workbench is locked down** with a strict Content-Security-Policy.
- **Logs stay local** and never contain file contents or the session token.

## Limitations

stackthread reads code; where a value only exists at run time (a URL built from a variable, a router registered
conditionally) it shows what it can, and `check` lists what it could not link.

Not supported yet: Next.js, Remix framework mode and other server-rendered React frameworks; Vue, Svelte,
Angular; Flask, Starlette and Litestar routes; GraphQL and tRPC; webpack and Rspack.

## Licence

MIT. The workbench bundles Preact and CodeMirror, and the parser bundles Babel, all under the MIT licence; their
notices ship in the package.
