App Topbar — Placement & Layout Rules
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.
Spec — App Topbar Placement
§1 — Applicability
All Koder *pps*that surface a user identity (KoderUserBadge ou KoderSignInButton) in chrome:
- ✅ Web apps (Hub, Drive, Talk, Cal, Flow Web, …)
- ✅ Desktop apps tradicionais (Flutter LinuxmacOSWindows — Hub Desktop, Drive Desktop, Talk Desktop, …)
- ✅ Mobile (Flutter Android/iOS — Hub Mobile, Drive Mobile, Talk Mobile, …)
- ✅ TV (Tizen/WebOS — Kortex TV, …)
- ⚠️ Desktop browserIDE (Kruze, devgrid, devdok, devkterm) — *xception* 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
| Surface | Default placement | Rationale |
|---|---|---|
| *eb*(qualquer produto) | Topbar *ireita* ú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 |
| *esktop tradicional*(Hub, Drive, Talk, Cal, Flow Web) | Topbar *ireita* último item | Idem web; mesma metáfora visual cross-surface dá memória muscular |
| *obile*(default) | Topbar *ireita* | Material 3 default; Apple HIG matches |
| *obile*(drawer-based, ex: ChatGPT/Telegram) | *rawer 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) |
| *V*(Kortex, futuro) | *idebar 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 |
| *rowser / IDE*(Kruze, future grid/dok) | *idebar vertical esquerda* topo | Convenção firme do nicho (Arc, Vivaldi, Brave, VSCode account, JetBrains account) — topbar de browserIDE é dos painéisabas, não do app shell |
§3 — Required rules
R1 — Avatar sempre presente em chrome no estado logado
Apps cobertos por §1 *EVEM*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 *EVE*seguir o default do seu surface conforme §2. Variação *er-produto*é exception que requer:
- Tag explícito no
koder.tomldo 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:
- *ruze*— sidebar vertical esquerda. Reason: browser app shell mirrors ArcVivaldiBrave 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:
- *opbar direita* dropdown menu abre *linhado à direita*(border-radius corner alinhado com avatar; menu cresce pra esquerda)
- *idebar esquerda* popup abre *ateralmente à direita do avatar*(não obstrui sidebar; respeita safe area top)
- *V* 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:
| Estado | Visual | Behavior on tap |
|---|---|---|
| Signed out | Icons.account_circle_outlined (silhouette outlined) — futura KoderIcons.avatar_default |
Open KoderSignInButton flow (per specs/auth/oauth-flow.kmd) |
| Signed in, com avatar URL | NetworkImage(avatarUrl) em CircleAvatar |
Open user menu (Account, Settings, Sign out, Switch account) |
| Signed in, sem avatar URL | Initials (1-2 chars) em CircleAvatar com background color hashed do user ID | Idem signed-in com avatar |
§4 — Tests (T1-T5)
| ID | Verificação |
|---|---|
| T1 | Audit 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) |
| T2 | Render snapshot do main screen em estado deslogado: avatar visível na posição declarada por §2 ± 8 px de margin |
| T3 | Render snapshot do main screen em estado logado |
| T4 | Tap region: posição do avatar tem hit-test area ≥ 40×40 px (T2/T3 + golden de bounding box) |
| T5 | Dropdown alignment: tap no avatar → menu aparece com edge alinhado conforme R4 |
koder-spec-audit topbar-placement (CLI a shipar em devkodertools) roda T1 estaticamente lendo widget trees + checking koder.toml overrides. T2T5 ficam pra widget testgolden 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
aoladodoavatar; 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 R1R5 + T1T5.
§6 — Migration & rollout
Para produtos já existentes em conformidade com R1 (têm KoderUserBadge):
- Audit estático identifica produtos com avatar fora do default per §2
- Cada não-conformidade ganha ticket no backlog do produto:
<product>#XYZ topbar avatar placement - 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)
- *obile drawer threshold*— qual produto/feature decide drawer
based vs topbarbased mobile layout? Ou é decisão per-produto? - *V avatar size*— mesma touch target 40×40 ou TV-tuned (10ft viewing distance) min 64×64?
- *ulti-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"?
- *eyboard accessibility*— qual tecla aciona avatarmenu? Padrão `CtrlCmd+Shift+A`? Diferente per surface?
- *otification badge*— quando há notificações user-scope (ex: invite recebido), avatar mostra dotcount overlay? Spec separada (`specsnotifications/`) ou inline aqui?
- *profile
astabpattern** — Hub (productsdevhubapp`) hoje (20260522) implementa avatar como *ab "Profile" dedicado no NavigationRailNavigationBar* semKoderUserBadgeno chrome. UX pattern comum em apps com 4-5 destinos de nav primária (YouTube, Maps, Twitter/X, Instagram). Decisão é sobre *lacement*(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].avatarplacementoverride = "profileastab"overrides.md`emkoder.toml+ entry emregistries/topbar - *c)*Refit pra default — Hub adiciona
KoderUserBadgeemAppBar.actions(mantendo tab Profile como atalho duplicado, OU removendo tab e usando só badge)
- *a)*Variant ratificada — adicionar 3ª linha em §2 ("apps com nav primária por tabs OU NavigationRail/NavigationBar com 4+ destinos podem usar
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
- *026
0522*— draft v0.1 criada. Motivada por revisão visual do Kruze sidebar vs Hub login modal (KRUZE170 hotfix); gap identificado: nenhuma spec governava crossproduct avatar placement antes. Statusdraftaguardando owner answers às 5 questões em §7 + 1 cycle de implementação prática (audit + 1 produto migrado) antes de promover praratified. - *026
0522 (later)*— Audit walk identificou 6ª open question: Hub usaprofile-as-tabUX pattern (perfil como tab de nav primária — pattern comum em apps com 45 destinos) não previsto em §2. Registry `topbaroverrides.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 *lacement*(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 *nde*colocarspecs/auth/oauth-flow.kmd— fluxo que dispara no tap do avatar deslogadoengines/sdk/koder_kit::KoderUserBadge— widget canonical referenciado em R5policies/reuse-first.kmd— proíbe re-implementar widget de avatar fora do SDKspecs/themes/verge.kmd—touch-target-minreferenciado em R3stack-RFC-003 — Koder Icons(draft) — quando ratificada,Icons.account_circle_outlinedem R5 viraKoderIcons.avatar_default