Skip to content

Repository layout

Three programs share one repository: the Next.js app, the Electron main process, and this documentation site. They have separate TypeScript configs and separate dependency trees.

Path Contents Notes
app/ App Router routes and globals.css (shell) is a route group; everything except /auth/* renders inside the app chrome
components/ React components, including components/tools/ one entry per registry entry, lazily imported
components/tools/ one file or one directory per tool, plus shared/ 48 tool entries, 1:1 with the registry; shared/ is a helper, not a tool
lib/ all pure logic and every network client this is where the testable code lives
lib/reference/ static reference data: ports, protocol numbers, address ranges data, not logic
contexts/ auth and project React contexts the only two providers with state that outlives a route
electron/ main process, preload, navigation policy, and electron/network/handlers.ts compiled separately by electron/tsconfig.json to CommonJS
tests/unit/ node-environment tests over lib/ and electron/ the larger of the two projects
tests/components/ happy-dom tests that mount real components includes the accessibility gate
tests/fixtures/ captured ping, traceroute and arp output real macOS transcripts, not synthetic
docs/ this Starlight site separate pnpm project with its own lockfile
public/ static assets copied verbatim into out/ also the drop point for the built docs
.github/workflows/ CI, release, CodeQL, Scorecard, dependency review every job pins actions by commit SHA
Casks/ the Homebrew cask, regenerated by the release workflow do not edit by hand

The split that matters is lib/ versus components/: anything worth testing goes in lib/, and components stay thin enough that the component suite can be about rendering and accessibility rather than logic. CONTRIBUTING.md states the rule directly: write tests for any new logic in lib/.

A tool is a directory whenever it has more than one panel, so a file count under components/tools/ is larger than the tool count and the two must not be conflated. The registry is the arbiter: every load thunk resolves to exactly one entry under components/tools/, and the only directory there that is not a tool is shared/. That distinction is also why docs/scripts/check-counts.mjs excludes the phrase “N tool files” from its total-count rule.

Four paths are build output and are gitignored. Editing them does nothing.

Path Generated by Source of truth
out/ next build the app
dist-electron/ tsc -p electron/tsconfig.json electron/
public/docs/ pnpm build:docs docs/dist/
docs/src/content/docs/tools/ docs/scripts/generate-tool-pages.mjs lib/tool-registry.ts

The last row is the one that surprises people. Every page under /docs/tools/ is written at docs build time from the registry, so a tool’s description in the docs is literally the same string the app renders. Change the registry, and the docs change on the next build. Editing the generated markdown is silently discarded.

The generator also fails the build if the registry gains or loses a category that the manual sidebar in docs/astro.config.mjs does not account for. A missing sidebar entry would be an invisible gap, so it is treated as an error.

Config Covers Target Notable settings
tsconfig.json app/, components/, lib/, contexts/, tests/ ES2020, jsx: preserve strict: true, noEmit, path alias @/*
electron/tsconfig.json electron/ ES2020, CommonJS moduleResolution: node, emits to dist-electron/
docs/tsconfig.json docs/ extends astro/tsconfigs/strict separate program, separate lockfile

electron/tsconfig.json carries ignoreDeprecations: "6.0" with a comment: node10 module resolution is deprecated in TypeScript 6 but it is the correct setting for a CommonJS main process, and node16 would force an explicit .js extension on every relative import.

Worth knowing before you trust a green build: next.config.mjs sets eslint.ignoreDuringBuilds and typescript.ignoreBuildErrors to true. A passing next build says nothing about types. pnpm typecheck and pnpm lint are the actual gates, and CI runs them as separate steps.

Terminal window
# what the root build script chains together
pnpm build:docs # cd docs && pnpm install && pnpm build, then copy dist to public/docs
next build # copies public/ verbatim into out/, so out/docs/ appears

The web deploy serves out/ from Vercel. The desktop app serves the same out/ over a loopback HTTP server, so /docs/ works with no network. That means anything the docs load from a remote host would break the offline story, which is why they load no webfont, no CDN script, and no external stylesheet.

Every link into the source points at main, not at a commit SHA. That is deliberate: a main link keeps working as the file evolves, at the cost of line ranges drifting. The line ranges were taken when each page was written, so treat the range as a hint and the symbol name in the link text as the real anchor. If a range looks wrong, search for the function name.