Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 6 additions & 5 deletions apps/code/src/main/services/workspace/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { CreateOrSwitchBranchSaga } from "@posthog/git/sagas/branch";
import { DetachHeadSaga } from "@posthog/git/sagas/head";
import { WorktreeManager } from "@posthog/git/worktree";
import { ANALYTICS_EVENTS } from "@shared/types/analytics";
import { inject, injectable } from "inversify";
import type { RepositoryRepository } from "../../db/repositories/repository-repository";
import type { WorkspaceRepository } from "../../db/repositories/workspace-repository";
Expand Down Expand Up @@ -340,9 +341,9 @@ export class WorkspaceService extends TypedEventEmitter<WorkspaceServiceEvents>
branchName,
error,
});
trackAppEvent("branch_link_default_branch_unknown", {
taskId,
branchName,
trackAppEvent(ANALYTICS_EVENTS.BRANCH_LINK_DEFAULT_BRANCH_UNKNOWN, {
task_id: taskId,
branch_name: branchName,
});
return;
}
Expand All @@ -368,7 +369,7 @@ export class WorkspaceService extends TypedEventEmitter<WorkspaceServiceEvents>
taskId,
branchName,
});
trackAppEvent("branch_linked", {
trackAppEvent(ANALYTICS_EVENTS.BRANCH_LINKED, {
task_id: taskId,
branch_name: branchName,
source: source ?? "unknown",
Expand All @@ -382,7 +383,7 @@ export class WorkspaceService extends TypedEventEmitter<WorkspaceServiceEvents>
taskId,
branchName: null,
});
trackAppEvent("branch_unlinked", {
trackAppEvent(ANALYTICS_EVENTS.BRANCH_UNLINKED, {
task_id: taskId,
source: source ?? "unknown",
});
Expand Down
8 changes: 7 additions & 1 deletion apps/code/src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ function App() {
const hasCompletedOnboarding = useOnboardingStore(
(state) => state.hasCompletedOnboarding,
);
const selectedDirectory = useOnboardingStore(
(state) => state.selectedDirectory,
);
const isAuthenticated = authState.status === "authenticated";
const hasCodeAccess = authState.hasCodeAccess;
const isDarkMode = useThemeStore((state) => state.isDarkMode);
Expand Down Expand Up @@ -210,8 +213,11 @@ function App() {
}

// Rendering: onboarding (includes auth + invite code gate) → main app
// We also route to onboarding when no directory is selected — without one, the
// main app has nothing meaningful to show (the dev "Skip setup" button can
// produce this state by flipping hasCompletedOnboarding without picking a directory).
const renderContent = () => {
if (!hasCompletedOnboarding) {
if (!hasCompletedOnboarding || !selectedDirectory) {
return (
<motion.div key="onboarding" initial={{ opacity: 1 }}>
<OnboardingFlow />
Expand Down
3 changes: 3 additions & 0 deletions apps/code/src/renderer/components/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useInboxDeepLink } from "@features/inbox/hooks/useInboxDeepLink";
import { FolderSettingsView } from "@features/settings/components/FolderSettingsView";
import { SettingsDialog } from "@features/settings/components/SettingsDialog";
import { useSettingsDialogStore } from "@features/settings/stores/settingsDialogStore";
import { SetupView } from "@features/setup/components/SetupView";
import { MainSidebar } from "@features/sidebar/components/MainSidebar";
import { useSidebarData } from "@features/sidebar/hooks/useSidebarData";
import { useVisualTaskOrder } from "@features/sidebar/hooks/useVisualTaskOrder";
Expand Down Expand Up @@ -129,6 +130,8 @@ export function MainLayout() {
{view.type === "command-center" && <CommandCenterView />}

{view.type === "skills" && <SkillsView />}

{view.type === "setup" && <SetupView />}
</Box>
</Flex>

Expand Down
4 changes: 4 additions & 0 deletions apps/code/src/renderer/di/container.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "reflect-metadata";
import { SetupRunService } from "@features/setup/services/setupRunService";
import { TaskService } from "@features/task-detail/service/service";
import type { TrpcRouter } from "@main/trpc/router";
import { trpcClient } from "@renderer/trpc";
Expand All @@ -20,6 +21,9 @@ container

// Bind services
container.bind<TaskService>(RENDERER_TOKENS.TaskService).to(TaskService);
container
.bind<SetupRunService>(RENDERER_TOKENS.SetupRunService)
.to(SetupRunService);

export function get<T>(token: symbol): T {
return container.get<T>(token);
Expand Down
1 change: 1 addition & 0 deletions apps/code/src/renderer/di/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export const RENDERER_TOKENS = Object.freeze({

// Services
TaskService: Symbol.for("Renderer.TaskService"),
SetupRunService: Symbol.for("Renderer.SetupRunService"),
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import {
isReportUpForReview,
} from "@features/inbox/utils/filterReports";
import { INBOX_REFETCH_INTERVAL_MS } from "@features/inbox/utils/inboxConstants";
import {
useIntegrations,
useRepositoryIntegration,
} from "@hooks/useIntegrations";
import { DiscoveredTaskDetailPane } from "@features/setup/components/DiscoveredTaskDetailPane";
import { RecommendedSetupTasks } from "@features/setup/components/RecommendedSetupTasks";
import { useSetupStore } from "@features/setup/stores/setupStore";
import { useIntegrations, useRepositoryIntegration } from "@hooks/useIntegrations";
import { Box, Flex, ScrollArea } from "@radix-ui/themes";
import type { SignalReportsQueryParams } from "@shared/types";
import { useNavigationStore } from "@stores/navigationStore";
Expand Down Expand Up @@ -227,6 +227,9 @@ export function InboxSignalsTab() {
// ── Click handler: plain / cmd / shift ──────────────────────────────────
const handleReportClick = useCallback(
(reportId: string, event: { metaKey: boolean; shiftKey: boolean }) => {
// Selecting a real report clears any discovered-task selection so the
// detail pane can swap to the report.
useSetupStore.getState().selectDiscoveredTask(null);
if (event.shiftKey) {
selectRange(
reportId,
Expand Down Expand Up @@ -310,14 +313,39 @@ export function InboxSignalsTab() {
};
}, [sidebarIsResizing, setSidebarWidth, setSidebarIsResizing]);

// ── Discovered-task suggestions (rendered inline at top of list) ───────
const discoveredTasks = useSetupStore((s) => s.discoveredTasks);
const hasDiscoveredTasks = discoveredTasks.length > 0;
const selectedDiscoveredTaskId = useSetupStore(
(s) => s.selectedDiscoveredTaskId,
);
const selectDiscoveredTask = useSetupStore((s) => s.selectDiscoveredTask);
const selectedDiscoveredTask =
discoveredTasks.find((t) => t.id === selectedDiscoveredTaskId) ?? null;

const handleSelectDiscoveredTask = useCallback(
(taskId: string) => {
selectDiscoveredTask(taskId);
clearSelection();
},
[selectDiscoveredTask, clearSelection],
);

const handleCloseDiscoveredTaskPane = useCallback(() => {
selectDiscoveredTask(null);
}, [selectDiscoveredTask]);

// ── Layout mode (computed early — needed by focus effect below) ────────
const hasReports = allReports.length > 0;
const hasActiveFilters =
sourceProductFilter.length > 0 ||
suggestedReviewerFilter.length > 0 ||
statusFilter.length < 5;
const shouldShowTwoPane =
hasReports || !!searchQuery.trim() || hasActiveFilters;
hasReports ||
!!searchQuery.trim() ||
hasActiveFilters ||
hasDiscoveredTasks;

// Sticky: once we enter two-pane mode, stay there even if a refetch
// momentarily empties the list (e.g. when sort order changes).
Expand Down Expand Up @@ -520,6 +548,9 @@ export function InboxSignalsTab() {
onConfigureSources={() => setSourcesDialogOpen(true)}
/>
</Box>
<RecommendedSetupTasks
onSelectTask={handleSelectDiscoveredTask}
/>
<ReportListPane
reports={reports}
allReports={allReports}
Expand Down Expand Up @@ -567,6 +598,11 @@ export function InboxSignalsTab() {
report={selectedReport}
onClose={clearSelection}
/>
) : selectedDiscoveredTask ? (
<DiscoveredTaskDetailPane
task={selectedDiscoveredTask}
onClose={handleCloseDiscoveredTaskPane}
/>
) : (
<SelectReportPane />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FileTextIcon } from "@phosphor-icons/react";
import { Checkbox, Flex, Tooltip } from "@radix-ui/themes";
import type { SignalReport } from "@shared/types";
import { motion } from "framer-motion";
import type { KeyboardEvent, MouseEvent } from "react";
import type { KeyboardEvent, MouseEvent, ReactNode } from "react";

function SourceProductIcon({ sourceProducts }: { sourceProducts?: string[] }) {
const firstProduct = sourceProducts?.[0];
Expand Down Expand Up @@ -45,6 +45,10 @@ interface ReportListRowProps {
onClick: (event: { metaKey: boolean; shiftKey: boolean }) => void;
onToggleChecked: () => void;
index: number;
/** Optional badge rendered before the standard status/priority/actionability badges. */
prependBadges?: ReactNode;
/** Optional override for the icon shown in the left-side icon column. */
iconOverride?: ReactNode;
}

export function ReportListRow({
Expand All @@ -54,6 +58,8 @@ export function ReportListRow({
onClick,
onToggleChecked,
index,
prependBadges,
iconOverride,
}: ReportListRowProps) {
const isInteractiveTarget = (target: EventTarget | null): boolean => {
return (
Expand Down Expand Up @@ -142,11 +148,17 @@ export function ReportListRow({
}
/>
) : (
<SourceProductIcon sourceProducts={report.source_products} />
(iconOverride ?? (
<SourceProductIcon sourceProducts={report.source_products} />
))
)}
</Flex>
<div className="min-w-0 flex-1">
<ReportCardContent report={report} compact />
<ReportCardContent
report={report}
compact
prependBadges={prependBadges}
/>
</div>
</Flex>
</motion.div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,23 @@ import { SignalReportSummaryMarkdown } from "@features/inbox/components/utils/Si
import { EyeIcon, LightningIcon } from "@phosphor-icons/react";
import { Flex, Text, Tooltip } from "@radix-ui/themes";
import type { SignalReport } from "@shared/types";
import type { ReactNode } from "react";

interface ReportCardContentProps {
report: SignalReport;
/** Show signal count, user count, and date in a meta row below the summary. */
showMeta?: boolean;
/** Tighter vertical and horizontal gaps for inbox list rows. */
compact?: boolean;
/** Optional badge node rendered before the standard status/priority/actionability badges. */
prependBadges?: ReactNode;
}

export function ReportCardContent({
report,
showMeta = false,
compact = false,
prependBadges,
}: ReportCardContentProps) {
const isReady = report.status === "ready";

Expand Down Expand Up @@ -57,6 +61,7 @@ export function ReportCardContent({
wrap="wrap"
className="min-w-0 flex-1"
>
{prependBadges}
{!isReady && <SignalReportStatusBadge status={report.status} />}
<SignalReportPriorityBadge priority={report.priority} />
<SignalReportActionabilityBadge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useOnboardingStore } from "@features/onboarding/stores/onboardingStore"
import { ArrowRight, SignOut } from "@phosphor-icons/react";
import { Button, Flex } from "@radix-ui/themes";
import { IS_DEV } from "@shared/constants/environment";
import { useNavigationStore } from "@stores/navigationStore";
import { AnimatePresence, LayoutGroup, motion } from "framer-motion";
import { useHotkeys } from "react-hotkeys-hook";

Expand Down Expand Up @@ -39,7 +40,11 @@ export function OnboardingFlow() {
const completeOnboarding = useOnboardingStore(
(state) => state.completeOnboarding,
);
const hasCompletedSetup = useOnboardingStore(
(state) => state.hasCompletedSetup,
);
const resetOnboarding = useOnboardingStore((state) => state.resetOnboarding);
const navigateToSetup = useNavigationStore((state) => state.navigateToSetup);
const logoutMutation = useLogoutMutation();
const isAuthenticated = useAuthStateValue(
(state) => state.status === "authenticated",
Expand All @@ -51,6 +56,9 @@ export function OnboardingFlow() {

const handleComplete = () => {
completeOnboarding();
if (!hasCompletedSetup) {
navigateToSetup();
}
};

const footerRight = (
Expand Down
Loading
Loading