@loafmarkets/ui 0.1.428 → 0.1.429

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -324,7 +324,43 @@ type PropertyNewsItem = {
324
324
  * can concern several properties, user call 2026-08-20). Takes precedence
325
325
  * over the singular `asset`. */
326
326
  assets?: NewsBadgeAsset[];
327
- };
327
+ /** Is this old news? Derived server-side on every read (loaf-news-service
328
+ * src/freshness.ts:41). Only `catching_up` and `retrospective` are badged —
329
+ * `fresh` is the default class and badging it is noise. */
330
+ freshness?: NewsFreshness;
331
+ /** When the EVENT happened, not when it was published (schemas/article.ts:91).
332
+ * Absent is common and correct — never stand `publishedAt` in for it. */
333
+ eventTime?: string;
334
+ /** `month`/`quarter`/`year` are PERIODS, not timestamps (freshness.ts:21). */
335
+ eventTimePrecision?: NewsEventTimePrecision;
336
+ /** How many publishers ran the story; ≥1 (article.ts:190). >1 earns the
337
+ * "Reported by N outlets" trust line. */
338
+ reportedByCount?: number;
339
+ /** Storyline id — equals `id` for a standalone story. */
340
+ storyId?: string;
341
+ storyRole?: 'canonical' | 'follow_up';
342
+ publishedAtSource?: 'publisher' | 'ingest_fallback' | 'clamped_future';
343
+ };
344
+ type NewsFreshness = 'breaking' | 'fresh' | 'catching_up' | 'retrospective' | 'evergreen';
345
+ /** One report in a storyline — loaf-news-service GET /v1/stories/:story_id. */
346
+ type NewsStoryEntry = {
347
+ id: string;
348
+ title: string;
349
+ publishedAt: string;
350
+ source?: string;
351
+ url?: string;
352
+ relation?: 'duplicate' | 'follow_up';
353
+ };
354
+ type NewsStoryThread = {
355
+ storyId: string;
356
+ canonical: NewsStoryEntry;
357
+ /** Oldest → newest. */
358
+ timeline: NewsStoryEntry[];
359
+ reportedBy: string[];
360
+ firstReportedAt?: string;
361
+ lastUpdatedAt?: string;
362
+ };
363
+ type NewsEventTimePrecision = 'instant' | 'day' | 'month' | 'quarter' | 'year' | 'unknown';
328
364
  /** The badge's tooltip is name and ticker only (user call 2026-08-24: no price or % change). */
329
365
  type NewsBadgeAsset = {
330
366
  ticker: string;
@@ -373,6 +409,10 @@ interface PropertyNewsUpdatesProps extends React$1.HTMLAttributes<HTMLDivElement
373
409
  price?: number;
374
410
  changePercent?: number;
375
411
  }) => void;
412
+ /** Loads one storyline for the article popup (DI, like SharePositionModal's
413
+ * `fetchCard`): loaf-ui owns no transport, and the consumer already knows its
414
+ * news base URL. Omitted → the popup shows the article alone. */
415
+ fetchStory?: (storyId: string) => Promise<NewsStoryThread | null>;
376
416
  /** Show the square asset thumbnail(s) on property rows: which platform
377
417
  * asset(s) the story is about. ON by default (user call 2026-08-24, which
378
418
  * reversed the 22 Aug "hide for now"); pass `false` to hide them. */
@@ -387,6 +427,16 @@ declare function rowAgeLabel(publishedAt: string | undefined, now: number): {
387
427
  label: string;
388
428
  fresh: boolean;
389
429
  } | null;
