Skip to content

Latest commit

 

History

History
34 lines (23 loc) · 7.08 KB

CONTRIBUTING.md

File metadata and controls

34 lines (23 loc) · 7.08 KB

Contributing to TreeMap

Three commands and one rule.

npm install
npm run build:ui && npm run typecheck
npm test

The rule: never edit public/index.html. It is generated. The frontend lives in src/ui/ as small source files — shell/ (the document's head and tail), styles/, markup/ (one file per view and modal) and app/ (the behaviour) — and scripts/build-ui.js stitches them, in src/ui/manifest.json order, into that one page. Edit the source, run npm run build:ui, and commit both; node scripts/build-ui.js --check says whether the page matches its sources without writing it. tests/buildUi.test.ts fails the suite if the page drifts from its sources, and a new source file must be added to the manifest or it never ships. src/ui/README.md explains the layout.

Working on it

  • npm run dev runs the server with reload; npm run build && node scripts/dev-isolated.js runs it against a throwaway data folder, so the scheduler cannot start scanning your real folders (it starts the compiled dist/server.js, hence the build).
  • One test file at a time: npx tsx --test tests/<name>.test.ts. The whole suite takes minutes; npm test runs it on every OS in CI, so use path.join and os.tmpdir() in tests rather than POSIX literals, and never touch a real home folder — synthetic fixtures only (tests/realHomeUntouched.test.ts fails a test that removes a path built from it).
  • Every change to behaviour comes with a test that failed before the change. A test that never went red is not evidence.
  • A test never touches the owner's app data or the machine's Trash. App data: every test file that loads src/ imports tests/fixtures/dataDir.ts at its top and calls isolatedDataDir() there, and makes its fixtures with fileTempDir(); tests/testDataIsolation.test.ts holds every file to it, and names the few that keep a data folder of their own, each with its reason. The machine: every door to it has a stand-in, and under a test runner the real door refuses — it throws RealMachineRefusal before any call to the operating system, says so on stderr, and fails the test file with exit code 1 even if the throw was caught (src/services/realMachineGuard.ts). The doors and their stand-ins: moving a file to the Trash (setTrashStepForTests, src/services/cleaner.ts — every delete, cart commit, offload, encode, restore and Autopilot run goes through it), emptying the Trash and listing it (TREEMAP_TRASH_DIR, a folder the test owns — a server whose page boots asks for the Trash's size), deleting Time Machine's local snapshots (setSnapshotPurgeStepForTests), a cloud provider's trash (TM_<PROVIDER>_API, a stand-in server) and git gc --prune=now (setGitGcStepForTests). "Under a test runner" means TREEMAP_FORBID_REAL_TRASH, which npm test sets for the whole run, tests/fixtures/dataDir.ts sets as it loads (so a plain npx tsx tests/x.test.ts is guarded too) and nestedRunEnv() puts into every nested run, or NODE_TEST_CONTEXT, which --test sets in each test file's process. A child a test starts carries them when its environment is built from ...process.env or nestedRunEnv(); a hand-built env drops both, and tests/childEnvCarriesGuard.test.ts fails on one. Outside a test runner every door goes ahead, as the app needs it to; tests/realMachineGuard.test.ts shows so for each, in a child that disarms the machine itself. Until 30 Sep 2026 none of this was enforced, and tests put files into the owner's real Trash on every run (tests/realMachineGuard.test.ts says which).
  • What a person reads is written in English on every machine, because the interface is English and formatBytes always prints an English decimal point. Sizes are in decimal units (1 KB = 1,000 bytes, 1 GB = 1,000,000,000 bytes, as Finder counts them): src/utils/formatBytes.ts on the server and the page's copy in src/ui/app/000-prelude.js, which tests/polishServerNumbers.test.ts holds to the same output. A size a person types goes through the page's KB, MB, GB and TB beside it, and tests/decimalUnitsUi.test.ts fails on a unit list whose option is not its decimal number of bytes, and on a 1024-based size in the page's script spelled as 1024 itself (v /= 1024, n % 1024), as a shift or power of two by 10, 20, 30 or 40 bits (1 << 20, 2 ** 30), or as an integer literal that is a whole number of MiB (104857600) — not on smaller powers of two such as 4096 or on hex literals, so write a size with the page's MB or GB (5 * GB) rather than counting on the test to catch every spelling of 1,024; a real binary quantity (a buffer, a block size) keeps its binary value and is named in KiB or MiB. A count goes through formatCount (src/utils/formatCount.ts on the server, the page's own in src/ui/app/000-prelude.js) and comes out "1,234" everywhere. A date names UI_LOCALE, and a clock also passes the machine's hour cycle (HOUR_CYCLE on the page, machineHourCycle() on the server), so it reads "Sep 6, 2026" but keeps the machine's 12- or 24-hour clock and its time zone. Never a locale-less toLocaleString(), toLocaleDateString([]) or Intl.DateTimeFormat(undefined, …): those follow the machine's locale, and once printed "1.234 shapes" beside "1.2 GB" and Arabic-Indic digits inside English sentences. tests/localeIndependence.test.ts bans the locale-less forms in the app, comments included (describe the call without its leading dot), and CI runs the whole suite under a Portuguese locale, so a test that hard-codes locale output, or a stub that follows the machine, goes red there.
  • Deletes go to the Trash, never a hard delete; nothing outside a scanned folder is ever touched. Those two rules are the product.

The native core (optional)

The app runs without it, but the scan engine is Rust (native/treemap-core). With Rust from https://rustup.rs: npm run build:native builds the module (on Windows also tm-mft-helper.exe) into native/prebuilt/<platform>-<arch>/; npm run test:native runs cargo test --workspace. rust-toolchain.toml pins the Rust version, and rustup installs it the first time cargo runs there. Before pushing a Rust change, run what CI runs in that folder: cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace. Without the module, or without gdu (npm run fetch:gdu:dev), the tests that need them skip on your machine and fail on CI, which builds and fetches both on every leg. Bump nativeVersion in package.json and the workspace version in native/treemap-core/Cargo.toml together whenever native/index.d.ts changes. Every dist build needs Rust too: it builds the module first and stops, with the reason, if it cannot. native/README.md has the rest.

Voice

Everything the user reads is calm, plain English in sentence case: "folder", not "directory"; "Trash", not "unlink"; a reason, never an error code.

Where things go