Skip to content

Cockpit launcher: Ctrl+K jump to a Jira ticket, here or in the browser #123

Description

@kud

Problem

From anywhere in the cockpit there is no way to jump to an arbitrary Jira ticket. ↵ on a ticket row already mounts the read-only ticket view in-tree (ambre app/src/cockpit/extensions/ticket.tsx, since 2026-09-11) and o in that view opens the browser — but a ticket that is not a row today cannot be reached without leaving the app. More generally the cockpit has no command surface: every verb is a single key, and the extension seam can only add more single keys.

Use cases

  • I know the key (ACC-1234), I am in the cockpit, I want it open here without hunting for the row.
  • Same, but I want it in Jira in the browser.
  • Later, the same box is where any extension hangs a verb the host cannot know about.

Scope

In (slice 1):

  • Ctrl+K opens a centred one-step modal over the list (60 cols, list dimmed behind). Footer hint ⌃K launch beside / search.
  • Typing a ticket key shows the match on top as the anchor and the destinations as sibling rows: ↵ open here (the existing drills view) and j open in Jira.
  • commands?: (target: ExtensionTarget) => Command[] on InboxExtension, Command = { id, title, group?, run: () => void | Promise<void> }. The host derives "open here" itself from drills; the ambre ticket extension contributes only "open in Jira". gh-ink stays Jira-free.
  • The overlay is a CommandPalette primitive in @kud/ink-ui, lifted from ambre's app/src/palette.tsx (rendering + grouped-when-idle / flat-fuzzy-when-typing filter). palette.tsx keeps its TSV reading and stdout printing and becomes a thin consumer. Contract roughly items: { id, title, group?, hint?, keywords? }[], onSelect(id), onClose, placeholder?.
  • Not-found is one muted line (no ticket matches "ACC-99"); not-configured is one line plus the fix (Jira not configured · jira config).
  • iTerm2 mapping ⌘K → Ctrl+K documented for the machine, not done by the app (⌘K is iTerm2's default Clear Buffer, so the map must override it).

Out (deferred, deliberately):

  • Fuzzy search over ticket titles, recents, PR/issue jump, any non-Jira command, a command list with more than the seeded entries.
  • "Open in ambre cockpit" — a phantom target. ambre's cockpit is this shell with three extensions plugged in; from inside it the target is where you already are, and a bare gh cockpit has no ticket extension. If a new pane running the cockpit on that ticket is wanted, that is a pane spawn and a separate ask.
  • Icons, preview pane, multi-column, search history. It is a jump box.

Decisions

  • Trigger Ctrl+K (Iris, Maya). / is already "narrow the list in place" (inbox.tsx:3987) and the launcher is a different verb; ? stays the legend; : is vim muscle memory for something this is not. key.ctrl is currently short-circuited at inbox.tsx:3930, so the chord is free.
  • One step, not two (Iris). Pick-ticket-then-pick-where costs a second ↵ for the 90 % case and hides the "where" until you have committed. Idle shows the last few tickets in the same row shape; no verbs without a subject.
  • Primitive in ink-ui, now (Maya). Two consumers exist, which is the bar; leaving the filter in palette.tsx means the inbox hand-rolls a second one — the same drift useTabs was fixed for on 2026-09-15.
  • Prompt-sized scope (Theo). A palette with one entry is a prompt, and that is fine. Maya's geometry, Theo's cut.
  • Colour (Iris): key in colors.accent, matched title default, destination verbs in colors.info with the key letter bright and the rest muted; matched characters underlined, never a third colour.

Decisions during the build

  • No hotkeys on the rows (Iris). The query box owns the keyboard while the palette is up, so j types; the rows are flat commands under one cursor — Open KEY here / Open KEY in Jira — verb plain, key in accent, destination word in info, ❯ + bold on the selected row, placeholder ticket key…, no row count unless the list scrolls.
  • The primitive is dumb (Maya). items, query, onQueryChange, onSelect, onClose?, message? — no internal filter, no internal query state. fuzzyFilter ships as a pure helper for the fixed-tree shape.
  • The key is matched once, by the host (Maya). ExtensionTarget gained query and ticketKey; commands(target) is sync and pure, memoised on the query; the inbox composes both messages itself and takes a jiraSetupHint prop for the host's own words.
  • The inbox synthesises "open in Jira" too (Maya). jiraBase is host config and the row menu already opened it; one extracted openInJira serves both. The commands seam landed with no producer, pinned by a stub-extension test so the append path and ordering (host rows first) stay honest.
  • Ambre's palette lost its per-group hues. The primitive draws headers in colors.group; danger rows keep their ! marker in colors.error. Iris's call if the hues are wanted back (a groupColor on the primitive).

Sequencing

Open questions

  • Whether ↵ on the palette with an empty query should do anything (probably nothing).

Next steps

  • @kud/ink-ui 0.30.0: CommandPalette + fuzzyFilter, lifted from $AMBRE/app/src/palette.tsx — kud/ink-ui@46bf748
  • @kud/gh-ink 0.58.0 / @kud/gh-cockpit 0.6.2: commands seam, Ctrl+K overlay, openInJira extracted, footer + legend, launcher.test.tsx — kud/gh@df7c837, released by #125
  • ambre ticket.tsx: no change needed — the inbox already held the one URL rule (t in the row menu), so it synthesises both rows itself (Maya, during build); palette.tsx repointed to the primitive and all three pins bumped — kud/ambre@be8d7166
  • ~/.ambre docs: docs/postinstall/iterm2.md — ⌘K → Send Hex Code 0x0b, same commit

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

planScoped work ready to be picked up by a fresh session

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions