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">
  // A session's transcript, in the group the reader is in.
  //
  // The transcript view reads the ONE session being read (`sessions`'
  // `activeSessionId`): its messages, its live updates, the message scrolled
  // to. So it is mounted once, in the focused group, and this is what keeps it
  // fed — the messages of the session in front, the scroll a link or a search
  // hit asked for.
  import { untrack } from "svelte";
  import SessionEditorView from "../content/SessionEditorView.svelte";
  import { messages } from "../../stores/messages.svelte.js";
  import { sessions } from "../../stores/sessions.svelte.js";
  import { sync } from "../../stores/sync.svelte.js";
  import { ui } from "../../stores/ui.svelte.js";
  import type { DisplayItem } from "../../utils/display-items.js";
  import { enrichSegments, parseContent } from "../../utils/content-parser.js";
  import { shouldAutoSwitchTranscriptModeToNormal } from "../../utils/transcript-mode.js";

  interface Props {
    sessionId: string;
  }

  let { sessionId }: Props = $props();

  let messageList:
    | {
        scrollToOrdinal: (o: number) => void;
        getDisplayItems: () => DisplayItem[];
        getNormalDisplayItems: () => DisplayItem[];
      }
    | undefined = $state(undefined);

  // The session in front is the one being read.
  $effect(() => {
    const id = sessionId;
    untrack(() => {
      if (sessions.activeSessionId !== id) void sessions.navigateToSession(id);
    });
  });

  // Its messages, and its live updates. Only the session id is followed: the
  // loading flags this sets are not reasons to load again.
  $effect(() => {
    const id = sessions.activeSessionId;
    untrack(() => {
      // A scroll queued for this session (a search hit selects the session and
      // the message together) is kept; any other is dropped.
      const pendingMatchesSession =
        ui.pendingScrollOrdinal !== null && (ui.pendingScrollSession === null || ui.pendingScrollSession === id);
      if (!pendingMatchesSession) {
        ui.clearSelection();
        ui.pendingScrollOrdinal = null;
        ui.pendingScrollSession = null;
      }
      if (id) {
        messages.loadSession(id);
        sessions.loadChildSessions(id);
        sync.watchSession(id, () => {
          messages.reload();
          sessions.refreshActiveSession();
          sessions.loadChildSessions(id);
        });
      }
    });
    return () => {
      sync.unwatchSession();
    };
  });

  // Scroll to the message asked for once the messages are loaded. A message
  // hidden only because thinking is hidden turns thinking on; one hidden by
  // another filter is left alone — changing unrelated filters is unexpected.
  $effect(() => {
    const ordinal = ui.pendingScrollOrdinal;
    const loading = messages.loading;
    const thinkingVisible = ui.isBlockVisible("thinking");
    untrack(() => {
      if (ordinal === null || loading || !messageList) return;
      // `-1` is "the last message": resolved once there are messages.
      if (ordinal === -1) {
        const last = messages.messages[messages.messages.length - 1];
        const target = ui.pendingScrollSession;
        if (last && (target === null || target === messages.sessionId)) ui.scrollToOrdinal(last.ordinal, target ?? undefined);
        return;
      }

      const items = messageList.getDisplayItems();
      const found = items.some((item) => item.ordinals.includes(ordinal));
      if (!found) {
        if (shouldAutoSwitchTranscriptModeToNormal(ui.transcriptMode, ordinal, items, messageList.getNormalDisplayItems())) {
          ui.setTranscriptMode("normal");
          return;
        }
        const message = messages.messages.find((m) => m.ordinal === ordinal);
        if (message && !thinkingVisible) {
          const segments = enrichSegments(parseContent(message.content, message.has_tool_use), message.tool_calls);
          if (segments.some((segment) => segment.type === "thinking")) {
            ui.setBlockVisible("thinking", true);
            return;
          }
        }
      }

      messageList.scrollToOrdinal(ordinal);
      ui.selectedOrdinal = ordinal;
      ui.pendingScrollOrdinal = null;
      ui.pendingScrollSession = null;
    });
  });
</script>

<SessionEditorView bind:bindMessageList={messageList} />