Conflict: packages/ui/src/components/chat/markdown/markdownCore.ts
main added per-image-mode markdown parsers (`imageMode` threaded through
`parseBlock` and into the block cache key); this branch replaced the
identity-keyed block cache with a content-addressed LRU. Resolution keeps the
content-addressed cache and folds `imageMode` into the content key, so the
`inline` and `label` renderings of the same source cannot answer for each other.
* fix(chat): defer composer value writeback during IME composition
The controlled-writeback effect compared the value prop against the
CodeMirror document and, on mismatch, dispatched a wholesale replacement
with the caret forced to the end. While the browser composes (pinyin,
kana, hangul) the uncommitted text lives in the DOM, not in the document,
so the mismatch is expected and the dispatch interrupted the IME session
and jumped the cursor. Skip the writeback while the view is composing,
using CodeMirror's public compositionStarted getter; the composition
commits through its own pipeline and reports via onChange.
Fixes#2527
* fix(chat): preserve external composer writes during IME
* fix(chat): restore composition-wide writeback guard
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
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 SDK client fetch wrapper now applies a 30s timeout to non-streaming
reads. Without it, a socket that neither resolves nor rejects keeps the
directory bootstrap concurrency slot busy forever and the UI stays on
"loading sessions". Long-lived streams (POST prompts, the /event SSE)
are explicitly excluded so they are not cut off mid-flight.
The normalized "request timed out" error is added to the retry
allowlist alongside undici's "terminated" (the exact failure observed
in #2470 when undici tears down a half-open upstream connection); both
are transient while the managed OpenCode process restarts. Caller-
initiated aborts keep their original error shape so a user-cancelled
request is not retried.
Tests cover: GET timeout fires after the bound, POST is not timed out,
/event SSE is not timed out, caller abort wins, AbortError is not
retried, and the SDK normalized error is retried 3x.
The assignee/label/milestone combobox and @-mention list were inline
absolute-positioned inside the ContextPanel rail (overflow-hidden) and the
PR-view scroll container, so the panels were clipped and invisible. Render
both via createPortal to document.body with fixed positioning computed from
the trigger's viewport rect (viewport-edge clamping, flip to the other side
when there is no room), close on outside pointerdown/scroll/resize while
still allowing interaction inside the panel, and keep the panel inside the
outside-click check.
Also show the loading spinner until the first lookup completes instead of a
brief 'No matches' flash (useForgeLookup now reports 'initialized').
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
Graceful probe: an optional stat of a config/backup path outside the active
workspace now reports exists:false instead of 400, so boot-time probes don't
surface console errors for a check that was never mandatory.
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
- linkedIssues: provider/repo/host fields (backward-compatible), gitea id generation fix, parseLinkedIssueRef/parseForgeEntityUrl
- work-status panel: live state cards (TTL-cached), link-by-URL dialog, per-row unlink
- forge entity view: 'Chats working on this' section derived from the session store
Add the Gitea teapot mark (Simple Icons, 24x24) to the icon sprite as a
custom icon, and swap the gitea fallback icons for it wherever the
provider brand is shown: context rail PR surface, context panel PR tab,
GitHeader PR chip, and the work-status Gitea PR row.
Extract a single shared parseGitHost (packages/ui/src/lib/gitHost.ts) used
by both the custom-domain registry (normalizeProviderDomain) and remote
detection (gitProvider). Previously both parsers required a user@ prefix,
rejecting valid bare scp remotes like codeberg.org:owner/repo.git, and
mangled IPv6 hosts. Now scp forms with or without a user, ssh:// URLs with
ports, git+ssh scheme, and bracketed/unbracketed IPv6 all normalize to a
bare hostname; Windows-path-like input is rejected.
Add an Issues tab alongside the merge-request / pull-request content in the
context panel's PR surface for both providers. The tab lists open issues
lazily and expands a selected issue into an inline detail view (body, labels,
assignees, comments) with a back action. Read-only: no issue create, update,
or close actions. Adds 15 i18n keys across all 11 locales.
Maintenance task commands now fill .github/PULL_REQUEST_TEMPLATE.md section by
section instead of inventing their own headings, and follow-up tasks keep the
description true for the final HEAD while preserving hand-added content.
Raise the anti-slop batch window to 60-120 findings and require each selected
file to be finished: remaining findings need an individual specific reason,
shared root causes count once, and difficulty alone no longer justifies a skip.
A half-fixed file otherwise returns as a second pull request over the same code.
Add the maintenance-review command, which reviews every open anti-slop and
react-doctor pull request and fixes the findings directly rather than
commenting, without merging or approving.