jacquardSnapshot

← snapshot

5275 bytes
"use client";

import Link from "next/link";
import { useState } from "react";
import type { Awaiting, AwaitingItem } from "@/lib/types";
import { ActorBadge } from "./provenance-badge";
import { Time } from "./time";
import { JackieViewing } from "./jackie/jackie-viewing";

/**
 * The front door: what is waiting on a person.
 *
 * This used to open on Jackie, mid-sentence, before anyone had asked her
 * anything. That is backwards. The first question is not "shall we look
 * around" — it is "does anything need me", and most of the time the honest
 * answer is no. So the floor leads with the queue, and Jackie materialises
 * when you actually start an interview.
 *
 * Ordering is not by recency. An unsettled decision outranks everything
 * because nothing else can clear it — a blocked gate is downstream of one, and
 * a remark blocks nothing at all. Within a rank the oldest is first: the thing
 * that has waited longest is the thing most likely to have been forgotten.
 */
export function AwaitingInbox({ awaiting }: { awaiting: Awaiting }) {
  const [interview, setInterview] = useState<string | null>(null);
  const { items, counts, repos } = awaiting;

  if (interview) {
    return (
      // Jackie takes the room while she has it: the rail is still there, but
      // nothing else competes with her.
      <div className="jac-stage-wrap jac-interviewing">
        <div className="jac-interview-top">
          <button type="button" className="jac-btn" onClick={() => setInterview(null)}>
            ← back to what&apos;s waiting
          </button>
          <span className="jac-small">
            Jackie is here because you asked. She stops when you leave.
          </span>
        </div>
        <JackieViewing key={interview} repo={interview} />
      </div>
    );
  }

  return (
    <div className="jac-page">
      <p className="jac-eyebrow">The floor</p>
      <h1 className="jac-h1">
        {counts.total === 0 ? "Nothing waits." : headline(counts)}
      </h1>
      <p className="jac-lede">
        {counts.total === 0
          ? "Every governing card is signed and no remark is open. The mill is not idle — it is just not waiting on you."
          : "Ranked by what can actually clear it. A decision needs a person; nothing else will do."}
      </p>

      {items.length > 0 ? (
        <ol className="jac-await-list">
          {items.map((item) => (
            <li key={`${item.kind}-${item.repo}-${item.id}`} data-kind={item.kind}>
              <Link href={item.href} className="jac-await-row">
                <span className="jac-await-mark" aria-hidden="true" />
                <span className="jac-await-main">
                  <span className="jac-await-title">{item.title}</span>
                  <span className="jac-await-asks">{item.asks}</span>
                  {item.quote ? (
                    <span className="jac-await-quote">{item.quote}</span>
                  ) : null}
                  <span className="jac-await-meta">
                    <code className="jac-mono">{item.repo}</code>
                    <span className="jac-await-kind">
                      {item.kind === "decision" ? "unsettled decision" : item.remark_kind}
                    </span>
                    {item.proposed_by ? <ActorBadge actor={item.proposed_by} /> : null}
                    <Time at={item.at} />
                  </span>
                </span>
              </Link>
            </li>
          ))}
        </ol>
      ) : null}

      <section className="jac-await-repos">
        <p className="jac-panel-label">Walk a repo</p>
        <p className="jac-small">
          Jackie will show you around and record why, if you want that. She does
          not start on her own.
        </p>
        <div className="jac-await-repo-row">
          {repos.map((r) => (
            <div key={r.slug} className="jac-await-repo">
              <span className="jac-await-repo-name">{r.slug}</span>
              <span className="jac-small">{r.org}</span>
              <span className="jac-await-repo-nums">
                {r.unsettled > 0 ? (
                  <b>{r.unsettled} waiting</b>
                ) : (
                  <span>{r.settled} settled</span>
                )}
              </span>
              <div className="jac-await-repo-acts">
                <button
                  type="button"
                  className="jac-btn jac-btn--primary"
                  onClick={() => setInterview(r.slug)}
                >
                  Walk it with Jackie
                </button>
                <Link href={`/repos/${r.slug}`} className="jac-btn">
                  Just look
                </Link>
              </div>
            </div>
          ))}
        </div>
      </section>

      <p className="jac-import-honesty">{awaiting.honesty}</p>
    </div>
  );
}

function headline(counts: Awaiting["counts"]): string {
  const parts: string[] = [];
  if (counts.decisions > 0) {
    parts.push(
      `${counts.decisions} decision${counts.decisions === 1 ? "" : "s"} waiting on a human`,
    );
  }
  if (counts.remarks > 0) {
    parts.push(`${counts.remarks} remark${counts.remarks === 1 ? "" : "s"} open`);
  }
  return parts.join(" · ");
}

export type { AwaitingItem };