"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 (
<div className="jac-stage-wrap">
<div className="jac-interview-top">
<button type="button" className="jac-btn" onClick={() => setInterview(null)}>
← back to what'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 };