feat(sp28): claimAcks slice + useTailStream/useMergedTail dispatch
This commit is contained in:
+46
-1
@@ -21,7 +21,14 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { create } from "zustand";
|
||||
import type { Ack, Activity, Claim, Remittance, Ta1Ack } from "@/types";
|
||||
import type {
|
||||
Ack,
|
||||
Activity,
|
||||
Claim,
|
||||
ClaimAck,
|
||||
Remittance,
|
||||
Ta1Ack,
|
||||
} from "@/types";
|
||||
import type { TailResource } from "@/lib/tail-stream";
|
||||
|
||||
/** Maximum number of items retained per slice before FIFO eviction kicks in. */
|
||||
@@ -52,6 +59,13 @@ interface TailStore {
|
||||
// `claims`/`remittances` (first write wins, FIFO-capped at TAIL_CAP).
|
||||
acks: Record<string, Ack>;
|
||||
ta1Acks: Record<string, Ta1Ack>;
|
||||
// SP28: the claim↔ack link slice. Mirrors the SP25 acks/ta1_acks
|
||||
// pattern — `claimAcks` rows have a stable numeric `id` from the
|
||||
// `claim_acks` table, so we key by id (first write wins, FIFO-capped
|
||||
// at TAIL_CAP). The ClaimDrawer's <Acknowledgments /> panel and the
|
||||
// AckDrawer's <MatchedClaim /> panel both subscribe to this slice via
|
||||
// `useTailStream("claim-acks")` + `useMergedTail("claim-acks", …)`.
|
||||
claimAcks: Record<string, ClaimAck>;
|
||||
|
||||
// --- Insertion-order trackers (kept in sync with the dicts above) ----
|
||||
// These are private to the store; consumers only read the dicts. We
|
||||
@@ -62,6 +76,7 @@ interface TailStore {
|
||||
remitOrder: string[];
|
||||
ackOrder: number[];
|
||||
ta1AckOrder: number[];
|
||||
claimAckOrder: number[];
|
||||
|
||||
// --- Setters ---------------------------------------------------------
|
||||
addClaim: (c: Claim) => void;
|
||||
@@ -69,6 +84,7 @@ interface TailStore {
|
||||
addActivity: (a: Activity) => void;
|
||||
addAck: (a: Ack) => void;
|
||||
addTa1Ack: (a: Ta1Ack) => void;
|
||||
addClaimAck: (a: ClaimAck) => void;
|
||||
reset: (resource: TailResource) => void;
|
||||
}
|
||||
|
||||
@@ -104,10 +120,12 @@ export const useTailStore = create<TailStore>((set) => ({
|
||||
activity: [],
|
||||
acks: {},
|
||||
ta1Acks: {},
|
||||
claimAcks: {},
|
||||
claimOrder: [],
|
||||
remitOrder: [],
|
||||
ackOrder: [],
|
||||
ta1AckOrder: [],
|
||||
claimAckOrder: [],
|
||||
|
||||
addClaim: (c) =>
|
||||
set((s) => {
|
||||
@@ -193,6 +211,31 @@ export const useTailStore = create<TailStore>((set) => ({
|
||||
return { ta1Acks: nextTa1Acks, ta1AckOrder: nextOrder };
|
||||
}),
|
||||
|
||||
// SP28: same first-write-wins / FIFO-capped shape as addAck /
|
||||
// addTa1Ack. The numeric `id` is the primary key from the
|
||||
// `claim_acks` table — distinct from (claim_id, ack_id, ak2_index)
|
||||
// which is the natural dedup key but isn't what the live-tail
|
||||
// payload uses (every claim_ack_written event carries the row id).
|
||||
addClaimAck: (a) =>
|
||||
set((s) => {
|
||||
if (s.claimAcks[String(a.id)]) return s;
|
||||
const nextClaimAcks: Record<string, ClaimAck> = Object.assign(
|
||||
{},
|
||||
s.claimAcks,
|
||||
{ [String(a.id)]: a },
|
||||
);
|
||||
const nextOrder = s.claimAckOrder.concat(a.id);
|
||||
if (nextOrder.length > TAIL_CAP) {
|
||||
const { order, dict } = evictOldest(
|
||||
nextOrder,
|
||||
nextClaimAcks,
|
||||
EVICT_BATCH,
|
||||
);
|
||||
return { claimAcks: dict, claimAckOrder: order };
|
||||
}
|
||||
return { claimAcks: nextClaimAcks, claimAckOrder: nextOrder };
|
||||
}),
|
||||
|
||||
reset: (resource) =>
|
||||
set(() => {
|
||||
switch (resource) {
|
||||
@@ -206,6 +249,8 @@ export const useTailStore = create<TailStore>((set) => ({
|
||||
return { acks: {}, ackOrder: [] };
|
||||
case "ta1_acks":
|
||||
return { ta1Acks: {}, ta1AckOrder: [] };
|
||||
case "claim-acks":
|
||||
return { claimAcks: {}, claimAckOrder: [] };
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user