Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ddad77e
wip
cemreinanc Mar 15, 2026
3e4467e
fix: sticky with portals and overlays
cemreinanc Mar 23, 2026
38714f0
filters bar
cemreinanc Mar 23, 2026
9a312eb
bar styles fine fixes
cemreinanc Mar 24, 2026
5c8c65c
add tw indicator
cemreinanc Mar 25, 2026
769bbe7
small fix
cemreinanc Mar 25, 2026
8225f63
add initial masonry layout
cemreinanc Mar 25, 2026
475ab4c
refactor: update layout and styling across feed tile components
cemreinanc Mar 28, 2026
1f9a1a8
review fixes
cemreinanc Mar 28, 2026
0a10882
Merge remote-tracking branch 'origin/main' into feat/new-question-feed
cemreinanc Mar 28, 2026
0bc7fc0
chage default to grid
cemreinanc Mar 28, 2026
6746249
qa fixes
cemreinanc Mar 30, 2026
6366860
collapsible sidebar titles
cemreinanc Mar 30, 2026
dbd93dd
fix: set default values for columns and gap in useMediaValues when me…
cemreinanc Mar 30, 2026
71e21e8
Merge remote-tracking branch 'origin/main' into feat/new-question-feed
cemreinanc Mar 30, 2026
1e6a596
Merge remote-tracking branch 'origin/main' into feat/new-question-feed
cemreinanc Mar 30, 2026
78019f1
Merge remote-tracking branch 'origin/main' into feat/new-question-feed
cemreinanc Mar 31, 2026
964794f
qa fixes
cemreinanc Apr 1, 2026
9408b47
Merge branch 'main' into feat/new-question-feed
cemreinanc Apr 5, 2026
cb220df
Merge branch 'main' into feat/new-question-feed
cemreinanc Apr 29, 2026
50666db
qa fixes
cemreinanc Apr 29, 2026
7146334
feed: sort filter bar updates
cemreinanc Apr 30, 2026
10e9fa5
add footer top border
cemreinanc Apr 30, 2026
e1c7c99
add mobile drawer
cemreinanc May 4, 2026
eeee3af
load all categories in sidebar
cemreinanc May 4, 2026
4f4b930
categories grid
cemreinanc May 4, 2026
1016525
Merge remote-tracking branch 'origin/main' into feat/new-question-feed
cemreinanc May 4, 2026
3686ba9
search tiles
cemreinanc May 5, 2026
9faaf99
increase post per page
cemreinanc May 5, 2026
c9eabb6
Merge remote-tracking branch 'origin/main' into feat/new-question-feed
cemreinanc May 5, 2026
2f7885e
stable feed and masonry updates
cemreinanc May 5, 2026
8b18d2b
fix sidebar scroll
cemreinanc May 5, 2026
ab0eedc
small fix
cemreinanc May 5, 2026
5f8185d
mobile hover state fix
cemreinanc May 5, 2026
0cc9e25
Merge branch 'main' into feat/new-question-feed
cemreinanc May 5, 2026
cc05804
tile cards hover borders
cemreinanc May 6, 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
2 changes: 1 addition & 1 deletion Claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This is a Django + Next.js monorepo. Python/Django backend lives in the root dir
- For any frontend content visible to the user, use the translation mechanism used across the whole frontend. `const t = useTranslations()` and then `t("stringKey")` while adding the "stringKey" to all the corresponding language files in `front_end/messages/`: `en.json`, `es.json`, `cs.json`, `pt.json`, `zh.json`, `zh-TW.json`.

