Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .specs/features/keyboard-orb/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,12 @@ Respondidas em 29/10 (D5–D10). Nenhuma em aberto.

| Requirement | Story | Tasks | Status |
| ----------- | ----- | ----- | ------ |
| KBO-01 | P1-A | KBO-T6 | Pending |
| KBO-02 | P1-A | KBO-T6 | Pending |
| KBO-03 | P1-A | KBO-T2, KBO-T6 | Parcial (padrão no codec/DataStore em KBO-T2; falta KBO-T6) |
| KBO-04 | P1-A | KBO-T3, KBO-T6 | Parcial (derivação/restauração pura em KBO-T3; falta KBO-T6) |
| KBO-01 | P1-A | KBO-T6 | Implementado (T6; golden `config-teclado*` na T9) |
| KBO-02 | P1-A | KBO-T6 | Implementado (T6; prévia com o mesmo pintor `drawOrb`, tempo congelado) |
| KBO-03 | P1-A | KBO-T2, KBO-T6 | Implementado (T6) |
| KBO-04 | P1-A | KBO-T3, KBO-T6 | Implementado (T6) |
| KBO-05 | P1-B | KBO-T5 | Implementado (T5) |
| KBO-06 | P1-B | KBO-T5, KBO-T6 | Parcial (encaixe e mic extra preservado/não desenhado em KBO-T5; falta o editor em KBO-T6) |
| KBO-06 | P1-B | KBO-T5, KBO-T6 | Implementado (T5, T6: o catálogo diz "Orbe" e não oferece uma segunda) |
| KBO-07 | P1-B | KBO-T1, KBO-T5 | Implementado (T5) |
| KBO-08 | P1-B | KBO-T1, KBO-T5 | Implementado (T5) |
| KBO-09 | P1-B | KBO-T4, KBO-T5, KBO-T10 | Parcial (aplicado no IME e provado no app de trás em KBO-T5; falta KBO-T10) |
Expand All @@ -245,15 +245,15 @@ Respondidas em 29/10 (D5–D10). Nenhuma em aberto.
| KBO-12 | P1-C | KBO-T1 | Verified |
| KBO-13 | P1-C | KBO-T3, KBO-T10 | Parcial (rajada e classificador JVM em KBO-T3; falta KBO-T10) |
| KBO-14 | P1-C | KBO-T3, KBO-T10 | Parcial (resolvedor JVM em KBO-T3; falta KBO-T10) |
| KBO-15 | P1-C | KBO-T3, KBO-T6 | Parcial (aviso de substituição e zero gravar/enviar em KBO-T3; falta KBO-T6) |
| KBO-15 | P1-C | KBO-T3, KBO-T6 | Implementado (T3, T6) |
| KBO-16 | P1-C | KBO-T3 | Verified |
| KBO-17 | P1-C | KBO-T2, KBO-T3 | Verified |
| KBO-18 | P1-C | KBO-T3, KBO-T5, KBO-T10 | Parcial (KBO-T5 liga o resolvedor; falta KBO-T10) |
| KBO-19 | P1-D | KBO-T3, KBO-T6 | Parcial (config efetiva JVM em KBO-T3; falta KBO-T6) |
| KBO-19 | P1-D | KBO-T3, KBO-T6 | Implementado (T3, T6: somente leitura quando copia) |
| KBO-20 | P1-D | KBO-T2, KBO-T3 | Verified |
| KBO-21 | P1-D | KBO-T5, KBO-T7 | Parcial (estado e tempo no IME em KBO-T5; skins em KBO-T7) |
| KBO-22 | P1-E | KBO-T7 | Pending |
| KBO-23 | P1-E | KBO-T7 | Pending |
| KBO-21 | P1-D | KBO-T5, KBO-T7 | Implementado (T5, T7: o fundo do bitmap é a cor lisa do teclado, claro ou escuro) |
| KBO-22 | P1-E | KBO-T7 | Implementado (T7) |
| KBO-23 | P1-E | KBO-T7 | Implementado (T7) |
| KBO-24 | P1-E | KBO-T5 | Implementado (T5) |
| KBO-25 | P1-F | KBO-T3, KBO-T10 | Parcial (prioridade da Visão no resolvedor JVM em KBO-T3; falta KBO-T10) |
| KBO-26 | P1-F | KBO-T5 | Implementado (T5) |
Expand All @@ -263,10 +263,10 @@ Respondidas em 29/10 (D5–D10). Nenhuma em aberto.
| KBO-30 | P1-H | KBO-T3 | Verified |
| KBO-31 | P1-H | KBO-T3 | Verified |
| KBO-32 | P2-I | KBO-T1 | Verified |
| KBO-33 | P2-I | KBO-T6, KBO-T8 | Pending |
| KBO-33 | P2-I | KBO-T6, KBO-T8 | Implementado (T6, T8; `TranslationsTest` verde) |
| KBO-34 | P2-I | KBO-T5 | Implementado (T5) |
| KBO-35 | P2-I | KBO-T9 | Pending |
| KBO-36 | P2-I | KBO-T10 | Pending |
| KBO-37 | P2-I | KBO-T8 | Pending |
| KBO-37 | P2-I | KBO-T8 | Implementado (T8; `DocsSiteExportTest` verde) |

