feat: add Linear integration (issue linking, branch auto-detect, settings)#66
feat: add Linear integration (issue linking, branch auto-detect, settings)#66asaadam wants to merge 1 commit into
Conversation
…ings) - New LinearService using @linear/sdk: extracts issue keys from branch name, fetches issues with 5-min cache, validates API keys - LinearSection in Right panel shows linked issues with status dot, priority, URL - LinearLookupField in AddWorktreeDialog for manual issue linking - SettingsLinear page for API key management with Test Connection - Full IPC pipeline: linear:getIssuesForBranch, linear:getIssueByKey, linear:validateApiKey - i18n strings in en/ja/id for right, settings, and sidebar namespaces Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive integration with Linear, mirroring the existing Jira functionality. It includes a new backend service for interacting with the Linear API, IPC handlers, and several UI components for displaying linked issues in the checks panel and looking up issues during worktree creation. Feedback focuses on optimizing issue lookups by using the direct client.issue method in the Linear SDK and refining the state management in the issue lookup field to handle invalid or duplicate inputs more robustly.
| // searchIssues is the type-safe way to look up by identifier string | ||
| const result = await client.searchIssues(key) | ||
| // Find the exact identifier match (search may return partial matches) | ||
| const issue = result.nodes.find((n) => n.identifier === key) |
There was a problem hiding this comment.
Using client.searchIssues(key) is less efficient for looking up a specific issue by its identifier (e.g., 'ENG-123'). The Linear SDK provides a direct client.issue(idOrIdentifier) method which is optimized for this purpose and avoids the overhead of a search query and manual filtering.
| // searchIssues is the type-safe way to look up by identifier string | |
| const result = await client.searchIssues(key) | |
| // Find the exact identifier match (search may return partial matches) | |
| const issue = result.nodes.find((n) => n.identifier === key) | |
| const issue = await client.issue(key) |
| const key = extractLinearKey(raw) | ||
| if (!key) { | ||
| if (raw.trim()) { setError(t('linearInvalidInput')); onError(t('linearInvalidInput')) } | ||
| return | ||
| } | ||
| if (resolvedKeyRef.current === key) return |
There was a problem hiding this comment.
There are two edge cases in the lookup logic:
- If the input is invalid (e.g., doesn't match the Linear key pattern), the previous successfully resolved issue is not cleared from the state, which can lead to a confusing UI where an error message is shown alongside an old issue card.
- If the user enters a key that was already resolved, the function returns early without clearing any existing error messages from previous failed attempts.
Clearing the issue state on invalid input and clearing the error state on a duplicate valid input improves the robustness of the lookup field.
| const key = extractLinearKey(raw) | |
| if (!key) { | |
| if (raw.trim()) { setError(t('linearInvalidInput')); onError(t('linearInvalidInput')) } | |
| return | |
| } | |
| if (resolvedKeyRef.current === key) return | |
| const key = extractLinearKey(raw) | |
| if (!key) { | |
| if (raw.trim()) { | |
| setError(t('linearInvalidInput')) | |
| onError(t('linearInvalidInput')) | |
| } | |
| setIssue(null) | |
| return | |
| } | |
| if (resolvedKeyRef.current === key) { | |
| setError('') | |
| return | |
| } |
There was a problem hiding this comment.
Pull request overview
This PR adds a new Linear integration across the main/preload/renderer layers, including settings for storing an API key, IPC endpoints to fetch issues for the current branch, and UI surfaces in both the Checks panel and Add Worktree dialog.
Changes:
- Add a main-process
LinearService(via@linear/sdk) plus IPC handlers for availability, API key validation, and issue lookup. - Add renderer UI: Linear section in Checks + Linear issue lookup field in AddWorktreeDialog; add a new Settings → Linear page for API key entry and connection test.
- Add i18n strings and styling for the new Linear UI elements, plus new shared types.
Reviewed changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Locks new dependency graph additions for Linear SDK. |
| package.json | Adds @linear/sdk dependency. |
| src/main/ipc.ts | Registers Linear IPC handlers in the main process. |
| src/main/services/linear.ts | Implements Linear API client, branch key extraction, caching, and validation. |
| src/preload/index.ts | Exposes Linear IPC methods via the context bridge API. |
| src/renderer/lib/ipc.ts | Adds typed renderer wrappers for Linear IPC calls. |
| src/renderer/lib/storageKeys.ts | Adds linearApiKey localStorage key. |
| src/renderer/store/ui/settings.ts | Adds linearApiKey state + setter persisted to localStorage. |
| src/renderer/types/session.ts | Adds LinearIssue/LinearResult types for renderer consumption. |
| src/renderer/lib/branchValidation.ts | Adds Linear key extraction + branch derivation helpers. |
| src/renderer/components/Sidebar/LinearLookupField.tsx | Adds Linear issue lookup UX for worktree creation. |
| src/renderer/components/Sidebar/AddWorktreeDialog.tsx | Wires Linear lookup into the add-worktree flow. |
| src/renderer/components/Right/LinearSection.tsx | Adds a Checks tab section to display Linear issues linked to the branch. |
| src/renderer/components/Right/ChecksView.tsx | Fetches Linear issues alongside PR/Jira data and passes to sections. |
| src/renderer/components/Right/ChecksSections.tsx | Renders Linear section in the no-PR state as well. |
| src/renderer/components/Settings/SettingsLinear.tsx | New settings page for API key save + “Test Connection”. |
| src/renderer/components/Settings/SettingsNav.tsx | Adds Linear to the Integrations nav group. |
| src/renderer/components/Settings/SettingsOverlay.tsx | Registers the new Linear settings section. |
| src/renderer/styles/dialogs.css | Styles for the Linear lookup field card/status in the dialog. |
| src/renderer/styles/checks.css | Shared Linear issue key/status badge styles + warn row styles. |
| src/renderer/locales/en/sidebar.json | Adds Linear lookup strings (EN). |
| src/renderer/locales/en/settings.json | Adds Linear settings strings (EN). |
| src/renderer/locales/en/right.json | Adds Linear section strings (EN). |
| src/renderer/locales/ja/sidebar.json | Adds Linear lookup strings (JA). |
| src/renderer/locales/ja/settings.json | Adds Linear settings strings (JA). |
| src/renderer/locales/ja/right.json | Adds Linear section strings (JA). |
| src/renderer/locales/id/sidebar.json | Adds Linear lookup strings (ID). |
| src/renderer/locales/id/settings.json | Adds Linear settings strings (ID). |
| src/renderer/locales/id/right.json | Adds Linear section strings (ID). |
| JIRA_INTEGRATION.md | Adds internal documentation describing the existing Jira integration as a reference. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <span className="linear-issue-key">{issue.key}</span> | ||
| <span className="checks-row-label">{issue.summary}</span> | ||
| <span className={`linear-status-badge linear-status-badge--${issue.statusCategory}`}> | ||
| {issue.status} |
There was a problem hiding this comment.
PR description mentions a “priority badge” in the right-panel Linear section, but this row currently only renders the status badge and never displays priority (issue.type/priorityLabel). Either render priority here (and name the backing field accordingly), or update the PR description/strings to match the implemented UI.
| /** Mapped from Linear workflow state type */ | ||
| statusCategory: 'new' | 'indeterminate' | 'done' | ||
| /** issue.priorityLabel or empty string */ | ||
| type: string |
There was a problem hiding this comment.
LinearIssue.type is documented as issue.priorityLabel, but the field name type implies issue type (bug/feature) and matches Jira’s semantics. Consider renaming this field to priorityLabel/priority (and updating call sites), or adjust the comment/UI so the meaning is unambiguous.
| type: string | |
| priorityLabel: string |
| status: string | ||
| statusCategory: 'new' | 'indeterminate' | 'done' | ||
| /** issue.priorityLabel or empty string */ | ||
| type: string |
There was a problem hiding this comment.
In LinearIssue, the type field is populated from priorityLabel (per the comment), which is confusing and differs from Jira’s type meaning. Rename the property to priorityLabel/priority (and adjust renderer types accordingly) to avoid mismatched semantics in the shared UI components.
| type: string | |
| priorityLabel: string |
| // Cache parsed issue data by "${key}:${apiKey.slice(-8)}" — 5 min TTL. | ||
| // Including the key suffix prevents one user's cache from serving another key's results. | ||
| private issueCache = new ServiceCache<LinearIssue | null>(300_000) |
There was a problem hiding this comment.
The issue cache key uses apiKey.slice(-8) as a user discriminator. This can collide between different API keys with the same suffix and return incorrect cached issues after a key rotation/switch. Prefer a collision-resistant discriminator (e.g., a short hash of the full apiKey) or include the full apiKey in-memory.
| export function extractLinearKey(input: string): string | null { | ||
| const trimmed = input.trim() | ||
| if (!trimmed) return null | ||
|
|
||
| // Try extracting from Linear URL path: /issue/KEY-123/ |
There was a problem hiding this comment.
extractLinearKey/deriveBranchFromLinear introduce new parsing/derivation behavior, but branchValidation.test.ts currently only covers the Jira helpers. Add unit tests for Linear key extraction (raw key + URL) and branch derivation to prevent regressions.
| const [available, setAvailable] = useState<boolean | null>(null) | ||
| useEffect(() => { | ||
| if (!linearApiKey) { setAvailable(false); return } | ||
| ipc.linear.isAvailable(linearApiKey).then(setAvailable).catch(() => setAvailable(false)) |
There was a problem hiding this comment.
useLinearAvailable does an IPC roundtrip (and sends the API key) even though linear:isAvailable currently only checks for a non-empty string. Consider making this a local Boolean(linearApiKey.trim()) check, or have the main-side availability check provide additional value (e.g., cached validation result).
| const [available, setAvailable] = useState<boolean | null>(null) | |
| useEffect(() => { | |
| if (!linearApiKey) { setAvailable(false); return } | |
| ipc.linear.isAvailable(linearApiKey).then(setAvailable).catch(() => setAvailable(false)) | |
| const [available, setAvailable] = useState<boolean | null>(() => Boolean(linearApiKey.trim())) | |
| useEffect(() => { | |
| setAvailable(Boolean(linearApiKey.trim())) |
Summary
ENG-123) and a manual lookup field when creating a new worktree.Layers touched
src/main/) — services, IPC handlerssrc/preload/) — context bridge APIsrc/renderer/) — components, stores, libApp.css)Changes
Sidebar / Center / Right panel:
LinearSection.tsx— new Right-panel section showing linked Linear issues with status dots, priority badge, and click-to-open URLChecksView.tsx/ChecksSections.tsx— integrateLinearSectioninto the checks tab; fetch issues on mountLinearLookupField.tsx— new combobox inAddWorktreeDialogto manually link a Linear issue when creating a worktree (sets branch name from issue key)Stores (
projects.ts/sessions.ts/ui.ts):store/ui/settings.ts— addslinearApiKeyfieldtypes/session.ts— addslinearIssueKeyfield to session typeServices (
git.ts/claude.ts/github.ts/pty.ts):services/linear.ts— newLinearServiceusing@linear/sdk; extracts issue keys from branch name, fetches issues with 5-min cache, validates API keysIPC (
main/ipc.ts→preload/index.ts→lib/ipc.ts):linear:getIssuesForBranch,linear:getIssueByKey,linear:validateApiKeyhandlers wired end-to-endSettings:
SettingsLinear.tsx— new settings page for API key entry with "Test Connection" validationSettingsNav.tsx/SettingsOverlay.tsx— register new Linear settings pagei18n:
locales/{en,ja,id}/right.json— Linear section stringslocales/{en,ja,id}/settings.json— Linear settings stringslocales/{en,ja,id}/sidebar.json— Linear lookup field stringsStyles:
checks.css— Linear section layout, status dot, priority badgedialogs.css— LinearLookupField styles in AddWorktreeDialogHow to test
yarn deveng-123-my-feature) — the Linear section in the Checks tab should display the linked issue with status dot and priority.Screenshots
clicking this will open the modal
Checklist
yarn devyarn typecheck