feat(desktop): choose a New task's Linear issue from its own chip, not the name field

Merged
DA
11d ago
+641-349

Summary

PR 1 of the New task redesign ("New task, prompt first"). Linear moves out of the name field and into a Linear chip of its own.

  • The name field is plain again. It is a TextField, its placeholder is always "Task name (optional)", and typing in it lists no issues and searches nothing. Return creates the task under the name as typed, with any modifier held.
  • Linear chip (tasks/linear-issue-chip.tsx, new). It stands after the repository chip in the card's foot, but only where the workspace the task starts in has Linear connected (linearIssuesIn(startsIn) !== undefined). Pressing it opens the shared SearchableMenu with its own search field:
    • With nothing typed it lists the issues read as the card opened, in the order they were read. Nothing in the issue summary says which issues are the developer's own, so it can't put them first.
    • Typing narrows the list at once (offeredIssues). Once typing rests, Linear itself is searched (the debounced search atom). A pasted key or link asks for that one issue (linearIssuesAskOf). useLinearIssues, useLinearSearch and the atom moved out of the dialog unchanged.
    • ↑/↓ move, Return chooses, and Escape closes the menu but not the card. Focus goes back to whatever opened the menu.
    • ⌘L opens it from the card. No app command holds ⌘L, so this PR adds L to SHORTCUT_KEY and exports shortcutMatches to read the chord.
    • Switching workspace closes the menu and reads that workspace's issues.
  • Choosing an issue works as it does today. The task starts at once, named by the issue's title, with linearIssueId, the repository, the workspace and the planning the card shows. (PR 2 changes this to attaching the issue above the prompt.)
  • SearchableMenu takes an optional onSearch. It lets the owner answer the search itself: the owner hears each query, and the menu shows the rows it hands back without filtering them. This is needed so issues that Linear's search finds outside the title still show. It also remembers a highlighted row by its id as well as its position, so rows that arrive late don't move the highlight onto another issue.
  • A card opened from one of the workspace's own issues offers no Linear chip, as its name searched no Linear before.
  • new-task-dialog.tsx's header comment now describes the new behavior.

Evidence

  • pnpm check:affected ran out of memory in this sandbox (8 GB) when its typechecks ran in parallel, so I ran each of its steps on its own. All passed:
    • scripts/repository-checks.sh: passed
    • pnpm lint (biome + oxlint): 0 errors
    • pnpm knip: clean
    • pnpm discarded-effect: clean
    • tsc for @luke/desktop and @luke/settings: clean
    • vitest run --project desktop --project settings --project ui: 302 files, 2643 tests passed
  • Not inspected visually: there is no Mac or packaged app in this sandbox, and scripts/evidence.sh runs only on macOS.

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged.
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added.
  • Gateway envelope goldens unchanged.
  • No new as type assertion outside the allowlisted files.
  • No Effect.runPromise/runSync/runFork outside the runtime edges.
  • No new raw async primitives.
  • No Effect, Stream, or Layer dropped.
  • Test count: new-task-dialog.test.ts goes from 24 to 26. Its Linear cases are rewritten: typing never lists or searches issues; the chip stands only with Linear connected; the chip lists, the arrows move, and Return starts; search narrows, then Linear is searched; a pasted link looks up one issue; Escape closes the menu and not the card; ⌘L opens it, but not while an input method is composing; a Linear result that sorts ahead of the highlighted issue leaves the highlight on the chosen issue; opening from a workspace issue shows no chip. new-task-workspace.test.ts goes from 6 to 7 tests: its Linear case now checks that a workspace switch closes the search and the chip lists the chosen workspace's issues, and a new case checks that Linear disconnecting and reconnecting doesn't reopen the search.
  • No hand-rolled file replaced.
  • No CLAUDE.md/AGENTS.md sentence names a changed part.
  • PRIVACY.md unchanged.
  • Package deps unchanged.
  • Barrel/door rule unaffected.

Notes

  • This branch merges origin/main twice to resolve conflicts with #3674 (SearchableMenu moved onto SearchField and ComboboxList) and #3694 (renderer runtime moved into @luke/panel).

  • Out of scope here: the prompt composer, Agent/Plan modes, and starting a coding agent (PR 2 stacks on this branch).

Made with Luke


Devin Review