Title-Bar Drag and Double-Click Gestures

desktop-apps specs/desktop-apps/title-bar-double-click.kmd

Como apps desktop Koder devem tratar arraste e duplo-clique na barra de título. Define o contrato cross-platform (Linux/Windows/macOS), proíbe o "gesture-arena hijack" do duplo-clique nos botões, e pinta os widgets canônicos do `engines/sdk/koder_kit` (`KoderTitleBar` + `KoderTitleBarFreeArea`).

Quando esta spec se aplica

Todos os triggers

Corpo da especificação

Spec — Title-Bar Drag and Double-Click Gestures

Applicability

Toda variante desktop (Linux / Windows / macOS) de qualquer produto Koder que renderiza sua própria barra de título — kruze, kterm, hub desktop, kortex, mosaic, talk, drive, eye, koru desktop, sky, e adjacentes. Não se aplica a variantes mobile, TV, web ou CLI.

Required Behavior

A barra de título de uma janela Koder DEVE responder a:

  1. Arraste (pan) em qualquer ponto da barra que não seja capturado por um widget interativo (botão, dropdown, controle de janela, célula de tab) — move a janela.
  2. Duplo-clique em uma área livre explicitamente declarada (espaço vazio decorativo, texto de título, gap entre tab strip e controles) — alterna entre maximizado e restaurado.
  3. Duplo-clique em um widget interativo (botão fechar X da aba, botão + nova aba, dropdown , controle de janela minimize/maximize/close, qualquer botão custom) NÃO dispara o toggle de maximize. O widget interativo trata o duplo-clique conforme sua semântica própria (geralmente: dispara onTap, ou um onDoubleTap distinto declarado pelo autor).

Implementation

A implementação canônica está em engines/sdk/koder_kit:

  • KoderTitleBar({child, height, color}) — wrapper externo da barra de título inteira. Declara apenas o gesto de arraste (onPanStart: (_) => windowManager.startDragging()). Não declara onDoubleTap — o gesto de toggle vive em KoderTitleBarFreeArea.

  • KoderTitleBarFreeArea({child}) — wrapper que marca uma região como área livre. Declara onDoubleTap e dispara windowManager.maximize() / unmaximize() conforme estado atual. Use behavior: HitTestBehavior.opaque. Posicione em Expanded(SizedBox.expand()) para o gap entre tab strip e controles, ou em volta de texto de título decorativo.

Layout canônico (kruze, kterm, hub):

KoderTitleBar(
  child: Row(
    children: [
      // Tab strip + botões internos (sized to content).
      Flexible(child: ReorderableListView.builder(...)),
      // Área livre — duplo-clique aqui alterna maximize/restore.
      Expanded(
        child: KoderTitleBarFreeArea(child: SizedBox.expand()),
      ),
      // Lado direito — dropdowns + window controls.
      DropdownAllTabs(),
      WindowControl(min),
      WindowControl(max),
      WindowControl(close),
    ],
  ),
)

Forbidden Patterns

Não envolver a barra inteira em um GestureDetector com onDoubleTap + onPanStart:

// ANTI-PATTERN — Flutter gesture arena entrega o duplo-clique pro
// detector externo mesmo quando o usuário clica num botão filho.
// Usuário double-clica em "X" → janela maximiza em vez de fechar tab.
GestureDetector(
  onDoubleTap: _toggleMaximize,
  onPanStart: (_) => windowManager.startDragging(),
  child: TitleBarRow(...),
)

Não declarar onDoubleTap no KoderTitleBar e em widgets filhos sem coordenar — gesture arena hijack reaparece.

Não usar Caddy DragToMoveArea ou outras alternativas de window_manager que envolvem toda a área — perdem o split free-area / interactive.

Validation

Esta spec é enforcement-checked via testes estruturais:

  • engines/sdk/koder_kit/tests/regression/<NNN>-title-bar-widgets.test.sh — verifica que KoderTitleBar e KoderTitleBarFreeArea existem, que KoderTitleBar NÃO declara onDoubleTap, e que KoderTitleBarFreeArea chama windowManager.maximize/unmaximize.
  • Per-product structural tests no tests/regression/ de cada módulo desktop, verificando que o pattern legacy (GestureDetector(onDoubleTap:...) em volta da barra) não voltou.

Casos históricos consertados (registry regression-test-cases.md):

  • kterm v1.3.2 — close button da aba e botão close do Preferences dialog (cases #438, #439).
  • kruze 1.0.14 — botão X close de aba e botão + nova aba (caso #459 e seguintes; este commit).
  • specs/desktop-apps/title-bar.kmd — formato do nome do produto na barra de título (texto, não gesto).
  • specs/koder-app/behaviors.kmd — comportamentos cross-cutting de apps Koder (auth, telemetria, update etc.).
  • policies/reuse-first.kmd — sempre usar koder_kit em vez de reimplementar gestos por app.

Referências