Version 1.13.0

Documentation

Local Test turns a folder on your computer into a website at an address like http://localhost:8000. This page covers installing it, deploying your first folder, every setting and its default, exactly what the server does with your files, and what to do when something breaks.

If something is broken right now, go straight to Troubleshooting, every entry starts with the message the app actually prints.

Install #

There are two ways to get Local Test, both from the downloads area of this site: download a ready-made build, or download the source archive and run it yourself. Both give you the same app. The build is faster to get going; running it yourself avoids the unsigned-app warning below.

macOS: the first launch #

  1. Download the .dmg file from the downloads area of this site.
  2. Open it and drag Local Test into your Applications folder.
  3. The first time only: do not double-click it. Right-click (or hold Control and click) the app, choose Open, and then click Open again in the dialog that appears.

That is a one-time step. From then on it opens like any other app.

The honest reason for that step: macOS checks downloaded applications for a Developer ID signature, and Apple only issues those certificates to members of its paid developer program. Local Test is free to download and use, and maintained by one student, so it is not enrolled and the app ships unsigned. Right-click → Open is macOS's own way of saying "I know where this came from", it is not a workaround, it is the documented path for unsigned apps.

Two things follow from that, and they are worth knowing before you install: the same warning appears on Windows in a different form, and updates replace the app in place rather than installing quietly in the background. A copy you build yourself from source opens normally from the start.

Windows #

The Windows .exe installer is in the downloads area, for Windows 10 and 11. It is an ordinary installer rather than a one-click one: you can choose where it goes, and it installs for your user account only, so there is no administrator prompt. It creates Desktop and Start Menu shortcuts and registers a normal entry in Add or remove programs.

It is unsigned, for the same reason the macOS build is, so Windows stops it the first time. SmartScreen halts any installer it does not recognize and shows Windows protected your PC. The way through is three steps:

  1. Run the installer. SmartScreen appears.
  2. Click More info, which reveals a button.
  3. Click Run anyway.

That is the documented path for an unrecognized installer, not a workaround, and it is worth doing the checksum check first if you want more than the site's word for what you downloaded.

The Windows build has been through a validation pass on real hardware: installing, launching, deploying a project and serving it over HTTP with live reload, running a command project, cloning a repository, and uninstalling again, which removes the shortcuts, the app data, and the registry entry. The uninstaller leaves an empty folder behind, which is ordinary behavior for this kind of installer rather than a fault.

One Windows note worth stating plainly: if you turn on LAN sharing, the first deploy afterwards may raise the Windows firewall prompt. That is expected.

Running it from the source code #

This takes about five minutes and works even if you have never opened a terminal. A terminal is just a window where you type one command, press Enter, and wait for it to finish.

  1. Install Node.js. Go to nodejs.org, click the large LTS button, run the installer, and accept the defaults. Node.js is the engine Local Test runs on.
  2. Get the source archive. Download the source ZIP from the downloads area of this site, then unzip it somewhere you can find again, the Desktop is fine. Double-clicking the file unzips it on both macOS and Windows.
  3. Open a terminal. On a Mac: Applications → Utilities → Terminal. On Windows: open Command Prompt from the Start menu.
  4. Point it at the folder. Type cd followed by a space, then drag the unzipped folder onto the terminal window (its location appears as text) and press Enter.
  5. Install and start. Run these two commands, one at a time, waiting for each to finish.
npm install
npm start

npm install downloads Electron, which is a large download, so give it a few minutes the first time. npm start opens the Local Test window. To run it again another day, repeat steps 3 and 4 and type npm start on its own.

While it is running that terminal window belongs to the app. Closing the window, or pressing Ctrl‑C in it, quits Local Test.

Checking a download is genuine #

Local Test is not signed by Apple or Microsoft, so your computer cannot vouch for who made the file. What you can do instead is confirm the file you received is byte-for-byte the one that was published. Every release lists a SHA-256 checksum on the download page and in SHA256SUMS.txt.

On macOS, in Terminal, from the folder holding the download:

shasum -a 256 Local-Test-1.8.0-arm64.dmg

On Windows, in PowerShell:

Get-FileHash Local-Test-1.8.0-setup.exe -Algorithm SHA256

Compare the result with the checksum on the download page. They must match exactly. If they do not, delete the file and do not open it, either the download was corrupted, or it is not the file that was published.

To check everything at once, download SHA256SUMS.txt into the same folder and run shasum -a 256 -c SHA256SUMS.txt. Every line should say OK.

The app performs this same check on itself: before installing an update it verifies the downloaded file against the checksum the manifest publishes, and refuses to install if they differ or if no checksum was published at all. An update that fails this check is deleted rather than installed.

Worth being clear about the limit: a checksum published on the same server as the file proves the download was not corrupted or swapped in transit, but not that the server itself is honest. Only code signing does that, and it costs money yearly on both platforms, which this project does not currently spend.

Your first deploy #

Add a project #

Click + Add Project in the sidebar and pick a folder, the one holding your index.html, if you have one. You can also drag a folder from Finder or Explorer straight onto the window. The folder becomes a project, named after itself, and it stays in the list between launches.

Adding a folder only records where it is. Local Test reads your files in order to serve them; the only writes are ones you ask for, saving in the built-in editor, creating, renaming, or trashing a file, inserting an image, or a git action. Adding a folder that is already a project just selects the existing one instead of making a duplicate.

Each new project gets a port: the app default (8000 out of the box), stepped up past any port another project already claims and past anything the operating system reports as busy.

Deploy #

Select the project and press Deploy. A block appears with your address, http://localhost:8000, with buttons to copy it or open it, and your browser opens there automatically unless you turn that off in settings. The sidebar dot turns green and the header reads LIVE.

What the browser shows depends on the folder: the index.html at its root if there is one, otherwise a plain generated list of the files, which you can click through.

What a port is #

localhost means this computer. The number after the colon is the port, think of it as a numbered door on your machine. A web address needs both: which computer, and which door.

Only one program can hold a door at a time, which is why two projects need two ports, and why deploying onto a port something else is already using fails. When that happens Local Test tells you and offers the next free port as a one-click fix. Ports at 1024 and below are reserved for the system and need administrator rights, so stay above them.

Change a project's port in the field next to the Deploy button. If the project is live when you change it, it is redeployed on the new port immediately.

Stop #

Press Stop, or Cmd/Ctrl D, or use the menu-bar icon. The address stops working at once, open connections are cut so the port is free again straight away.

On macOS, closing the window does not stop anything: the app stays in the Dock and your sites keep serving, which is the platform convention. Quit the app to stop everything. On Windows, closing the window quits the app and stops the servers.

Starting from a template #

If you do not have a folder yet, press New beside + Add Project (or Cmd/Ctrl N), pick a starter, and choose an empty folder to put it in. Local Test writes the files, adds the project, and it is ready to deploy.

