All files / src/components/layout CodeHeaderStatus.tsx

41.66% Statements 5/12
61.53% Branches 8/13
50% Functions 1/2
41.66% Lines 5/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86                                                                                              6x                           11x   11x 5x     6x                                    
/**
 * CodeHeaderStatus — presentational status line for the Code context header.
 * Contains both the pure computation ({@link describeCodeStatus}) and the
 * React component ({@link CodeHeaderStatus}).
 *
 * @module
 */
 
import type { CSSProperties, JSX } from "react";
import type { Session } from "../../hooks/types.js";
import { isActiveSession } from "../sessions/sessionsView.js";
import { formatRelativeTime } from "../../utils/time.js";
 
// ---------------------------------------------------------------------------
// Pure logic
// ---------------------------------------------------------------------------
 
/** Summary of session activity for the Code context header. */
export interface CodeStatusSummary {
  /** Number of currently active (live) sessions. */
  activeCount: number;
  /** ISO timestamp of the most recently started session, or `undefined` if none. */
  lastActivityAt: string | undefined;
}
 
/** Derive a Code context status summary from the sessions array. */
export function describeCodeStatus(
  sessions: readonly Pick<Session, "status" | "endReason" | "startedAt">[],
): CodeStatusSummary {
  let activeCount: number = 0;
  let lastActivityAt: string | undefined;
  for (const s of sessions) {
    Iif (isActiveSession(s)) {
      activeCount++;
    }
    Iif (!lastActivityAt || s.startedAt > lastActivityAt) {
      lastActivityAt = s.startedAt;
    }
  }
  return { activeCount, lastActivityAt };
}
 
// ---------------------------------------------------------------------------
// Presentational component
// ---------------------------------------------------------------------------
 
/** Inline styles matching the agent header's HeartbeatStatus pattern. */
const STATUS_ITEM_STYLE: CSSProperties = {
  fontSize: "var(--font-size-xs)",
  color: "var(--text-tertiary)",
  whiteSpace: "nowrap",
};
 
/** Props for {@link CodeHeaderStatus}. */
export interface CodeHeaderStatusProps {
  /** Summary computed by {@link describeCodeStatus}. */
  summary: CodeStatusSummary;
}
 
/** Renders the Code context header status line. Returns nothing when there is no data. */
export function CodeHeaderStatus({ summary }: CodeHeaderStatusProps): JSX.Element | undefined {
  const { activeCount, lastActivityAt } = summary;
 
  if (activeCount === 0 && !lastActivityAt) {
    return undefined;
  }
 
  return (
    <div
      style={{ display: "flex", alignItems: "center", gap: "var(--space-md)", flexWrap: "wrap" }}
      data-testid="code-header-status"
    >
      {activeCount > 0 && (
        <span style={STATUS_ITEM_STYLE} data-testid="code-header-active-sessions">
          {activeCount} active {activeCount === 1 ? "session" : "sessions"}
        </span>
      )}
      {lastActivityAt && (
        <span style={STATUS_ITEM_STYLE} data-testid="code-header-last-activity">
          Last activity {formatRelativeTime(lastActivityAt)}
        </span>
      )}
    </div>
  );
}