Support Supabase branches (#1394)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Adds Supabase database branch selection per app, with a new schema field
and UI to choose a branch after connecting a project. Resets branch when
changing or disconnecting the project to keep state consistent.
- **New Features**
- Added apps.supabase_branch_id column.
- Branch dropdown in SupabaseConnector shown after a project is
connected; selection persists and triggers app refresh.
- New state and hooks: supabaseBranchesAtom, loadBranches(projectId),
setAppBranch(branchId).
- IPC endpoints: supabase:list-branches and supabase:set-app-branch;
setting/unsetting project also clears the branch.
- **Migration**
- Apply drizzle migration 0013_supabase_branch.sql to add the
supabase_branch_id column (defaults to null).
<!-- End of auto-generated description by cubic. -->
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Adds Supabase database branch selection per app, including parent
project tracking, new IPC endpoints, UI dropdown, and an accompanying DB
migration with e2e tests.
>
> - **Database**:
> - Add `apps.supabase_parent_project_id` via migration
`drizzle/0015_complete_old_lace.sql`; snapshot and journal updated.
> - **IPC/Main**:
> - New `supabase:list-branches` handler and management client
`listSupabaseBranches` (real API + test stubs).
> - Update `supabase:set-app-project` to accept `{ projectId,
parentProjectId?, appId }`; unset clears both IDs.
> - `get-app` resolves `supabaseProjectName` using
`supabase_parent_project_id` when present.
> - **Types & Client**:
> - Add `SupabaseBranch`, `SetSupabaseAppProjectParams`, and
`App.supabaseParentProjectId`; expose `listSupabaseBranches` and updated
`setSupabaseAppProject` in `ipc_client` and preload whitelist.
> - **UI/Hooks**:
> - Supabase UI: branch dropdown in `SupabaseConnector` with
`loadBranches`, selection persists via updated `setAppProject`.
> - State: add `supabaseBranchesAtom`; `useSupabase` gets `branches`,
`loadBranches`, new param shape for `setAppProject`.
> - TokenBar/ChatInput: add `data-testid` for token bar and toggle.
> - **Supabase Context (tests)**:
> - Test build returns large context for `test-branch-project-id` to
validate branch selection.
> - **E2E Tests**:
> - Add `supabase_branch.spec.ts` and snapshot verifying branch
selection affects token usage.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
33054278db8396b4371ed6e8224105cb5684b7ac. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
This commit is contained in:
@@ -56,6 +56,8 @@ export function SupabaseConnector({ appId }: { appId: number }) {
|
||||
loading,
|
||||
error,
|
||||
loadProjects,
|
||||
branches,
|
||||
loadBranches,
|
||||
setAppProject,
|
||||
unsetAppProject,
|
||||
} = useSupabase();
|
||||
@@ -70,7 +72,7 @@ export function SupabaseConnector({ appId }: { appId: number }) {
|
||||
|
||||
const handleProjectSelect = async (projectId: string) => {
|
||||
try {
|
||||
await setAppProject(projectId, appId);
|
||||
await setAppProject({ projectId, appId });
|
||||
toast.success("Project connected to app successfully");
|
||||
await refreshApp();
|
||||
} catch (error) {
|
||||
@@ -78,6 +80,14 @@ export function SupabaseConnector({ appId }: { appId: number }) {
|
||||
}
|
||||
};
|
||||
|
||||
const projectIdForBranches =
|
||||
app?.supabaseParentProjectId || app?.supabaseProjectId;
|
||||
useEffect(() => {
|
||||
if (projectIdForBranches) {
|
||||
loadBranches(projectIdForBranches);
|
||||
}
|
||||
}, [projectIdForBranches, loadBranches]);
|
||||
|
||||
const handleUnsetProject = async () => {
|
||||
try {
|
||||
await unsetAppProject(appId);
|
||||
@@ -122,9 +132,56 @@ export function SupabaseConnector({ appId }: { appId: number }) {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button variant="destructive" onClick={handleUnsetProject}>
|
||||
Disconnect Project
|
||||
</Button>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="supabase-branch-select">Database Branch</Label>
|
||||
<Select
|
||||
value={app.supabaseProjectId || ""}
|
||||
onValueChange={async (supabaseBranchProjectId) => {
|
||||
try {
|
||||
const branch = branches.find(
|
||||
(b) => b.projectRef === supabaseBranchProjectId,
|
||||
);
|
||||
if (!branch) {
|
||||
throw new Error("Branch not found");
|
||||
}
|
||||
await setAppProject({
|
||||
projectId: branch.projectRef,
|
||||
parentProjectId: branch.parentProjectRef,
|
||||
appId,
|
||||
});
|
||||
toast.success("Branch selected");
|
||||
await refreshApp();
|
||||
} catch (error) {
|
||||
toast.error("Failed to set branch: " + error);
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="supabase-branch-select"
|
||||
data-testid="supabase-branch-select"
|
||||
>
|
||||
<SelectValue placeholder="Select a branch" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{branches.map((branch) => (
|
||||
<SelectItem
|
||||
key={branch.projectRef}
|
||||
value={branch.projectRef}
|
||||
>
|
||||
{branch.name}
|
||||
{branch.isDefault && " (Default)"}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Button variant="destructive" onClick={handleUnsetProject}>
|
||||
Disconnect Project
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -351,6 +351,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
||||
showTokenBar ? "text-purple-500 bg-purple-100" : ""
|
||||
}`}
|
||||
size="sm"
|
||||
data-testid="token-bar-toggle"
|
||||
>
|
||||
<ChartColumnIncreasing size={14} />
|
||||
</Button>
|
||||
|
||||
@@ -67,7 +67,7 @@ export function TokenBar({ chatId }: TokenBarProps) {
|
||||
const inputPercent = (inputTokens / contextWindow) * 100;
|
||||
|
||||
return (
|
||||
<div className="px-4 pb-2 text-xs">
|
||||
<div className="px-4 pb-2 text-xs" data-testid="token-bar">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
Reference in New Issue
Block a user