# Workflow
- When connected to an IDE, check terminal outputs first. If a dev server is already running, do not run a build. Instead, read the dev server terminal output for any latest errors and use those for feedback.
- When connected to an IDE, or have access to a tmux session running node/uv, check terminal outputs first. If a dev server is already running for the project, do not run a build. Instead, read the dev server terminal output for any latest errors and use those for feedback.
- When done making code changes, run the relevant linters and formatters based on which files you edited:
- Python files: run `uv run ruff format .` and `uv run ruff check .`
- Frontend (JS/TS) files: run `cd ./front_end && bun run lint` and `cd ./front_end && bun run format`, and try to build with `cd ./front_end && bun run build` if there is no running dev server in IDE.
4 changes: 4 additions & 0 deletions front_end/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@
"toggleAllTopics": "Přepnout všechna témata",
"toggleNewsAnnotations": "Přepnout anotace zpráv",
"Filter": "Filtr",
"sort": "Řadit",
"Done": "Hotovo",
"Clear": "Vymazat",
"predicted": "Předpovězeno",
Expand Down Expand Up @@ -1021,6 +1022,9 @@
"communitySlugDescription": "Vaše komunita je přístupná přes následující URL:",
"communityDescription": "Popis komunity",
"contentTranslatedHeaderText": "Některý obsah na této stránce je automaticky přeložen a může být nepřesný.",
"translatedBy": "přeloženo pomocí",
"listLayout": "Zobrazení seznamu",
"gridLayout": "Zobrazení mřížky",
"showOriginalContent": "Zobrazit originál",
"nextQuestion": "Další otázka",
"fullName": "Celé jméno",
Expand Down
4 changes: 4 additions & 0 deletions front_end/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@
"toggleAllTopics": "Toggle all topics",
"toggleNewsAnnotations": "Toggle news annotations",
"Filter": "Filter",
"sort": "Sort",
"done": "done",
"clear": "clear",
"predicted": "Predicted",
Expand Down Expand Up @@ -1285,6 +1286,9 @@
"youArePostingAPrivateComment": "You are posting a private comment",
"unread": "unread",
"contentTranslatedHeaderText": "Some content on this page is automatically translated, and may be inaccurate.",
"translatedBy": "translated by",
"listLayout": "List layout",
"gridLayout": "Grid layout",
"showOriginalContent": "Show original",
"nextQuestion": "Next Question",
"next": "Next",
Expand Down
4 changes: 4 additions & 0 deletions front_end/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@
"toggleAllTopics": "Alternar todos los temas",
"toggleNewsAnnotations": "Alternar anotaciones de noticias",
"Filter": "Filtrar",
"sort": "Ordenar",
"Done": "Hecho",
"Clear": "Limpiar",
"predicted": "Predicho",
Expand Down Expand Up @@ -1020,6 +1021,9 @@
"communitySlugDescription": "Tu comunidad puede ser accedida a través de la siguiente URL:",
"communityDescription": "Descripción de la Comunidad",
"contentTranslatedHeaderText": "Parte del contenido en esta página se traduce automáticamente y puede ser inexacto.",
"translatedBy": "traducido por",
"listLayout": "Vista de lista",
"gridLayout": "Vista de cuadrícula",
"showOriginalContent": "Mostrar original",
"nextQuestion": "Siguiente Pregunta",
"fullName": "Nombre Completo",
Expand Down
4 changes: 4 additions & 0 deletions front_end/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@
"toggleAllTopics": "Alternar todos os tópicos",
"toggleNewsAnnotations": "Alternar anotações de notícias",
"Filter": "Filtro",
"sort": "Ordenar",
"done": "concluído",
"clear": "limpar",
"predicted": "Previsto",
Expand Down Expand Up @@ -974,6 +975,9 @@
"youArePostingAPrivateComment": "Você está postando um comentário privado",
"unread": "não lido",
"contentTranslatedHeaderText": "Algum conteúdo nesta página foi traduzido automaticamente e pode estar incorreto.",
"translatedBy": "traduzido por",
"listLayout": "Layout de lista",
"gridLayout": "Layout de grade",
"showOriginalContent": "Mostrar original",
"discard": "Descartar",
"onboardingStep4AlmostDone": "Você está quase terminando este tutorial!",
Expand Down
4 changes: 4 additions & 0 deletions front_end/messages/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@
"toggleAllTopics": "切換所有話題",
"toggleNewsAnnotations": "切換新聞註釋",
"Filter": "篩選",
"sort": "排序",
"done": "完成",
"clear": "清除",
"predicted": "已預測",
Expand Down Expand Up @@ -1047,6 +1048,9 @@
"youArePostingAPrivateComment": "您正在發佈一則私人評論",
"unread": "未讀",
"contentTranslatedHeaderText": "此頁面上的部分內容是自動翻譯的,可能不準確。",
"translatedBy": "翻譯提供",
"listLayout": "列表佈局",
"gridLayout": "網格佈局",
"showOriginalContent": "顯示原文",
"nextQuestion": "下一個問題",
"next": "下一步",
Expand Down
4 changes: 4 additions & 0 deletions front_end/messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@
"toggleAllTopics": "切換所有主題",
"toggleNewsAnnotations": "切换新闻注释",
"Filter": "篩選",
"sort": "排序",
"Done": "完成",
"Clear": "清除",
"predicted": "已預測",
Expand Down Expand Up @@ -1022,6 +1023,9 @@
"communitySlugDescription": "您可以通过以下网址访问您的社区:",
"communityDescription": "社区描述",
"contentTranslatedHeaderText": "此页面上的一些内容是自动翻译的,可能不准确。",
"translatedBy": "翻译提供",
"listLayout": "列表布局",
"gridLayout": "网格布局",
"showOriginalContent": "显示原文",
"nextQuestion": "下一问题",
"fullName": "全名",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const TournamentFeed: FC<Props> = ({ tournament }) => {
indexWeights={weights}
filters={pageFilters}
initialQuestions={questions}
forceLayout="list"
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default async function Questions(props: Props) {
<LoadingIndicator className="mx-auto h-8 w-24 text-gray-600 dark:text-gray-600-dark" />
}
>
<AwaitedPostsFeed filters={userQuestionsFilters} />
<AwaitedPostsFeed filters={userQuestionsFilters} forceLayout="list" />
</Suspense>
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion front_end/src/app/(main)/c/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ export default async function IndividualCommunity(props: Props) {
<LoadingIndicator className="mx-auto h-8 w-24 text-gray-600 dark:text-gray-600-dark" />
}
>
<AwaitedPostsFeed filters={pageFilters} isCommunity={true} />
<AwaitedPostsFeed
filters={pageFilters}
isCommunity={true}
forceLayout="list"
/>
</Suspense>
</div>
</main>
Expand Down
6 changes: 5 additions & 1 deletion front_end/src/app/(main)/c/[slug]/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ export default async function CommunityManagementSettings(props: Props) {
<LoadingIndicator className="mx-auto h-8 w-24 text-gray-600 dark:text-gray-600-dark" />
}
>
<AwaitedPostsFeed filters={pageFilters} isCommunity />
<AwaitedPostsFeed
filters={pageFilters}
isCommunity
forceLayout="list"
/>
</Suspense>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const ContentTranslatedBanner: FC<{ forceVisible?: boolean }> = ({
return (
<>
<div className="mt-10 sm:mt-8" />
<div className="fixed top-12 z-40 flex w-screen justify-center gap-8 border-t border-t-blue-500/50 bg-gradient-to-b from-white/65 to-white shadow-md backdrop-blur-sm dark:border-t-blue-700/50 dark:from-blue-900/65 dark:to-blue-900">
<div className="fixed top-12 z-[101] flex w-screen justify-center gap-8 border-t border-t-blue-500/50 bg-gradient-to-b from-white/65 to-white shadow-md backdrop-blur-sm dark:border-t-blue-700/50 dark:from-blue-900/65 dark:to-blue-900">
<div className="flex w-full justify-between p-1 sm:items-center sm:justify-center">
<p className="m-1 text-xs text-gray-700 dark:text-gray-700-dark">
{t("contentTranslatedHeaderText")}{" "}
Expand All @@ -48,8 +48,8 @@ const ContentTranslatedBanner: FC<{ forceVisible?: boolean }> = ({

<div className="static right-2 sm:absolute">
<div className="item-center flex flex-col justify-center gap-1 sm:flex-row">
<span className="whitespace-nowrap text-[10px] text-gray-700 dark:text-gray-700-dark">
translated by
<span className="whitespace-nowrap text-[10px] text-gray-700 dark:text-gray-700-dark">
{t("translatedBy")}
</span>
<GoogleTranslateAttribution />
</div>
Expand Down
2 changes: 1 addition & 1 deletion front_end/src/app/(main)/components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ const Footer: FC<{ hideSelectors?: boolean }> = ({ hideSelectors }) => {
const handleContactClick = () => setCurrentModal({ type: "contactUs" });

return (
<footer className="flex w-full flex-col gap-16 bg-blue-950 px-4 py-20 text-gray-300 lg:items-center lg:px-20 print:hidden">
<footer className="flex w-full flex-col gap-16 border-t border-blue-800 bg-blue-950 px-4 py-20 text-gray-300 dark:border-blue-700 lg:items-center lg:px-20 print:hidden">
{/* Main content */}
<div className="flex w-full max-w-[1352px] flex-col gap-16 lg:flex-row lg:gap-4">
{/* Left column - Logo, description, socials, selectors */}
Expand Down
25 changes: 4 additions & 21 deletions front_end/src/app/(main)/components/global_search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import { debounce } from "lodash";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import React, { useState, useEffect, useCallback } from "react";
import React, { useCallback } from "react";

import RandomButton from "@/components/random_button";
import SearchInput from "@/components/search_input";
import {
POST_ORDER_BY_FILTER,
Expand All @@ -30,8 +29,6 @@ const GlobalSearch: React.FC<GlobalSearchProps> = ({
}) => {
const t = useTranslations();
const router = useRouter();
const [isHidden, setIsHidden] = useState(true);

// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedAnalyticsEvent = useCallback(
debounce(() => {
Expand All @@ -42,17 +39,8 @@ const GlobalSearch: React.FC<GlobalSearchProps> = ({
[]
);

const {
globalSearch,
updateGlobalSearch,
isVisible: otherSearchIsVisible,
isSearched,
setIsSearched,
} = useGlobalSearchContext();

useEffect(() => {
setIsHidden(otherSearchIsVisible);
}, [otherSearchIsVisible]);
const { globalSearch, updateGlobalSearch, isSearched, setIsSearched } =
useGlobalSearchContext();

const eraseSearch = () => {
updateGlobalSearch("");
Expand All @@ -72,11 +60,7 @@ const GlobalSearch: React.FC<GlobalSearchProps> = ({
);
};

const visibilityClass = isMobile
? "flex md:hidden"
: isHidden
? "hidden"
: "hidden md:flex";
const visibilityClass = isMobile ? "flex md:hidden" : "hidden md:flex";

return (
<div
Expand Down Expand Up @@ -107,7 +91,6 @@ const GlobalSearch: React.FC<GlobalSearchProps> = ({
submitButtonClassName="hidden md:block"
submitIconClassName="text-blue-500 dark:text-blue-500"
/>
<RandomButton />
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion front_end/src/app/(main)/news/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default async function NewsFeed(props: {
<LoadingIndicator className="mx-auto h-8 w-24 text-gray-600 dark:text-gray-600-dark" />
}
>
<AwaitedPostsFeed filters={filters} type="news" />
<AwaitedPostsFeed filters={filters} type="news" forceLayout="list" />
</Suspense>
</div>
</main>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,24 +136,38 @@ export const KeyFactorTileQuestionLinkView: FC<
Props & {
href?: string;
label: string | null;
labelPlaceholder?: string;
title: string;
}
> = ({ className, expanded, href, label, onToggle, title }) => {
> = ({
className,
expanded,
href,
label,
labelPlaceholder,
onToggle,
title,
}) => {
const tooltipText = "This is another Metaculus question.";
const shouldRenderLabelSlot = !!label || !!labelPlaceholder;

return (
<KeyFactorTileContainer
expanded={expanded}
className={className}
onClick={onToggle}
startAdornment={
label ? (
shouldRenderLabelSlot ? (
<span
className={cn(
"shrink-0 font-medium text-olive-800 dark:text-olive-800-dark"
)}
>
{label}
{label ?? (
<span aria-hidden className="invisible">
{labelPlaceholder}
</span>
)}
</span>
) : undefined
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,25 @@ const KeyFactorsTileView: React.FC<Props> = ({
return other;
}, [primaryQuestionLink, post.id]);

const [binaryLabel, setBinaryLabel] = useState<string | null>(null);
const [binaryLabel, setBinaryLabel] = useState<{
key: string;
label: string;
} | null>(null);

useEffect(() => {
setBinaryLabel(null);

if (!otherQuestion) return;
if (otherQuestion.type !== QuestionType.Binary) return;

let cancelled = false;
const labelKey = getQuestionLabelKey(otherQuestion);

const applyProb = (rawProb?: number | null) => {
if (cancelled || typeof rawProb !== "number") return;
const pct = Math.round(rawProb * 100);
setBinaryLabel(`${pct}% ${t("chance")}`);
setBinaryLabel({
key: labelKey,
label: `${pct}% ${t("chance")}`,
});
};

const inlineCP = getBinaryCPFromQuestion(otherQuestion);
Expand Down Expand Up @@ -180,7 +185,10 @@ const KeyFactorsTileView: React.FC<Props> = ({
}

const isBinary = otherQuestion.type === QuestionType.Binary;
const label = isBinary && binaryLabel ? binaryLabel : null;
const labelKey = getQuestionLabelKey(otherQuestion);
const label =
isBinary && binaryLabel?.key === labelKey ? binaryLabel.label : null;
const labelPlaceholder = isBinary ? `100% ${t("chance")}` : undefined;
const questionLinkHref = getPostLink({ id: otherQuestion.post_id });

return (
Expand All @@ -189,13 +197,21 @@ const KeyFactorsTileView: React.FC<Props> = ({
kf={{} as KeyFactor}
href={questionLinkHref}
label={label}
labelPlaceholder={labelPlaceholder}
title={otherQuestion.title}
expanded={isQuestionLinkExpanded}
onToggle={onToggleQuestionLink}
/>
</li>
);
}, [primaryQuestionLink, otherQuestion, binaryLabel, isQuestionLinkExpanded]);
}, [
primaryQuestionLink,
otherQuestion,
binaryLabel,
isQuestionLinkExpanded,
onToggleQuestionLink,
t,
]);

const items = useMemo(
() =>
Expand Down Expand Up @@ -270,6 +286,10 @@ function getBinaryCPFromQuestion(
return null;
}

function getQuestionLabelKey(question: QuestionWithCP) {
return `${question.post_id ?? ""}:${question.id ?? ""}`;
}

const score = (kf: KeyFactor) => (kf.freshness ?? 0) * 10;

export default KeyFactorsTileView;
Loading
Loading