The five starter templates.
TemplateWhat you get
Blank pageAn index.html and a stylesheet, nothing else.
Landing pageHero, three feature cards, a call to action, and a README.
Docs siteTwo pages with sidebar navigation, plus a 404.html.
Single-page appA small client-side router. SPA fallback is switched on for the project automatically.
PortfolioA heading and a grid of project cards.

Templates refuse to write over anything. If the folder already contains a file the template would create, nothing is written and you are told which file clashed.

Running software instead of serving files #

Some folders are not a website sitting on disk, they are a program that produces one. For those, put a command in the project's Command field. As soon as that field has anything in it, the Deploy button becomes Run: Local Test starts that command inside the project folder, through a shell, and streams everything it prints into the Activity tab. Stop ends it.

In this mode Local Test serves nothing itself. Your program does the serving; Local Test is the launcher, the log window, and the off switch.

What gets detected automatically #

When you add a folder, Local Test looks at it once and fills the Command in for you if it recognizes the project. The checks run in this order and stop at the first match.

Detection order when a folder is added.
Found in the folderCommand filled inCalled
package.json with a start scriptnpm startNode app
package.json with a dev scriptnpm run devNode app
index.htmlnothing, served as a static site,
Cargo.tomlcargo runRust app
go.modgo run .Go app
manage.pypython3 manage.py runserverDjango app
app.pypython3 app.pyPython app
main.pypython3 main.pyPython app

Detection happens only at the moment the folder is added. You can edit the command yourself at any time, and clearing it returns the project to plain folder-serving.

Ports for commands #

This one catches people out, so it is worth being blunt: Local Test does not tell your command which port to use, and does not check that anything is listening. The address shown on the Overview tab is simply built from the project's Port field, the app is guessing on your behalf, and it says so.

So set the project's port to the port your program actually listens on, or make your program follow the project by passing the port yourself as an environment variable.

Environment variables #

The project's Settings tab has an environment variables box: one KEY=value per line. They are handed to the command on top of the environment the app already has.

PORT=3000
API_URL=http://localhost:8000
NODE_ENV=development

Names must start with a letter or underscore and contain only letters, digits, and underscores. A matching pair of surrounding quotes is stripped, so NAME="value" and NAME=value are the same thing. Lines that do not look like an assignment are ignored.

These values are stored as plain text in the app's config file, alongside your project list. That is fine for a port or a local URL; it is not the place for a credential you would mind sitting unencrypted on your own disk.

How Stop kills things #

Development commands usually start other processes, a bundler starts a server, a script starts a watcher. Local Test starts your command in its own process group and, on Stop, terminates the whole group, then forces it three seconds later if anything is still alive. On Windows it uses taskkill with the tree flag. So Stop takes the children with it, rather than leaving a stray process holding your port.

Output is cleaned of terminal color codes and each line is cut at 500 characters before it reaches the Activity feed.

Finding npm, python, and git #

An app launched from the Dock or the Start menu inherits a minimal search path, which is why GUI apps often cannot find tools you installed yourself. At startup Local Test asks your login shell what its real path is and adopts it, and adds /usr/local/bin and /opt/homebrew/bin if they are missing. That is what makes npm start, python3, and git work the same from the app as they do in your terminal.

How the server behaves #

For projects without a command, Local Test runs a small static file server rooted at your folder. Its behavior is modeled on GitHub Pages, with a few conveniences that only make sense while you are still working on a site.

Which folder gets served #

A repository is not always a website at its root, often the site is a build output in a subfolder. Deploy resolves the folder to serve in this order:

  1. The project's Serve folder setting, if you filled it in. It must be a real subfolder of the project; if it does not exist, the deploy fails rather than quietly serving something else.
  2. The project root, if it contains index.html or index.htm.
  3. Otherwise the first of _site, dist, build, public, out that contains an index.html.
  4. Otherwise the project root anyway, which usually means you get a directory listing.

When a build folder is chosen for you, the Activity feed records it and the live block says which folder it picked.

Local Test serves what is on disk. It never runs your build. If the built site is stale, run the build yourself, or make the build the project's run command.

index.html, redirects, and 404.html #

A URL that points at a directory without a trailing slash gets a 301 redirect to the version with one, so relative links inside the page resolve correctly. With the slash, the server looks for index.html, then index.htm.

If a request matches nothing and there is a 404.html at the root of the served folder, that page is returned with status 404, exactly like GitHub Pages. Without one you get a plain built-in 404 page.

Directory listings #

On by default. Any folder without an index page renders a generated listing: folders first, then files, alphabetically, with a link back up. Dot-files and node_modules are left out, because a listing should be about the site rather than the toolchain. Turn the setting off per project and folders without an index page produce a 404 instead, which is what GitHub Pages does.

SPA fallback #

Off by default. With it on, any request that matches no file and has no file extension is answered with the root index.html at status 200. That is what a client-side router needs so that loading /about directly works instead of 404ing. Requests that do have an extension still 404, so a missing stylesheet still looks missing rather than returning HTML.

Live reload #

On by default. Local Test watches the served folder and tells open pages to reload themselves when a file changes. It works by appending a few lines of script just before the closing </body> of each HTML page as it is sent, which opens an event stream at /__localtest/reload. Your files on disk are never modified, the injection happens on the way out.

Changes inside .git and node_modules, and to .DS_Store, are ignored, and a burst of changes collapses into a single reload about 150 milliseconds later. Paired with the editor's auto-save, this is what makes the page in your browser update as you type.

Live reload relies on recursive folder watching, which is not available on every system. Where it is unavailable, serving still works normally; only the automatic reload is missing.

Range requests, caching, and methods #

  • Range requests are supported, so video and audio scrub properly and interrupted downloads can resume. A range that starts past the end of the file gets a 416.
  • Caching: every response carries Cache-Control: no-cache. Browsers revalidate on each load, so an edit always shows up on refresh. This is a development server; freshness beats cache performance.
  • Methods: GET and HEAD only. Anything else gets a 405 with an Allow: GET, HEAD header. A form that posts to the server has nothing to post to.
  • Content types are set correctly for the usual web formats, HTML, CSS, JavaScript, JSON, SVG, PNG/JPEG/GIF/WebP/AVIF/ICO, WOFF and WOFF2, MP4/WebM/MP3/WAV/OGG, WASM, PDF, Markdown, XML, ZIP, with charset=utf-8 on text. Anything unrecognized is sent as application/octet-stream.
  • Every response is logged to the project's Activity feed with its status and how long it took: GET /index.html → 200 (2ms).

Staying inside the folder #

Every requested path is resolved against the served folder and checked twice: once literally, to stop ../ and encoded traversal, and once after following symbolic links, so a repository that ships a link pointing somewhere else on your disk cannot read through it. Anything landing outside is refused with 403. The Files tab, the project search, and the editor use the same guard, so nothing in the app reaches outside the project folder.