A FLX-59 e a parte de teclado da FLX-60 **não** estão nesta tabela: continuam na FLX-T29 (texto que substituiu o bloqueado em `flows/tasks.md`), que depende de KBO-T3 e KBO-T5 e não é dependência deles.
8 changes: 4 additions & 4 deletions .specs/features/keyboard-orb/tasks.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Orbe do teclado — Tasks

**Spec**: `spec.md` · **Design**: `design.md`
**Status**: spec e design em rascunho (2026-10-29), **KBO-T1..T3 feitas (2026-10-30)**, o resto pendente. **Fora da v1** (só `main`, release seguinte). Não toma a vez de frente aberta.
**Status**: spec e design em rascunho (2026-10-29), **KBO-T1..T8 feitas (2026-10-30)**, T9..T11 pendentes (goldens, gestos no emulador, fechamento). **Fora da v1** (só `main`, release seguinte). Não toma a vez de frente aberta.

> **Para o agente executor**: leia `spec.md` + `design.md` + este arquivo e `.specs/codebase/TESTING.md`. Uma task por vez, na ordem do plano,
> testes primeiro, gate verde antes de avançar. Cada task é verificável sozinha. Os textos de UI que ela criar vão com ela (`tr("…")` +
Expand Down Expand Up @@ -87,21 +87,21 @@ A Fase A inteira é JVM: pode ser entregue e revisada antes de qualquer pixel.
- **Tests**: unit do que for extrator + instrumentado curto. **Gate**: `build` e `pool instrumentar com.keyflow.ime.OrbeDoTecladoLayoutTest`. **Commit**: `feat(orbe-teclado): desenha a orbe no IME`.
- **Evidência (2026-10-30)**: `OrbeDoTecladoLayoutTest` 9/9 (encaixe, círculo acima da borda sem tocar teclas, viewport do app de trás igual antes/depois da faixa em resize e pan, retrato e paisagem, com e sem overscan); `assembleDebug`, `testDebugUnitTest` e `lintDebug` verdes. Lição: `../../project/licoes/2026-10-30-orbe-teclado-b.md`.

### KBO-T6 — Seção na aba Teclado — **Status: pendente**
### KBO-T6 — Seção na aba Teclado — **Status: concluída**
- **What**: seção Orbe no `TecladoPanel` (KBO-01..04, aviso ao substituir uma ação já atribuída na hora de salvar — KBO-15, D9 —, KBO-19 somente leitura quando copia). Rótulo do encaixe no editor de barras vira "Orbe"; não dá para adicionar um segundo. Textos por `tr` + arquivos en/es. A seção só aparece nesta task (antes, a aba fica idêntica).
- **Where**: `ui/config/TecladoPanel.kt`, `ui/config/BarsEditor.kt`, `i18n/en/OrbeTeclado.kt`, `i18n/es/OrbeTeclado.kt`.
- **Depends on**: T2. **Requirement**: KBO-01, KBO-02, KBO-03, KBO-04, KBO-06, KBO-15, KBO-19, KBO-33.
- **Done when**: instrumentado — copiar ligado reflete `orbSkin` e não escreve a chave ao abrir a aba; desligar restaura a config própria plantada; salvar com zero gravar/enviar grava, no Alternar e no segurar-para-falar; substituir um toque que já tinha ação mostra o aviso do que saiu antes de gravar; `TranslationsTest` verde; a rota da aba Teclado ainda não muda pixel até a T9 aprovar.
- **Tests**: instrumentado. **Gate**: `pool instrumentar com.keyflow.ui.config.TecladoPanelTest` (a classe já existe; estender, não duplicar) + `quick`. **Commit**: `feat(orbe-teclado): seção Orbe na aba Teclado`.

### KBO-T7 — Skins da orbe no Canvas — **Status: pendente**
### KBO-T7 — Skins da orbe no Canvas — **Status: concluída**
- **What**: bitmap em cache via `orbStyleFor(id, skinsUnlocked)`, queda para Ateliê sem direito ou sem quadro de orbe, `dark` do teclado. No teclado real a pele animada corre (`KeyboardMotion` / `motionEnabled`, D10). Prévia e golden passam `motionTimeMs` e ficam parados. Não editar `FORMAT.md`.
- **Where**: `ime/OrbeDoTecladoPele.kt` (ou ao lado da view da T5); teste de escolha da pele em JVM se a decisão for pura, e um instrumentado curto da queda para Ateliê.
- **Depends on**: T5. **Requirement**: KBO-21, KBO-22, KBO-23.
- **Done when**: unit ou instrumentado — sem `skinsUnlocked`, id `aero` desenha Ateliê e o JSON continua `aero`; pacote sem orbe desenha Ateliê; `dark = true` e `dark = false` não produzem o mesmo bitmap no Ateliê; com `motionTimeMs` preenchido o bitmap não muda entre dois runs; sem esse gancho o teclado real pode avançar o quadro; nenhum arquivo de `orbe/` ganhou import Android para "facilitar" o Canvas.
- **Tests**: unit + instrumentado se o bitmap precisar de `Canvas`. **Gate**: `quick` e, se houver classe Android, `pool instrumentar com.keyflow.ime.OrbeDoTecladoPeleTest`. **Commit**: `feat(orbe-teclado): skins da orbe no teclado`.

### KBO-T8 — Docs e tour — **Status: pendente**
### KBO-T8 — Docs e tour — **Status: concluída**
- **What**: a seção da Docs que ensina o mic do teclado e o tour `tour-keyboard-mic` passam a descrever a orbe (toques, segurar, arrastar, sobressair). Regenerar `website/_fonte/doc.json` uma vez (`KEYFLOW_REGERAR_DOC_SITE=1`).
- **Where**: a seção existente em Docs (não criar uma aba), o roteiro do tour, `i18n`.
- **Depends on**: T5. **Requirement**: KBO-33, KBO-37.
Expand Down
42 changes: 42 additions & 0 deletions .specs/project/licoes/2026-10-30-orbe-teclado-c.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 2026-10-30 — Orbe do teclado: seção na aba, skins no Canvas, Docs e tour (equipe/orbe-teclado-c)

KBO-T6, KBO-T7 e KBO-T8. A KBO-T9 (goldens novos `keyboard-orb-*`) e a T10/T11 seguem pendentes.

## KBO-T6 — seção Orbe na aba Teclado

- `OrbeTecladoSecao` (em `ui/config`) fica abaixo do editor de barras. `TecladoPanel` ganhou `orbeConfig` e `onOrbeConfigChange`;
a `SetupActivity` lê `configOrbeTecladoFlow()` e grava por `atualizarOrbeTeclado { updated }`, fora do auto-save das `Settings`
(KBO-27). Abrir a aba com "copiar" ligado não escreve a chave (teste `orbeCopiadaRefleteOrbSkinENaoEscreveAoAbrirAAba`).
- Prévia: o mesmo pintor (`drawOrb` via `VoiceOrb`, `timeProvider` fixo em 1234 ms) nos seis estados; a skin vem de
`efetiva(config, settings)` e o pacote, de `rememberOrbPack`.
- Aviso (D9): `substituirToque` devolve o aviso do que saiu e a tela o mostra numa `InfoNote`; nada recusa salvar sem gravar/enviar,
nos dois modos (`salvarSemGravarEnviarGravaNo*`). Mudar skin, modo ou ligar/desligar copiar não substitui ação e não avisa.
- `Dropdown` (ConfigFields) ganhou `testTag("$tag-$option")` nos itens: sem isso o teste não distingue o item do menu do valor já
mostrado na linha (duas vezes "Nenhuma").
- Lição de teste: uma `Row` só com `testTag` não junta o texto dos filhos; para `assertTextContains` precisa
`semantics(mergeDescendants = true)` (a `InfoNote` já faz).
- Editor de barras: o catálogo diz "Orbe" e some quando a barra ativa já tem uma (KBO-06). Os textos antigos do mic saíram dos
arquivos de tradução (`TranslationsTest` cobra tradução sobrando).
- Vermelho: 6 dos 18 testes de `TecladoPanelTest` (os novos) falharam contra a tela sem a seção; os 12 antigos passaram.

## KBO-T7 — skins no Canvas

- `EscolhaDaPeleDaOrbe` (decisão pura, JVM): `orbStyleFor(id, skinsUnlocked)` e Ateliê para pacote sem quadros de orbe; o id guardado
não é mexido. `OrbeDoTecladoPele` é o cache de um bitmap (chave: pele, pacote e sua versão, estado, lado, fundo, miolo, instante),
refeito só quando a chave muda; o texto do centro continua por cima, com a tinta da pele (`orbCenterInk`, extraída de `VoiceOrb`).
- `dark` entra pelo fundo do bitmap: a cor lisa do teclado do tema (`colors.tray`) que o halo e o vidro atravessam; teclado de vidro
(`tray` transparente) não tem fundo. Por isso o Ateliê claro e o escuro dão bitmaps diferentes.
- Movimento (D10): `motionTimeMs` congela (prévia, galeria, golden); sem ele e com `motionEnabled`, a pele corre a ~15 quadros/s só
na região da orbe (`invalidate(rect)`), parando com o teclado escondido, com "reduzir animações" ou com economia de bateria
(`MotionPolicy`). Sem nada disso o instante é 1234 ms, o mesmo da orbe estática de antes: o Ateliê padrão não muda de pixel.
- Pacote: abre fora da main (`Thread`) e fecha ao sair; até chegar, ou enquanto o quadro do estado não veio, o Ateliê aparece e o
teclado pede outro quadro (`aguardandoPacote`). Prévia/golden usam o render determinístico; o teclado real, o assíncrono.
- `drawPackOrb` virou `internal` para o IME reaproveitar o desenho do pacote.

## KBO-T8 — Docs e tour

- Seção `teclado` da Docs: o bloco "Mic" virou "Orbe" (toques, sobressair, segurar/arrastar, segurar para falar); `aba-teclado`
ganhou o bloco Orbe; `erros` e `pausa` não falam mais do mic antigo. O tour `kbd-mic-hold` agora segura e arrasta para cima
(pausa), sem o seletor de chips que o app não tem mais (`TourOverlayHint.MIC_ACTION_SELECTOR` segue existindo só na cena).
- `website/_fonte/doc.json` (só pt e en): ajustado à mão com o texto novo, e o `DocsSiteExportTest` confirma byte a byte. O pool manda a
tarefa para a AWS, que não leva `KEYFLOW_REGERAR_DOC_SITE=1`.
66 changes: 66 additions & 0 deletions app/src/androidTest/java/com/keyflow/ime/OrbeDoTecladoPeleTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.keyflow.ime

import android.graphics.Bitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.keyflow.ui.orb.OrbState
import com.keyflow.ui.orb.OrbStyle
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

/** KBO-21..23: o bitmap da pele da orbe do teclado (claro/escuro, tempo congelado, queda para o Ateliê). */
@RunWith(AndroidJUnit4::class)
class OrbeDoTecladoPeleTest {
private val densidade = InstrumentationRegistry.getInstrumentation().targetContext.resources.displayMetrics.density
private val lado = 96
private val trayEscuro = 0xFF332E27.toInt()
private val trayClaro = 0xFFDCD0AC.toInt()

private fun quadro(
estilo: OrbStyle,
fundo: Int = trayEscuro,
tempoMs: Float = 2000f,
estado: OrbState = OrbState.Idle,
pele: OrbeDoTecladoPele = OrbeDoTecladoPele(densidade),
): Bitmap = pele.quadro(estilo, null, estado, lado, fundo, miolo = false, tempoMs = tempoMs).copy(Bitmap.Config.ARGB_8888, false)

@Test
fun ateliePorTemaClaroEEscuroNaoDaOMesmoBitmap() {
assertFalse(quadro(OrbStyle.ATELIE, trayEscuro).sameAs(quadro(OrbStyle.ATELIE, trayClaro)))
}

@Test
fun comTempoCongeladoOBitmapNaoMudaEntreDoisRuns() {
for (estilo in listOf(OrbStyle.ATELIE, OrbStyle.AERO, OrbStyle.GLITCH, OrbStyle.ROSE_GOLD, OrbStyle.MASCOT)) {
for (estado in OrbState.entries) {
assertTrue("$estilo/$estado", quadro(estilo, estado = estado).sameAs(quadro(estilo, estado = estado)))
}
}
}

@Test
fun semOGanchoOTempoAvancaAPele() {
assertFalse(quadro(OrbStyle.ATELIE, tempoMs = 1000f).sameAs(quadro(OrbStyle.ATELIE, tempoMs = 2500f)))
}

@Test
fun oMesmoQuadroNaoRefazOBitmap() {
val pele = OrbeDoTecladoPele(densidade)
quadro(OrbStyle.GLITCH, pele = pele)
quadro(OrbStyle.GLITCH, pele = pele)
assertEquals(1, pele.desenhosParaTeste)
quadro(OrbStyle.GLITCH, tempoMs = 2100f, pele = pele)
assertEquals(2, pele.desenhosParaTeste)
}

@Test
fun semDireitoOuSemQuadrosDeOrbeADecisaoDesenhaOAtelie() {
val atelie = quadro(OrbStyle.ATELIE)
assertTrue(quadro(EscolhaDaPeleDaOrbe.estilo("aero", skinsUnlocked = false, temQuadrosDeOrbe = false)).sameAs(atelie))
assertTrue(quadro(EscolhaDaPeleDaOrbe.estilo("pack:so-teclado", skinsUnlocked = true, temQuadrosDeOrbe = false)).sameAs(atelie))
assertFalse(quadro(EscolhaDaPeleDaOrbe.estilo("aero", skinsUnlocked = true, temQuadrosDeOrbe = false)).sameAs(atelie))
}
}
Loading
Loading