Skip to content

feat: add Linear integration (issue linking, branch auto-detect, settings)#66

Open
asaadam wants to merge 1 commit into
gedeagas:mainfrom
asaadam:zhengzhou-vx
Open

feat: add Linear integration (issue linking, branch auto-detect, settings)#66
asaadam wants to merge 1 commit into
gedeagas:mainfrom
asaadam:zhengzhou-vx

Conversation

@asaadam
Copy link
Copy Markdown
Collaborator

@asaadam asaadam commented Apr 20, 2026

Summary

  • Adds full Linear integration: fetch issues linked to the current branch, display them in the Right panel's Checks section, and configure the API key via a new Settings page.
  • Supports branch-name auto-detection of Linear issue keys (e.g. ENG-123) and a manual lookup field when creating a new worktree.
  • Full IPC pipeline threaded through all 3 layers (main → preload → renderer).

Layers touched

  • Main process (src/main/) — services, IPC handlers
  • Preload (src/preload/) — context bridge API
  • Renderer (src/renderer/) — components, stores, lib
  • Styles (App.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 URL
  • ChecksView.tsx / ChecksSections.tsx — integrate LinearSection into the checks tab; fetch issues on mount
  • LinearLookupField.tsx — new combobox in AddWorktreeDialog to 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 — adds linearApiKey field
  • types/session.ts — adds linearIssueKey field to session type

Services (git.ts / claude.ts / github.ts / pty.ts):

  • services/linear.ts — new LinearService using @linear/sdk; extracts issue keys from branch name, fetches issues with 5-min cache, validates API keys

IPC (main/ipc.tspreload/index.tslib/ipc.ts):

  • linear:getIssuesForBranch, linear:getIssueByKey, linear:validateApiKey handlers wired end-to-end

Settings:

  • SettingsLinear.tsx — new settings page for API key entry with "Test Connection" validation
  • SettingsNav.tsx / SettingsOverlay.tsx — register new Linear settings page

i18n:

  • locales/{en,ja,id}/right.json — Linear section strings
  • locales/{en,ja,id}/settings.json — Linear settings strings
  • locales/{en,ja,id}/sidebar.json — Linear lookup field strings

Styles:

  • checks.css — Linear section layout, status dot, priority badge
  • dialogs.css — LinearLookupField styles in AddWorktreeDialog

How to test

  1. yarn dev
  2. Go to Settings → Linear, enter a valid Linear API key and click "Test Connection" — should show success.
  3. Create a worktree from a branch containing a Linear issue key (e.g. eng-123-my-feature) — the Linear section in the Checks tab should display the linked issue with status dot and priority.
  4. Use the Linear lookup field in AddWorktreeDialog to search and select an issue — branch name should auto-populate from the issue key.
  5. Check all three locale languages display Linear strings correctly.

Screenshots

clicking this will open the modal

image

Checklist

  • Self-reviewed the diff
  • Tested locally with yarn dev
  • Types pass — yarn typecheck
  • No console errors or warnings in DevTools
  • IPC changes are threaded through all 3 layers (main → preload → renderer)
  • New state is added to the correct Zustand store

…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>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +90 to +93
// 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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
// 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)

Comment on lines +34 to +39
const key = extractLinearKey(raw)
if (!key) {
if (raw.trim()) { setError(t('linearInvalidInput')); onError(t('linearInvalidInput')) }
return
}
if (resolvedKeyRef.current === key) return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There are two edge cases in the lookup logic:

  1. 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.
  2. 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.

Suggested change
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
}

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +72 to +75
<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}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
/** Mapped from Linear workflow state type */
statusCategory: 'new' | 'indeterminate' | 'done'
/** issue.priorityLabel or empty string */
type: string
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
type: string
priorityLabel: string

Copilot uses AI. Check for mistakes.
status: string
statusCategory: 'new' | 'indeterminate' | 'done'
/** issue.priorityLabel or empty string */
type: string
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
type: string
priorityLabel: string

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +38
// 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)
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +104
export function extractLinearKey(input: string): string | null {
const trimmed = input.trim()
if (!trimmed) return null

// Try extracting from Linear URL path: /issue/KEY-123/
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +129
const [available, setAvailable] = useState<boolean | null>(null)
useEffect(() => {
if (!linearApiKey) { setAvailable(false); return }
ipc.linear.isAvailable(linearApiKey).then(setAvailable).catch(() => setAvailable(false))
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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()))

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants