Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d0e6ad1
fix(dashboard): repair GitHub config link-existing flow
BilalG1 May 19, 2026
65789a1
fix(dashboard): use npx instead of pnpx in config sync workflow
BilalG1 May 19, 2026
ed25eab
feat(dashboard): improve local CLI step in link-existing flow
BilalG1 May 19, 2026
ebb090e
chore(dashboard): trim helper text on local CLI link step
BilalG1 May 19, 2026
55ff7e3
feat(stack-cli): fall back to STACK_PROJECT_ID env var for project id
BilalG1 May 19, 2026
de9ec19
fix(dashboard): avoid flashing GitHub providerAccountId in account dr…
BilalG1 May 19, 2026
7550eaa
feat(dashboard): searchable combobox for repo + branch in link flow
BilalG1 May 19, 2026
2faffb6
refactor(dashboard): drop unused useTransition around onboarding stat…
BilalG1 May 19, 2026
5ce1b6b
refactor(dashboard): race-safe loadRepositories and simpler combobox API
BilalG1 May 19, 2026
34db0d5
Merge branch 'dev' into fix/github-config-link-flow
BilalG1 May 19, 2026
03e8a5e
feat(dashboard): scope repo search, surface rate limits, branch refresh
BilalG1 May 19, 2026
08c8356
chore(dashboard): bump generated workflow to actions/{checkout,setup-…
BilalG1 May 19, 2026
08bbba5
Merge remote-tracking branch 'origin/dev' into fix/github-config-link…
BilalG1 May 19, 2026
cdf4c68
fix(dashboard): throw if config path normalizes to empty in workflow …
BilalG1 May 19, 2026
b6783b2
fix(dashboard): escape project.id via JSON.stringify in copy-paste CL…
BilalG1 May 19, 2026
815560c
Merge branch 'dev' into fix/github-config-link-flow
BilalG1 May 19, 2026
c80c89f
fix(dashboard): throw on invalid matching-refs response
BilalG1 May 20, 2026
22e9f63
Merge branch 'dev' into fix/github-config-link-flow
BilalG1 May 20, 2026
4d415a5
Merge branch 'dev' into fix/github-config-link-flow
BilalG1 May 20, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { PlusCircleIcon } from "@phosphor-icons/react";
import { AdminOwnedProject, useStackApp } from "@stackframe/stack";
import { runAsynchronouslyWithAlert, wait } from "@stackframe/stack-shared/dist/utils/promises";
import { useSearchParams } from "next/navigation";
import { Suspense, useCallback, useEffect, useMemo, useRef, useState, useTransition } from "react";
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";

import type { ProjectOnboardingStatus } from "@stackframe/stack-shared/dist/schema-fields";
import { ProjectOnboardingWizard } from "./project-onboarding-wizard";
Expand Down Expand Up @@ -75,7 +75,6 @@ function PageClientInner() {
const [projectStatuses, setProjectStatuses] = useState<Map<string, ProjectOnboardingStatus>>(new Map());
const [projectOnboardingStates, setProjectOnboardingStates] = useState<Map<string, ProjectOnboardingState | null>>(new Map());
const [loadingStatuses, setLoadingStatuses] = useState(true);
const [, startStatusTransition] = useTransition();
const [projectName, setProjectName] = useState(displayNameFromSearch ?? "");
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
const [creatingTeam, setCreatingTeam] = useState(false);
Expand Down Expand Up @@ -217,12 +216,10 @@ function PageClientInner() {
throw new Error(`Failed to update onboarding status: ${response.status} ${await response.text()}`);
}

startStatusTransition(() => {
setProjectStatuses((previous) => {
const next = new Map(previous);
next.set(project.id, status);
return next;
});
setProjectStatuses((previous) => {
const next = new Map(previous);
next.set(project.id, status);
return next;
});

await appInternals.refreshOwnedProjects();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"use client";

import { Spinner, Typography, cn } from "@/components/ui";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import { useState } from "react";

export type ComboboxItem = {
value: string,
label: string,
description?: string,
};

type Props = {
value: string,
items: ComboboxItem[],
onSelect: (value: string) => void,
query: string,
onQueryChange: (query: string) => void,
triggerPlaceholder?: string,
inputPlaceholder?: string,
emptyMessage?: string,
loading?: boolean,
disabled?: boolean,
};

export function RemoteSearchCombobox(props: Props) {
const [open, setOpen] = useState(false);
const selectedLabel = props.items.find((item) => item.value === props.value)?.label ?? props.value;

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
type="button"
disabled={props.disabled}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-xl",
"border border-black/[0.08] bg-white/80 px-3 py-2 text-sm shadow-sm ring-1 ring-black/[0.08]",
"transition-all duration-150 hover:transition-none hover:ring-black/[0.12]",
"focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500/30",
"disabled:cursor-not-allowed disabled:opacity-50",
"dark:border-white/[0.06] dark:bg-background/60 dark:ring-white/[0.06] dark:hover:ring-white/[0.1]",
)}
>
<span className={cn("truncate text-left", selectedLabel.length === 0 && "text-muted-foreground")}>
{selectedLabel.length > 0 ? selectedLabel : (props.triggerPlaceholder ?? "Select")}
</span>
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-60" />
</button>
</PopoverTrigger>
<PopoverContent
className="w-[--radix-popover-trigger-width] p-0"
align="start"
>
<Command shouldFilter={false}>
<CommandInput
placeholder={props.inputPlaceholder ?? "Search..."}
value={props.query}
onValueChange={props.onQueryChange}
/>
<CommandList>
{props.loading && (
<div className="flex items-center gap-2 px-3 py-3">
<Spinner size={14} />
<Typography variant="secondary" className="text-sm">Searching...</Typography>
</div>
)}
{!props.loading && props.items.length === 0 && (
<CommandEmpty>{props.emptyMessage ?? "No results."}</CommandEmpty>
)}
{props.items.length > 0 && (
<CommandGroup>
{props.items.map((item) => (
<CommandItem
key={item.value}
value={item.value}
onSelect={() => {
props.onSelect(item.value);
setOpen(false);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4 shrink-0",
props.value === item.value ? "opacity-100" : "opacity-0",
)}
/>
<div className="min-w-0 flex-1">
<div className="truncate text-sm">{item.label}</div>
{item.description != null && (
<div className="truncate text-xs text-muted-foreground">{item.description}</div>
)}
</div>
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ function encodeYamlScalar(value: string): string {
return JSON.stringify(value);
}

// GitHub Actions `on.push.paths` filters are repo-relative and do not match a
// leading `./`. Config-path suggestions and manual input may include one, so
// strip it to keep the push trigger (and the checked-out file path) canonical.
export function normalizeConfigPath(configPath: string): string {
return configPath.trim().replace(/^(?:\.\/)+/, "");
}

export function buildWorkflowYaml(branch: string, configPath: string): string {
const encodedBranch = encodeYamlScalar(branch);
const encodedConfigPath = encodeYamlScalar(configPath);
const normalizedConfigPath = normalizeConfigPath(configPath);
if (normalizedConfigPath.length === 0) {
throw new Error("Expected a non-empty config path after normalization (input must not be blank or only './').");
}
const encodedConfigPath = encodeYamlScalar(normalizedConfigPath);
const encodedWorkflowPath = encodeYamlScalar(WORKFLOW_FILE_PATH);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return `name: Stack Auth Config Sync
Expand All @@ -30,12 +41,16 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "20"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Push Stack Auth config
env:
STACK_PROJECT_ID: \${{ secrets.${GITHUB_PROJECT_ID_SECRET_NAME} }}
STACK_SECRET_SERVER_KEY: \${{ secrets.${GITHUB_SECRET_SERVER_KEY_SECRET_NAME} }}
STACK_AUTH_CONFIG_PATH: ${encodedConfigPath}
run: pnpx @stackframe/stack-cli@latest config push --config-file "$STACK_AUTH_CONFIG_PATH"
run: npx --yes @stackframe/stack-cli@latest config push --config-file "$STACK_AUTH_CONFIG_PATH"
`;
}
Loading
Loading