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">
  import { placeLabel } from "../../utils/places.js";
  import { analytics } from "../../stores/analytics.svelte.js";
  import { sessions } from "../../stores/sessions.svelte.js";
  import { context } from "../../stores/context.svelte.js";
  import LoadingSpinner from "../layout/LoadingSpinner.svelte";

  function truncate(text: string, max: number): string {
    if (text.length <= max) return text;
    return text.slice(0, max - 1) + "\u2026";
  }

  function formatDuration(mins: number): string {
    const total = Math.round(mins);
    if (total < 60) return `${total}m`;
    const h = Math.floor(total / 60);
    const m = total % 60;
    return m > 0 ? `${h}h ${m}m` : `${h}h`;
  }

  function handleSessionClick(id: string) {
    if (analytics.includeOneShot && !sessions.filters.includeOneShot) {
      sessions.filters.includeOneShot = true;
      sessions.invalidateFilterCaches();
    }
    void context.openSession(id, false);
  }
</script>

<div class="top-sessions-container">
  <div class="top-header">
    <h3 class="chart-title">Top Sessions</h3>
    <div class="metric-toggle">
      <button
        class="toggle-btn"
        class:active={analytics.topMetric === "messages"}
        onclick={() => analytics.setTopMetric("messages")}
      >
        By Messages
      </button>
      <button
        class="toggle-btn"
        class:active={analytics.topMetric === "duration"}
        onclick={() => analytics.setTopMetric("duration")}
      >
        By Duration
      </button>
    </div>
  </div>

  {#if !analytics.loaded.topSessions || analytics.loading.topSessions}
    <LoadingSpinner label="Loading top sessions" />
  {:else if analytics.errors.topSessions}
    <div class="error">
      {analytics.errors.topSessions}
      <button
        class="retry-btn"
        onclick={() => analytics.fetchTopSessions()}
      >
        Retry
      </button>
    </div>
  {:else if analytics.topSessions && analytics.topSessions.sessions.length > 0}
    <div class="session-list">
      {#each analytics.topSessions.sessions as session, i}
        <!-- svelte-ignore a11y_click_events_have_key_events -->
        <!-- svelte-ignore a11y_no_static_element_interactions -->
        <div
          class="session-row"
          onclick={() => handleSessionClick(session.id)}
        >
          <span class="rank">{i + 1}</span>
          <div class="session-info">
            <span class="session-label">
              {session.first_message
                ? truncate(session.first_message, 50)
                : session.id.slice(0, 12)}
            </span>
            <span class="session-project">{placeLabel(session)}</span>
          </div>
          <span class="session-metric">
            {#if analytics.topMetric === "duration"}
              {formatDuration(session.duration_min)}
            {:else}
              {session.message_count}
            {/if}
          </span>
        </div>
      {/each}
    </div>
  {:else}
    <div class="empty">No sessions in range</div>
  {/if}
</div>

<style>
  .top-sessions-container {
    flex: 1;
    display: flex;
    flex-direction: column;
  }

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

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

  .metric-toggle {
    display: flex;
    gap: var(--wb-spacing-2);
    background: var(--vscode-input-background);
    border-radius: var(--wsb-radius-sm);
    padding: var(--wb-spacing-1);
  }

  .toggle-btn {
    padding: var(--wb-spacing-2) var(--wb-spacing-8);
    font-size: var(--wb-font-size-10);
    border-radius: var(--wsb-radius-sm);
    color: var(--vscode-disabledForeground);
    cursor: pointer;
    transition: background 0.1s, color 0.1s;
  }

  .toggle-btn.active {
    background: var(--vscode-sideBar-background);
    color: var(--vscode-editor-foreground);
    font-weight: 500;
  }

  .toggle-btn:hover:not(.active) {
    color: var(--vscode-foreground);
  }

  .session-list {
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-2);
    overflow-y: auto;
    flex: 1;
  }

  .session-row {
    display: flex;
    align-items: center;
    gap: var(--wb-spacing-8);
    padding: var(--wb-spacing-4) var(--wb-spacing-6);
    border-radius: var(--wsb-radius-sm);
    cursor: pointer;
    transition: background 0.1s;
  }

  .session-row:hover {
    background: var(--vscode-list-hoverBackground);
  }

  .rank {
    flex-shrink: 0;
    width: var(--wb-control-size-18);
    text-align: right;
    font-size: var(--wb-font-size-10);
    font-weight: 600;
    color: var(--vscode-disabledForeground);
    font-family: var(--wsb-font-mono);
  }

  .session-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-1);
  }

  .session-label {
    font-size: var(--wb-font-size-11);
    color: var(--vscode-foreground);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .session-project {
    font-size: var(--wb-font-size-9);
    color: var(--vscode-disabledForeground);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .session-metric {
    flex-shrink: 0;
    font-size: var(--wb-font-size-11);
    font-weight: 500;
    font-family: var(--wsb-font-mono);
    color: var(--vscode-focusBorder);
    min-width: var(--wb-control-size-36);
    text-align: right;
  }

  .empty {
    color: var(--vscode-disabledForeground);
    font-size: var(--wb-font-size-12);
    padding: var(--wb-spacing-24);
    text-align: center;
  }

  .error {
    color: var(--vscode-errorForeground);
    font-size: var(--wb-font-size-12);
    padding: var(--wb-spacing-12);
    display: flex;
    align-items: center;
    gap: var(--wb-spacing-8);
  }

  .retry-btn {
    padding: var(--wb-spacing-2) var(--wb-spacing-8);
    border: 1px solid currentColor;
    border-radius: var(--wsb-radius-sm);
    font-size: var(--wb-font-size-11);
    color: inherit;
    cursor: pointer;
  }
</style>