Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy
| 101 | 101 | and Message, and memory at two levels (project and workspace) that | |
| 102 | 102 | agents write and read. | |
| 103 | 103 | - Projects with deployments on g1t.page: a preview for every pull request, | |
| 104 | − | production on merge, dependencies between projects, custom domains. | |
| 104 | + | production on merge, custom domains. | |
| 105 | 105 | - GitHub Actions workflows from `.g1t/workflows`, secrets and variables, | |
| 106 | 106 | webhooks and integrations (Sentry, Datadog, Jira, Linear). | |
| 107 | 107 | - Profiles, workspaces with display names, icons and renameable slugs. | |
| ⋯ | |||
| 148 | 148 | | `services/integrations` | Model providers, alerts, trackers and the GitHub App. | Rust | | |
| 149 | 149 | | `services/webhooks` | Webhook deliveries. | Rust | | |
| 150 | 150 | | `services/runner` | Starts sandboxes: for g1t agents, workflow jobs and the merge queue. | TypeScript | | |
| 151 | − | | `services/projects` | Projects and the dependencies between them. | TypeScript | | |
| 151 | + | | `services/projects` | Projects: what a workspace builds and runs, and where its code lives. | TypeScript | | |
| 152 | 152 | | `services/deployments` | Builds, previews and production on `g1t.page`. | TypeScript | | |
| 153 | 153 | | `services/pages` | Serves every app deployed on `g1t.page`, and custom domains. | TypeScript | | |
| 154 | 154 | | `services/models` | The model proxy at `models.g1t.sh`. | TypeScript | | |
| 4478 | 4478 | "kind": "project", | |
| 4479 | 4479 | "id": "ent_9b1d6f0a2c3e4b5d6e7f8a9b", | |
| 4480 | 4480 | "title": "web", | |
| 4481 | − | "snippet": "The storefront. Written in TypeScript. Packages: @acme/web. Uses api. Owned by ana.", | |
| 4481 | + | "snippet": "The storefront. Written in TypeScript. Packages: @acme/web. Owned by ana.", | |
| 4482 | 4482 | "project": "web", | |
| 4483 | 4483 | "url": "/acme/web", | |
| 4484 | 4484 | "score": 0.71, | |
| ⋯ | |||
| 4503 | 4503 | "kind": "project", | |
| 4504 | 4504 | "key": "web", | |
| 4505 | 4505 | "name": "web", | |
| 4506 | − | "summary": "The storefront. Written in TypeScript. Packages: @acme/web. Uses api. Owned by ana.", | |
| 4506 | + | "summary": "The storefront. Written in TypeScript. Packages: @acme/web. Owned by ana.", | |
| 4507 | 4507 | "project": "web", | |
| 4508 | 4508 | "private": true, | |
| 4509 | 4509 | "data": { | |
| ⋯ | |||
| 4528 | 4528 | }, | |
| 4529 | 4529 | "relations": [ | |
| 4530 | 4530 | { | |
| 4531 | − | "kind": "depends_on", | |
| 4531 | + | "kind": "exposes", | |
| 4532 | 4532 | "direction": "out", | |
| 4533 | 4533 | "entity": { | |
| 4534 | 4534 | "id": "ent_0a7c3e5b9d1f2a4c6e8b0d2f", | |
| 4535 | 4535 | "workspace": "acme", | |
| 4536 | − | "kind": "project", | |
| 4537 | − | "key": "api", | |
| 4538 | − | "name": "api", | |
| 4539 | − | "summary": "The public API. Written in Rust.", | |
| 4540 | − | "project": "api", | |
| 4536 | + | "kind": "package", | |
| 4537 | + | "key": "npm:@acme/web", | |
| 4538 | + | "name": "@acme/web", | |
| 4539 | + | "summary": null, | |
| 4540 | + | "project": "web", | |
| 4541 | 4541 | "private": true, | |
| 4542 | 4542 | "data": {}, | |
| 4543 | 4543 | "source": "scan", | |
| 4544 | − | "ref": "/acme/api", | |
| 4545 | − | "updated_at": "2026-10-04T20:57:12.000Z" | |
| 4544 | + | "ref": "/acme/web/blob/main/package.json", | |
| 4545 | + | "updated_at": "2026-10-04T20:58:41.000Z" | |
| 4546 | 4546 | } | |
| 4547 | 4547 | }, | |
| 4548 | 4548 | { | |
| 162 | 162 | ||
| 163 | 163 | Every run is also given a **Context** section from the | |
| 164 | 164 | [context hub](/guides/context-hub/#agents-start-with-context): the | |
| 165 | − | project's stack, owners, environments and the projects it uses with their | |
| 166 | − | live addresses, the kept memories closest to its task, and recent | |
| 165 | + | project's stack, owners and environments, the kept memories closest to its task, and recent | |
| 167 | 166 | decisions. | |
| 168 | 167 | ||
| 169 | 168 | Your own agents can use memory too, through the [`memory` tool](/reference/mcp/#memory) |
| 52 | 52 | ||
| 53 | 53 | | Relation | Example | | |
| 54 | 54 | | --- | --- | | |
| 55 | − | | `depends_on` | `web` depends on `api`; `@acme/web` depends on `@acme/ui` | | |
| 55 | + | | `depends_on` | `@acme/web` depends on `@acme/ui` | | |
| 56 | 56 | | `owned_by` | `web` is owned by `ana` | | |
| 57 | 57 | | `deploys_to` | the `web` app deploys to `web/production` | | |
| 58 | 58 | | `documented_by` | `web` is documented by its `README.md` | | |
| ⋯ | |||
| 227 | 227 | ||
| 228 | 228 | - the project's stack, test commands, owners, docs, and its environments | |
| 229 | 229 | with their addresses; | |
| 230 | − | - the projects it uses, with their live addresses and the variables that | |
| 231 | − | carry them, and the projects that use it; | |
| 232 | 230 | - the kept memories closest to the task, beyond the pinned ones every run | |
| 233 | 231 | already has; | |
| 234 | 232 | - the latest decisions from merged pull requests. | |
| 142 | 142 | What Deployments cannot run yet, such as long-running servers, is on | |
| 143 | 143 | [What g1t can't do yet](/about/limitations/#deployments). | |
| 144 | 144 | ||
| 145 | − | ## Addresses of other projects | |
| 146 | − | ||
| 147 | − | A project that [depends on another](/guides/projects/#dependencies) with | |
| 148 | − | `as: API_URL` gets that project's address as `API_URL`, in its build and in | |
| 149 | − | its running app: | |
| 150 | − | ||
| 151 | − | | Building | `API_URL` is | | |
| 152 | − | | --- | --- | | |
| 153 | − | | Production | The other project's production. | | |
| 154 | − | | A preview of branch `x` | The other project's preview of `x` if it is up, else its production. | | |
| 155 | − | ||
| 156 | − | A secret or variable of the same name wins over it. | |
| 157 | − | ||
| 158 | − | ### Preview stacks | |
| 159 | − | ||
| 160 | − | A change to an API is best seen in the apps that call it. On a pull | |
| 161 | − | request whose preview is up, **Preview them against this change** (under | |
| 162 | − | **Affects**) builds a preview of every project that uses this one, from its | |
| 163 | − | own default branch, under the same branch name. Each gets this preview's | |
| 164 | − | address through its variable. They come down with their idle days, like | |
| 165 | − | any preview. | |
| 166 | − | ||
| 167 | 145 | ## Previews of branches | |
| 168 | 146 | ||
| 169 | 147 | A preview is built when a pull request is opened, when it is marked ready, | |
| ⋯ | |||
| 311 | 289 | | | Needs | | |
| 312 | 290 | | --- | --- | | |
| 313 | 291 | | See its deployments, their build logs, previews and domains | Read | | |
| 314 | − | | **Redeploy**, preview a stack, take an app down | Write | | |
| 292 | + | | **Redeploy**, take an app down | Write | | |
| 315 | 293 | | Turn deployments on or off, change their settings | Admin | | |
| 316 | 294 | | Add, verify and remove custom domains | Admin | | |
| 317 | 295 | ||
| 63 | 63 | | **Active branches** | Branches other than the default, newest first. See [active branches](#active-branches). | | |
| 64 | 64 | | **Recent changes**, **Latest on main**, **Activity**, **Previews** | What landed, the default branch's latest commits, what happened (pull requests opened, readied and merged, issues opened and closed, checks, reviews, comments, pushes to the default branch and production deploys), and the previews that are up. | | |
| 65 | 65 | | **About** | Its description, its [links](#links), what it is, and its latest release (its newest tag). People who can change its settings edit the description and links from here. | | |
| 66 | − | | **Health**, **Dependencies**, **Clone** | How often checks pass, recent builds and open issues by age; what it uses and what uses it; the clone address. | | |
| 66 | + | | **Health**, **Clone** | How often checks pass, recent builds and open issues by age; the clone address. | | |
| 67 | 67 | ||
| 68 | 68 | ### The checklist | |
| 69 | 69 | ||
| ⋯ | |||
| 219 | 219 | | **General** | The project's name and description, its source, its **root directory**, [what it is](#what-a-project-is), and its [links](#links). A project shows its repository's description, and follows it as it changes, until you give the project one of its own; **Use the repository's description** goes back. | | |
| 220 | 220 | | **Deployments** | Production, previews, build command, output directory and idle days. See [Deployments](/guides/deployments/#settings). | | |
| 221 | 221 | | **Domains** | Custom domains for production. See [custom domains](/guides/deployments/#custom-domains). | | |
| 222 | − | | **Dependencies** | The projects this one uses, and the ones that use it. See [Dependencies](#dependencies). | | |
| 223 | 222 | | **Agents** | How g1t's agents pick up work here, and what they read first. | | |
| 224 | 223 | | **Guardrails** | What agents may reach, run and spend while they work here. See [guardrails](/guides/guardrails/). | | |
| 225 | 224 | | **Repository** | The repository's name, description, website, [topics](/guides/search/#what-is-indexed) and default branch, and its danger zone: visibility, archive, transfer and delete. See [Managing a repository](/guides/managing-repositories/). | | |
| ⋯ | |||
| 281 | 280 | requests. | |
| 282 | 281 | - **Several projects on one repository**, each from its own root | |
| 283 | 282 | directory, with a push building only the projects it touched. | |
| 284 | − | ||
| 285 | − | ||
| 286 | − | ## Dependencies | |
| 287 | − | ||
| 288 | − | A project can depend on others in its workspace: `web` calls `api`'s HTTP | |
| 289 | − | API, or consumes `ui-kit`'s package. Declare it under **Settings → | |
| 290 | − | Dependencies**, or in a `.g1t/project.yml` in the project's root | |
| 291 | − | directory: | |
| 292 | − | ||
| 293 | − | ```yaml | |
| 294 | − | dependsOn: | |
| 295 | − | - project: api | |
| 296 | − | as: API_URL | |
| 297 | − | - project: ui-kit | |
| 298 | − | ``` | |
| 299 | − | ||
| 300 | − | The file is read on every push to the default branch, and its dependencies | |
| 301 | − | replace the ones it declared before; those are marked **project.yml** and | |
| 302 | − | changed only in the file. A dependency that would make a cycle, or names a | |
| 303 | − | project that does not exist, is left out. | |
| 304 | − | ||
| 305 | − | What g1t does with them: | |
| 306 | − | ||
| 307 | − | | | | | |
| 308 | − | | --- | --- | | |
| 309 | − | | **Addresses in builds and apps** | With `as: API_URL`, `web`'s builds and its running app get `API_URL` set to `api`'s address for the same environment: production gets `api`'s production; a preview gets the preview of `api` on the same branch if one is up, else `api`'s production. A secret or variable of the same name on `web` wins. | | |
| 310 | − | | **Preview stacks** | On a pull request of `api` whose preview is up, **Preview them against this change** builds a preview of every project that uses `api`, from its default branch, under the same branch name, so each reaches the change through its variable. A reviewer clicks through the whole change. | | |
| 311 | − | | **Affects** | A pull request lists the projects that use its project, so reviewers see what else a change can break. | | |
| 312 | − | | **Agents** | An agent working on a project is told what it uses and what uses it. If its change alters what those rely on, it keeps it working for them or opens an issue on each saying what to change, and says so in its summary. | | |
| 313 | − | | **The overview** | Each project's overview shows what it depends on and what uses it. | | |
| 314 | 283 | ||
| 315 | 284 | ## From the API | |
| 316 | 285 | ||
Binary or large file; its contents are not shown.
| 5 | 5 | "": { title: "General", about: "The project's name, where its code lives, and what it is for." }, | |
| 6 | 6 | deployments: { title: "Deployments", about: "How the project builds, where it runs, and its previews." }, | |
| 7 | 7 | domains: { title: "Domains", about: "Addresses of your own for the production deployment." }, | |
| 8 | − | dependencies: { title: "Dependencies", about: "What the project relies on and what relies on it." }, | |
| 9 | 8 | agents: { title: "Agents", about: "How g1t picks up work here, and what it reads first." }, | |
| 10 | 9 | guardrails: { title: "Guardrails", about: "What agents may reach, run and spend while they work here." }, | |
| 11 | 10 | repository: { title: "Repository", about: "Its name, details and default branch, who can see it, and archiving, moving or deleting it." }, |
| 1117 | 1117 | <SidebarLink to={`${base}/settings/domains`} icon={<Globe size={15} />}> | |
| 1118 | 1118 | Domains | |
| 1119 | 1119 | </SidebarLink> | |
| 1120 | − | <SidebarLink to={`${base}/settings/dependencies`} icon={<Network size={15} />}> | |
| 1121 | − | Dependencies | |
| 1122 | − | </SidebarLink> | |
| 1123 | 1120 | </div> | |
| 1124 | 1121 | </> | |
| 1125 | 1122 | )} | |
| ⋯ | |||
| 1704 | 1701 | branches: "Branches", | |
| 1705 | 1702 | tags: "Tags", | |
| 1706 | 1703 | compare: "Compare", | |
| 1707 | − | dependencies: "Dependencies", | |
| 1708 | 1704 | code: "Files", | |
| 1709 | 1705 | secrets: "Secrets and variables", | |
| 1710 | 1706 | settings: "Settings", | |
| 37 | 37 | runners: "manage_integrations", | |
| 38 | 38 | deployments: "manage_integrations", | |
| 39 | 39 | domains: "manage_integrations", | |
| 40 | − | dependencies: "manage_settings", | |
| 41 | 40 | // Write and up can see who has access; Admins change it. | |
| 42 | 41 | access: "push", | |
| 43 | 42 | // Deploy keys decide who reaches a repository too: Admins only. |
| 110 | 110 | ( | |
| 111 | 111 | "account active_agents all_ids get list queue pulls_for_repos blame blob branches by_author by_repo contributions catalog check_invite check_limit " + | |
| 112 | 112 | "check_workspace_deletion check_workspace_rename collaborator_permission compare counts deleted deliveries " + | |
| 113 | − | "dependencies domains entitlements entity explore features git_access graph has_feature invoices ledger limit " + | |
| 113 | + | "domains entitlements entity explore features git_access has_feature invoices ledger limit " + | |
| 114 | 114 | "limit_requests links log logs managed_pulls memories_by_id memory_context my_repo_invitations " + | |
| 115 | 115 | "outside_collaborators overview path_by_id prices profile profile_workspaces public_namespaces read_session " + | |
| 116 | 116 | "readable ready_issues references registration repo_access resolve resolve_branch resolve_path resolve_slug " + |
| 47 | 47 | key: "roadmap", | |
| 48 | 48 | title: "Roadmap", | |
| 49 | 49 | section: "Workspace", | |
| 50 | − | summary: "Outcomes on a timeline, with their dependencies across projects.", | |
| 51 | − | why: "An outcome is what should be true when the work is done. On a roadmap you see when each is expected, what it waits on in other projects, and how much has landed.", | |
| 50 | + | summary: "Outcomes on a timeline, with how much of each has landed.", | |
| 51 | + | why: "An outcome is what should be true when the work is done. On a roadmap you see when each is expected and how much has landed.", | |
| 52 | 52 | plans: [ | |
| 53 | 53 | "Outcomes as bars on a timeline, from start to target", | |
| 54 | 54 | "Progress from merged work, not from guesses", | |
| 55 | − | "Dependencies across projects, drawn from the project graph", | |
| 56 | 55 | "Slip warnings when the work left outgrows the time left", | |
| 57 | 56 | ], | |
| 58 | 57 | today: { label: "Outcomes", path: "plans" }, |
| 305 | 305 | route("settings/runners", "routes/repo/settings-runners.tsx"), | |
| 306 | 306 | route("settings/deployments", "routes/repo/settings-deployments.tsx"), | |
| 307 | 307 | route("settings/domains", "routes/repo/settings-domains.tsx"), | |
| 308 | − | route("settings/dependencies", "routes/repo/settings-dependencies.tsx"), | |
| 308 | + | route("settings/dependencies", "routes/repo/settings-dependencies.ts"), | |
| 309 | 309 | route("settings/guardrails", "routes/repo/settings-guardrails.tsx"), | |
| 310 | 310 | route("settings/agents", "routes/repo/settings-agents.tsx"), | |
| 311 | 311 | // What the project will have: one page for each Soon in its menu. |
| 1 | 1 | import { | |
| 2 | 2 | Activity as ActivityIcon, | |
| 3 | − | ArrowDownLeft, | |
| 4 | 3 | ArrowRight, | |
| 5 | 4 | ArrowUpRight, | |
| 6 | 5 | BookOpen, | |
| ⋯ | |||
| 16 | 15 | HeartPulse, | |
| 17 | 16 | Loader2, | |
| 18 | 17 | Lock, | |
| 19 | − | Network, | |
| 20 | 18 | Package as PackageGlyph, | |
| 21 | 19 | Pin, | |
| 22 | 20 | Rocket, | |
| ⋯ | |||
| 209 | 207 | branchesP.catch(() => null), | |
| 210 | 208 | new Promise<"slow">((resolve) => setTimeout(() => resolve("slow"), crawler ? BRANCHES_WAIT_CRAWLER_MS : BRANCHES_WAIT_MS)), | |
| 211 | 209 | ]); | |
| 212 | − | const [{ insider: member, can }, project, settings, list, open, closed, log, counts, deps, runs, queue, issues, memories, recent, mine, domains, root, packageList, workflows, tagList] = await Promise.all([ | |
| 210 | + | const [{ insider: member, can }, project, settings, list, open, closed, log, counts, runs, queue, issues, memories, recent, mine, domains, root, packageList, workflows, tagList] = await Promise.all([ | |
| 213 | 211 | accessP, | |
| 214 | 212 | projectP, | |
| 215 | 213 | forMembers(() => deployments.settings(ref, viewer)), | |
| ⋯ | |||
| 219 | 217 | // With their people, in one identity call. | |
| 220 | 218 | soft(repos.log(path, viewer, null, COMMITS_SHOWN).then(async (found) => (found.ok ? { ok: true as const, value: await showCommits(found.value) } : found))), | |
| 221 | 219 | soft(countsFor(context, params)), | |
| 222 | − | soft(projects.dependencies(params.owner, params.repo, viewer)), | |
| 223 | 220 | soft(agents.listRuns(viewer, { repo: path, limit: 60 })), | |
| 224 | 221 | soft(work.queue(path, viewer)), | |
| 225 | 222 | soft(work.listIssues(path, viewer, { state: "open" })), | |
| ⋯ | |||
| 452 | 449 | languages: null as { name: string; share: number }[] | null, | |
| 453 | 450 | contributors: null as { username: string }[] | null, | |
| 454 | 451 | open: ok(counts) ?? { issues: openIssues?.length ?? 0, pulls: openPulls.length }, | |
| 455 | − | dependencies: ok(deps), | |
| 456 | 452 | agentsLive: live.slice(0, 6), | |
| 457 | 453 | runsLoaded: runs?.ok ?? false, | |
| 458 | 454 | pullsLoaded: open?.ok ?? false, | |
| ⋯ | |||
| 788 | 784 | actionData: Route.ComponentProps["actionData"]; | |
| 789 | 785 | params: Route.ComponentProps["params"]; | |
| 790 | 786 | }) { | |
| 791 | − | const { member, project, settings, builds, live, commit, open, dependencies, agentsLive, columns, needs, landed, groups, health, knows, checklist, branches, library, checks } = | |
| 787 | + | const { member, project, settings, builds, live, commit, open, agentsLive, columns, needs, landed, groups, health, knows, checklist, branches, library, checks } = | |
| 792 | 788 | loaderData; | |
| 793 | 789 | const base = `/${params.owner}/${params.repo}`; | |
| 794 | 790 | const production = live.find((app) => app.kind === "production") ?? null; | |
| ⋯ | |||
| 1461 | 1457 | )} | |
| 1462 | 1458 | </div> | |
| 1463 | 1459 | </div> | |
| 1464 | − | </section> | |
| 1465 | − | ||
| 1466 | − | <section className="rounded-xl border border-line bg-surface p-5"> | |
| 1467 | − | <div className="flex items-center justify-between"> | |
| 1468 | − | <h2 className="flex items-center gap-1.5 text-sm font-semibold"> | |
| 1469 | − | <Network size={14} className="text-faint" /> | |
| 1470 | − | Dependencies | |
| 1471 | − | </h2> | |
| 1472 | − | {member && ( | |
| 1473 | − | <Link to={`${base}/settings/dependencies`} className="text-xs text-muted hover:text-fg"> | |
| 1474 | − | Manage | |
| 1475 | − | </Link> | |
| 1476 | − | )} | |
| 1477 | − | </div> | |
| 1478 | − | {!dependencies ? ( | |
| 1479 | − | <p className="mt-3 text-xs text-muted">Dependencies could not be loaded just now.</p> | |
| 1480 | − | ) : dependencies.dependsOn.length === 0 && dependencies.usedBy.length === 0 ? ( | |
| 1481 | − | <p className="mt-3 text-xs leading-5 text-muted"> | |
| 1482 | − | Uses no other project, and none uses it. Declare one, and builds get its address and agents know what depends on | |
| 1483 | − | what. | |
| 1484 | − | </p> | |
| 1485 | − | ) : ( | |
| 1486 | − | <div className="mt-3 space-y-3 text-xs"> | |
| 1487 | − | {dependencies.dependsOn.length > 0 && ( | |
| 1488 | − | <div> | |
| 1489 | − | <p className="flex items-center gap-1 text-muted"> | |
| 1490 | − | <ArrowUpRight size={12} /> Uses | |
| 1491 | − | </p> | |
| 1492 | − | <ul className="mt-1.5 space-y-1"> | |
| 1493 | − | {dependencies.dependsOn.map((d) => ( | |
| 1494 | − | <li key={d.slug} className="flex items-center justify-between gap-2"> | |
| 1495 | − | <Link to={`/${params.owner}/${d.slug}`} className="font-medium hover:underline"> | |
| 1496 | − | {d.name} | |
| 1497 | − | </Link> | |
| 1498 | − | {d.as && <code className="font-mono text-faint">{d.as}</code>} | |
| 1499 | − | </li> | |
| 1500 | − | ))} | |
| 1501 | − | </ul> | |
| 1502 | − | </div> | |
| 1503 | − | )} | |
| 1504 | − | {dependencies.usedBy.length > 0 && ( | |
| 1505 | − | <div> | |
| 1506 | − | <p className="flex items-center gap-1 text-muted"> | |
| 1507 | − | <ArrowDownLeft size={12} /> Used by | |
| 1508 | − | </p> | |
| 1509 | − | <ul className="mt-1.5 space-y-1"> | |
| 1510 | − | {dependencies.usedBy.map((d) => ( | |
| 1511 | − | <li key={d.slug}> | |
| 1512 | − | <Link to={`/${params.owner}/${d.slug}`} className="font-medium hover:underline"> | |
| 1513 | − | {d.name} | |
| 1514 | − | </Link> | |
| 1515 | − | </li> | |
| 1516 | − | ))} | |
| 1517 | − | </ul> | |
| 1518 | − | </div> | |
| 1519 | − | )} | |
| 1520 | − | </div> | |
| 1521 | − | )} | |
| 1522 | 1460 | </section> | |
| 1523 | 1461 | ||
| 1524 | 1462 | {source && ( | |
| 19 | 19 | ArrowUpRight, | |
| 20 | 20 | MessagesSquare, | |
| 21 | 21 | Rocket, | |
| 22 | − | Network, | |
| 23 | 22 | StickyNote, | |
| 24 | 23 | User, | |
| 25 | 24 | Users, | |
| ⋯ | |||
| 69 | 68 | } from "../../components/ui"; | |
| 70 | 69 | import { CheckboxOption } from "../../components/ui/checkbox"; | |
| 71 | 70 | import { Hint } from "../../components/ui/hint"; | |
| 72 | − | import { Loading, SkeletonLine } from "../../components/ui/skeleton"; | |
| 73 | 71 | import { TabStrip } from "../../components/ui/tab-strip"; | |
| 74 | 72 | import { WorkflowStatuses } from "../../components/actions"; | |
| 75 | 73 | import { AddCiPrompt } from "../../components/add-ci"; | |
| ⋯ | |||
| 93 | 91 | import { CATCH_UP_TIMEOUT_MS } from "../../lib/catch-up"; | |
| 94 | 92 | import { notFound } from "../../lib/not-found.server"; | |
| 95 | 93 | import { computeNoteFor } from "../../lib/compute.server"; | |
| 96 | − | import { actions, agents, deployments, identity, inbox, projects, repos, work } from "../../lib/services.server"; | |
| 94 | + | import { actions, agents, deployments, identity, inbox, repos, work } from "../../lib/services.server"; | |
| 97 | 95 | import { commitChecksFor } from "../../lib/commit-checks.server"; | |
| 98 | 96 | import { CommitChecksBadge } from "../../components/commit-checks"; | |
| 99 | 97 | import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server"; | |
| ⋯ | |||
| 141 | 139 | const ref = { workspace: params.owner, slug: params.repo }; | |
| 142 | 140 | const access = accessTo(context, params); | |
| 143 | 141 | const pullFound = work.getPull(path, number, viewer); | |
| 144 | − | const deps = projects.dependencies(params.owner, params.repo, viewer); | |
| 145 | − | // Awaited below, unless the pull request is missing first. | |
| 146 | − | deps.catch(() => null); | |
| 147 | 142 | // Its labels and milestone, with the repository's to choose from. | |
| 148 | 143 | const labelsFound = work.listLabels(path, viewer); | |
| 149 | 144 | const milestonesFound = work.listMilestones(path, viewer); | |
| ⋯ | |||
| 202 | 197 | pull.status === "open" && | |
| 203 | 198 | (found.value.statuses ?? []).length === 0 && | |
| 204 | 199 | (found.value.requiredChecks ?? []).length === 0; | |
| 205 | − | const [comparison, used, noChecks] = await Promise.all([ | |
| 200 | + | const [comparison, noChecks] = await Promise.all([ | |
| 206 | 201 | // The first screens of it highlighted here, so their colours do not pop in. | |
| 207 | 202 | tab === "changes" | |
| 208 | 203 | ? repos | |
| 209 | 204 | .compare(range.repoId, viewer, range.base, range.head, range.baseBranch) | |
| 210 | 205 | .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found)) | |
| 211 | 206 | : null, | |
| 212 | − | deps, | |
| 213 | 207 | unchecked | |
| 214 | 208 | ? actions | |
| 215 | 209 | .workflows(path, viewer) | |
| ⋯ | |||
| 217 | 211 | .catch(() => false) | |
| 218 | 212 | : false, | |
| 219 | 213 | ]); | |
| 220 | − | const affects = used.ok ? used.value.usedBy : []; | |
| 221 | 214 | // Every pull request for its issue, to compare: streamed, and read only | |
| 222 | 215 | // when there is more than this one. | |
| 223 | 216 | const attempts = | |
| ⋯ | |||
| 284 | 277 | // Who may choose the required checks. | |
| 285 | 278 | canProtect: can.manage_protection, | |
| 286 | 279 | defaultBranch: repo.ok ? repo.value.defaultBranch : "main", | |
| 287 | − | affects, | |
| 288 | − | // Streamed: the conversation shows first, the preview card after. | |
| 289 | − | ...deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, affects, viewer), | |
| 280 | + | ...deploymentOf(deployed?.ok ? deployed.value : null, number), | |
| 290 | 281 | }; | |
| 291 | 282 | } | |
| 292 | 283 | ||
| ⋯ | |||
| 329 | 320 | ||
| 330 | 321 | /** | |
| 331 | 322 | * Where this pull request is live: its preview (or its latest build, while | |
| 332 | − | * one is going or after one failed), and the previews of the projects | |
| 333 | − | * that use this one, built against it. | |
| 323 | + | * one is going or after one failed). | |
| 334 | 324 | */ | |
| 335 | 325 | function deploymentOf( | |
| 336 | 326 | list: { deployments: Deployment[]; live: LiveApp[] } | null, | |
| 337 | 327 | number: number, | |
| 338 | − | owner: string, | |
| 339 | − | affects: { slug: string; name: string }[], | |
| 340 | − | viewer: Viewer, | |
| 341 | − | ): { preview: LiveApp | null; build: Deployment | null; stacked: Promise<Stacked[]> } { | |
| 328 | + | ): { preview: LiveApp | null; build: Deployment | null } { | |
| 342 | 329 | const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null; | |
| 343 | 330 | const build = list?.deployments.find((d) => d.kind === "preview" && d.number === number) ?? null; | |
| 344 | − | const branch = preview?.branch ?? build?.branch ?? null; | |
| 345 | − | // Streamed: another lookup per project, shown in the card once known. | |
| 346 | − | const stacked: Promise<Stacked[]> = branch | |
| 347 | − | ? Promise.all( | |
| 348 | − | affects.slice(0, 5).map(async (project) => { | |
| 349 | − | const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer).catch(() => null); | |
| 350 | − | const app = theirs?.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined; | |
| 351 | − | return app ? { name: project.name, slug: project.slug, url: app.url } : null; | |
| 352 | − | }), | |
| 353 | − | ).then((found) => found.filter((entry) => entry != null)) | |
| 354 | − | : Promise.resolve([]); | |
| 355 | − | return { preview, build, stacked }; | |
| 331 | + | return { preview, build }; | |
| 356 | 332 | } | |
| 357 | − | ||
| 358 | − | /** A project that uses this one, with its preview built against this change. */ | |
| 359 | − | type Stacked = { name: string; slug: string; url: string }; | |
| 360 | − | ||
| 361 | 333 | const PULL_NEEDS: Record<string, Capability> = { | |
| 362 | − | stack: "run", | |
| 363 | 334 | "agent-review": "run", | |
| 364 | 335 | "rerun-workflow": "run", | |
| 365 | 336 | "rerun-failed": "run", | |
| ⋯ | |||
| 389 | 360 | // `@acme/backend` is asked as `acme/backend`. | |
| 390 | 361 | ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")), | |
| 391 | 362 | ]; | |
| 392 | − | // The projects that use this one, built against this pull request's preview. | |
| 393 | − | if (action === "stack") { | |
| 394 | − | const built = await deployments.stack(user, { workspace: params.owner, slug: params.repo }, String(form.get("branch") ?? "")); | |
| 395 | − | return built.ok | |
| 396 | − | ? { action, notice: `Building ${built.value.join(", ")} against this preview. They appear on their Deployments pages.` } | |
| 397 | − | : { action, error: built.error.message }; | |
| 398 | − | } | |
| 399 | 363 | // Asking g1t for its review records the request, then starts it. | |
| 400 | 364 | if (action === "agent-review") { | |
| 401 | 365 | const asked = await work.updatePull(user, path, number, { | |
| ⋯ | |||
| 666 | 630 | earlierChecks = [], | |
| 667 | 631 | workflowJobs, | |
| 668 | 632 | attempts, | |
| 669 | − | affects, | |
| 670 | 633 | preview, | |
| 671 | 634 | build, | |
| 672 | − | stacked, | |
| 673 | 635 | landing, | |
| 674 | 636 | stalled, | |
| 675 | 637 | requireUpToDate, | |
| ⋯ | |||
| 1149 | 1111 | failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null} | |
| 1150 | 1112 | /> | |
| 1151 | 1113 | ||
| 1152 | − | {(preview || build) && <DeploymentCard preview={preview} build={build} stacked={stacked} stacking={affects.length > 0} base={base} />} | |
| 1114 | + | {(preview || build) && <DeploymentCard preview={preview} build={build} base={base} />} | |
| 1153 | 1115 | ||
| 1154 | 1116 | {pull.status === "draft" && ( | |
| 1155 | 1117 | <StatusBox> | |
| ⋯ | |||
| 1452 | 1414 | </Form> | |
| 1453 | 1415 | )} | |
| 1454 | 1416 | {actionData && | |
| 1455 | − | !["merge", "comment", "stack", "rerun-failed", "rerun-workflow", "update", "agent-review", "reviewers", "assign", "edit-comment", "delete-comment"].includes( | |
| 1417 | + | !["merge", "comment", "rerun-failed", "rerun-workflow", "update", "agent-review", "reviewers", "assign", "edit-comment", "delete-comment"].includes( | |
| 1456 | 1418 | String(actionData.action), | |
| 1457 | 1419 | ) && <ErrorText>{actionData.error}</ErrorText>} | |
| 1458 | 1420 | </div> | |
| ⋯ | |||
| 1475 | 1437 | > | |
| 1476 | 1438 | {/* Other than a draft, a pull request shows its checks in full in the conversation. */} | |
| 1477 | 1439 | {pull.status === "draft" && <WorkflowStatuses statuses={statuses} />} | |
| 1478 | − | {affects.length > 0 && ( | |
| 1479 | − | <section> | |
| 1480 | − | <h3 className="flex items-center gap-1.5 text-sm font-medium"> | |
| 1481 | − | <Network size={14} className="text-faint" /> | |
| 1482 | − | Affects | |
| 1483 | − | </h3> | |
| 1484 | − | <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p> | |
| 1485 | − | <ul className="mt-2 space-y-1 text-sm"> | |
| 1486 | − | {affects.map((project) => ( | |
| 1487 | − | <li key={project.slug} className="flex items-center justify-between gap-2"> | |
| 1488 | − | <Link to={`/${params.owner}/${project.slug}`} className="hover:underline"> | |
| 1489 | − | {project.name} | |
| 1490 | − | </Link> | |
| 1491 | − | {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>} | |
| 1492 | − | </li> | |
| 1493 | − | ))} | |
| 1494 | − | </ul> | |
| 1495 | − | {preview?.branch && canMerge && ( | |
| 1496 | − | <Form method="post" className="mt-3"> | |
| 1497 | − | <input type="hidden" name="action" value="stack" /> | |
| 1498 | − | <input type="hidden" name="branch" value={preview.branch} /> | |
| 1499 | − | <SubmitButton | |
| 1500 | − | match={{ action: "stack" }} | |
| 1501 | − | pending="Starting the builds…" | |
| 1502 | − | className="flex w-full items-center justify-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs text-muted transition-colors hover:border-line-strong hover:text-fg disabled:opacity-50" | |
| 1503 | − | > | |
| 1504 | − | Preview them against this change | |
| 1505 | − | </SubmitButton> | |
| 1506 | − | </Form> | |
| 1507 | − | )} | |
| 1508 | − | {actionData?.action === "stack" && | |
| 1509 | − | ("notice" in actionData ? ( | |
| 1510 | − | <p className="mt-2 text-xs text-success">{String(actionData.notice)}</p> | |
| 1511 | − | ) : ( | |
| 1512 | − | <ErrorText>{actionData.error}</ErrorText> | |
| 1513 | − | ))} | |
| 1514 | − | </section> | |
| 1515 | − | )} | |
| 1516 | − | ||
| 1517 | 1440 | <section> | |
| 1518 | 1441 | <h3 className="text-sm font-medium">Reviewers</h3> | |
| 1519 | 1442 | <ul className="mt-2 space-y-1.5 text-sm"> | |
| ⋯ | |||
| 1731 | 1654 | function DeploymentCard({ | |
| 1732 | 1655 | preview, | |
| 1733 | 1656 | build, | |
| 1734 | − | stacked, | |
| 1735 | − | stacking, | |
| 1736 | 1657 | base, | |
| 1737 | 1658 | }: { | |
| 1738 | 1659 | preview: LiveApp | null; | |
| 1739 | 1660 | build: Deployment | null; | |
| 1740 | − | stacked: Promise<Stacked[]>; | |
| 1741 | − | /** Projects use this one, so previews built against it may be on their way. */ | |
| 1742 | − | stacking: boolean; | |
| 1743 | 1661 | base: string; | |
| 1744 | 1662 | }) { | |
| 1745 | 1663 | const building = build?.status === "queued" || build?.status === "building"; | |
| ⋯ | |||
| 1795 | 1713 | {preview.branch && ` · from ${preview.branch}`} | |
| 1796 | 1714 | </p> | |
| 1797 | 1715 | )} | |
| 1798 | − | {/* Read after the page: the card's own part shows at once. */} | |
| 1799 | − | <Suspense | |
| 1800 | − | fallback={ | |
| 1801 | − | stacking && ( | |
| 1802 | − | <Loading className="border-t border-line px-4 py-2.5 text-xs"> | |
| 1803 | − | <SkeletonLine className="w-40" /> | |
| 1804 | − | <SkeletonLine className="mt-1.5 w-64 max-w-full" /> | |
| 1805 | − | </Loading> | |
| 1806 | − | ) | |
| 1807 | − | } | |
| 1808 | − | > | |
| 1809 | − | <Await resolve={stacked}> | |
| 1810 | − | {(stacked) => | |
| 1811 | − | stacked.length > 0 && ( | |
| 1812 | − | <div className="animate-fade-in border-t border-line px-4 py-2.5"> | |
| 1813 | − | <p className="text-xs text-muted">Built against this change:</p> | |
| 1814 | − | <ul className="mt-1.5 space-y-1"> | |
| 1815 | − | {stacked.map((entry) => ( | |
| 1816 | − | <li key={entry.slug} className="flex items-center gap-2 text-xs"> | |
| 1817 | − | <span className="font-medium">{entry.name}</span> | |
| 1818 | − | <a href={entry.url} className="truncate font-mono text-muted hover:text-accent"> | |
| 1819 | − | {entry.url.replace(/^https?:[/][/]/, "")} | |
| 1820 | − | </a> | |
| 1821 | − | </li> | |
| 1822 | − | ))} | |
| 1823 | − | </ul> | |
| 1824 | − | </div> | |
| 1825 | − | ) | |
| 1826 | − | } | |
| 1827 | − | </Await> | |
| 1828 | − | </Suspense> | |
| 1829 | 1716 | </section> | |
| 1830 | 1717 | ); | |
| 1831 | 1718 | } | |
| 1 | + | import { redirect } from "react-router"; | |
| 2 | + | ||
| 3 | + | import type { Route } from "./+types/settings-dependencies"; | |
| 4 | + | ||
| 5 | + | /** | |
| 6 | + | * Projects no longer declare which other projects they use. An old link to | |
| 7 | + | * that settings page lands on the project's settings. | |
| 8 | + | */ | |
| 9 | + | export function loader({ params }: Route.LoaderArgs) { | |
| 10 | + | throw redirect(`/${params.owner}/${params.repo}/settings`, 301); | |
| 11 | + | } |
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
This change is too large to show in full.