-
Notifications
You must be signed in to change notification settings - Fork 1
Notificações flutuantes (toasts) e overlay com visual de toast #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
8c51b21
docs: spec design para notificações flutuantes (toasts)
ChamPlz c9da107
docs: plano de implementação para notificações flutuantes
ChamPlz 3a925bc
feat: showToast compartilhado e savePinChange com callback
ChamPlz c6885b7
docs: corrigir empilhamento de toasts (novo no topo via column-reverse)
ChamPlz b38eb2c
feat: notificações flutuantes no desktop
ChamPlz d439396
feat: notificações flutuantes na interface web
ChamPlz f1225b3
docs: spec design para redesign do overlay com visual de toast
ChamPlz 0a750ed
docs: plano de implementação para redesign do overlay
ChamPlz 6d283f3
feat: overlay de contagem regressiva com visual de toast
ChamPlz c9d3392
feat: remover pulse e posicionar botão de fechar no canto da barra no…
ChamPlz fef5246
docs: sincronizar spec/plano do overlay com o visual final; css: usar…
ChamPlz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
694 changes: 694 additions & 0 deletions
694
docs/superpowers/plans/2026-08-15-floating-notifications.md
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| # Redesign do Overlay (Visual de Toast) — Implementation Plan | ||
|
|
||
| > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. | ||
|
|
||
| **Goal:** Redesenhar a janela overlay de contagem regressiva para o visual do `#timerToast` (barra escura translúcida, borda roxa, cantos 12px, pulse), mantendo o botão "cancelar" e o "×". | ||
|
|
||
| **Architecture:** Apenas restyle em `overlay/`. O HTML perde o wrapper `.pill-inner` (barra única), o CSS é reescrito replicando o visual do toast do renderer com valores literais (a janela overlay é separada e não acessa as variáveis CSS do renderer). IPC, JS e lógica de janela ficam intactos. | ||
|
|
||
| **Tech Stack:** Electron (BrowserWindow overlay), HTML/CSS/JS vanilla, Jest (testes existentes). | ||
|
|
||
| ## Global Constraints | ||
|
|
||
| - Alvo: a janela overlay separada (`overlay/`), NÃO o `#timerToast` do app. | ||
| - "×" continua apenas escondendo a janela (desligamento continua); "cancelar" cancela o shutdown. | ||
| - Layout: barra com tempo + "cancelar" lado a lado, "×" no canto superior direito **da barra**. | ||
| - Barra (`.pill`): `position: relative`, `background: rgba(15, 23, 42, 0.92)`, `border: 1px solid rgba(167, 139, 250, 0.4)`, `border-radius: 12px`, sombra roxa, padding `12px 16px`. Sem animação. | ||
| - Tempo (`#time`): cor `#a78bfa`, bold, `font-variant-numeric: tabular-nums`, sem gradiente branco. | ||
| - Cancelar (`.cancel`): pílula gradiente roxo (`#6b46c1` → `#7c3aed`), texto branco. | ||
| - "×" (`.close`): círculo `position: absolute` no canto superior direito da barra (`top: -9px; right: -9px`), fundo `rgba(15, 23, 42, 0.8)` + borda roxa, `×` branco, hover vermelho. | ||
| - `body` = `-webkit-app-region: drag`; botões = `no-drag`. | ||
| - Janela mantém `300x120` (sem mudanças em `overlay/overlayWindow.js`). | ||
| - `npm test` deve passar (o `client-require.test.js` NÃO cobre `overlay/`). | ||
| - Sem novos testes automatizados (CSS/DOM puro); validação manual. | ||
|
|
||
| --- | ||
|
|
||
| ### Task 1: Restyle do overlay com visual de toast | ||
|
|
||
| **Files:** | ||
| - Modify: `overlay/overlay.html:14-24` (remover `.pill-inner`) | ||
| - Modify: `overlay/overlay.css` (reescrever estilos) | ||
|
|
||
| **Interfaces:** | ||
| - Consumes: ids `#time`, `#btnCancel`, `#btnClose` (já wired em `overlay/overlayWindow.js` via `did-finish-load` e atualizados por `overlay/overlay.js` via `getElementById("time")`). | ||
| - Produces: nada novo para outras tasks. | ||
|
|
||
| - [ ] **Step 1: Reescrever o corpo de `overlay/overlay.html`** | ||
|
|
||
| Substituir o bloco dentro de `<body>` por (removendo o wrapper `.pill-inner` e | ||
| aninhando o `#btnClose` dentro da barra `.pill`): | ||
|
|
||
| ```html | ||
| <body> | ||
| <div class="overlay"> | ||
| <div class="pill"> | ||
| <button id="btnClose" class="close" title="Fechar">×</button> | ||
| <div id="time">--:--</div> | ||
| <button id="btnCancel" class="cancel">cancelar</button> | ||
| </div> | ||
| </div> | ||
|
|
||
| <script src="overlay.js"></script> | ||
| </body> | ||
| ``` | ||
|
|
||
| O restante do arquivo (head, CSP, link CSS) permanece inalterado. | ||
|
|
||
| - [ ] **Step 2: Reescrever `overlay/overlay.css`** | ||
|
|
||
| Substituir o conteúdo inteiro do arquivo por: | ||
|
|
||
| ```css | ||
| /* ========================================================================== | ||
| ShutDW - Overlay Styles | ||
| Contagem regressiva de desligamento (visual de toast) | ||
| ========================================================================== */ | ||
|
|
||
| /* ========================================================================== | ||
| BASE | ||
| ========================================================================== */ | ||
| * { | ||
| box-sizing: border-box; | ||
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||
| } | ||
|
|
||
| body { | ||
| margin: 0; | ||
| width: 300px; | ||
| height: 120px; | ||
| background: transparent; | ||
| -webkit-app-region: drag; | ||
| } | ||
|
|
||
| /* ========================================================================== | ||
| OVERLAY CONTAINER | ||
| ========================================================================== */ | ||
| .overlay { | ||
| position: relative; | ||
| width: 300px; | ||
| height: 120px; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| } | ||
|
|
||
| /* ========================================================================== | ||
| TOAST BAR | ||
| ========================================================================== */ | ||
| .pill { | ||
| position: relative; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 14px; | ||
| padding: 12px 16px; | ||
| background: rgba(15, 23, 42, 0.92); | ||
| border: 1px solid rgba(167, 139, 250, 0.4); | ||
| border-radius: 12px; | ||
| box-shadow: 0 8px 24px rgba(124, 58, 237, 0.3); | ||
| } | ||
|
|
||
| /* ========================================================================== | ||
| TIME DISPLAY | ||
| ========================================================================== */ | ||
| #time { | ||
| color: #a78bfa; | ||
| font-size: 22px; | ||
| font-weight: 700; | ||
| letter-spacing: 0.5px; | ||
| font-variant-numeric: tabular-nums; | ||
| pointer-events: none; | ||
| } | ||
|
|
||
| /* ========================================================================== | ||
| CANCEL BUTTON | ||
| ========================================================================== */ | ||
| .cancel { | ||
| background: linear-gradient(135deg, #6b46c1, #7c3aed); | ||
| border: none; | ||
| color: #fff; | ||
| font-size: 13px; | ||
| font-weight: 600; | ||
| padding: 8px 16px; | ||
| border-radius: 999px; | ||
| cursor: pointer; | ||
| -webkit-app-region: no-drag; | ||
| box-shadow: 0 4px 12px rgba(124, 58, 237, 0.4); | ||
| transition: transform 0.15s ease, box-shadow 0.2s; | ||
| } | ||
|
|
||
| .cancel:hover { | ||
| transform: scale(1.05); | ||
| box-shadow: 0 8px 20px rgba(124, 58, 237, 0.5); | ||
| } | ||
|
|
||
| .cancel:active { | ||
| transform: scale(0.97); | ||
| } | ||
|
|
||
| /* ========================================================================== | ||
| CLOSE BUTTON | ||
| ========================================================================== */ | ||
| .close { | ||
| position: absolute; | ||
| top: -9px; | ||
| right: -9px; | ||
| width: 26px; | ||
| height: 26px; | ||
| background: rgba(15, 23, 42, 0.8); | ||
| border: 1px solid rgba(167, 139, 250, 0.4); | ||
| border-radius: 50%; | ||
| color: #fff; | ||
| font-size: 14px; | ||
| line-height: 1; | ||
| cursor: pointer; | ||
| -webkit-app-region: no-drag; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| z-index: 2; | ||
| transition: transform 0.15s ease, background 0.2s; | ||
| } | ||
|
|
||
| .close:hover { | ||
| transform: scale(1.1); | ||
| background: rgba(239, 68, 68, 0.6); | ||
| } | ||
| ``` | ||
|
|
||
| - [ ] **Step 3: Rodar testes** | ||
|
|
||
| Run: `npm test` | ||
| Expected: todas as suítes passam (9/9, 85/85). Nenhum teste cobre `overlay/`, mas a suíte deve permanecer verde. | ||
|
|
||
| - [ ] **Step 4: Validação manual** | ||
|
|
||
| Run: `npm run dev` | ||
| Expected: | ||
| - Agendar um desligamento (ex.: 10 min): a janela overlay abre com a nova barra toast escura com borda roxa, `#time` em roxo mostrando `10:00`, sem animação. | ||
| - Clicar "cancelar": o overlay fecha E o desligamento é cancelado (o `#timerToast` do app também some). | ||
| - Agendar novamente e clicar no "×" (no canto da barra): o overlay fecha, mas o desligamento continua (reabrir/verificar via `/status` ou o `#timerToast` do app segue contando). | ||
|
|
||
| - [ ] **Step 5: Commit** | ||
|
|
||
| ```bash | ||
| git add overlay/overlay.html overlay/overlay.css | ||
| git commit -m "feat: overlay de contagem regressiva com visual de toast" | ||
| ``` | ||
120 changes: 120 additions & 0 deletions
120
docs/superpowers/specs/2026-08-15-floating-notifications-design.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| # Design — Notificações Flutuantes (Toasts) | ||
|
|
||
| **Data:** 2026-08-15 | ||
| **Status:** Aprovado | ||
| **Escopo:** Desktop (Electron/renderer) + Interface Web/mobile | ||
|
|
||
| ## Problema | ||
|
|
||
| As mensagens de status (ex.: "Desligamento em 10 minutos", "PIN inválido") e o | ||
| timer de contagem regressiva aparecem em um card no final da aba de controle | ||
| (`#statuscard` no desktop, `.status-container` na web). Para vê-los, o usuário | ||
| precisa rolar a tela. | ||
|
|
||
| ## Objetivo | ||
|
|
||
| Exibir mensagens de status e o timer de contagem regressiva como elementos | ||
| flutuantes fixos no canto inferior direito da janela, sempre visíveis, sem | ||
| necessidade de scroll. | ||
|
|
||
| ## Decisões de Escopo | ||
|
|
||
| - Válido para **ambas** as interfaces (desktop + web), usando o código | ||
| compartilhado em `shared/api.js`. | ||
| - **Timer** e **mensagens** são elementos flutuantes **separados**. | ||
| - Mensagens têm **auto-dismiss + botão X** (sucesso ~4s, erro ~6s). | ||
| - Várias mensagens **empilham** (mais nova no topo, antigas deslizam para baixo). | ||
|
|
||
| ## Abordagem escolhida | ||
|
|
||
| Abordagem 1 — Toast compartilhado em `shared/api.js`, com CSS por UI e timer | ||
| flutuante persistente. Segue o padrão já existente de código compartilhado. | ||
|
|
||
| ## Componentes | ||
|
|
||
| ### 1. Sistema de toast em `shared/api.js` | ||
|
|
||
| Nova função `showToast(message, isError = false)`: | ||
|
|
||
| - Cria dinamicamente (na primeira chamada) um container fixo no canto inferior | ||
| direito: | ||
| - `position: fixed; right: 12px; bottom: 76px;` | ||
| - `z-index: 1000` (acima dos cards e da drag-bar `z-index: 10/11`, abaixo do | ||
| modal `z-index: 9999`). | ||
| - Cada chamada adiciona um toast na pilha: o **mais novo fica no topo**, os | ||
| antigos deslizam para baixo. | ||
| - Cada toast contém: | ||
| - Texto da mensagem. | ||
| - Botão de fechar (X) que remove o toast imediatamente. | ||
| - Auto-dismiss: sucesso ~4000ms, erro ~6000ms. | ||
| - Limite de ~5 toasts visíveis; o mais antigo é removido ao exceder o limite. | ||
| - Guard para `typeof document === "undefined"` (o arquivo é importado em testes | ||
| Node via `module.exports`). | ||
|
|
||
| `showStatus` e `showConfigStatus` permanecem exportadas para compatibilidade, | ||
| mas deixam de ser usadas pelas UIs. | ||
|
|
||
| ### 2. Timer flutuante | ||
|
|
||
| - Novo elemento `#timerToast` (div oculta por padrão) em ambos os HTMLs, fixado | ||
| no canto inferior direito (`right: 12px; bottom: 12px`), abaixo da pilha de | ||
| toasts. | ||
| - Elemento **persistente**: fica na tela enquanto houver desligamento ativo, | ||
| atualizando a cada segundo via polling. | ||
| - `startStatusPolling(API_URL, el.timerToast, null)` — reutiliza a função | ||
| existente em `shared/api.js`. Passar `null` como container é seguro, pois | ||
| `updateTimer` já usa optional chaining (`containerEl?.`). O `#timerToast` é | ||
| mostrado/ocultado conforme `remaining` muda e o texto | ||
| `⏱️ Xm Ys restantes` continua vindo de `updateTimer`. | ||
|
|
||
| ### 3. HTML e CSS (desktop + web) | ||
|
|
||
| **HTML:** | ||
| - Remover blocos antigos de status: | ||
| - Desktop: `#statuscard` (card com `#timer` e `#status`). | ||
| - Web: `.status-container` (com `#timer` e `#status`). | ||
| - Ambos: `#configStatus`. | ||
| - Adicionar `#timerToast` em ambos. | ||
| - `el.timer`, `el.status`, `el.statusCard`, `el.configStatus` saem de | ||
| `cacheElements`; entra `el.timerToast`. | ||
|
|
||
| **CSS (cada `style.css`, temas próprios):** | ||
| - Novo estilo: | ||
| - `.shutdw-toast-stack` — container fixo. | ||
| - `.shutdw-toast` — toast base, com variantes `.success` e `.error`. | ||
| - `.shutdw-toast-close` — botão X. | ||
| - Animações de entrada/saída (slide + fade). | ||
| - Novo estilo de `#timerToast` (persistente, mesmo visual de contagem atual). | ||
| - Remover estilos órfãos: `.status-timer`, `.status-message`, `.card-status`, | ||
| `.status-container`, `.status-container.hidden`. | ||
|
|
||
| ### 4. renderer.js / app.js | ||
|
|
||
| - `displayStatus` → chama `showToast`. | ||
| - `handleSavePin` e `resetPinDesktop` → chamam `showToast`. | ||
|
|
||
| `savePinChange` muda a assinatura de `statusEl` para callback | ||
| `onResult(message, isError)` (mesmo padrão de `sendAction`/`scheduleExactTime`). | ||
|
|
||
| ## Fluxo de dados | ||
|
|
||
| 1. Usuário dispara ação (shutdown, cancel, schedule, salvar PIN, redefinir PIN). | ||
| 2. `sendAction`/`scheduleExactTime`/`savePinChange` resolvem/rejeitam e chamam o | ||
| callback `onResult(message, isError)`. | ||
| 3. `displayStatus` (renderer/web) chama `showToast`, que adiciona o toast na | ||
| pilha fixa do canto inferior direito. | ||
| 4. O polling de status atualiza `#timerToast` enquanto houver desligamento ativo. | ||
|
|
||
| ## Tratamento de erros | ||
|
|
||
| - `showToast` é puro DOM, sem async; guard para ambiente sem `document`. | ||
| - Auto-dismiss usa `setTimeout` por toast; limpeza ocorre na remoção (X ou | ||
| timeout). | ||
|
|
||
| ## Testes | ||
|
|
||
| - Sem novos testes automatizados (lógica é DOM pura; o `client-require.test.js` | ||
| continua valendo e garante que `shared/api.js`, `renderer/renderer.js` e | ||
| `web/app.js` não usam `require()`). | ||
| - Validação manual: disparar ações e conferir empilhamento, auto-dismiss e | ||
| timer flutuante em ambas as interfaces. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remova
pulsedo objetivo do plano.Line 16 and the manual validation step require no animation. Line 5 still mentions
pulse. Update the goal so implementers do not reintroduce the removed animation.🤖 Prompt for AI Agents