~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
1377 lines · 49.9 KB Raw History
// The world the screenshots show: a reference PDS holding the same
// repositories, pulls and issues every time, so each page renders to the
// same pixels. Commits carry fixed dates, so their hashes are fixed too, and
// every record's createdAt is rewritten to a fixed time, in the order the
// records were made. The index a knotless-serve built while it was seeded
// is kept in `dir`, with its mirrors, for the next server to start from.
//
//   node harness/shots/world.mjs    seed one and keep it up, for
//                                   `just shots` runs to reuse, until
//                                   ctrl-c; written to target/shots/world.json

import { spawn, execFileSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { DatabaseSync } from 'node:sqlite'
import { dirname, join, resolve } from 'node:path'
import { createInterface } from 'node:readline'
import { fileURLToPath } from 'node:url'
import { crc32, deflateSync } from 'node:zlib'

const here = dirname(fileURLToPath(import.meta.url))
export const root = resolve(here, '../..')
const fixture = join(here, 'fixture')
const bin = join(root, 'target/debug')
// Where the world answers, and the pages behind it on the next two ports:
// three of their own for each checkout, from its path, so the shots of two
// side by side do not meet, and three more for the end-to-end run's world
// (E2E=1), so it goes beside `shots-up`'s; SHOTS_PORT moves them.
const own =
  (process.env.E2E ? 29000 : 20000) +
  3 * (createHash('sha256').update(root).digest().readUInt16BE(0) % 3000)
export const port = Number(process.env.SHOTS_PORT ?? own)
export const base = `http://127.0.0.1:${port}`
export const saved = join(root, 'target/shots/world.json')
// The pages say how long ago things were: now is a few hours after the
// fixtures' last, for the pages and the browser both, so the pictures are
// the same any day.
export const now = '2026-10-20T14:00:00Z'

/// When some of each runner's minutes free up, as the pages show it.
const freeing = '2026-11-02T00:00:00Z'

// The views drawn signed in, and as whom: only from the recording, where
// the stand-in for knotless-serve answers getViewer as that account
// (fixtures.mjs). A world has no session to sign them in, so shots-live
// skips them.
export const viewers = {
  settings: 'alice.test',
  'settings-branches': 'alice.test',
  'settings-labels': 'alice.test',
  'settings-access': 'alice.test',
  'settings-actions': 'alice.test',
  notifications: 'alice.test',
  // One who is neither the owner nor a collaborator: what to hear of the
  // repository, and of the issue it opened.
  'repository-watch': 'bob.test',
  'issue-subscribe': 'bob.test',
}

const people = {
  alice: { name: 'Alice Moreau', email: 'alice@example.com' },
  bob: { name: 'Bob Tanaka', email: 'bob@example.com' },
}

export const until = async (what, check) => {
  for (let i = 0; i < 300; i++) {
    try {
      if (await check()) return
    } catch {}
    await new Promise((r) => setTimeout(r, 100))
  }
  throw new Error(`timed out waiting for ${what}`)
}

/// What knotless keys a ref by: its SHA-256 in lower base32, cut to 26.
const refHash = (name) => {
  const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
  let bits = ''
  for (const b of createHash('sha256').update(name).digest()) bits += b.toString(2).padStart(8, '0')
  return (bits.match(/.{1,5}/g) ?? [])
    .map((c) => alphabet[parseInt(c.padEnd(5, '0'), 2)])
    .join('')
    .slice(0, 26)
}

export const page = async (path) => {
  const res = await fetch(base + path)
  return res.ok ? res.text() : ''
}

/// The environment `who` runs the CLI, git or the server in: committing
/// `when`, as written `authored`, or then too.
export const environment = (net, dir, who, when, authored) => {
  const clean = Object.fromEntries(
    Object.entries(process.env).filter(([k]) => !k.startsWith('KNOTLESS_')),
  )
  const person = people[who] ?? { name: who, email: `${who}@example.com` }
  return {
    ...clean,
    PATH: `${bin}:${process.env.PATH}`,
    KNOTLESS_HANDLE_RESOLVER: net.pds,
    KNOTLESS_PLC_URL: net.plc,
    KNOTLESS_COMPACT: 'off',
    KNOTLESS_APPVIEW: base,
    KNOTLESS_CONFIG_DIR: join(dir, who, 'config'),
    KNOTLESS_CACHE_DIR: join(dir, who, 'cache'),
    GIT_CONFIG_GLOBAL: '/dev/null',
    GIT_CONFIG_NOSYSTEM: '1',
    GIT_AUTHOR_NAME: person.name,
    GIT_AUTHOR_EMAIL: person.email,
    GIT_COMMITTER_NAME: person.name,
    GIT_COMMITTER_EMAIL: person.email,
    GIT_AUTHOR_DATE: authored ?? when ?? '2026-10-01T09:00:00Z',
    GIT_COMMITTER_DATE: when ?? '2026-10-01T09:00:00Z',
  }
}

/// The pages, from `build` (this checkout's web/build unless given),
/// answering on `at` and reading from `data`.
export const pages = (at, data, build = join(root, 'web/build')) =>
  spawn('node', [build], {
    env: {
      ...process.env,
      HOST: '127.0.0.1',
      PORT: String(at),
      KNOTLESS_SERVE: data,
      KNOTLESS_NOW: now,
      HOST_HEADER: 'x-forwarded-host',
      PROTOCOL_HEADER: 'x-forwarded-proto',
    },
    stdio: 'inherit',
  })

/// Whether Zoekt is here to search code with: the world's server then
/// keeps a code index, and its code results are shown. `just
/// shots-record` needs it, as the shots record them.
export const zoekt = (() => {
  try {
    execFileSync('sh', ['-c', 'command -v zoekt-webserver && command -v zoekt-git-index'])
    return true
  } catch {
    return false
  }
})()

/// knotless-serve from this build, over the world's index and mirrors, on
/// `at`: in front of the pages, unless `alone`. With `gateway`, the https
/// front a PDS sends logins back to, it is a gateway too, which the pages
/// log in to.
export const serve = async (world, { at = port, alone = false, gateway } = {}) => {
  const drawn = alone ? null : pages(port + 1, base)
  const server = spawn(
    join(bin, 'knotless-serve'),
    [
      '--listen',
      `127.0.0.1:${at}`,
      '--index',
      join(world.dir, 'index.db'),
      '--firehose',
      world.net.pds.replace('http', 'ws'),
      ...(zoekt ? ['--code-index', join(world.dir, 'code')] : []),
      ...(alone ? [] : ['--web', `http://127.0.0.1:${port + 1}`]),
      // atproto takes a did:web with a port only for localhost.
      ...(gateway ? ['--public-url', `http://localhost:${at}`, '--oauth-url', gateway] : []),
    ],
    {
      env: {
        ...environment(world.net, world.dir, 'server'),
        ...(gateway ? { KNOTLESS_DID_WEB_SCHEME: 'http' } : {}),
      },
      stdio: 'inherit',
    },
  )
  const kill = () => {
    server.kill()
    drawn?.kill()
  }
  const up = alone ? `http://127.0.0.1:${at}/healthz` : base
  try {
    await until('knotless-serve and the pages', async () => (await fetch(up)).ok)
  } catch (e) {
    kill()
    throw e
  }
  return { kill }
}

/// A new world: its PDS, a server, and the seeded data. Returns them with
/// the pages to shoot.
export async function create() {
  const dir = mkdtempSync(join(tmpdir(), 'knotless-shots-'))
  const children = []
  const stop = () => {
    for (const child of children.reverse()) child.kill()
    rmSync(dir, { recursive: true, force: true })
  }
  try {
    const pds = spawn('node', ['harness/pds.mjs', '--accounts', 'alice,bob,textkit'], {
      cwd: root,
      stdio: ['pipe', 'pipe', 'inherit'],
    })
    children.push(pds)
    const [line] = await createInterface({ input: pds.stdout })
      [Symbol.asyncIterator]()
      .next()
      .then((r) => [r.value])
    const net = JSON.parse(line)
    const account = Object.fromEntries(net.accounts.map((a) => [a.handle.split('.')[0], a]))

    const work = dir
    const env = (who, when, authored) => environment(net, dir, who, when, authored)
    const run = (who, cmd, args, opts = {}) =>
      execFileSync(cmd, args, {
        env: env(who, opts.when, opts.authored),
        cwd: opts.cwd ?? work,
        input: opts.input ?? '',
      })
        .toString()
        .trim()
    const knotless = (who, ...args) => run(who, join(bin, 'knotless'), args)
    const git = (who, dir, when, ...args) => run(who, 'git', args, { cwd: dir, when })
    /// Copies files from a fixture step into `dir` and commits them, as
    /// written `authored` if that was before.
    const commit = (who, dir, when, message, files, authored) => {
      for (const [from, to] of Object.entries(files)) {
        mkdirSync(dirname(join(dir, to)), { recursive: true })
        cpSync(join(fixture, from), join(dir, to))
      }
      git(who, dir, when, 'add', '-A')
      run(who, 'git', ['commit', '-q', '-m', message], { cwd: dir, when, authored })
    }

    children.push(await serve({ net, dir }))

    for (const who of ['alice', 'bob']) {
      run(who, join(bin, 'knotless'), ['login', account[who].handle, '--password-stdin'], {
        input: account[who].password,
      })
    }
    const tally = 'knotless://alice.test/tally'
    knotless(
      'alice',
      'repo',
      'create',
      tally,
      '--description',
      'Counts lines, words and bytes, with output that lines up',
      '--link',
      'https://tally.dev',
      '--link',
      'https://docs.tally.dev',
      '--title',
      'Docs',
    )
    const a = join(work, 'alice/tally')
    mkdirSync(a, { recursive: true })
    git('alice', a, null, 'init', '-q', '-b', 'main')
    commit('alice', a, '2026-10-01T09:12:00Z', 'Start tally', {
      'start/Cargo.toml': 'Cargo.toml',
      'start/.gitignore': '.gitignore',
      'start/README.md': 'README.md',
      'start/main.rs': 'src/main.rs',
    })
    commit('alice', a, '2026-10-02T14:40:00Z', 'Count words, lines and bytes', {
      'count/lib.rs': 'src/lib.rs',
      'count/main.rs': 'src/main.rs',
    })
    commit(
      'alice',
      a,
      '2026-10-03T08:05:00Z',
      'Read from stdin when no file is given\n\nSo tally works at the end of a pipe, as wc does. The name shown\nfor stdin is "-".',
      { 'stdin/main.rs': 'src/main.rs' },
    )
    // Written days before it was committed, as a rebase leaves one: it is
    // listed on the day it was committed, saying when it was written.
    commit(
      'alice',
      a,
      '2026-10-05T17:21:00Z',
      'Add a logo',
      { 'logo/logo.svg': 'assets/logo.svg' },
      '2026-10-02T11:30:00Z',
    )
    commit('alice', a, '2026-10-06T10:02:00Z', 'Document usage and output', {
      'docs/README.md': 'README.md',
      'docs/design.md': 'docs/design.md',
    })
    git('alice', a, null, 'mv', 'docs/design.md', 'docs/DESIGN.md')
    commit('alice', a, '2026-10-08T11:47:00Z', 'Split parsing into its own module', {
      'parse/parse.rs': 'src/parse.rs',
      'parse/lib.rs': 'src/lib.rs',
    })
    const parse = git('alice', a, null, 'rev-parse', 'HEAD')
    git('alice', a, null, 'push', '-q', tally, 'main')

    const notes = 'knotless://alice.test/notes'
    knotless('alice', 'repo', 'create', notes, '--description', 'Scratch notes 📝')
    const n = join(work, 'alice/notes')
    mkdirSync(n, { recursive: true })
    git('alice', n, null, 'init', '-q', '-b', 'main')
    commit('alice', n, '2026-09-20T19:30:00Z', 'Begin', { 'notes/README.md': 'README.md' })
    git('alice', n, null, 'push', '-q', notes, 'main')
    // A branch, then main moved past it: the feed lists only what main
    // brought that the branch had not.
    git('alice', n, null, 'checkout', '-q', '-b', 'reading')
    commit('alice', n, '2026-09-22T08:15:00Z', 'Start a reading list', {
      'reading/reading.md': 'reading.md',
    })
    git('alice', n, null, 'push', '-q', notes, 'reading')
    git('alice', n, null, 'checkout', '-q', 'main')
    git('alice', n, null, 'merge', '-q', '--ff-only', 'reading')
    commit('alice', n, '2026-09-23T21:40:00Z', 'Add Working in Public', {
      'reading/more.md': 'reading.md',
    })
    knotless('alice', 'repo', 'create', 'knotless://alice.test/scratch')
    git('alice', n, null, 'push', '-q', notes, 'main')

    const fork = knotless('bob', 'repo', 'fork', tally)
    // A star and a follow, for the counts the pages show.
    knotless('bob', 'repo', 'star', tally)
    knotless('bob', 'account', 'follow', 'alice.test')
    const b = join(work, 'bob/tally')
    git('bob', work, null, 'clone', '-q', fork, b)
    git('bob', b, null, 'checkout', '-q', '-b', 'empty-input')
    commit('bob', b, '2026-10-09T09:31:00Z', 'Count no lines in empty input', {
      'fix/parse.rs': 'src/parse.rs',
    })
    git('bob', b, null, 'push', '-q', 'origin', 'empty-input')
    git('bob', b, null, 'checkout', '-q', '-b', 'json', 'main')
    commit('bob', b, '2026-10-10T16:12:00Z', 'Print counts as JSON with --json', {
      'json/main.rs': 'src/main.rs',
    })
    git('bob', b, null, 'push', '-q', 'origin', 'json')

    // An issue the pull names to close, which its merge closes.
    const empty = knotless('bob', 'issue', 'create', tally, 'Counts one line for an empty file')
    const [, , issuer, , issueKey] = empty.split('/')
    const pull = knotless(
      'bob',
      'pull',
      'create',
      tally,
      'main',
      fork,
      'empty-input',
      'Count no lines in empty input',
      '-m',
      `\`tally\` printed one line for an empty file. \`lines\` now yields nothing when the text is empty, and a test covers it.\n\nFixes [the empty file's count](https://knotless.dev/alice.test/tally/issue/${issuer}/${issueKey}).`,
    )
    knotless(
      'alice',
      'comment',
      'add',
      tally,
      pull,
      'Could `lines` check for emptiness once, rather than in the filter?',
      '--changes',
    )
    knotless(
      'bob',
      'comment',
      'add',
      tally,
      pull,
      'It only runs per line, so the cost is one comparison each. Happy to change it if you prefer.',
    )
    knotless('alice', 'comment', 'add', tally, pull, 'Fair enough. Thanks!', '--approve')
    // Merging and closing read the item from the index, which follows the
    // PDS a moment behind.
    await until(
      'the pull',
      async () =>
        (await page('/alice.test/tally/pulls')).includes('Count no lines in empty input') &&
        (await page('/alice.test/tally/issues')).includes('Counts one line'),
    )
    // Squashed into main, the merge's commit links back to the pull: bob
    // wrote it, and alice committed it later that day.
    knotless('alice', 'branch', 'protect', tally, 'main')
    const [merged] = run('alice', join(bin, 'knotless'), ['pull', 'merge', tally, pull], {
      when: '2026-10-09T15:00:00Z',
    }).split('\n')
    const json = knotless(
      'bob',
      'pull',
      'create',
      tally,
      'main',
      fork,
      'json',
      'Print counts as JSON with --json',
      '-m',
      'For scripts. The object has `lines`, `words`, `bytes` and `name`.',
    )
    const utf8 = knotless(
      'bob',
      'issue',
      'create',
      tally,
      'Fails on files that are not UTF-8',
      '-m',
      "Running `tally image.png` panics:\n\n```\nthread 'main' panicked at src/main.rs:8:56:\nstream did not contain valid UTF-8\n```\n\nwc counts bytes for any file.",
    )
    const confirmed = knotless(
      'alice',
      'comment',
      'add',
      tally,
      utf8,
      'Confirmed. Reading bytes would fix it.',
    )
    // Said again, better: the page says it was edited.
    knotless(
      'alice',
      'comment',
      'edit',
      tally,
      confirmed,
      '-m',
      'Confirmed. Reading bytes and counting words lossily would fix it.',
    )
    // Labelled: one colored and described, one exclusive in its scope.
    knotless(
      'alice',
      'label',
      'define',
      tally,
      'kind/bug',
      '--color',
      '#d73a4a',
      '--description',
      'Something is broken',
    )
    knotless(
      'alice',
      'label',
      'define',
      tally,
      'priority/high',
      '--exclusive',
      '--color',
      '#fbca04',
    )
    knotless('alice', 'label', 'define', tally, 'priority/low', '--exclusive')
    knotless('alice', 'issue', 'label', tally, utf8, 'kind/bug', 'priority/high')
    const typo = knotless('bob', 'issue', 'create', tally, 'Typo in the README ✏️')
    await until('the issue', async () =>
      (await page('/alice.test/tally/issues')).includes('Typo in the README'),
    )
    knotless('alice', 'issue', 'close', tally, typo)

    // Pulls into the fork, for its merge queue: one landed, which merging
    // fast-forwards, and four to queue.
    const change = (branch, when, message, path, text) => {
      git('bob', b, null, 'checkout', '-q', '-B', branch, 'main')
      mkdirSync(dirname(join(b, path)), { recursive: true })
      writeFileSync(join(b, path), text)
      git('bob', b, when, 'add', '-A')
      git('bob', b, when, 'commit', '-q', '-m', message)
      git('bob', b, null, 'push', '-q', 'origin', branch)
      return knotless('bob', 'pull', 'create', fork, 'main', fork, branch, message)
    }
    const usage = change(
      'usage',
      '2026-10-11T08:00:00Z',
      'Show usage on --help',
      'docs/usage.md',
      'tally [--json] [FILE]...\n',
    )
    await until('the usage pull', async () =>
      (await page('/bob.test/tally/pulls')).includes('Show usage on --help'),
    )
    const [landed] = knotless('bob', 'pull', 'merge', fork, usage).split('\n')
    git('bob', b, null, 'fetch', '-q', 'origin')
    git('bob', b, null, 'checkout', '-q', '-B', 'main', 'origin/main')
    const queued = [
      change(
        'bytes',
        '2026-10-11T09:00:00Z',
        'Count bytes in files that are not UTF-8',
        'docs/bytes.md',
        'Bytes are counted as they are.\n',
      ),
      change(
        'tabs',
        '2026-10-11T10:00:00Z',
        'Line the columns up with tabs',
        'docs/tabs.md',
        'Columns are tab separated.\n',
      ),
      change(
        'words',
        '2026-10-11T11:00:00Z',
        'Count words in any script',
        'docs/words.md',
        'Words are split on Unicode white space.\n',
      ),
    ]
    const ejected = change(
      'totals',
      '2026-10-11T12:00:00Z',
      'Print a total for many files',
      'docs/totals.md',
      'A total follows the files.\n',
    )

    // Each names itself the runner of its tally, whose checks are shown.
    knotless('alice', 'runner', 'add', tally, 'alice.test')
    knotless('bob', 'runner', 'add', fork, 'bob.test')
    // The fork's main takes pulls through its merge queue.
    knotless('bob', 'branch', 'protect', fork, 'main', '--merges', '--queue')

    // Releases of alice's tally: the command twice, a pre-release of it
    // after, and a server's image, as an OCI layout holds one. Bob's fork
    // publishes none.
    const shipped = git('alice', a, null, 'ls-remote', tally, 'refs/heads/main').split('\t')[0]
    const dist = join(work, 'dist')
    const built = (version) =>
      ['aarch64-apple-darwin', 'x86_64-unknown-linux-musl'].map((target) => {
        const file = join(dist, version, `tally-${version}-${target}.tar.gz`)
        mkdirSync(dirname(file), { recursive: true })
        writeFileSync(file, `tally ${version} for ${target}\n`.repeat(4096))
        return file
      })
    // Each a little after the last, as one write's records are ordered
    // by their keys, not their times.
    const publish = async (pkg, version, files, notes, ...more) => {
      knotless(
        'alice',
        'release',
        'publish',
        tally,
        pkg,
        version,
        ...files,
        '--commit',
        shipped,
        '--notes',
        notes,
        ...more,
      )
      await new Promise((r) => setTimeout(r, 100))
    }
    await publish(
      'tally',
      '0.1.0',
      built('0.1.0'),
      'The first release: `tally` counts lines, words and bytes, as `wc` does.',
    )
    await publish(
      'tally',
      '0.1.1',
      built('0.1.1'),
      '### Fixed\n\n- An empty file counts no lines.\n- Input that is not UTF-8 is counted as bytes rather than failing.',
    )
    const layout = join(dist, 'oci')
    mkdirSync(join(layout, 'blobs/sha256'), { recursive: true })
    const blob = (bytes, mediaType) => {
      const hex = createHash('sha256').update(bytes).digest('hex')
      writeFileSync(join(layout, 'blobs/sha256', hex), bytes)
      return { mediaType, digest: `sha256:${hex}`, size: bytes.length }
    }
    const layer = blob(
      Buffer.from('tally-server layer\n'.repeat(2048)),
      'application/vnd.oci.image.layer.v1.tar+gzip',
    )
    const config = blob(
      Buffer.from(
        JSON.stringify({
          architecture: 'amd64',
          os: 'linux',
          rootfs: { type: 'layers', diff_ids: [layer.digest] },
        }),
      ),
      'application/vnd.oci.image.config.v1+json',
    )
    const manifest = blob(
      Buffer.from(
        JSON.stringify({
          schemaVersion: 2,
          mediaType: 'application/vnd.oci.image.manifest.v1+json',
          config,
          layers: [layer],
        }),
      ),
      'application/vnd.oci.image.manifest.v1+json',
    )
    writeFileSync(
      join(layout, 'index.json'),
      JSON.stringify({
        schemaVersion: 2,
        mediaType: 'application/vnd.oci.image.index.v1+json',
        manifests: [manifest],
      }),
    )
    writeFileSync(join(layout, 'oci-layout'), '{"imageLayoutVersion":"1.0.0"}')
    await publish(
      'tally-server',
      '0.1.0',
      [],
      'Serves counts over HTTP: `POST /count` with the text, and the counts come back as JSON.',
      '--oci',
      layout,
    )
    await publish(
      'tally',
      '0.2.0-rc.1',
      built('0.2.0-rc.1'),
      'A first look at 0.2.0: try it before it is the latest.\n\n- `tally --json` prints the counts as JSON.',
    )

    // A workflow run only when asked, which the checks page offers to run:
    // the fork's main's last commit, after the branches made from it, so no
    // other's hash moves.
    git('bob', b, null, 'checkout', '-q', 'main')
    mkdirSync(join(b, '.knotless/workflows'), { recursive: true })
    writeFileSync(
      join(b, '.knotless/workflows/release.yml'),
      '# Builds the release binaries for a tag and attaches them to it.\non: {asked: {refs: [refs/tags/*]}}\nsteps:\n  - name: build\n    run: cargo build --release\n',
    )
    git('bob', b, '2026-10-20T12:00:00Z', 'add', '-A')
    git('bob', b, '2026-10-20T12:00:00Z', 'commit', '-q', '-m', 'Release from tags when asked')
    git('bob', b, null, 'push', '-q', 'origin', 'main')

    // Every record a fixed createdAt, keeping their order.
    const records = []
    for (const who of ['alice', 'bob']) {
      const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ identifier: account[who].handle, password: account[who].password }),
      }).then((r) => r.json())
      const did = account[who].did
      if (who === 'alice') {
        const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
          method: 'POST',
          headers: {
            'content-type': 'application/json',
            authorization: `Bearer ${session.accessJwt}`,
          },
          body: JSON.stringify({
            repo: did,
            collection: 'app.bsky.actor.profile',
            rkey: 'self',
            record: {
              $type: 'app.bsky.actor.profile',
              displayName: people.alice.name,
              description: 'Small tools for text. Writes tally.',
            },
          }),
        })
        if (!res.ok) throw new Error(`profile: ${await res.text()}`)
      }
      const { collections } = await fetch(
        `${net.pds}/xrpc/com.atproto.repo.describeRepo?repo=${did}`,
      ).then((r) => r.json())
      for (const collection of collections) {
        let cursor = ''
        do {
          const listed = await fetch(
            `${net.pds}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=${collection}&limit=100${cursor}`,
          ).then((r) => r.json())
          for (const r of listed.records) {
            if (typeof r.value.createdAt === 'string' && collection !== 'dev.knotless.ref')
              records.push({ ...r, who, did, collection, jwt: session.accessJwt })
          }
          cursor = listed.cursor ? `&cursor=${listed.cursor}` : ''
        } while (cursor)
      }
    }
    // A new ref's push is its record's first version, which no claim
    // records: the index keeps it as a push with nothing before it, under
    // the key a claim on the generation before would have had, at the
    // time it was made. Each takes its place among the records as that
    // claim would, and its stamp is written into the index rather than the
    // PDS. The ref records themselves keep the times they were pushed at,
    // which no page shows.
    const index = new DatabaseSync(join(dir, 'index.db'))
    index.exec('PRAGMA busy_timeout = 10000')
    const who = Object.fromEntries(Object.entries(account).map(([name, a]) => [a.did, name]))
    for (const p of index
      .prepare('SELECT uri, did, created_at FROM push WHERE before IS NULL')
      .all()) {
      records.push({
        uri: p.uri,
        who: who[p.did],
        did: p.did,
        collection: 'dev.knotless.claim',
        value: { createdAt: p.created_at },
        pushed: true,
      })
    }
    // One write's records can be stamped a millisecond apart or in the same
    // one, which would order them either way: those made within 50 ms of
    // each other are ordered by account, collection and key instead: by
    // the account's name, as its DID is new in every world.
    const at = (r) => Date.parse(r.value.createdAt)
    records.sort((x, y) => at(x) - at(y))
    const writes = []
    for (const r of records) {
      const last = writes.at(-1)
      if (last && at(r) - at(last.at(-1)) < 50) last.push(r)
      else writes.push([r])
    }
    const named = (r) => `${r.who} ${r.collection} ${r.uri.split('/').pop()}`
    records.splice(
      0,
      records.length,
      ...writes.flatMap((w) => w.sort((x, y) => (named(x) < named(y) ? -1 : 1))),
    )
    const start = Date.parse('2026-10-09T10:00:00Z')
    const cids = new Map()
    // Strong references to a record whose CID changed are pointed at the new one.
    const repoint = (v) => {
      if (Array.isArray(v)) return v.map(repoint)
      if (v && typeof v === 'object') {
        const out = Object.fromEntries(Object.entries(v).map(([k, x]) => [k, repoint(x)]))
        if (typeof out.uri === 'string' && typeof out.cid === 'string' && cids.has(out.uri))
          out.cid = cids.get(out.uri)
        return out
      }
      return v
    }
    for (const [i, r] of records.entries()) {
      const created = start + i * 37 * 60_000
      // One edited says so a little after it was made.
      const updated = r.value.updatedAt
        ? { updatedAt: new Date(created + 12 * 60_000).toISOString() }
        : {}
      if (r.pushed) {
        index
          .prepare('UPDATE push SET created_at = ? WHERE uri = ?')
          .run(new Date(created).toISOString(), r.uri)
        continue
      }
      const value = repoint({ ...r.value, createdAt: new Date(created).toISOString(), ...updated })
      const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
        method: 'POST',
        headers: { 'content-type': 'application/json', authorization: `Bearer ${r.jwt}` },
        body: JSON.stringify({
          repo: r.did,
          collection: r.collection,
          rkey: r.uri.split('/').pop(),
          record: value,
        }),
      })
      if (!res.ok) throw new Error(`putRecord ${r.uri}: ${await res.text()}`)
      cids.set(r.uri, (await res.json()).cid)
    }

    // An organization, textkit, with bob a member in public and alice
    // invited. Written as records, dated as the rest are: joining asks
    // the organization's gateway, which this world has none of. Lapsing
    // goes by the clock, not the world's now, so alice's invitation runs
    // long enough to stay one.
    const put = async (who, collection, rkey, record) => {
      const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ identifier: account[who].handle, password: account[who].password }),
      }).then((r) => r.json())
      const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          authorization: `Bearer ${session.accessJwt}`,
        },
        body: JSON.stringify({
          repo: account[who].did,
          collection,
          rkey,
          record: { $type: collection, ...record },
        }),
      })
      if (!res.ok) throw new Error(`putRecord ${who} ${collection}: ${await res.text()}`)
    }
    await put('textkit', 'app.bsky.actor.profile', 'self', {
      displayName: 'Textkit',
      description: 'Tools for text, made together.',
    })
    await put('textkit', 'dev.knotless.org', 'self', { createdAt: '2026-10-12T09:00:00.000Z' })
    await put('textkit', 'dev.knotless.orgMember', account.bob.did, {
      member: account.bob.did,
      roles: [{ role: 'admin' }],
      invitedAt: '2026-10-12T09:00:00.000Z',
      expiresAt: '2026-10-19T09:00:00.000Z',
      joinedAt: '2026-10-12T11:00:00.000Z',
    })
    await put('textkit', 'dev.knotless.orgMember', account.alice.did, {
      member: account.alice.did,
      invitedAt: '2026-10-20T09:00:00.000Z',
      expiresAt: '2099-10-27T09:00:00.000Z',
    })
    await put('bob', 'dev.knotless.orgAcceptance', account.textkit.did, {
      subject: account.textkit.did,
      createdAt: '2026-10-12T10:00:00.000Z',
    })

    // Checks, as a runner reports them: of tally's main, and of both pulls' branches.
    const main = git('alice', a, null, 'ls-remote', tally, 'refs/heads/main').split('\t')[0]
    const tip = (branch) => git('bob', b, null, 'rev-parse', branch)
    const checks = [
      ['alice', 'alice/tally', 'main', main, 'test', 'success', '2026-10-11T09:00:00Z'],
      ['alice', 'alice/tally', 'main', main, 'lint', 'success', '2026-10-11T09:00:00Z'],
      [
        'bob',
        'bob/tally',
        'empty-input',
        tip('empty-input'),
        'test',
        'success',
        '2026-10-09T09:32:00Z',
      ],
      ['bob', 'bob/tally', 'json', tip('json'), 'build', 'success', '2026-10-10T16:13:00Z'],
      ['bob', 'bob/tally', 'json', tip('json'), 'test', 'failure', '2026-10-10T16:13:00Z'],
      ['bob', 'bob/tally', 'json', tip('json'), 'lint', 'running', '2026-10-10T16:13:00Z'],
      // One the change left out, as the runner notes it.
      [
        'bob',
        'bob/tally',
        'json',
        tip('json'),
        'docs',
        'success',
        '2026-10-10T16:13:00Z',
        'skipped: nothing changed in docs/**, assets/**, .knotless/workflows/docs.yml',
      ],
    ]
    // What each run did, step by step, and the log of the one that failed.
    const ran = {
      success: [
        ['build', 'success', 0, 38400],
        ['test', 'success', 0, 2100],
      ],
      failure: [
        ['build', 'success', 0, 41200],
        ['test', 'failure', 101, 3300],
      ],
      running: [],
    }
    const failed = [
      '==> build',
      '   Compiling tally v0.1.0 (/work)',
      '    Finished `test` profile [unoptimized + debuginfo] target(s) in 41.17s',
      '==> test',
      '     Running unittests src/lib.rs (target/debug/deps/tally-3f9c2a1b)',
      '',
      'running 4 tests',
      'test tests::counts_bytes ... ok',
      'test tests::counts_lines ... ok',
      'test tests::counts_words ... ok',
      'test tests::prints_json ... FAILED',
      '',
      'failures:',
      '',
      '---- tests::prints_json stdout ----',
      "thread 'tests::prints_json' panicked at src/lib.rs:41:9:",
      'assertion `left == right` failed',
      '  left: "{\"lines\":2,\"words\":3,\"bytes\":14}"',
      ' right: "{\"lines\":2,\"words\":3,\"bytes\":14,\"name\":\"-\"}"',
      '',
      'test result: FAILED. 3 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s',
      '',
    ].join('\n')
    // What a failed shots run leaves: a page before and after a change, and
    // where they differ, drawn as a band that moved down.
    const band = (top) => (x, y) =>
      y >= top && y < top + 24 && x >= 24 && x < 216 ? [60, 64, 72] : [244, 245, 247]
    const pictures = [
      ['pull-desktop-light 1 before', band(40)],
      ['pull-desktop-light 2 after', band(72)],
      [
        'pull-desktop-light 3 difference',
        (x, y) => (band(40)(x, y)[0] !== band(72)(x, y)[0] ? [230, 40, 40] : [250, 250, 250]),
      ],
    ]
    const rkeys = {}
    for (const [who, repo, branch, commit, workflow, status, startedAt, skipped] of checks) {
      const [owner, name] = repo.split('/')
      const did = account[owner].did
      const { key } = await fetch(
        `${net.pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=dev.knotless.repo&rkey=${name}`,
      )
        .then((r) => r.json())
        .then((r) => r.value)
      const ref = `refs/heads/${branch}`
      const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ identifier: account[who].handle, password: account[who].password }),
      }).then((r) => r.json())
      const auth = { authorization: `Bearer ${session.accessJwt}` }
      const log =
        status === 'failure'
          ? await fetch(`${net.pds}/xrpc/com.atproto.repo.uploadBlob`, {
              method: 'POST',
              headers: { 'content-type': 'text/plain', ...auth },
              body: failed,
            })
              .then((r) => r.json())
              .then((r) => ({ log: r.blob }))
          : {}
      const outputs =
        status === 'failure'
          ? {
              outputs: await Promise.all(
                pictures.map(async ([alt, draw]) => {
                  const image = await fetch(`${net.pds}/xrpc/com.atproto.repo.uploadBlob`, {
                    method: 'POST',
                    headers: { 'content-type': 'image/png', ...auth },
                    body: png(240, 136, draw),
                  })
                    .then((r) => r.json())
                    .then((r) => r.blob)
                  return { $type: 'dev.knotless.check#image', image, alt }
                }),
              ),
            }
          : {}
      const steps = skipped
        ? [{ name: 'build', status: 'skipped', durationMs: 0 }]
        : ran[status].map(([name, status, exitCode, durationMs]) => ({
            name,
            status,
            exitCode,
            durationMs,
          }))
      const took = skipped ? 0 : steps.reduce((ms, s) => ms + s.durationMs, 0) + 1000
      const record = {
        $type: 'dev.knotless.check',
        repo: { uri: `at://${did}/dev.knotless.repo/${name}`, key },
        ref,
        commit,
        workflow,
        status,
        startedAt,
        ...(status === 'running'
          ? {}
          : { finishedAt: new Date(Date.parse(startedAt) + took).toISOString() }),
        steps,
        ...(skipped ? { skipped } : {}),
        ...log,
        ...outputs,
      }
      const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...auth },
        body: JSON.stringify({
          repo: account[who].did,
          collection: 'dev.knotless.check',
          rkey: (rkeys[`${repo} ${workflow} ${status}`] =
            `${refHash(did)}.${key}.${refHash(ref)}.${commit}.${workflow}`),
          record,
        }),
      })
      if (!res.ok) throw new Error(`check ${workflow}: ${await res.text()}`)
    }

    // The minutes each runner counts for its owner: alice nearing her
    // allowance, bob past his.
    for (const [who, minutes, frees] of [
      ['alice', 1640, 310],
      ['bob', 2140, 400],
    ]) {
      const record = {
        $type: 'dev.knotless.minutes',
        owner: account[who].did,
        minutes,
        allowance: 2000,
        freeing: [{ at: freeing, minutes: frees }],
        updatedAt: '2026-10-25T17:52:00Z',
      }
      const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ identifier: account[who].handle, password: account[who].password }),
      }).then((r) => r.json())
      const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          authorization: `Bearer ${session.accessJwt}`,
        },
        body: JSON.stringify({
          repo: account[who].did,
          collection: 'dev.knotless.minutes',
          rkey: account[who].did,
          record,
        }),
      })
      if (!res.ok) throw new Error(`minutes ${who}: ${await res.text()}`)
    }

    // alice, the tally's runner, publishes a key its secrets are sealed to.
    // None is set: this server has no gateway for `knotless secret` to sign
    // for, so the settings draw the box empty, with its form.
    {
      const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          identifier: account.alice.handle,
          password: account.alice.password,
        }),
      }).then((r) => r.json())
      const record = {
        $type: 'dev.knotless.runnerKey',
        key: 'did:key:zDnaepwQjCS7ayRrLp2j3oFd8kfJ97mehqYTGFBNA3QqSsvNV',
        createdAt: '2026-10-25T17:52:00Z',
      }
      const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          authorization: `Bearer ${session.accessJwt}`,
        },
        body: JSON.stringify({
          repo: account.alice.did,
          collection: 'dev.knotless.runnerKey',
          rkey: 'self',
          record,
        }),
      })
      if (!res.ok) throw new Error(`runner key: ${await res.text()}`)
    }

    // The fork's merge queue, as bob, its owner and runner, keeps it: three
    // pulls queued, the first two in a batch being checked and the third
    // running its pre-flight; one ejected by the checks of the batch it was
    // in; and the batch that landed.
    {
      const did = account.bob.did
      const { key } = await fetch(
        `${net.pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=dev.knotless.repo&rkey=tally`,
      )
        .then((r) => r.json())
        .then((r) => r.value)
      const repo = { uri: `at://${did}/dev.knotless.repo/tally`, key }
      const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ identifier: account.bob.handle, password: account.bob.password }),
      }).then((r) => r.json())
      const write = async (collection, record, rkey) => {
        const res = await fetch(
          `${net.pds}/xrpc/com.atproto.repo.${rkey ? 'putRecord' : 'createRecord'}`,
          {
            method: 'POST',
            headers: {
              'content-type': 'application/json',
              authorization: `Bearer ${session.accessJwt}`,
            },
            body: JSON.stringify({
              repo: did,
              collection,
              ...(rkey ? { rkey } : {}),
              record: { $type: collection, ...record },
            }),
          },
        )
        if (!res.ok) throw new Error(`${collection}: ${await res.text()}`)
        return (await res.json()).uri
      }
      const base = git('bob', b, null, 'rev-parse', `${landed}^`)
      const batch = (pulls, commit) => pulls.map((pull) => ({ pull, commit }))
      const runs = [
        [
          'landed',
          [usage],
          landed,
          base,
          [
            ['build', 'success'],
            ['test', 'success'],
          ],
          '2026-10-19T09:00:00Z',
        ],
        [
          'ejected',
          [ejected],
          tip('totals'),
          landed,
          [
            ['build', 'success'],
            ['test', 'failure'],
          ],
          '2026-10-19T15:00:00Z',
        ],
        [
          'checking',
          queued.slice(0, 2),
          tip('tabs'),
          landed,
          [
            ['build', 'success'],
            ['test', 'running'],
          ],
          '2026-10-20T10:00:00Z',
        ],
      ]
      const failed = []
      for (const [name, pulls, commit, baseCommit, ran, startedAt] of runs) {
        for (const [workflow, status] of ran) {
          const rkey = `${refHash(did)}.${key}.${refHash('queue refs/heads/main')}.${commit}.${baseCommit}.${workflow}`
          const finished =
            status === 'running'
              ? {}
              : { finishedAt: new Date(Date.parse(startedAt) + 95_000).toISOString() }
          const uri = await write(
            'dev.knotless.check',
            {
              repo,
              ref: 'refs/heads/main',
              commit,
              baseCommit,
              batch: batch(pulls, commit),
              workflow,
              status,
              startedAt,
              ...finished,
              steps: [],
            },
            rkey,
          )
          if (name === 'ejected' && status === 'failure')
            failed.push({ workflow, status, check: uri })
        }
      }
      // Their pre-flights: the first two's passed, in six minutes each, and
      // the third's has run since a little before now.
      const flights = [
        [queued[0], 'bytes', 'success', '2026-10-20T09:40:00Z'],
        [queued[1], 'tabs', 'success', '2026-10-20T09:42:00Z'],
        [queued[2], 'words', 'running', '2026-10-20T13:57:00Z'],
      ]
      for (const [pull, branch, status, startedAt] of flights) {
        const commit = tip(branch)
        const rkey = `${refHash(did)}.${key}.${refHash(pull)}.${commit}.${landed}.test`
        const finished =
          status === 'running'
            ? {}
            : { finishedAt: new Date(Date.parse(startedAt) + 360_000).toISOString() }
        await write(
          'dev.knotless.check',
          {
            repo,
            ref: `refs/heads/${branch}`,
            commit,
            baseCommit: landed,
            pull,
            queue: 'first',
            workflow: 'test',
            status,
            startedAt,
            ...finished,
            steps: [],
          },
          rkey,
        )
      }
      for (const [i, pull] of [...queued, ejected].entries()) {
        await write('dev.knotless.queue', {
          repo,
          subject: pull,
          state: 'queued',
          createdAt: `2026-10-1${5 + i}T09:30:00.000Z`,
        })
      }
      await write('dev.knotless.queue', {
        repo,
        subject: ejected,
        state: 'ejected',
        commit: tip('totals'),
        checks: failed,
        reason: `its batch failed its checks at ${tip('totals').slice(0, 12)}, on main at ${landed.slice(0, 12)}: test (failure)`,
        createdAt: '2026-10-19T15:02:00.000Z',
      })
    }

    const item = (kind, uri) => {
      const [, , did, , rkey] = uri.split('/')
      return `/alice.test/tally/${kind}/${did}/${rkey}`
    }
    const views = {
      home: '/',
      search: '/search?q=t',
      code: '/search?type=code&q=count',
      'code-repo': '/search?type=code&q=count+repo:alice.test/tally',
      start: '/start',
      terms: '/terms',
      privacy: '/privacy',
      guidelines: '/guidelines',
      stats: '/stats',
      profile: '/alice.test',
      'profile-fork': '/bob.test',
      'profile-repos': '/alice.test?tab=repos',
      'profile-activity': '/alice.test?tab=activity',
      org: '/textkit.test',
      'org-members': '/textkit.test?tab=members',
      repository: '/alice.test/tally',
      'repository-watch': '/alice.test/tally',
      directory: '/alice.test/tally/tree/main/src',
      file: '/alice.test/tally/blob/main/src/lib.rs',
      blame: '/alice.test/tally/blame/main/src/lib.rs',
      markdown: '/alice.test/tally/blob/main/README.md',
      'markdown-plain': '/alice.test/tally/blob/main/README.md?plain=1',
      diagram: '/alice.test/tally/blob/main/docs/DESIGN.md',
      image: '/alice.test/tally/blob/main/assets/logo.svg',
      commits: '/alice.test/tally/commits/main',
      commit: `/alice.test/tally/commit/${parse}`,
      'commit-merged': `/alice.test/tally/commit/${merged}`,
      pulls: '/alice.test/tally/pulls',
      'pulls-closed': '/alice.test/tally/pulls?state=closed',
      pull: item('pull', pull),
      'pull-open': item('pull', json),
      'pull-files': `${item('pull', pull)}/files`,
      issues: '/alice.test/tally/issues',
      'issues-closed': '/alice.test/tally/issues?state=closed',
      issue: item('issue', utf8),
      'issue-subscribe': item('issue', utf8),
      'issue-closed': item('issue', typo),
      'issue-fixed': item('issue', empty),
      fork: '/bob.test/tally',
      empty: '/alice.test/scratch',
      missing: '/alice.test/tally/blob/main/nowhere',
      check: `/bob.test/tally/check/${account.bob.did}/${rkeys['bob/tally test failure']}`,
      queue: '/bob.test/tally/queue',
      checks: '/bob.test/tally/checks',
      'pull-queued': item('pull', queued[0]).replace('/alice.test/', '/bob.test/'),
      'pull-ejected': item('pull', ejected).replace('/alice.test/', '/bob.test/'),
      settings: '/alice.test/tally/settings',
      'settings-branches': '/alice.test/tally/settings/branches',
      'settings-labels': '/alice.test/tally/settings/labels',
      'settings-access': '/alice.test/tally/settings/access',
      'settings-actions': '/alice.test/tally/settings/actions',
      releases: '/alice.test/tally/releases',
      'releases-image': '/alice.test/tally/releases?package=tally-server',
      'releases-empty': '/bob.test/tally/releases',
      notifications: '/notifications',
    }
    // The pages whose social cards are shot, by the names of the shots.
    const cards = {
      home: '/',
      repository: '/alice.test/tally',
      notes: '/alice.test/notes',
      pull: views.pull,
      'pull-open': views['pull-open'],
      issue: views.issue,
      'issue-closed': item('issue', typo),
    }
    // The index has caught up when no page shows today's date, every
    // record now being dated before it, and the merge shows. The day
    // runner minutes free up on is a date the pages show too, and is left
    // out, as on that day it is today's.
    const day = (t) =>
      new Date(t).toLocaleDateString('en-US', {
        month: 'short',
        day: 'numeric',
        year: 'numeric',
        timeZone: 'UTC',
      })
    const today = day(Date.now())
    // What still holds the wait back, said if it times out.
    let missing = []
    const shown = [
      'Merged as',
      'Closed by',
      '2 Closed',
      'Forked from',
      '1 Closed',
      '2/2',
      'prints_json',
      'In a batch of 2 being checked',
      'Landed',
      'Runs only when asked',
      'tally-server',
      'Pre-release',
    ]
    // Each tally's default branch, which its code is found at once indexed.
    const tips = () =>
      ['alice.test', 'bob.test'].map(
        (o) =>
          run('alice', 'git', ['ls-remote', `knotless://${o}/tally`, 'refs/heads/main']).split(
            /\s/,
          )[0],
      )
    const indexed = (html) => tips().every((tip) => html.includes(`/blob/${tip}/`))
    const caughtUp = async () => {
      const names = Object.keys(views)
      const pages = await Promise.all(Object.values(views).map(page))
      const all = pages.join('')
      const dated = names.filter((_, i) => pages[i].replaceAll(day(freeing), '').includes(today))
      missing = [
        ...dated.map((v) => {
          const at = pages[names.indexOf(v)].replaceAll(day(freeing), '').indexOf(today)
          return `today's date on ${v}: ${JSON.stringify(pages[names.indexOf(v)].slice(Math.max(0, at - 160), at + 40))}`
        }),
        ...shown.filter((t) => !all.includes(t)),
        ...(all.includes('Some checks failed') || all.includes('Some checks were not successful')
          ? []
          : ['Some checks failed']),
        ...(/Ejected <time/.test(all) ? [] : ['Ejected <time']),
        // Code, once Zoekt has indexed both tallies at their tips.
        ...(!zoekt || indexed(pages[names.indexOf('code')])
          ? []
          : ["code results at each tally's tip"]),
        ...((await page('/bob.test')).includes('textkit.test')
          ? []
          : ['textkit.test on /bob.test']),
        ...((await page(views['org-members'])).includes('bob.test')
          ? []
          : ['bob.test on org-members']),
      ]
      return missing.length === 0
    }
    try {
      await until('the index', caughtUp)
    } catch (e) {
      throw new Error(`${e.message}, still missing: ${missing.join('; ')}`)
    }
    return { net, dir, views, cards, pds, server: children[1], stop }
  } catch (e) {
    stop()
    throw e
  }
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const world = await create()
  world.server.kill()
  mkdirSync(dirname(saved), { recursive: true })
  writeFileSync(
    saved,
    JSON.stringify(
      { net: world.net, dir: world.dir, views: world.views, cards: world.cards },
      null,
      2,
    ),
  )
  console.log(`seeded; kept up for \`just shots\` until ctrl-c (${saved})`)
  const end = () => {
    rmSync(saved, { force: true })
    world.stop()
    process.exit(0)
  }
  process.on('SIGINT', end)
  process.on('SIGTERM', end)
  process.stdin.on('end', end)
  process.stdin.resume()
}

/// A PNG of `width` by `height`, each pixel's [r, g, b] from `draw(x, y)`.
function png(width, height, draw) {
  const chunk = (type, data) => {
    const head = Buffer.alloc(8)
    head.writeUInt32BE(data.length)
    head.write(type, 4, 'latin1')
    const sum = Buffer.alloc(4)
    sum.writeUInt32BE(crc32(Buffer.concat([head.subarray(4), data])))
    return Buffer.concat([head, data, sum])
  }
  const header = Buffer.alloc(13)
  header.writeUInt32BE(width)
  header.writeUInt32BE(height, 4)
  header.set([8, 2, 0, 0, 0], 8)
  const rows = Buffer.alloc((width * 3 + 1) * height)
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) rows.set(draw(x, y), y * (width * 3 + 1) + 1 + x * 3)
  }
  return Buffer.concat([
    Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
    chunk('IHDR', header),
    chunk('IDAT', deflateSync(rows)),
    chunk('IEND', Buffer.alloc(0)),
  ])
}