routes.js reads resp.page?.hasMore and resp.page?.nextUrl — the old
HTTP client returned page as an object from parsePageInfo. The CLI
pivot accidentally returned page as a number, making hasMore always
undefined in routes.js. Restore the object shape in both gitea and
gitlab clients. Update tests to assert the object shape.
Fix critical bugs in the CLI transport layer that were masked by
idealized unit test mocks. All fixes verified against live binaries.
Server (gitea/client.js):
- C1: Remove --paginate flag (tea rejects it with exit 1)
- C2: Pass auth via -H 'Authorization: token' header instead of
GITEA_SERVER_TOKEN env (tea ignores that env var)
- C3: Parse --include output from stderr (tea writes headers to
stderr, not stdout)
- H2: Parse Link header for hasMore/pagination
Server (gitlab/client.js):
- C4: Remove /api/v4 prefix (glab adds it automatically; double
prefix caused every call to 404)
- H2: Parse Link header for hasMore/pagination
Tests (both client.test.js):
- H1: Rewrite mocks to match real CLI behavior: headers on stderr
for tea, no --paginate, auth via -H header, Link header parsing
- Add C3, C1, C4 specific regression tests
UI (GiteaSettings, GitLabSettings):
- U4: Replace return null loading state with animated skeleton
(prevents blank flash)
- U2: Surface actual error message in connect failure toast
- U5: Add CLI transport hint below connect form
- U6 (GitLab): Add transport hint for unconnected state
UI (GiteaIssuePickerDialog, GitLabIssuePickerDialog):
- U3: Add retry button when error state is displayed
Live smoke tests passed:
- tea api -H 'Authorization: token WRONG' /user → 401
- tea api /repos/Vibing/openchamber/issues?state=open&limit=2 → 200
- glab api user with GITLAB_TOKEN=dummy → 401 (not 404)
Replace raw fetch transport with CLI subprocess calls:
- Gitea: spawn 'tea api --include' with GITEA_SERVER_TOKEN env var
- GitLab: spawn 'glab api --include' with GITLAB_TOKEN env var
Binary paths env-overridable (TEA_BIN / GLAB_BIN).
8s request timeout via AbortSignal on spawned process.
ETag cache and rate-limit cooldown dropped (tradeoff documented).
Pagination via --paginate for list endpoints.
Tests mock child_process.spawn instead of globalThis.fetch.
Adds a per-project forced provider (github|gitlab|gitea) on top of the
per-project API base URL overrides: stored under gitProviders.provider in
projects/<projectId>.json, sanitized server-side, and winning over remote-host
detection both in useGitProvider and in server repo resolution
(parseGitLabRemoteUrl/parseGiteaRemoteUrl accept any host when the provider
is forced). The Projects settings page replaces the three always-visible URL
fields with a provider selector (auto-detect + the three forges) and one URL
override for the active provider. Global provider override fields on the
GitHub/GitLab/Gitea settings tabs now render only once an account is
connected, and Settings search availability matches that gating.
Also fixes the useConfigStore/useDirectoryStore circular-import TDZ in the
bundled chunk via the window-registered store handle and defers the directory
subscription to a microtask; fixes the Gitea PR merge payload (Do carries the
merge-style string enum, not a boolean + MergeMethod); and adds a documented
Gitea client live-test harness (scripts/gitea-live-test.ts + client.d.ts).
Per provider (github|gitlab|gitea) a project override stored in
~/.config/openchamber/projects/<projectId>.json under gitProviders wins
over the global settings.json value (precedence: project override > global
> built-in default). Server forge routes resolve the override per request
directory (worktree-aware via git-common-dir + containment + path fallback,
60s TTL cache); the override host is also accepted for remote parsing and
client detection. New GET/PUT /api/projects/:projectId/git-providers route;
client openchamberConfig preserves the server-owned gitProviders key;
Projects page gains a Git provider API base URLs section; detection store
hydrates per-project overrides (memory-only, server-authoritative).
Configure a default API base URL per git provider (github/gitlab/gitea)
in settings.json gitProviders, with GitHub Enterprise support (Octokit
baseUrl + device-flow web origin derived from the API base), and replace
the client-side custom-domains list with server-persisted detection URL
chips (SSH/HTTPS forms normalized to hosts). The configured API base host
auto-counts as a detection host. Settings round-trip through the existing
/api/config/settings sanitizer; the UI store hydrates from server settings
with a one-time localStorage migration.
The Gitea create-issue path (server route, client, wire api, facade, UI
button/dialog) landed with the forge user-lookup work; close the remaining
verification/documentation gaps:
- wire: gitea.test.ts covers issueCreate (POST /api/gitea/issues/create,
full/optional body, error throw)
- docs: DOCUMENTATION.md lists createIssue client method and the
POST /repos/{owner}/{repo}/issues endpoint (labels as names)
- ui: fix stale 'read-only by design' JSDoc in GiteaIssuesSection — creation
is offered here and the detail view provides edit/close/reopen
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