feat: presença por sessão, eventos SSE em tempo real e autosave
- presence.go: hub SSE + registro de presença (TTL 30s por heartbeat)
- GET /api/events (EventSource, auth cookie/Bearer) transmite saved/presence;
POST /api/presence publica {session, notes[]}; create/update/restore/delete
publicam savedEvent com a session de origem (eco ignorado pelo autor)
- PUT/POST/restore aceitam campo opcional session
- UI: id de sessão em sessionStorage; selo 👥 na aba, na lista de notas e no
topo do editor quando outra sessão tem a mesma nota aberta; aba LIMPA
recarrega sozinha ao receber saved; aba SUJA abre o diálogo de conflito
automaticamente (conflictPending evita diálogos empilhados)
- Autosave: debounce de 1,5s em qualquer alteração (rascunho vira nota),
mensagem de revisão 'autosave', status 'salvando…', toggle nas Preferências
- Testes: SSE hello/presence/saved + presença 400/401 via httptest
This commit is contained in:
1 parent
3b91c1a509
commit
a55591d208
9 files changed
+543
-15
No files matched your search
@@ -82,6 +82,20 @@ ocupa a tela inteira; menus e toolbar rolam horizontalmente com botões só de
|
||||
(autor, data, mensagem, hash SHA-256 e parent). O painel de histórico mostra o **diff unificado**
|
||||
de cada revisão e permite **restaurar** qualquer versão anterior (a restauração vira uma nova
|
||||
revisão — nada se perde). Conteúdo idêntico é deduplicado (content-addressed).
|
||||
- **Colaboração em tempo real entre dispositivos/sessões** (SSE, sem recarregar página):
|
||||
- **Presença**: cada janela/aba do navegador é uma *sessão* (id aleatório em `sessionStorage`)
|
||||
que envia heartbeat das notas abertas (`POST /api/presence`). Quando outra sessão está com a
|
||||
mesma nota aberta, aparece **👥** na aba, na lista de notas e um selo
|
||||
*"outra sessão aberta aqui"* no topo do editor.
|
||||
- **Propagação de salvamento**: criar/salvar/restaurar/excluir publica um evento
|
||||
`{type:"saved", note_id, hash, session}` no stream SSE (`GET /api/events`). Instância com a
|
||||
nota **limpa** recarrega o conteúdo sozinha (toast discreto); com a nota **suja**, abre
|
||||
automaticamente o diálogo *Conflito de edição* (recarregar | salvar por cima). A própria
|
||||
sessão que salvou ignora o evento; reconexão do `EventSource` é automática e o fallback
|
||||
continua sendo a checagem no foco da janela.
|
||||
- **Autosave**: ligado por padrão — após ~1,5 s sem digitar, a alteração é salva como revisão
|
||||
`autosave` (rascunho novo vira nota criada automaticamente; status *"salvando…"* na barra).
|
||||
Desligável em Preferências. Conflitos durante autosave pausam o ciclo até a resolução.
|
||||
- **Sincronia entre dispositivos** (celular + desktop no mesmo servidor):
|
||||
- Cada aba lembra o hash em que se baseia (`base_hash`). Ao salvar, se o servidor mudou desde
|
||||
então, a API responde **409** e a UI abre o diálogo **Conflito de edição** com as duas saídas:
|
||||
@@ -189,6 +203,14 @@ curl -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
|
||||
curl -X PUT -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
|
||||
-d '{"name":"infra","parent":"<novo-pai>"}' $B/api/categories/<id> # renomear/mover
|
||||
curl -X DELETE -H "Authorization: Bearer $T" $B/api/categories/<id> # remove subtree
|
||||
# tempo real (SSE): stream de eventos saved/presence (auth por cookie no navegador;
|
||||
# para scripts, leia o stream com o header Bearer)
|
||||
curl -N -H "Authorization: Bearer $T" "$B/api/events?session=meu-id"
|
||||
# heartbeat de presença: informa quais notas a sessão tem abertas
|
||||
curl -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
|
||||
-d '{"session":"meu-id","notes":["<id>"]}' $B/api/presence
|
||||
# PUT/POST/restore aceitam campo opcional "session" — o eco vai no evento saved
|
||||
# (clientes que ignoram o evento podem sobrescrever a própria edição sem querer)
|
||||
```
|
||||
|
||||
## Como funciona o versionamento
|
||||
@@ -217,6 +239,9 @@ curl -X DELETE -H "Authorization: Bearer $T" $B/api/categories/<id> #
|
||||
`exec` de depuração, por design.
|
||||
- Preferências do editor e abas abertas ficam em `localStorage` (dados não sensíveis, por
|
||||
dispositivo/navegador); o token nunca vai para lá.
|
||||
- Presença/SSE: o stream exige a mesma autenticação das rotas `/api/*` (cookie HttpOnly no
|
||||
navegador, Bearer em scripts). O registro de presença guarda apenas id de sessão aleatório +
|
||||
ids de notas abertas — nada de conteúdo trafega no canal de presença.
|
||||
|
||||
## Testes
|
||||
|
||||
|
||||
Reference in new issue
Block a user