@loafmarkets/ui 0.1.428 → 0.1.430

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();
@@ -3529,10 +3588,45 @@ function NewsArticleModal({ item, onClose }) {
3529
3588
  },
3530
3589
  children: item.type === "property" ? catStyle.label : "Market News"
3531
3590
  }
3532
- )
3591
+ ),
3592
+ /* @__PURE__ */ jsxRuntime.jsx(NewsMetaNotes, { item, fontSize: "0.65rem" })
3533
3593
  ] }),
3534
3594
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "1px", background: "rgba(255,255,255,0.08)", marginBottom: "1.25rem" } }),
3535
3595
  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." }),
3596
+ thread && threadEntries.length > 0 && // Lands after the popup is already open, so it is announced rather than silent.
3597
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "aria-live": "polite", style: { marginTop: "1.25rem", paddingTop: "1rem", borderTop: "1px solid rgba(255,255,255,0.08)" }, children: [
3598
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "0.75rem", fontWeight: 600, color: "rgba(255,255,255,0.75)", margin: "0 0 0.75rem" }, children: [
3599
+ "Reported by ",
3600
+ thread.reportedBy.length > 0 ? thread.reportedBy.length : threadEntries.length + 1,
3601
+ " outlets"
3602
+ ] }),
3603
+ /* @__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(
3604
+ "li",
3605
+ {
3606
+ "data-testid": "news-story-entry",
3607
+ "data-entry-id": entry.id,
3608
+ "data-relation": entry.relation,
3609
+ style: { borderLeft: "1px solid rgba(255,255,255,0.14)", paddingLeft: "0.7rem" },
3610
+ children: [
3611
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.4rem", alignItems: "baseline", flexWrap: "wrap", marginBottom: "0.15rem" }, children: [
3612
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.65rem", color: "#848e9c" }, children: formatDateShort(entry.publishedAt) }),
3613
+ entry.source && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.65rem", color: "rgba(255,255,255,0.5)" }, children: entry.source })
3614
+ ] }),
3615
+ entry.url && isSafeUrl(entry.url) ? /* @__PURE__ */ jsxRuntime.jsx(
3616
+ "a",
3617
+ {
3618
+ href: entry.url,
3619
+ target: "_blank",
3620
+ rel: "noopener noreferrer",
3621
+ style: { fontSize: "0.8rem", color: "rgba(255,255,255,0.8)", textDecoration: "none", lineHeight: 1.5, overflowWrap: "anywhere" },
3622
+ children: entry.title
3623
+ }
3624
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.8rem", color: "rgba(255,255,255,0.8)", lineHeight: 1.5, overflowWrap: "anywhere" }, children: entry.title })
3625
+ ]
3626
+ },
3627
+ entry.id
3628
+ )) })
3629
+ ] }),
3536
3630
  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
3631
  "a",
3538
3632
  {
@@ -3592,6 +3686,7 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
3592
3686
  pinBreaking = false,
3593
3687
  onAssetOpen,
3594
3688
  showAssetBadges = true,
3689
+ fetchStory,
3595
3690
  ...props
3596
3691
  }, ref) => {
3597
3692
  const isPurchaseVariant = variant === "purchases";
@@ -4128,6 +4223,7 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
4128
4223
  },
4129
4224
  badgeAsset.ticker
4130
4225
  )),
4226
+ /* @__PURE__ */ jsxRuntime.jsx(NewsMetaNotes, { item, fontSize: isMobile ? "0.52rem" : "0.62rem" }),
4131
4227
  /* @__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
4228
  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
4229
  sentimentInfo.arrow ? `${sentimentInfo.arrow} ` : "",
@@ -4182,7 +4278,10 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
4182
4278
  children: [
4183
4279
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.9375rem", fontWeight: 500, marginBottom: "0.4rem", color: "#fff", lineHeight: 1.4 }, children: item.title }),
4184
4280
  /* @__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 }) }),
4281
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", minWidth: 0, flexWrap: "wrap" }, children: [
4282
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.75rem", color: isHighlighted ? "#0ecb81" : "#b5b8c5", whiteSpace: "nowrap" }, children: dateLabel }),
4283
+ /* @__PURE__ */ jsxRuntime.jsx(NewsMetaNotes, { item, fontSize: "0.65rem" })
4284
+ ] }),
4186
4285
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "0.3rem", flexShrink: 0 }, children: [
4187
4286
  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
4287
  sentimentInfo.arrow ? `${sentimentInfo.arrow} ` : "",
@@ -4261,7 +4360,7 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
4261
4360
  }
4262
4361
  )
4263
4362
  ] }),
4264
- selectedItem && !isPurchaseVariant && /* @__PURE__ */ jsxRuntime.jsx(NewsArticleModal, { item: selectedItem, onClose: () => setSelectedItem(null) }),
4363
+ selectedItem && !isPurchaseVariant && /* @__PURE__ */ jsxRuntime.jsx(NewsArticleModal, { item: selectedItem, onClose: () => setSelectedItem(null), fetchStory }),
4265
4364
  badgeTip && ReactDOM__namespace.createPortal(
4266
4365
  /* @__PURE__ */ jsxRuntime.jsx(
4267
4366
  "span",
@@ -22125,6 +22224,7 @@ exports.badgeVariants = badgeVariants;
22125
22224
  exports.buttonVariants = buttonVariants;
22126
22225
  exports.extractSparkline = extractSparkline;
22127
22226
  exports.formatAge = formatAge;
22227
+ exports.freshnessNote = freshnessNote;
22128
22228
  exports.hasPendingActivity = hasPendingActivity;
22129
22229
  exports.matchesTab = matchesTab;
22130
22230
  exports.rowAgeLabel = rowAgeLabel;