From f0fec5dedfcd18b8c1633245b8d9bb7d8e8043e0 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:04:43 -0700 Subject: [PATCH] fix(settings): tighten Linear skill section behavior --- .../settings/LinearAgentSkillPane.test.tsx | 2 + .../settings/LinearAgentSkillPane.tsx | 10 ++- .../settings/SkillUsageExamplesSection.tsx | 8 +- .../hooks/useLinearProviderConnected.test.tsx | 90 +++++++++++++++++++ .../src/hooks/useLinearProviderConnected.ts | 11 ++- src/renderer/src/i18n/locales/en.json | 16 +++- src/renderer/src/i18n/locales/es.json | 16 +++- src/renderer/src/i18n/locales/ja.json | 16 +++- src/renderer/src/i18n/locales/ko.json | 16 +++- src/renderer/src/i18n/locales/zh.json | 16 +++- src/renderer/src/lib/linear-usage-examples.ts | 62 ++++++++++--- 11 files changed, 230 insertions(+), 33 deletions(-) create mode 100644 src/renderer/src/hooks/useLinearProviderConnected.test.tsx diff --git a/src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx b/src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx index 57395b7b28d..fac6db03bba 100644 --- a/src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx +++ b/src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx @@ -101,6 +101,8 @@ describe('LinearAgentSkillPane', () => { expect(examples).toHaveLength(5) for (const example of examples) { expect(markup).toContain(example.title) + expect(example.prompt).toContain('/orca-linear') + expect(example.prompt).not.toContain('{{value0}}') } }) diff --git a/src/renderer/src/components/settings/LinearAgentSkillPane.tsx b/src/renderer/src/components/settings/LinearAgentSkillPane.tsx index c7146370c88..38dd9dd62de 100644 --- a/src/renderer/src/components/settings/LinearAgentSkillPane.tsx +++ b/src/renderer/src/components/settings/LinearAgentSkillPane.tsx @@ -102,8 +102,14 @@ export function LinearAgentSkillPane(): React.JSX.Element { )} command={installCommand} installedCommand={updateCommand} - terminalTitle="Linear skill setup" - terminalAriaLabel="Linear skill install terminal" + terminalTitle={translate( + 'auto.components.settings.LinearAgentSkillPane.terminalTitle', + 'Linear skill setup' + )} + terminalAriaLabel={translate( + 'auto.components.settings.LinearAgentSkillPane.terminalAriaLabel', + 'Linear skill install terminal' + )} terminalWorktreeId="settings-linear-skill-terminal" terminalShellOverride={activeSkillRuntime.terminalShellOverride} installed={linearSkillInstalled} diff --git a/src/renderer/src/components/settings/SkillUsageExamplesSection.tsx b/src/renderer/src/components/settings/SkillUsageExamplesSection.tsx index f4d615cf6bf..ecadbb44b6f 100644 --- a/src/renderer/src/components/settings/SkillUsageExamplesSection.tsx +++ b/src/renderer/src/components/settings/SkillUsageExamplesSection.tsx @@ -1,5 +1,6 @@ import { useState } from 'react' import type { LucideIcon } from 'lucide-react' +import { Button } from '@/components/ui/button' import type { SkillUsageExample } from '@/lib/skill-usage-example' import { SkillUsageExampleDialog } from './SkillUsageExampleDialog' @@ -31,10 +32,11 @@ export function SkillUsageExamplesSection({ {examples.map((example) => { const Icon = resolveIcon(example) return ( - + ) })} diff --git a/src/renderer/src/hooks/useLinearProviderConnected.test.tsx b/src/renderer/src/hooks/useLinearProviderConnected.test.tsx new file mode 100644 index 00000000000..c10afed44c5 --- /dev/null +++ b/src/renderer/src/hooks/useLinearProviderConnected.test.tsx @@ -0,0 +1,90 @@ +// @vitest-environment happy-dom + +import { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it } from 'vitest' +import type { LinearConnectionStatus } from '../../../shared/types' +import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context' +import { useAppStore } from '@/store' +import { useLinearProviderConnected } from './useLinearProviderConnected' + +let root: Root | null = null +let container: HTMLDivElement | null = null + +describe('useLinearProviderConnected', () => { + afterEach(() => { + if (root) { + act(() => root?.unmount()) + } + root = null + container?.remove() + container = null + useAppStore.setState({ + linearStatus: { connected: false, viewer: null }, + linearStatusContextKey: null + }) + }) + + it('does not rerender for Linear metadata changes that preserve section visibility', () => { + const settings = useAppStore.getState().settings + useAppStore.setState({ + linearStatus: { connected: true, viewer: null }, + linearStatusContextKey: getProviderRuntimeContextKey(settings) + }) + let renders = 0 + let connected = false + + function Probe(): null { + renders += 1 + connected = useLinearProviderConnected() + return null + } + + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + act(() => root?.render()) + + expect(connected).toBe(true) + expect(renders).toBe(1) + + act(() => { + useAppStore.setState({ + linearStatus: { + ...useAppStore.getState().linearStatus, + selectedWorkspaceId: 'workspace-2' + } satisfies LinearConnectionStatus + }) + }) + + expect(connected).toBe(true) + expect(renders).toBe(1) + + act(() => { + useAppStore.setState({ linearStatus: { connected: false, viewer: null } }) + }) + + expect(connected).toBe(false) + expect(renders).toBe(2) + }) + + it('rejects a connected status from a different runtime context', () => { + useAppStore.setState({ + linearStatus: { connected: true, viewer: null }, + linearStatusContextKey: 'stale-runtime-context' + }) + let connected = true + + function Probe(): null { + connected = useLinearProviderConnected() + return null + } + + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + act(() => root?.render()) + + expect(connected).toBe(false) + }) +}) diff --git a/src/renderer/src/hooks/useLinearProviderConnected.ts b/src/renderer/src/hooks/useLinearProviderConnected.ts index 52735e755dc..01896987a83 100644 --- a/src/renderer/src/hooks/useLinearProviderConnected.ts +++ b/src/renderer/src/hooks/useLinearProviderConnected.ts @@ -6,8 +6,11 @@ import { useAppStore } from '@/store' // the rendered section from drifting. The context-key guard rejects a status // fetched for a different runtime environment than the active one. export function useLinearProviderConnected(): boolean { - const linearStatus = useAppStore((s) => s.linearStatus) - const linearStatusContextKey = useAppStore((s) => s.linearStatusContextKey) - const settings = useAppStore((s) => s.settings) - return linearStatusContextKey === getProviderRuntimeContextKey(settings) && linearStatus.connected + // Why: Cmd+J keeps this hook mounted globally; project to the visibility bit so + // unrelated Linear metadata and settings updates do not rerender the palette. + return useAppStore( + (state) => + state.linearStatusContextKey === getProviderRuntimeContextKey(state.settings) && + state.linearStatus.connected + ) } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index ea864c13e70..6b5a097d673 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -619,7 +619,17 @@ "postUpdate": "Post a progress update", "moveState": "Move the ticket forward", "attachPr": "Attach the PR link", - "triageFollowups": "Triage and create follow-ups" + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." } } } @@ -9217,7 +9227,9 @@ "skillTitle": "Linear skill", "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", "howToUse": "How to use it", - "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR." + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index c198d05d8fe..e533ee41e64 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -596,7 +596,17 @@ "postUpdate": "Post a progress update", "moveState": "Move the ticket forward", "attachPr": "Attach the PR link", - "triageFollowups": "Triage and create follow-ups" + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." } } } @@ -9194,7 +9204,9 @@ "skillTitle": "Linear skill", "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", "howToUse": "How to use it", - "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR." + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index ae240e364fe..d322e01b8e4 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -596,7 +596,17 @@ "postUpdate": "Post a progress update", "moveState": "Move the ticket forward", "attachPr": "Attach the PR link", - "triageFollowups": "Triage and create follow-ups" + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." } } } @@ -9194,7 +9204,9 @@ "skillTitle": "Linear skill", "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", "howToUse": "How to use it", - "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR." + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 8d2bb9ac40b..2f6cda47452 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -596,7 +596,17 @@ "postUpdate": "Post a progress update", "moveState": "Move the ticket forward", "attachPr": "Attach the PR link", - "triageFollowups": "Triage and create follow-ups" + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." } } } @@ -9194,7 +9204,9 @@ "skillTitle": "Linear skill", "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", "howToUse": "How to use it", - "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR." + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index e14e205c838..1de024b5958 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -596,7 +596,17 @@ "postUpdate": "Post a progress update", "moveState": "Move the ticket forward", "attachPr": "Attach the PR link", - "triageFollowups": "Triage and create follow-ups" + "triageFollowups": "Triage and create follow-ups", + "readTicketSummary": "Pull the linked Linear issue's full context before starting work.", + "readTicketPrompt": "Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.", + "postUpdateSummary": "Comment progress or a completion summary back to the Linear issue.", + "postUpdatePrompt": "Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.", + "moveStateSummary": "Advance the Linear workflow state as the work progresses.", + "moveStatePrompt": "Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.", + "attachPrSummary": "Link the pull request to the Linear issue when you open it.", + "attachPrPrompt": "Use {{value0}} to attach this PR to the linked Linear issue with a PR link.", + "triageFollowupsSummary": "Set assignee, priority, or estimate, and file parented follow-up tickets.", + "triageFollowupsPrompt": "Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup." } } } @@ -9194,7 +9204,9 @@ "skillTitle": "Linear skill", "skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.", "howToUse": "How to use it", - "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR." + "howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.", + "terminalTitle": "Linear skill setup", + "terminalAriaLabel": "Linear skill install terminal" } }, "right": { diff --git a/src/renderer/src/lib/linear-usage-examples.ts b/src/renderer/src/lib/linear-usage-examples.ts index f5f77e33936..af6b0a5f1e8 100644 --- a/src/renderer/src/lib/linear-usage-examples.ts +++ b/src/renderer/src/lib/linear-usage-examples.ts @@ -1,34 +1,62 @@ import { translate } from '@/i18n/i18n' import { createLocalizedCatalog } from '@/i18n/localized-catalog' +import { ORCA_LINEAR_SKILL_NAME } from './agent-feature-install-commands' import type { SkillUsageExample } from './skill-usage-example' +const LINEAR_SLASH_COMMAND = `/${ORCA_LINEAR_SKILL_NAME}` + export const getLinearUsageExamples = createLocalizedCatalog((): SkillUsageExample[] => [ { id: 'read-ticket', title: translate('auto.lib.linear.usage.examples.readTicket', 'Read the linked ticket'), - summary: "Pull the linked Linear issue's full context before starting work.", - prompt: - 'Use /orca-linear to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.' + summary: translate( + 'auto.lib.linear.usage.examples.readTicketSummary', + "Pull the linked Linear issue's full context before starting work." + ), + prompt: translate( + 'auto.lib.linear.usage.examples.readTicketPrompt', + 'Use {{value0}} to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.', + { value0: LINEAR_SLASH_COMMAND } + ) }, { id: 'post-update', title: translate('auto.lib.linear.usage.examples.postUpdate', 'Post a progress update'), - summary: 'Comment progress or a completion summary back to the Linear issue.', - prompt: - 'Use /orca-linear to post a completion update on the linked Linear issue with what changed and how it was verified.' + summary: translate( + 'auto.lib.linear.usage.examples.postUpdateSummary', + 'Comment progress or a completion summary back to the Linear issue.' + ), + prompt: translate( + 'auto.lib.linear.usage.examples.postUpdatePrompt', + 'Use {{value0}} to post a completion update on the linked Linear issue with what changed and how it was verified.', + { value0: LINEAR_SLASH_COMMAND } + ) }, { id: 'move-state', title: translate('auto.lib.linear.usage.examples.moveState', 'Move the ticket forward'), - summary: 'Advance the Linear workflow state as the work progresses.', - prompt: - 'Use /orca-linear to move the linked Linear issue to In Review now that the change is ready.' + summary: translate( + 'auto.lib.linear.usage.examples.moveStateSummary', + 'Advance the Linear workflow state as the work progresses.' + ), + prompt: translate( + 'auto.lib.linear.usage.examples.moveStatePrompt', + 'Use {{value0}} to move the linked Linear issue to In Review now that the change is ready.', + { value0: LINEAR_SLASH_COMMAND } + ) }, { id: 'attach-pr', title: translate('auto.lib.linear.usage.examples.attachPr', 'Attach the PR link'), - summary: 'Link the pull request to the Linear issue when you open it.', - prompt: 'Use /orca-linear to attach this PR to the linked Linear issue with a PR link.' + summary: translate( + 'auto.lib.linear.usage.examples.attachPrSummary', + 'Link the pull request to the Linear issue when you open it.' + ), + prompt: translate( + 'auto.lib.linear.usage.examples.attachPrPrompt', + 'Use {{value0}} to attach this PR to the linked Linear issue with a PR link.', + { value0: LINEAR_SLASH_COMMAND } + ) }, { id: 'triage-followups', @@ -36,8 +64,14 @@ export const getLinearUsageExamples = createLocalizedCatalog((): SkillUsageExamp 'auto.lib.linear.usage.examples.triageFollowups', 'Triage and create follow-ups' ), - summary: 'Set assignee, priority, or estimate, and file parented follow-up tickets.', - prompt: - 'Use /orca-linear to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup.' + summary: translate( + 'auto.lib.linear.usage.examples.triageFollowupsSummary', + 'Set assignee, priority, or estimate, and file parented follow-up tickets.' + ), + prompt: translate( + 'auto.lib.linear.usage.examples.triageFollowupsPrompt', + 'Use {{value0}} to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup.', + { value0: LINEAR_SLASH_COMMAND } + ) } ])