Skip to content

Development

Andrea de Ruvo edited this page Oct 12, 2026 · 15 revisions

Development

python3 -m pytest -q -n 16              # everything, the browser suite included: under 2 minutes
python3 -m pytest -q -n 16 -m "not browser"   # the quick loop: Python only
npm test                                # the first line
npm run test:quick                      # the second
npm run lint                            # ESLint on static/
npm run relink                          # regenerate the frontend modules' import blocks
npm ci                                  # once: ESLint and the parser the checks use

-n is pytest-xdist (in requirements.txt): each worker starts its own Argus, port, temp HOME, tmux socket and Chromium, so the browser suite runs in parallel safely — 12 minutes on one worker, under 2 on sixteen. CI uses four. The count changes most weeks; pytest is the truth. Node is used for checks only — the app itself has no build step and no dependencies, and without npm ci those checks skip rather than fail (CI sets ARGUS_BROWSER_REQUIRED=1, which turns a missing browser or linter into a failure, so a runner that lost one cannot pass by testing nothing).

The tests never touch tmux's default socket. Neither should anything else you run against this code: a tmux server that dies takes every session on it with it, which is how this repository learned the rule the hard way.

The tooling, since a folder of unexplained scripts is worse than no folder:

scripts/openapi.py rewrites docs/openapi.json from the code. The test suite runs it with --check and fails if the published description has drifted, so this one is not optional.
scripts/demo.py builds the instance every screenshot and clip is made from — fabricated sessions, fabricated files, on its own tmux socket. It exists so the pictures can be remade when the interface moves, and so that "none of this is a real machine" is something you can verify rather than take on trust.
scripts/orchestra.py, scripts/referee.py, scripts/fullstack.py three worked orchestrators, each run against real tmux before being committed — see Orchestrating several agents
tools/argus_client.py, tools/argus-say the Python client and a command-line front door onto it: the transport. Standard library only, one file, no package — see An agent driving Argus.
tools/argus_orchestra.py the orchestration framework on top of it — the contract, the waiting, the names, the worktrees. It is why the three examples are twenty lines each: see Writing an orchestrator.
scripts/surface.py lists every public name in tools/ and fails if one has no docstring, or — given a checkout of this wiki — if one is named nowhere on it. The test suite runs both; CI clones the wiki so the second one is real there rather than skipped. It exists because the client was once documented eight methods out of fourteen and nothing noticed.
scripts/record.mjs drives a Chromium over the debugging protocol to record those clips, from the demo instance.
scripts/modules.mjs npm run relink: regenerates the import block at the top of every frontend module from what the module actually uses. Also moves code between modules without breaking it — see below.
scripts/apiref.py writes this wiki's API reference from docs/openapi.json (itself from scripts/openapi.py): python3 scripts/apiref.py ~/argus.wiki/API-reference.md. tests/test_wiki.py fails when it is not current, when a wiki link or #anchor goes nowhere, or an image points at a branch that does not exist — with the wiki cloned at ~/argus.wiki; skipped otherwise.
tests/browser/ the browser suite: a real Argus driven by a real headless Chromium. See below.

The frontend

Plain ES modules in static/js/, no bundler: edit, reload, done. It was one 16,600-line app.js until September 2026, split one section per module with the behaviour unchanged and the browser suite green at every step. app.js is still the entry point — one import — because the page, the service worker and installed PWAs all know it by name; js/main.js is the start-up and imported by nothing else.

Three rules, each learnt during the split, and each checked by a test:

  • The imports are generated. Every module starts with a // <imports> block listing what it uses from the others. Do not edit it: write the code, run npm run relink. ESLint says when a name is used but not imported (no-undef), or when an imported variable is being assigned (no-import-assign — an ES import is read-only, so shared state has a setter in the module that owns it). A stale block fails the tests.
  • Load order is not file order. The sections use each other in circles, so a module's top-level code can run before a module it reads from is ready — "Cannot access 'prefs' before initialization", and a page that never starts. Code that runs at load may only read another module's variables if that module is in no import cycle (state.js, dom.js, words.js, icons.js…). tests/js/loadorder.mjs follows the calls, across modules, and names the whole chain when it finds one.
  • Every module is in the service worker's list, or an installed app breaks offline; and a module that is there for what it does at load (taking the token from the address) needs an import of its own, since nothing uses its exports.

The browser suite

tests/browser/ starts Argus as a person would, with a config of its own, a folder of sample files, its own HOME and a tmux socket named argus-t-… — never the default one — and drives it with headless Chromium over the debugging protocol, at desktop and phone size. Every screen, and the things people do: a shell from an empty session list and typing into it, rename and kill with and without confirming, a markdown report with its figure (and its <script> neutralised), hidden files, theme, language, desks, labelling a process.

Any problem fails the test by itself — an uncaught exception, a console error, a request of ours answering 4xx or 5xx, a module answered with HTML because the file is missing — so a test written to check one button also catches the error it set off somewhere else. It was checked against itself: bugs planted on purpose (a misspelt name in a rare path, a screen that throws, kill without confirmation, a broken figure URL…) all fail it. A terminal is checked by what a typed command does, never by reading the screen back.

Chromium is found in $ARGUS_CHROMIUM, then Playwright's cache, then the PATH.

Third-party code

Vendored under static/vendor/, unmodified, each keeping its own copyright header:

  • xterm.js 6.0.0 — MIT
  • marked 18.0.7 — MIT
  • qrcode-generator 2.0.4 — MIT
  • highlight.js 11.12.0 — BSD-3-Clause, the ESM build: the core and 27 languages, imported only when a file of code is opened
  • pdf.js 4.10.38 — Apache-2.0, the build Argus draws every PDF with
  • swagger-ui 5.32.14 — Apache-2.0, the bundle and its stylesheet, served at /api/docs; FastAPI's own one-liner would fetch it from a CDN
  • three.js 0.185.1 — MIT, the module build with OrbitControls and STLLoader, for the 3D viewer
  • occt-import-js 0.0.23 — LGPL-2.1, the OpenCascade kernel compiled to WebAssembly, loaded only to read a STEP file. It has its own NOTICE.md explaining the licence
  • mermaid 11.16.1 — MIT, the minified ESM build with its chunks and without its source maps: 3.7MB on disk and none of it fetched until a document with a ```mermaid fence in it is opened, at which point the entry and the chunks for that one kind of diagram are what load
  • diff2html 3.4.56 — MIT, the UI bundle without its highlighter (Argus's highlight.js is handed to it) and its stylesheet: the built-in diff viewer pack, which draws .diff/.patch files and the Changes window
  • Material Icon Theme 5.39.0 — MIT, the file icons only (633 SVGs, 512 KB) and their extension and file-name map, cut from the package by scripts/fileicons_vendor.py; each loaded when a file of its kind is listed. The genomics ones (sequences, alignments, variants, trees, annotations, pipelines) are Argus's own, in static/img/fileicons/

Optional at runtime: pandoc for Word documents, and pdftotext (poppler) to search inside PDFs. Without either, those files still open — they are just plainer.

Clone this wiki locally