Pular para o conteúdo

App Topbar — Placement & Layout Rules

app-layout specs/app-layout/topbar-placement.kmd

Posição canônica do avatar (KoderUserBadge) e demais elementos primários da topbar em todos os apps Koder, per surface (web, desktop tradicional, desktop browser/IDE, mobile, TV, CLI/TUI). Spec extensível pra cobrir search bar, primary nav, contextual actions, breadcrumbs em §N futuro — v0.1 normativa apenas pra avatar.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — App Topbar Placement

§1 — Applicability

All Koder apps that surface a user identity (KoderUserBadge ou KoderSignInButton) in chrome:

  • ✅ Web apps (Hub, Drive, Talk, Cal, Flow Web, …)
  • ✅ Desktop apps tradicionais (Flutter Linux/macOS/Windows — Hub Desktop, Drive Desktop, Talk Desktop, …)
  • ✅ Mobile (Flutter Android/iOS — Hub Mobile, Drive Mobile, Talk Mobile, …)
  • ✅ TV (Tizen/WebOS — Kortex TV, …)
  • ⚠️ Desktop browser/IDE (Kruze, dev/grid, dev/dok, dev/kterm) — exception, ver §3 R2
  • ❌ CLI / TUI — N/A (sem topbar visual; identidade na status line bottom-right via [user@scope] opcional)
  • ❌ Landing pages — N/A (estáticas; auth lives elsewhere)

§2 — Decision matrix per surface

SurfaceDefault placementRationale
Web (qualquer produto)Topbar direita, último item (após contextual actions)Padrão universal (Gmail, GitHub, Slack, Linear, Notion). Reading LTR — branding esquerda, content middle, account direita. Espaço pra dropdown menu (account, settings, sign-out) abrir alinhado pra esquerda
Desktop tradicional (Hub, Drive, Talk, Cal, Flow Web)Topbar direita, último itemIdem web; mesma metáfora visual cross-surface dá memória muscular
Mobile (default)Topbar direitaMaterial 3 default; Apple HIG matches
Mobile (drawer-based, ex: ChatGPT/Telegram)Drawer header (top do drawer aberto via ham menu)Quando primary nav é drawer, account vai no header do drawer. Topbar direita fica pra contextual actions (busca, +, menu)
TV (Kortex, futuro)Sidebar esquerda primária, topo (above primary nav items)TV é vertical-first; topbar fina não é foco da atenção; account precisa estar visível sem navegação adicional
Browser / IDE (Kruze, future grid/dok)Sidebar vertical esquerda, topoConvenção firme do nicho (Arc, Vivaldi, Brave, VSCode account, JetBrains account) — topbar de browser/IDE é dos painéis/abas, não do app shell

§3 — Required rules

R1 — Avatar sempre presente em chrome no estado logado

Apps cobertos por §1 DEVEM mostrar KoderUserBadge (logado) ou KoderSignInButton (deslogado) no chrome principal a partir do primeiro frame após cold-start. Não é aceitável esconder o badge atrás de menu (Mais → Conta) — discoverability falha.

R2 — Posição segue §2 sem variação per-produto

Cada produto DEVE seguir o default do seu surface conforme §2. Variação per-produto é exception que requer:

  • Tag explícito no koder.toml do produto: [ui.topbar] avatar_placement_override = "sidebar-left" # reason: <inline justification>
  • Inclusão em registro meta/docs/stack/registries/topbar-overrides.md (a criar quando 1ª exception aparecer)

Exception ratificadas:

  • Kruze — sidebar vertical esquerda. Reason: browser app shell mirrors Arc/Vivaldi/Brave convention; topbar belongs to the active tab, not the app.

R3 — Touch target ≥ 40×40 px

Conforme specs/themes/verge.kmd token touch-target-min, o avatar consome área tappable ≥ 40×40 px mesmo se o ícone visual é menor (24×24 ou 32×32). Padding/Gestures absorvem o resto.

R4 — Dropdown / popup abre alinhado

Ao tap no avatar:

  • Topbar direita: dropdown menu abre alinhado à direita (border-radius corner alinhado com avatar; menu cresce pra esquerda)
  • Sidebar esquerda: popup abre lateralmente à direita do avatar (não obstrui sidebar; respeita safe area top)
  • TV: focus highlight visualmente óbvio (Verge focus token); A button abre full-screen account modal

R5 — Estado consistente com KoderUserBadge canonical

Os 3 estados visuais e behaviour seguem KoderUserBadge do koder_kit:

EstadoVisualBehavior on tap
Signed outIcons.account_circle_outlined (silhouette outlined) — futura KoderIcons.avatar_defaultOpen KoderSignInButton flow (per specs/auth/oauth-flow.kmd)
Signed in, com avatar URLNetworkImage(avatarUrl) em CircleAvatarOpen user menu (Account, Settings, Sign out, Switch account)
Signed in, sem avatar URLInitials (1-2 chars) em CircleAvatar com background color hashed do user IDIdem signed-in com avatar

§4 — Tests (T1-T5)

IDVerificação
T1Audit walk em todo app/lib/ de produto coberto por §1: existe exatamente 1 ocorrência de KoderUserBadge no widget tree do main screen chrome (topbar ou sidebar conforme §2)
T2Render snapshot do main screen em estado deslogado: avatar visível na posição declarada por §2 ± 8 px de margin
T3Render snapshot do main screen em estado logado-com-avatar: NetworkImage carregado, posição idem T2
T4Tap region: posição do avatar tem hit-test area ≥ 40×40 px (T2/T3 + golden de bounding box)
T5Dropdown alignment: tap no avatar → menu aparece com edge alinhado conforme R4

