wsb-daemon 0.275.7622

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
<script lang="ts">
  // The catalogue's findings, in the content view rather than the sidebar.
  //
  // They were rendered inline in a 300px panel: sixty-one duplicate numbers and
  // thirty-two name/description disagreements, each with two or three file
  // references. Every line wrapped, nothing could be scanned, and the list a
  // person actually needed — "which repository, and which files do I open" —
  // was reachable only by scrolling past all of it.
  //
  // Here there is room to group by repository, to say what each finding MEANS,
  // and to open the file the finding is about.
  import { onMount } from "svelte";
  import { catalogue as catalogueStore } from "../../stores/catalogue.svelte.js";
  import CatalogueBrowser from "./CatalogueBrowser.svelte";
  import { workbench } from "../../stores/workbench.svelte.js";
  import type { CataloguedTest } from "../../api/types/catalogue.js";
  import { scoped, viewWorktree } from "../../stores/worktree-scope.svelte.js";
  const catalogue = scoped(catalogueStore);
  const worktree = viewWorktree("the test catalogue");

  /** Every test, or what is wrong with them. */
  type View = "browse" | "findings";
  let view = $state<View>("browse");

  type Group = "all" | "duplicates" | "misnumbered";
  let showing = $state<Group>("all");
  let repoFilter = $state<string | null>(null);

  let survey = $derived(catalogue.survey);

  // This page is the catalogue's only view, so it reads the survey itself
  // rather than relying on something else having read it first.
  onMount(() => {
    if (!catalogue.survey) void catalogue.load();
  });

  /// Repositories that actually have findings — the only ones worth offering
  /// as a filter. A list of every repository, most of them clean, is a list
  /// nobody uses.
  let affected = $derived.by(() => {
    if (!survey) return [];
    const seen = new Map<string, number>();
    for (const duplicate of survey.duplicates) {
      seen.set(duplicate.repo, (seen.get(duplicate.repo) ?? 0) + 1);
    }
    for (const mismatch of survey.misnumbered) {
      seen.set(mismatch.test.repo, (seen.get(mismatch.test.repo) ?? 0) + 1);
    }
    return [...seen.entries()].sort((a, b) => b[1] - a[1]);
  });

  let duplicates = $derived(
    (survey?.duplicates ?? []).filter((d) => !repoFilter || d.repo === repoFilter),
  );
  let misnumbered = $derived(
    (survey?.misnumbered ?? []).filter((m) => !repoFilter || m.test.repo === repoFilter),
  );

  /// A finding names a file in a repository; the worktree path is the two
  /// joined. The superproject's own files carry no prefix.
  function worktreePath(test: CataloguedTest): string {
    return test.repo === "(superproject)" || !test.repo
      ? test.file_path
      : `${test.repo}/${test.file_path}`;
  }

  function open(test: CataloguedTest) {
    workbench.openFile(worktreePath(test), true, undefined, worktree);
  }
</script>

