From 73f709217bb44d9ac2deb56e2dca1e27fcc9a11b Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:43:42 -0700 Subject: [PATCH] test(linear): pin project out of the hydration-preserved field set (#16364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linear list issues never carry `project` (only getIssue maps it, via includeProject: true). If `project` joins EDITED_LINEAR_ISSUE_FIELDS, an edit made while getIssue is in flight overwrites the hydrated project with the list issue's undefined, permanently blanking it — the sidebar shows 'Add to project' and LinearIssueSubIssues then files sub-issues with projectId: null instead of inheriting the parent's project. Verified discriminating: re-adding 'project' to the field set fails these. --- ...near-issue-workspace-detail-state.test.tsx | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/renderer/src/components/linear-issue-workspace-detail-state.test.tsx b/src/renderer/src/components/linear-issue-workspace-detail-state.test.tsx index 414f7f113de..c22b11d93b6 100644 --- a/src/renderer/src/components/linear-issue-workspace-detail-state.test.tsx +++ b/src/renderer/src/components/linear-issue-workspace-detail-state.test.tsx @@ -201,3 +201,36 @@ describe('Linear issue workspace detail state', () => { expect(runtimeMocks.linearIssueComments).toHaveBeenCalledWith(sourceContext, 'b', 'workspace-1') }) }) + +describe('mergeLinearIssueHydration project preservation', () => { + // Why: Linear *list* issues never carry `project` — only getIssue maps it, via + // includeProject: true. So `project` must never join the preserved-from-current + // set: an edit made while getIssue is in flight would overwrite the hydrated + // project with the list issue's undefined, permanently blanking it. + it('takes project from the hydrated detail even when the user has edited', () => { + const listIssue = { ...issue('ENG-1'), project: undefined } + const hydrated = { ...issue('ENG-1'), project: { id: 'p1', name: 'Compiler' } } + + const merged = mergeLinearIssueHydration(hydrated, listIssue, true) + + expect(merged.project).toEqual({ id: 'p1', name: 'Compiler' }) + }) + + it('still preserves the fields the user actually edits', () => { + const edited = { ...issue('ENG-1'), title: 'my edit', priority: 1 } + const hydrated = { ...issue('ENG-1'), title: 'server title', priority: 4 } + + const merged = mergeLinearIssueHydration(hydrated, edited, true) + + expect(merged.title).toBe('my edit') + expect(merged.priority).toBe(1) + }) + + it('keeps project out of the preserved-field set', () => { + // Guards the list itself: adding 'project' here is the exact regression. + const listIssue = { ...issue('ENG-1'), project: undefined } + const hydrated = { ...issue('ENG-1'), project: { id: 'p2', name: 'Runtime' } } + + expect(mergeLinearIssueHydration(hydrated, listIssue, true).project?.id).toBe('p2') + }) +})