Local Test also refuses to add, serve, or run its own installation folder.

What it binds to #

The server binds to 127.0.0.1 (this machine and nothing else) unless you turn on LAN sharing in app settings, in which case it binds 0.0.0.0 and the live block shows a second address other devices on your Wi-Fi can open. The binding is chosen when a project deploys, so the setting applies to deploys started after you save it.

Project settings #

Each project has its own Settings tab. Nothing here affects any other project.

Per-project settings, with the value you get if you never touch them.
SettingDefaultWhat it does
Namefolder nameEditable in the header. Display only, the folder is not renamed.
Port8000Taken from the app default when the project is added, stepped past ports other projects hold and ports the system reports busy. Any whole number from 1 to 65535.
CommandemptyEmpty means serve the folder. Anything else means run that command instead. Filled in automatically for recognized project types.
Environment variablesemptyOne KEY=value per line, passed to the command.
Live reloadonRefresh open browser tabs when a file in the served folder changes.
Directory listingsonGenerate a file list for folders that have no index page.
SPA fallbackoffAnswer unknown extensionless routes with the root index.html.
Serve folderempty (auto)Which subfolder is the website. Empty means the root, or a detected build folder. Must stay inside the project.
GitHub repositoryemptyAn owner/name pair (or a full git URL) for Sync now.
Pull before every deployoffFetch the latest from the linked repository each time you deploy. Switched on automatically for projects added through Add from GitHub.

Changing the port, the serve folder, live reload, listings, or SPA fallback while a static project is live redeploys it immediately so the change takes effect, no stop-and-start needed.

The same tab holds Duplicate project (a second entry pointing at the same folder, on its own port) and Remove project, which takes the project out of Local Test's list after a confirmation and never touches the folder on disk.

App settings #

The gear in the top right opens the app-wide settings, with explicit Save and Cancel buttons. Theme changes preview live while the dialog is open; Cancel puts them back.

App-wide settings, with defaults.
SettingDefaultNotes
ThemeDefaultEight themes: Default, Midnight Harbor, Neon Drift, Amber Terminal, Rose Quartz, Matcha Cream, Glacier, and Paper & Ink. Default follows your system's light or dark setting. Each is shown as its two colors with its name set in its own font.
Font sizeDefaultCompact, Default, Comfortable, or Large, 12, 13, 14, and 16 pixels.
Default port for new projects8000The starting point for the port of the next project you add.
Open browser automatically after DeployonApplies to deploys that serve a folder. Running a command never opens a browser.
Allow devices on my network (LAN)offBinds servers to every network interface and shows a second, network-visible address. Applies to deploys started after saving. May prompt the Windows firewall the first time.
Check for updates when the app opensonOne request a few seconds after launch to see whether a newer version exists. Nothing appears unless there is one.
Clone folderDocuments/Local TestWhere Add from GitHub clones repositories.
Personal access tokenemptyOptional. Raises GitHub's rate limit and lists your private repositories. Kept on this machine, shown masked afterwards, left out of backups. See the optional token.
License keyemptyUnlocks VIP: unlimited projects, and the AI tab. See unlocking VIP.
Anthropic API keyemptyYour own provider key, used by AI mode and nothing else. Kept on this machine, shown masked afterwards, left out of backups. See where the key lives.

The same dialog holds Export and Import for your projects and settings, and the Uninstall button described under updating and uninstalling.

Files, editing, and search #

Browsing #

The Files tab lists the project folder with breadcrumbs and file sizes. At the project root the README is rendered underneath the listing, the way a code host does it, with spec.md as a fallback when there is no README.

Click a file to open it: text is shown as it is, Markdown is rendered, images are previewed. Files with no text in them are reported as binary rather than shown as noise. Practical limits: images over about 3 MB and text files over about 1.5 MB are not previewed, and a very long text preview is cut at 200,000 characters.

The editor #

Click the pencil on any text file (Markdown, HTML, CSS, JavaScript, JSON, and the rest) to edit it in place. There are three modes: Edit, Split, and Preview. Markdown files get a formatting toolbar: headings, bold, italic, strikethrough, inline code, links, quotes, bullet lists, task lists, code blocks, tables, and dividers.

  • Cmd/Ctrl B, I, and K apply bold, italic, and a link. Applying the same wrap again removes it.
  • Enter continues a list, keeping numbering and task boxes; pressing it on an empty item ends the list.
  • Tab inserts two spaces.
  • Cmd/Ctrl S saves. The foot of the editor shows word, character, and line counts, and a reading-time estimate.
  • Auto-save, when ticked, writes the file about eight tenths of a second after you stop typing. With live reload on, that means the real page in your browser updates as you write.
  • Find and replace is the Find button or Cmd/Ctrl Alt F. It matches plain text, case-sensitively, no regular expressions.
  • Image copies a picture from anywhere on your computer into the project's images folder and inserts the Markdown that links to it.

Saving is capped at 5 MB per file. Switching to another project with unsaved changes asks first, unless auto-save is on, in which case it saves and moves on.

The box at the top of the Files tab (Cmd/Ctrl F) does two jobs at once: it filters the names in the current folder, and from two characters onwards it searches the text inside every file in the project. Results show the file, the line number, and the line itself; clicking one opens that file in the editor.

The search deliberately skips .git, node_modules, dist, build, _site, out, .next, coverage, and vendor, along with dot-files, files over 1 MB, and anything that is not a recognized text type. It stops after 200 matches.

Creating, renaming, deleting #

New file and New folder create inside the folder you are looking at; a new .md file opens in the editor with its name already set as a heading. Aa renames (a name without slashes, to move a file, use Finder or Explorer). The trash icon asks first and then moves the file to the Trash rather than deleting it outright, so a mistake is recoverable.

Git and GitHub #

Commit and push #

If the project folder is a git repository, its Settings tab shows the branch, how many files changed and which, how far ahead or behind the remote you are, and the last commit. Write a message, press Commit all (which stages every change in the folder and commits it) then Push. The first push on a new branch sets its upstream for you.

Local Test runs the git already installed on your machine and uses the credentials it already has. It never asks for, stores, or transmits a password or token, and it never writes to a repository unless you press one of those buttons.

Syncing a project from GitHub #

Put owner/name in the project's Repository field and press Sync now. Into an empty folder that clones; into an existing clone it does a fast-forward-only pull. A folder that already has files but is not a repository is refused rather than overwritten. Tick Pull the latest before every deploy and the project updates itself every time you deploy.

Add from GitHub #

In app settings, choose a Clone folder, type any owner/name under Add from GitHub, and press Add. The repository is cloned into that folder as a new project with pull-before-deploy already switched on, and its project type is detected the same way as any other folder.

Private repositories work only if you are already signed in to git on this machine. Rather than hanging on an invisible password prompt, the attempt fails immediately with a message saying the repository may be private or misspelled.

