wsb-daemon 0.275.7622

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
<!--
  The boards — every GitHub Projects board of the accounts — the most
  recently changed first. The machine's own panels are the workshop's Machine
  view.

  A board belongs to no project; the projects that work from one are named on
  it, and lead to them. The boards are the machine's mirror, kept current by
  its GitHub stream, so nothing here needs reloading.
-->
<script lang="ts">
  import { onMount } from "svelte";
  import { boards } from "../../stores/boards.svelte.js";
  import { router } from "../../stores/router.svelte.js";
  import { workbench } from "../../stores/workbench.svelte.js";
  import { opensInWorkbench } from "../../utils/open-link.js";
  import { describeStanding } from "../../api/types/github.js";
  import { formatRelativeTime } from "../../utils/format.js";

  onMount(() => {
    void boards.load();
    return boards.watch();
  });

</script>

<div class="home">
  <header class="home-header">
    <h1>Boards</h1>
    <button class="secondary" disabled={boards.refreshing} onclick={() => void boards.refresh()}>
      {boards.refreshing ? "Asking GitHub…" : "Refresh"}
    </button>
  </header>

  {#if boards.noToken}
    <p class="home-error" role="alert">
      There is no GitHub login on this machine: the boards shown are the last ones read. Log in with
      <code>gh auth login</code>.
    </p>
  {/if}
  {#each boards.problems as problem (problem)}
    <p class="home-error" role="alert">{problem}</p>
  {/each}
  {#if boards.error}
    <p class="home-error" role="alert">{boards.error}</p>
  {/if}

  {#if boards.loading && boards.projects.length === 0}
    <p class="empty">Reading the boards…</p>
  {:else if boards.projects.length === 0}
    <p class="empty">
      No boards. They are read from GitHub with the <code>read:project</code> scope on the GitHub
      login (<code>gh auth login</code>, or <code>GITHUB_TOKEN</code>) — a token without it sees none.
    </p>
  {/if}

  <div class="cards">
    {#each boards.byActivity as { board, activity } (board.id)}
      <a
        class="card board-card"
        class:closed={board.closed}
        href={router.href({ level: "board", owner: board.owner, number: board.number })}
        onclick={opensInWorkbench((beside) => workbench.openBoard(board.owner, board.number, { beside }))}
      >
        <span class="card-title">{board.title}</span>
        <span class="subtle">{board.owner} · #{board.number}{board.closed ? " · closed" : ""}</span>
        <span class="activity" title={describeStanding(activity.cards)}>
          {activity.newest_change
            ? `changed ${formatRelativeTime(activity.newest_change)}`
            : activity.cards.ever_synced
              ? "no cards"
              : "cards not read yet"}
        </span>
        {#if boards.projectsUsing(board).length > 0}
          <span class="used-by">
            {#each boards.projectsUsing(board) as project (project)}
              <span class="chip">{project}</span>
            {/each}
          </span>
        {/if}
      </a>
    {/each}
  </div>
</div>

<style>
  .home {
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--wb-spacing-24) var(--wb-spacing-20);
  }

  .home-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--wb-spacing-12);
  }

  h1 {
    font-size: var(--wb-font-size-18);
    font-weight: 600;
    margin: 0;
    color: var(--vscode-editor-foreground);
  }

  code {
    font-family: var(--vscode-editor-font-family, monospace);
  }

  .home-error {
    color: var(--wb-error);
    font-size: var(--wb-font-size-12);
  }

  .cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: var(--wb-spacing-12);
  }

  .board-card {
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-4);
    padding: var(--wb-spacing-12);
    border: 1px solid var(--vscode-panel-border);
    border-radius: var(--wsb-radius-lg);
    background: var(--vscode-sideBar-background);
    color: inherit;
    text-decoration: none;
  }

  .board-card:hover {
    border-color: var(--vscode-focusBorder);
  }

  .board-card.closed {
    opacity: 0.7;
  }

  .card-title {
    font-size: var(--wb-font-size-14);
    font-weight: 600;
    color: var(--vscode-editor-foreground);
  }

  .activity {
    font-size: var(--wb-font-size-12);
  }

  .used-by {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wb-spacing-4);
  }
</style>