fix(market): simplify offer hero

Signed-off-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz>
This commit is contained in:
Wintermute
2026-09-07 12:11:34 -04:00
parent 251465938f
commit 170f11b81d
4 changed files with 50 additions and 11 deletions

View File

@@ -104,18 +104,38 @@ test("market timeline hides protocol events but leaves negotiation in its bid th
bid.id,
bid.id,
);
const ordinary = message("5".repeat(64), "General market note");
const imageUrl = "https://relay.example/media/product.png";
const productImage = message(
"5".repeat(64),
`Product image for this offer\n![image](${imageUrl})`,
);
const imageReply = message(
"6".repeat(64),
`Product image for this offer\n![image](${imageUrl})`,
bid.id,
bid.id,
);
const ordinary = message("7".repeat(64), "General market note");
const projection = {
listingEventId: contract.id,
contract: { listing: { imageUrl } },
bids: [{ eventId: bid.id }],
};
assert.deepEqual(
selectMarketTimelineMessages(
[contract, bid, negotiation, nestedProtocol, ordinary],
[
contract,
bid,
negotiation,
nestedProtocol,
productImage,
imageReply,
ordinary,
],
projection,
).map(({ id }) => id),
[negotiation.id, ordinary.id],
[negotiation.id, imageReply.id, ordinary.id],
);
assert.equal(selectMarketTimelineMessages([contract, bid], null).length, 2);
});

View File

@@ -13,6 +13,20 @@ export function isMarketProtocolMessage(
return parseMarketEnvelope(message.body) !== null;
}
const MARKET_PRODUCT_IMAGE_LABEL = "Product image for this offer";
function isRedundantMarketImageMessage(
message: Pick<TimelineMessage, "body" | "parentId">,
projection: MarketProjection,
): boolean {
const imageUrl = projection.contract.listing.imageUrl;
if (!imageUrl || message.parentId != null) return false;
return (
message.body.trim() ===
`${MARKET_PRODUCT_IMAGE_LABEL}\n![image](${imageUrl})`
);
}
export function marketTimelineAnchor(
messages: TimelineMessage[],
isChannelCreated: (message: TimelineMessage) => boolean,
@@ -42,7 +56,11 @@ export function selectMarketTimelineMessages(
projection: MarketProjection | null,
): TimelineMessage[] {
if (!projection) return messages;
return messages.filter((message) => !isMarketProtocolMessage(message));
return messages.filter(
(message) =>
!isMarketProtocolMessage(message) &&
!isRedundantMarketImageMessage(message, projection),
);
}
export function useMarketTimelineMessages(messages: TimelineMessage[]) {

View File

@@ -70,6 +70,11 @@ test("the contract card leaves horizontal alignment to the board", () => {
}),
);
const sectionClass = html.match(/<section class="([^"]+)"/)?.[1] ?? "";
const imageFrameClass =
html.match(/<div class="([^"]*rounded-xl[^"]*)"/)?.[1] ?? "";
assert.doesNotMatch(sectionClass, /(?:^|\s)m[lrxy]-/);
assert.match(imageFrameClass, /(?:^|\s)m-4(?:\s|$)/);
assert.match(imageFrameClass, /(?:^|\s)overflow-hidden(?:\s|$)/);
assert.doesNotMatch(html, /OFFER · FIXED · FINITE/i);
});

View File

@@ -2,7 +2,6 @@ import { Bot, PackageCheck, Radio, Store } from "lucide-react";
import type { MarketScenario } from "@/features/market/lib/marketPrototypeData";
import { rewriteRelayUrl } from "@/shared/lib/mediaUrl";
import { Badge } from "@/shared/ui/badge";
const COMMERCIAL_TERM_LABELS = new Set(["Price", "Reward", "Initial quantity"]);
@@ -21,7 +20,7 @@ export function MarketContractCard({ scenario }: { scenario: MarketScenario }) {
data-testid="market-contract-card"
>
<div className="grid sm:grid-cols-[11rem_minmax(0,1fr)]">
<div className="flex aspect-square items-center justify-center overflow-hidden border-b bg-muted/60 text-muted-foreground sm:border-b-0 sm:border-r">
<div className="m-4 flex aspect-square items-center justify-center overflow-hidden rounded-xl bg-muted/60 text-muted-foreground sm:mr-0">
{scenario.imageUrl ? (
<img
alt={scenario.title}
@@ -33,11 +32,8 @@ export function MarketContractCard({ scenario }: { scenario: MarketScenario }) {
<PackageCheck className="h-8 w-8" strokeWidth={1.5} />
)}
</div>
<div className="min-w-0 p-5">
<div className="flex flex-wrap items-center gap-2">
<Badge variant="secondary">{scenario.eyebrow}</Badge>
</div>
<h2 className="mt-3 text-xl font-semibold tracking-tight">
<div className="min-w-0 p-4">
<h2 className="text-xl font-semibold tracking-tight">
{scenario.title}
</h2>
<p className="mt-1 text-sm leading-relaxed text-muted-foreground">