A project added this way gets its own free port, stepped past the ports other projects already hold, so cloning several in a row does not leave them all fighting over the default.

Adding repositories in bulk #

Add from GitHub takes one repository at a time. When you have a folder full of clones, or an account full of repositories, the Find repositories button in app settings (also in the command palette) opens a picker that adds as many as you tick. It has two panes, and both end in the same list of tick boxes.

Scanning a folder of clones you already have #

Already on this machine starts on your clone folder and lists every git repository inside it. Choose folder points it somewhere else, Rescan reads it again. It looks at the folder itself, at every folder directly inside it, and one level deeper, which covers both the GitHub/repo and the GitHub/owner/repo layouts. Folders whose name begins with a dot are skipped, so is node_modules, and the scan stops at 300 repositories and tells you it did.

Nothing is copied, moved, or run. To describe each repository Local Test reads two text files inside it, .git/HEAD and .git/config, rather than running git. That keeps a folder of two hundred clones fast, and it means a repository's own config never gets the chance to name a program for git to run.

Each row reports:

  • the folder name;
  • the current branch, or detached at plus the short commit id when the checkout is detached;
  • index.html when the folder has one, and runs plus the command when a start command was detected, the same detection that runs when you add any folder by hand;
  • no page or run command found when it has neither, which is a warning that deploying it will show a file listing;
  • where it came from: the origin remote, shown as owner/name when it is a GitHub remote and as the raw URL when it is not.

Repositories that are already projects are greyed out rather than hidden: the row is dimmed, labelled already a project, its tick box is disabled, and a line above the list counts them. Seeing that a repository is already there is more useful than wondering where it went. Those rows sort to the bottom, and the rest are alphabetical.

The filter box matches on the name and the remote. Select all ticks every row currently visible that is not already a project. Add selected then adds them, up to 100 in one press. Each folder is checked again in the app's main process before anything happens to it, so the list on screen is a suggestion rather than the authority, and anything that turns out not to be a git repository is skipped with a reason. Local Test's own installation folder is never offered.

Adding a scanned repository is a bookmark, not a copy. The project points at the folder where it already sits, and nothing inside it changes.

Looking up a GitHub account #

From a GitHub account takes a username and lists the repositories that account owns, most recently pushed first. All of these forms work:

What you can type in the account box.
FormExample
A usernameoctocat
A username with an at sign@octocat
A bare addressgithub.com/octocat
A profile URLhttps://github.com/octocat
The URL of one of their repositorieshttps://github.com/octocat/Hello-World

The owner is taken from whichever of those you paste, and the rest is thrown away. Anything that is not a valid GitHub username is refused before a request is made, with That is not a GitHub username or profile URL. A well-formed name that GitHub does not know comes back as GitHub has no account by that name.

The listing is fetched 100 repositories at a time, for at most three pages, so 300 at most; beyond that it says Stopped after the first 300. Each row shows the name and description, the main language, the default branch, how long ago it was last pushed to, and the labels private, archived, and fork where they apply. As in the other pane, anything already added is greyed out, matched here by its owner/name.

Ticking rows and pressing Clone and add clones each one into your clone folder, one at a time with progress as it goes, up to 50 in one press. Every one becomes a project with pull-before-deploy switched on and its own free port. A repository that fails to clone is reported by name rather than silently dropped.

When GitHub rate limits the lookup #

Account lookups go to GitHub's public API. Without a token that API allows 60 requests an hour from one machine, and a single lookup spends one request per page of 100 repositories, so one to three of them. Roughly twenty lookups an hour, in other words, which is more than a picker needs, until it is not.

When the limit is reached the lookup stops with this, and no repository list appears:

GitHub is rate limiting this machine. Without a token the public API allows 60 requests an hour, and this one resets at 4:15 PM. Add a personal access token in Settings to raise the limit, or try again later.

The reset time comes from GitHub's own response and is shown in your local time. It appears only when GitHub sends it; without it the sentence simply ends after the limit. Nothing else in Local Test is affected, because nothing else in Local Test talks to that API: the scan pane, deploying, running, and editing all keep working. Being offline says so instead, with Could not reach github.com. Check your connection: everything else in Local Test works offline.

The optional personal access token #

A token does two things: it raises that rate limit, and it lets your private repositories appear in the account list. Neither the scan pane nor anything else in the app needs one, and there is no prompt asking you to add one.

  1. Create a fine-grained token. At github.com/settings/personal-access-tokens, choose Generate new token, give it a name you will recognize later and an expiry you are comfortable with.
  2. Choose which repositories it can see. Under Repository access, all of them, or only the ones you want listed.
  3. Give it one permission. Under Repository permissions, set Contents to Read-only, and leave everything else at No access. That is all Local Test does with it: read the repository list and clone. With a read-only token it cannot push, delete, or change anything.
  4. Paste it into Settings. The Personal access token field in app settings, then Save.

The token is checked for shape before it is stored: 20 to 255 characters, letters, digits, underscores, and hyphens only. Anything else is refused at the setting, with That does not look like a GitHub token. Paste the whole thing, with no spaces., rather than being kept and failing later against GitHub.

After saving, the field shows only a mask, the first seven characters and the last four. The token itself never comes back out of the app's main process, so the window that draws the interface cannot read it and neither can anything reading over your shoulder. It lives in config.json (see where your settings live), which is written so that only your user account can read it, and it is deliberately left out of Export, so a backup you email yourself does not carry it. It is never written to the activity log. It goes to api.github.com in a request header and nowhere else.

Leaving the field empty when you save means leave what is stored alone. Erasing it is the Remove button beside it, which says so. If GitHub stops accepting it, the app says GitHub rejected the stored token. Remove it in Settings, or create a new one.

Private repositories appear only when the token belongs to the account you are looking up. Local Test asks GitHub who the token belongs to, and uses the signed-in listing only when that login matches the name you typed; otherwise it uses the public one. The note above the list tells you which happened: Signed in with your token, so private repositories are included. or Public repositories only. Add a token in Settings to see private ones.

There is no Sign in with GitHub button. A GitHub OAuth app needs a client secret and a callback address to be redirected to, which means a service running somewhere on your behalf. Local Test has none: every GitHub request it makes goes from your machine straight to github.com, which is the reason it can promise that and the reason it cannot offer OAuth.

A token you create yourself is the trade. It is more work for you once, and in exchange it needs no service in the middle, it can be narrowed to a single read-only permission and a chosen set of repositories, it expires on a date you pick, and you can remove it here or revoke it at GitHub without touching anything else.

Around the app #

The command palette #

Cmd/Ctrl K opens a search box over the window. Type a few letters of a project or an action, deploy, stop, open in browser, copy URL, switch project, open in your editor, open a terminal, duplicate, add from GitHub, settings, tutorial, shortcuts, check for updates, then use the arrow keys and Enter. It is the fastest route to everything the app does.

Menu bar and tray #

