fix(desktop): restore the Projects sidebar row and overview route

The channel-first migration removed the Projects row from the sidebar's
primary menu and turned /projects into a redirect to home, leaving the
projects overview (ProjectsScreen) unreachable: the sidebar Projects
section's "Browse all" action and every other goProjects() caller
silently landed on the Inbox.

Bring back the entry point without reverting the migration:

- /projects renders ProjectsScreen again (lazy, in Suspense) instead of
  redirecting to /. The usePreviewFeatureWarning("projects") call from
  the pre-promotion route stays gone.
- The primary menu shows Projects under Inbox again, wired through
  onSelectProjects and projectsOverviewActive exactly as before, but
  with no FeatureGate: Projects was promoted out of preview features on
  this branch and the "projects" feature id no longer exists, so the
  old gate would only re-flood the console with unknown-feature
  warnings.
- channel-project-features.spec.ts pinned the row's absence; it now
  asserts the row is visible alongside the sidebar Projects section.
  badge.spec.ts already required the row (primary rows share the same
  inactive emphasis) and passes again without changes.

Verified: desktop unit suite 5943 passing; tsc, biome clean; the
channel-project-features and badge smoke specs pass (20 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
This commit is contained in:
Matt Toohey
2026-09-02 15:53:14 +10:00
parent d77611dd08
commit 6ed11ffe5e
6 changed files with 47 additions and 6 deletions

View File

@@ -147,6 +147,7 @@ export function AppShell() {
goChannel,
goHome,
goNewMessage,
goProjects,
goPulse,
goSettings,
goWorkflows,
@@ -885,6 +886,7 @@ export function AppShell() {
scopeSearchFocusRequest,
]}
onSelectHome={() => void goHome()}
onSelectProjects={() => void goProjects()}
onSelectPulse={() => void goPulse()}
onSelectSettings={handleOpenSettings}
onSelectWorkflows={() => void goWorkflows()}
@@ -901,6 +903,9 @@ export function AppShell() {
})
}
profile={profileQuery.data}
projectsOverviewActive={
location.pathname === "/projects"
}
selfUserStatus={
deferredPubkey
? (selfStatusQuery.data?.[

View File

@@ -1,7 +1,21 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import * as React from "react";
import { createFileRoute } from "@tanstack/react-router";
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
const ProjectsScreen = React.lazy(async () => {
const module = await import("@/features/projects/ui/ProjectsScreen");
return { default: module.ProjectsScreen };
});
export const Route = createFileRoute("/projects")({
beforeLoad: () => {
throw redirect({ to: "/" });
},
component: ProjectsRouteComponent,
});
function ProjectsRouteComponent() {
return (
<React.Suspense fallback={<ViewLoadingFallback kind="projects" />}>
<ProjectsScreen />
</React.Suspense>
);
}

View File

@@ -91,6 +91,7 @@ export function AppSidebar({
isCreatingChannel,
isCreatingForum,
profile,
projectsOverviewActive,
relayConnectionCard,
selfPresenceStatus,
errorMessage,
@@ -116,6 +117,7 @@ export function AppSidebar({
onRemoveCommunity,
onCreateAgent,
onSelectAgents,
onSelectProjects,
onSelectPulse,
onSelectWorkflows,
onSelectHome,
@@ -542,8 +544,10 @@ export function AppSidebar({
homeBadgeCount={homeBadgeCount}
onSelectAgents={onSelectAgents}
onSelectHome={onSelectHome}
onSelectProjects={onSelectProjects}
onSelectPulse={onSelectPulse}
onSelectWorkflows={onSelectWorkflows}
projectsOverviewActive={projectsOverviewActive}
selectedView={selectedView}
/>

View File

@@ -32,6 +32,7 @@ export type AppSidebarProps = {
isCreatingChannel: boolean;
isCreatingForum: boolean;
profile?: Profile;
projectsOverviewActive: boolean;
relayConnectionCard: ReturnType<typeof useSidebarRelayConnectionCard>;
selfPresenceStatus: PresenceStatus;
errorMessage?: string;
@@ -82,6 +83,7 @@ export type AppSidebarProps = {
onRemoveCommunity: (id: string) => Promise<LeaveCommunityResult | undefined>;
onCreateAgent: () => void;
onSelectAgents: () => void;
onSelectProjects: () => void;
onSelectPulse: () => void;
onSelectWorkflows: () => void;
onSelectHome: () => void;

View File

@@ -1,4 +1,4 @@
import { Activity, Bot, Inbox, Zap } from "lucide-react";
import { Activity, Bot, Folders, Inbox, Zap } from "lucide-react";
import { TopbarSearch } from "@/features/search/ui/TopbarSearch";
import { FeatureGate } from "@/shared/features";
@@ -41,8 +41,10 @@ type AppSidebarPrimaryMenuProps = {
homeBadgeCount: number;
onSelectAgents: () => void;
onSelectHome: () => void;
onSelectProjects: () => void;
onSelectPulse: () => void;
onSelectWorkflows: () => void;
projectsOverviewActive: boolean;
selectedView: SidebarSelectedView;
};
@@ -89,8 +91,10 @@ export function AppSidebarPrimaryMenu({
homeBadgeCount,
onSelectAgents,
onSelectHome,
onSelectProjects,
onSelectPulse,
onSelectWorkflows,
projectsOverviewActive,
selectedView,
}: AppSidebarPrimaryMenuProps) {
return (
@@ -134,6 +138,18 @@ export function AppSidebarPrimaryMenu({
</SidebarMenuButton>
</SidebarMenuItem>
</FeatureGate>
<SidebarMenuItem>
<SidebarMenuButton
data-testid="open-projects-view"
isActive={selectedView === "projects" && projectsOverviewActive}
onClick={onSelectProjects}
tooltip="Projects"
type="button"
>
<Folders className="h-4 w-4" />
<SidebarMenuLabel>Projects</SidebarMenuLabel>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton
className="data-[active=true]:font-normal"

View File

@@ -278,7 +278,7 @@ test("existing channel project data infers features with separate Projects navig
await expect
.poll(async () => (await acceptedProjectEvents(page)).length)
.toBe(0);
await expect(page.getByTestId("open-projects-view")).toHaveCount(0);
await expect(page.getByTestId("open-projects-view")).toBeVisible();
const projectsSection = page.getByTestId("sidebar-projects-section");
await expect(projectsSection).toBeVisible();