Repository navigation
Gitea task source (#5493) rebased onto main + two validated fixes - #6920
charilaouc wants to merge 16 commits into
Conversation
Checks, conversation comments (view + add), merge, close/reopen, and title editing for Gitea PRs in the right sidebar — reusing the Gitea PR IPCs. The main process already produces a Gitea HostedReviewInfo; flow it into the panel's activeReview (guarded so it never masks a GitHub/GitLab review) and mirror the GitLab branch for fetch/poll/refresh. Resolve/edit/delete of individual review threads stays off (Gitea exposes no thread id). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Conflicts resolved (6 files): - i18n locales en/es/ja/ko/zh.json: union of additive sibling keys (PR's gitea.* keys + main's new namespaces). Verified each result parses and equals an independent 3-way JSON merge. - ChecksPanel.tsx (6 hunks): kept main's selectChecksPanelReview refactor, pre-refresh git-identity block, gated onResolveSelectedCommentsWithAI, and refreshOutcome telemetry; re-applied PR's Gitea review path on top. - checks-panel-review.ts (non-conflict edit): extended selectChecksPanelReview with a gitea hosted-review branch so the PR's Gitea checks path is reachable after main's helper extraction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-merge typecheck:web (TS2322) failed at launch-work-item-direct.ts:113: LaunchableWorkItem.provider is TaskProvider (which PR #5493 widened to include 'gitea'), but WorkspaceIntentWorkItem.provider omitted 'gitea'. This is a latent PR defect — the same incompatibility exists at the PR head, since all three contributing types are unchanged by the merge. The field is type-only metadata (naming helpers key off type/linearIdentifier/jiraIdentifier, not provider), so widening the union is zero-risk. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parseGiteaRepoRef stored repo.host from url.hostname (no port), but the stored- server match (getServerForHost/giteaServerHost) and the env-base-url scope check (sameHost) compare URL.host, which keeps a non-default port. A Gitea on e.g. :3000 therefore never matched its stored server, so the token was dropped — reads ran unauthenticated and writes were blocked. Store the web host with its non-default port (url.host) so the store and compare sides agree. Default ports (the :443 reverse-proxy case) still collapse on both sides, the env-token path with no base URL is untouched, and SSH remotes keep the bare hostname (their port is the SSH port, not the web port). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GiteaIssueMetaControls rebuilt the full label-id set by matching applied label names against repoLabels, which is fetched limit:100 page:1 and excludes org labels. Gitea's label PUT replaces the whole set, so any applied label missing from that capped list was silently dropped when toggling a different label. Plumb the issue's own label IDs through (GiteaIssue.labelIds) and toggle as a delta against them, so unrelated labels survive regardless of the repo-label list. Falls back to name resolution only while the issue detail is still loading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Ready to review this PR? Stage has broken it down into 8 individual chapters for you: Chapters generated by Stage for commit 16fd528 on Jun 30, 2026 11:40pm UTC. |
|
Caution Review failedFailed to post review comments. We encountered an issue with GitHub. Use ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (84)
🧰 Additional context used📓 Path-based instructions (4)src/renderer/src/**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
src/renderer/src/**/*.{ts,tsx,css}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (5)📚 Learning: 2026-06-16T15:35:16.815ZApplied to files:
📚 Learning: 2026-06-24T11:33:31.894ZApplied to files:
📚 Learning: 2026-06-16T21:44:06.855ZApplied to files:
📚 Learning: 2026-06-16T21:44:07.826ZApplied to files:
📚 Learning: 2026-06-16T21:44:10.732ZApplied to files:
🪛 ast-grep (0.44.0)src/renderer/src/store/slices/gitea.ts[error] 74-76: Recursive/iterative merge copies attacker-controllable keys from a source object into a target via a computed property assignment without rejecting dangerous keys, allowing prototype pollution. Skip or block "proto", "constructor", and "prototype" keys (e.g. (prototype-pollution-recursive-merge-typescript) 🪛 LanguageToolFIXES-CR.md[uncategorized] ~64-~64: The official name of this software platform is spelled with a capital “H”. (GITHUB) 🪛 markdownlint-cli2 (0.22.1)FIXES-CR.md[warning] 27-27: Fenced code blocks should have a language specified (MD040, fenced-code-language) 📝 WalkthroughWalkthroughThis pull request adds Gitea as a first-class task, issue, and pull-request provider. It introduces shared Gitea types, persistent server/token handling, repo-scoped request helpers, issue and PR backends, IPC/preload wiring, renderer store and workspace support, hosted-review checks integration, settings surfaces, and localization updates across the app. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/renderer/src/store/slices/ui.ts (1)
1228-1233: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftDon't treat Gitea as globally available from connection state alone.
giteaConfigured: state.giteaStatus?.connected === truemakesopenTaskPageresolve the Gitea source even when the current repo selection contains repos that are not registered against a Gitea host.TaskPagethen passesselectedReposstraight intoGiteaTaskList, and that list does aPromise.all(...)overfetchGiteaWorkItems(...), so one unsupported repo rejects the entire load. Filter to Gitea-backed repos before resolving the source, or make the Gitea load path skip unsupported repos instead of failing the whole view.
🟡 Minor comments (11)
src/main/ipc/gitea.ts-57-78 (1)
57-78: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winReject empty or array
updatespayloads before reporting success.
normalizeIssueUpdatecurrently accepts arrays and{}; downstream can then skip every write and still return{ ok: true }, making a failed renderer mutation look successful.Proposed fix
function normalizeIssueUpdate(value: unknown): GiteaIssueUpdate | null { - if (!value || typeof value !== 'object') { + if (!value || typeof value !== 'object' || Array.isArray(value)) { return null } const input = value as GiteaIssueUpdate + const hasKnownUpdate = + input.title !== undefined || + input.body !== undefined || + input.state !== undefined || + input.assignees !== undefined || + input.labelIds !== undefined + if (!hasKnownUpdate) { + return null + }Also applies to: 192-196
src/main/ipc/gitea-pr.ts-58-66 (1)
58-66: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDo not fetch PR file contents with empty refs.
If
baseShaorheadShais malformed, this passes''into the raw-content requests. Return the empty content shape instead so the diff view does not render content from an unintended ref.Proposed fix
if (typeof args.path !== 'string' || !args.path) { return { original: '', modified: '', originalIsBinary: false, modifiedIsBinary: false } } + if ( + typeof args.baseSha !== 'string' || + !args.baseSha || + typeof args.headSha !== 'string' || + !args.headSha + ) { + return { original: '', modified: '', originalIsBinary: false, modifiedIsBinary: false } + } return getGiteaPullRequestFileContents( repo.path, { path: args.path, oldPath: typeof args.oldPath === 'string' ? args.oldPath : undefined, status: args.status, - baseSha: typeof args.baseSha === 'string' ? args.baseSha : '', - headSha: typeof args.headSha === 'string' ? args.headSha : '' + baseSha: args.baseSha, + headSha: args.headSha },src/renderer/src/i18n/locales/zh.json-6283-6285 (1)
6283-6285: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd matching Settings-search keywords for the new provider.
This adds the Gitea toggle to
settings.TasksPane, butsettings.tasks.searchstill only indexes Jira/Linear/GitLab/GitHub. Searching Settings for “Gitea” will not surface this control.Suggested follow-up
"tasks": { "search": { "58cda6f9c0": "隐藏", "44083ae418": "展示", "604d8e4089": "阿特拉斯语", "5430396e11": "Jira", "412ec3c702": "Linear", "11f001cdd4": "GitLab", "c10ac2125e": "GitHub", + "gitea": "Gitea", "3d81c26d78": "来源", "cf0e3e0c2f": "提供者", "2ec54bee51": "任务", "765f0c544d": "选择要在“任务”页面和侧边栏快捷键中显示的任务提供商。", "5b8e4aace5": "任务提供者" } },src/renderer/src/components/task-source-context-summary.ts-53-53 (1)
53-53: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPass Gitea repo contexts through the caller.
TaskPagestill buildstaskSourceRepoContextsonly for'github'and'gitlab', so this new branch receives[]for Gitea and the header summary falls back to the zero-repo state. Please update that caller in the same change so the Gitea summary gets the selected repo context too.src/renderer/src/i18n/locales/ja.json-12172-12320 (1)
12172-12320: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winMerge this into the existing
gitealocale object.Line 12172 starts a second top-level
"gitea"key. In JSON, the later object overwrites the earlier one, so thegitea.integration.cardentries added at Line 8473 stop existing in the Japanese catalog.Suggested structure
- "gitea": { - "connect": { - "dialog": { - ... - } - }, - "issue": { - ... - }, - "pr": { - ... - }, - "new": { - ... - } - }, + "gitea": { + "integration": { + "card": { + ... + } + }, + "connect": { + "dialog": { + ... + } + }, + "issue": { + ... + }, + "pr": { + ... + }, + "new": { + ... + } + },src/renderer/src/components/gitea-issue-comments.tsx-7-10 (1)
7-10: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winLocalize the invalid-date fallback.
The
"recently"fallback bypassestranslate(), so this path always renders in English. Route the fallback string through i18n like the rest of the component.src/renderer/src/components/right-sidebar/ChecksPanel.tsx-674-676 (1)
674-676: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick winAlso gate the GitHub PR refresh path on Gitea review context.
isGiteaReviewContextis derived here, but the existingenqueueGitHubPRRefresh(...)effect still only excludes GitLab. In a Gitea review, opening the panel will continue to schedule the GitHub PR refresh flow, which is unnecessary work and can surface GitHub-side noise on Gitea-only repos.src/renderer/src/components/GiteaPullRequestWorkspace.tsx-101-129 (1)
101-129: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the workspace loading state alive until the checks request finishes.
prChecksis launched after the firstPromise.all(...)resolves, but Line 127 clearsloadingimmediately. That makes the spinner disappear and the Checks tab briefly render0/ empty-state before the checks call comes back.Suggested fix
- .then(([prDetail, prFiles, prComments, prReviewComments]) => { + .then(async ([prDetail, prFiles, prComments, prReviewComments]) => { if (requestId !== requestRef.current) { return } setDetail(prDetail) setFiles(prFiles) setComments(prComments) setReviewComments(prReviewComments) - if (prDetail?.headSha) { - void ( - window.api.gitea.prChecks(scoped(scope, { headSha: prDetail.headSha })) as Promise< - GiteaPRCheck[] - > - ) - .then((result) => { - if (requestId === requestRef.current) { - setChecks(result) - } - }) - .catch(() => { - // Why: keep checks state consistent and surface the failure. - if (requestId === requestRef.current) { - setChecks([]) - toast.error( - translate( - 'auto.components.GiteaPullRequestWorkspace.c2d3e4f5a6', - 'Failed to load PR checks.' - ) - ) - } - }) + if (prDetail?.headSha) { + try { + const result = (await window.api.gitea.prChecks( + scoped(scope, { headSha: prDetail.headSha }) + )) as GiteaPRCheck[] + if (requestId === requestRef.current) { + setChecks(result) + } + } catch { + if (requestId === requestRef.current) { + setChecks([]) + toast.error( + translate( + 'auto.components.GiteaPullRequestWorkspace.c2d3e4f5a6', + 'Failed to load PR checks.' + ) + ) + } + } } })src/renderer/src/components/gitea-pr-merge-button.tsx-33-40 (1)
33-40: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winTranslate the merge-method menu labels.
The main action button is localized, but the popover still renders raw enum values (
merge,squash,rebase). That leaves the new merge flow partially untranslated in non-English locales.Suggested fix
+const MERGE_METHOD_LABELS: Record<GiteaMergeMethod, string> = { + merge: translate('auto.components.gitea.pr.merge.button.method.merge', 'Merge commit'), + squash: translate('auto.components.gitea.pr.merge.button.method.squash', 'Squash and merge'), + rebase: translate('auto.components.gitea.pr.merge.button.method.rebase', 'Rebase and merge') +} + <PopoverContent align="end" className="w-40 p-1"> {MERGE_METHODS.map((method) => ( <button key={method} type="button" onClick={() => onMerge(method)} className="flex w-full items-center rounded-sm px-2 py-1.5 text-left text-[12px] capitalize hover:bg-accent" > - {method} + {MERGE_METHOD_LABELS[method]} </button> ))} </PopoverContent>src/renderer/src/components/gitea-pr-file-diff.tsx-27-34 (1)
27-34: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winLocalize the file-status badges.
Added / Modified / Deleted / ...is hardcoded English, so this new diff header will stay untranslated even when the rest of the Gitea workspace is localized.Suggested fix
-const STATUS_LABELS: Record<GiteaPRFile['status'], string> = { - added: 'Added', - modified: 'Modified', - deleted: 'Deleted', - renamed: 'Renamed', - copied: 'Copied', - changed: 'Changed' -} +const STATUS_LABELS: Record<GiteaPRFile['status'], string> = { + added: translate('auto.components.gitea.pr.file.diff.status.added', 'Added'), + modified: translate('auto.components.gitea.pr.file.diff.status.modified', 'Modified'), + deleted: translate('auto.components.gitea.pr.file.diff.status.deleted', 'Deleted'), + renamed: translate('auto.components.gitea.pr.file.diff.status.renamed', 'Renamed'), + copied: translate('auto.components.gitea.pr.file.diff.status.copied', 'Copied'), + changed: translate('auto.components.gitea.pr.file.diff.status.changed', 'Changed') +}src/renderer/src/components/settings/gitea-integration-card.tsx-21-25 (1)
21-25: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTrack test state per server, not in a single
testingServerId.If the user starts a test for server A and then server B before A settles, A's
finallyclearstestingServerIdand re-enables B's row even though B is still running. Use aSet<string>or per-server boolean map so concurrent tests can't clobber each other's loading state.Also applies to: 43-80
🧹 Nitpick comments (1)
src/renderer/src/components/settings/IntegrationsPane.tsx (1)
55-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a short why-comment for Gitea appearing in both sections.
Seeing Gitea under both “Review providers” and “Task providers” reads like accidental duplication unless you already know the cross-provider design. A one-line comment here would prevent future cleanup from removing one of the cards. As per coding guidelines, "When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does."
Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 6616fa44-85d1-40e5-a725-a3462f242572
📒 Files selected for processing (77)
src/main/gitea/client.test.tssrc/main/gitea/client.tssrc/main/gitea/connect.test.tssrc/main/gitea/connect.tssrc/main/gitea/issues.test.tssrc/main/gitea/issues.tssrc/main/gitea/mappers.test.tssrc/main/gitea/mappers.tssrc/main/gitea/pull-requests.test.tssrc/main/gitea/pull-requests.tssrc/main/gitea/repository-ref.test.tssrc/main/gitea/repository-ref.tssrc/main/gitea/request.tssrc/main/gitea/server-store.test.tssrc/main/gitea/server-store.tssrc/main/ipc/gitea-pr.tssrc/main/ipc/gitea-repo-access.tssrc/main/ipc/gitea.tssrc/main/ipc/register-core-handlers.test.tssrc/main/ipc/register-core-handlers.tssrc/main/persistence.test.tssrc/preload/api-types.tssrc/preload/index.tssrc/renderer/src/assets/main.csssrc/renderer/src/components/GiteaIssueWorkspace.tsxsrc/renderer/src/components/GiteaPullRequestWorkspace.tsxsrc/renderer/src/components/GiteaTaskList.tsxsrc/renderer/src/components/TaskPage.tsxsrc/renderer/src/components/automations/automation-source-display.tssrc/renderer/src/components/automations/automation-target-availability.tssrc/renderer/src/components/gitea-connect-dialog.tsxsrc/renderer/src/components/gitea-issue-comments.tsxsrc/renderer/src/components/gitea-issue-label-toggle.test.tssrc/renderer/src/components/gitea-issue-label-toggle.tssrc/renderer/src/components/gitea-issue-meta-controls.tsxsrc/renderer/src/components/gitea-new-issue-dialog.tsxsrc/renderer/src/components/gitea-pr-checks.tsxsrc/renderer/src/components/gitea-pr-file-diff.tsxsrc/renderer/src/components/gitea-pr-line-comments.tsxsrc/renderer/src/components/gitea-pr-merge-button.tsxsrc/renderer/src/components/gitea-pr-request-scope.tssrc/renderer/src/components/gitea-pr-tab-body.tsxsrc/renderer/src/components/icons/GiteaIcon.tsxsrc/renderer/src/components/right-sidebar/ChecksPanel.tsxsrc/renderer/src/components/right-sidebar/checks-panel-gitea.test.tssrc/renderer/src/components/right-sidebar/checks-panel-gitea.tssrc/renderer/src/components/right-sidebar/checks-panel-review.tssrc/renderer/src/components/right-sidebar/use-hosted-review-actions.tssrc/renderer/src/components/settings/IntegrationsPane.tsxsrc/renderer/src/components/settings/TasksPane.tsxsrc/renderer/src/components/settings/gitea-integration-card.test.tsxsrc/renderer/src/components/settings/gitea-integration-card.tsxsrc/renderer/src/components/settings/task-tracker-integration-cards.tsxsrc/renderer/src/components/settings/use-integration-provider-status-refresh.tssrc/renderer/src/components/task-page-list-chrome-visibility.tssrc/renderer/src/components/task-page-localized-options.tsxsrc/renderer/src/components/task-source-context-summary-format.tssrc/renderer/src/components/task-source-context-summary.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/store/index.tssrc/renderer/src/store/slices/diffComments.test.tssrc/renderer/src/store/slices/gitea.test.tssrc/renderer/src/store/slices/gitea.tssrc/renderer/src/store/slices/store-test-helpers.tssrc/renderer/src/store/slices/ui.tssrc/renderer/src/store/types.tssrc/shared/gitea-types.tssrc/shared/integration-credential-errors.tssrc/shared/task-providers.test.tssrc/shared/task-providers.tssrc/shared/task-source-context.tssrc/shared/types.tssrc/shared/workspace-name.ts
… 403 Address CodeRabbit findings on env/stored credential resolution and the env-token auth probe: - request.ts: scope ORCA_GITEA_TOKEN to the exact API base (host + subpath), not the host alone, so a token can't leak to a sibling instance under a different subpath on the same host. Pass repo.apiBaseUrl into the stored lookup. (CR Major: request.ts) - server-store.ts: add giteaServerKey() (host+path, keeps the #5493 non-default port) and match getServerForHost() on it when an apiBaseUrl is supplied, falling back to host-only for back-compat. (CR Major: server-store.ts) - issues.ts: pass repo.apiBaseUrl when resolving the server for issue labels so multi-instance hosts label the correct serverId/serverName. - connect.ts: only treat the read:user-scope 403 on /user as a valid token; every other 403 now fails. Export fetchGiteaUser with a timeout option. (CR Major: connect.ts) - client.ts: getGiteaAuthStatus() reuses fetchGiteaUser for the token probe so a read:user-scope 403 reports authenticated (account null) instead of the raw GET's null. (CR Major: client.ts) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address CodeRabbit findings in the Gitea issue/PR data layer and IPC validators:
- issues.ts: decouple the owner-wide search page size from the requested result
cap (limit MAX_LIMIT, not max) so a small limit doesn't under-scan and miss a
matching issue past the first global hits. (CR Major: issues.ts:53-60)
- issues.ts: when the field PATCH commits but the labels PUT fails, return a
partial success { ok: true, warning } instead of a clean failure, so the UI
refreshes the already-changed issue. Adds GiteaMutationResult.warning.
(CR Major: issues.ts:266-280)
- mappers.ts: preserve the raw issue body (raw.body ?? undefined) instead of
trimming it, so opening/saving can't drop meaningful whitespace. (CR Major)
- pull-requests.ts: paginate the PR file list instead of stopping at page 1, so
large PRs don't silently drop changed files. (CR Major)
- ipc/gitea.ts: reject array/empty issue-update payloads (no recognized field)
so a no-op update can't skip every write and still report success. (CR Minor)
- ipc/gitea-pr.ts: return the empty content shape when base/head SHAs are
missing, so the diff view never renders against an unintended ref. (CR Minor)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address CodeRabbit findings on stale async results overwriting a newly-selected Gitea item, and a couple of adjacent load/partial-success issues: - GiteaIssueWorkspace: capture requestRef before each awaited mutation (refreshDetail, toggle state, add comment) and bail if the selection changed, so a late response can't overwrite the new sheet. (CR Major) - GiteaPullRequestWorkspace: same requestRef guard on add-comment, merge, and add-review-comment; and keep `loading` up until the checks request finishes so the Checks tab doesn't flash an empty state. (CR Major + Minor) - GiteaTaskList: load with Promise.allSettled and merge fulfilled repos, only showing the error banner when every repo failed, so one bad repo in a mixed selection can't blank the whole list. (CR Major) - gitea-issue-meta-controls: surface the partial-success warning (fields saved but labels rejected) as a non-fatal toast instead of swallowing it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… update Address CodeRabbit findings on the Gitea store slice cache keys: - scopeKey() now keys unpinned entries by activeServerId (falling back to the explicit selection, then 'all'), so two single-server states stay distinct and reconnecting to a different host can't reuse the previous server's cached work items / issue detail until the TTL expires. (CR Major) - updateGiteaIssue() now also drops the cached work-item lists for the scope, not just the detail entry, so closing/relabeling/retitling an issue refreshes the task list instead of leaving it stale for up to 60s. (CR Major) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…itea Address CodeRabbit findings spanning the Tasks page and the hosted-review checks panel: - TaskPage: opening a Gitea issue now clears any selected PR (and vice versa), so the two modal drawers stay mutually exclusive. (CR Major) - TaskPage: build taskSourceRepoContexts for Gitea too, so the header summary reflects the selected projects instead of the zero-repo fallback. (CR Minor) - ChecksPanel: refreshHostedReviewAfterMutation gains a Gitea branch so a successful Gitea title/merge edit refreshes via the Gitea path instead of falling through to the GitHub PR force-refresh, which could error/stale the panel. (CR Major) - ChecksPanel: also skip enqueueGitHubPRRefresh in a Gitea review context, not only GitLab, to avoid wasted work and GitHub-side noise. (CR Minor) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address CodeRabbit i18n/UX findings in the Gitea renderer surface: - gitea-issue-comments: route the invalid-date "recently" fallback through translate(). (CR Minor) - gitea-pr-merge-button: localize the merge-method menu labels (merge/squash/ rebase) instead of rendering raw enum values. (CR Minor) - gitea-pr-file-diff: localize the file-status badges (Added/Modified/…). (CR Minor) - settings/tasks-search: index "gitea" as a Tasks-pane provider search keyword so Settings search surfaces the Gitea toggle. (CR Minor) - settings/gitea-integration-card: track test state per server in a Set so concurrent tests can't clear each other's spinner. (CR Minor) - settings/IntegrationsPane: document why Gitea appears under both Review and Task providers. (CR Nitpick) - i18n catalogs: register the new keys across all locales. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The selection-race guards and PR-file pagination pushed three files just over their max-lines budgets. Per the repo guideline (never disable max-lines), extract focused modules instead: - pull-request-reviews.ts: the diff-anchored review-comment reads/writes, moved out of pull-requests.ts (now PR detail/files/checks/merge only). - gitea-workspace-chrome.ts: shared isWindows + copyGiteaText helpers used by both Gitea work-item sheets. - gitea-pr-tabs.ts: the PR workspace tab model/builder. No behavior change. Also switches a test assertion to String#startsWith to satisfy the lint rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Maps every CodeRabbit finding on PR #6920 (16 inline Major + 1 outside-diff + 11 Minor + 1 Nitpick) to its resolution, tests, and validation results. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-review caught a regression in the new credential scoping: an SSH clone URL (git@host:owner/repo.git) can't carry the host's web ROOT_URL subpath, so a subpath-hosted Gitea accessed over SSH derived a bare-host apiBaseUrl that no longer key-matched the stored server's subpath base — dropping the token (reads returned null, writes failed with "Connect a Gitea account to make changes"). Fix: flag such bases as host-inferred (GiteaRepoRef.apiBaseFromHost, set only for SSH/SCP remotes with no subpath segment) and allow a host-only credential match for that case alone: - repository-ref.ts: set apiBaseFromHost when an SSH/SCP remote has no subpath. - server-store.ts: getServerForHost(host, apiBaseUrl?, apiBaseFromHost?) keeps the exact host+path match for authoritative bases, but accepts the host's lone connected server for a host-inferred base; never guesses across instances. - request.ts: sameApiBase falls back to a host match only for host-inferred bases; pass the flag into the stored lookup. - issues.ts: pass the flag through for serverId/serverName labeling. Keeps the #5493 host+port match and the multi-instance-same-host protection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts
|
The pushed commits address all comments from the previous review — each fix carries a short inline @coderabbitai full review |
|
✅ Action performedFull review finished. |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/gitea/repository-ref.ts (1)
92-99: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
KNOWN_NON_GITEA_HOSTSexclusion now compares against a port-qualified host for HTTP(S) remotes.
normalizedHostinmakeRepoRefis derived from thehostparam, which for HTTP(S) is nowurl.host(includes an explicit port if present). If a remote explicitly specifies a default port (e.g.https://github.com:443/owner/repo.git),normalizedHost(github.com:443) won't matchKNOWN_NON_GITEA_HOSTSentries (presumably bare hostnames likegithub.com), letting a known non-Gitea host slip past the exclusion check.🐛 Suggested fix — strip the port before the exclusion check
const normalizedHost = host.toLowerCase() + const exclusionHost = normalizedHost.replace(/:\d+$/, '') if ( - !normalizedHost || - KNOWN_NON_GITEA_HOSTS.has(normalizedHost) || - normalizedHost.endsWith('.visualstudio.com') + !normalizedHost || + KNOWN_NON_GITEA_HOSTS.has(exclusionHost) || + exclusionHost.endsWith('.visualstudio.com') ) { return null }Also applies to: 146-153
♻️ Duplicate comments (1)
src/renderer/src/components/gitea-issue-meta-controls.tsx (1)
34-46: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winTitle draft still isn't reset when the selected issue changes.
titleDraftinitializes fromtitleonly on mount (useState(title)). Without a remount key or a syncing effect onissueNumber/title, switching to a different Gitea issue keeps the previous issue's title in the input — saving from there renames the wrong issue. This matches a previously flagged concern that doesn't show as resolved in this update.🐛 Suggested fix
const [titleDraft, setTitleDraft] = useState(title) const [pending, setPending] = useState<string | null>(null) + + useEffect(() => { + setTitleDraft(title) + }, [issueNumber, title])
🧹 Nitpick comments (4)
src/renderer/src/components/gitea-pr-request-scope.ts (1)
5-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a more descriptive function name.
scopedis generic given the exported surface; a name likebuildGiteaPrRequestPayloadwould be clearer at call sites given the file already carries the "gitea-pr" context in its filename.src/main/gitea/request.ts (1)
104-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate fetch-with-timeout boilerplate across three functions.
giteaGetJsonAtBase,giteaRepoGetText, andgiteaRepoWriteeach re-implement the sameAbortController+ timeout +clearTimeoutscaffolding. Consider extracting a sharedwithGiteaTimeout(fn, timeoutMs)helper (or a commongiteaFetchwrapper) to reduce duplication and the risk of the pattern diverging (e.g., a future edit forgettingclearTimeoutin one of the three copies).♻️ Sketch of a shared timeout helper
+async function withTimeout<T>( + timeoutMs: number, + run: (signal: AbortSignal) => Promise<T> +): Promise<T> { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), timeoutMs) + try { + return await run(controller.signal) + } finally { + clearTimeout(timeout) + } +}Then each of
giteaGetJsonAtBase,giteaRepoGetText, andgiteaRepoWritecan callwithTimeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, (signal) => fetch(...))instead of duplicating the controller/timeout setup.Also applies to: 139-161, 180-214
src/renderer/src/components/TaskPage.tsx (2)
6594-6611: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
prefilledNamefor Gitea workspaces loses the issue/PR title.
handleUseGiteaItembuildsprefilledNameas a bare${type}-${number}string, unlike GitHub/GitLab which derive a richer, title-based seed (getGitHubWorkItemWorkspaceSeed,getGitLabWorkItemWorkspaceSeed). Consider adding an equivalentgetGiteaWorkItemWorkspaceSeed(item)helper for naming consistency across providers.♻️ Suggested direction
- prefilledName: `${item.type === 'pull' ? 'pr' : 'issue'}-${item.number}`, + prefilledName: getGiteaWorkItemWorkspaceSeed(item),
10082-10113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
TaskProjectSourceComboboxwiring across GitHub/GitLab/Gitea branches.This block repeats the same
onChange/onSelectAlllogic (normalize selection →setRepoSelection→ persistdefaultRepoSelection) already present verbatim for GitHub and GitLab. Now that it's tripled, consider extracting a sharedhandleRepoSelectionChange/handleSelectAllRepospair (memoized once) reused by all three branches.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 89324aca-cf50-4770-99d2-c1ed5044be8c
📒 Files selected for processing (84)
FIXES-CR.mdsrc/main/gitea/client.test.tssrc/main/gitea/client.tssrc/main/gitea/connect.test.tssrc/main/gitea/connect.tssrc/main/gitea/issues.test.tssrc/main/gitea/issues.tssrc/main/gitea/mappers.test.tssrc/main/gitea/mappers.tssrc/main/gitea/pull-request-reviews.tssrc/main/gitea/pull-requests.test.tssrc/main/gitea/pull-requests.tssrc/main/gitea/repository-ref.test.tssrc/main/gitea/repository-ref.tssrc/main/gitea/request.tssrc/main/gitea/server-store.test.tssrc/main/gitea/server-store.tssrc/main/ipc/gitea-pr.tssrc/main/ipc/gitea-repo-access.tssrc/main/ipc/gitea.tssrc/main/ipc/register-core-handlers.test.tssrc/main/ipc/register-core-handlers.tssrc/main/persistence.test.tssrc/preload/api-types.tssrc/preload/index.tssrc/renderer/src/assets/main.csssrc/renderer/src/components/GiteaIssueWorkspace.tsxsrc/renderer/src/components/GiteaPullRequestWorkspace.tsxsrc/renderer/src/components/GiteaTaskList.tsxsrc/renderer/src/components/TaskPage.tsxsrc/renderer/src/components/automations/automation-source-display.tssrc/renderer/src/components/automations/automation-target-availability.tssrc/renderer/src/components/gitea-connect-dialog.tsxsrc/renderer/src/components/gitea-issue-comments.tsxsrc/renderer/src/components/gitea-issue-label-toggle.test.tssrc/renderer/src/components/gitea-issue-label-toggle.tssrc/renderer/src/components/gitea-issue-meta-controls.tsxsrc/renderer/src/components/gitea-new-issue-dialog.tsxsrc/renderer/src/components/gitea-pr-checks.tsxsrc/renderer/src/components/gitea-pr-file-diff.tsxsrc/renderer/src/components/gitea-pr-line-comments.tsxsrc/renderer/src/components/gitea-pr-merge-button.tsxsrc/renderer/src/components/gitea-pr-request-scope.tssrc/renderer/src/components/gitea-pr-tab-body.tsxsrc/renderer/src/components/gitea-pr-tabs.tssrc/renderer/src/components/gitea-workspace-chrome.tssrc/renderer/src/components/icons/GiteaIcon.tsxsrc/renderer/src/components/right-sidebar/ChecksPanel.tsxsrc/renderer/src/components/right-sidebar/checks-panel-gitea.test.tssrc/renderer/src/components/right-sidebar/checks-panel-gitea.tssrc/renderer/src/components/right-sidebar/checks-panel-review.tssrc/renderer/src/components/right-sidebar/use-hosted-review-actions.tssrc/renderer/src/components/settings/IntegrationsPane.tsxsrc/renderer/src/components/settings/TasksPane.tsxsrc/renderer/src/components/settings/gitea-integration-card.test.tsxsrc/renderer/src/components/settings/gitea-integration-card.tsxsrc/renderer/src/components/settings/settings-search-keywords.test.tssrc/renderer/src/components/settings/task-tracker-integration-cards.tsxsrc/renderer/src/components/settings/tasks-search.tssrc/renderer/src/components/settings/use-integration-provider-status-refresh.tssrc/renderer/src/components/task-page-list-chrome-visibility.tssrc/renderer/src/components/task-page-localized-options.tsxsrc/renderer/src/components/task-source-context-summary-format.tssrc/renderer/src/components/task-source-context-summary.test.tssrc/renderer/src/components/task-source-context-summary.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/store/index.tssrc/renderer/src/store/slices/diffComments.test.tssrc/renderer/src/store/slices/gitea.test.tssrc/renderer/src/store/slices/gitea.tssrc/renderer/src/store/slices/store-test-helpers.tssrc/renderer/src/store/slices/ui.tssrc/renderer/src/store/types.tssrc/shared/gitea-types.tssrc/shared/integration-credential-errors.tssrc/shared/task-providers.test.tssrc/shared/task-providers.tssrc/shared/task-source-context.tssrc/shared/types.tssrc/shared/workspace-name.ts
| const groups = await Promise.all( | ||
| withComments.map((review) => | ||
| giteaRepoGet<RawGiteaReviewComment[]>(repo, `${base}/${review.id}/comments`) | ||
| ) | ||
| ) | ||
| return groups | ||
| .flat() | ||
| .filter((comment): comment is RawGiteaReviewComment => Boolean(comment)) | ||
| .filter((comment) => typeof comment.id === 'number' && typeof comment.position === 'number') |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
One failing review-comments fetch drops all inline comments for the PR.
Promise.all rejects entirely if any single giteaRepoGet call for a review's comments fails, so a transient error on one review among many wipes out the whole result instead of degrading gracefully.
🛡️ Proposed fix using Promise.allSettled
- const groups = await Promise.all(
- withComments.map((review) =>
- giteaRepoGet<RawGiteaReviewComment[]>(repo, `${base}/${review.id}/comments`)
- )
- )
- return groups
- .flat()
+ const groups = await Promise.allSettled(
+ withComments.map((review) =>
+ giteaRepoGet<RawGiteaReviewComment[]>(repo, `${base}/${review.id}/comments`)
+ )
+ )
+ return groups
+ .filter(
+ (result): result is PromiseFulfilledResult<RawGiteaReviewComment[]> =>
+ result.status === 'fulfilled'
+ )
+ .map((result) => result.value)
+ .flat()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const groups = await Promise.all( | |
| withComments.map((review) => | |
| giteaRepoGet<RawGiteaReviewComment[]>(repo, `${base}/${review.id}/comments`) | |
| ) | |
| ) | |
| return groups | |
| .flat() | |
| .filter((comment): comment is RawGiteaReviewComment => Boolean(comment)) | |
| .filter((comment) => typeof comment.id === 'number' && typeof comment.position === 'number') | |
| const groups = await Promise.allSettled( | |
| withComments.map((review) => | |
| giteaRepoGet<RawGiteaReviewComment[]>(repo, `${base}/${review.id}/comments`) | |
| ) | |
| ) | |
| return groups | |
| .filter( | |
| (result): result is PromiseFulfilledResult<RawGiteaReviewComment[]> => | |
| result.status === 'fulfilled' | |
| ) | |
| .map((result) => result.value) | |
| .flat() |
| if (result.ok) { | ||
| setBaseUrl('') | ||
| setToken('') | ||
| setConnectState('idle') | ||
| onOpenChange(false) | ||
| onConnected?.() | ||
| return | ||
| } | ||
| setConnectState('error') | ||
| setConnectError(result.error ?? null) | ||
| } catch (error) { | ||
| if (mountedRef.current) { | ||
| setConnectState('error') | ||
| setConnectError(error instanceof Error ? error.message : 'Connection failed') | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Silent failure when result.error is empty.
If giteaConnect resolves with result.ok === false and a falsy result.error, connectError is set to null (Line 87). The error message <p> only renders connectState === 'error' && connectError (Line 161), so the dialog silently shows nothing — the user sees the form stay in place with no explanation, unlike the catch branch which already has a 'Connection failed' fallback (Line 91).
🐛 Proposed fix: fall back to a generic error message
setConnectState('error')
- setConnectError(result.error ?? null)
+ setConnectError(
+ result.error ??
+ translate('auto.components.gitea.connect.dialog.generic_error', 'Connection failed')
+ )Also applies to: 161-165
| void Promise.all([ | ||
| window.api.gitea.prDetail( | ||
| scoped(scope, { number: item.number }) | ||
| ) as Promise<GiteaPullRequestDetail | null>, | ||
| window.api.gitea.prFiles(scoped(scope, { number: item.number })) as Promise<GiteaPRFile[]>, | ||
| window.api.gitea.issueComments(scoped(scope, { number: item.number })) as Promise< | ||
| GiteaComment[] | ||
| >, | ||
| window.api.gitea.prReviewComments(scoped(scope, { number: item.number })) as Promise< | ||
| GiteaPRReviewComment[] | ||
| > | ||
| ]) | ||
| .then(async ([prDetail, prFiles, prComments, prReviewComments]) => { | ||
| if (requestId !== requestRef.current) { | ||
| return | ||
| } | ||
| setDetail(prDetail) | ||
| setFiles(prFiles) | ||
| setComments(prComments) | ||
| setReviewComments(prReviewComments) | ||
| if (prDetail?.headSha) { | ||
| // Why: await checks before the chained finally clears `loading`, so the | ||
| // spinner stays up and the Checks tab doesn't flash an empty state first. | ||
| try { | ||
| const result = (await window.api.gitea.prChecks( | ||
| scoped(scope, { headSha: prDetail.headSha }) | ||
| )) as GiteaPRCheck[] | ||
| if (requestId === requestRef.current) { | ||
| setChecks(result) | ||
| } | ||
| } catch { | ||
| // Why: keep checks state consistent and surface the failure. | ||
| if (requestId === requestRef.current) { | ||
| setChecks([]) | ||
| toast.error( | ||
| translate( | ||
| 'auto.components.GiteaPullRequestWorkspace.c2d3e4f5a6', | ||
| 'Failed to load PR checks.' | ||
| ) | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| .catch(() => {}) | ||
| .finally(() => { | ||
| if (requestId === requestRef.current) { | ||
| setLoading(false) | ||
| } | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Silent failure on primary PR-detail load.
The nested prChecks failure surfaces a toast, but the top-level Promise.all failure at Line 123 is swallowed by .catch(() => {}), leaving the sheet stuck in an empty, no-error state if prDetail/prFiles/issueComments/prReviewComments reject.
🩹 Proposed fix
})
- .catch(() => {})
+ .catch(() => {
+ if (requestId === requestRef.current) {
+ toast.error(
+ translate(
+ 'auto.components.GiteaPullRequestWorkspace.load-failed',
+ 'Failed to load pull request.'
+ )
+ )
+ }
+ })
.finally(() => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void Promise.all([ | |
| window.api.gitea.prDetail( | |
| scoped(scope, { number: item.number }) | |
| ) as Promise<GiteaPullRequestDetail | null>, | |
| window.api.gitea.prFiles(scoped(scope, { number: item.number })) as Promise<GiteaPRFile[]>, | |
| window.api.gitea.issueComments(scoped(scope, { number: item.number })) as Promise< | |
| GiteaComment[] | |
| >, | |
| window.api.gitea.prReviewComments(scoped(scope, { number: item.number })) as Promise< | |
| GiteaPRReviewComment[] | |
| > | |
| ]) | |
| .then(async ([prDetail, prFiles, prComments, prReviewComments]) => { | |
| if (requestId !== requestRef.current) { | |
| return | |
| } | |
| setDetail(prDetail) | |
| setFiles(prFiles) | |
| setComments(prComments) | |
| setReviewComments(prReviewComments) | |
| if (prDetail?.headSha) { | |
| // Why: await checks before the chained finally clears `loading`, so the | |
| // spinner stays up and the Checks tab doesn't flash an empty state first. | |
| try { | |
| const result = (await window.api.gitea.prChecks( | |
| scoped(scope, { headSha: prDetail.headSha }) | |
| )) as GiteaPRCheck[] | |
| if (requestId === requestRef.current) { | |
| setChecks(result) | |
| } | |
| } catch { | |
| // Why: keep checks state consistent and surface the failure. | |
| if (requestId === requestRef.current) { | |
| setChecks([]) | |
| toast.error( | |
| translate( | |
| 'auto.components.GiteaPullRequestWorkspace.c2d3e4f5a6', | |
| 'Failed to load PR checks.' | |
| ) | |
| ) | |
| } | |
| } | |
| } | |
| }) | |
| .catch(() => {}) | |
| .finally(() => { | |
| if (requestId === requestRef.current) { | |
| setLoading(false) | |
| } | |
| }) | |
| }) | |
| .catch(() => { | |
| if (requestId === requestRef.current) { | |
| toast.error( | |
| translate( | |
| 'auto.components.GiteaPullRequestWorkspace.load-failed', | |
| 'Failed to load pull request.' | |
| ) | |
| ) | |
| } | |
| }) | |
| .finally(() => { |
There was a problem hiding this comment.
Actionable comments posted: 19
♻️ Duplicate comments (1)
src/main/gitea/pull-request-reviews.ts (1)
38-45: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winUse
Promise.allSettled()for per-review comment fetches.This earlier failure mode is still present: one rejected
${base}/${review.id}/commentsrequest rejects the whole function and drops every inline comment for the PR. Keep fulfilled groups and degrade gracefully instead.
🧹 Nitpick comments (6)
src/renderer/src/components/settings/settings-search-keywords.test.ts (1)
41-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAvoid coupling this test to catalog order.
Using
[0]makes this fail if another Tasks search entry is inserted ahead of “Task Providers,” even though the Gitea keyword is still indexed correctly. Look up the specific entry instead of relying on position.Proposed test shape
- const keywords = getTasksPaneSearchEntries()[0]?.keywords ?? [] - expect(keywords).toContain('gitea') - // Sanity: the other providers are still indexed alongside it. - expect(keywords).toEqual(expect.arrayContaining(['github', 'gitlab'])) + const entry = getTasksPaneSearchEntries().find(({ title }) => title === 'Task Providers') + expect(entry?.keywords).toEqual(expect.arrayContaining(['gitea', 'github', 'gitlab']))src/renderer/src/components/gitea-pr-merge-button.tsx (1)
45-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the shared menu/item styling here instead of a custom
text-[12px]button list.This hand-rolled list sidesteps the renderer’s shared typography/component primitives, and the arbitrary
text-[12px]size will drift from the rest of the UI. Please swap these actions to the existing tokenized/menu-item pattern used elsewhere.As per coding guidelines, “All UI work — layout, color, typography, spacing, component selection, UX behavior — must follow docs/STYLEGUIDE.md and use tokens defined in src/renderer/src/assets/main.css and shadcn primitives in src/renderer/src/components/ui/” and “Never invent new color values, font sizes, or shadow tiers when a documented one in STYLEGUIDE.md already covers the role.”
Source: Coding guidelines
src/shared/task-providers.ts (1)
64-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake
giteaConfiguredrequired once the rollout is complete.Keeping the new availability flag optional means any missed producer silently evaluates to “hide Gitea” instead of failing the build. Once the remaining call sites are updated, this should be required so the type system enforces the provider contract.
src/renderer/src/components/gitea-issue-comments.tsx (1)
26-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the ad-hoc text sizes with shared typography tokens.
text-[13px]andtext-[12px]introduce new font sizes insrc/renderer, which drifts from the shared type scale. Please swap them for the matching tokenized classes from the style guide. As per coding guidelines,src/renderer/src/**/*.{ts,tsx,css}must not invent new font sizes, andsrc/renderer/src/**/*.{ts,tsx}UI work must use tokens defined insrc/renderer/src/assets/main.css.Source: Coding guidelines
src/renderer/src/components/settings/gitea-integration-card.tsx (1)
193-203: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the shared button primitive for the unlink action.
This is the only new control in the card that bypasses the shared shadcn button, so it won't inherit the same interaction styling as the rest of the settings UI.
Proposed refactor
- <button + <Button onClick={() => void handleDisconnect(server.id)} aria-label={translate( 'auto.components.settings.gitea.integration.card.d0b31b4845', 'Disconnect {{value0}}', { value0: server.displayName } )} - className="rounded-md p-1 text-muted-foreground/50 transition-colors hover:text-destructive" + variant="ghost" + size="icon" + className="size-7 text-muted-foreground/50 hover:text-destructive" > <Unlink className="size-3.5" /> - </button> + </Button>As per coding guidelines, "All UI work — layout, color, typography, spacing, component selection, UX behavior — must follow
docs/STYLEGUIDE.mdand use tokens defined insrc/renderer/src/assets/main.cssand shadcn primitives insrc/renderer/src/components/ui/."Source: Coding guidelines
src/renderer/src/components/gitea-pr-line-comments.tsx (1)
91-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the shared textarea primitive for this composer.
This raw
<textarea>bypasses the renderer’s shared shadcn primitives and duplicates focus/border styling locally. As per coding guidelines, “All UI work … must followdocs/STYLEGUIDE.mdand use tokens defined insrc/renderer/src/assets/main.cssand shadcn primitives insrc/renderer/src/components/ui/.”Proposed direction
+import { Textarea } from '`@/components/ui/textarea`' ... - <textarea + <Textarea value={body} onChange={(event) => setBody(event.target.value)} rows={2} disabled={submitting} ... - className="min-h-10 flex-1 resize-none rounded-md border border-input bg-transparent px-3 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50" + className="min-h-10 flex-1 resize-none text-sm" />Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 89324aca-cf50-4770-99d2-c1ed5044be8c
📒 Files selected for processing (84)
FIXES-CR.mdsrc/main/gitea/client.test.tssrc/main/gitea/client.tssrc/main/gitea/connect.test.tssrc/main/gitea/connect.tssrc/main/gitea/issues.test.tssrc/main/gitea/issues.tssrc/main/gitea/mappers.test.tssrc/main/gitea/mappers.tssrc/main/gitea/pull-request-reviews.tssrc/main/gitea/pull-requests.test.tssrc/main/gitea/pull-requests.tssrc/main/gitea/repository-ref.test.tssrc/main/gitea/repository-ref.tssrc/main/gitea/request.tssrc/main/gitea/server-store.test.tssrc/main/gitea/server-store.tssrc/main/ipc/gitea-pr.tssrc/main/ipc/gitea-repo-access.tssrc/main/ipc/gitea.tssrc/main/ipc/register-core-handlers.test.tssrc/main/ipc/register-core-handlers.tssrc/main/persistence.test.tssrc/preload/api-types.tssrc/preload/index.tssrc/renderer/src/assets/main.csssrc/renderer/src/components/GiteaIssueWorkspace.tsxsrc/renderer/src/components/GiteaPullRequestWorkspace.tsxsrc/renderer/src/components/GiteaTaskList.tsxsrc/renderer/src/components/TaskPage.tsxsrc/renderer/src/components/automations/automation-source-display.tssrc/renderer/src/components/automations/automation-target-availability.tssrc/renderer/src/components/gitea-connect-dialog.tsxsrc/renderer/src/components/gitea-issue-comments.tsxsrc/renderer/src/components/gitea-issue-label-toggle.test.tssrc/renderer/src/components/gitea-issue-label-toggle.tssrc/renderer/src/components/gitea-issue-meta-controls.tsxsrc/renderer/src/components/gitea-new-issue-dialog.tsxsrc/renderer/src/components/gitea-pr-checks.tsxsrc/renderer/src/components/gitea-pr-file-diff.tsxsrc/renderer/src/components/gitea-pr-line-comments.tsxsrc/renderer/src/components/gitea-pr-merge-button.tsxsrc/renderer/src/components/gitea-pr-request-scope.tssrc/renderer/src/components/gitea-pr-tab-body.tsxsrc/renderer/src/components/gitea-pr-tabs.tssrc/renderer/src/components/gitea-workspace-chrome.tssrc/renderer/src/components/icons/GiteaIcon.tsxsrc/renderer/src/components/right-sidebar/ChecksPanel.tsxsrc/renderer/src/components/right-sidebar/checks-panel-gitea.test.tssrc/renderer/src/components/right-sidebar/checks-panel-gitea.tssrc/renderer/src/components/right-sidebar/checks-panel-review.tssrc/renderer/src/components/right-sidebar/use-hosted-review-actions.tssrc/renderer/src/components/settings/IntegrationsPane.tsxsrc/renderer/src/components/settings/TasksPane.tsxsrc/renderer/src/components/settings/gitea-integration-card.test.tsxsrc/renderer/src/components/settings/gitea-integration-card.tsxsrc/renderer/src/components/settings/settings-search-keywords.test.tssrc/renderer/src/components/settings/task-tracker-integration-cards.tsxsrc/renderer/src/components/settings/tasks-search.tssrc/renderer/src/components/settings/use-integration-provider-status-refresh.tssrc/renderer/src/components/task-page-list-chrome-visibility.tssrc/renderer/src/components/task-page-localized-options.tsxsrc/renderer/src/components/task-source-context-summary-format.tssrc/renderer/src/components/task-source-context-summary.test.tssrc/renderer/src/components/task-source-context-summary.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/store/index.tssrc/renderer/src/store/slices/diffComments.test.tssrc/renderer/src/store/slices/gitea.test.tssrc/renderer/src/store/slices/gitea.tssrc/renderer/src/store/slices/store-test-helpers.tssrc/renderer/src/store/slices/ui.tssrc/renderer/src/store/types.tssrc/shared/gitea-types.tssrc/shared/integration-credential-errors.tssrc/shared/task-providers.test.tssrc/shared/task-providers.tssrc/shared/task-source-context.tssrc/shared/types.tssrc/shared/workspace-name.ts
| ``` | ||
| refactor(gitea): extract focused modules to stay within max-lines | ||
| fix(gitea): localize remaining UI strings and per-server settings state | ||
| fix(gitea): correct Tasks-page drawers and review-panel refresh for Gitea | ||
| fix(gitea): key issue caches by active server and invalidate lists on update | ||
| fix(gitea): guard renderer work-item state against selection changes | ||
| fix(gitea): correct issue/PR read & write edge cases in the main process | ||
| fix(gitea): scope credentials by API-base subpath and fix auth-status 403 | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language to the fenced commit block.
This fence is already tripping markdownlint (MD040) on the touched file. text is enough here.
Proposed fix
-```
+```text
refactor(gitea): extract focused modules to stay within max-lines
fix(gitea): localize remaining UI strings and per-server settings state
fix(gitea): correct Tasks-page drawers and review-panel refresh for Gitea
fix(gitea): key issue caches by active server and invalidate lists on update
fix(gitea): guard renderer work-item state against selection changes
fix(gitea): correct issue/PR read & write edge cases in the main process
fix(gitea): scope credentials by API-base subpath and fix auth-status 403</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 27-27: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
Source: Linters/SAST tools
| saveToken(id, token) | ||
| const file = getServerFile() | ||
| writeServerFile({ | ||
| version: 1, | ||
| activeServerId: id, | ||
| selectedServerId: id, | ||
| servers: [server, ...file.servers.filter((entry) => entry.id !== id)] | ||
| }) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Rollback the saved token if metadata persistence fails.
saveToken(id, token) runs before writeServerFile(...). If the file write throws, the connect flow fails but the PAT stays stored locally without any matching server entry, so the app silently retains a credential the user can no longer manage from settings.
Suggested fix
saveToken(id, token)
- const file = getServerFile()
- writeServerFile({
- version: 1,
- activeServerId: id,
- selectedServerId: id,
- servers: [server, ...file.servers.filter((entry) => entry.id !== id)]
- })
+ try {
+ const file = getServerFile()
+ writeServerFile({
+ version: 1,
+ activeServerId: id,
+ selectedServerId: id,
+ servers: [server, ...file.servers.filter((entry) => entry.id !== id)]
+ })
+ } catch (error) {
+ deleteToken(id)
+ throw error
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| saveToken(id, token) | |
| const file = getServerFile() | |
| writeServerFile({ | |
| version: 1, | |
| activeServerId: id, | |
| selectedServerId: id, | |
| servers: [server, ...file.servers.filter((entry) => entry.id !== id)] | |
| }) | |
| saveToken(id, token) | |
| try { | |
| const file = getServerFile() | |
| writeServerFile({ | |
| version: 1, | |
| activeServerId: id, | |
| selectedServerId: id, | |
| servers: [server, ...file.servers.filter((entry) => entry.id !== id)] | |
| }) | |
| } catch (error) { | |
| deleteToken(id) | |
| throw error | |
| } |
| // Why: empty/malformed base or head SHAs would make the raw-content endpoint | ||
| // fall back to the default branch, rendering a diff against an unintended ref. | ||
| if ( | ||
| typeof args.baseSha !== 'string' || | ||
| !args.baseSha || | ||
| typeof args.headSha !== 'string' || | ||
| !args.headSha | ||
| ) { | ||
| return { original: '', modified: '', originalIsBinary: false, modifiedIsBinary: false } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Actually validate “malformed” SHAs here.
This guard only rejects missing values. Non-empty junk like 'foo' or whitespace still reaches getGiteaPullRequestFileContents, so the raw-content endpoint can still fall back to the default branch and show the wrong diff—the exact case the new comment says we're preventing.
| ipcMain.handle( | ||
| 'gitea:listWorkItems', | ||
| async ( | ||
| _event, | ||
| args: GiteaRepoSelectorArgs & { filter?: GiteaWorkItemFilter; limit?: number } | ||
| ) => { | ||
| const repo = assertRegisteredRepo(args, store) | ||
| const filter = VALID_FILTERS.has(args?.filter as GiteaWorkItemFilter) | ||
| ? (args.filter as GiteaWorkItemFilter) | ||
| : undefined | ||
| return listGiteaWorkItems(repo.path, filter, clampLimit(args.limit), repoConnectionId(repo)) | ||
| } | ||
| ) | ||
|
|
||
| ipcMain.handle( | ||
| 'gitea:issue', | ||
| async (_event, args: GiteaRepoSelectorArgs & { number: number }) => { | ||
| const repo = assertRegisteredRepo(args, store) | ||
| if (typeof args.number !== 'number') { | ||
| return null | ||
| } | ||
| return getGiteaIssue(repo.path, args.number, repoConnectionId(repo)) | ||
| } | ||
| ) | ||
|
|
||
| ipcMain.handle( | ||
| 'gitea:issueComments', | ||
| async (_event, args: GiteaRepoSelectorArgs & { number: number }) => { | ||
| const repo = assertRegisteredRepo(args, store) | ||
| if (typeof args.number !== 'number') { | ||
| return [] | ||
| } | ||
| return listGiteaIssueComments(repo.path, args.number, repoConnectionId(repo)) | ||
| } | ||
| ) | ||
|
|
||
| ipcMain.handle('gitea:labels', async (_event, args: GiteaRepoSelectorArgs) => { | ||
| const repo = assertRegisteredRepo(args, store) | ||
| return listGiteaLabels(repo.path, repoConnectionId(repo)) | ||
| }) | ||
|
|
||
| ipcMain.handle('gitea:assignees', async (_event, args: GiteaRepoSelectorArgs) => { | ||
| const repo = assertRegisteredRepo(args, store) | ||
| return listGiteaAssignees(repo.path, repoConnectionId(repo)) | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check how assertRegisteredRepo validates its args parameter.
rg -n -B2 -A15 'function assertRegisteredRepo' src/main/ipc/gitea-repo-access.tsRepository: stablyai/orca
Length of output: 866
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the repo lookup helper used by assertRegisteredRepo and the surrounding types.
rg -n -B4 -A20 'function findRegisteredGiteaRepo|const findRegisteredGiteaRepo|export function findRegisteredGiteaRepo|type GiteaRepoSelectorArgs|interface GiteaRepoSelectorArgs' src/main/ipc/gitea-repo-access.ts src/main/ipc/gitea.tsRepository: stablyai/orca
Length of output: 4020
Guard assertRegisteredRepo against missing IPC args
assertRegisteredRepo(args, store) still dereferences args directly, so a malformed IPC payload can throw a TypeError before these handlers return a structured access error. Add a null/undefined check in the helper.
| updateIssue: (args: { | ||
| repoPath: string | ||
| repoId?: string | null | ||
| sourceContext?: unknown | ||
| number: number | ||
| updates: unknown | ||
| }): Promise<{ ok: true } | { ok: false; error: string }> => | ||
| ipcRenderer.invoke('gitea:updateIssue', args), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserve updateIssue partial-success warnings in the preload contract.
updateGiteaIssue() can return { ok: true, warning } after a PATCH succeeds but label saving fails; this preload type erases that warning, so renderer callers can’t surface the label failure type-safely.
Proposed fix
- }): Promise<{ ok: true } | { ok: false; error: string }> =>
+ }): Promise<{ ok: true; warning?: string } | { ok: false; error: string }> =>
ipcRenderer.invoke('gitea:updateIssue', args),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| updateIssue: (args: { | |
| repoPath: string | |
| repoId?: string | null | |
| sourceContext?: unknown | |
| number: number | |
| updates: unknown | |
| }): Promise<{ ok: true } | { ok: false; error: string }> => | |
| ipcRenderer.invoke('gitea:updateIssue', args), | |
| updateIssue: (args: { | |
| repoPath: string | |
| repoId?: string | null | |
| sourceContext?: unknown | |
| number: number | |
| updates: unknown | |
| }): Promise<{ ok: true; warning?: string } | { ok: false; error: string }> => | |
| ipcRenderer.invoke('gitea:updateIssue', args), |
| const giteaStatusLoaded = useAppStore((s) => s.giteaStatusLoaded) | ||
| const checkLinearConnection = useAppStore((s) => s.checkLinearConnection) | ||
| const checkJiraConnection = useAppStore((s) => s.checkJiraConnection) | ||
| const refreshGiteaStatus = useAppStore((s) => s.refreshGiteaStatus) |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '## File outline\n'
ast-grep outline src/renderer/src/components/settings/use-integration-provider-status-refresh.ts --view expanded || true
printf '\n## Relevant file contents\n'
sed -n '1,220p' src/renderer/src/components/settings/use-integration-provider-status-refresh.ts
printf '\n## Search for related store methods\n'
rg -n "refreshGiteaStatus|giteaStatusLoaded|checkLinearConnection|checkJiraConnection" src/renderer/src -g '*.ts' -g '*.tsx'
printf '\n## Likely store file(s)\n'
fd -a "useAppStore" src/renderer/src || true
fd -a "store" src/renderer/src || trueRepository: stablyai/orca
Length of output: 19842
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '## Gitea slice outline\n'
ast-grep outline src/renderer/src/store/slices/gitea.ts --view expanded || true
printf '\n## Gitea slice contents\n'
sed -n '1,240p' src/renderer/src/store/slices/gitea.ts
printf '\n## Gitea tests around refresh behavior\n'
sed -n '240,340p' src/renderer/src/store/slices/gitea.test.ts
printf '\n## Similar hook in TaskPage\n'
sed -n '7100,7145p' src/renderer/src/components/TaskPage.tsxRepository: stablyai/orca
Length of output: 13033
Prevent duplicate Gitea status refreshes. giteaStatusLoaded stays false until refreshGiteaStatus() resolves, so any rerender from the Linear/Jira/Preflight updates can start another Gitea fetch before the first one completes. Add an in-flight guard or store-level dedupe here.
| const [selectedGiteaItem, setSelectedGiteaItem] = useState<GiteaWorkspaceSelection | null>(null) | ||
| const [selectedGiteaPr, setSelectedGiteaPr] = useState<GiteaWorkspaceSelection | null>(null) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Clear Gitea selections when leaving the Gitea source.
selectedGiteaItem/selectedGiteaPr persist after switching providers, but the Escape guard still treats them as open modal state. That leaves Esc unable to close the current Tasks page and can reopen stale Gitea details when switching back.
Suggested fix
const [selectedGiteaItem, setSelectedGiteaItem] = useState<GiteaWorkspaceSelection | null>(null)
const [selectedGiteaPr, setSelectedGiteaPr] = useState<GiteaWorkspaceSelection | null>(null)
+
+ useEffect(() => {
+ if (taskSource !== 'gitea') {
+ setSelectedGiteaItem(null)
+ setSelectedGiteaPr(null)
+ }
+ }, [taskSource])Also applies to: 7059-7061
| const linkedWorkItem: LinkedWorkItemSummary = { | ||
| type: item.type === 'pull' ? 'pr' : 'issue', | ||
| number: item.number, | ||
| title: item.title, | ||
| url: item.url | ||
| } | ||
| openModal('new-workspace-composer', { | ||
| linkedWorkItem, | ||
| taskSourceContext: getTaskPageRepoSourceContext(repo, 'gitea'), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Mark Gitea composer links with provider: 'gitea'.
Gitea issues/PRs are converted to the same issue/pr shape used by GitHub, so omitting provider can misclassify the workspace intent and attachment metadata.
Suggested fix
const linkedWorkItem: LinkedWorkItemSummary = {
type: item.type === 'pull' ? 'pr' : 'issue',
+ provider: 'gitea',
number: item.number,
title: item.title,
url: item.url
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const linkedWorkItem: LinkedWorkItemSummary = { | |
| type: item.type === 'pull' ? 'pr' : 'issue', | |
| number: item.number, | |
| title: item.title, | |
| url: item.url | |
| } | |
| openModal('new-workspace-composer', { | |
| linkedWorkItem, | |
| taskSourceContext: getTaskPageRepoSourceContext(repo, 'gitea'), | |
| const linkedWorkItem: LinkedWorkItemSummary = { | |
| type: item.type === 'pull' ? 'pr' : 'issue', | |
| provider: 'gitea', | |
| number: item.number, | |
| title: item.title, | |
| url: item.url | |
| } | |
| openModal('new-workspace-composer', { | |
| linkedWorkItem, | |
| taskSourceContext: getTaskPageRepoSourceContext(repo, 'gitea'), |
| "gitea": { | ||
| "connect": { | ||
| "dialog": { | ||
| "d2823a1de0": "Connect Gitea server", | ||
| "d076a10ae3": "Use a Gitea server URL and a personal access token to browse and manage issues.", | ||
| "690c1da1bb": "Gitea server URL", | ||
| "83b75fa9b7": "https://gitea.example.com", | ||
| "062ae1ea05": "Access token", | ||
| "79c04f358b": "Gitea personal access token", | ||
| "3ecb3e1153": "Create a token under your Gitea user settings → Applications, with read/write access to issues.", | ||
| "4d1f2d9f21": "Your token is stored locally and encrypted when local runtime storage supports it.", | ||
| "7acfed1da5": "Cancel", | ||
| "f5d526570f": "Verifying…", | ||
| "f68c3d011f": "Connect" | ||
| } | ||
| }, | ||
| "issue": { | ||
| "meta": { | ||
| "controls": { | ||
| "ba343731c0": "Failed to update issue.", | ||
| "63f46a8d86": "Title", | ||
| "8cc407607b": "Labels", | ||
| "11fac85e07": "No labels in this repo.", | ||
| "401f7dc7a2": "Assignees", | ||
| "3cd6f5acbd": "No assignable users." | ||
| } | ||
| }, | ||
| "comments": { | ||
| "8608602d16": "Comments", | ||
| "ea3f6f14a1": "No comments yet.", | ||
| "6a519bdb1c": "Unknown", | ||
| "7649eb9d31": "recently" | ||
| } | ||
| }, | ||
| "pr": { | ||
| "file": { | ||
| "diff": { | ||
| "81e07de6a5": "Binary file not shown.", | ||
| "f1a2b3c4d5": "Failed to load file diff.", | ||
| "e6f7a8b9c0": "Retry", | ||
| "643d805160": "Added", | ||
| "552647cfb3": "Modified", | ||
| "6a18d47646": "Deleted", | ||
| "184ffbc460": "Renamed", | ||
| "3e96a427af": "Copied", | ||
| "1e228bc58e": "Changed" | ||
| } | ||
| }, | ||
| "checks": { | ||
| "bed82a55cd": "No status checks reported for this PR.", | ||
| "448c6113a4": "Open check details" | ||
| }, | ||
| "line": { | ||
| "comments": { | ||
| "489b36633a": "Line {{value0}}", | ||
| "c01a062b92": "Unknown", | ||
| "4e59b0c4f8": "Line #", | ||
| "a43b5cc38b": "New-file line number from the diff", | ||
| "02339776da": "Review comment…", | ||
| "317313f8af": "Comment", | ||
| "6cbd906d88": "Comment on a line" | ||
| } | ||
| }, | ||
| "tab": { | ||
| "body": { | ||
| "1fe3a05742": "No description provided.", | ||
| "413feaa83a": "Inline", | ||
| "5372c28376": "Side by side", | ||
| "53ab0c5a49": "No changed files." | ||
| } | ||
| }, | ||
| "merge": { | ||
| "button": { | ||
| "2378ab4908": "Conflicts", | ||
| "8e662b9b6b": "Merge", | ||
| "725a660786": "Squash", | ||
| "f765e24257": "Rebase" | ||
| } | ||
| } | ||
| }, | ||
| "new": { | ||
| "issue": { | ||
| "dialog": { | ||
| "db951c3a29": "New issue in {{value0}}", | ||
| "b42f72cfc5": "Create an issue in the selected Gitea project.", | ||
| "becfadf1df": "Title", | ||
| "6c8b731fb2": "Description", | ||
| "032fd2ed27": "Cancel", | ||
| "5e1d23a112": "Create issue" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "GiteaTaskList": { | ||
| "heading": "Issues & pull requests", | ||
| "c07dd13940": "All open", | ||
| "ab2a229e50": "Assigned to me", | ||
| "74f30cd767": "Created", | ||
| "031c70d963": "Closed", | ||
| "bf66fad935": "All", | ||
| "65a3bffe7a": "Issues", | ||
| "9b21aab4c9": "PRs", | ||
| "9986e0e679": "Merged", | ||
| "747423c0f8": "Draft", | ||
| "980519b0c8": "Open", | ||
| "0285721e62": "Failed to load Gitea tasks.", | ||
| "5997067bee": "New issue", | ||
| "e3db16e7c7": "Refresh Gitea tasks", | ||
| "34cd2d1024": "Select a Gitea-hosted project to see its issues and pull requests.", | ||
| "6e1e6691d7": "Loading Gitea tasks…", | ||
| "00f67933b5": "No open issues or pull requests." | ||
| }, | ||
| "GiteaIssueWorkspace": { | ||
| "comments": "Comments", | ||
| "noComments": "No comments yet.", | ||
| "unknown": "Unknown", | ||
| "47b064c003": "{{value0}} copied", | ||
| "927222e135": "Failed to copy.", | ||
| "2d2e69fe70": "Failed to update issue.", | ||
| "b7c8d9e0f1": "Failed to load issue.", | ||
| "565680a39b": "Failed to add comment.", | ||
| "a792c22414": "Gitea item", | ||
| "01b6ea43ca": "Preview and start work from the selected Gitea item.", | ||
| "ce513e3ca2": "Close", | ||
| "e849a4c80f": "Start workspace", | ||
| "c0d4f97901": "Closed", | ||
| "ab08895bb5": "Open", | ||
| "1ab551635b": "Reopen", | ||
| "6b3c5a3144": "Close issue", | ||
| "63cbb99973": "Open in Gitea", | ||
| "c29b657be3": "Copy URL", | ||
| "ca242eaf5d": "No description provided.", | ||
| "0fee3f06ff": "Add a comment...", | ||
| "d3e0033e0f": "Comment" | ||
| }, | ||
| "GiteaPullRequestWorkspace": { | ||
| "conflicts": "Conflicts", | ||
| "merge": "Merge", | ||
| "noDescription": "No description provided.", | ||
| "inline": "Inline", | ||
| "sideBySide": "Side by side", | ||
| "noFiles": "No changed files.", | ||
| "f5cf34ed40": "Failed to add comment.", | ||
| "c2d3e4f5a6": "Failed to load PR checks.", | ||
| "22f9c29c9b": "Pull request merged.", | ||
| "d93415a77d": "Failed to merge.", | ||
| "c7c84081fc": "Failed to add review comment.", | ||
| "f51240a59a": "Conversation", | ||
| "4f4abe43ab": "Files", | ||
| "95a6c97ba6": "Checks", | ||
| "8e44409da0": "Gitea pull request", | ||
| "0a432f2c1a": "Review files, checks, and conversation, then merge or start work.", | ||
| "5fa5ec9fff": "Close", | ||
| "f88c7cf08c": "Start workspace", | ||
| "9813b44d73": "Open in Gitea", | ||
| "ddc99ed766": "Add a comment...", | ||
| "d646ddef48": "Comment" | ||
| }, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Regenerate these locale IDs with the catalog sync.
Keys like f1a2b3c4d5, c2d3e4f5a6, and d4e5f6a7b8 look hand-authored instead of sync-generated. That makes the locale catalog easy to drift or get rewritten on the next localization sync. Based on learnings, new locale entries should be auto-keyed using SHA1(file path + source text) and kept in parity via pnpm run sync:localization-catalog.
Source: Learnings
| giteaConnect: async (args) => { | ||
| const result = await window.api.gitea.connect(args) | ||
| if (result.ok) { | ||
| await get().refreshGiteaStatus() | ||
| } | ||
| return result.ok ? { ok: true } : { ok: false, error: result.error } | ||
| }, | ||
|
|
||
| giteaDisconnect: async (serverId) => { | ||
| await window.api.gitea.disconnect(serverId ? { serverId } : undefined) | ||
| await get().refreshGiteaStatus() | ||
| }, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Clear cached issue data when credentials change.
refreshGiteaStatus() updates status, but the caches survive connect/disconnect. Because scopeKey() can be derived from sourceContext alone and server IDs are stable per apiBaseUrl, reconnecting the same host with a different token can serve the previous account's work items/details until the TTL expires.
Proposed fix
giteaConnect: async (args) => {
const result = await window.api.gitea.connect(args)
if (result.ok) {
await get().refreshGiteaStatus()
+ set({ giteaWorkItems: {}, giteaIssueDetail: {} })
}
return result.ok ? { ok: true } : { ok: false, error: result.error }
},
giteaDisconnect: async (serverId) => {
await window.api.gitea.disconnect(serverId ? { serverId } : undefined)
await get().refreshGiteaStatus()
+ set({ giteaWorkItems: {}, giteaIssueDetail: {} })
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| giteaConnect: async (args) => { | |
| const result = await window.api.gitea.connect(args) | |
| if (result.ok) { | |
| await get().refreshGiteaStatus() | |
| } | |
| return result.ok ? { ok: true } : { ok: false, error: result.error } | |
| }, | |
| giteaDisconnect: async (serverId) => { | |
| await window.api.gitea.disconnect(serverId ? { serverId } : undefined) | |
| await get().refreshGiteaStatus() | |
| }, | |
| giteaConnect: async (args) => { | |
| const result = await window.api.gitea.connect(args) | |
| if (result.ok) { | |
| await get().refreshGiteaStatus() | |
| set({ giteaWorkItems: {}, giteaIssueDetail: {} }) | |
| } | |
| return result.ok ? { ok: true } : { ok: false, error: result.error } | |
| }, | |
| giteaDisconnect: async (serverId) => { | |
| await window.api.gitea.disconnect(serverId ? { serverId } : undefined) | |
| await get().refreshGiteaStatus() | |
| set({ giteaWorkItems: {}, giteaIssueDetail: {} }) | |
| }, |
This is @beatkapo's Gitea task source from #5493 — credit for the entire integration is theirs — brought current and hardened. Their branch had fallen 650+ commits behind
mainand gone conflicting; this resolves that and adds two functional fixes I found validating it against a live self-hosted Gitea (v1.26.2).On top of #5493
main— 6 files (5 i18n key-unions, cross-checked against an independent 3-way JSON merge;ChecksPanel.tsx, wheremain's refactoredselectChecksPanelReviewhad no Gitea branch and would have dead-coded the Gitea checks path — extended the helper instead).WorkspaceIntentWorkItem.providerto include'gitea'(the PR widened the centralTaskProviderunion but missed this downstream one →typecheck:webTS2322).parseGiteaRepoRefstored the repo host fromurl.hostname(no port), but the stored-server match (giteaServerHost/getServerForHost) and theORCA_GITEA_API_BASE_URLscope check (sameHost) compareURL.host(with the port). A Gitea on e.g.:3000never matched its stored server, so its token was dropped — reads ran unauthenticated, writes blocked. The repo host now storesurl.host, so store and compare agree;:443/reverse-proxy still collapses identically, the env-token path is untouched, and SSH remotes keep the bare hostname. Validated against a live Gitea on:3000(authenticated 200 with token; 404 without).repoLabels(limit:100 page:1, no org labels); Gitea's label PUT replaces the whole set, so any applied label absent from that list was silently dropped. It now toggles as a delta against the IDs the issue actually carries (viaGiteaIssue.labelIds), falling back to name-resolution only while detail is still loading. Org labels and labels beyond page 1 survive.Tests
src/main/giteasuite + new label-toggle module: green.repository-ref,server-store); label-delta unit tests + mapper coverage.typecheck:nodeandtypecheck:web: clean.cc @beatkapo — thank you for the original implementation. Happy to fold these fixes into #5493 instead if you'd prefer to keep it as your PR.