Local Test puts an icon in the macOS menu bar or the Windows tray. Its menu lists your projects (up to twelve) with a filled dot and a port number when they are live, and clicking one starts or stops it without opening the main window. There is also Stop All, Show Local Test, and Quit. On macOS the Dock icon carries a badge with the number of projects currently running.

Device previews #

When a site is live, the Phone, Tablet, and Desktop buttons open it in a window sized to that class of device, 390 × 844, 820 × 1180, and 1280 × 860 points. It is a real window at a real size, which is a better check of a layout than dragging a corner.

Editors and the terminal #

Local Test looks for Cursor, VS Code, Windsurf, Zed, Sublime Text, WebStorm, IntelliJ IDEA, and Neovim, by their command-line tool, or on macOS by the application in /Applications or your own Applications folder. The first one found gets a button next to the project path; the rest are in the command palette. Open terminal opens Terminal on macOS and Command Prompt on Windows, already in the project folder.

Editors are detected once when the app starts. If you install one while Local Test is open, restart it to see the button.

Deploy all, stop all, and the sidebar #

With two or more projects, Deploy all and Stop all appear at the top of the sidebar (Cmd/Ctrl Shift D and Cmd/Ctrl Shift S). Deploy all skips anything already running and reports how many started and how many failed. You can also drag rows to reorder the list and type in the search box to filter it.

The Activity tab #

Each project keeps a feed: deploy and stop events, every HTTP request with its status and timing, every line your command prints, live reloads, git results, and editor saves. Above it are counts of events, requests, and errors (any 4xx or 5xx), plus uptime and current state. You can filter the feed, copy it, export it to a file, or clear it.

The feed lives in memory, holds the last 300 lines per project, and is gone when you quit. Export it if you need to keep it.

Backup and moving machines #

Export in app settings writes your settings and project list to a JSON file. Import on another machine adds any project it does not already have, giving each a fresh port, skipping duplicates, and telling you how many of the folders in the file do not exist on this machine. Folders themselves are not copied, the file records where they were.

Your GitHub token, your Anthropic API key, your license key, and any environment variables set on a project are all deliberately kept out of that file, because a backup travels: it gets emailed, synced, and copied to a stick. Each is quick to enter again on the other machine, and impossible to un-leak. Import brings across projects, not settings, so the other machine keeps its own.

The built-in tutorial #

The Help menu, and the command palette, open a short beginner walkthrough in its own window. It covers the same first steps as this page in fewer words.

Free and VIP #

Local Test Free keeps five projects at a time. That is the entire difference between the two tiers, along with the AI tab described in the next section. Deploying, running software, the editor, project search, all the themes, the templates, git, the repository browser, live reload, LAN sharing, device previews, backup and import, and updates are ungated and unmetered on both tiers, with no trial period and no expiry.

What is counted is projects saved in the list, not projects running. Removing a project frees its place at once, and removing a project never touches the folder on your disk: a project is a bookmark to a folder, so removing it puts nothing at risk.

What the limit stops, and the one thing it does not #

There is one place in the app where the limit is checked, and every route that can create a project asks it first, so there is no way around it by a different door. At the limit, these are refused:

  • + Add Project, and dragging a folder onto the window;
  • New, the starter templates, which asks before opening the folder picker rather than after, so you are never asked to name a folder there was no room for;
  • Duplicate project;
  • Add from GitHub, checked before the clone starts, so a refusal never leaves a downloaded repository sitting in your clone folder with nothing pointing at it;
  • Add selected and Clone and add in the repository browser, checked for the whole batch at once. Five ticked with room for two is refused as a batch rather than half done, and the message says how much room there is.

The one exception is Import. A backup can easily hold more projects than this tier keeps, and refusing the whole file over that would be useless, so Local Test imports as many as there is room for and says in the confirmation how many were left behind.

The refusal is a sentence, not a wall. It names the limit, says how many you have, and offers both ways on:

Local Test Free keeps 5 projects, and you have 5. Remove one you have finished with, or unlock VIP for unlimited projects.

It arrives with an About VIP button that opens app settings with the key box already focused.

If you already had more than five #

The limit arrived in a version that could find installs already holding more than five projects. Nothing is ever taken away from them. The first time a profile opens a build that has the limit, Local Test stamps how many projects were there at that moment. If that number is above five, that becomes the limit for this install. The stamp is written once and can never run a second time.

So an install with seven projects keeps all seven, indefinitely, on Free. The limit shows up at the eighth, and it says exactly what it is doing:

Local Test Free keeps 5 projects. You had 7 before the limit existed, so all 7 are yours to keep. This is the one place it stops: adding a new one.

The Free panel in app settings says the same thing, so you can see the arrangement without having to trip over it.

Unlocking VIP, and removing the key #

A key looks like LT1-XXXXX-XXXXX-XXXXX-XXXXX: the prefix LT1, then four groups of five characters. The alphabet is Crockford base32, which is the ten digits plus the letters with I, L, O, and U left out, those being the ones people misread when copying a key off a screen.

Paste it into License key under Local Test VIP in app settings and press Unlock. The key is read forgivingly, on the same reasoning: case is ignored, spaces are stripped, the dashes are put back if they were lost or the key wrapped across two lines, and I and L are read as 1 and O as 0. The L in the LT1 prefix is left alone, being a real letter. A key that is not one of ours, or is the right prefix but the wrong shape, is turned down with a sentence saying which of the two happened.

Once it is accepted, the panel shows the tier, the date you unlocked it, and the key masked down to its first group and its last, LT1-XXXXX-•••••-•••••-XXXXX. That is enough to tell two keys apart and useless to anybody who reads it. The key itself is kept on this machine and never handed back out to the interface.

Remove key puts the install back on Free, after asking. It never removes a project. If there are more than five at that moment, that number becomes the limit exactly as it does for an install that never had VIP, so nothing disappears and Free simply stops the next new one.

What VIP changes is two things: projects become unlimited, and the AI tab appears on every project. Nothing else in the app behaves differently on one tier or the other.

Keys are checked on this machine. Unlocking sends nothing anywhere, and nothing about your projects leaves the computer at any point. What VIP costs, and when it opens, is on the pricing page.

AI mode #

With VIP unlocked, every project gets an AI tab: a set of premade prompts that already carry what Local Test knows about the project you have open. It needs two separate things, and it says which one is missing. Without VIP the tab explains what it is and offers to unlock. With VIP but no key, the panel loads but warns that it has nothing to send with, and links to the setting.

Whose account pays, and roughly how much #

Yours. You supply your own Anthropic API key, requests go from your machine to Anthropic carrying it, and Anthropic bills your account. Local Test charges nothing for AI mode and takes no cut of what you spend. There is no account with us, no key of ours in the path, and no service of ours in the middle. If your credit runs out, AI mode says Your Anthropic account has no credit left. Top it up at console.anthropic.com and try again. and stops there; nothing else in the app is affected.

