Commit Graph
29 Commits
Author SHA1 Message Date
Neil 4371aaf722 refactor provider clients into domain modules (#16168) 2026-08-24 19:51:57 -07:00
Neil 83117f2860 refactor(integrations): split issue-tracker clients under the max-lines budget (#14704)
The GitLab, GitHub, Jira and Linear integration modules, their two IPC
registrars, and the shared GitHub project types each carried a file-level
`eslint-disable max-lines` and ran 351-614 counted lines against a 300-line
budget. AGENTS.md calls for splitting rather than suppressing, and
config/max-lines-baseline.txt is a shrink-only ratchet, so this removes all
eight suppressions and prunes their entries (341 -> 333).

Pure move, no behavior change. Each client is cut along the seam it already
had: per-operation modules for the issue APIs (create / update / comment /
field options), and for Jira the request queue, site credential store,
authenticated request, and site identity. The two IPC registrars keep their own
handlers and delegate the rest to per-domain sub-registrars, so they remain
real entry points rather than re-export shims.

The IPC surface is proved intact rather than assumed: comparing (method,
channel) multisets between HEAD and the split gives 52 registrations across 52
distinct channels on both sides.

Provider-neutrality is preserved -- GitLab and GitHub keep separate, parallel
module layouts rather than being merged behind a shared abstraction.

Verified: oxlint clean, ratchet passes, typecheck clean, full unit suite green
(the one remaining failure is a pre-existing load flake in an untouched file,
green when re-run serially), no new runtime import cycles among 744 modules,
and no lint suppression added anywhere.
2026-08-15 18:17:20 -07:00
Neil 77f23b013f refactor(shared): drop the shared/types barrel and import from the real modules (#14447)
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as
a re-export barrel so the import sites did not have to change. This removes
the barrel: every consumer now imports from the module that actually declares
the type, and `src/shared/types.ts` is deleted.

Barrels hide where a type lives, make every consumer look like it depends on
the whole domain, and let an unrelated edit invalidate a module that ~2,000
files transitively import.

2,323 import declarations across 2,321 files. Rewritten mechanically: each
specifier was resolved to an absolute path via the TypeScript AST and
recomputed, rather than string-substituted, so alias forms (`@/../../shared/
types`) and per-specifier `type` modifiers survive.

Four cases the mechanical pass had to handle, each found by a gate rather than
by reading the diff:

- Modules inside `src/shared` import the barrel as `./types`, not
  `shared/types`. A pre-filter on the latter string skipped 176 of them and
  left imports dangling at a deleted file, which surfaced as confusing
  `Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>`
  errors rather than "module not found".
- The barrel RENAMED one type on the way through
  (`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name
  in the owning module has to be re-aliased at each consumer.
- Three test files put `;(globalThis as ...)` on the line after the import.
  TypeScript parses that `;` as the import statement's terminator, so
  replacing through `statement.getEnd()` deletes it and breaks ASI. The
  rewrite now stops at the module specifier.
- A file that already imported directly from a module got a SECOND import
  from it, because the barrel re-exported those same names — which trips
  `import/no-duplicates` under `--deny-warnings`. A post-pass merges
  declarations sharing a specifier and type-only-ness; the `import type` plus
  `import` pair from one module is left alone, since that form is allowed.

Splitting one barrel import into several genuinely adds lines, which pushed
`terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character
import must wrap, and neither local type collapses onto one line (101 and 116
characters). Rather than contort a type declaration to fit a line budget,
`collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` —
they are pure structural operations on the layout tree and independent of PTY
ownership. `visible-worktrees.ts` similarly loses its own mini-barrel
re-export of `isDefaultBranchWorkspace`, with the four real consumers
repointed at the declaring module. No `max-lines` bypass added.

Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted
first — these projects are `composite: true` and reuse stale caches); the full
`pnpm lint` green, not just bare oxlint — the narrower local check is what let
the duplicate imports reach CI; max-lines ratchet OK at 344.
2026-08-13 22:48:24 -07:00
d305e48547 fix(gitlab): guard against non-array API responses in MR/issue listing (#12911)
* fix(gitlab): guard against non-array API responses in MR/issue listing

fetchIssuesAsWorkItems and listMergeRequests parsed glab's JSON output
and called .map straight on it. When the GitLab API returns a JSON
object instead of an array (error body, unexpected shape) on a
successful exit, this crashed with a bare TypeError that got
misclassified as "Failed to load issues: JSON.parse(...).map is not
a function" instead of a useful message.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(gitlab): cover listIssues and keep payloads out of error classification

The guard missed listIssues in issues.ts — the RPC-backed issue list that
produces the reported "Failed to load issues: JSON.parse(...).map is not a
function". Hoist the guard into glab-api-response.ts so both files share it.

The thrown message is fed to classifyGlabError, which substring-matches it.
A response payload is content, not a diagnostic: an MR titled "fix network
timeout" classified as network_error and the canned copy replaced the payload
the user needed. Report a GitLab error envelope by its own message, and mark
an opaque body so classification is skipped.

* test(gitlab): make the list-guard tests fail on the regressions they name

Two assertions were vacuous under mutation. The envelope test used a "403
Forbidden" message whose keyword matches earlier in the classifier chain than
its sibling payload, so leaking the payload into classification still passed;
it now uses a 404 envelope beside a "403 forbidden" sibling. No call-site test
carried a classifier keyword, so deleting the marker-error branch entirely
failed only one unit test; the MR API path now uses a keyword-bearing body.

Also give the non-list branch the same "Failed to load issues" prefix as every
other list error, cover the `{ error }` envelope field, and pin the thrown type.

* test(gitlab): pin the reported-payload bound

Removing the 300-char slice survived the whole suite, and the banner's
break-words now depends on it. Name the limit and assert both branches
truncate, plus the envelope falling through a blank message to `error`.

* test(gitlab): pin message-over-error envelope precedence

Swapping the lookup order passed the whole suite. Anchor the bound regex too
so it cannot match an incidental ": " near the end of a message.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-08-10 01:10:12 -07:00
Pongsakorn PaetrakulandJinwoo-H 6aafb1d318 fix(gitlab): include bridge/child pipeline jobs in Checks (#12863)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 14:11:31 -07:00
NeilandOrca 0927b9c156 fix(gitlab): load pipeline job traces in the Checks side panel (#7732) (#12266)
* test(repro): demonstrate #7732 GitLab pipeline job details never load in Checks panel

Co-authored-by: Orca <help@stably.ai>

* fix(gitlab): load pipeline job traces in the Checks side panel (#7732)

Expanding a GitLab pipeline job in the Checks panel always showed
"No inline details are available for this check.": the mapper dropped the
numeric job id, `PRCheckDetail` had nowhere to carry it, and every consumer
called the GitHub check-runs API, which returns null for a GitLab job.

- carry `gitlabJobId` on `PRCheckDetail` and add the `gitlab-job:` branch to
  all three identity ladders (panel rows, editor tabs, fix-prompt keys) so
  same-stage jobs with no web_url stop colliding
- add a runtime-routed trace client so SSH/remote workspaces work, not just
  local IPC, and thread the MR's `projectRef` for fork pipelines
- bound the trace in main via the existing `sliceCheckLogTail` (now shared,
  not GitHub-only) so a multi-megabyte CI log never crosses the 1 MB
  transport frame cap; strip ANSI/section markers up to the CR only, which
  keeps each section's visible header and command echo
- render the excerpt inline instead of "Log tail available in full details."
- feed GitLab traces to "Fix with AI", which previously sent bare check names
- skip the fetch for jobs that cannot have a trace (created/manual/skipped)
  so GitLab's 404 does not replace the benign empty state, and re-arm a
  failed load when the job's state changes since the panel has no retry

Co-authored-by: Orca <help@stably.ai>

* fix(gitlab): treat a missing job log as an empty log, not an error (#7732)

Round-1 review follow-up.

- a job canceled before it started (or whose log was erased/expired) is
  `completed`/`cancelled`, so the panel fetched its trace, GitLab answered 404,
  and `classifyGlabError`'s issue-edit copy ("Issue not found — it may have been
  deleted.") landed verbatim on the auto-expanded check row; main now maps that
  404 to an empty trace so the row keeps its benign empty state
- keep a missing project a real error (GitLab masks unauthorized projects as
  404) and add `classifyJobLogError` so 403/unknown failures stop borrowing
  issue-edit wording on a job-log read
- broaden the empty-log copy in all five catalogs: it now covers erased and
  expired logs, not only jobs that never ran
- e2e: derive the repro screenshot dir from `process.cwd()` (or an env
  override) instead of a hardcoded POSIX path to a throwaway worktree
- bound the raw trace before the ANSI/section passes so a multi-megabyte log
  is not scanned in full on the main-process event loop
- drop the redundant `if (repo)` in `handleFixChecksWithAI` and the now-dead
  "Log tail available in full details." catalog entry

Co-authored-by: Orca <help@stably.ai>

* fix(gitlab): address review — project ref on reload, retry re-arm, IPC timeout

- Carry the MR's GitLab project ref on the check-details tab so reloading a
  fork/cross-project job tab fetches the trace from the pipeline's own project.
- Re-arm the sidebar retry when a details load resolves to null, not only when
  it throws; a detail-less row otherwise never retried after the job moved on.
- Bound the local `gl.jobTrace` IPC call with the same 30s timeout the runtime
  RPC path uses — glab runs without a subprocess timeout in main.
- Document that the trace 404 -> empty-log mapping is deliberately broad.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-03 22:48:37 -07:00
余辉andOrcaWin 4517088c42 fix(gitlab): refresh self-hosted provider detection (#9909)
* fix(gitlab): refresh self-hosted provider detection

* fix(gitlab): preserve auth refresh during host probe

* fix(gitlab): merge refreshed auth hosts linearly

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 00:56:05 -07:00
NeilandOrca aab112933e Revert "fix(memory): bound OOM-prone accumulators (#10179)" (#10255)
Co-authored-by: Orca <help@stably.ai>
2026-07-23 18:35:31 -07:00
Neil 8f40ddf328 fix(memory): bound OOM-prone accumulators (#10179) 2026-07-23 06:22:56 -07:00
Brennan Benson aca7d50bab Stop attaching stale closed PRs/MRs to default-branch checkouts (#9469)
* Stop attaching stale closed PRs/MRs to default-branch checkouts

On the repo default branch, the implicit head-branch PR lookup (state=all)
could attach a historical closed/merged PR whose head ref was the default
branch name and show its wrong diffs and checks (#9171).

Add a shared default-branch guard: an implicit branch-name match on the
repository's default branch never surfaces a non-open review. Applied at
the branch-lookup choke point of all five provider clients (GitHub,
GitLab, Bitbucket, Azure DevOps, Gitea). Explicitly linked reviews are
exempt; open reviews from the trunk stay visible; resolution is lazy
(zero git calls unless a non-open candidate appears), TTL-cached,
transport-aware (local/WSL/SSH), probe-time-bounded, and fails open.

* Treat stuck-locked GitLab MRs as non-open in the default-branch guard

Three code-review lanes flagged (one reproduced) that 'locked' — normally
a seconds-long merge transition, but a known GitLab wedge state — leaked
past the closed/merged-only check and would re-create the #9171 symptom
for a stuck-locked MR whose source branch is the trunk.

* Bound default-branch lookup to one refresh budget

* Coalesce default-branch resolution probes
2026-07-20 11:54:30 -07:00
NeilandOrca 190de8223e refactor(comments): slim verbose comments in main integrations (git/providers/…) (#9543)
Collapse multi-line explanatory comment blocks into single-line "why" statements
per AGENTS.md ("Document the Why, Briefly"): drop restatements of the code and
mechanism narration; keep the non-obvious reason, external refs, and directives.

Comments-only — verified no code changed via a Babel/esbuild comment-strip
token-equality gate against origin/main; typecheck and oxlint clean.

Area: main — git, source-control, providers & integrations. 40 files changed, 1432 insertions(+), 4473 deletions(-).

Co-authored-by: Orca <help@stably.ai>
2026-07-20 03:18:28 -07:00
OrcaWin b320bcb374 fix(gitlab): bound and coalesce auth probes (#9476) 2026-07-19 16:54:31 -07:00
Jinjing d67ede1594 Implement confirm-only PR panel composer with classified error blocking (#9428)
* Clarify PR panel guidance: classify errors and confirm-only composer

Replace the ambiguous GitHub hosted-review boolean with a four-state evidence
model (found/positive_unresolved/not_found/unknown) so "No PR found" never
appears without an accepted lookup result. Classify GitHub refresh failures
into types (rate_limited, auth, network, permission, repo_unavailable,
gh_unavailable, unknown) for stable, honest copy. Confirmed-only composer:
preserve drafts across transient failures; hide Create during hard errors and
positive-unresolved evidence. Hard errors clear only when an eligibility
request starts after the error and returns an accepted outcome. Propagate
error types and unified retry schedule through the store. Sync mobile parity
with shouldOpenChecksPanelCreateComposer gating. Localize all new copy.

* Clarify PR panel guidance: classify errors and confirm-only composer

Add reviewLookupOutcome to hosted-review eligibility and thread it through
the panel so it never claims "No PR found" without accepted evidence. A
failed lookup is unavailable, not a settled no-PR. Fail closed on positive
unresolved evidence, hard refresh errors, and unavailable lookups. Add
structured GitHub refresh-error classification with Retry-After parsing.
Implement confirmed-only composer gating based on fresh, matching-context
eligibility with hard-error clearing. Mobile gates on reviewLookupOutcome
to prevent false Create claims. Surface throwOnFailure variants for each
provider so transport failures cross the RPC boundary instead of collapsing
to null. (Design success criteria 1–4; invariant 8.)

* Add exec-error helpers for subprocess error classification

Extracts stderr/stdout parsing and Retry-After detection into a
lightweight module that can be imported without pulling in the heavier
runner machinery. Supports PR-refresh error classification and proper
rate-limit handling for gh commands.

* test(mobile): include reviewLookupOutcome in create eligibility fixtures

Create / Push & Create now fails closed unless the lookup is not_found.
Update mobile test fixtures so accepted-no-PR cases can still proceed.

* Add OrThrow mock variants to forge-provider test mocks

forge-provider resolves branch reviews via the OrThrow variant so
lookup failures surface as unavailable instead of "no PR found".
2026-07-19 16:32:36 -07:00
Jinwoo Hong e537953d8f Fix GitLab auth diagnostic waking WSL (#7967) 2026-07-09 18:06:30 -04:00
Neil ce687221d3 lint(unicorn): enable prefer-number-properties, prefer-array-find, prefer-array-index-of (#7516)
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.

prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
  argument, Number.isNaN does not. Verified every call site already passes a
  number (Number.parseInt results, number-typed fields, Date.getTime()), so the
  conversion is behavior-preserving today and guards against a future non-numeric
  argument silently coercing.

prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
  Drops the intermediate array and short-circuits.

prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).

Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
2026-07-05 23:56:37 -07:00
a10e1d7584 fix(gitlab): recognize self-hosted GitLab on non-default ports over SSH connections; stop one project failing the whole issues panel (#5400)
* fix(gitlab): port-aware self-hosted host recognition

Use the URL host (including a non-default web/API port) as the GitLab
host identity instead of the port-less hostname, and match known hosts
port-aware:

- A known-host entry without a port matches any port of the same
  hostname (preserves legacy bare-host and gitlab.com recognition).
- A known-host entry WITH a port matches only that exact host:port, so
  two services sharing a hostname on different ports (e.g. a GitLab and
  a Gitea) are no longer conflated.
- For ssh/git remotes the port is a transport port (e.g. ssh :2222) and
  is dropped; for http(s) remotes the port is the endpoint and kept.
- Also capture an optional :port in parseGlabAuthStatusHosts so a
  self-hosted GitLab on a non-default port is discovered correctly.

* fix(gitlab): per-connection known-hosts cache + port-aware auth-status parsing

getGlabKnownHosts() was connection-blind and cached process-globally,
and on any failure it cached [gitlab.com] forever — so a repo on an SSH
connection never discovered its self-hosted host once a probe failed
before the tunnel was ready.

- getGlabKnownHosts(connectionId?) now caches per connection so a
  connected repo's authenticated hosts don't leak into the local
  context (or vice versa).
- The failure fallback (canonical default) is no longer cached, so a
  later probe can re-discover the real host once auth/tunnel is ready.
- parseGlabAuthStatusHosts captures an optional :port on both the
  'Logged in to <host>' and header-style lines, keeping two services on
  the same hostname distinct by port.

* fix(gitlab): isolate unresolvable projects instead of cwd-fallback that hits exit 128

listIssues/getIssue fell back to an unscoped 'glab issue list' / 'glab
issue view' that infers the project from cwd. For a repo on an SSH
connection cwd is not the repo dir, so glab runs git resolution in a
non-repo dir and fails with 'git: exit status 128'. In an 'All projects'
aggregate one such failure could sink the whole issues panel.

When a projectRef cannot be resolved, return a structured, isolated
per-project result (listIssues: { items: [], error: not_found };
getIssue: null) and spawn no glab subprocess. Behavior is unchanged when
a projectRef IS resolved (the scoped '-R' / 'api projects/...' path).

* fix(gitlab): recognize modern /-/work_items/<iid> issue URLs

Modern GitLab emits issue URLs as /-/work_items/<iid> in addition to the
legacy /-/issues/<iid>. The URL classifiers only matched /-/issues/, so
work-item-form issue links went unrecognized.

Extend the gitlab-links parsers (parseGitLabIssueOrMRNumber /
parseGitLabIssueOrMRLink, which also backs isWorkItemLookupText) and
isGitLabIssueUrl to accept /-/work_items/<iid>, mapping it to an issue
work item with the same project-path + iid extraction.

* fix(gitlab): thread connectionId into getGlabKnownHosts call sites

Follow the existing connectionId-threading pattern: pass the repo's
connectionId into every getGlabKnownHosts() call (client.ts,
work-item-details.ts, orca-runtime.ts) so the per-connection known-hosts
cache is keyed correctly and self-hosted hosts are discovered against
the right glab context.

* docs(gitlab): use generic example hosts in comments

* fix(gitlab): pass self-hosted host:port via GITLAB_HOST (glab --hostname rejects ports)

* polish: satisfy oxlint curly + oxfmt on merged gitlab port-recognition code

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Ptah-CT <auctor@xinfty.space>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 16:24:01 -07:00
NeilandOrca 31bfeff01d fix(gitlab): thread MR search query and surface MR base failures (#6263) (#6591)
Defect 1: the typed GitLab MR search query was dropped before reaching
the API. Thread query?: string end-to-end through the renderer effect,
the source-lookup, the preload/RPC args, and the desktop IPC handlers
(which previously passed a hardcoded undefined), and honor it on both the
glab REST path (&search=) and the cwd-inferred 'glab mr list' fallback.

Defect 2: when MR base resolution failed the renderer silently returned,
leaving baseBranch undefined so the worktree was created off the repo
default branch (origin/master) with no feedback. Surface the failure via
toast and clear stale base state, mirroring the GitHub PR path. Also make
resolveManagedMrBase resilient to an optional compare-base (target branch)
fetch failure: degrade gracefully by dropping compareBaseRef instead of
aborting, so a merged MR with a deleted target ref still resolves to its
valid source-branch base.

Fixes #6263

Co-authored-by: Orca <help@stably.ai>
2026-06-28 18:05:29 -07:00
0ec3882cb8 Add project Windows runtime selection (#5519)
* Add project Windows runtime selection

* Fix project Windows runtime selection

Co-authored-by: Orca <help@stably.ai>

* fix: preserve WSL shell variables

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <neil@stably.ai>
2026-06-17 16:08:14 -07:00
Brennan BensonandOrca 7077736602 Create folder workspaces from project groups (#5474)
Co-authored-by: Orca <help@stably.ai>
2026-06-16 18:04:50 -07:00
Neil 9a7f4f57c9 perf: bound GitLab todos fetch (#4178)
Fetch only the first GitLab todos page instead of paginating every pending todo page.
2026-05-31 07:06:37 -07:00
Neil 579623a09d fix: bound gitlab rate limit cache (#4156) 2026-05-31 06:16:45 -07:00
Neil 54048312fe Fix GitLab branch pipeline fallback (#4118) 2026-05-31 04:55:53 -07:00
Neil 1d4616b67d fix: paginate gitlab work item issues (#4069)
Paginate GitLab work item issue queries.
2026-05-31 03:19:16 -07:00
Neil a090176de9 feat: close GitLab review parity gaps (#4001) 2026-05-31 00:05:15 -07:00
Neil c99eb29bb0 chore: remove unused code 2026-05-30 11:05:58 -07:00
Pablo Lozano 395e98f015 fix(gitlab): restore cwd fallback in listMergeRequests
Restore the glab mr list cwd fallback when listMergeRequests cannot resolve a project ref locally. Preserve the existing error envelope behavior and guard SSH-backed repos from running cwd-less fallback commands that could resolve an unrelated local project.
2026-05-22 01:28:16 -07:00
Jinwoo HongandOrca b1973657ea Add mobile Tasks parity (#2452)
Co-authored-by: Orca <help@stably.ai>
2026-05-21 20:26:07 -07:00
Pablo LozanoandNeil 7cf154592c Add GitLab Issues tab and workspace creation for issues/MRs (#2431)
* feat: add GitLab Issues tab and workspace creation for issues/MRs

- Extend TaskPage GitLab section with Issues | MRs tabs (outside card)
  plus a repo selector via RepoMultiCombobox, matching the GitHub UX.
- Add separate IPC paths: listIssues (with "Assigned to me" filter)
  and listMRs (with Open/Closed/Merged/All filters).
- Add "Start workspace" (→) button to every GitLab row and
  "Create workspace" action inside GitLabItemDialog for both issues
  and MRs.
- Extend CreateWorktreeArgs / worktree.create RPC to accept
  linkedGitLabMR and linkedGitLabIssue, and wire them through
  the runtime, IPC, preload, and renderer store slices.
- Fix self-hosted GitLab support by falling back to glab CLI
  (issue view / mr view / issue list / mr list) when the remote
  host is not in getGlabKnownHosts().
- Update useComposerState to initialise GitLab-linked context
  from initialLinkedWorkItem.

* Remove logs

* fix: address GitLab issues review findings

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-05-21 00:37:30 -07:00
bca39bc928 Add GitLab and Bitbucket hosted review support (#1839)
* feat(gitlab): add foundational glab runner, types, and issue operations

First slice of GitLab support, mirroring src/main/github/ structurally
without refactoring the working GitHub path.

- runner: add glabExecFileAsync parallel to ghExecFileAsync (same WSL
  routing and retry policy; HTTP-status / network classification is
  provider-agnostic so the existing helpers are reused).
- types: GitLabProjectRef carries host alongside path so self-hosted
  instances and nested groups round-trip through the IPC layer. Mirror
  shapes for MR/issue/work-item/comment/file/assignable-user.
- gitlab/gl-utils: concurrency limiter, error classification, project-ref
  resolution honoring upstream/origin preference, and known-host
  discovery via `glab auth status` so non-gitlab.com remotes are
  recognized after the user authenticates.
- gitlab/mappers: pipeline-job → check-status mapping, MR state
  resolution (including draft inferred from `Draft:`/`WIP:` title
  prefix), and pipeline rollup.
- gitlab/issues: full issue CRUD via `glab api` against URL-encoded
  project paths, with the same upstream/origin preference semantics as
  the GitHub side.

63 unit tests passing across gl-utils / mappers / issues. Both
typecheck:node and typecheck:web clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): preflight glab auth check and URL parser

- preflight: probe `glab --version` + `glab auth status` alongside the
  existing gh checks. PreflightStatus.glab is optional so renderer call
  sites that only render git/gh keep typechecking; consumers gating on
  GitLab affordances opt in via `glab?.authenticated`.
- gitlab-links: parse GitLab issue and merge-request URLs honoring (a)
  arbitrary self-hosted hosts via the project-internal `/-/` separator
  rather than locking to gitlab.com, (b) nested group paths, and (c)
  GitLab's `!42` MR convention alongside `#42`.

26 unit tests added (5 new preflight cases, 21 URL-parser cases). Full
typecheck (node + cli + web) clean. Pre-existing runtime/orchestration
test failures unrelated to this branch — Node 25 vs the project's
pinned Node 24 engine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): MR list/get + paginated `glab api -i` helper

Lean mirror of github/client.ts focused on the workspace-from-MR
keystone. Adopts GitLab-native filter semantics (Open / Merged /
Closed / All) instead of porting GitHub's search-DSL — that path is
covered by the upcoming My Todos surface.

- gl-utils: glabApiWithHeaders + parseGlabApiResponse for strict
  pagination via X-Total / X-Total-Pages on `glab api -i` output.
  CRLF / LF tolerant; status line never leaks into the headers map.
- types: MRListState, GitLabPagedResult<T>, ListMergeRequestsResult.
- mappers: mapMRToWorkItem + mapIssueToWorkItem produce the unified
  GitLabWorkItem shape the picker consumes. isCrossRepository derived
  from source_project_id !== target_project_id; deterministic id
  fallback when the per-MR detail endpoint omits global id.
- client: getAuthenticatedViewer, getMergeRequest (with head pipeline
  rolled up), getMergeRequestForBranch (mirrors github/getPRForBranch
  semantics including refs/heads/ stripping and detached-HEAD guard),
  listMergeRequests (paginated), getWorkItemByProjectRef (paste-URL
  flow). Re-exports issues + projectRef helpers so callers don't have
  to know the gl-utils module split.

35 new tests (98 total in src/main/gitlab/), full typecheck clean.
Tests split into client.test.ts + client-mr.test.ts to stay under the
oxlint max-lines budget — matches github/client*.test.ts pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): worktrees:resolveMrBase IPC + linkedGitLab* persistence

The workspace-from-MR keystone. Mirror of worktrees:resolvePrBase
shape and semantics — caller passes mrIid (with optional source_branch
/ isCrossRepository hints), handler returns either a remote/branch
ref (same-project MRs) or a SHA fetched from
refs/merge-requests/<iid>/head (fork MRs).

- types: linkedGitLabMR / linkedGitLabIssue on Worktree + WorktreeMeta.
  Marked optional so existing test fixtures and persisted older
  worktrees that pre-date these fields keep typechecking and loading
  without a migration.
- persistence: getDefaultWorktreeMeta initializes both fields to null.
- worktree-logic: mergeWorktree carries them through from meta.
- worktrees IPC: resolveMrBase mirrors resolvePrBase. Resolves the
  GitLab project via getProjectRef + known-host discovery, fetches the
  MR work-item to derive source_branch + isCrossRepository when those
  hints aren't provided, and uses GitLab's refs/merge-requests/<iid>/head
  for fork MRs (parallel of GitHub's refs/pull/<N>/head).
- tests: 6 fixture updates for the new optional fields. Full
  typecheck (node + cli + web) clean; 165 tests passing across
  src/main/gitlab/, preflight, worktree-logic, and gitlab-links.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): IPC channels + preload bindings (gl.*)

Wire the GitLab backend to the renderer. Lean v1 surface — issues
CRUD, MR list/get/getForBranch, viewer, project slug, paste-URL
work-item lookup. Skips workItemDetails / listWorkItems-combined /
listTodos until the matching backend pieces land.

- main/ipc/gitlab.ts: thirteen handlers under the `gitlab:*` channel
  prefix with the same assertRegisteredRepo guard the gh handlers use.
  listIssues unwraps the structured result envelope to bare items[]
  to match window.api.gh.listIssues' shape; consumers that need the
  classified error can graduate to the envelope later.
- main/ipc/register-core-handlers.ts: register alongside gh.
- preload/api-types.ts: typed `gl: { ... }` block parallel to the
  existing `gh: { ... }`. Imports the new GitLab types so renderer
  code consuming the preload gets full inference.
- preload/index.ts: runtime `gl: { ... }` exposes wired to ipcRenderer.

Full typecheck (node + cli + web) clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): workspace-from-MR via paste-URL (keystone end-to-end)

The first user-visible GitLab moment. Pasting a GitLab issue or MR URL
into the workspace name field now resolves through the full pipeline
to a created workspace with the right base ref and linkedGitLab*
persisted. The dedicated GitLab tab + state-filter chips remain a
follow-up; everything below it is wired.

- shared/lib/new-workspace.ts: LinkedWorkItemSummary.type accepts
  `'mr'` alongside `'issue' | 'pr'`. Renderer code that switches on
  type explicitly handles each kind.
- ui store slice: NewWorkspaceDraft mirrors the new linked slots so
  drafts persist GitLab selections across navigation. Optional fields
  for backward compatibility with drafts saved before this branch.
- useComposerState:
  - linkedGitLabIssue / linkedGitLabMR state, draft persistence,
    repo-switch reset, applyWorktreeMeta wiring.
  - applyLinkedGitLabWorkItem mirrors applyLinkedWorkItem; reuses
    getLinkedWorkItemSuggestedName by structurally projecting the
    GitLab item onto the helper's input shape.
  - handleSmartGitLabItemSelect parallels handleSmartGitHubItemSelect:
    for picked MRs, calls window.api.worktrees.resolveMrBase to
    resolve the base ref (refs/merge-requests/<iid>/head for fork
    MRs) and threads it through handleBaseBranchMrSelect.
  - "was MR !N" reset hint when a repo switch wipes a GitLab
    selection — `!N` matches gitlab.com's MR-reference convention.
- preload: window.api.worktrees.resolveMrBase + window.api.gl.* are
  already in. ComposerCardProps grows onSmartGitLabItemSelect (+
  optional onBaseBranchMrSelect).
- SmartWorkspaceNameField:
  - Paste-URL detection: parseGitLabIssueOrMRLink (host-agnostic via
    `/-/` separator) → window.api.gl.workItemByPath → row in the
    dropdown → click → forwarded to onGitLabItemSelect.
  - SmartWorkspaceNameSelection union, RowEntry union, RowIcon,
    RowLabel, SelectionIcon all carry the gitlab-mr / gitlab-issue
    kinds. MR rows show `!N` prefix; issue rows show `#N`.
  - Tab UI not added in this commit — paste-URL works in 'smart'
    mode, the dedicated tab + Open/Merged/Closed/All chips lands
    in a follow-up.
- NewWorkspaceComposerCard: forwards onSmartGitLabItemSelect to the
  picker.

Full typecheck (node + cli + web) clean. 165 unit tests passing in
affected files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): GitLab tab in SmartWorkspaceNameField with state filter

The discoverable demo path. The picker now has a "GitLab" tab — when
selected it lists the project's MRs filtered by state via
`gitlab:listMRs`, with an Open / Merged / Closed / All chip strip
that mirrors gitlab.com's MR-page tab strip. Paste-URL detection in
'smart' mode is unchanged; the new tab simply makes the surface
discoverable without requiring a URL.

- SmartNameMode gains 'gitlab'; Gitlab icon (lucide) added to the
  MODES array between GitHub and Branch.
- MrStateFilter / MR_STATE_FILTERS centralizes the four chip values
  so the labels stay GitLab-native (Open vs the GraphQL 'opened').
- listMRs effect: fires when mode === 'gitlab' and no GitLab URL is
  in the input, with the current state filter and a page-1 fetch
  bounded by RESULT_LIMIT.
- Paste-URL effect now coexists with the list effect: it owns
  gitlabItems while a URL is in the input, the list effect owns it
  otherwise. Switching tabs no longer clears the list.
- Chip strip rendered above the popover's CommandList only when
  mode === 'gitlab'. Buttons use the same Button component the rest
  of the picker uses for visual consistency.

Full typecheck (node + cli + web) clean. 165 unit tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): GitLab source on Tasks screen

The Tasks screen now offers GitLab as a third source alongside GitHub
and Linear. Selecting it surfaces MRs and issues for the primary
selected repo with a state filter (Open / Merged / Closed / All) that
mirrors gitlab.com's MR-page tab strip. Skips cross-repo aggregation,
search DSL, and Projects mode for v1 — those layers are GitHub-API-
shaped and would need a parallel store slice that is not worth porting
ahead of the actual demand for them.

- shared/types: GlobalSettings.defaultTaskSource accepts 'gitlab'.
- TaskPage:
  - TaskSource union grows a 'gitlab' member; SOURCE_OPTIONS adds the
    Gitlab icon between GitHub and Linear so the toolbar order matches
    SmartWorkspaceNameField for cross-surface consistency.
  - GITLAB_TASK_FILTERS centralizes the four chip values.
  - Per-source state slim (matches Linear's pattern) — gitlabFilter,
    gitlabItems, gitlabLoading, gitlabError, gitlabRefreshNonce.
  - Data-fetch effect runs Promise.all over `window.api.gl.listMRs`
    and `window.api.gl.listIssues` for the primary repo, merges and
    sorts by updatedAt desc. 'merged' filter skips the issue fetch
    (GitLab issues are 'opened' / 'closed' only).
  - Filter bar block parallel to Linear's, with chips + a refresh
    icon-button.
  - List block: 5-column grid (ID / Title / Type+State / Updated /
    Open-link). Row click opens the web URL — the GitLabItemDialog
    is a follow-up commit, but the row affordance is enough for the
    Tasks-screen demo.
  - GitLab MRs render as `!N`; issues render as `#N` to match
    gitlab.com's reference convention.

Full typecheck (node + cli + web) clean. 165 unit tests still
passing in affected files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): GitLabItemDialog (minimal) + Tasks screen wiring

Clicking a GitLab row on the Tasks screen now opens a side-sheet
preview with the item's title, state, author, and description body
rendered as markdown. "Open in browser" footer button stays as the
escape hatch; opening from the row is dialog-first now (matching the
GitHub side's row-click-to-dialog pattern). Files / comments /
pipeline tabs are deferred — they mirror substantial GitHub-side
surface area (work-item-details ~550 lines, GitHubItemDialog 2680
lines) and are not blocking the demo.

- types: MRInfo and GitLabIssueInfo gain optional description /
  author / authorAvatarUrl. Optional because list endpoints strip
  them; populated on detail-endpoint reads (`getMR` / `getIssue`).
- mappers: mapMRInfo and mapGitLabIssueInfo now pass description /
  author / avatar through when present. Skipped (rather than
  defaulted to '') so callers can distinguish "no body authored"
  from "this came from a list".
- GitLabItemDialog: new ~200-line side sheet. Fetches the detail
  payload via `window.api.gl.mr` / `gl.issue` on open; renders
  CommentMarkdown for the description (reused from the GitHub
  side); falls back to "No description." when the body is blank.
  State badge tones picked locally — GitLab's MR state space is
  wider than GitHub's so coupling them buys nothing.
- TaskPage: GitLab row now uses a div role=button with keyboard
  handling so the inner Open-in-browser <button> nests cleanly
  (HTML disallows nested <button>s, React would warn). Row click
  sets gitlabDialogItem; the small ExternalLink icon stops
  propagation so it still opens the URL.

Full typecheck (node + cli + web) clean. 165 unit tests passing in
affected files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): My Todos cross-project view on Tasks screen

The GitLab tab now has a Project | My Todos sub-toggle. "My Todos"
fetches gitlab.com/dashboard/todos via `glab api todos?state=pending`
and surfaces them in a separate table — action / title / project /
updated. This is the closest GitLab-native equivalent of GitHub's
notifications/inbox and lands in lieu of porting GitHub's search-DSL
which doesn't translate.

- shared/types: GitLabTodo type with action_name, target_type/iid,
  target_url, project_path, author, updated_at. action_name kept as
  open-ended string because new GitLab versions extend the verb set.
- gitlab/client.ts: listTodos uses `glab api --paginate todos?state=
  pending&per_page=50`. User-scoped — cwd doesn't matter, but the
  IPC path-validation guard still requires *some* registered repo
  path so we keep the signature consistent with the rest of gl.*.
- IPC: `gitlab:todos` channel; preload `gl.todos`.
- TaskPage:
  - gitlabView ('project' | 'todos') gates which list to render.
  - Sub-toggle row above the chip strip; chips are hidden on the
    Todos view since pending state has no Open/Merged/Closed axis.
  - Refresh button serves both views (uses gitlabRefreshNonce).
  - Todos table: 5-col grid, action verb (snake_case → spaces),
    target title, project path (mono font for repo-likeness),
    updated date, open-link icon. Row click opens target_url.

Full typecheck (node + cli + web) clean. 165 unit tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): gitlabProjects settings (recents auto-tracked) + tests

Settings persistence for GitLab project preferences plus tests for
the surface added since the last green run.

- shared/types: GitLabProjectSettings { pinned, recent } and an
  optional GlobalSettings.gitlabProjects slot. Optional for
  backward compat with profiles saved before this branch — the
  persistence merge fills the empty default.
- shared/gitlab-projects: pure helper computeNextGitLabRecents that
  prepends-and-dedupes by host+path, caps at GITLAB_RECENTS_MAX
  (10). Pulled out of the IPC handler so it tests without mocking
  Store.
- gitlab IPC: workItemByPath handler now pushes the resolved
  project ref onto recents on success. 404 / auth-fail lookups
  do not pollute the list — recents reflects projects the user
  actually read.

Tests added (12 new, 177 total passing in affected files):
- gitlab-projects.test: prepend, dedupe, host-vs-host distinct,
  cap at max, no input mutation.
- client.test: listTodos mapping, defensive state coercion,
  empty-on-error fallback, missing-target field defaults.
- mappers.test: description / author / authorAvatarUrl pass-
  through on both mapMRInfo and mapGitLabIssueInfo, plus the
  "absent vs blank" distinguishing assertion.
- mappers-workitem.test (split): mapMRToWorkItem + mapIssueToWorkItem
  cases moved out of mappers.test.ts to keep both files under the
  oxlint max-lines budget.

Full typecheck (node + cli + web) clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): combined listWorkItems IPC + TaskPage refactor

Centralize the MR + issue merge logic that TaskPage was doing inline
into a single backend function and IPC channel. Future callers (the
picker's GitLab tab, any new widget) get the merge / sort / state-
mapping rule for free. The TaskPage effect drops from 60 lines of
inline orchestration to a single call.

- gitlab/issues: listIssues now accepts an IssueListState so the
  combined caller can ask for closed / all instead of always opened.
  CLI fallback path picks the right --opened / --closed / --all flag
  per glab version. Existing callers keep the 'opened' default.
- gitlab/client: listWorkItems(state, page, perPage, preference) fans
  out listMergeRequests + a raw issues fetch in parallel, merges by
  updatedAt desc, returns a GitLabPagedResult<GitLabWorkItem>.
  Bypasses listIssues for the issues side because IssueInfo strips
  updated_at — the combined sort needs it.
  state='merged' skips the issues fetch entirely (issues don't have
  a merged lifecycle).
- IPC: new gitlab:listWorkItems handler.
- preload: gl.listWorkItems alongside gl.listMRs.
- TaskPage: GitLab fetch effect now calls gl.listWorkItems and stops
  re-implementing the merge. Same UX, fewer moving parts.

Tests added (8 new in client-work-items.test.ts; +1 fix to
issues.test.ts for the new url-param order):
- merge ordering by updatedAt desc
- 'merged' state skips issues fetch
- closed / all state pass-through
- not_found envelope when project ref unresolved
- mr-error vs issue-side success interleaving
- combined error surfacing on either side failing

Full typecheck (node + cli + web) clean. 117 unit tests passing in
src/main/gitlab/ and src/shared/gitlab-projects.test.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): work-item-details + dialog Conversation/Pipeline tabs

Task 3 lean version. The minimal description-only dialog grows two
new tabs (Conversation / Pipeline) and four footer actions
(close / reopen / merge / comment). Files-tab and inline review-
comment positioning stay deferred — they mirror substantial GitHub-
side surface (GitHubItemDialog is 2680 lines, work-item-details.ts is
551) and the v1 demo doesn't need them.

- shared/types: GitLabPipelineJob (id, name, stage, status, webUrl,
  duration), GitLabWorkItemDetails (item + body + comments[] +
  pipelineJobs?[]). Mirrors GitHubWorkItemDetails layout.
- main/gitlab/work-item-details: getWorkItemDetails(repoPath, iid,
  type) fans out parallel reads — issue: detail + discussions; MR:
  detail + discussions, then pipeline jobs follow-up keyed off
  head_pipeline.id. Discussion → MRComment flatten skips system
  notes (auto-generated activity entries) so the conversation tab
  shows only user content. Inline-review position carried through
  as `path` + `line` for v1.5 to consume.
- main/gitlab/client: closeMR / reopenMR / mergeMR / addMRComment
  mutations. mergeMR accepts the same 'merge' | 'squash' | 'rebase'
  union as the GitHub side; close/reopen treat "already X" stderr
  as success since the desired state is reached.
- IPC: gitlab:workItemDetails, closeMR, reopenMR, mergeMR,
  addMRComment channels; preload `gl.*` bindings parallel.
- GitLabItemDialog rewrite: three Tabs (Description / Conversation /
  Pipeline-MRs-only) + footer with comment composer + state-aware
  Merge / Close / Reopen buttons. Cmd/Ctrl+Enter sends the comment
  to match gitlab.com's textarea shortcut. Refresh icon in the
  header re-fetches via a refreshNonce. eslint-disable max-lines on
  the dialog matches the GitHub-side equivalent's reasoning.

Full typecheck (node + cli + web) clean. 184 unit tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): sidebar icon, Smart-mix MRs, Integrations card, "Project MRs" rename

Four follow-up fixes that surfaced from smoke-testing:

- SidebarNav: GitLab icon next to GitHub / Linear in the Tasks-row
  shortcut strip; clicks open the Tasks page already filtered to the
  GitLab source. ui.ts taskPageData.taskSource union grows to accept
  'gitlab' so the openTaskPage call typechecks.
- SmartWorkspaceNameField: list-MRs effect now fires in 'smart' mode
  too, not just on the dedicated GitLab tab. The mixed picker
  surfaces the user's project MRs alongside GitHub items. Paste-URL
  effect still wins when a GitLab URL is in the input — the list
  effect bails on parsedGlLink !== null.
- TaskPage: GitLab toggle relabels "Project" → "Project MRs" so the
  pairing with "My Todos" reads more clearly.
- IntegrationsPane: new GitLab card mirroring the GitHub card —
  status badge (checking / connected / not-installed / not-
  authenticated), install link to gitlab.com/gitlab-org/cli, copy-
  ready `glab auth login` block, learn-more link to the auth/login
  doc, re-check button. Search-entry registered so settings search
  finds it. eslint-disable max-lines justified by the same pattern
  that already lives there for GitHub + Linear.
- preload: PreflightStatus.glab is optional on the type so older
  payloads typecheck; consumers gate on the optional chain.

Full typecheck (node + cli + web) clean. 184 unit tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gitlab): multi-repo aggregation on Tasks screen

Mirrors GitHub's cross-repo behavior. Previously the GitLab tab only
queried the first selected repo; now it fans out to every eligible
selected repo in parallel and merges results sorted by updatedAt
desc. The repo selector at the top of Tasks is the project picker —
it's the same one the GitHub tab uses, so the selection model is
consistent across providers.

- TaskPage gitlab fetch effect: Promise.allSettled across all
  selectedRepos that aren't SSH-relay (folder-mode repos and remote
  worktrees fall through). Each repo's project is resolved from its
  own git remote by the main process; non-GitLab repos return
  not_found which the renderer drops silently so a mixed selection
  (GitHub + GitLab repos) doesn't surface false errors on the GitLab
  tab.
- Per-row repoId tagging stays correct — items keep their source
  repo's id through the merge, which matters for the dialog repoPath
  resolution below.
- Banner display: only shown when EVERY eligible repo failed; partial
  failure is signaled by the row count being lower, not a banner that
  overshadows working repos.
- GitLabItemDialog repoPath: derived from the clicked item's
  source repo (selectedRepos.find by repoId) instead of primaryRepo.
  Without this, clicking an item from a non-primary repo would route
  the detail fetch through the wrong repo's remote.

Full typecheck (node + cli + web) clean. 117 unit tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(gitlab): swap MR icon to GitMerge for visual distinction

GitPullRequest (curved-merge) reads similar to GitBranch (forking
line) at the small sizes we use in the picker — feedback was that
MR rows looked like branch rows. GitMerge (arrow-merge-into-line)
reads as its own thing and matches gitlab.com's MR iconography, so
users coming from the web UI find it familiar.

GitHub PRs keep GitPullRequest — that matches github.com and keeps
provider attribution distinct from GitLab MRs at a glance:
  GitHub PR: GitPullRequest (curved merge)
  GitLab MR: GitMerge (arrow merge)
  Branch:    GitBranch (fork)
  Issue:     CircleDot (provider-agnostic)

- SmartWorkspaceNameField RowIcon + SelectionIcon: gitlab-mr →
  GitMerge. github-pr stays GitPullRequest.
- GitLabItemDialog header icon: GitMerge for MRs.

Full typecheck (node + cli + web) clean. 117 unit tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(gitlab): split shared types + preload into per-provider files

Pre-emptive merge-conflict reduction. The two recent main syncs
each surfaced ~5 conflicts, all in the same handful of central
files where every provider lands code. Moving the GitLab footprint
into provider-scoped files cuts the conflict surface roughly in half
without changing any runtime behavior.

- shared/gitlab-types.ts (new, 272 lines): every standalone GitLab
  type that previously lived in shared/types.ts —
  GitLabProjectRef / MRState / MRMergeableState / MRCheckDetail /
  MRInfo / GitLabReaction / MRComment / GitLabCommentResult /
  GitLabIssueInfo / GitLabViewer / GitLabAssignableUser /
  GitLabWorkItem / GitLabMRFile / GitLabProjectSettings /
  GitLabTodo[TargetType] / GitLabPipelineJob /
  GitLabWorkItemDetails / GitLabIssueUpdate / MRListState /
  GitLabPagedResult / ListMergeRequestsResult.
- shared/types.ts: re-exports the GitLab types so existing call
  sites importing from '../shared/types' keep working unchanged.
  GitLabProjectSettings additionally imported locally for the
  GlobalSettings.gitlabProjects field. Worktree.linkedGitLabMR /
  WorktreeMeta.linkedGitLabIssue / GlobalSettings.defaultTaskSource
  union member stay here — they're entangled with non-GitLab
  structs and moving them out would just shuffle the conflict
  vector to a different file.
- preload/gitlab.ts (new, 106 lines): the entire gl.* runtime
  binding block — viewer / projectSlug / mrForBranch / mr /
  listMRs / listWorkItems / issue / listIssues / createIssue /
  updateIssue / addIssueComment / listLabels /
  listAssignableUsers / todos / workItemDetails / closeMR /
  reopenMR / mergeMR / addMRComment / workItemByPath. Exported as
  `glApi`.
- preload/index.ts: imports `glApi` and inlines as `gl: glApi`,
  shrinking the file by ~95 lines.

Net: the two files most prone to conflict on upstream sync
(shared/types.ts, preload/index.ts) lose ~360 lines of
GitLab-specific code that now live in their own files where main's
non-GitLab edits can't touch them.

Full typecheck (node + cli + web) clean. 190 unit tests passing
in src/main/gitlab/, src/shared/gitlab-projects.test.ts,
src/main/ipc/{preflight,worktree-logic}.test.ts,
src/renderer/src/lib/gitlab-links.test.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(gitlab): satisfy pnpm pre-flight (lint + handler-registration test)

- TaskPage: lift the selected-repos identity key into a useMemo so the
  GitLab fetch effect's dep array no longer holds a complex expression
  (oxlint exhaustive-deps).
- register-core-handlers.test: mock ./gitlab alongside ./github / ./linear
  so registerGitLabHandlers doesn't try to call ipcMain.handle in a unit
  test that fakes only individual handler modules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(source-control): add Bitbucket hosted review support

* fix(source-control): align hosted review lookup with provider model

---------

Co-authored-by: Emilian Stoilkov <emilian.stoilkov@qaiware.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:09:52 -07:00