Skip to content

Commit 33cfc2e

Browse files
committed
fix: scope PR Inbox refresh to visibility
Co-authored-by: Jon Tirsen <tirsen@squareup.com> Signed-off-by: Jon Tirsen <tirsen@squareup.com>
1 parent 078be67 commit 33cfc2e

14 files changed

Lines changed: 297 additions & 200 deletions

File tree

docs/work-status-platform-surfaces.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ without coupling the implementations.
77

88
| Track | User-facing name | Implementation term |
99
| --- | --- | --- |
10-
| Berd top bar | PR tracker | Pull Requests popover |
10+
| Berd top bar | PR Inbox | PR Inbox popover |
1111
| macOS | Work Status | menu bar popover |
1212
| Windows | Work Status | system tray flyout |
1313

1414
## 1. In-app PR tracker
1515

16-
The in-app popover shows open pull requests only. Berd already exposes chat
16+
The PR Inbox popover shows open pull requests only. Berd already exposes chat
1717
status in its left sidebar, so duplicating chats inside the app would add noise.
1818
The PR tracker groups a pull request under the Berd project of the session that
1919
created it when that association can be recovered; otherwise it uses **No

src-tauri/src/commands/pr_tracker.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,42 @@ const MAX_REPOSITORY_LENGTH: usize = 256;
3434
const MAX_BRANCH_LENGTH: usize = 512;
3535
const GIT_PROBE_CONCURRENCY: usize = 4;
3636
const WORKSPACE_CANDIDATES_QUERY: &str = r#"
37-
WITH session_activity AS (
37+
WITH recent_messages AS (
38+
SELECT session_id, created_timestamp
39+
FROM messages
40+
ORDER BY id DESC
41+
LIMIT ?
42+
), recent_message_activity AS (
43+
SELECT session_id,
44+
MAX(
45+
CASE
46+
WHEN created_timestamp > 10000000000
47+
THEN created_timestamp / 1000
48+
ELSE created_timestamp
49+
END
50+
) AS activity_at
51+
FROM recent_messages
52+
GROUP BY session_id
53+
), session_activity AS (
3854
SELECT s.id,
3955
s.working_dir,
4056
s.project_id,
4157
COALESCE(
42-
MAX(
43-
CASE
44-
WHEN m.created_timestamp > 10000000000
45-
THEN m.created_timestamp / 1000
46-
ELSE m.created_timestamp
47-
END
48-
),
58+
m.activity_at,
4959
CASE
5060
WHEN unixepoch(s.updated_at) >= unixepoch(s.created_at)
5161
THEN unixepoch(s.updated_at)
5262
ELSE COALESCE(unixepoch(s.created_at), unixepoch(s.updated_at))
5363
END
5464
) AS activity_at
5565
FROM sessions s
56-
LEFT JOIN messages m ON m.session_id = s.id
66+
LEFT JOIN recent_message_activity m ON m.session_id = s.id
5767
WHERE s.archived_at IS NULL
5868
AND COALESCE(s.session_type, 'user') IN ('user', 'acp')
5969
AND s.project_id IS NOT NULL
6070
AND TRIM(s.project_id) != ''
6171
AND s.working_dir IS NOT NULL
6272
AND TRIM(s.working_dir) != ''
63-
GROUP BY s.id, s.working_dir, s.project_id, s.created_at, s.updated_at
6473
), ranked_workspaces AS (
6574
SELECT id,
6675
working_dir,
@@ -232,6 +241,7 @@ async fn resolve_pr_tracker_projects_inner(
232241
.await
233242
.map_err(|error| format!("Failed to open Berd chat database: {error}"))?;
234243
let rows = sqlx::query(WORKSPACE_CANDIDATES_QUERY)
244+
.bind(MAX_MESSAGE_CANDIDATES)
235245
.bind(MAX_WORKSPACE_CANDIDATES as i64)
236246
.fetch_all(&pool)
237247
.await
@@ -579,7 +589,8 @@ mod tests {
579589
id INTEGER PRIMARY KEY,
580590
session_id TEXT NOT NULL,
581591
created_timestamp INTEGER NOT NULL
582-
)
592+
);
593+
CREATE INDEX idx_messages_session ON messages(session_id);
583594
"#,
584595
)
585596
.execute(&pool)
@@ -624,6 +635,7 @@ mod tests {
624635
.unwrap();
625636

626637
let rows = sqlx::query(WORKSPACE_CANDIDATES_QUERY)
638+
.bind(MAX_MESSAGE_CANDIDATES)
627639
.bind(MAX_WORKSPACE_CANDIDATES as i64)
628640
.fetch_all(&pool)
629641
.await

src/app/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { SelectedTextContextMenu } from "@/app/ui/SelectedTextContextMenu";
66
import { StartupLoadingView } from "@/app/ui/StartupLoadingView";
77
import { useAuthGate } from "@/features/auth/hooks/useAuthGate";
88
import { GlobalShortcutBridge } from "@/features/global-shortcut/GlobalShortcutBridge";
9-
import { WorkStatusBridge } from "@/features/work-status/WorkStatusBridge";
109
import { LoginView } from "@/features/auth/ui/LoginView";
1110
import { getBuildFeatureState } from "@/shared/profile/buildProfile";
1211
import { useZoom } from "@/shared/hooks/useZoom";
@@ -49,7 +48,6 @@ export function App() {
4948
content = (
5049
<TopBarActionsProvider>
5150
<GlobalShortcutBridge />
52-
<WorkStatusBridge />
5351
<AppShell
5452
authStatus={authGate.authStatus}
5553
onLoggedOut={authGate.completeLogin}

src/app/ui/TopBar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from "@tabler/icons-react";
1111
import { useTranslation } from "react-i18next";
1212
import { useTopBarActions } from "@/app/contexts/TopBarActionsContext";
13-
import { WORK_STATUS_EXPERIMENT_ID } from "@/features/experiments/experimentDefinitions";
13+
import { RELATED_PULL_REQUESTS_EXPERIMENT_ID } from "@/features/experiments/experimentDefinitions";
1414
import { useExperiment } from "@/features/experiments/experimentPreferences";
1515
import { BetaBadge } from "@/features/updates/ui/BetaBadge";
1616
import { PullRequestsPopover } from "@/features/work-status/PullRequestsPopover";
@@ -68,8 +68,8 @@ export function TopBar({
6868
}: TopBarProps) {
6969
const { t } = useTranslation(["sidebar", "feedback"]);
7070
const viewActions = useTopBarActions();
71-
const workStatusEnabled =
72-
useExperiment(WORK_STATUS_EXPERIMENT_ID)?.enabled === true;
71+
const pullRequestsEnabled =
72+
useExperiment(RELATED_PULL_REQUESTS_EXPERIMENT_ID)?.enabled === true;
7373
const topBarTitle =
7474
breadcrumbs.find((breadcrumb) => breadcrumb.id === "chat-session")?.label ??
7575
breadcrumbs.find((breadcrumb) => breadcrumb.id === "skills")?.label ??
@@ -155,7 +155,7 @@ export function TopBar({
155155
) : null}
156156
</div>
157157
<div className="flex shrink-0 items-center gap-3 text-app-top-bar-control-fg [&_svg]:size-[length:var(--text-app-top-bar-icon)]">
158-
{workStatusEnabled ? <PullRequestsPopover /> : null}
158+
{pullRequestsEnabled ? <PullRequestsPopover /> : null}
159159
{viewActions}
160160
<BetaBadge />
161161

src/features/experiments/__tests__/ExperimentsSettings.test.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
STARTER_TASKS_EXPERIMENT_ID,
1414
TRANSCRIPT_VIRTUAL_RENDERER_EXPERIMENT_ID,
1515
VOICE_CONVERSATION_EXPERIMENT_ID,
16-
WORK_STATUS_EXPERIMENT_ID,
1716
type ExperimentDefinition,
1817
} from "../experimentDefinitions";
1918
import { ExperimentsSettings } from "../ExperimentsSettings";
@@ -130,26 +129,11 @@ describe("ExperimentsSettings", () => {
130129
).toBeInTheDocument();
131130
});
132131

133-
it("enables Work Status by default and lets users turn it off", async () => {
134-
vi.stubEnv("DEV", false);
135-
const user = userEvent.setup();
136-
renderWithProviders(<ExperimentsSettings />);
137-
138-
const toggle = screen.getByRole("switch", {
139-
name: i18n.t("experiments.workStatus.title", { ns: "settings" }),
140-
});
141-
expect(toggle).toBeChecked();
142-
143-
await user.click(toggle);
144-
expect(toggle).not.toBeChecked();
145-
});
146-
147132
it("registers only the currently supported experiments", () => {
148133
expect(EXPERIMENT_DEFINITIONS.map(({ id }) => id)).toEqual([
149134
BUILDERBOT_SURFACE_EXPERIMENT_ID,
150135
TRANSCRIPT_VIRTUAL_RENDERER_EXPERIMENT_ID,
151136
SKILL_DISCOVERY_EXPERIMENT_ID,
152-
WORK_STATUS_EXPERIMENT_ID,
153137
STARTER_TASKS_EXPERIMENT_ID,
154138
VOICE_CONVERSATION_EXPERIMENT_ID,
155139
CHAT_ON_CANVAS_EXPERIMENT_ID,

src/features/experiments/experimentDefinitions.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export const SKILL_DISCOVERY_EXPERIMENT_ID = "skill-discovery";
6767

6868
export const RELATED_PULL_REQUESTS_EXPERIMENT_ID = "related-pull-requests";
6969

70-
export const WORK_STATUS_EXPERIMENT_ID = "work-status";
7170
export const EXPERIMENT_DEFINITIONS = [
7271
{
7372
id: BUILDERBOT_SURFACE_EXPERIMENT_ID,
@@ -88,12 +87,6 @@ export const EXPERIMENT_DEFINITIONS = [
8887
// sq-agents CLI and can make remote catalog requests.
8988
defaultEnabled: false,
9089
},
91-
{
92-
id: WORK_STATUS_EXPERIMENT_ID,
93-
titleKey: "experiments.workStatus.title",
94-
descriptionKey: "experiments.workStatus.description",
95-
defaultEnabled: true,
96-
},
9790
{
9891
id: STARTER_TASKS_EXPERIMENT_ID,
9992
titleKey: "experiments.starterTasks.title",

src/features/work-status/PullRequestsPopover.tsx

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { GitPullRequest } from "lucide-react";
1111
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
1212
import { TopBarIconButton } from "@/shared/ui/top-bar-icon-button";
1313
import { PullRequestsPanel } from "./PullRequestsPanel";
14+
import { WorkStatusBridge } from "./WorkStatusBridge";
1415
import { useWorkStatusStore } from "./workStatusStore";
1516

1617
const DEFAULT_POPOVER_HEIGHT = 480;
@@ -114,48 +115,53 @@ export function PullRequestsPopover() {
114115
};
115116

116117
return (
117-
<Popover open={open} onOpenChange={handleOpenChange}>
118-
<PopoverTrigger asChild>
119-
<TopBarIconButton
120-
type="button"
121-
size="icon-top-bar"
122-
className="relative"
123-
aria-label={t("workStatus.topBarLabel", { count: pullRequestCount })}
124-
tooltip={t("workStatus.title")}
118+
<>
119+
<WorkStatusBridge active={open} />
120+
<Popover open={open} onOpenChange={handleOpenChange}>
121+
<PopoverTrigger asChild>
122+
<TopBarIconButton
123+
type="button"
124+
size="icon-top-bar"
125+
className="relative"
126+
aria-label={t("workStatus.topBarLabel", {
127+
count: pullRequestCount,
128+
})}
129+
tooltip={t("workStatus.title")}
130+
>
131+
<GitPullRequest aria-hidden />
132+
{pullRequestCount > 0 ? (
133+
<span
134+
aria-hidden="true"
135+
className="pointer-events-none absolute -right-1 -top-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-foreground px-1 font-medium text-[9px] leading-none text-background tabular-nums"
136+
>
137+
{formatCount(pullRequestCount)}
138+
</span>
139+
) : null}
140+
</TopBarIconButton>
141+
</PopoverTrigger>
142+
<PopoverContent
143+
align="end"
144+
sideOffset={8}
145+
style={{ height }}
146+
className="relative w-[min(460px,calc(100vw-2rem))] max-w-[calc(100vw-2rem)] overflow-visible border-0 bg-transparent p-0 shadow-none"
125147
>
126-
<GitPullRequest aria-hidden />
127-
{pullRequestCount > 0 ? (
128-
<span
129-
aria-hidden="true"
130-
className="pointer-events-none absolute -right-1 -top-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-foreground px-1 font-medium text-[9px] leading-none text-background tabular-nums"
131-
>
132-
{formatCount(pullRequestCount)}
133-
</span>
134-
) : null}
135-
</TopBarIconButton>
136-
</PopoverTrigger>
137-
<PopoverContent
138-
align="end"
139-
sideOffset={8}
140-
style={{ height }}
141-
className="relative w-[min(460px,calc(100vw-2rem))] max-w-[calc(100vw-2rem)] overflow-visible border-0 bg-transparent p-0 shadow-none"
142-
>
143-
<PullRequestsPanel />
144-
<hr
145-
tabIndex={0}
146-
aria-label={t("workStatus.resize")}
147-
aria-orientation="horizontal"
148-
aria-valuemin={minAvailableHeight()}
149-
aria-valuemax={maxAvailableHeight()}
150-
aria-valuenow={Math.round(height)}
151-
onKeyDown={handleResizeKeyDown}
152-
onPointerDown={handleResizePointerDown}
153-
onPointerMove={handleResizePointerMove}
154-
onPointerUp={handleResizePointerUp}
155-
onPointerCancel={handleResizePointerUp}
156-
className="absolute right-3 bottom-0 left-3 z-10 h-3 translate-y-1/2 cursor-ns-resize touch-none rounded-sm border-0 bg-transparent outline-none focus-visible:ring-2 focus-visible:ring-ring"
157-
/>
158-
</PopoverContent>
159-
</Popover>
148+
<PullRequestsPanel />
149+
<hr
150+
tabIndex={0}
151+
aria-label={t("workStatus.resize")}
152+
aria-orientation="horizontal"
153+
aria-valuemin={minAvailableHeight()}
154+
aria-valuemax={maxAvailableHeight()}
155+
aria-valuenow={Math.round(height)}
156+
onKeyDown={handleResizeKeyDown}
157+
onPointerDown={handleResizePointerDown}
158+
onPointerMove={handleResizePointerMove}
159+
onPointerUp={handleResizePointerUp}
160+
onPointerCancel={handleResizePointerUp}
161+
className="absolute right-3 bottom-0 left-3 z-10 h-3 translate-y-1/2 cursor-ns-resize touch-none rounded-sm border-0 bg-transparent outline-none focus-visible:ring-2 focus-visible:ring-ring"
162+
/>
163+
</PopoverContent>
164+
</Popover>
165+
</>
160166
);
161167
}

0 commit comments

Comments
 (0)