Billing is by the token, which is about three quarters of a word, counting what the model reads as well as what it writes. The figures this build prices against are $5 per million tokens read and $25 per million written. In practice a short exchange costs a cent or two, and one that hands over a whole long file can reach about twenty cents. You do not have to take that on trust: the exact figure for each answer is shown next to it once it comes back, or less than a cent when it lands under one.

The model id is compiled into the build (currently claude-opus-5) and shown at the top of the AI tab, so the panel always names the one your copy is actually using. Each reply is capped at 4000 tokens, and a request that has not answered within two minutes is given up on.

The premade prompts, and what each one reads #

There are nine. Picking one fills the box with its wording plus the context it names, gathered from the project you have selected, so there is nothing to paste by hand.

Every premade prompt, and what it puts in the box with it.
PromptWhat it gathers
Explain this errorThe recent activity log.
Write a READMEThe file list.
Suggest what to build nextThe file list.
Review this fileThe file open in the editor.
Generate a starter pageNothing. You describe the page you want, and the prompt stops mid-sentence with the cursor where the description goes.
Explain this project to meThe file list.
Why is my page not loading right?The file list and the activity log.
Check this file for accessibilityThe file open in the editor.
Write a commit messageThe changed files.

Those four kinds of context are each bounded, and this is exactly what they contain:

  • The file list is names and byte sizes, three folders deep, up to 250 entries, ending in a note when it had to stop there. Anything beginning with a dot is left out, and so are .git, node_modules, dist, build, _site, out, .next, coverage, vendor, .cache, .venv, and __pycache__. File contents are not in it.
  • The activity log is the last 120 lines of the feed for that project, then trimmed to the last 6,000 characters.
  • The file open in the editor is that one file, up to its first 60,000 characters. If no file is open, the prompt says so instead of quietly sending the wrong one.
  • The changed files is the branch name and the list of changed file names with their state. The diff itself is not sent. If the folder is not a git repository, the prompt says that instead.

Nothing is sent until you press Send #

This is the part worth understanding, because it is the whole design of the panel.

Picking a prompt sends nothing. The app gathers the context that prompt names, hands the whole thing back to the window, and drops it into a large editable box together with the prompt wording. What is in that box is exactly what will be sent, and you can change every word of it. If there is a file, a path, or a log line in there you would rather not hand over, delete it and send the rest. The character count under the box tells you how much you are about to send, and Send takes what is on screen at that moment, not what was gathered a minute ago.

From there it goes over HTTPS from your machine to api.anthropic.com, and nowhere else. Nothing about the request is written to the activity log, deliberately: the prompt can hold the contents of your files, and the key is a secret. A short standing instruction travels with every request, telling the model you are building a site or a small app locally in Local Test and asking for plain language and short code. Nothing else is added to what you can see.

If something goes wrong, your typing survives it. A rejected key, an empty balance, a rate limit, a dropped connection, a timeout: every one of them leaves the box exactly as you left it, and the ones worth retrying come back with a Try again button rather than making you write the prompt again.

Where the key lives. In config.json (see where your settings live), which is written so that only your user account can read it. After you save it, the field shows a mask and nothing else, the first eleven characters and the last four.

The key never reaches the window that draws the interface. It is held in the app's main process, put into the request header there, and that is the only place it goes: not into the activity log, not into an error message, and not into an exported backup, so a backup you carry to another machine does not carry your key with it. Something that does not look like a key is refused at the setting rather than stored, with That does not look like an API key. Paste the whole thing, with no spaces.

Keyboard shortcuts #

The same list is in the app: open the command palette and choose Keyboard shortcuts.
ActionmacOSWindows
Command paletteCmd KCtrl K
Add a projectCmd OCtrl O
New from templateCmd NCtrl N
Deploy or stop the selected projectCmd DCtrl D
Deploy allCmd Shift DCtrl Shift D
Stop allCmd Shift SCtrl Shift S
Search project filesCmd FCtrl F
Save the open fileCmd SCtrl S
Find and replace in the editorCmd Alt FCtrl Alt F
Bold, italic, link in the editorCmd B / I / KCtrl B / I / K
Overview, Files, Activity, SettingsCmd 1 to 4Ctrl 1 to 4
App settingsCmd ,gear icon
Indent in the editor (two spaces)TabTab
Close a dialog or the paletteEscEsc

Two rough edges worth knowing. Cmd , lives in the macOS application menu, so on Windows the gear is the way in. And in the Markdown editor Cmd/Ctrl K inserts the link markup and opens the command palette on top of it, press Esc to dismiss the palette and the link is waiting underneath.

Updating and uninstalling #

Checking #

A few seconds after launch, Local Test checks whether a newer version exists, only that, and only when Check for updates when the app opens is on. Nothing appears unless there is one. You can check at any time by clicking the version badge in the bottom-left corner, or through the command palette.

Installing #

Choosing Install downloads the new version and, on macOS, unpacks it, moves the current app aside as a backup, puts the new one in its place, and reopens it, restoring the backup if the move fails. On Windows it downloads the installer and hands off to it. If a version has no build available for your platform, the download page opens in your browser instead.

Because the app is unsigned, this is a visible swap rather than a silent background update. Running from the source archive there is no installed app to replace, so Local Test opens the download page and leaves your folder alone; update it by downloading a fresh source archive and running npm install in it again.

Uninstalling #

App settings has an Uninstall button. After a confirmation it stops everything that is running, deletes the app's settings and project list, moves the app itself to the Trash, and quits. Your project folders are never touched, Local Test only ever held a reference to them.

On Windows, uninstall through Add or remove programs as usual; the installer registers an uninstaller that also removes the app's data.

Where your settings live #

Everything Local Test remembers is one JSON file called config.json, in the per-user application data folder.

Where the config file lives.
PlatformLocation
macOS~/Library/Application Support/Local Test/config.json
Windows%APPDATA%\Local Test\config.json
Linux~/.config/Local Test/config.json

Inside it are the app settings and the project list. Each project records where it is, which port it uses, and whichever of its options you changed.

{
  "schemaVersion": 1,
  "settings": {
    "defaultPort": 8000,
    "autoOpenBrowser": true,
    "allowLan": false,
    "theme": "default",
    "fontSize": 13,
    "githubDir": "",
    "checkUpdatesOnLaunch": true
  },
  "projects": [
    {
      "id": "0c9f2f4e-…",
      "name": "my-portfolio",
      "path": "Sites/my-portfolio",
      "port": 8000,
      "createdAt": "2026-08-26T21:00:00.000Z",
      "liveReload": true,
      "spaFallback": false,
      "serveDir": ""
    }
  ]
}

The file is written atomically (to a temporary file that is then renamed) so a crash mid-save cannot leave you with half a config. If it is ever unreadable, it is renamed to config.json.bak and a fresh one is started, rather than the app refusing to open. Settings from the app's earlier name, Test Run, are carried over automatically the first time.

