Artifact Registry: Add the repository create entry
What does this MR do and why?
This change is behind the feature flag :artifact_registry_ui (default off).
The hosted create form and its new/hosted route are already on master, but nothing on the repositories list navigates to them — the route is reachable only by typing the URL. monolith/S07 assigns every repository write affordance to this slice, so the create entry belongs here rather than to the read slices that host it. This is the create-entry half of S07 Step 5; the per-row edit/delete menu, Step 5's other affordance, ships in !248233 (merged).
The design proposal puts the entry in a page header above the search bar, so the page title moves out of the list view into a header component that carries the entry beside it.
Changes
repositories/components/create_button.vue(new) — aGlDisclosureDropdown(variant="confirm",placement="bottom-end") with one item, Hosted repository, whosetois the existingnew/hostedroute. Following the sibling precedent invirtual_registries/pages/index.vue. The component takes no props, somonolith/S04's toolbar or the detail page can mount it unchanged, which is what Step 5 asks of it.repositories/list/repositories_header.vue(new) — wraps~/vue_shared/components/page_heading.vue: theRepositoriesheading moved verbatim out of the list view, with the create entry in the#actionsslot. The#descriptionslot is deliberately left free — that is where the repository count and total storage land when the aggregate header ships.repositories/list/repositories_list.vue— renders<repositories-header />in place of<page-heading>, still inside the existingv-elseso it stays hidden on the not-found state and visible on the empty and service-unavailable states.- Jest specs for both new components, the three existing list-heading assertions retargeted at the header, and one new case covering that the entry survives the empty state.
- A
create_buttonstory, and one newlocale/gitlab.potentry.
No routes, no constants, no GraphQL, no Ruby.
The kind is chosen at the entry rather than in the form, which is why this is a dropdown rather than a plain button. Hosted is the only kind Artifact Registry accepts in Phase 1, so remote and virtual are absent rather than rendered disabled — an entry that cannot lead anywhere is not an affordance.
Screenshots or screen recordings
| Before | After |
|---|---|
![]() |
![]() |
The entry survives the empty state, so a first repository is creatable, and the dropdown open:
How to set up and validate locally
- Enable the flag:
Feature.enable(:artifact_registry_ui, Organizations::Organization.first). - Visit
/o/<org>/-/artifact_registry/acme/repositories(the slug is stubbed toacme; the controller 404s on anything else). - The
Repositoriesheading now sits in a header with a New repository dropdown on the right, on both the empty and populated states. - Open it, choose Hosted repository, and confirm it client-side navigates to
…/repositories/new/hostedwith the browser tab readingCreate hosted repository · …. - Submit the existing form and confirm you land back on the list with the success toast and the new row.
- Keyboard: Enter on the toggle opens it, ↓ moves into the list, Enter navigates.
Agent Notes
Two deliberate deviations from the merged S07 plan, both worth a look:
- The plan calls this "the list-toolbar create button" and has it mounted into
monolith/S04'srepositories_toolbar.vue. The prototype puts it in the page header one level above the search bar, so I followed the design. The component is still prop-free, so if we later decide it belongs in the toolbar the move is a template change with no component change. - The plan's
repositories/landing.vueis moot —monolith/S04's list shipped first, which triggers the plan's own conditional ("if S04's list already exists … the affordance components mount onto that existing list and no competing view is created").
Deliberately excluded: the breadcrumb restructure. !248296 (merged) and !248233 (merged) both carry it because both need a dynamic crumb, which SpaBreadcrumbs cannot resolve under Vue 3. This MR adds no route and needs no dynamic crumb, so it carries none of it and does not depend on which of those two lands first.
Naming: I called the component repositories_header.vue rather than repositories_topbar.vue because monolith/S04 reserves repositories_toolbar.vue for the filtered-search bar, and a one-letter difference between two adjacent files seemed like a trap. Happy to rename.
Worth manual attention beyond the automated tests: the dark variant="confirm" toggle renders darker than the prototype's blue. That is the current Pajamas token for a primary confirm button rather than anything in this MR — virtual_registries uses the same variant — but a designer should confirm it is the intended treatment.
Resolved in review: @rchanila confirmed the #description slot works as the seam for the repository count and total storage, and flagged that the empty state still needs to match design as a follow-up. The toggle was renamed Create -> New repository per @bonnie-tsang, since toggleText is the toggle's accessible name and a bare verb is not understandable in isolation; the unused data-testid came off at the same time.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.
This MR was authored with the assistance of an AI coding agent and reviewed by @zcuddy before submission.