430
+ /**
431
+ * The two freshness classes worth saying out loud. `fresh` is the default class, so
432
+ * badging it would mark every row; `breaking` already has the gold pin and `evergreen`
433
+ * (an explainer with no datable event) has nothing to date. Wording is what the class
434
+ * MEANS to a reader, not the wire value — loaf-news-service src/freshness.ts:41-51.
435
+ */
436
+ declare function freshnessNote(freshness?: string): {
437
+ label: string;
438
+ title: string;
439
+ } | null;
390
440
  declare function SentimentBar({ score, color }: {
391
441
  score: number;
392
442
  color: string;
@@ -1552,4 +1602,4 @@ declare function extractSparkline(candles: ReadonlyArray<{
1552
1602
  }>, maxPoints?: number): number[];
1553
1603
  declare const SparklineChart: React__default.NamedExoticComponent<SparklineChartProps>;
1554
1604
 
1555
- export { type ActivityTabId, AssetSelectorBar, type AssetSelectorBarProps, type AssetSelectorMetric, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, ContactPopup, type ContactPopupProps, type FinancialItem, type FundWalletParams, type FundWalletResult, Header, type HeaderNavItem, type HeaderPortfolioData, type HeaderProps, HlsVideo, type HlsVideoProps, type HlsVideoSource, type HousePositionOrderbook, type HousePositionPendingOrder, HousePositionSlider, HousePositionSliderMobile, type HousePositionSliderMobileOrderPayload, type HousePositionSliderMobileOrderbook, type HousePositionSliderMobileProps, type HousePositionSliderOrderPayload, type HousePositionSliderProps, type InspectionSlot, LoafLiquidityBadge, type LoafLiquidityDotState, LoafLiquidityLogo, type LoafLiquidityLogoProps, LoginPopup, type LoginPopupProps, MobileTradeNav, type MobileTradeNavItem, type MobileTradeNavProps, type NewsBadgeAsset, type OfferStatus, OfferingProgressCard, type OfferingProgressCardProps, OnboardingGuide, type OnboardingGuideProps, Orderbook, type OrderbookLevel, type OrderbookProps, type OrderbookSide, type OrderbookTrade, type OverviewImage, type OverviewResponse, OwnerBooking, type OwnerBookingProps, PaymentPopup, type PaymentPopupProps, PortfolioActivityPanel, type PortfolioActivityPanelProps, type PortfolioPosition, PortfolioSummary, type PortfolioSummaryProps, PriceChart, type PriceChartCandle, type PriceChartProps, type PriceChartRange, type PropertyAddressOption, PropertyBuy, PropertyCompareBar, type PropertyCompareBarProps, PropertyDocuments, type PropertyGalleryHotspot, type PropertyGalleryImage, PropertyHeroHeader, type PropertyHeroHeaderProps, PropertyHistory, type PropertyInfoItem, PropertyInspectionTimes, PropertyMediaRow, type PropertyMediaRowProps, type PropertyNewsItem, type PropertyNewsType, PropertyNewsUpdates, type PropertyNewsUpdatesProps, type PropertyOffer, PropertyOffers, type PropertyOffersProps, PropertyOverview, type PropertyOverviewProps, PropertyPhotoGallery, type PropertyPriceSummary, PropertySubheader, type PropertySubheaderAction, type PropertySubheaderProps, type PropertySubheaderTab, PropertyTour, type PropertyTourProps, PropertyValuation, type PropertyValuationProps, type PropertyValueSummary, type PurchaseFeedItem, type SelectorItem, SentimentBar, SiteFooter, Skeleton, type SkeletonProps, SlideDigit, SparklineChart, type SparklineChartProps, type ToastData, ToastProvider, type ToastVariant, TradeConfirmationModal, type TradeConfirmationModalDetails, type TradeConfirmationModalProps, TradingSlider, type TradingSliderProps, type YourOrder, type YourOrderSide, YourOrders, type YourOrdersProps, __reloadBreakingMemory, __resetDismissedBreaking, badgeVariants, buttonVariants, extractSparkline, formatAge, hasPendingActivity, matchesTab, rowAgeLabel, useAdaptivePolling, useToast };
1605
+ export { type ActivityTabId, AssetSelectorBar, type AssetSelectorBarProps, type AssetSelectorMetric, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, ContactPopup, type ContactPopupProps, type FinancialItem, type FundWalletParams, type FundWalletResult, Header, type HeaderNavItem, type HeaderPortfolioData, type HeaderProps, HlsVideo, type HlsVideoProps, type HlsVideoSource, type HousePositionOrderbook, type HousePositionPendingOrder, HousePositionSlider, HousePositionSliderMobile, type HousePositionSliderMobileOrderPayload, type HousePositionSliderMobileOrderbook, type HousePositionSliderMobileProps, type HousePositionSliderOrderPayload, type HousePositionSliderProps, type InspectionSlot, LoafLiquidityBadge, type LoafLiquidityDotState, LoafLiquidityLogo, type LoafLiquidityLogoProps, LoginPopup, type LoginPopupProps, MobileTradeNav, type MobileTradeNavItem, type MobileTradeNavProps, type NewsBadgeAsset, type NewsEventTimePrecision, type NewsFreshness, type NewsStoryEntry, type NewsStoryThread, type OfferStatus, OfferingProgressCard, type OfferingProgressCardProps, OnboardingGuide, type OnboardingGuideProps, Orderbook, type OrderbookLevel, type OrderbookProps, type OrderbookSide, type OrderbookTrade, type OverviewImage, type OverviewResponse, OwnerBooking, type OwnerBookingProps, PaymentPopup, type PaymentPopupProps, PortfolioActivityPanel, type PortfolioActivityPanelProps, type PortfolioPosition, PortfolioSummary, type PortfolioSummaryProps, PriceChart, type PriceChartCandle, type PriceChartProps, type PriceChartRange, type PropertyAddressOption, PropertyBuy, PropertyCompareBar, type PropertyCompareBarProps, PropertyDocuments, type PropertyGalleryHotspot, type PropertyGalleryImage, PropertyHeroHeader, type PropertyHeroHeaderProps, PropertyHistory, type PropertyInfoItem, PropertyInspectionTimes, PropertyMediaRow, type PropertyMediaRowProps, type PropertyNewsItem, type PropertyNewsType, PropertyNewsUpdates, type PropertyNewsUpdatesProps, type PropertyOffer, PropertyOffers, type PropertyOffersProps, PropertyOverview, type PropertyOverviewProps, PropertyPhotoGallery, type PropertyPriceSummary, PropertySubheader, type PropertySubheaderAction, type PropertySubheaderProps, type PropertySubheaderTab, PropertyTour, type PropertyTourProps, PropertyValuation, type PropertyValuationProps, type PropertyValueSummary, type PurchaseFeedItem, type SelectorItem, SentimentBar, SiteFooter, Skeleton, type SkeletonProps, SlideDigit, SparklineChart, type SparklineChartProps, type ToastData, ToastProvider, type ToastVariant, TradeConfirmationModal, type TradeConfirmationModalDetails, type TradeConfirmationModalProps, TradingSlider, type TradingSliderProps, type YourOrder, type YourOrderSide, YourOrders, type YourOrdersProps, __reloadBreakingMemory, __resetDismissedBreaking, badgeVariants, buttonVariants, extractSparkline, formatAge, freshnessNote, hasPendingActivity, matchesTab, rowAgeLabel, useAdaptivePolling, useToast };
package/dist/index.d.ts CHANGED
@@ -324,7 +324,43 @@ type PropertyNewsItem = {
324
324
  * can concern several properties, user call 2026-08-20). Takes precedence
325
325
  * over the singular `asset`. */
326
326
  assets?: NewsBadgeAsset[];
327
- };
327
+ /** Is this old news? Derived server-side on every read (loaf-news-service
328
+ * src/freshness.ts:41). Only `catching_up` and `retrospective` are badged —
329
+ * `fresh` is the default class and badging it is noise. */
330
+ freshness?: NewsFreshness;
331
+ /** When the EVENT happened, not when it was published (schemas/article.ts:91).
332
+ * Absent is common and correct — never stand `publishedAt` in for it. */
333
+ eventTime?: string;
334
+ /** `month`/`quarter`/`year` are PERIODS, not timestamps (freshness.ts:21). */
335
+ eventTimePrecision?: NewsEventTimePrecision;
336
+ /** How many publishers ran the story; ≥1 (article.ts:190). >1 earns the
337
+ * "Reported by N outlets" trust line. */
338
+ reportedByCount?: number;
339
+ /** Storyline id — equals `id` for a standalone story. */
340
+ storyId?: string;
341
+ storyRole?: 'canonical' | 'follow_up';
342
+ publishedAtSource?: 'publisher' | 'ingest_fallback' | 'clamped_future';
343
+ };
344
+ type NewsFreshness = 'breaking' | 'fresh' | 'catching_up' | 'retrospective' | 'evergreen';
345
+ /** One report in a storyline — loaf-news-service GET /v1/stories/:story_id. */
346
+ type NewsStoryEntry = {
347
+ id: string;
348
+ title: string;
349
+ publishedAt: string;
350
+ source?: string;
351
+ url?: string;
352
+ relation?: 'duplicate' | 'follow_up';
353
+ };
354
+ type NewsStoryThread = {
355
+ storyId: string;
356
+ canonical: NewsStoryEntry;
357
+ /** Oldest → newest. */
358
+ timeline: NewsStoryEntry[];
359
+ reportedBy: string[];
360
+ firstReportedAt?: string;
361
+ lastUpdatedAt?: string;
362
+ };
363
+ type NewsEventTimePrecision = 'instant' | 'day' | 'month' | 'quarter' | 'year' | 'unknown';
328
364
  /** The badge's tooltip is name and ticker only (user call 2026-08-24: no price or % change). */
