Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
57700be
feat(webapp): your brief homepage mockup behind feature flag
tsahimatsliah May 19, 2026
ed1fc76
chore(brief): default briefing_home flag to true for preview review
tsahimatsliah May 19, 2026
d53a21e
Merge branch 'main' into feat/briefing-mockup
tsahimatsliah May 19, 2026
b88ec66
feat: standup creation in smart post composer (#6074)
idoshamun May 19, 2026
aca8fed
refactor(brief): turn briefing into a magazine Cover above the home feed
tsahimatsliah May 19, 2026
0a8e782
refactor(brief): tldr-style polish, segmented tabs, modal reading panel
tsahimatsliah May 19, 2026
fb4e6e8
refactor(brief): floating tabs dock, scannable attention list, all-to…
tsahimatsliah May 19, 2026
8e7a800
feat(briefing): flatten brief, sticky rectangular tabs at boundary
tsahimatsliah May 19, 2026
524119f
feat(briefing): visual hero, image cards, dual-state floating bar
tsahimatsliah May 19, 2026
f45d21e
fix(briefing): portal floating bar, click-to-expand, magazine grid
tsahimatsliah May 19, 2026
7facce3
feat(briefing): right sidebar, reset toggle, light P.S. closing, sing…
tsahimatsliah May 19, 2026
4d9922a
feat(briefing): widen cover, sidebar extends right instead of shrinki…
tsahimatsliah May 19, 2026
9772bac
feat(briefing): tie floating bar position to scroll, push from feed b…
tsahimatsliah May 19, 2026
ab7a158
feat(briefing): rework closing as positive 'all caught up' completion
tsahimatsliah May 19, 2026
9e18b67
refactor(briefing-home): revert sidebar, breathe out cover sections
tsahimatsliah May 19, 2026
2f04587
fix(briefing-home): align floating tabs to feed width, balance tab icons
tsahimatsliah May 19, 2026
9ad8f62
fix(briefing-home): shrink floating bar to its content, more bottom room
tsahimatsliah May 19, 2026
fa8fe9b
refactor(briefing-home): cleaner header, calmer sections, fix tabs ce…
tsahimatsliah May 19, 2026
24a26ec
style(briefing-home): brand the brief tab purple in both states
tsahimatsliah May 19, 2026
946831d
feat(briefing-home): magical header, faq tldr, 3-col quick hits, scro…
tsahimatsliah May 19, 2026
ac8931c
refactor(briefing-home): top-left anchored floating tabs, brand inact…
tsahimatsliah May 19, 2026
9627366
refactor(briefing-home): plain-spoken "we did the work" microcopy
tsahimatsliah May 19, 2026
4f107b6
refactor(briefing-home): rework debate row layout and expand transition
tsahimatsliah May 19, 2026
69bec76
fix(briefing-home): align floating tabs to the H1's left edge
tsahimatsliah May 19, 2026
7b45bf8
refactor(briefing-home): closing CTA is a copy-link button
tsahimatsliah May 19, 2026
3f5ce66
fix(briefing-home): keep debate row image at fixed 80x80 square
tsahimatsliah May 19, 2026
ae33047
fix(briefing-home): ReadingPanel modal scrollable + full-width header
tsahimatsliah May 19, 2026
d1c87fe
refactor(briefing-home): drop per-section sub-line microcopy
tsahimatsliah May 19, 2026
6c171c7
fix(briefing-home): move floating tabs 24px down off the page header
tsahimatsliah May 19, 2026
cf78475
refactor(briefing-home): chevron-left layout, TLDR replaces quote
tsahimatsliah May 19, 2026
19ae480
fix(briefing-home): unify engagement chips with subtle pill style
tsahimatsliah May 19, 2026
d6205e5
fix(briefing-home): flatten floating tabs, tighter gap, behind page h…
tsahimatsliah May 19, 2026
c9476a3
fix(briefing-home): restructure quick hit card layout
tsahimatsliah May 19, 2026
b402be3
fix(briefing-home): align debate rows flush left, move chevron to sta…
tsahimatsliah May 19, 2026
820a656
fix(briefing-home): anchor debate thumbnail to top so it does not shi…
tsahimatsliah May 19, 2026
df6996a
fix(briefing-home): flush-align On your radar items, drop card chrome…
tsahimatsliah May 19, 2026
790c070
fix(briefing-home): transparent stat pills, eyebrow above card, neutr…
tsahimatsliah May 19, 2026
ced7beb
feat(briefing-home): make stat pills interactive with hover/active/fo…
tsahimatsliah May 19, 2026
1446e80
fix(briefing-home): replace debate row chevron with inline read summa…
tsahimatsliah May 19, 2026
ec044eb
fix(briefing-home): keep quick hit eyebrow inside card, flat text bot…
tsahimatsliah May 19, 2026
9f579b6
fix(briefing-home): tighten gap between floating tabs and H1 title
tsahimatsliah May 19, 2026
ac8cc86
fix(briefing-home): bump debate TLDR to Callout for readability
tsahimatsliah May 19, 2026
680d353
fix(briefing-home): widen On your radar grid gaps
tsahimatsliah May 19, 2026
b78cf3b
fix(briefing-home): use single Primary button for Read full breakdown
tsahimatsliah May 19, 2026
a24d32e
fix(briefing-home): match quick hit title size and padding to debate …
tsahimatsliah May 19, 2026
04bfd08
fix(briefing-home): brief tab scrolls to page top instead of overlapp…
tsahimatsliah May 19, 2026
9d28e85
fix(briefing-home): hover chevron indicator, conditional read full bu…
tsahimatsliah May 19, 2026
b998faf
fix(briefing-home): match On your radar TLDR size to debate rows
tsahimatsliah May 19, 2026
7d9ab72
fix(briefing-home): full-width stats strip with lead-style read full …
tsahimatsliah May 19, 2026
4ef320b
fix(briefing-home): slightly smaller header subtitle
tsahimatsliah May 19, 2026
2d6b793
fix(briefing-home): balance floating tabs spacing, widen H1 to subtit…
tsahimatsliah May 19, 2026
e4e8cad
fix(briefing-home): restore tabs top offset, tighten gap below tabs
tsahimatsliah May 19, 2026
1233672
feat(briefing-home): redesign reading panel to match brief design lan…
tsahimatsliah May 20, 2026
9df31df
fix(briefing-home): tighter denser reading panel, fix modal corner cl…
tsahimatsliah May 20, 2026
67d8af7
Merge remote-tracking branch 'origin/main' into feat/briefing-mockup
tsahimatsliah May 20, 2026
19cdcc9
fix(briefing-home): use Title4 instead of nonexistent Headline type
tsahimatsliah May 20, 2026
614b930
fix(briefing-home): prettier formatting
tsahimatsliah May 20, 2026
9daeadc
ci: retrigger for flaky MyFeedPage test
tsahimatsliah May 20, 2026
0e53ff9
ci: retry flaky test_webapp
tsahimatsliah May 20, 2026
66896db
feat(briefing-home): contextual eyebrow per entity, polished panel UX
tsahimatsliah May 20, 2026
ba6cbea
feat(briefing-home): remove Quick hits section
tsahimatsliah May 20, 2026
16d5f63
chore: retrigger CI (flaky MyFeedPage test)
tsahimatsliah May 20, 2026
3124a21
feat(briefing-home): gray upvotes, calmer hovers, expandable topics, …
tsahimatsliah May 20, 2026
5d044fb
fix(briefing-home): left-align Read full breakdown in On your radar
tsahimatsliah May 20, 2026
2e55f15
fix(briefing-home): match Collection modal size and layout exactly
tsahimatsliah May 20, 2026
0bb3981
fix(briefing-home): make reading panel scrollable when content overflows
tsahimatsliah May 20, 2026
b674c52
fix(briefing-home): use Collection modal portal CSS so overlay actual…
tsahimatsliah May 20, 2026
315bab0
feat(briefing-home): scannable defaults, renamed categories
tsahimatsliah May 20, 2026
fe71711
feat(briefing-home): dedicated /briefing page, right-aligned discussi…
tsahimatsliah May 20, 2026
57904ff
feat(briefing-home): move brief to /your-brief; add persistent Brief/…
tsahimatsliah May 20, 2026
2150d6a
fix(briefing-home): unstick reading-panel scroll via overscroll-y-auto
tsahimatsliah May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/shared/src/components/MainFeedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { buildPersonalizedCategories } from './feeds/exploreCategories';
import { useFeedTagsList } from '../hooks/useFeedTagsList';
import ReadingReminderHero from './marketing/banners/ReadingReminderHero';
import { WebappShortcutsRow } from '../features/shortcuts/components/WebappShortcutsRow';
import { BriefSwitcher } from '../features/briefingHome/BriefSwitcher';
import { LiveStandupsStrip } from './liveRooms/LiveStandupsStrip';
import { AskSearchBanner } from './marketing/banners/AskSearchBanner';
import AuthContext from '../contexts/AuthContext';
Expand Down Expand Up @@ -719,6 +720,9 @@ export default function MainFeedLayout({
{isSearchOn && isFinder && !isSearchPageLaptop && (
<AskSearchBanner className="mx-4 mb-4" />
)}
{isHomePage && !!user && (
<BriefSwitcher className="mb-4 mt-2 self-center" />
)}
{shouldShowReadingReminderOnHomepage && (
<ReadingReminderHero
className="px-4 pb-2"
Expand Down
14 changes: 14 additions & 0 deletions packages/shared/src/components/sidebar/sections/MainSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
HotIcon,
JoystickIcon,
SquadIcon,
MagicIcon,
MegaphoneIcon,
YearInReviewIcon,
} from '../../icons';
Expand Down Expand Up @@ -144,9 +145,22 @@ export const MainSection = ({
}
: undefined;

const brief = isLoggedIn
? {
icon: (active: boolean) => (
<ListIcon Icon={() => <MagicIcon secondary={active} />} />
),
title: 'Your brief',
path: `${webappUrl}your-brief`,
isForcedLink: true,
requiresLogin: true,
}
: undefined;

return (
[
myFeed,
brief,
{
title: 'Following',
// this path can be opened on extension so it purposly
Expand Down
186 changes: 186 additions & 0 deletions packages/shared/src/features/briefingHome/BriefCover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import type { ReactElement } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import classNames from 'classnames';
import { BriefSwitcher } from './BriefSwitcher';
import { CoverHeader } from './CoverHeader';
import { CoverLead } from './CoverLead';
import { CoverGrid } from './CoverGrid';
import { CoverTopics } from './CoverTopics';
import { CoverClosing } from './CoverClosing';
import { ReadingPanel } from './ReadingPanel';
import type { EntityKind } from './ReadingPanel';
import { useBriefItems } from './hooks/useBriefItems';
import { useReadTracker } from './hooks/useReadTracker';
import type { BriefEntity, StoryItem, TopicDigest } from './types';

interface BriefCoverProps {
className?: string;
}

const wordsIn = (text: string): number =>
text.split(/\s+/).filter(Boolean).length;

const estimateReadMinutes = (
lead: StoryItem,
reads: StoryItem[],
topics: TopicDigest[],
): number => {
const stories = [lead, ...reads];
const storyWords = stories.reduce(
(sum, s) =>
sum +
wordsIn(s.summary) +
s.highlightedComments
.slice(0, 2)
.reduce((acc, c) => acc + wordsIn(c.content), 0),
0,
);
const topicWords = topics.reduce(
(sum, t) =>
sum + wordsIn(t.tldr) + wordsIn(t.content.replace(/<[^>]+>/g, ' ')) * 0.3,
0,
);
return Math.max(3, Math.round((storyWords + topicWords) / 220));
};

export const BriefCover = ({
className,
}: BriefCoverProps): ReactElement | null => {
const brief = useBriefItems();
const { readSet, markRead, reset: resetReads } = useReadTracker();
const [activePanel, setActivePanel] = useState<{
entity: StoryItem | TopicDigest;
list: Array<StoryItem | TopicDigest>;
} | null>(null);

const totals = useMemo(() => {
const total = 1 + brief.reads.length + brief.topics.length;
const readMinutes = estimateReadMinutes(
brief.lead,
brief.reads,
brief.topics,
);
const savedMinutes = [brief.lead, ...brief.reads].reduce(
(sum, s) => sum + s.posts.length * 3 + Math.round(s.totalComments * 0.2),
0,
);
const readCount = [
brief.lead.id,
...brief.reads.map((s) => s.id),
...brief.topics.map((t) => t.id),
].filter((id) => readSet.has(id)).length;
return {
total,
readMinutes,
savedMinutes,
readCount,
isComplete: readCount === total,
};
}, [brief, readSet]);

const storyList = useMemo<Array<StoryItem | TopicDigest>>(
() => [brief.lead, ...brief.reads, ...brief.topics],
[brief],
);

const entityKindById = useMemo<Map<string, EntityKind>>(() => {
const map = new Map<string, EntityKind>();
map.set(brief.lead.id, 'lead');
brief.reads.forEach((s) => map.set(s.id, 'read'));
brief.topics.forEach((t) => map.set(t.id, 'topic'));
return map;
}, [brief]);

const openPanel = useCallback(
(entity: StoryItem | TopicDigest) => {
markRead(entity.id);
setActivePanel({ entity, list: storyList });
},
[markRead, storyList],
);

const uniqueSourceCount = useMemo(() => {
const ids = new Set<string>();
[brief.lead, ...brief.reads].forEach((s) =>
s.sources.forEach((src) => ids.add(src.sourceId)),
);
return ids.size;
}, [brief]);

const scannedCount = useMemo(() => {
const stories = [brief.lead, ...brief.reads];
const postsAcrossStories = stories.reduce(
(acc, s) => acc + Math.max(s.posts.length, 1),
0,
);
return postsAcrossStories * 47 + brief.topics.length * 220 + 1130;
}, [brief]);

const edition = useMemo(() => {
const epochDay = Math.floor(Date.now() / (1000 * 60 * 60 * 24));
return epochDay - 19800 + 142;
}, []);

const navigatePanel = useCallback(
(delta: 1 | -1) => {
setActivePanel((current) => {
if (!current) {
return current;
}
const idx = current.list.findIndex((e) => e.id === current.entity.id);
const nextIdx =
(idx + delta + current.list.length) % current.list.length;
const next = current.list[nextIdx];
markRead(next.id);
return { entity: next, list: current.list };
});
},
[markRead],
);

return (
<section
id="brief-bounds"
aria-label="Your daily brief"
className={classNames(
'mx-auto mb-6 flex w-full max-w-[64rem] flex-col px-3 pt-8 tablet:px-4',
className,
)}
>
<BriefSwitcher className="mb-10" />
<div className="flex flex-col gap-12">
<CoverHeader
totals={totals}
sourceCount={uniqueSourceCount}
scannedCount={scannedCount}
onReset={resetReads}
/>
<CoverLead story={brief.lead} onOpen={() => openPanel(brief.lead)} />
<CoverGrid
stories={brief.reads}
readSet={readSet}
onMarkRead={markRead}
onOpen={(s) => openPanel(s as StoryItem)}
/>
<CoverTopics
topics={brief.topics}
readSet={readSet}
onMarkRead={markRead}
onOpen={(t) => openPanel(t as TopicDigest)}
/>
<CoverClosing totals={totals} edition={edition} />
</div>
{activePanel ? (
<ReadingPanel
entity={activePanel.entity}
entityKind={entityKindById.get(activePanel.entity.id) ?? 'read'}
onNext={() => navigatePanel(1)}
onPrev={() => navigatePanel(-1)}
onClose={() => setActivePanel(null)}
/>
) : null}
</section>
);
};

export type { BriefEntity };
89 changes: 89 additions & 0 deletions packages/shared/src/features/briefingHome/BriefSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { ReactElement } from 'react';
import React from 'react';
import { useRouter } from 'next/router';
import classNames from 'classnames';
import Link from '../../components/utilities/Link';
import {
Typography,
TypographyColor,
TypographyTag,
TypographyType,
} from '../../components/typography/Typography';
import { MagicIcon, HomeIcon } from '../../components/icons';
import { IconSize } from '../../components/Icon';

interface BriefSwitcherProps {
className?: string;
}

const TAB_BRIEF = '/your-brief';
const TAB_FEED = '/';

export const BriefSwitcher = ({
className,
}: BriefSwitcherProps): ReactElement => {
const router = useRouter();
const path = router?.pathname ?? '';
const isBrief = path === TAB_BRIEF;

return (
<nav
aria-label="Brief / feed switch"
className={classNames(
'mx-auto inline-flex w-fit items-center gap-1 rounded-12 border border-border-subtlest-tertiary bg-background-default p-1',
className,
)}
>
<Link href={TAB_BRIEF} passHref>
<a
aria-current={isBrief ? 'page' : undefined}
className={classNames(
'inline-flex items-center gap-2 rounded-10 px-3 py-1.5 transition-colors',
isBrief
? 'bg-surface-float text-text-primary'
: 'text-text-tertiary hover:bg-surface-float hover:text-text-primary',
)}
>
<MagicIcon
size={IconSize.XSmall}
className={
isBrief ? 'text-accent-cabbage-default' : 'text-text-tertiary'
}
secondary={isBrief}
/>
<Typography
tag={TypographyTag.Span}
type={TypographyType.Footnote}
bold
color={isBrief ? TypographyColor.Primary : TypographyColor.Tertiary}
>
Your brief
</Typography>
</a>
</Link>
<Link href={TAB_FEED} passHref>
<a
aria-current={!isBrief ? 'page' : undefined}
className={classNames(
'inline-flex items-center gap-2 rounded-10 px-3 py-1.5 transition-colors',
!isBrief
? 'bg-surface-float text-text-primary'
: 'text-text-tertiary hover:bg-surface-float hover:text-text-primary',
)}
>
<HomeIcon size={IconSize.XSmall} secondary={!isBrief} />
<Typography
tag={TypographyTag.Span}
type={TypographyType.Footnote}
bold
color={
!isBrief ? TypographyColor.Primary : TypographyColor.Tertiary
}
>
Your feed
</Typography>
</a>
</Link>
</nav>
);
};
Loading
Loading