Version 1.6.1

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 not built yet, the packaging for it is configured, but no Windows build has been produced, so there is nothing to download for Windows today. In the meantime, running it from the source archive works on Windows and is described below.

When the installer does arrive it will appear in the same downloads area, and it will be an ordinary installer: you can choose where it goes, and it registers a normal uninstaller in Add or remove programs. Because it will be unsigned for the same reason as above, SmartScreen may say it protected your PC, choose More info, then Run anyway.

Two Windows notes stated plainly. The code is written for Windows (paths, process termination, the installer and uninstaller are all configured) but it has so far been exercised on macOS and has not had a validation pass on real Windows hardware. And 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.

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 takes the app's default port as-is rather than searching for a free one, so if another project already sits there the first deploy will report the conflict and offer you the next free port.

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.

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.

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 14Ctrl 14
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.

That is the complete list. 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.

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.
  • 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.
  • Windows is untested on real hardware. The code is written for it; a validation pass has not happened yet.
  • 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.

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.

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.