Linux is expected to work, since the app uses only standard Electron and Node APIs, but it is untested and unsupported.

Privacy and network behavior #

Stated precisely, so you know exactly what the app does rather than having to take it on trust.

What binds to what #

A deployed project listens on 127.0.0.1 at its port, reachable from this machine only. With LAN sharing on it listens on 0.0.0.0 instead and the app shows you the second address; that is the only way anything else on your network can reach a site. Nothing is ever published to the internet.

Every place the app touches the network #

  1. The update check. One HTTPS request asking whether a newer version exists. It happens a few seconds after launch when the setting is on, and whenever you ask for it. It sends nothing but the request itself.
  2. Installing an update. Downloading that version's file, and only after you choose Install.
  3. Git. Sync now, pull-before-deploy, Add from GitHub, and Push run the git program on your machine against the remote you configured. Local Test does not speak to GitHub itself here; git does, with your existing credentials.
  4. Opening a link. The live URL, the download page, and links inside rendered Markdown are handed to your default browser.
  5. Looking up a GitHub account. Only when you type a name into the account pane of the repository browser, and only to api.github.com. It asks for that account's repository list and sends nothing about you, unless you have stored a token, which travels in the request header so that your private repositories are included. Scanning a folder of clones is entirely local and makes no request at all.
  6. Sending an AI prompt. Only when you press Send in the AI tab, and only to api.anthropic.com. What goes is the text sitting in the box in front of you, plus your own API key in the request header. Nothing is gathered in the background and nothing is sent while you are still reading it.

That is the complete list. The last two happen only on a deliberate press, and only if you have gone looking for them. There is no telemetry, no analytics, no crash reporting, no account, and no check-in of any kind. Nothing about your projects, files, paths, or settings is transmitted anywhere on its own.

Inside the app #

  • The window that draws the interface runs with context isolation on and Node disabled, under a content security policy that permits only the app's own files and inline images. Every privileged action (reading a file, starting a server, running git) goes through a short list of checked handlers.
  • File access of every kind is confined to the project folder, checked both literally and after resolving symbolic links.
  • The git commands behind the status, commit, and push buttons run with the repository's own hooks, pager, editor, and credential helpers disabled, so opening someone else's clone cannot make your git run their code.
  • Environment variables you set are stored as plain text in config.json.
  • The GitHub token, the license key, and the Anthropic API key are held in that same file, which is written so that only your user account can read it. None of the three is ever handed back to the window that draws the interface, which sees a mask and nothing more, and none of the three is ever written to the activity log. All three are left out of an exported backup, along with any environment variables you have set on a project, since that is where a database password usually ends up.
  • Local Test refuses to serve or run its own installation folder, and drops such a project if one is ever in the list.

What Local Test does not do #

  • No build step. It serves files as they are on disk. Run your build yourself, or make it the project's run command.
  • No HTTPS, no custom domains, no proxying. Plain HTTP on localhost.
  • No backend. GET and HEAD only; a form that posts has nothing to post to. If you need one, run it as a command project.
  • No compression. Files are sent as they are.
  • The builds are unsigned, hence the macOS first-launch step, the Windows SmartScreen warning, and updates that swap the app rather than installing quietly. Signing on either platform needs a paid certificate.
  • No sign in with GitHub. The account lookup in the repository browser uses a token you create yourself, or nothing at all. OAuth would need a service in the middle, and there is not one.
  • Linux is untested. It should work, since the app uses only standard Electron and Node APIs, but nobody has verified it.
  • Live reload needs recursive folder watching, which is unavailable on some systems. Serving still works there; automatic reloads do not.
  • Find and replace is literal and case-sensitive, no regular expressions.
  • Project search stops at 200 matches and skips build folders, dot-files, files over 1 MB, and non-text types.
  • Run commands are not told which port to use, and are not checked for actually listening.

Troubleshooting #

Each entry starts with the message as the app prints it, because that is what you are most likely to be searching for.

Deploying #

Port 8000 is already in use.

Something else already holds that door, another project of yours, or an unrelated program. The error comes with a button reading Use 8001 & Deploy, which moves the project to the next free port and deploys in one click; that is almost always the right answer. If you specifically need that port, quit whatever is holding it first.

Port 80 needs elevated permissions, use a port above 1024.

Ports at 1024 and below are reserved for the operating system and need administrator rights. Local Test will not ask for them. Pick something higher, 8000, 3000, 5173, anything you like above 1024.

The folder no longer exists:

Followed by the path the project points at. You moved, renamed, or deleted the folder; the project is still pointing at where it used to be. Remove the project from Local Test and add it again from its new location. Removing never deletes anything on disk.

The configured serve folder does not exist: dist

The project's Serve folder setting names a subfolder that is not there, usually because the build has not been run yet, or the folder is called something else. Run the build, correct the name, or clear the field to go back to automatic detection.

That folder is Local Test itself, it cannot deploy its own installation.

You picked the application, or the folder containing it. Pick your project folder instead. (Running the Local Test source code as a project is fine, that is a different folder from the installed app.)

Running a command #

Stopped: exited with code 127

127 is the shell's way of saying it could not find the command. Check the spelling in the Command field, and check that the tool is installed, try the same command in a real terminal in the same folder. If it works there but not here, the app is not seeing the same search path; restarting Local Test makes it re-read your shell's path. As a last resort, use the tool's full path in the Command field.

Stopped: exited with code 1

Your program started and then failed on its own terms. The lines directly above this one in the Activity tab are its own output, that is where the actual reason is. The same applies to any other non-zero code.

Stopped: killed (SIGTERM)

Normal. That is what Stop, Stop all, and quitting the app look like from the program's side.

The command runs but the address shows nothing

Local Test never tells your program which port to use. The address on the Overview tab is built from the project's Port field, so if your program listens on 3000 and the project says 8000, the link goes nowhere. Read the port out of your program's own output in Activity and set the project's port to match, or pass PORT to it through the environment variables box.

Git and GitHub #

git is not installed on this machine.

Local Test uses the git you have rather than bundling its own. Install git, on macOS, running git --version in a terminal offers to install the developer tools; on Windows, git-scm.com, then restart Local Test.

Couldn’t reach owner/name, the repo may be private or misspelled. Sign in with git (or GitHub Desktop) first, or check the name.

Either the name is wrong or the repository is private and this machine is not signed in. Local Test deliberately fails fast here instead of hanging on a password prompt you cannot see. Check the spelling first; if it is private, sign in with the git command line or GitHub Desktop once, then try again.

Folder has files but is not a git repository, sync would overwrite it.

Cloning into a folder that already has content would destroy that content, so it is refused. Point the project at an empty folder, or run git init and add the remote yourself in the existing one.

Nothing to commit, no changes.

Git sees no difference between your files and the last commit. If you expected changes, check that you saved the file, and that it is not excluded by .gitignore.