329
365
  type NewsBadgeAsset = {
330
366
  ticker: string;
@@ -373,6 +409,10 @@ interface PropertyNewsUpdatesProps extends React$1.HTMLAttributes<HTMLDivElement
373
409
  price?: number;
374
410
  changePercent?: number;
375
411
  }) => void;
412
+ /** Loads one storyline for the article popup (DI, like SharePositionModal's
413
+ * `fetchCard`): loaf-ui owns no transport, and the consumer already knows its
414
+ * news base URL. Omitted → the popup shows the article alone. */
415
+ fetchStory?: (storyId: string) => Promise<NewsStoryThread | null>;
376
416
  /** Show the square asset thumbnail(s) on property rows: which platform
377
417
  * asset(s) the story is about. ON by default (user call 2026-08-24, which
378
418
  * reversed the 22 Aug "hide for now"); pass `false` to hide them. */
@@ -387,6 +427,16 @@ declare function rowAgeLabel(publishedAt: string | undefined, now: number): {
387
427
  label: string;
388
428
  fresh: boolean;
389
429
  } | null;
430
+ /**
431
+ * The two freshness classes worth saying out loud. `fresh` is the default class, so
432
+ * badging it would mark every row; `breaking` already has the gold pin and `evergreen`
433
+ * (an explainer with no datable event) has nothing to date. Wording is what the class
434
+ * MEANS to a reader, not the wire value — loaf-news-service src/freshness.ts:41-51.
435
+ */
436
+ declare function freshnessNote(freshness?: string): {
437
+ label: string;
438
+ title: string;
439
+ } | null;
390
440
  declare function SentimentBar({ score, color }: {
391
441
  score: number;
392
442
  color: string;
@@ -1552,4 +1602,4 @@ declare function extractSparkline(candles: ReadonlyArray<{
1552
1602
  }>, maxPoints?: number): number[];
1553
1603
  declare const SparklineChart: React__default.NamedExoticComponent<SparklineChartProps>;
1554
1604
 
1555
- export { type ActivityTabId, AssetSelectorBar, type AssetSelectorBarProps, type AssetSelectorMetric, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, ContactPopup, type ContactPopupProps, type FinancialItem, type FundWalletParams, type FundWalletResult, Header, type HeaderNavItem, type HeaderPortfolioData, type HeaderProps, HlsVideo, type HlsVideoProps, type HlsVideoSource, type HousePositionOrderbook, type HousePositionPendingOrder, HousePositionSlider, HousePositionSliderMobile, type HousePositionSliderMobileOrderPayload, type HousePositionSliderMobileOrderbook, type HousePositionSliderMobileProps, type HousePositionSliderOrderPayload, type HousePositionSliderProps, type InspectionSlot, LoafLiquidityBadge, type LoafLiquidityDotState, LoafLiquidityLogo, type LoafLiquidityLogoProps, LoginPopup, type LoginPopupProps, MobileTradeNav, type MobileTradeNavItem, type MobileTradeNavProps, type NewsBadgeAsset, type OfferStatus, OfferingProgressCard, type OfferingProgressCardProps, OnboardingGuide, type OnboardingGuideProps, Orderbook, type OrderbookLevel, type OrderbookProps, type OrderbookSide, type OrderbookTrade, type OverviewImage, type OverviewResponse, OwnerBooking, type OwnerBookingProps, PaymentPopup, type PaymentPopupProps, PortfolioActivityPanel, type PortfolioActivityPanelProps, type PortfolioPosition, PortfolioSummary, type PortfolioSummaryProps, PriceChart, type PriceChartCandle, type PriceChartProps, type PriceChartRange, type PropertyAddressOption, PropertyBuy, PropertyCompareBar, type PropertyCompareBarProps, PropertyDocuments, type PropertyGalleryHotspot, type PropertyGalleryImage, PropertyHeroHeader, type PropertyHeroHeaderProps, PropertyHistory, type PropertyInfoItem, PropertyInspectionTimes, PropertyMediaRow, type PropertyMediaRowProps, type PropertyNewsItem, type PropertyNewsType, PropertyNewsUpdates, type PropertyNewsUpdatesProps, type PropertyOffer, PropertyOffers, type PropertyOffersProps, PropertyOverview, type PropertyOverviewProps, PropertyPhotoGallery, type PropertyPriceSummary, PropertySubheader, type PropertySubheaderAction, type PropertySubheaderProps, type PropertySubheaderTab, PropertyTour, type PropertyTourProps, PropertyValuation, type PropertyValuationProps, type PropertyValueSummary, type PurchaseFeedItem, type SelectorItem, SentimentBar, SiteFooter, Skeleton, type SkeletonProps, SlideDigit, SparklineChart, type SparklineChartProps, type ToastData, ToastProvider, type ToastVariant, TradeConfirmationModal, type TradeConfirmationModalDetails, type TradeConfirmationModalProps, TradingSlider, type TradingSliderProps, type YourOrder, type YourOrderSide, YourOrders, type YourOrdersProps, __reloadBreakingMemory, __resetDismissedBreaking, badgeVariants, buttonVariants, extractSparkline, formatAge, hasPendingActivity, matchesTab, rowAgeLabel, useAdaptivePolling, useToast };
1605
+ export { type ActivityTabId, AssetSelectorBar, type AssetSelectorBarProps, type AssetSelectorMetric, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, ContactPopup, type ContactPopupProps, type FinancialItem, type FundWalletParams, type FundWalletResult, Header, type HeaderNavItem, type HeaderPortfolioData, type HeaderProps, HlsVideo, type HlsVideoProps, type HlsVideoSource, type HousePositionOrderbook, type HousePositionPendingOrder, HousePositionSlider, HousePositionSliderMobile, type HousePositionSliderMobileOrderPayload, type HousePositionSliderMobileOrderbook, type HousePositionSliderMobileProps, type HousePositionSliderOrderPayload, type HousePositionSliderProps, type InspectionSlot, LoafLiquidityBadge, type LoafLiquidityDotState, LoafLiquidityLogo, type LoafLiquidityLogoProps, LoginPopup, type LoginPopupProps, MobileTradeNav, type MobileTradeNavItem, type MobileTradeNavProps, type NewsBadgeAsset, type NewsEventTimePrecision, type NewsFreshness, type NewsStoryEntry, type NewsStoryThread, type OfferStatus, OfferingProgressCard, type OfferingProgressCardProps, OnboardingGuide, type OnboardingGuideProps, Orderbook, type OrderbookLevel, type OrderbookProps, type OrderbookSide, type OrderbookTrade, type OverviewImage, type OverviewResponse, OwnerBooking, type OwnerBookingProps, PaymentPopup, type PaymentPopupProps, PortfolioActivityPanel, type PortfolioActivityPanelProps, type PortfolioPosition, PortfolioSummary, type PortfolioSummaryProps, PriceChart, type PriceChartCandle, type PriceChartProps, type PriceChartRange, type PropertyAddressOption, PropertyBuy, PropertyCompareBar, type PropertyCompareBarProps, PropertyDocuments, type PropertyGalleryHotspot, type PropertyGalleryImage, PropertyHeroHeader, type PropertyHeroHeaderProps, PropertyHistory, type PropertyInfoItem, PropertyInspectionTimes, PropertyMediaRow, type PropertyMediaRowProps, type PropertyNewsItem, type PropertyNewsType, PropertyNewsUpdates, type PropertyNewsUpdatesProps, type PropertyOffer, PropertyOffers, type PropertyOffersProps, PropertyOverview, type PropertyOverviewProps, PropertyPhotoGallery, type PropertyPriceSummary, PropertySubheader, type PropertySubheaderAction, type PropertySubheaderProps, type PropertySubheaderTab, PropertyTour, type PropertyTourProps, PropertyValuation, type PropertyValuationProps, type PropertyValueSummary, type PurchaseFeedItem, type SelectorItem, SentimentBar, SiteFooter, Skeleton, type SkeletonProps, SlideDigit, SparklineChart, type SparklineChartProps, type ToastData, ToastProvider, type ToastVariant, TradeConfirmationModal, type TradeConfirmationModalDetails, type TradeConfirmationModalProps, TradingSlider, type TradingSliderProps, type YourOrder, type YourOrderSide, YourOrders, type YourOrdersProps, __reloadBreakingMemory, __resetDismissedBreaking, badgeVariants, buttonVariants, extractSparkline, formatAge, freshnessNote, hasPendingActivity, matchesTab, rowAgeLabel, useAdaptivePolling, useToast };
package/dist/index.js CHANGED
@@ -3343,6 +3343,47 @@ var formatTimeAgo = (timestamp) => {
3343
3343
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
3344
3344
  return `${Math.floor(diff / 3600)}h ago`;
3345
3345
  };
3346
+ function freshnessNote(freshness) {
3347
+ if (freshness === "catching_up") return { label: "Catching up", title: "Reported promptly; we picked it up late" };
3348
+ if (freshness === "retrospective") return { label: "Older event", title: "The event is materially older than this report" };
3349
+ return null;
3350
+ }
3351
+ function NewsMetaNotes({ item, fontSize }) {
3352
+ const note = freshnessNote(item.freshness);
3353
+ const outlets = typeof item.reportedByCount === "number" && item.reportedByCount > 1 ? item.reportedByCount : null;
3354
+ if (!note && outlets === null) return null;
3355
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3356
+ note && /* @__PURE__ */ jsxRuntime.jsx(
3357
+ "span",
3358
+ {
3359
+ "data-testid": "news-freshness",
3360
+ title: note.title,
3361
+ style: {
3362
+ fontSize,
3363
+ fontWeight: 500,
3364
+ color: "rgba(255,255,255,0.5)",
3365
+ border: "1px solid rgba(255,255,255,0.14)",
3366
+ borderRadius: "3px",
3367
+ padding: "0.1rem 0.35rem",
3368
+ whiteSpace: "nowrap"
3369
+ },
3370
+ children: note.label
3371
+ }
3372
+ ),
3373
+ outlets !== null && /* @__PURE__ */ jsxRuntime.jsxs(
3374
+ "span",
3375
+ {
3376
+ "data-testid": "news-outlets",
3377
+ title: `Reported by ${outlets} publishers`,
3378
+ style: { fontSize, fontWeight: 500, color: "rgba(255,255,255,0.5)", whiteSpace: "nowrap" },
3379
+ children: [
3380
+ outlets,
3381
+ " outlets"
3382
+ ]
3383
+ }
3384
+ )
3385
+ ] });
3386
+ }
3346
3387
  function getSentimentInfo(score) {
3347
3388
  if (score == null) return null;
3348
3389
  if (score > 0.15) return { arrow: "\u25B2", label: "Bullish", color: "#0ecb81" };
@@ -3405,9 +3446,27 @@ function SentimentBar({ score, color }) {
3405
3446
  }
3406
3447
  );