koder-spec-audit topbar-placement (CLI a shipar em dev/koder-tools) roda T1 estaticamente lendo widget trees + checking koder.toml overrides. T2-T5 ficam pra widget test/golden test per produto.

§5 — Extensibility (não-normativo v0.1)

Spec aberta pra cobrir, em §6+ futuras, outros elementos da topbar com mesma estrutura (decision matrix per surface + required rules + tests):

  • §6 — Branding/logo placement (esquerda, sempre)
  • §7 — Primary navigation (tab bar, hamburger drawer, sidebar — per surface)
  • §8 — Search bar / Cmd+K trigger (centro ou direita-ao-lado-do-avatar; keybinding spec)
  • §9 — Contextual actions (icons antes do avatar, max 3, overflow → kebab menu)
  • §10 — Breadcrumbs (abaixo da topbar OU dentro dela, depending on surface)

Cada §N futura segue mesmo padrão R1-R5 + T1-T5.

§6 — Migration & rollout

Para produtos já existentes em conformidade com R1 (têm KoderUserBadge):

  1. Audit estático identifica produtos com avatar fora do default per §2
  2. Cada não-conformidade ganha ticket no backlog do produto: <product>#XYZ topbar avatar placement
  3. Migração não-bloqueante pra release (cosmetic) — agendar em release window dedicada por produto

Pra produtos novos: §1+§2+§3 são gatilho obrigatório no momento de implementar topbar (entry em CLAUDE.md).

§7 — Open questions (para owner ratification — status draft)

  1. Mobile drawer threshold — qual produto/feature decide drawer-based vs topbar-based mobile layout? Ou é decisão per-produto?

  2. TV avatar size — mesma touch target 40×40 ou TV-tuned (10ft viewing distance) min 64×64?

  3. Multi-tenant / workspace switcher — quando o avatar precisa expor switcher (ex: user com múltiplos workspaces no Hub), abre menu híbrido OU avatar tem ícone secundário (chevron) indicando "click to switch"?

  4. Keyboard accessibility — qual tecla aciona avatar/menu? Padrão Ctrl/Cmd+Shift+A? Diferente per surface?

  5. Notification badge — quando há notificações user-scope (ex: invite recebido), avatar mostra dot/count overlay? Spec separada (specs/notifications/) ou inline aqui?

  6. profile-as-tab pattern — Hub (products/dev/hub/app) hoje (2026-05-22) implementa avatar como tab "Profile" dedicado no NavigationRail/NavigationBar, sem KoderUserBadge no chrome. UX pattern comum em apps com 4-5 destinos de nav primária (YouTube, Maps, Twitter/X, Instagram). Decisão é sobre placement (estrutura de layout), independente da linguagem visual — Verge (canonical KDS) ou Material (atual implementação do Hub via Flutter widgets) renderizam o mesmo placement com tokens diferentes. Decisão owner:

    • (a) Variant ratificada — adicionar 3ª linha em §2 ("apps com nav primária por tabs OU NavigationRail/NavigationBar com 4+ destinos podem usar profile-as-tab"), Hub passa automatically conforme
    • (b) Override per-produto registrável — Hub declara [ui.topbar].avatar_placement_override = "profile-as-tab" em koder.toml + entry em registries/topbar-overrides.md
    • (c) Refit pra default — Hub adiciona KoderUserBadge em AppBar.actions (mantendo tab Profile como atalho duplicado, OU removendo tab e usando só badge)

    Tradeoff: (a) é mais aberto, valida o pattern, mas dilui spec; (b) preserva spec restrita e força ratificação per-produto; (c) é mais consistente mas força mudança em production de Hub. Default sugerido: (b) — alinha com Kruze (browser/IDE exception) como precedent.

§8 — Histórico

  • 2026-05-22 — draft v0.1 criada. Motivada por revisão visual do Kruze sidebar vs Hub login modal (KRUZE-170 hotfix); gap identificado: nenhuma spec governava cross-product avatar placement antes. Status draft aguardando owner answers às 5 questões em §7 + 1 cycle de implementação prática (audit + 1 produto migrado) antes de promover pra ratified.
  • 2026-05-22 (later) — Audit walk identificou 6ª open question: Hub usa profile-as-tab UX pattern (perfil como tab de nav primária — pattern comum em apps com 4-5 destinos) não previsto em §2. Registry topbar-overrides.md adicionou Hub como "Pending owner ratification — 3rd pattern observed". Owner decide se ratifica como variant em §2, override per-produto, OU refit. Nota: decisão é sobre placement (estrutura), não sobre linguagem visual — Verge canonical aplica os tokens; Material em Hub é detalhe transitório de implementação.

§9 — Spec/policy alignment

  • specs/koder-app/behaviors.kmd §1 — diz "app DEVE ter user badge"; este spec diz onde colocar
  • specs/auth/oauth-flow.kmd — fluxo que dispara no tap do avatar deslogado
  • engines/sdk/koder_kit::KoderUserBadge — widget canonical referenciado em R5
  • policies/reuse-first.kmd — proíbe re-implementar widget de avatar fora do SDK
  • specs/themes/verge.kmdtouch-target-min referenciado em R3
  • stack-RFC-003 — Koder Icons (draft) — quando ratificada, Icons.account_circle_outlined em R5 vira KoderIcons.avatar_default

Referências