Git needs your name and email first: git config --global user.email "you@example.com"

Git stamps every commit with an author and has never been told who you are. Run that command in a terminal, along with git config --global user.name "Your Name", then commit again.

Git could not authenticate. Sign in with the git CLI or GitHub Desktop, then try again.

Push needs credentials Local Test does not have and will not ask for. Set them up once with the git command line or GitHub Desktop and every later push works.

GitHub is rate limiting this machine. Without a token the public API allows 60 requests an hour, and this one resets at 4:15 PM. Add a personal access token in Settings to raise the limit, or try again later.

Account lookups in the repository browser use GitHub's public API, which allows 60 requests an hour from one machine when nobody is signed in, and each lookup spends one request per page of 100 repositories. Wait for the reset time (GitHub's own, shown in your local clock) or add a personal access token, which raises the limit. Nothing else in the app is held up by this: scanning a folder of clones never touches the network, and neither does anything else you were doing.

GitHub rejected the stored token. Remove it in Settings, or create a new one.

GitHub answered with a 401, which means the token has expired, been revoked, or was only ever partly pasted. Tokens have an expiry date chosen when they were made, so this turns up eventually for everyone who sets one. Press Remove beside the field and paste a new one; the lookup works without a token in the meantime, on public repositories only.

GitHub has no account by that name.

The name is a valid GitHub username in shape but no such account exists, so check the spelling. The nearby That is not a GitHub username or profile URL. is a different thing: it means what you typed could not be a username at all, and it is refused before any request is made. A username, a profile URL, or the URL of one of their repositories all work.

That does not look like a GitHub token. Paste the whole thing, with no spaces.

The token is checked for shape before it is stored: 20 to 255 characters, letters, digits, underscores, and hyphens only. This almost always means the copy caught a trailing space or a line break, or stopped short of the end. Copy it again from GitHub in one go. Local Test refuses it here rather than storing something that would fail later against GitHub for reasons that would be harder to read.

Pages in the browser #

404, /about doesn’t exist in this project.

Nothing matched that path in the folder being served. Three things to check: whether the file is really there under that exact name (capitalization counts), whether the folder being served is the one you think (a build folder may have been picked automatically, which the live block tells you) and, for a client-side router, whether SPA fallback is on. Put a 404.html at the root of the served folder and it replaces this page, exactly like GitHub Pages.

403, That path is outside the project folder.

The request resolved to somewhere outside the served folder, either through ../ in a path or through a symbolic link pointing elsewhere on your disk. This is the path guard doing its job; move whatever you are trying to serve inside the project folder.

405, Only GET and HEAD are supported.

Something tried to POST, PUT, or DELETE. The static server only reads files; it has no backend. If your page needs one, run the real thing as a command project instead.

The page did not update after I saved

Live reload is on by default, so a save should refresh the tab. If it did not: check that live reload is still ticked in the project's settings; check that the file you edited is inside the folder actually being served, not beside it; and reload once by hand, the injected reload script only reaches pages loaded after the deploy. On systems without recursive folder watching, reloads simply do not fire and a manual refresh is the workaround.

I see a file list instead of my site

There is no index.html at the root of the served folder. Add one, point the Serve folder setting at the folder that has one, or turn directory listings off if you would rather have a 404.

Projects, VIP, and AI mode #

Local Test Free keeps 5 projects, and you have 5. Remove one you have finished with, or unlock VIP for unlimited projects.

You are at the free tier limit. Removing a project you have finished with frees a place immediately and never touches the folder on disk, so nothing is lost by clearing out one you have not opened in a month. The other way on is a VIP key, and the message comes with a button that opens Settings at the right box. When you were adding several at once the sentence instead says how many you picked and how much room there is, because the batch is checked as a whole rather than half done.

That key is not the right shape. It looks like LT1-XXXXX-XXXXX-XXXXX-XXXXX, in five groups of five.

The prefix was right but the rest was not. Case, spaces, and missing dashes are all forgiven, and the letters that get misread are corrected for you, so this normally means a group is short or a character was dropped. Copy the key again in one piece. The related Local Test keys start with LT1. Check you copied the whole key. means the copy began part way through.

AI mode is part of VIP.

The AI tab is switched on by a VIP key, and this is the answer from the app's main process rather than the window, so it also appears if the tab is reached some way other than the button. Unlock VIP in Settings and the tab starts working. Note that the key and the API key are two different things: VIP switches the tab on, and your own Anthropic key is what pays for what it sends.

Anthropic rejected that API key. Check it was copied whole, and that it has not been revoked, in your account at console.anthropic.com.

The key stored here is not one Anthropic will accept: mistyped, partly pasted, or revoked since. Create a fresh one at console.anthropic.com and paste it into Settings, replacing what is there. Your prompt is untouched, so you can send it again as soon as the key is fixed.

Your Anthropic account has no credit left. Top it up at console.anthropic.com and try again.

AI mode bills your own Anthropic account, so an empty balance stops it. Adding credit is the whole fix, and nothing else in Local Test is affected in the meantime. See whose account pays for what an exchange typically costs.

Could not reach api.anthropic.com. You are offline, or something is blocking it. Everything else in Local Test works without a connection.

AI mode is the only part of the app that needs a connection to do its job, apart from the GitHub lookups and the update check. Your prompt is kept exactly as you wrote it and the error comes with a Try again button, so reconnecting and pressing it again is the whole recovery.

The app itself #

macOS refuses to open the downloaded app

Expected on first launch, and the fix is the right-click → Open step described under Install. It is needed once per copy.

In-place update needs the installed app, opened the release page instead (you are running from source).

You are running from the source archive, so there is no application bundle to swap. The download page opens in your browser instead. Update by downloading a fresh source archive and running npm install in it.

No published releases yet, you’re ahead of the curve.

The update check completed but found no published version to compare against, the usual reason is that none has been posted yet. Nothing is wrong with your copy.

That file is not editable text.

The editor opens plain text only. The file is an image, a binary, or past the size limit for opening, about 1.5 MB for text and 3 MB for images. Open it in a real editor instead; the Open in… button next to the project path is one click away.

No supported editor found on this machine.

None of Cursor, VS Code, Windsurf, Zed, Sublime Text, WebStorm, IntelliJ IDEA, or Neovim was found. If one is installed, it may have no command-line tool on your path, VS Code and its relatives install theirs from the command palette inside the editor. Detection runs at startup, so restart Local Test afterwards.

The app will not start from source

Delete the node_modules folder inside the Local Test folder, then run npm install again, then npm start. If the window never appears on macOS but the icon is in the Dock, an earlier copy may still be running and holding the single-instance lock; quit it first.

Everything is confused and I want a clean slate

Quit Local Test and move config.json (see where your settings live) somewhere else. The app starts fresh with no projects and default settings, and your folders are untouched. Keep the file if you might want your project list back.