3407
3448
  }
3408
- function NewsArticleModal({ item, onClose }) {
3449
+ function NewsArticleModal({
3450
+ item,
3451
+ onClose,
3452
+ fetchStory
3453
+ }) {
3409
3454
  const sentimentInfo = getSentimentInfo(item.sentimentScore);
3410
3455
  const catStyle = categoryStyles[item.type] ?? categoryStyles.market;
3456
+ const [thread, setThread] = React9__namespace.useState(null);
3457
+ const threadEntries = thread ? thread.timeline.filter((entry) => entry.id !== item.id) : [];
3458
+ const storyId = item.storyId;
3459
+ const threaded = storyId !== void 0 && (typeof item.reportedByCount === "number" && item.reportedByCount > 1 || storyId !== item.id);
3460
+ React9__namespace.useEffect(() => {
3461
+ if (!fetchStory || !threaded || storyId === void 0) return;
3462
+ let dropped = false;
3463
+ void fetchStory(storyId).then((t) => {
3464
+ if (!dropped) setThread(t);
3465
+ });
3466
+ return () => {
3467
+ dropped = true;
3468
+ };
3469
+ }, [fetchStory, threaded, storyId]);
3411
3470
  React9__namespace.useEffect(() => {
3412
3471
  const handler = (e) => {
3413
3472
  if (e.key === "Escape") onClose();
@@ -3533,6 +3592,40 @@ function NewsArticleModal({ item, onClose }) {
3533
3592
  ] }),
3534
3593
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "1px", background: "rgba(255,255,255,0.08)", marginBottom: "1.25rem" } }),
3535
3594
  item.summary ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.875rem", color: "rgba(255,255,255,0.8)", lineHeight: 1.75, margin: 0 }, children: item.summary }) : /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8rem", color: "rgba(255,255,255,0.35)", fontStyle: "italic", margin: 0 }, children: "Summary not available for this article." }),
