From 5d682c771ca037152142cb84d7db8dc2dca82908 Mon Sep 17 00:00:00 2001
From: Ido Shamun <1993245+idoshamun@users.noreply.github.com>
Date: Wed, 8 Jul 2026 16:00:31 +0300
Subject: [PATCH 01/12] feat(post): community sentiment mockup (L1 + L2)
Adds a Community Sentiment section to the redesigned post page (PostFocusCard), behind the community_sentiment flag (dev-on, prod-off) and gated to the full page (not the preview modal).
- Layer 1 surface: plain-language TL;DR + positive/mixed/skeptical breakdown bar, discussion count, and a Deep dive toggle.
- Layer 2 (inline expand): modular blocks that render only when populated (pros/cons, the big debate, open questions, by-community with source badges + links to the real threads).
Uses typed sample data so wiring a real aggregation query later is a one-line swap.
---
.../post/focus/CommunitySentiment.tsx | 222 +++++++++++++++
.../focus/CommunitySentimentBreakdown.tsx | 267 ++++++++++++++++++
.../components/post/focus/PostFocusCard.tsx | 11 +-
packages/shared/src/lib/featureManagement.ts | 4 +
4 files changed, 503 insertions(+), 1 deletion(-)
create mode 100644 packages/shared/src/components/post/focus/CommunitySentiment.tsx
create mode 100644 packages/shared/src/components/post/focus/CommunitySentimentBreakdown.tsx
diff --git a/packages/shared/src/components/post/focus/CommunitySentiment.tsx b/packages/shared/src/components/post/focus/CommunitySentiment.tsx
new file mode 100644
index 0000000000..f28ac4da8a
--- /dev/null
+++ b/packages/shared/src/components/post/focus/CommunitySentiment.tsx
@@ -0,0 +1,222 @@
+import type { ReactElement } from 'react';
+import React, { useState } from 'react';
+import classNames from 'classnames';
+import {
+ Typography,
+ TypographyColor,
+ TypographyTag,
+ TypographyType,
+} from '../../typography/Typography';
+import { DiscussIcon, ArrowIcon } from '../../icons';
+import { IconSize } from '../../Icon';
+import { CommunitySentimentBreakdown } from './CommunitySentimentBreakdown';
+
+export interface SentimentBreakdown {
+ /** Share of the community that is positive (0-100). */
+ positive: number;
+ /** Share that is mixed/neutral (0-100). */
+ mixed: number;
+ /** Share that is critical/skeptical (0-100). */
+ critical: number;
+}
+
+export type SourceLean = 'positive' | 'mixed' | 'skeptical' | 'heated';
+
+export interface SourceSentiment {
+ /** External community name, e.g. "Hacker News". */
+ source: string;
+ /** How that community leans overall. */
+ lean: SourceLean;
+ /** One-line characterization of the conversation there. */
+ note: string;
+ /** Link to the actual discussion, when it lives in one place (HN/Lobsters). */
+ url?: string;
+}
+
+export interface CommunitySentimentData {
+ /** How opinion splits across positive / mixed / skeptical — the surface metric. */
+ breakdown: SentimentBreakdown;
+ /** One-to-two sentence plain-English summary of what the community thinks. */
+ tldr: string;
+ /** Total discussions the take is aggregated from. */
+ postCount: number;
+ /** External communities the take is drawn from. */
+ sources: string[];
+ /** Layer 2 — the case for (coalition). */
+ pros: string[];
+ /** Layer 2 — the pushback (opposition). */
+ cons: string[];
+ /** Layer 2 — how each external community leans. */
+ bySource: SourceSentiment[];
+ /** Layer 2 — the single biggest flashpoint, if the take is divisive. */
+ hottestDebate: string;
+ /** Layer 2 — what the community is still asking. */
+ openQuestions: string[];
+}
+
+/**
+ * Layer 1 + 2 mock data. Replaced by a real aggregation query once the design
+ * lands; kept as a typed default so wiring live data later is a one-line swap.
+ */
+export const SAMPLE_COMMUNITY_SENTIMENT: CommunitySentimentData = {
+ breakdown: { positive: 57, mixed: 27, critical: 16 },
+ tldr: `Developers love the idea of collapsing their stack onto Postgres to cut ops overhead and complexity, but a vocal group warns that Redis, Kafka, and Elasticsearch exist for a reason once you hit real scale.`,
+ postCount: 410,
+ sources: ['X', 'Hacker News', 'Lobsters'],
+ pros: [
+ 'One database to run, back up and monitor — a real ops win for small teams',
+ 'Extensions like pgvector, pg_search, PGMQ and TimescaleDB are genuinely capable now',
+ 'Transactions and joins across data that used to live in separate systems',
+ ],
+ cons: [
+ 'Purpose-built tools still win at scale — Kafka throughput, ES relevance, Redis latency',
+ 'Overloading one primary turns it into a single point of failure and contention',
+ 'Extensions bring their own upgrade and operational pain',
+ ],
+ bySource: [
+ {
+ source: 'Hacker News',
+ lean: 'heated',
+ note: `Classic "just use Postgres" vs "know your scale" flame war`,
+ url: 'https://news.ycombinator.com/item?id=39135501',
+ },
+ {
+ source: 'X',
+ lean: 'positive',
+ note: 'Devs cheering the stack simplification and cost savings',
+ },
+ {
+ source: 'Lobsters',
+ lean: 'skeptical',
+ note: `More measured — "it works until it doesn't; depends on scale"`,
+ url: 'https://lobste.rs/s/vtfkqh',
+ },
+ ],
+ hottestDebate: `Is consolidating onto Postgres a smart simplification, or just deferring the scaling pain you'll pay for later?`,
+ openQuestions: [
+ 'At what scale does the single-Postgres approach actually break down?',
+ 'Does pgvector / pg_search hold up against dedicated Elasticsearch on large corpora?',
+ 'Is the migration cost worth the ops savings for an existing stack?',
+ ],
+};
+
+const BREAKDOWN_SEGMENTS: Array<{
+ key: keyof SentimentBreakdown;
+ label: string;
+ color: string;
+}> = [
+ { key: 'positive', label: 'positive', color: 'bg-accent-avocado-default' },
+ { key: 'mixed', label: 'mixed', color: 'bg-accent-bun-default' },
+ { key: 'critical', label: 'skeptical', color: 'bg-accent-ketchup-default' },
+];
+
+interface CommunitySentimentProps {
+ data?: CommunitySentimentData;
+ onSeeBreakdown?: () => void;
+ className?: string;
+}
+
+/**
+ * Community Sentiment — a light, at-a-glance read on what the developer community
+ * outside daily.dev thinks about a post. Layer 1 (surface) is a plain-English
+ * TL;DR + a breakdown bar; "Deep dive" expands the modular Layer 2 blocks in place.
+ */
+export const CommunitySentiment = ({
+ data = SAMPLE_COMMUNITY_SENTIMENT,
+ onSeeBreakdown,
+ className,
+}: CommunitySentimentProps): ReactElement => {
+ const { breakdown, tldr, postCount } = data;
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const toggle = () => {
+ setIsExpanded((prev) => !prev);
+ onSeeBreakdown?.();
+ };
+
+ return (
+
+
+
+
+
+ Community take
+
+
+
+
+ {postCount}
+
+
+ discussions
+
+
+
By community
@@ -396,27 +283,6 @@ export const CommunitySentimentBreakdown = ({
)}
-
- {highlights.length > 0 && (
-
- Top picks
-
- {visibleHighlights.map((item) => (
-
- ))}
-
- {hasMoreHighlights && (
-
- )}
-
- )}
);
};
From dcf2e38323fd7936a37d8bd71aa37bc9416cb9a1 Mon Sep 17 00:00:00 2001
From: Chris Bongers
Date: Thu, 23 Jul 2026 11:45:08 +0200
Subject: [PATCH 10/12] feat(sentiment): lead the header stat with total
comments analyzed
post_count is the number of source threads (one HN + one Lobsters thread
= "2 discussions"), which undersells the take. Sum commentsCount from the
raw discussions instead, falling back to the thread count when no
discussion data exists.
Co-Authored-By: Claude Fable 5
---
.../post/focus/CommunitySentiment.tsx | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/packages/shared/src/components/post/focus/CommunitySentiment.tsx b/packages/shared/src/components/post/focus/CommunitySentiment.tsx
index 96567c7f91..c46d460993 100644
--- a/packages/shared/src/components/post/focus/CommunitySentiment.tsx
+++ b/packages/shared/src/components/post/focus/CommunitySentiment.tsx
@@ -193,7 +193,20 @@ export const CommunitySentiment = ({
return null;
}
- const { breakdown, tldr, postCount, updatedAt } = data;
+ const { breakdown, tldr, postCount, discussions, updatedAt } = data;
+
+ // `postCount` is the number of source threads (e.g. one HN + one Lobsters
+ // thread = 2), which undersells the analysis — the take is distilled from
+ // every comment in those threads, so lead with the comment total instead.
+ const totalComments =
+ discussions?.reduce(
+ (total, discussion) => total + discussion.commentsCount,
+ 0,
+ ) ?? 0;
+ const stat =
+ totalComments > 0
+ ? { value: totalComments, label: 'comments' }
+ : { value: postCount, label: 'discussions' };
const toggle = () => {
setIsExpanded((prev) => !prev);
@@ -252,13 +265,13 @@ export const CommunitySentiment = ({
color={TypographyColor.Primary}
bold
>
- {postCount}
+ {formatMetricCount(stat.value)}
- discussions
+ {stat.label}
From b2948b9ce080210e8108498201fc993506f72013 Mon Sep 17 00:00:00 2001
From: Chris Bongers
Date: Thu, 23 Jul 2026 11:56:37 +0200
Subject: [PATCH 11/12] fix(sentiment): keep header stat inside the card on
mobile
The header row had no shrinkable segment, so the comment stat pushed
past the card edge on narrow screens. Let the title group shrink
(min-w-0 flex-1) with the updated-time truncating first, and pin the
stat with shrink-0.
Co-Authored-By: Claude Fable 5
---
.../components/post/focus/CommunitySentiment.tsx | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/packages/shared/src/components/post/focus/CommunitySentiment.tsx b/packages/shared/src/components/post/focus/CommunitySentiment.tsx
index c46d460993..bfae89f57c 100644
--- a/packages/shared/src/components/post/focus/CommunitySentiment.tsx
+++ b/packages/shared/src/components/post/focus/CommunitySentiment.tsx
@@ -240,26 +240,32 @@ export const CommunitySentiment = ({
)}
>
-
-
+ {/* Shrinkable so the stat never overflows the card on mobile; the
+ "Updated Xh ago" segment truncates first. */}
+
+
Community take
{updatedAt && (
<>
·
-
+
Updated
>
)}
-
+
Date: Thu, 23 Jul 2026 12:19:56 +0200
Subject: [PATCH 12/12] fix(sentiment): stack the header stat on its own line
on mobile
Truncation alone still overflowed at phone widths; stack the header
column-wise on mobile and go back to inline right-aligned from tablet
up.
Co-Authored-By: Claude Fable 5
---
.../src/components/post/focus/CommunitySentiment.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/shared/src/components/post/focus/CommunitySentiment.tsx b/packages/shared/src/components/post/focus/CommunitySentiment.tsx
index bfae89f57c..3b83838928 100644
--- a/packages/shared/src/components/post/focus/CommunitySentiment.tsx
+++ b/packages/shared/src/components/post/focus/CommunitySentiment.tsx
@@ -239,10 +239,10 @@ export const CommunitySentiment = ({
isExpanded && 'rounded-b-none',
)}
>
-
- {/* Shrinkable so the stat never overflows the card on mobile; the
- "Updated Xh ago" segment truncates first. */}
-
+ {/* Stacked on mobile (the stat gets its own line), inline and
+ right-aligned from tablet up. */}
+