Feat/open projects config to any (#681)

* feat(server): derive owner/repo display name for any remote host

Generalize deriveProjectGroupingName so any remote:<host>/<segments...>
project key returns the last two path segments (owner/repo) instead of
just the trailing segment. Path-fallback for non-remote keys is unchanged.

Brings GitLab, Gitea, Bitbucket, and self-hosted remotes to parity with
the prior github.com-only behavior — no separate special-case needed.

* feat(app): show projects from any git remote in Projects settings

Remove the isSupportedProjectKey filter so workspaces with non-GitHub
remotes (GitLab, Gitea, Bitbucket, self-hosted, ssh-style) appear in the
Projects list and route to the project settings screen. The daemon RPCs,
config schema, and registry were already host-agnostic; this lifts a
client-side filter that hid them.

Also remove the now-vestigial hiddenUnsupportedRemoteCount field from
ProjectSummary, BuildProjectsResult, and UseProjectsResult — once the
filter is gone, the count is always zero and the field is dead state.
This is an internal app-package type, not a wire schema, so deletion is
safe.

* chore(app): simplify Projects empty state to "No projects yet"

Drop the "Non-GitHub remote projects aren't supported yet" empty-state
branch — it can no longer fire now that any git remote is shown. The
empty state is unconditional now.

* test(app): cover non-GitHub remote in projects-settings e2e

Add a fixture that creates a temp git repo with origin pointing at a
gitlab.com URL and exercises the same paseo.json read/edit/save flow
already covered for local repos. Verifies the project surfaces with the
"acme/app" display name and that the round-trip persists correctly.

Extracted the remote-setup branch in createTempGitRepo into a small
configureRemote helper to keep the main function under the cyclomatic
complexity limit.

---------

Co-authored-by: Mathias Kurz <mkurz@stamus-networks.com>
This commit is contained in:
Mathias Kurz
2026-05-05 19:56:51 +07:00
committed by Mohamed Boudra
co-authored by Mathias Kurz
parent 2b6d7d4ec2
commit 44d13919d7
11 changed files with 171 additions and 78 deletions
@@ -66,7 +66,6 @@ const { theme, projectsState, hostState, navigate, confirmDialogMock, client } =
current: {
projects: [],
hostErrors: [] as ProjectHostError[],
hiddenUnsupportedRemoteCount: 0,
isLoading: false,
isFetching: false,
refetch: vi.fn(),
@@ -442,7 +441,6 @@ function project(overrides: Partial<ProjectSummary> = {}): ProjectSummary {
hostCount: hosts.length,
onlineHostCount: hosts.filter((host) => host.isOnline).length,
githubUrl: "https://github.com/acme/app",
hiddenUnsupportedRemoteCount: 0,
...overrides,
};
}
@@ -451,7 +449,6 @@ function setProjectsState(overrides: Partial<UseProjectsResult>) {
projectsState.current = {
projects: [],
hostErrors: [],
hiddenUnsupportedRemoteCount: 0,
isLoading: false,
isFetching: false,
refetch: vi.fn(),
@@ -32,7 +32,6 @@ const { theme, projectsState, navigate } = vi.hoisted(() => ({
current: {
projects: [],
hostErrors: [],
hiddenUnsupportedRemoteCount: 0,
isLoading: false,
isFetching: false,
refetch: vi.fn(),
@@ -231,7 +230,6 @@ function project(overrides: Partial<ProjectSummary> = {}): ProjectSummary {
hostCount: hosts.length,
onlineHostCount,
githubUrl: "https://github.com/acme/app",
hiddenUnsupportedRemoteCount: 0,
...overrides,
};
}
@@ -240,7 +238,6 @@ function setProjectsState(overrides: Partial<UseProjectsResult>) {
projectsState.current = {
projects: [],
hostErrors: [],
hiddenUnsupportedRemoteCount: 0,
isLoading: false,
isFetching: false,
refetch: vi.fn(),
@@ -343,19 +340,12 @@ describe("ProjectsScreen", () => {
});
it("renders the empty state when there are no projects", () => {
setProjectsState({ projects: [], hiddenUnsupportedRemoteCount: 0 });
setProjectsState({ projects: [] });
render({ kind: "projects" });
expect(container?.textContent).toContain("No projects yet");
});
it("renders the unsupported empty state when only non-GitHub remotes were filtered", () => {
setProjectsState({ projects: [], hiddenUnsupportedRemoteCount: 3 });
render({ kind: "projects" });
expect(container?.textContent).toContain("Non-GitHub remote projects aren't supported yet");
expect(container?.textContent).not.toContain("Non-GitHub remote projects aren't supported yet");
});
it("renders a partial-host-failure banner above the list, naming each failed host", () => {
+2 -6
View File
@@ -15,7 +15,7 @@ interface ProjectsScreenProps {
}
export default function ProjectsScreen({ view }: ProjectsScreenProps) {
const { projects, hostErrors, hiddenUnsupportedRemoteCount, isLoading } = useProjects();
const { projects, hostErrors, isLoading } = useProjects();
const selectedProjectKey = view.kind === "project" ? view.projectKey : null;
if (isLoading && projects.length === 0) {
@@ -27,13 +27,9 @@ export default function ProjectsScreen({ view }: ProjectsScreenProps) {
}
if (projects.length === 0) {
const message =
hiddenUnsupportedRemoteCount > 0
? "Non-GitHub remote projects aren't supported yet"
: "No projects yet";
return (
<View style={styles.centered} testID="projects-list">
<Text style={styles.emptyText}>{message}</Text>
<Text style={styles.emptyText}>No projects yet</Text>
</View>
);
}