3595
+ thread && threadEntries.length > 0 && // Lands after the popup is already open, so it is announced rather than silent.
3596
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "aria-live": "polite", style: { marginTop: "1.25rem", paddingTop: "1rem", borderTop: "1px solid rgba(255,255,255,0.08)" }, children: [
3597
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "0.75rem", fontWeight: 600, color: "rgba(255,255,255,0.75)", margin: "0 0 0.75rem" }, children: [
3598
+ "Reported by ",
3599
+ thread.reportedBy.length > 0 ? thread.reportedBy.length : threadEntries.length + 1,
3600
+ " outlets"
3601
+ ] }),
3602
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "0.6rem" }, children: threadEntries.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs(
3603
+ "li",
3604
+ {
3605
+ "data-testid": "news-story-entry",
3606
+ "data-entry-id": entry.id,
3607
+ "data-relation": entry.relation,
3608
+ style: { borderLeft: "1px solid rgba(255,255,255,0.14)", paddingLeft: "0.7rem" },
3609
+ children: [
3610
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.4rem", alignItems: "baseline", flexWrap: "wrap", marginBottom: "0.15rem" }, children: [
3611
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.65rem", color: "#848e9c" }, children: formatDateShort(entry.publishedAt) }),
3612
+ entry.source && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.65rem", color: "rgba(255,255,255,0.5)" }, children: entry.source })
3613
+ ] }),
3614
+ entry.url && isSafeUrl(entry.url) ? /* @__PURE__ */ jsxRuntime.jsx(
3615
+ "a",
3616
+ {
3617
+ href: entry.url,
3618
+ target: "_blank",
3619
+ rel: "noopener noreferrer",
3620
+ style: { fontSize: "0.8rem", color: "rgba(255,255,255,0.8)", textDecoration: "none", lineHeight: 1.5, overflowWrap: "anywhere" },
3621
+ children: entry.title
3622
+ }
3623
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.8rem", color: "rgba(255,255,255,0.8)", lineHeight: 1.5, overflowWrap: "anywhere" }, children: entry.title })
3624
+ ]
3625
+ },
3626
+ entry.id
3627
+ )) })
3628
+ ] }),
3536
3629
  item.url && isSafeUrl(item.url) && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: "1.25rem", paddingTop: "1rem", borderTop: "1px solid rgba(255,255,255,0.08)" }, children: /* @__PURE__ */ jsxRuntime.jsxs(
3537
3630
  "a",
3538
3631
  {
@@ -3592,6 +3685,7 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
3592
3685
  pinBreaking = false,
3593
3686
  onAssetOpen,
3594
3687
  showAssetBadges = true,
3688
+ fetchStory,
3595
3689
  ...props
3596
3690
  }, ref) => {
3597
3691
  const isPurchaseVariant = variant === "purchases";
@@ -4128,6 +4222,7 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
4128
4222
  },