<div class="diagnostics">
  <header>
    <div class="title">
      <h1>Test catalogue</h1>
      {#if survey}
        <p class="sub">
          {survey.total} tests, {survey.numbered} numbered, across
          {survey.per_repo.length} repositories
        </p>
      {/if}
    </div>
    <button class="secondary" disabled={catalogue.scanning} onclick={() => void catalogue.rescan()}>
      {catalogue.scanning ? "Rescanning…" : "Rescan"}
    </button>
  </header>

  <div class="tabs views">
    <button class="tab" class:on={view === "browse"} onclick={() => (view = "browse")}>
      Browse
    </button>
    <button class="tab" class:on={view === "findings"} onclick={() => (view = "findings")}>
      Findings{survey && !survey.clean
        ? ` (${survey.duplicates.length + survey.misnumbered.length})`
        : ""}
    </button>
  </div>

  {#if catalogue.error}
    <p class="empty">{catalogue.error}</p>
  {/if}

  {#if view === "browse"}
    <CatalogueBrowser />
  {:else if !survey}
    <p class="empty">Nothing catalogued yet. Rescan to read the worktree.</p>
  {:else if survey.clean}
    <p class="clean">
      No number is used twice within a repository, and every name agrees with
      its description.
    </p>
  {:else}
    <div class="controls">
      <div class="tabs">
        <button class="tab" class:on={showing === "all"} onclick={() => (showing = "all")}>
          All ({survey.duplicates.length + survey.misnumbered.length})
        </button>
        <button
          class="tab"
          class:on={showing === "duplicates"}
          onclick={() => (showing = "duplicates")}
        >
          Duplicates ({survey.duplicates.length})
        </button>
        <button
          class="tab"
          class:on={showing === "misnumbered"}
          onclick={() => (showing = "misnumbered")}
        >
          Name ≠ description ({survey.misnumbered.length})
        </button>
      </div>

      <div class="repos">
        <button class="chip" class:on={repoFilter === null} onclick={() => (repoFilter = null)}>
          every repository
        </button>
        {#each affected as [repo, count] (repo)}
          <button
            class="chip"
            class:on={repoFilter === repo}
            onclick={() => (repoFilter = repoFilter === repo ? null : repo)}
          >
            {catalogue.labelFor(repo)} <span class="count">{count}</span>
          </button>
        {/each}
      </div>
    </div>

    {#if showing !== "misnumbered" && duplicates.length > 0}
      <section>
        <h2>One number, more than one test</h2>
        <!-- What it means, once, rather than left for the reader to infer from
             a list of file paths. -->
        <p class="what">
          A number identifies a test across every mirror, so two tests sharing
          one inside a repository makes both unaddressable: a parity query
          returns the wrong one, and neither can be pointed at.
        </p>
        {#each duplicates as duplicate (duplicate.repo + duplicate.number)}
          <article class="finding">
            <div class="head">
              <span class="number">TEST{duplicate.number}</span>
              <span class="repo">{catalogue.labelFor(duplicate.repo)}</span>
              <span class="count">{duplicate.members.length} tests</span>
            </div>
            {#each duplicate.members as member (member.id)}
              <button class="ref" onclick={() => open(member)} title="Open {worktreePath(member)}">
                <span class="where">{member.file_path}:{member.line_number}</span>
                <span class="fn">{member.function_name}</span>
              </button>
            {/each}
          </article>
        {/each}
      </section>
    {/if}

    {#if showing !== "duplicates" && misnumbered.length > 0}
      <section>
        <h2>The name and the description disagree</h2>
        <p class="what">
          The number in the function name and the number in the comment above it
          are different. One of them is what everything else refers to, and
          there is no way to tell which from here — the file has to be opened.
        </p>
        {#each misnumbered as mismatch (mismatch.test.id)}
          <article class="finding">
            <div class="head">
              <span class="number">TEST{mismatch.test.number}</span>
              <span class="versus">described as</span>
              <span class="number other">
                {mismatch.described_number === null
                  ? "no number"
                  : `TEST${mismatch.described_number}`}
              </span>
              <span class="repo">{catalogue.labelFor(mismatch.test.repo)}</span>
            </div>
            <button
              class="ref"
              onclick={() => open(mismatch.test)}
              title="Open {worktreePath(mismatch.test)}"
            >
              <span class="where">
                {mismatch.test.file_path}:{mismatch.test.line_number}
              </span>
              <span class="fn">{mismatch.test.function_name}</span>
            </button>
          </article>
        {/each}
      </section>
    {/if}

    {#if duplicates.length === 0 && misnumbered.length === 0}
      <p class="empty">
        No findings in {repoFilter ? catalogue.labelFor(repoFilter) : "this selection"}.
      </p>
    {/if}
  {/if}
</div>

<style>
  .diagnostics {
    max-width: 1100px;
    margin: 0 auto;
    padding: var(--wb-spacing-20) var(--wb-spacing-24) var(--wb-spacing-48);
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-14);
  }

  header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--wb-spacing-12);
  }

  h1 {
    margin: 0;
    font-size: var(--wb-font-size-17);
    font-weight: 600;
  }

  h2 {
    margin: 0;
    font-size: var(--wb-font-size-12);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    opacity: 0.7;
  }

  .sub,
  .what,
  .empty,
  .clean {
    margin: 0;
    font-size: var(--wb-font-size-12);
    opacity: 0.65;
    line-height: 1.5;
  }

  .what {
    max-width: 72ch;
  }

  .clean {
    opacity: 0.8;
  }

  .controls {
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-8);
    padding-bottom: var(--wb-spacing-4);
    border-bottom: 1px solid var(--vscode-panel-border, rgba(128, 128, 128, 0.2));
  }

  .tabs,
  .repos {
    display: flex;
    gap: var(--wb-spacing-5);
    flex-wrap: wrap;
  }

  .tab,
  .chip,
  .secondary {
    padding: var(--wb-spacing-3) var(--wb-spacing-10);
    font-size: var(--wb-font-size-11);
    font-family: inherit;
    border-radius: var(--wb-corner-radius-3);
    border: 1px solid var(--vscode-button-border, rgba(128, 128, 128, 0.3));
    background: transparent;
    color: inherit;
    cursor: pointer;
  }

  .tab.on,
  .chip.on {
    background: var(--vscode-list-activeSelectionBackground, rgba(120, 150, 220, 0.25));
    border-color: transparent;
  }

  .chip {
    font-size: var(--wb-font-size-10-5);
    opacity: 0.85;
  }

  .chip .count {
    opacity: 0.55;
    padding-left: var(--wb-spacing-3);
  }

  .secondary:disabled {
    opacity: 0.5;
    cursor: default;
  }

  section {
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-8);
    padding-top: var(--wb-spacing-6);
  }

  .finding {
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-2);
    padding: var(--wb-spacing-7) var(--wb-spacing-10);
    border-radius: var(--wb-corner-radius-4);
    background: var(--vscode-editor-inactiveSelectionBackground, rgba(128, 128, 128, 0.07));
  }

  .head {
    display: flex;
    align-items: baseline;
    gap: var(--wb-spacing-8);
    flex-wrap: wrap;
    padding-bottom: var(--wb-spacing-2);
  }

  .number {
    font-family: var(--vscode-editor-font-family, monospace);
    font-size: var(--wb-font-size-12);
    font-weight: 600;
  }

  .number.other {
    opacity: 0.75;
  }

  .versus,
  .repo,
  .head .count {
    font-size: var(--wb-font-size-10-5);
    opacity: 0.6;
  }

  /* A finding is about a file, so it opens the file. */
  .ref {
    display: flex;
    gap: var(--wb-spacing-10);
    align-items: baseline;
    padding: var(--wb-spacing-2) var(--wb-spacing-4);
    margin-left: var(--wb-spacing-neg-4);
    border: none;
    border-radius: var(--wb-corner-radius-3);
    background: none;
    color: inherit;
    font-family: var(--vscode-editor-font-family, monospace);
    font-size: var(--wb-font-size-11);
    text-align: left;
    cursor: pointer;
  }

  .ref:hover {
    background: var(--vscode-list-hoverBackground, rgba(128, 128, 128, 0.14));
  }

  .where {
    opacity: 0.85;
    white-space: nowrap;
  }

  .fn {
    opacity: 0.55;
    overflow-wrap: anywhere;
  }
</style>