wsb-daemon 0.291.8591

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the host
<script lang="ts">
  // One worktree's logs: where they are, and what they hold against the
  // workshop's limit.
  //
  // What it manages is stated per stream rather than as one number: an
  // operator deciding whether 2 GB is too much needs to know whether it is
  // test sessions or their own build logs. The limit itself is the workshop's,
  // set in Workshop settings.
  import SettingsSection from "./SettingsSection.svelte";
  import { settings } from "../../stores/settings.svelte.js";
  import { workbench } from "../../stores/workbench.svelte.js";
  import { human } from "../../stores/host.svelte.js";

  /** What each stream directory is, for somebody who has not read the code. */
  const STREAMS: Record<string, string> = {
    ops: "operations run from here",
    test: "test sessions",
    daemon: "the daemon's own log",
  };

  let copied = $state(false);

  const usage = $derived(settings.logs.usage);
  /** How full it is, against the limit that is actually in force. */
  const fraction = $derived(
    settings.logs.max_bytes > 0
      ? Math.min(1, usage.total_bytes / settings.logs.max_bytes)
      : 0,
  );

  async function copyRoot() {
    try {
      await navigator.clipboard.writeText(settings.logs.root);
      copied = true;
      setTimeout(() => (copied = false), 1500);
    } catch {
      // The path is on screen and selectable; a clipboard the browser
      // withheld is not worth an error.
    }
  }
</script>

<SettingsSection
  title="Logs"
  description="What ran in this worktree, one file per operation and per test session."
>
  <div class="setting-row column">
    <span class="setting-label">Directory</span>
    <div class="path-row">
      <code class="path">{settings.logs.root}</code>
      <button class="path-copy" onclick={() => void copyRoot()}>
        {copied ? "copied" : "copy"}
      </button>
    </div>
  </div>

  <div class="setting-row column">
    <span class="setting-label">
      Holding {human(usage.total_bytes)} of {human(settings.logs.max_bytes)}
    </span>
    <div class="meter" class:meter-full={fraction > 0.9}>
      <div class="meter-fill" style="width: {Math.round(fraction * 100)}%"></div>
    </div>
    <div class="streams">
      {#each Object.entries(usage.by_group) as [name, bytes] (name)}
        <span class="stream">
          <span class="stream-name">{name}</span>
          <span class="stream-what">{STREAMS[name] ?? ""}</span>
          <span class="stream-bytes">{human(bytes)}</span>
        </span>
      {/each}
      {#if usage.unmanaged_bytes > 0}
        <span class="stream">
          <span class="stream-name">other</span>
          <span class="stream-what">not written here — counted, never removed</span>
          <span class="stream-bytes">{human(usage.unmanaged_bytes)}</span>
        </span>
      {/if}
    </div>
  </div>

  <p class="setting-note">
    {human(settings.logs.max_bytes)} is the workshop's limit for each worktree's
    logs.
    <button class="path-copy" onclick={() => workbench.openSettings("settings-logs")}>change it</button>
  </p>
</SettingsSection>

<style>
  .setting-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--wb-spacing-12);
    padding: var(--wb-spacing-8) 0;
  }


  .setting-row.column {
    flex-direction: column;
    align-items: stretch;
    gap: var(--wb-spacing-6);
  }


  .setting-label {
    font-size: var(--wb-font-size-12);
    color: var(--vscode-foreground);
  }


  .path-row {
    display: flex;
    align-items: center;
    gap: var(--wb-spacing-8);
    min-width: 0;
  }


  .path {
    font-family: var(--wsb-font-mono, monospace);
    font-size: var(--wb-font-size-11);
    color: var(--vscode-descriptionForeground, #999);
    overflow-x: auto;
    white-space: nowrap;
    user-select: text;
    min-width: 0;
  }


  .path-copy {
    background: none;
    border: 1px solid var(--vscode-widget-border);
    border-radius: var(--wsb-radius-sm);
    color: var(--vscode-descriptionForeground, #999);
    cursor: pointer;
    font-size: var(--wb-font-size-10);
    padding: var(--wb-spacing-1) var(--wb-spacing-6);
    flex-shrink: 0;
  }


  .path-copy:hover {
    color: var(--vscode-foreground);
    border-color: var(--vscode-focusBorder);
  }


  .meter {
    height: var(--wb-control-size-6);
    border-radius: var(--wb-corner-radius-3);
    background: var(--vscode-input-background);
    border: 1px solid var(--vscode-widget-border);
    overflow: hidden;
  }


  .meter-fill {
    height: 100%;
    background: var(--vscode-focusBorder);
    transition: width 0.2s;
  }


  .meter-full .meter-fill {
    background: var(--vscode-errorForeground, #f14c4c);
  }


  .streams {
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-2);
    margin-top: var(--wb-spacing-4);
  }


  .stream {
    display: flex;
    align-items: baseline;
    gap: var(--wb-spacing-8);
    font-size: var(--wb-font-size-11);
    color: var(--vscode-descriptionForeground, #999);
  }


  .stream-name {
    font-family: var(--wsb-font-mono, monospace);
    min-width: var(--wb-control-size-56);
  }


  .stream-what {
    flex: 1;
    opacity: 0.8;
  }


  .stream-bytes {
    font-variant-numeric: tabular-nums;
  }







  .setting-note {
    margin: var(--wb-spacing-4) 0 0;
    font-size: var(--wb-font-size-11);
    color: var(--vscode-descriptionForeground, #999);
  }
</style>