fix(market): refresh chronological bid board

Signed-off-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz>
This commit is contained in:
Wintermute
2026-09-03 14:51:30 -04:00
parent 13276b0252
commit ed20f88298
4 changed files with 44 additions and 11 deletions

View File

@@ -3,6 +3,7 @@ import test from "node:test";
import {
isMarketProtocolMessage,
marketBidsAfterAnchor,
marketTimelineAnchor,
selectMarketTimelineMessages,
} from "./marketTimeline.ts";
@@ -74,6 +75,18 @@ test("the market board anchors to channel creation, not above history", () => {
);
assert.equal(isMarketProtocolMessage(contract), true);
assert.equal(isMarketProtocolMessage(created), false);
assert.deepEqual(
marketBidsAfterAnchor(
[
{ createdAt: 9, eventId: "9".repeat(64) },
{ createdAt: 11, eventId: "1".repeat(64) },
{ createdAt: 11, eventId: "3".repeat(64) },
{ createdAt: 12, eventId: "0".repeat(64) },
],
created,
).map(({ eventId }) => eventId),
["3".repeat(64), "0".repeat(64)],
);
});
test("market timeline hides protocol events but leaves negotiation in its bid thread", () => {

View File

@@ -1,6 +1,9 @@
import * as React from "react";
import { useMarketChannel } from "@/features/market/lib/MarketChannelContext";
import type { MarketProjection } from "@/features/market/lib/marketProtocol";
import type {
MarketBid,
MarketProjection,
} from "@/features/market/lib/marketProtocol";
import { parseMarketEnvelope } from "@/features/market/lib/marketProtocol";
import type { TimelineMessage } from "@/features/messages/types";
@@ -21,6 +24,18 @@ export function marketTimelineAnchor(
);
}
export function marketBidsAfterAnchor(
bids: MarketBid[],
anchor: Pick<TimelineMessage, "createdAt" | "id">,
): MarketBid[] {
return bids.filter(
(bid) =>
bid.createdAt > anchor.createdAt ||
(bid.createdAt === anchor.createdAt &&
bid.eventId.localeCompare(anchor.id) > 0),
);
}
/** Keep protocol state out of the market's main chat; negotiation stays in threads. */
export function selectMarketTimelineMessages(
messages: TimelineMessage[],

View File

@@ -20,10 +20,15 @@ export function useMarketChannelTimeline(messages: TimelineMessage[]) {
() =>
anchor
? {
[anchor.id]: <MarketChannelIntro anchorMessage={anchor} />,
[anchor.id]: (
<MarketChannelIntro
anchorMessage={anchor}
bids={projection?.bids ?? []}
/>
),
}
: undefined,
[anchor],
[anchor, projection?.bids],
);
return {

View File

@@ -5,7 +5,10 @@ import { MessageCircle } from "lucide-react";
import { useMarketChannel } from "@/features/market/lib/MarketChannelContext";
import type { MarketBid } from "@/features/market/lib/marketProtocol";
import { isMarketProtocolMessage } from "@/features/market/lib/marketTimeline";
import {
isMarketProtocolMessage,
marketBidsAfterAnchor,
} from "@/features/market/lib/marketTimeline";
import { MarketContractCard } from "@/features/market/ui/MarketContractCard";
import type { TimelineMessage } from "@/features/messages/types";
import { Button } from "@/shared/ui/button";
@@ -79,21 +82,18 @@ function BidList({
export function MarketChannelIntro({
anchorMessage,
bids,
}: {
anchorMessage: Pick<TimelineMessage, "body" | "createdAt" | "id">;
bids: MarketBid[];
}): React.ReactNode {
const projection = useMarketChannel();
if (!projection || isMarketProtocolMessage(anchorMessage)) return undefined;
const bids = projection.bids.filter(
(bid) =>
bid.createdAt > anchorMessage.createdAt ||
(bid.createdAt === anchorMessage.createdAt &&
bid.eventId.localeCompare(anchorMessage.id) > 0),
);
const visibleBids = marketBidsAfterAnchor(bids, anchorMessage);
return (
<div className="mt-2">
<MarketContractCard scenario={projection.scenario} />
<BidList bids={bids} channelId={projection.channelId} />
<BidList bids={visibleBids} channelId={projection.channelId} />
</div>
);
}