Juntei Obsidian, Notion e TickTick num app só (open source) — e o que aprendi fazendo ele funcionar offline
Durante muito tempo eu usei três apps ao mesmo tempo para me organizar:
- Obsidian para notas
- Notion para projetos e tabelas
- TickTick para tarefas e lembretes
Três assinaturas, três lugares para procurar a mesma informação e nenhum conversava com o outro. Um dia resolvi juntar o que eu mais gostava de cada um num app só, rodando no meu próprio servidor. O nome saiu das iniciais: Obsidian + Notion + TickTick + Y = ONTY.
Hoje uso ele todos os dias e decidi abrir o código.

O que ele faz
- Tarefas estilo TickTick: listas com kanban, subtarefas, lembretes, repetição, matriz de Eisenhower, pomodoro e uma captura rápida que entende
amanhã 10h ^Trabalho #cliente !alta. - Notas estilo Obsidian: arquivos
.mdcomuns (abrem no Obsidian),[[links]], backlinks e grafo. - Tabelas estilo Notion: visões de tabela, quadro, calendário, galeria e gráficos, além de uma tabela de finanças com fluxo de caixa.
- Calendário dia/semana/mês/ano que também mostra o Google Agenda (via link iCal) e publica as tarefas como calendário no iPhone.
- Inbox: tudo o que eu penso cai ali primeiro — digitado, pelo atalho do computador ou por mensagem e áudio no WhatsApp. Depois decido se vira tarefa ou nota.
- Modo offline: sem internet continuo usando; as alterações sincronizam quando a conexão volta.

A stack (propositalmente simples)
- Node.js + Express no back-end
- Vite no front-end, com JavaScript puro e um helper
h()para montar o DOM (o editor de blocos usa React/BlockNote) - Sem banco de dados: notas são arquivos
.mde tarefas ficam em JSON. Backup é copiar uma pasta. - Electron para uma janelinha de captura no Windows
- OpenClaw como agente que recebe as mensagens do WhatsApp e chama a API
4 coisas que aprendi no caminho
1. Modo offline: rodar o código do servidor dentro do navegador
O jeito "normal" de fazer offline seria reescrever no front-end as regras de negócio: o que acontece ao completar uma tarefa repetida, em qual coluna do kanban ela entra quando ganha data, como interpretar amanhã 10h !alta… Isso duplica a lógica e cedo ou tarde as duas versões divergem.
Em vez disso, o navegador roda o mesmo arquivo do servidor (TaskStore). O Vite troca node:fs, node:path e node:crypto por versões mínimas em memória:
// vite.config.js
resolve: { alias: {
'node:fs': shim('fs'), // "arquivos" num Map em memória
'node:path': shim('path'),
'node:crypto': shim('crypto'), // randomUUID() do navegador
} }
Sem internet, o app carrega a última cópia das tarefas (guardada no IndexedDB), aplica a alteração com o código do servidor e coloca a requisição numa fila. Quando a conexão volta, a fila é enviada em ordem.
Um detalhe que evitou muita dor de cabeça: as tarefas criadas offline já levam o id gerado no dispositivo, e o servidor aceita esse id. Assim não preciso remapear ids depois, e se a mesma requisição chegar duas vezes ela não duplica.
2. null <= 0 é true em JavaScript
Tinha este código no timer de foco:
// null = tarefa sem tempo estimado
if (!st.warned && taskCountdown() <= 0) estimateReached(); // "null <= 0 é false"
O comentário estava errado. Em JS, null <= 0 é true (o null vira 0 na comparação), enquanto null < 0 e null == 0 são false. Resultado: iniciar um pomodoro sem tempo estimado mostrava "Acabou o tempo estimado (0 min)". Só descobri porque estava tirando prints para o README. A correção:
const restante = taskCountdown();
if (!st.warned && restante != null && restante <= 0) estimateReached();
3. Service worker + fallback de SPA = tela preta
O app de captura tem um service worker para abrir rápido. Depois de um deploy, ele abriu com a tela toda preta. A causa:
- O HTML em cache apontava para
/assets/index-ABC123.js, que não existia mais depois do deploy. - O servidor tinha o fallback de SPA (
app.get('*')devolve oindex.html)… então o pedido do JS voltava HTML com status 200. - O service worker guardou esse HTML como se fosse o JS.
A correção teve duas partes: /assets/* inexistente agora responde 404 (nunca o fallback), e o service worker só aceita um asset se o content-type bater com a extensão e só troca a cópia da página quando todos os arquivos novos já foram baixados.
4. Sem banco de dados dá (bem) para uso pessoal
Para um usuário, arquivos .md + JSON resolvem: são legíveis, versionáveis, compatíveis com o Obsidian e o backup é trivial. As gravações são atômicas (escreve num .tmp e faz rename) para não corromper nada se o processo cair no meio.
Experimente
É gratuito e open source (AGPL-3.0). Com Docker:
git clone https://github.com/koko00123/onty.git && cd onty
# edite CEREBRO_TOKEN e CEREBRO_PASSWORD no docker-compose.yml
docker compose up -d
Repositório: https://github.com/koko00123/onty
Dois avisos honestos: a interface ainda está em espanhol (tradução para português e inglês está no roadmap, ajuda é bem-vinda) e o app foi feito para uma pessoa por instalação.
Adoraria receber feedback, principalmente sobre a instalação e o modo offline. E se curtir, uma ⭐ no GitHub ajuda muito.
Consejos para publicarlo en TabNews
- Responde a todos los comentarios el primer día: TabNews premia la conversación y te dará TabCoins (la reputación del sitio).
- Si alguien pregunta algo técnico, como la cola offline o el service worker, contéstale con detalle. Es la mejor publicidad.
- No lo publiques a la vez en otros sitios brasileños: espera 1 o 2 días y mira en GitHub Insights → Traffic cuántas visitas llegan desde tabnews.com.br.