4129
4223
  badgeAsset.ticker
4130
4224
  )),
4225
+ /* @__PURE__ */ jsxRuntime.jsx(NewsMetaNotes, { item, fontSize: isMobile ? "0.52rem" : "0.62rem" }),
4131
4226
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { padding: isMobile ? "0.1rem 0.35rem" : "0.15rem 0.4rem", borderRadius: "2px", fontSize: isMobile ? "0.52rem" : "0.65rem", fontWeight: 500, backgroundColor: isProperty ? "rgba(14,203,129,0.1)" : "rgba(240,185,11,0.1)", color: isProperty ? "#0ecb81" : "#E6C87E" }, children: isProperty ? "Property Update" : "Market News" }),
4132
4227
  sentimentInfo && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: isMobile ? "0.52rem" : "0.65rem", fontWeight: 600, color: sentimentInfo.color }, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.3rem" }, children: [
4133
4228
  sentimentInfo.arrow ? `${sentimentInfo.arrow} ` : "",
@@ -4182,7 +4277,10 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
4182
4277
  children: [
4183
4278
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.9375rem", fontWeight: 500, marginBottom: "0.4rem", color: "#fff", lineHeight: 1.4 }, children: item.title }),
4184
4279
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: "0.5rem" }, children: [
4185
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", minWidth: 0, flexWrap: "wrap" }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.75rem", color: isHighlighted ? "#0ecb81" : "#b5b8c5", whiteSpace: "nowrap" }, children: dateLabel }) }),
4280
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", minWidth: 0, flexWrap: "wrap" }, children: [
4281
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.75rem", color: isHighlighted ? "#0ecb81" : "#b5b8c5", whiteSpace: "nowrap" }, children: dateLabel }),
4282
+ /* @__PURE__ */ jsxRuntime.jsx(NewsMetaNotes, { item, fontSize: "0.65rem" })
4283
+ ] }),
4186
4284
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "0.3rem", flexShrink: 0 }, children: [
4187
4285
  sentimentInfo && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.65rem", fontWeight: 700, color: sentimentInfo.color, whiteSpace: "nowrap" }, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.3rem" }, children: [
4188
4286
  sentimentInfo.arrow ? `${sentimentInfo.arrow} ` : "",
@@ -4261,7 +4359,7 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
4261
4359
  }
4262
4360
  )
4263
4361
  ] }),
4264
- selectedItem && !isPurchaseVariant && /* @__PURE__ */ jsxRuntime.jsx(NewsArticleModal, { item: selectedItem, onClose: () => setSelectedItem(null) }),
4362
+ selectedItem && !isPurchaseVariant && /* @__PURE__ */ jsxRuntime.jsx(NewsArticleModal, { item: selectedItem, onClose: () => setSelectedItem(null), fetchStory }),
4265
4363
  badgeTip && ReactDOM__namespace.createPortal(
4266
4364
  /* @__PURE__ */ jsxRuntime.jsx(
4267
4365
  "span",
@@ -22125,6 +22223,7 @@ exports.badgeVariants = badgeVariants;
22125
22223
  exports.buttonVariants = buttonVariants;
22126
22224
  exports.extractSparkline = extractSparkline;
22127
22225
  exports.formatAge = formatAge;
22226
+ exports.freshnessNote = freshnessNote;
22128
22227
  exports.hasPendingActivity = hasPendingActivity;
22129
22228
  exports.matchesTab = matchesTab;
22130
22229
  exports.rowAgeLabel = rowAgeLabel;