fix: derive repository UI from registry
This commit is contained in:
parent
2f0d1cee19
commit
14e63ca5b6
|
|
@ -1,4 +1,8 @@
|
||||||
use std::{collections::BTreeSet, path::PathBuf, process::Command};
|
use std::{
|
||||||
|
collections::BTreeSet,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -336,15 +340,35 @@ fn parse_git_log(raw: &str) -> Vec<GitCommitSummary> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sanitize_remote_url(url: &str) -> String {
|
fn sanitize_remote_url(url: &str) -> String {
|
||||||
let Some((scheme, rest)) = url.split_once("://") else {
|
let trimmed = url.trim();
|
||||||
return url.to_string();
|
if is_local_path_like(trimmed) {
|
||||||
|
return "<redacted-local-path>".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((scheme, rest)) = trimmed.split_once("://") else {
|
||||||
|
return trimmed.to_string();
|
||||||
};
|
};
|
||||||
|
if scheme.eq_ignore_ascii_case("file") {
|
||||||
|
return "file://<redacted-local-path>".to_string();
|
||||||
|
}
|
||||||
let Some((_credentials, host_path)) = rest.split_once('@') else {
|
let Some((_credentials, host_path)) = rest.split_once('@') else {
|
||||||
return url.to_string();
|
return trimmed.to_string();
|
||||||
};
|
};
|
||||||
format!("{scheme}://<redacted>@{host_path}")
|
format!("{scheme}://<redacted>@{host_path}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_local_path_like(value: &str) -> bool {
|
||||||
|
Path::new(value).is_absolute() || is_windows_absolute_path_like(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_windows_absolute_path_like(value: &str) -> bool {
|
||||||
|
let bytes = value.as_bytes();
|
||||||
|
bytes.len() >= 3
|
||||||
|
&& bytes[0].is_ascii_alphabetic()
|
||||||
|
&& bytes[1] == b':'
|
||||||
|
&& matches!(bytes[2], b'\\' | b'/')
|
||||||
|
}
|
||||||
|
|
||||||
fn non_empty_string(value: &str) -> Option<String> {
|
fn non_empty_string(value: &str) -> Option<String> {
|
||||||
if value.is_empty() {
|
if value.is_empty() {
|
||||||
None
|
None
|
||||||
|
|
@ -358,7 +382,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sanitizes_remote_credentials() {
|
fn sanitizes_remote_credentials_and_local_paths() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sanitize_remote_url("https://user:token@example.com/org/repo.git"),
|
sanitize_remote_url("https://user:token@example.com/org/repo.git"),
|
||||||
"https://<redacted>@example.com/org/repo.git"
|
"https://<redacted>@example.com/org/repo.git"
|
||||||
|
|
@ -367,6 +391,26 @@ mod tests {
|
||||||
sanitize_remote_url("git@example.com:org/repo.git"),
|
sanitize_remote_url("git@example.com:org/repo.git"),
|
||||||
"git@example.com:org/repo.git"
|
"git@example.com:org/repo.git"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sanitize_remote_url("/home/alice/private/repo.git"),
|
||||||
|
"<redacted-local-path>"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sanitize_remote_url("/Users/alice/private/repo.git"),
|
||||||
|
"<redacted-local-path>"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sanitize_remote_url("C:\\Users\\alice\\private\\repo.git"),
|
||||||
|
"<redacted-local-path>"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sanitize_remote_url("file:///home/alice/private/repo.git"),
|
||||||
|
"file://<redacted-local-path>"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sanitize_remote_url("file://localhost/home/alice/private/repo.git"),
|
||||||
|
"file://<redacted-local-path>"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"dev": "deno run -A npm:vite@7.2.7 dev",
|
"dev": "deno run -A npm:vite@7.2.7 dev",
|
||||||
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server -- serve --workspace . --db .yoi/workspace.db --listen 127.0.0.1:8787",
|
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server -- serve --workspace . --db .yoi/workspace.db --listen 127.0.0.1:8787",
|
||||||
"check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json",
|
"check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json",
|
||||||
"test": "deno test --allow-read=src src/lib/workspace-console/model.test.ts src/lib/workspace-console/worker-console.ui.test.ts src/lib/workspace-settings/model.test.ts src/lib/workspace-sidebar/worker-launch.test.ts",
|
"test": "deno test --allow-read=src src/lib/workspace-console/model.test.ts src/lib/workspace-console/worker-console.ui.test.ts src/lib/workspace-settings/model.test.ts src/lib/workspace-sidebar/worker-launch.test.ts src/lib/workspace-sidebar/repository-nav.test.ts",
|
||||||
"build": "deno run -A npm:vite@7.2.7 build",
|
"build": "deno run -A npm:vite@7.2.7 build",
|
||||||
"preview": "deno run -A npm:vite@7.2.7 preview"
|
"preview": "deno run -A npm:vite@7.2.7 preview"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
ObjectiveDetail,
|
ObjectiveDetail,
|
||||||
ObjectiveListResponse,
|
ObjectiveListResponse,
|
||||||
RepositoryDetailResponse,
|
RepositoryDetailResponse,
|
||||||
|
RepositoryListResponse,
|
||||||
RepositorySummary,
|
RepositorySummary,
|
||||||
RepositoryTicketsResponse,
|
RepositoryTicketsResponse,
|
||||||
Worker,
|
Worker,
|
||||||
|
|
@ -25,12 +26,13 @@
|
||||||
let {
|
let {
|
||||||
view = 'overview',
|
view = 'overview',
|
||||||
objectiveId = null,
|
objectiveId = null,
|
||||||
repositoryId = 'main'
|
repositoryId = null
|
||||||
}: { view?: WorkspaceView; repositoryId?: string; objectiveId?: string | null } = $props();
|
}: { view?: WorkspaceView; repositoryId?: string | null; objectiveId?: string | null } = $props();
|
||||||
|
|
||||||
let workspace = $state<WorkspaceResponse | null>(null);
|
let workspace = $state<WorkspaceResponse | null>(null);
|
||||||
let hosts = $state<ListResponse<Host> | null>(null);
|
let hosts = $state<ListResponse<Host> | null>(null);
|
||||||
let workers = $state<ListResponse<Worker> | null>(null);
|
let workers = $state<ListResponse<Worker> | null>(null);
|
||||||
|
let repositories = $state<RepositoryListResponse | null>(null);
|
||||||
let repository = $state<RepositorySummary | null>(null);
|
let repository = $state<RepositorySummary | null>(null);
|
||||||
let repositoryTickets = $state<RepositoryTicketsResponse | null>(null);
|
let repositoryTickets = $state<RepositoryTicketsResponse | null>(null);
|
||||||
let objectives = $state<ObjectiveListResponse | null>(null);
|
let objectives = $state<ObjectiveListResponse | null>(null);
|
||||||
|
|
@ -39,6 +41,7 @@
|
||||||
let workspaceError = $state<string | null>(null);
|
let workspaceError = $state<string | null>(null);
|
||||||
let hostsError = $state<string | null>(null);
|
let hostsError = $state<string | null>(null);
|
||||||
let workersError = $state<string | null>(null);
|
let workersError = $state<string | null>(null);
|
||||||
|
let repositoriesError = $state<string | null>(null);
|
||||||
let repositoryError = $state<string | null>(null);
|
let repositoryError = $state<string | null>(null);
|
||||||
let repositoryTicketsError = $state<string | null>(null);
|
let repositoryTicketsError = $state<string | null>(null);
|
||||||
let objectivesError = $state<string | null>(null);
|
let objectivesError = $state<string | null>(null);
|
||||||
|
|
@ -86,12 +89,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadRepository() {
|
async function loadRepositories() {
|
||||||
|
repositoriesError = null;
|
||||||
|
try {
|
||||||
|
repositories = await getJson<RepositoryListResponse>('/api/repositories');
|
||||||
|
} catch (error) {
|
||||||
|
repositoriesError = error instanceof Error ? error.message : String(error);
|
||||||
|
repositories = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRepository(id: string) {
|
||||||
repositoryError = null;
|
repositoryError = null;
|
||||||
const selectedRepositoryId = route.page === 'repository' ? route.repositoryId : repositoryId;
|
|
||||||
try {
|
try {
|
||||||
const detail = await getJson<RepositoryDetailResponse>(
|
const detail = await getJson<RepositoryDetailResponse>(
|
||||||
`/api/repositories/${encodeURIComponent(selectedRepositoryId)}`
|
`/api/repositories/${encodeURIComponent(id)}`
|
||||||
);
|
);
|
||||||
repository = detail.item;
|
repository = detail.item;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -100,12 +112,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadRepositoryTickets() {
|
async function loadRepositoryTickets(id: string) {
|
||||||
repositoryTicketsError = null;
|
repositoryTicketsError = null;
|
||||||
const selectedRepositoryId = route.page === 'repository' ? route.repositoryId : repositoryId;
|
|
||||||
try {
|
try {
|
||||||
repositoryTickets = await getJson<RepositoryTicketsResponse>(
|
repositoryTickets = await getJson<RepositoryTicketsResponse>(
|
||||||
`/api/repositories/${encodeURIComponent(selectedRepositoryId)}/tickets`
|
`/api/repositories/${encodeURIComponent(id)}/tickets`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
repositoryTicketsError = error instanceof Error ? error.message : String(error);
|
repositoryTicketsError = error instanceof Error ? error.message : String(error);
|
||||||
|
|
@ -147,9 +158,9 @@
|
||||||
function routeFromView(
|
function routeFromView(
|
||||||
view: WorkspaceView,
|
view: WorkspaceView,
|
||||||
objectiveId: string | null,
|
objectiveId: string | null,
|
||||||
repositoryId: string
|
repositoryId: string | null
|
||||||
): RouteState {
|
): RouteState {
|
||||||
if (view === 'repository') {
|
if (view === 'repository' && repositoryId) {
|
||||||
return { page: 'repository', repositoryId };
|
return { page: 'repository', repositoryId };
|
||||||
}
|
}
|
||||||
if (view === 'objective' && objectiveId) {
|
if (view === 'objective' && objectiveId) {
|
||||||
|
|
@ -182,11 +193,22 @@
|
||||||
void loadWorkspace();
|
void loadWorkspace();
|
||||||
void loadHosts();
|
void loadHosts();
|
||||||
void loadWorkers();
|
void loadWorkers();
|
||||||
void loadRepository();
|
void loadRepositories();
|
||||||
void loadRepositoryTickets();
|
|
||||||
void loadObjectives();
|
void loadObjectives();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (route.page === 'repository') {
|
||||||
|
void loadRepository(route.repositoryId);
|
||||||
|
void loadRepositoryTickets(route.repositoryId);
|
||||||
|
} else {
|
||||||
|
repository = null;
|
||||||
|
repositoryTickets = null;
|
||||||
|
repositoryError = null;
|
||||||
|
repositoryTicketsError = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const selectedObjectiveId = route.page === 'objective' ? route.objectiveId : null;
|
const selectedObjectiveId = route.page === 'objective' ? route.objectiveId : null;
|
||||||
if (selectedObjectiveId) {
|
if (selectedObjectiveId) {
|
||||||
|
|
@ -210,7 +232,7 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="workspace-layout">
|
<div class="workspace-layout">
|
||||||
<WorkspaceSidebar {workspace} {workspaceError} {currentPath} />
|
<WorkspaceSidebar {workspace} {workspaceError} {repositories} {repositoriesError} {currentPath} />
|
||||||
|
|
||||||
<main class="shell">
|
<main class="shell">
|
||||||
{#if route.page === 'repository'}
|
{#if route.page === 'repository'}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,46 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { WorkspaceResponse } from './types';
|
import { projectRepositoryNav } from './repository-nav';
|
||||||
|
import type { RepositoryListResponse } from './types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspace: WorkspaceResponse | null;
|
repositories: RepositoryListResponse | null;
|
||||||
|
repositoriesError?: string | null;
|
||||||
currentPath?: string;
|
currentPath?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let { workspace, currentPath = '/' }: Props = $props();
|
let { repositories, repositoriesError = null, currentPath = '/' }: Props = $props();
|
||||||
|
let navigation = $derived(projectRepositoryNav(repositories, currentPath));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="nav-section" aria-labelledby="repositories-heading">
|
<section class="nav-section" aria-labelledby="repositories-heading">
|
||||||
<div class="section-heading-row">
|
<div class="section-heading-row">
|
||||||
<h2 id="repositories-heading">repositories</h2>
|
<h2 id="repositories-heading">repositories</h2>
|
||||||
<span class="section-count">1</span>
|
<span class="section-count">{navigation.count}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if repositoriesError}
|
||||||
|
<p class="nav-empty error">Repository registry unavailable.</p>
|
||||||
|
{:else if !repositories}
|
||||||
|
<p class="nav-empty">Loading repositories…</p>
|
||||||
|
{:else if navigation.items.length === 0}
|
||||||
|
<p class="nav-empty">No repositories configured.</p>
|
||||||
|
{#if navigation.diagnostics.length > 0}
|
||||||
|
<ul class="diagnostics" aria-label="Repository diagnostics">
|
||||||
|
{#each navigation.diagnostics as diagnostic}
|
||||||
|
<li><code>{diagnostic.code}</code>: {diagnostic.message}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
<ul class="nav-list" aria-label="Repositories">
|
<ul class="nav-list" aria-label="Repositories">
|
||||||
|
{#each navigation.items as item (item.id)}
|
||||||
<li>
|
<li>
|
||||||
<a class="nav-item" class:active={currentPath.startsWith('/repositories')} href="/repositories/main">
|
<a class="nav-item" class:active={item.active} href={item.href} aria-current={item.active ? 'page' : undefined}>
|
||||||
<span class="item-title">main</span>
|
<span class="item-title">{item.title}</span>
|
||||||
<span class="item-meta">configured repository · read-only</span>
|
<span class="item-meta">{item.meta}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,23 @@
|
||||||
import ObjectivesNavSection from './ObjectivesNavSection.svelte';
|
import ObjectivesNavSection from './ObjectivesNavSection.svelte';
|
||||||
import RepositoriesNavSection from './RepositoriesNavSection.svelte';
|
import RepositoriesNavSection from './RepositoriesNavSection.svelte';
|
||||||
import WorkersNavSection from './WorkersNavSection.svelte';
|
import WorkersNavSection from './WorkersNavSection.svelte';
|
||||||
import type { WorkspaceResponse } from './types';
|
import type { RepositoryListResponse, WorkspaceResponse } from './types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspace: WorkspaceResponse | null;
|
workspace: WorkspaceResponse | null;
|
||||||
workspaceError?: string | null;
|
workspaceError?: string | null;
|
||||||
|
repositories?: RepositoryListResponse | null;
|
||||||
|
repositoriesError?: string | null;
|
||||||
currentPath?: string;
|
currentPath?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let { workspace, workspaceError = null, currentPath = '/' }: Props = $props();
|
let {
|
||||||
|
workspace,
|
||||||
|
workspaceError = null,
|
||||||
|
repositories = null,
|
||||||
|
repositoriesError = null,
|
||||||
|
currentPath = '/'
|
||||||
|
}: Props = $props();
|
||||||
let settingsActive = $derived(currentPath.startsWith("/settings"));
|
let settingsActive = $derived(currentPath.startsWith("/settings"));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -43,7 +51,7 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<nav class="sidebar-sections" aria-label="Workspace sections">
|
<nav class="sidebar-sections" aria-label="Workspace sections">
|
||||||
<RepositoriesNavSection {workspace} {currentPath} />
|
<RepositoriesNavSection {repositories} {repositoriesError} {currentPath} />
|
||||||
<ObjectivesNavSection {currentPath} />
|
<ObjectivesNavSection {currentPath} />
|
||||||
<WorkersNavSection {currentPath} />
|
<WorkersNavSection {currentPath} />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { projectRepositoryNav } from "./repository-nav.ts";
|
||||||
|
import type { RepositoryListResponse } from "./types.ts";
|
||||||
|
|
||||||
|
declare const Deno: {
|
||||||
|
test(name: string, fn: () => void): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function assertEquals<T>(actual: T, expected: T): void {
|
||||||
|
const actualJson = JSON.stringify(actual);
|
||||||
|
const expectedJson = JSON.stringify(expected);
|
||||||
|
if (actualJson !== expectedJson) {
|
||||||
|
throw new Error(`Expected ${expectedJson}, got ${actualJson}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function repositories(
|
||||||
|
items: RepositoryListResponse["items"],
|
||||||
|
): RepositoryListResponse {
|
||||||
|
return {
|
||||||
|
workspace_id: "workspace-1",
|
||||||
|
items,
|
||||||
|
source: "workspace_backend_config",
|
||||||
|
diagnostics: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test("repository nav does not invent main for an empty registry", () => {
|
||||||
|
const projection = projectRepositoryNav({
|
||||||
|
workspace_id: "workspace-1",
|
||||||
|
items: [],
|
||||||
|
source: "workspace_backend_config",
|
||||||
|
diagnostics: [
|
||||||
|
{
|
||||||
|
code: "repository_config_empty",
|
||||||
|
severity: "warning",
|
||||||
|
message: "No repositories configured",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(projection.count, 0);
|
||||||
|
assertEquals(projection.items, []);
|
||||||
|
assertEquals(projection.diagnostics[0].code, "repository_config_empty");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("repository nav links configured non-main repository ids", () => {
|
||||||
|
const projection = projectRepositoryNav(
|
||||||
|
repositories([
|
||||||
|
{
|
||||||
|
id: "infra",
|
||||||
|
display_name: "Infrastructure",
|
||||||
|
kind: "git",
|
||||||
|
provider: "git",
|
||||||
|
record_authority: "workspace-backend-config",
|
||||||
|
git: null,
|
||||||
|
diagnostics: [],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
"/repositories/infra",
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(projection.count, 1);
|
||||||
|
assertEquals(projection.items[0], {
|
||||||
|
id: "infra",
|
||||||
|
title: "Infrastructure",
|
||||||
|
href: "/repositories/infra",
|
||||||
|
meta: "git repository · read-only",
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
36
web/workspace/src/lib/workspace-sidebar/repository-nav.ts
Normal file
36
web/workspace/src/lib/workspace-sidebar/repository-nav.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import type { Diagnostic, RepositoryListResponse } from "./types";
|
||||||
|
|
||||||
|
export type RepositoryNavItem = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
href: string;
|
||||||
|
meta: string;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RepositoryNavProjection = {
|
||||||
|
count: number;
|
||||||
|
items: RepositoryNavItem[];
|
||||||
|
diagnostics: Diagnostic[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function projectRepositoryNav(
|
||||||
|
repositories: RepositoryListResponse | null,
|
||||||
|
currentPath = "/",
|
||||||
|
): RepositoryNavProjection {
|
||||||
|
const summaries = repositories?.items ?? [];
|
||||||
|
return {
|
||||||
|
count: summaries.length,
|
||||||
|
diagnostics: repositories?.diagnostics ?? [],
|
||||||
|
items: summaries.map((repository) => {
|
||||||
|
const href = `/repositories/${encodeURIComponent(repository.id)}`;
|
||||||
|
return {
|
||||||
|
id: repository.id,
|
||||||
|
title: repository.display_name || repository.id,
|
||||||
|
href,
|
||||||
|
meta: `${repository.provider} repository · read-only`,
|
||||||
|
active: currentPath === href || currentPath.startsWith(`${href}/`),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -212,6 +212,13 @@ export type GitCommitSummary = {
|
||||||
refs: string[];
|
refs: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RepositoryListResponse = {
|
||||||
|
workspace_id: string;
|
||||||
|
items: RepositorySummary[];
|
||||||
|
source: string;
|
||||||
|
diagnostics: Diagnostic[];
|
||||||
|
};
|
||||||
|
|
||||||
export type RepositoryDetailResponse = {
|
export type RepositoryDetailResponse = {
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
item: RepositorySummary;
|
item: RepositorySummary;
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
import WorkspacePage from '$lib/workspace-pages/WorkspacePage.svelte';
|
import WorkspacePage from '$lib/workspace-pages/WorkspacePage.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WorkspacePage view="repository" repositoryId={page.params.repositoryId ?? 'main'} />
|
<WorkspacePage view="repository" repositoryId={page.params.repositoryId} />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user