feat(ui): forge user lookup — assignee combobox, @-mentions, repo-scoped user search

Repo-scoped assignable-user search for GitHub, GitLab, and Gitea, surfaced as
an assignee combobox in the metadata editor and @-mention autocomplete in
forge comment/reply/review surfaces.

- server: GET /api/{provider}/users/search (assignees / project members),
  query + directory/override repo resolution, 429 -> 503, connected:false
  degradation; GitLab assignee writes resolve login -> ID server-side
- wire: searchUsers (+ searchLabels/milestones/branches/tags) on the three
  API clients with tests
- facade: userSearch capability (all three), searchUsers adapters,
  mapGithubAssignee/mapGitlabMember/mapGiteaAssignee -> ForgeUser
- ui: ForgeLookupCombobox (keyboard nav, debounced 30s-TTL cache,
  connected-only caching), ForgeMentionTextarea (@ token parsing, caret
  restore), free-text fallback when lookup is unavailable; i18n in 12 locales
- extras sharing the same infrastructure: GitLab create-issue dialog and
  label/milestone/branch/tag lookups in the metadata editor
This commit is contained in:
2026-08-16 16:29:25 +00:00
parent 1f28b61c5a
commit 3800c84948
47 changed files with 4052 additions and 35 deletions
+8
View File
@@ -265,6 +265,8 @@ export function createGiteaClient({ token, baseUrl }) {
request(`/repos/${owner}/${repo}/issues/${number}/comments`, { query: params }),
createIssueComment: (owner, repo, number, body) =>
request(`/repos/${owner}/${repo}/issues/${number}/comments`, { method: 'POST', body: { body } }),
createIssue: (owner, repo, params) =>
request(`/repos/${owner}/${repo}/issues`, { method: 'POST', body: params }),
updateIssue: (owner, repo, number, params) =>
request(`/repos/${owner}/${repo}/issues/${number}`, { method: 'PATCH', body: params }),
milestones: (owner, repo, params = {}) =>
@@ -295,6 +297,12 @@ export function createGiteaClient({ token, baseUrl }) {
request(`/repos/${owner}/${repo}/pulls/${number}/merge`, { method: 'POST', body }),
branches: (owner, repo, params = {}) =>
request(`/repos/${owner}/${repo}/branches`, { query: params }),
// Assignable users (collaborators with role access + org members) are the
// mention/assign candidate set; Gitea mirrors the GitHub assignees route.
assignees: (owner, repo, params = {}) =>
request(`/repos/${owner}/${repo}/assignees`, { query: params }),
tags: (owner, repo, params = {}) =>
request(`/repos/${owner}/${repo}/tags`, { query: params }),
};
}