authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-03 18:28:55-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-03 18:28:55-07:00
log07c4ab5077fa9c32bf34ab07dd2caa3907878dd9
tree14a0ead12ef7a1acec0cac471803d1dd02b5aa4f
parent0ba06a122bc73f705bfc18875366b4eeb1160992
signature Signed by PGP key B5690EEEBB952194

fix(web): keep automatic project icons consistent (#9535)


5 files changed, 42 insertions(+), 16 deletions(-)

apps/web/src/components/LegacySidebar.tsx+1-1
......@@ -2344,7 +2344,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
23442344 <ProjectFavicon
23452345 environmentId={project.environmentId}
23462346 cwd={project.workspaceRoot}
2347 projectName={project.displayName}
2347 projectName={project.title}
23482348 faviconPath={project.faviconPath}
23492349 projectIcon={project.projectIcon}
23502350 />
apps/web/src/components/Sidebar.tsx+38-12
......@@ -276,6 +276,7 @@ function terminalProcessLabel(count: number): string {
276276function SidebarThreadTooltip({
277277 thread,
278278 projectTitle,
279 projectDisplayName,
279280 projectCwd,
280281 projectFaviconPath,
281282 projectIcon,
......@@ -291,6 +292,7 @@ function SidebarThreadTooltip({
291292}: {
292293 thread: SidebarThreadSummary;
293294 projectTitle: string | null;
295 projectDisplayName: string | null;
294296 projectCwd: string | null;
295297 projectFaviconPath: string | null;
296298 projectIcon: ProjectIconOverride | null;
......@@ -321,17 +323,17 @@ function SidebarThreadTooltip({
321323 {thread.title}
322324 </div>
323325 <div className="grid gap-1.5 pl-0.5 text-xs text-muted-foreground">
324 {projectTitle ? (
326 {projectDisplayName ? (
325327 <div className="flex min-w-0 items-center gap-2">
326328 <ProjectFavicon
327329 environmentId={thread.environmentId}
328330 cwd={projectCwd ?? ""}
329 projectName={projectTitle}
331 projectName={projectTitle ?? ""}
330332 faviconPath={projectFaviconPath}
331333 projectIcon={projectIcon}
332334 className="size-3 shrink-0"
333335 />
334 <div className="min-w-0 truncate text-foreground/75">{projectTitle}</div>
336 <div className="min-w-0 truncate text-foreground/75">{projectDisplayName}</div>
335337 </div>
336338 ) : null}
337339 {environmentLabel ? (
......@@ -497,6 +499,7 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: {
497499 session: DraftSessionState;
498500 composer: ComposerThreadDraftState;
499501 projectTitle: string | null;
502 projectDisplayName: string | null;
500503 projectCwd: string | null;
501504 projectFaviconPath: string | null;
502505 projectIcon: ProjectIconOverride | null;
......@@ -571,7 +574,7 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: {
571574 className="size-4 shrink-0"
572575 />
573576 <span className="min-w-0 flex-1 truncate text-xs font-medium text-secondary-label">
574 {props.projectTitle}
577 {props.projectDisplayName}
575578 </span>
576579 <span className="ml-auto flex h-5 min-w-5 shrink-0 items-center justify-end">
577580 <Tooltip>
......@@ -609,6 +612,7 @@ interface SidebarDraftRowData {
609612// subscription + closing divider) so per-keystroke composer updates
610613// re-render only this block, never the whole sidebar. Vanishes at count 0.
611614const SidebarDraftBlock = memo(function SidebarDraftBlock(props: {
615 projectTitleByKey: ReadonlyMap<string, string>;
612616 projectDisplayNameByKey: ReadonlyMap<string, string>;
613617 projectCwdByKey: ReadonlyMap<string, string>;
614618 projectFaviconPathByKey: ReadonlyMap<string, string | null | undefined>;
......@@ -706,7 +710,8 @@ const SidebarDraftBlock = memo(function SidebarDraftBlock(props: {
706710 draftId={draftId}
707711 session={session}
708712 composer={composer}
709 projectTitle={props.projectDisplayNameByKey.get(projectKey) ?? null}
713 projectTitle={props.projectTitleByKey.get(projectKey) ?? null}
714 projectDisplayName={props.projectDisplayNameByKey.get(projectKey) ?? null}
710715 projectCwd={props.projectCwdByKey.get(projectKey) ?? null}
711716 projectFaviconPath={props.projectFaviconPathByKey.get(projectKey) ?? null}
712717 projectIcon={props.projectIconByKey.get(projectKey) ?? null}
......@@ -759,6 +764,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
759764 projectFaviconPath: string | null;
760765 projectIcon: ProjectIconOverride | null;
761766 projectTitle: string | null;
767 projectDisplayName: string | null;
762768 providerEntryByInstanceId: ReadonlyMap<string, ProviderInstanceEntry>;
763769 timestampFormat: TimestampFormat;
764770 onThreadClick: (event: ReactMouseEvent, threadRef: ScopedThreadRef) => void;
......@@ -994,6 +1000,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
9941000 <SidebarThreadTooltip
9951001 thread={thread}
9961002 projectTitle={props.projectTitle}
1003 projectDisplayName={props.projectDisplayName}
9971004 projectCwd={props.projectCwd}
9981005 projectFaviconPath={props.projectFaviconPath}
9991006 projectIcon={props.projectIcon}
......@@ -1304,7 +1311,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
13041311 <ProjectFavicon
13051312 environmentId={thread.environmentId}
13061313 cwd={props.projectCwd ?? ""}
1307 projectName={props.projectTitle ?? thread.title}
1314 projectName={props.projectTitle ?? ""}
13081315 faviconPath={props.projectFaviconPath}
13091316 projectIcon={props.projectIcon}
13101317 className="size-4"
......@@ -1465,14 +1472,14 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
14651472 projectIcon={props.projectIcon}
14661473 className="size-4 shrink-0"
14671474 />
1468 {props.projectTitle ? (
1475 {props.projectDisplayName ? (
14691476 <span
14701477 className={cn(
14711478 "min-w-0 flex-1 truncate text-secondary-label text-xs",
14721479 shouldRecede ? "font-normal" : "font-medium",
14731480 )}
14741481 >
1475 {props.projectTitle}
1482 {props.projectDisplayName}
14761483 </span>
14771484 ) : (
14781485 <span className="flex-1" />
......@@ -1669,6 +1676,7 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
16691676 projectFaviconPath: string | null;
16701677 projectIcon: ProjectIconOverride | null;
16711678 projectTitle: string | null;
1679 projectDisplayName: string | null;
16721680 environmentLabel: string | null;
16731681 environmentMachine: EnvironmentMachineKind;
16741682 providerEntryByInstanceId: ReadonlyMap<string, ProviderInstanceEntry>;
......@@ -1735,7 +1743,9 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
17351743 aria-selected={props.isHighlighted}
17361744 aria-current={props.isRouteActive ? "page" : undefined}
17371745 aria-label={
1738 props.projectTitle ? `${thread.title}, ${props.projectTitle}` : thread.title
1746 props.projectDisplayName
1747 ? `${thread.title}, ${props.projectDisplayName}`
1748 : thread.title
17391749 }
17401750 onMouseMove={props.onHighlight}
17411751 onClick={props.onSelect}
......@@ -1751,7 +1761,7 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
17511761 <ProjectFavicon
17521762 environmentId={thread.environmentId}
17531763 cwd={props.projectCwd ?? ""}
1754 projectName={props.projectTitle ?? thread.title}
1764 projectName={props.projectTitle ?? ""}
17551765 faviconPath={props.projectFaviconPath}
17561766 projectIcon={props.projectIcon}
17571767 className="size-4 shrink-0"
......@@ -1764,6 +1774,7 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
17641774 <SidebarThreadTooltip
17651775 thread={thread}
17661776 projectTitle={props.projectTitle}
1777 projectDisplayName={props.projectDisplayName}
17671778 projectCwd={props.projectCwd}
17681779 projectFaviconPath={props.projectFaviconPath}
17691780 projectIcon={props.projectIcon}
......@@ -1995,6 +2006,12 @@ export default function Sidebar() {
19952006 ),
19962007 [projects],
19972008 );
2009 // Icons use saved titles. Group labels can include a repository owner or a different title.
2010 const projectTitleByKey = useMemo(
2011 () =>
2012 new Map(projects.map((project) => [`${project.environmentId}:${project.id}`, project.title])),
2013 [projects],
2014 );
19982015 const projectDisplayNameByKey = useMemo(
19992016 () =>
20002017 new Map(
......@@ -3631,7 +3648,7 @@ export default function Sidebar() {
36313648 <ProjectFavicon
36323649 environmentId={scopedProjectGroup.environmentId}
36333650 cwd={scopedProjectGroup.workspaceRoot}
3634 projectName={scopedProjectGroup.displayName}
3651 projectName={scopedProjectGroup.title}
36353652 faviconPath={scopedProjectGroup.faviconPath}
36363653 projectIcon={scopedProjectGroup.projectIcon}
36373654 className="size-4"
......@@ -3689,7 +3706,7 @@ export default function Sidebar() {
36893706 <ProjectFavicon
36903707 environmentId={project.environmentId}
36913708 cwd={project.workspaceRoot}
3692 projectName={project.displayName}
3709 projectName={project.title}
36933710 faviconPath={project.faviconPath}
36943711 projectIcon={project.projectIcon}
36953712 className="size-4 shrink-0"
......@@ -3780,6 +3797,10 @@ export default function Sidebar() {
37803797 null
37813798 }
37823799 projectTitle={
3800 projectTitleByKey.get(`${thread.environmentId}:${thread.projectId}`) ??
3801 null
3802 }
3803 projectDisplayName={
37833804 projectDisplayNameByKey.get(
37843805 `${thread.environmentId}:${thread.projectId}`,
37853806 ) ?? null
......@@ -3902,6 +3923,10 @@ export default function Sidebar() {
39023923 null
39033924 }
39043925 projectTitle={
3926 projectTitleByKey.get(`${thread.environmentId}:${thread.projectId}`) ??
3927 null
3928 }
3929 projectDisplayName={
39053930 projectDisplayNameByKey.get(
39063931 `${thread.environmentId}:${thread.projectId}`,
39073932 ) ?? null
......@@ -3941,6 +3966,7 @@ export default function Sidebar() {
39413966 const items: ReactNode[] = [
39423967 <SidebarDraftBlock
39433968 key="draft-sessions"
3969 projectTitleByKey={projectTitleByKey}
39443970 projectDisplayNameByKey={projectDisplayNameByKey}
39453971 projectCwdByKey={projectCwdByKey}
39463972 projectFaviconPathByKey={projectFaviconPathByKey}
apps/web/src/components/pullRequest/PullRequestListFilters.tsx-1
......@@ -78,7 +78,6 @@ export function PullRequestFilterOptionIcon<Value extends string>({
7878 projectName={option.label}
7979 faviconPath={option.favicon.faviconPath}
8080 projectIcon={option.favicon.projectIcon}
81 fallbackIcon={FolderGit2Icon}
8281 className="size-3.5"
8382 />
8483 ) : (
apps/web/src/components/settings/ProjectSettingsPanel.tsx+1-1
......@@ -857,7 +857,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
857857 <ProjectFavicon
858858 environmentId={representative.environmentId}
859859 cwd={representative.workspaceRoot}
860 projectName={group.displayName}
860 projectName={representative.title}
861861 faviconPath={faviconPath}
862862 projectIcon={projectIcon}
863863 className="size-6"
docs/user/project-settings.md+2-1
......@@ -2,7 +2,8 @@
22
33T3 Code selects a project icon automatically. It checks `t3.json`, common favicon and app icon
44paths, and icon links in project HTML files. If it does not find an image, it chooses a built-in
5icon from the project name.
5icon from the saved project name. In web and desktop, this icon stays the same when the sidebar
6shows a repository label such as `owner/repo`.
67
78To choose a different icon or emoji:
89