+
+ {/* Candidate dropdown — same compact-row treatment as the
+ candidates lane. Picking one fires `matchAckToClaim` and
+ refetches (the optimistic removal lands on success). */}
+ {showCandidates ? (
+
+ );
+}
+
+export function AckOrphansLane({
+ items,
+ loading,
+ error,
+ refetch,
+}: {
+ items: AckOrphanRow[];
+ loading: boolean;
+ error: Error | null;
+ refetch: () => Promise;
+}) {
+ // Live-tail: open the claim-acks stream. When a manual match
+ // resolves anywhere in the app, the corresponding row lands in
+ // the slice and we drop it from the orphan lane optimistically.
+ useTailStream("claim-acks");
+ const claimAckOrder = useTailStore((s) => s.claimAckOrder);
+ const claimAcks = useTailStore((s) => s.claimAcks);
+
+ // Set of (kind, ack_id) tuples that have just been linked — when
+ // the live-tail store grows, any orphan whose (kind, id) is now
+ // present drops out of the lane until the next refetch confirms.
+ const [optimisticDismissed, setOptimisticDismissed] = useState>(
+ () => new Set(),
+ );
+
+ function orphanKey(o: AckOrphanRow): string {
+ return `${o.kind}:${o.id}`;
+ }
+
+ function dropLocal(id: number) {
+ setOptimisticDismissed((prev) => {
+ const orphan = items.find((o) => o.id === id);
+ if (!orphan) return prev;
+ const next = new Set(prev);
+ next.add(orphanKey(orphan));
+ return next;
+ });
+ // Refetch in the background so the lane converges with the
+ // server. The optimistic drop is purely visual.
+ void refetch();
+ }
+
+ // Live-tail: drop orphans whose (kind, id) just landed in the
+ // claimAcks slice (i.e. a manual match fired elsewhere). We only
+ // act when claimAckOrder grows — the store is the source of truth.
+ const [prevOrderLen, setPrevOrderLen] = useState(claimAckOrder.length);
+ if (claimAckOrder.length > prevOrderLen) {
+ // Find newly-added links and dismiss orphans by (kind, ack_id).
+ const newKeys = claimAckOrder
+ .slice(prevOrderLen)
+ .map((i) => claimAcks[i])
+ .filter((row): row is NonNullable => Boolean(row))
+ .map((row) => `${row.ackKind}:${row.ackId}`);
+ if (newKeys.length > 0) {
+ const matched = items.filter((o) => newKeys.includes(orphanKey(o)));
+ if (matched.length > 0) {
+ setOptimisticDismissed((prev) => {
+ const next = new Set(prev);
+ matched.forEach((o) => next.add(orphanKey(o)));
+ return next;
+ });
+ }
+ }
+ setPrevOrderLen(claimAckOrder.length);
+ }
+
+ const visible = items.filter((o) => !optimisticDismissed.has(orphanKey(o)));
+
+ return (
+
+
+