Skip to content
edalcinPublic

About

Personal Knowledge Database

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

PKD

PKD — Personal Knowledge Database

Base de conhecimento pessoal auto-hospedada, focada em organização, conexões e recuperação rápida de informações.

Go 1.25 Svelte 5 SQLite Docker MIT XERJ Corpus Hub contributor


Funcionalidades

Conteúdo e edição

📁 Hierarquia ilimitada Documentos dentro de documentos, arrastar e soltar para reorganizar
✏️ Editor rico TipTap v3 — negrito, itálico, títulos, listas, código, citações, imagens inline
📐 Barra de ferramentas completa Tabelas, imagem por URL, alinhamento de parágrafo, destaque de texto com cor personalizável
🖼️ Redimensionamento de imagens Passe o mouse sobre uma imagem e arraste a alça (canto inferior direito) para redimensioná-la; largura persiste no documento
⬇️⬆️ Importar / Exportar Markdown Botão ⬇ .md converte o documento para Markdown e baixa o arquivo; ⬆ .md faz o caminho inverso — escolha um .md e o conteúdo é inserido na posição do cursor
🧜 Diagramas Mermaid colapsáveis Blocos de código com fences ```mermaid renderizam o diagrama e escondem o código-fonte por padrão; clique no diagrama ou no chip </> para alternar a exibição do código
🎯 Modo foco Botão de tela cheia abre o editor em uma janela separada, sem distrações; estado de conteúdo sincronizado ao fechar
🔗 Notas relacionadas Relacione documentos pelo painel "Notas relacionadas"; as relações são simétricas — ambos os documentos exibem a mesma lista de relacionados, sem distinção de direção
🔗 Links inline no editor Digite [ no editor para abrir autocomplete de documentos internos; selecione para inserir um chip de link clicável diretamente no texto
🔒 Títulos únicos Títulos funcionam como identificadores de documentos — duplicatas são bloqueadas com diálogo informativo e botão para navegar ao documento existente
📍 Breadcrumb de hierarquia Ancestrais clicáveis exibidos abaixo do título no editor — navegue para qualquer nível da hierarquia com um clique
📡 Captura externa Envie links de outros apps via PWA share target; Open Graph extraído automaticamente
🔍 Busca híbrida (RRF) Campo de busca da árvore lateral funde ranking léxico (FTS5 + LIKE) e semântico (embeddings Gemini) por Reciprocal Rank Fusion; sem GEMINI_API_KEY degrada automaticamente para busca léxica, sem erro
💬 Chat com os documentos Ícone 💬 na topbar (rota #/chat) abre uma conversa fundamentada apenas no conteúdo do seu PKD: recupera os documentos pela mesma busca híbrida, envia os corpos completos ao Gemini e responde em streaming. Lista os documentos consultados servida pelo backend (não pelo modelo, portanto não alucinável), e quando nada relevante é encontrado diz isso em vez de inventar. Botão para salvar a conversa como documento. Requer GEMINI_API_KEY; sem ela o ícone fica desabilitado com tooltip
📅 Calendário Navegue pelos documentos pela data de criação
🗓️ Calendário de Memórias Navegue pelas Memórias pela Data da Memória; segue o filtro Ativos/Arquivados/Todos
🕰️ Histórico de versões Snapshot automático a cada save com dedup SHA-256 (saves idênticos não geram versão). Visualize, compare e restaure qualquer versão anterior via botão ⏱ na barra do documento. Retenção configurável (padrão 50 versões/documento)
⭐ Favoritar da barra Botão ⭐ na barra de ações do documento — alterna favorito sem precisar ir à sidebar
🗓️ Memória Cronológica Registre eventos no tempo (almoço, entrega, consulta) como Memórias, em árvore própria Ano → Mês → Dia no menu lateral. ID público estável MEM-…, API para agentes (Hermes). Ver Memória Cronológica (MC)
📝 Nota Texto curto e rápido (endereço, contato, lista) no bloco "Notas" da barra lateral, fora da árvore normal, sem pai nem filhos. Conversível em Documento (arrastar para a árvore) ou Memória (arrastar para a MC). Ver Nota

Árvore lateral

🔎 Filtro unificado Campo de busca na sidebar filtra a árvore em tempo real por título; pesquisa e navegação integradas em um único controle
🎨 Ícones Boxicons Cada documento tem um ícone Boxicons selecionável; pasta ganha ícone de pasta automaticamente ao receber filhos; folha volta ao ícone padrão ao ficar sem filhos
🗂️ Estado persistido Nós expandidos e colapsados na árvore são lembrados entre sessões

Associações por documento

Cada documento possui uma área de associações no rodapé, com três colunas:

Coluna Funcionalidade
📄 Notas relacionadas Relacione documentos com busca autocomplete; relações simétricas — ambos os documentos exibem a mesma lista
📎 Arquivos Imagens mostram thumbnail; PDFs, áudios e outros tipos exibem ícone. Clicar abre modal de visualização (lightbox, embed PDF, player de áudio/vídeo)
🔗 Links externos URLs com título opcional; verificação de validade no painel de administração

Sub-documentos

Quando um documento possui filhos diretos na hierarquia, eles são exibidos como cards clicáveis logo abaixo do conteúdo principal, antes da área de associações. Cada card mostra o ícone, o título e um preview do texto (até 160 caracteres). Clicar em um card navega diretamente para o sub-documento.

Organização e visualização

🏷️ Hashtags com autocomplete Marque documentos com tags; ao digitar, sugeridas as tags já existentes no sistema
🎨 Cores por tag Cada tag possui cor configurável; chips coloridos em toda a interface (sidebar e editor)
🗂️ Arquivamento Arquive documentos pelo ícone na barra; arquivar pai cascateia para todos os filhos recursivamente. Documentos arquivados são auto-trancados; desarquivar pai libera todos os filhos
🕸️ Graph View Grafo D3.js force-directed: nós de documentos, tags e hierarquia pai/filho. Toggle Semântico exibe arestas de similaridade (Gemini, roxo tracejado) e agrupa docs em comunidades Louvain — nós coloridos por comunidade, layout em clusters, filtro por comunidade na legenda. Toggles independentes; filtro de texto integrado
🔗 Links públicos Links de compartilhamento revogáveis, somente leitura

Administração

📊 Dashboard Aba padrão da Administração: cards com totais de documentos, arquivos, links e tags ativos; cards de uso de disco (banco de dados, WAL, arquivos, total)
💾 Backup / Restore do banco Download do SQLite + restore com confirmação
📦 Backup / Restauração de anexos Backend local: ZIP síncrono. Backend S3: backup assíncrono que monta o ZIP no próprio bucket (sem usar disco da instância) e oferece URL pré-assinada (TTL 15 min). Restauração cross-backend: ZIP gerado em S3 pode ser restaurado em backend local e vice-versa. Manifesto interno usa SHA256 (dedup natural + verificação de integridade). Job tracking com progresso e contadores (gravados / mantidos / órfãos / hash inválido).
🗑️ Lixeira Documentos excluídos ficam em lixeira; restauráveis individualmente ou em lote
🏷️ Tags Renomear, editar cor, excluir ou mesclar tags globalmente; botão para remover tags órfãs
📎 Arquivos Grade com todos os anexos do sistema (thumbnail para imagens); botão "Verificar Órfãos" lista arquivos sem documento associado; seção "Imagens externas" lista documentos com imagens hospedadas fora do PKD e permite importá-las individualmente com um clique
🔗 Links externos Tabela de gerenciamento de todos os links externos; testa validade (HTTP HEAD) e permite excluir inválidos em lote
☁️ Armazenamento Migração de anexos entre armazenamento local e Amazon S3; teste de conexão, migração SHA256-verificada com progresso em tempo real; reconciliação S3↔DB e LOCAL↔DB (corrige storage_location sem mover arquivos); limpeza da origem com verificação de cópia no destino antes de apagar; backup/restauração assíncrona com streaming multipart e URL pré-assinada
🧹 Limpeza Executa VACUUM no banco de dados para recuperar espaço em disco
⚙️ Configurações Retenção de versões configurável (padrão 50/documento); painel de embeddings semânticos: status da chave Gemini, contagem de vetores, seletor de modelo Gemini (3 opções; persiste no DB, sobrepõe env var, dispara re-embed automático ao trocar)

Segurança

📧 2FA por e-mail (login vinculado ao dispositivo) Opcional — habilitado com SES_USERNAME, SES_PASSWORD, EMAIL_SENDER e EMAIL_2FA. Dispositivos não reconhecidos recebem um código de 6 dígitos por e-mail após a senha mestra; uma vez validado, o dispositivo é lembrado permanentemente e não pede código de novo
🛡️ Proteção de documentos Ícone de escudo no editor criptografa o corpo do documento em repouso (AES-256-GCM). Título, ícone e tags continuam visíveis na árvore; o conteúdo some da busca e dos embeddings semânticos enquanto protegido. Abrir exige um código por e-mail; a sessão do servidor mantém o documento decifrado até o logout ou reinício do servidor

Interface

🌙 Tema claro / escuro Alternância persistida no localStorage
📱 Mobile master-detail Em telas ≤ 640px: tela-lista (topbar 2 linhas, busca larga, árvore inteira) e tela-detalhe (botão ← para voltar). Sem drawer hambúrguer. Desktop (≥ 641px) inalterado
📲 PWA Instalável como app; share target no celular; offline somente leitura
↩️ Histórico de navegação Botões ← / → na barra superior (desktop) e atalhos Alt+← / Alt+→ navegam pelo histórico de documentos visitados na sessão
🔄 Sessão persistente Login lembrado por até 30 dias de inatividade; último documento aberto restaurado automaticamente ao retornar à ferramenta

API para agentes (Hermes)

O Hermes cria os três tipos de conteúdo com Bearer PKD_IMPORT_TOKEN, sem sessão de login. Uma Skill única cobre os três: prompt em docs/promptHermes.md.

Tipo Rotas com Bearer
Documento POST /api/documents — {title, content (HTML), tags?, attachments? (base64, [ADR-003](docs/adr/003-import-de-anexos-do-notas.md)), parent_id?, idempotency_key?} → 201 (200 se a idempotency_key já existe). Sem parent_id, o Documento fica na raiz. Sem tag automática. Só criação
Nota POST, GET, PATCH /api/notes (ver Nota)
Memória POST, GET, PATCH /api/memories (ver Memória Cronológica)

Sem PKD_IMPORT_TOKEN, essas rotas aceitam só a sessão de login. O antigo POST /api/import (app Notas) foi removido.

Memória Cronológica (MC)

Memórias registram eventos no tempo (um almoço, uma entrega, um evento de saúde). Cada Memória é um documento com busca, embeddings, Chat, tags, anexos e links, mas vive somente na árvore da MC do menu lateral (Ano → Mês → Dia), posicionada pela Data da Memória — nunca na árvore normal, sem pai nem filhos. Termos em docs/adr/glossary.md; ID público em ADR-007.

  • Data da Memória: só o ano é obrigatório; mês, dia e hora exata ou Período (madrugada, manhã, almoço, tarde, lanche, jantar, noite) entram quando conhecidos
  • Árvore da MC: bloco próprio no menu lateral, entre a árvore de documentos e "+ Novo documento", recolhível. Anos, meses e dias do mais recente para o mais antigo; dentro do dia: sem hora → início do dia → Período mais longo → hora exata → criação
  • Criar pela interface: "+ Nova Memória" abre um diálogo com Ano e Mês de hoje e o Dia vazio. No editor, o controle Data da Memória substitui a Data Associada e o ID tem botão Copiar. Ícone padrão bx-calendar-event
  • Sem hierarquia: Memória não tem pai nem filhos (bloqueado no backend e na interface); associe por link. Memórias entram na busca, no Chat e no Graph View
  • ID de Memória: MEM-AAAA[-MM[-DD[T<HH>[MM]]]]-SUFIXO6, ex. MEM-2026-09-23T12-7QF3K9. Congelado na criação: corrigir a data não muda o ID
  • API para agentes (Bearer PKD_IMPORT_TOKEN, ou sessão de login):
    • POST /api/memories — {title, content (HTML), tags?, attachments?, date:{year, month?, day?, hour?, minute?, period?}, idempotency_key?} → 201. A mesma idempotency_key devolve a Memória existente (200), sem duplicar
    • GET /api/memories/{MEM-id} e PATCH /api/memories/{MEM-id} (title, content, tags, date — date substitui a data inteira)
    • Sem DELETE e sem busca: apagar é ação do usuário na interface
  • Prompt para a Skill do Hermes: docs/promptHermes.md

Nota

Notas guardam um texto curto e de formatação simples (um endereço, um contato, uma lista). Cada Nota é um documento com busca, embeddings, Chat, Graph View, tags, anexos e links, mas vive somente no bloco "Notas" da barra lateral — nunca na árvore normal, sem pai nem filhos. Termos em docs/adr/glossary.md.

  • Bloco Notas: lista plana na barra lateral, favoritas primeiro, depois mais recentes; recolhido por padrão. Respeita os filtros de tag e favoritos da barra lateral
  • Criar pela interface: "+ Nova Nota" pede só o título e abre o editor completo (mesmo TipTap do Documento)
  • Sem hierarquia: Nota não tem pai nem filhos (bloqueado no backend e na interface). Ícone padrão bx-sticky-note
  • Conversão só de ida: arraste uma Nota para a árvore normal para virar Documento na posição solta (sem diálogo); arraste para o bloco MC para virar Memória (diálogo pede a Data da Memória, ano obrigatório). Corpo, tags, anexos e favorito são preservados; não existe conversão de volta
  • API para agentes (Bearer PKD_IMPORT_TOKEN, ou sessão de login):
    • POST /api/notes — {title, content (HTML)?, tags?, attachments?, favorite?, idempotency_key?, created_at?, updated_at?} → 201. A mesma idempotency_key devolve a Nota existente (200), sem duplicar. created_at/updated_at só são honrados com Bearer
    • GET /api/notes/{id} e PATCH /api/notes/{id} (title, content, tags)
    • GET /api/notes (só sessão) — lista do bloco Notas, com os mesmos filtros de tag/favorito da árvore
    • POST /api/notes/{id}/convert (só sessão) — {to:"document", parent_id?, before_id?} ou {to:"memory", date:{year, ...}}
  • Migração do app Notas (2026-09-26): as 74 notas ativas (não arquivadas, fora da lixeira) viraram Notas por POST /api/notes, com anexos, tags, favoritas (as notas fixadas) e as datas originais. Título = primeira linha da nota; linhas só de hashtags viraram tags. O script foi descartável e não está no repositório; detalhes em docs/proximosPassos.md

Início rápido

mkdir -p /caminho/pkd/db /caminho/pkd/attachments

docker run -d \
  --name pkd \
  --restart unless-stopped \
  -p 8080:8080 \
  -v /caminho/pkd/db:/data/db \
  -v /caminho/pkd/attachments:/data/attachments \
  -e PKD_PASSWORD='SUBSTITUA_POR_UMA_SENHA_FORTE' \
  -e PKD_DB_PATH=/data/db/pkd.sqlite \
  -e PKD_ATTACHMENTS_PATH=/data/attachments \
  ghcr.io/edalcin/pkd:latest

Acesse http://localhost:8080 e digite a senha mestra.


Instalação no UNRAID

Guia completo em português (sem terminal): UNRAID.md


docker compose

services:
  pkd:
    image: ghcr.io/edalcin/pkd:latest
    container_name: pkd
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      PKD_PASSWORD: ${PKD_PASSWORD:?PKD_PASSWORD is required}
      PKD_DB_PATH: /data/db/pkd.sqlite
      PKD_ATTACHMENTS_PATH: /data/attachments
      # Opcional: ativa as APIs para agentes (/api/documents, /api/memories, /api/notes)
      PKD_IMPORT_TOKEN: ${PKD_IMPORT_TOKEN}
    volumes:
      - ./data/db:/data/db
      - ./data/attachments:/data/attachments
    healthcheck:
      test: ["/pkd", "-healthcheck"]
      interval: 30s
      timeout: 3s
      retries: 3
# .env — nunca versione este arquivo
PKD_PASSWORD=senha-forte-aqui
PKD_IMPORT_TOKEN=token-secreto-compartilhado-com-agentes  # opcional

Variáveis de ambiente

Variável Obrigatória Padrão Descrição
PKD_PASSWORD sim — Senha mestra (nunca armazenada, comparada apenas em memória)
PKD_DB_PATH sim — Caminho do arquivo SQLite dentro do container
PKD_ATTACHMENTS_PATH sim — Caminho do diretório de anexos dentro do container
PKD_LISTEN_ADDR não :8080 Endereço de escuta HTTP
PKD_SESSION_IDLE_MINUTES não 43200 Minutos de inatividade até expirar a sessão (padrão ≈ 30 dias)
PKD_MAX_IMAGE_MB não 10 Tamanho máximo de imagem inline (MB)
PKD_MAX_ATTACHMENT_MB não 100 Tamanho máximo de arquivo anexado (MB)
PKD_TRUST_PROXY_HEADERS não 0 Defina como 1 apenas atrás de proxy reverso confiável
PKD_BASE_URL não (host da request) URL pública base para links de compartilhamento (ex: https://pkd.exemplo.com/)
PKD_IMPORT_TOKEN não (desativado) Token secreto (Bearer) para as APIs de agentes: POST /api/documents, Memórias (/api/memories) e Notas (/api/notes). Se vazio, essas rotas aceitam só sessão de login. Gere com openssl rand -hex 32
PKD_S3_BUCKET não (vazio) Nome do bucket S3; junto com PKD_S3_REGION habilita S3 como backend disponível
PKD_S3_REGION não (vazio) Região AWS do bucket (ex: us-east-1)
PKD_S3_PREFIX não (vazio) Prefixo de caminho dentro do bucket (ex: pkd/attachments/)
PKD_S3_ACCESS_KEY_ID não (credenciais da instância) Access Key ID; se vazio, usa credenciais padrão da instância/IAM role
PKD_S3_SECRET_ACCESS_KEY não (credenciais da instância) Secret Access Key correspondente
GEMINI_API_KEY não (desativado) Chave de API Google Gemini, usada por três recursos: embeddings semânticos, Chat com os documentos e sugestão de nome de comunidade no Graph View. Sem ela o toggle Semântico no Graph View fica indisponível, o worker de embedding não roda, o ícone 💬 do Chat fica desabilitado (POST /api/chat → 503), e a busca da árvore lateral opera só com o léxico (RRF degrada automaticamente, nunca erro)
PKD_EMBED_SWEEP_MINUTES não 15 Cadência do worker de embedding em background (minutos). O worker também dispara imediatamente ao criar/editar documentos
SES_USERNAME não (desativado) Usuário SMTP do Amazon SES. Junto com SES_PASSWORD, EMAIL_SENDER e EMAIL_2FA, habilita o 2FA por e-mail no login (vinculado ao dispositivo) e a criptografia de documentos protegidos
SES_PASSWORD não (desativado) Senha SMTP do Amazon SES
EMAIL_SENDER não (desativado) Endereço From: do e-mail de código (precisa ser uma identidade verificada no SES)
EMAIL_2FA não (desativado) Endereço que recebe todo código de 2FA (login e desbloqueio de documento)
SES_HOST não email-smtp.us-east-1.amazonaws.com Host SMTP do Amazon SES
SES_PORT não 587 Porta SMTP (STARTTLS)

⚠️ A chave de criptografia dos documentos protegidos é derivada de PKD_PASSWORD. Trocar a senha mestra torna os documentos já protegidos indecifráveis — use Desproteger em cada um antes de rotacionar a senha.


Como usar

Busca híbrida (léxica + semântica)

O campo de busca da árvore lateral (topbar) sempre executa busca híbrida: o termo digitado é buscado tanto pelo motor léxico (FTS5 + LIKE, cobrindo título, corpo, tags e títulos de links externos) quanto pelo motor semântico (similaridade de cosseno sobre embeddings Gemini), e os dois rankings são fundidos por Reciprocal Rank Fusion (RRF, k=60) numa única lista plana ordenada por relevância.

  • Sem GEMINI_API_KEY, ou antes do primeiro embed de um documento, a perna semântica fica vazia e o RRF degrada automaticamente para a ordem léxica — a busca nunca falha e nunca retorna erro.
  • O score de similaridade não é exibido na interface: a fusão RRF consome apenas a ordem dos hits. Para observar a grandeza dos pisos de similaridade, use a densidade de arestas do Graph View.
  • A busca abrange automaticamente ativos e arquivados enquanto o campo estiver preenchido; a view anterior (Ativos/Arquivados/Todos) é restaurada ao limpar a busca.
  • Não há mais seletor "Léxica | Semântica" — a fusão é sempre automática.

Chat com os documentos

O ícone 💬 na topbar abre a rota #/chat, uma conversa fundamentada apenas no conteúdo do PKD. O caminho de uma pergunta:

  1. Recuperação — a pergunta (concatenada com as últimas mensagens do usuário, para que "e sobre a fase escura?" ainda encontre o assunto do turno anterior) entra na mesma busca híbrida descrita acima.
  2. Piso de relevância — se o melhor candidato semântico não atingir chatRelevanceFloor (~0,50), o servidor responde "não encontrei nada relevante" sem chamar o modelo. Isso existe porque a busca híbrida nunca retorna vazio: sem o piso, uma pergunta fora do acervo produziria uma resposta de conhecimento geral sob uma lista de fontes que não a sustenta.
  3. Contexto — até 8 documentos, com o corpo completo (não trechos), sob um teto de tokens. Documento que não cabe é omitido, nunca truncado. Documentos protegidos por senha e na lixeira ficam fora. Cada documento leva uma linha Data: com a Data da Memória ou a data associada (com hora ou Período da Memória, quando existem), para o Chat responder "quando?" e "a que horas?".
  4. Resposta — streaming SSE via POST /api/chat, com prompt de sistema estrito ("responda apenas com base nos documentos"). Fechar a aba cancela a geração.
  5. Atribuição — a lista de documentos consultados vem do servidor, não do modelo, e por isso não pode ser alucinada. Cada item é um link que abre o documento em nova aba.

A conversa é efêmera: trocar de rota ou recarregar a página a descarta. O botão "Salvar como documento" transforma a conversa num documento PKD normal — indexado, versionado e linkável como qualquer outro.

O Modelo de Chat é escolhido em Administração → Preferências → Chat com os documentos (chave chat.model). Não confundir com o modelo de embedding, que é fixo: trocar o de Chat afeta apenas a próxima resposta, enquanto trocar o de embedding exigiria reprocessar todo o acervo.

Decisões completas em ADR-006.

Notas relacionadas

Use o campo "Buscar nota para relacionar…" na coluna Notas relacionadas no rodapé do documento para criar relações entre documentos. As relações são simétricas — ambos os documentos exibem a mesma lista de relacionados. Para remover um vínculo, clique no ✕ ao lado do documento em qualquer um dos lados.

Tags e cores

Digite no campo + tag no cabeçalho do documento. Ao digitar, um dropdown sugere as tags já existentes no sistema — selecione ou continue digitando para criar uma nova. Confirme com Enter ou ,.

Para personalizar a cor de uma tag, acesse Administração → Tags, clique no seletor de cor ao lado da tag desejada e confirme. A cor propaga imediatamente para os chips de tag na sidebar e no editor.

Ícones dos documentos

Cada documento na árvore lateral possui um ícone Boxicons selecionável. Clique no ícone ao lado do título para abrir o seletor — há busca dinâmica por nome. Documentos com filhos ganham automaticamente um ícone de pasta; ao perder todos os filhos, voltam ao ícone padrão.

Modo foco

Clique no botão de modo foco (⛶) na barra de ferramentas. O editor abre em uma janela separada sem sidebar, tags ou cabeçalho — apenas o conteúdo. Ao fechar a janela, o conteúdo editado é sincronizado de volta ao documento principal.

Sub-documentos

Crie documentos filhos normalmente pela sidebar (clique direito → "Novo sub-documento"). Ao abrir o documento pai, os filhos diretos aparecem automaticamente como cards logo abaixo do conteúdo, com preview do texto. Clicar em um card navega para o sub-documento.

Anexos e visualização

Arraste um arquivo ou clique em "+ Anexar arquivo". Imagens exibem thumbnail diretamente; clicar em qualquer anexo abre um modal:

  • Imagem → lightbox com zoom nativo do browser
  • PDF → visualizador embutido
  • Áudio → player HTML5
  • Vídeo → player HTML5
  • Outros → tela de download

Redimensionar imagens

Insira uma imagem no editor (upload, URL ou colar). Passe o mouse sobre ela — uma alça azul aparece no canto inferior direito. Clique e arraste horizontalmente para ajustar a largura. Ao soltar, a nova largura é salva no documento automaticamente.

Histórico de versões

Clique no botão ⏱ na barra de ações do documento. O dialog de histórico exibe todas as versões salvas com data/hora e preview do conteúdo.

  • Restaurar — clique em "Restaurar esta versão" para reverter o documento ao snapshot selecionado. A restauração reutiliza o fluxo normal de save (incluindo bloqueio otimista e sincronia de links).
  • Excluir versão — passe o mouse sobre uma versão para revelar o ícone de lixeira; confirme para excluí-la individualmente.
  • Configurar retenção — em Administração → Configurações, ajuste o campo "Máx. versões por documento" (padrão 50, aceita 1–10000).

Saves idênticos não geram versão nova (dedup por SHA-256 do conteúdo).

Arquivamento

Clique no ícone de caixa (📦) na barra superior do editor para arquivar ou desarquivar um documento. Ao arquivar:

  • O documento e todos os seus filhos são arquivados recursivamente e trancados automaticamente
  • Documentos arquivados somem da árvore principal e aparecem na view Arquivados
  • Um documento arquivado pode ser editado se destrancado individualmente

Ao desarquivar o documento pai, todos os filhos são desarquivados em cascata e ficam novamente disponíveis na árvore principal.

2FA por e-mail e proteção de documentos

Ambos os recursos usam o mesmo canal de e-mail (Amazon SES) e ficam desativados até as quatro variáveis SES_USERNAME, SES_PASSWORD, EMAIL_SENDER e EMAIL_2FA estarem definidas (ver Variáveis de ambiente). Sem elas, o login continua de etapa única, como antes.

Login com 2FA (vinculado ao dispositivo)

  1. Digite a senha mestra normalmente.
  2. Se o navegador não for reconhecido, um código de 6 dígitos chega por e-mail em EMAIL_2FA — digite-o na tela seguinte.
  3. Uma vez validado, esse navegador é lembrado permanentemente (cookie de dispositivo) e não pede código de novo, mesmo após logout/login.
  4. Para revogar todos os dispositivos (ex: perda de acesso a um navegador confiável), acesse Administração → Configurações → Esquecer dispositivos confiáveis. O próximo login em qualquer navegador volta a pedir código.

Proteção de documento (criptografia em repouso)

  1. Abra o documento e clique no ícone de escudo (🛡) na barra de título.
  2. O corpo é criptografado (AES-256-GCM, chave derivada de PKD_PASSWORD) e permanece visível apenas na sessão atual — você não perde o que estava editando. O ícone muda para escudo preenchido confirmando a proteção.
  3. Ao reabrir o documento numa sessão nova (outro navegador, ou após logout/login), o editor mostra um painel de desbloqueio no lugar do conteúdo: clique em "Enviar código por e-mail", digite o código de 6 dígitos recebido e o conteúdo é decifrado para aquela sessão.
  4. Documentos protegidos saem da busca por conteúdo e dos embeddings semânticos enquanto criptografados — o título continua pesquisável e visível na árvore, já que não é secreto.
  5. Clique no escudo novamente para desproteger (exige que o documento já esteja desbloqueado na sessão atual).

⚠️ A chave de criptografia é derivada de PKD_PASSWORD — trocar a senha mestra torna documentos protegidos indecifráveis. Desproteja-os antes de rotacionar a senha.

Armazenamento S3

Configure as variáveis PKD_S3_BUCKET e PKD_S3_REGION para habilitar o backend S3. Depois acesse Administração → Storage:

  1. Testar conexão — verifica credenciais e acesso ao bucket
  2. Migrar para backend ativo — copia todos os anexos do backend de origem para o ativo com verificação SHA256 por arquivo; exibe barra de progresso em tempo real e relatório final; idempotente (pode ser repetido após falha)
  3. Ativar S3 — troca o backend ativo; novos uploads vão para o S3
  4. Reconciliar S3 ↔ DB — corrige storage_location no banco quando ele diverge do que existe no bucket (ex: após restore do DB); não move arquivos
  5. Reconciliar LOCAL ↔ DB — mesmo que acima, para o backend local; sempre visível mesmo sem S3
  6. Limpar origem — remove arquivos do backend anterior; verifica que existe cópia confirmada no backend ativo antes de cada exclusão; arquivos sem cópia são ignorados e reportados

Todas as operações (Migrar, Reconciliar, Limpar) são assíncronas com job tracking: barra de progresso durante execução e relatório detalhado ao concluir. Somente uma operação por backend por vez (segunda tentativa retorna 409).

Fluxo completo Local → S3:

Testar conexão → Migrar → Ativar S3 → Limpar origem (verificar ignorados = 0)

Backup e restauração de anexos (S3)

Quando o backend ativo é S3, o PKD oferece backup/restauração assíncrona sem materializar o ZIP no disco da instância de aplicação:

  1. Backup assíncrono — Administração → Storage → Backup assíncrono (S3). Pipeline archive/zip → io.Pipe → manager.Uploader grava o ZIP diretamente no bucket em <prefix>/_backup-tmp/<job-id>.zip. Retorna URL pré-assinada (TTL 15 min) para download direto S3 → navegador. Progresso e contadores via polling.
  2. Restauração assíncrona — Administração → Storage → Restauração assíncrona aceita ZIP gerado pela aplicação. Restauração cross-backend (ZIP de prod S3 restaurado em dev local e vice-versa) e in-place (mesmo backend, recupera arquivos perdidos). 3 modos de conflito: sobrescrever (padrão), manter existente, abortar.
  3. Manifesto interno (manifest.json como última entrada do ZIP) — mapeia SHA256 → stored_filenames. Dedup natural quando múltiplas linhas de attachments compartilham o mesmo conteúdo; fan-out automático no restore.
  4. Integridade — cada entrada restaurada tem hash verificado antes de gravar. Hash mismatch é relatado por arquivo sem abortar a operação (FR-008). Entradas órfãs (SHA256 sem linha em attachments) são ignoradas e listadas no resultado.
  5. Limpeza automática — no startup, sweep não-bloqueante remove objetos em _backup-tmp/ com idade > 24h (recuperação de crashes).
  6. Concorrência — uma operação ativa por backend por vez; segunda requisição retorna 409.

IAM mínimo: s3:GetObject, s3:PutObject, s3:DeleteObject, s3:ListBucket, s3:DeleteObjects, s3:AbortMultipartUpload.

Endpoints legados (/api/admin/storage/backup-attachments síncrono local) continuam funcionando.

Detalhes operacionais em docs/operations.md.

Importar imagens externas

Ao colar conteúdo de outra página (blog, artigo, e-mail), imagens com URL externa ficam referenciadas no documento mas não são armazenadas no PKD — podem quebrar se o site original sair do ar.

No editor — quando o documento aberto contém imagens externas, um botão 🌐⬇ aparece na barra de ferramentas. O botão fica desabilitado durante a importação (evita duplo-clique) e desaparece imediatamente ao concluir quando não restam mais imagens externas. Um feedback inline exibe quantas foram importadas e quantas falharam (visível por 5 segundos).

Em lote via Administração → Arquivos — a seção "Imagens externas" lista todos os documentos com imagens externas, com o número de imagens por documento. Clique em "🌐⬇ Importar" ao lado de cada documento para importar individualmente. Se o documento importado estiver aberto no editor, o conteúdo é atualizado automaticamente sem necessidade de recarregar a página.

As imagens importadas passam a aparecer na grade de "Arquivos anexados" e ficam disponíveis mesmo se o site externo for removido.

Importar e exportar Markdown

  • Exportar — clique em ⬇ .md na barra de ferramentas. O browser baixa imediatamente o arquivo <título>.md com o conteúdo do documento convertido para Markdown (headings #, código em triple-backtick, links [Documento] preservados).
  • Importar — clique em ⬆ .md, escolha um arquivo .md/.markdown/.txt no seletor do sistema. O conteúdo é convertido e inserido na posição atual do cursor (ou no fim do documento, se o editor não estiver focado). Desabilitado em documentos trancados. Suporta headings, listas (incluindo checklist quando todos os itens da lista usam - [ ]/- [x]), tabelas e formatação inline.

Diagramas Mermaid

Um bloco de código com fence ```mermaid renderiza o diagrama automaticamente. O código-fonte fica oculto por padrão — apenas o diagrama é exibido, com um chip </> no canto superior direito do bloco.

  • Clique no diagrama ou no chip para alternar a exibição do código-fonte.
  • Posicionar o cursor dentro do código (para editá-lo) revela a fonte automaticamente; ela volta a ocultar-se ao mover o cursor para fora do bloco.
  • Funciona em documentos trancados — o chip continua alternando a visualização mesmo sem poder editar.
  • Blocos de código de outras linguagens (```js, ```python, etc.) não são afetados — permanecem sempre com o código visível, sem renderização.

Links inline no editor

Digite [ em qualquer posição do editor. Um dropdown de autocomplete aparece buscando documentos pelo título em tempo real (FTS5). Continue digitando para filtrar os resultados; use ↑/↓ para navegar, Enter ou clique para confirmar.

Ao selecionar, o texto digitado é substituído por um chip de link clicável com o título do documento. Clicar no chip navega para o documento vinculado. Os links são bidirecionais — ambos os documentos exibem a relação no painel "Notas relacionadas" e no Graph View (aresta de tipo link).

No export Markdown (botão ⬇ .md), chips de link são convertidos para [Título do Documento].


Graph View

Acesse pelo ícone 🕸️ na barra superior. O grafo mostra:

  • Nós de documento → círculos coloridos pela tag primária
  • Nós de tag → círculos com borda tracejada rosa
  • Arestas de hierarquia → linhas tracejadas --accent entre pai e filho
  • Arestas de link → relações manuais entre documentos (azul)
  • Arestas de tag → relação documento ↔ tag (rosa)
  • Arestas semânticas → similaridade de conteúdo via Gemini embeddings (roxo tracejado); visíveis quando GEMINI_API_KEY está configurada

O campo de filtro da barra superior também atua no grafo — ao digitar, apenas os nós que correspondem ao filtro são exibidos, mantendo as arestas de seus vizinhos diretos para preservar o contexto.

Toggles de visibilidade (independentes, sem reiniciar simulação):

Toggle O que controla
Hierarquia Exibe/oculta arestas e nós de relação pai/filho
Links entre docs Exibe/oculta arestas e nós de relação manual entre documentos
Relações com tags Exibe/oculta arestas e nós de tag
Semântico Exibe arestas de similaridade semântica (requer GEMINI_API_KEY); ao ativar, detecta comunidades via Louvain, colorindo nós por cluster e posicionando-os em layout agrupado; legenda filtra comunidades individualmente ou em bloco
Todos os docs Força exibição de todos os documentos, mesmo sem conexões

Clicar em um nó de tag filtra o grafo por aquela tag. Clicar em um nó de documento navega para o documento.

Embeddings semânticos são gerados proativamente em background: no startup do servidor, ao criar/editar documentos e a cada PKD_EMBED_SWEEP_MINUTES minutos. Os vetores ficam armazenados no próprio SQLite — o grafo semântico carrega instantaneamente sem nova chamada à API no momento do clique no toggle.

Comunidades semânticas — ao ativar o toggle Semântico o PKD detecta comunidades automaticamente:

  • Algoritmo Louvain local-moving (JS puro, sem dependências) sobre o grafo de similaridade visível
  • Nós coloridos por comunidade via ângulo dourado HSL — suporta qualquer número de clusters sem paleta fixa
  • Singletons (nós sem aresta semântica com peso suficiente) exibidos em cinza
  • Layout inicial em posições circulares por cluster, forças forceX/forceY mantendo agrupamento
  • Legenda interativa: checkbox por comunidade + checkbox "Todas"; visibilidade via D3 display sem reiniciar a simulação física
  • Barra de status exibe N nós · M arestas · K comunidades

Proxy reverso (HTTPS)

O PKD não gerencia TLS — encerre no proxy reverso. Exemplo com Caddy:

pkd.exemplo.lan {
    reverse_proxy localhost:8080
}

Ao usar proxy reverso, adicione PKD_TRUST_PROXY_HEADERS=1 e configure PKD_BASE_URL com a URL pública para que os links de compartilhamento gerem URLs corretas.


Compilar a partir do código-fonte

Requer Go 1.25+ e Node.js 22+.

git clone https://github.com/edalcin/pkd.git
cd pkd

# 1. Build do frontend Svelte
cd frontend && npm install && npm run build && cd ..

# 2. Rodar localmente
PKD_PASSWORD=devpassword \
PKD_DB_PATH=/tmp/pkd.sqlite \
PKD_ATTACHMENTS_PATH=/tmp/pkd-att \
go run ./cmd/pkd

Bundle do editor para o app móvel (pkdMobile)

npm run build:editor (dentro de frontend/) gera uma build separada de editor.html em frontend/dist-editor/ (base: './', sem o service worker/manifest da PWA), com as mesmas extensões TipTap da PWA (src/lib/editor/extensions.js). O pkdMobile vendoriza esse bundle via scripts/update-editor-bundle; ver ADR pkdMobile 0002.


CI/CD

Todo push para o branch main dispara o workflow do GitHub Actions que:

  1. Executa go test ./...
  2. Builda a imagem Docker (linux/amd64)
  3. Publica ghcr.io/edalcin/pkd:edge (UNRAID/dev — atualiza automaticamente) e ghcr.io/edalcin/pkd:sha-<hash> (imutável)

Promoção para produção — execute manualmente o workflow promote-to-prod (via workflow_dispatch no GitHub Actions) ou crie uma tag semântica:

git tag -a v1.2.3 -m "descrição" && git push origin v1.2.3

O workflow re-etiqueta a imagem :edge como :stable e :v1.2.3 — sem rebuild.

Limpeza automática — workflow semanal remove tags sha-* com mais de 30 dias do GHCR.

Nenhuma credencial é armazenada no repositório — o workflow usa o GITHUB_TOKEN automático do GitHub Actions.


Arquitetura

graph TD
    User(["👤 Usuário"]) -->|"HTTPS / Browser"| App
    Mobile(["📱 Mobile OS"]) -->|"PWA Share Target"| App
    Hermes(["🤖 Hermes"]) -->|"Bearer token\n/api/documents, /api/memories, /api/notes"| App

    subgraph Container ["🐳 Docker Container"]
        App["⚙️ Go HTTP Server\n(chi router · handlers · middleware)"]
        SPA["🌐 Svelte 5 SPA\n(TipTap v3 · D3.js · embutido no binário)"]
    end

    App --- SPA
    App -->|"SQL / modernc-sqlite"| DB[("🗄️ SQLite\ndocuments · document_links\ndocument_urls · attachments\ntags · shares · FTS5")]
    App -->|"storage.Backend"| Vol[("📂 Local Volume\n(padrão)")]
    App -.->|"storage.Backend (opcional)"| S3[("☁️ Amazon S3\n(PKD_S3_BUCKET)")]
Loading

Modelo de dados

Tabela Descrição
documents Documentos com hierarquia via parent_id, soft-delete, versionamento otimista
documents (Memória) Memória = documento com memory_id não nulo. Colunas memory_id (ID público, UNIQUE), memory_hour, memory_minute, memory_period, memory_key (idempotência, UNIQUE); dia em assoc_year/month/day
documents (Nota) Nota = documento com is_note = 1. Coluna note_key (idempotência, UNIQUE); sem pai nem filhos, fora da árvore normal
document_links Arestas simétricas entre documentos; o "outro doc" é derivado via CASE WHEN independente de qual lado é source_id. Flag manual distingue links do painel de notas relacionadas
document_urls URLs externas com título opcional associadas a documentos
attachments Metadados de arquivos; binários em volume externo com path sharding
tags + document_tags Tags normalizadas com campo color; join N:N com documentos
documents_fts Tabela virtual FTS5 (contentless) para busca full-text
share_links Links públicos com hash de token e campo revoked_at
document_embeddings Cache de vetores semânticos: embedding BLOB float32 LE (3072 dims em ambos os modelos suportados), content_hash SHA-256 sobre nome do modelo + texto formatado, para invalidação. Gerenciado pelo worker proativo; limpeza automática de docs trashed/archived a cada sweep
settings Configurações persistidas em chave-valor (chat.model, retenção de versões, backend de armazenamento). O modelo de embedding não vive aqui: é constante compilada
document_versions Snapshots de conteúdo com dedup SHA-256 e retenção configurável

Documentação

Documento Idioma Descrição
UNRAID.md 🇧🇷 PT-BR Instalação no UNRAID via GUI
docs/c4/context.md 🇺🇸 EN C4 Level 1 — Contexto do sistema
docs/c4/container.md 🇺🇸 EN C4 Level 2 — Containers
docs/c4/component.md 🇺🇸 EN C4 Level 3 — Componentes Go
docs/c4/code.md 🇺🇸 EN C4 Level 4 — Structs e fluxos de código
docs/security.md 🇺🇸 EN Referência de segurança
docs/operations.md 🇧🇷 PT-BR Guia de operações, atualização, promoção dev → prod e backup
docs/proximosPassos.md 🇧🇷 PT-BR Estado atual, próximos passos e decisões de cada feature
docs/adr/glossary.md 🇧🇷 PT-BR Glossário (Memória, Nota, app Notas, busca, embeddings…)
docs/adr/ 🇧🇷 PT-BR Decisões de arquitetura (ADRs)

Changelog

2026-09-26 — v1.3.0

Nota

  • Terceiro tipo de conteúdo: Nota, documento sem pai nem filhos, fora da árvore normal, com bloco próprio "Notas" na barra lateral (favoritas primeiro, depois mais recentes; filtros de tag/favorito)
  • API POST /api/notes (idempotente por idempotency_key, created_at/updated_at honrados só via Bearer), GET/PATCH /api/notes/{id}, GET /api/notes; Bearer PKD_IMPORT_TOKEN ou sessão
  • Conversão só de ida: POST /api/notes/{id}/convert para Documento (posição na árvore) ou Memória (Data da Memória, ID MEM-…); arrastar a Nota para a árvore ou para o bloco MC dispara a conversão na interface
  • "+ Nova Nota" pede só o título e abre o editor completo; ícone padrão bx-sticky-note
  • Migração aditiva (is_note, note_key em documents, sem backfill)
  • As 74 notas ativas do app Notas foram migradas (homologação e produção); o app Notas foi desligado

Correções

  • /healthz prendia a única conexão do banco (QueryRow().Err() sem Scan); depois da primeira checagem o servidor inteiro parava de responder
  • O bloco Notas perdia o filtro de tag/favoritos ao recarregar depois de renomear, arquivar ou mandar para a lixeira

2026-09-24

Memória Cronológica (MC)

  • Nova árvore Ano → Mês → Dia no menu lateral para Memórias: documentos posicionados pela Data da Memória (só o ano é obrigatório; hora exata ou Período opcionais)
  • ID público estável MEM-AAAA[-MM[-DD[T<HH>[MM]]]]-SUFIXO6; corrigir a data não muda o ID (ADR-007)
  • API POST /api/memories (idempotente por idempotency_key), GET/PATCH /api/memories/{MEM-id}; Bearer PKD_IMPORT_TOKEN ou sessão
  • "+ Nova Memória" na interface; Data da Memória e ID copiável no editor
  • Migração aditiva (colunas e índices novos em documents, sem backfill)
  • Prompt para a Skill do Hermes em docs/promptHermes.md

2026-07-28

Busca híbrida por fusão RRF — fim do seletor de modo

  • GET /api/tree?q=… passa a rodar sempre os dois recuperadores (léxico via FTS5+LIKE, semântico via embeddings Gemini) e funde os resultados por Reciprocal Rank Fusion (k=60); sem GEMINI_API_KEY a perna semântica fica vazia e a fusão degrada exatamente para a ordem léxica — sem 503, sem seletor de modo
  • Removido o <select> "Léxica | Semântica" da topbar e os stores searchMode/semanticAvailable — busca sempre única e automática
  • Piso semântico reduzido de 0.45 para 0.30 e top-k de 50 para 100 (o RRF já pondera por posição de rank, não por corte rígido de similaridade)
  • Ver ADR-002 para a decisão completa

2026-06-10

Sessão persistente e restauração do último documento

  • Timeout de inatividade da sessão aumentado de 60 min para 30 dias (43200 min) — usuário permanece logado entre sessões do browser sem precisar re-autenticar. Variável PKD_SESSION_IDLE_MINUTES ainda permite sobrescrever o valor em produção
  • Último documento visitado salvo em localStorage a cada navegação; restaurado automaticamente ao abrir a ferramenta quando a URL não possui hash específico
  • Home (/) não é salvo como "último estado" — navegação intencional à raiz nunca polui o restore

2026-06-09

Títulos de documentos únicos

  • Títulos funcionam como identificadores nas relações entre documentos — duplicatas passam a ser bloqueadas no nível do banco (índice UNIQUE parcial) e da API (HTTP 409)
  • Ao tentar salvar um título já existente, diálogo informa o conflito e oferece botão para navegar ao documento existente
  • Documentos existentes com títulos duplicados detectados e corrigidos na migração

2026-06-08

Navegação por histórico de documentos

  • Botões ← e → na barra superior (desktop) navegam pelo histórico de documentos visitados na sessão atual
  • Atalhos de teclado: Alt+← (voltar) e Alt+→ (avançar) — funcionam em qualquer view
  • Histórico em memória: preservado durante a sessão, não persiste entre recarregamentos de página

2026-06-06

Filtro na Graph View

  • Campo de filtro da barra superior passa a atuar também na visualização em grafo
  • Nós que não correspondem ao texto filtrado são ocultados; arestas de vizinhos diretos dos nós visíveis são mantidas para preservar contexto de conexões

2026-06-07

Links inline no editor ([Título])

  • Digite [ no editor para ativar autocomplete de documentos internos com busca FTS5 em tempo real
  • Selecionar um resultado substitui o texto digitado por um chip de link clicável (docLink inline node)
  • Clicar no chip navega para o documento vinculado (#/doc/{id})
  • Links bidirecionais: sincronizados automaticamente na tabela document_links a cada save (parser HTML existente via data-doc-link)
  • Export Markdown: chips renderizam como [Título do Documento]

2026-06-01

Importar imagens externas como attachments

  • Ao colar conteúdo de outros sites ("corta-e-cola"), imagens hospedadas externamente (<img src="https://...">) podem ser convertidas em attachments internos
  • No editor: botão 🌐⬇ aparece na barra de ferramentas quando o documento contém imagens externas. Desabilitado durante a operação (evita duplo-clique); desaparece imediatamente após importação completa; exibe resultado inline ("N importada(s) / N falhou") por 5 s
  • Na Administração → Arquivos: nova seção "Imagens externas" lista todos os documentos afetados com contagem de imagens por documento. Botão "Importar" por linha; se o documento estiver aberto no editor, o conteúdo é sincronizado automaticamente sem recarregar a página
  • Endpoints: POST /api/documents/{id}/attachments/from-url (editor), GET /api/admin/external-images, POST /api/admin/documents/{id}/import-external-images
  • Segurança: fetch server-side protegido contra SSRF com bloqueio de IPs privados, loopback e metadata (169.254.x) via net.Dialer.Control pós-DNS

2026-05-29

Gerenciador de arquivos órfãos em Administração → Arquivos

  • Botão "🔍 Verificar Órfãos" ao lado de "↻ Atualizar" na aba Arquivos carrega a lista de arquivos no disco sem registro no banco (órfãos de uploads com falha ou exclusões incompletas)
  • Cada arquivo órfão exibe nome, tamanho, botão "⬇ Baixar" (download individual) e "🗑 Eliminar" (exclusão com confirmação)
  • Endpoints: GET /api/admin/attachments/orphans retorna lista completa com tamanhos; GET /api/admin/attachments/orphans/download?key=… faz download; DELETE /api/admin/attachments/orphans/item?key=… elimina
  • Segurança: download e delete verificam que a chave é de fato um órfão antes de agir
  • Aba Limpeza: passa a fazer apenas VACUUM (gerenciamento de órfãos movido para Arquivos)

2026-05-28

Histórico de versões de documentos

  • Snapshot automático do conteúdo (título + corpo + ícone) a cada save, com dedup SHA-256 — saves idênticos não geram versão
  • Retenção padrão: 50 versões por documento; configurável em Administração → Configurações → Versões
  • Botão ⏱ na barra de ações do documento abre dialog de histórico: lista versões com data/hora, preview do conteúdo, restauração com confirmação
  • Excluir versão individual: ícone de lixeira por entrada (aparece no hover), com confirmação
  • Endpoints: GET /api/documents/:id/versions, GET /api/documents/:id/versions/:vid, POST /api/documents/:id/versions/:vid/restore, DELETE /api/documents/:id/versions/:vid
  • Nova tabela: document_versions (CASCADE ao excluir o documento, indexada por doc+id DESC)
  • Admin → Configurações: campo numérico para versions.max_per_doc (validado 1–10000)

Reconciliação S3 ↔ DB (fix em Administração → Storage)

  • Novo botão "Reconciliar S3 ↔ DB": varre o bucket S3, identifica arquivos com storage_location incorreto no banco (comum após restore de backup do banco sem os arquivos S3) e corrige sem mover arquivos
  • Migração agora expõe total_found além de copied — quando total_found=0 a UI sugere usar o reconcile antes de migrar
  • Endpoint: POST /api/admin/storage/reconcile

2026-05-21

Mobile master-detail

  • Interface mobile (≤ 640 px) redesenhada: tela-lista com topbar de 2 linhas (ícones de ação na primeira linha, campo de busca largo na segunda) e árvore de documentos ocupando a tela inteira
  • Documentos, grafo, calendário e admin abrem em tela-detalhe com botão ← na topbar para voltar à lista
  • Drawer hambúrguer removido (padrão master-detail torna-o redundante)
  • Desktop (≥ 641 px) inalterado

Favoritar da barra de ações

  • Botão ⭐ adicionado à barra de ações do documento (entre "Copiar link" e "Trancar")
  • Alterna favorito diretamente no documento aberto; sincroniza automaticamente com a árvore lateral

2026-05-18

Backup e restauração de anexos com backend S3 (feature 005)

  • Backup assíncrono S3 — novo endpoint POST /api/admin/storage/backup-start gera ZIP no próprio bucket via streaming multipart (io.Pipe + manager.Uploader), sem usar disco da instância. URL pré-assinada para download direto (TTL 15 min, re-gerável). Job tracking com polling de progresso.
  • Restauração assíncrona cross-backend — POST /api/admin/storage/restore-start aceita o ZIP gerado pela aplicação. ZIP de produção (S3) pode ser restaurado em desenvolvimento (local) e vice-versa. Backend S3 usa Range GETs (io.ReaderAt) para evitar disco; backend local usa temp file.
  • Manifesto interno com SHA256 como chave lógica — dedup natural no ZIP, fan-out automático no restore quando múltiplas linhas de attachments compartilham conteúdo.
  • Integridade verificada por entrada — hash mismatch reportado sem abortar restauração; entradas órfãs ignoradas e listadas.
  • 3 modos de conflito no restore: overwrite (padrão), keep, abort.
  • Sweep automático no startup remove ZIPs temporários órfãos com idade > 24h em _backup-tmp/.
  • Single-in-flight por backend com BackupJobManager (mutex + LRU 50, ErrJobInFlight → 409).
  • Backfill SHA256 automático para linhas históricas sem hash, executado durante o backup via io.TeeReader.
  • IAM: novas ações necessárias s3:DeleteObjects (sweep) e s3:AbortMultipartUpload.

Tests: 20 unit tests novos (manifest, jobs, writer, reader, fan-out, on_conflict modes, hash mismatch, in-place, per-entry failure isolation). Documentação completa em docs/operations.md.


2026-05-09

Editor

  • Breadcrumb de hierarquia abaixo do título: ancestrais clicáveis via CTE recursiva no backend; navegue para qualquer nível da hierarquia com um clique

2026-05-08

Armazenamento S3

  • Backend S3 (AWS SDK v2, SSE-S3, Intelligent-Tiering) com interface storage.Backend e injeção de dependência
  • Migração Local → S3 com verificação SHA256 por arquivo; processo idempotente e seguro para re-execução
  • Painel de administração: teste de conexão, migração, ativação de backend e limpeza da origem
  • Novas variáveis: PKD_S3_BUCKET, PKD_S3_REGION, PKD_S3_PREFIX, PKD_S3_ACCESS_KEY_ID, PKD_S3_SECRET_ACCESS_KEY

Backup de anexos

  • Download ZIP de todos os anexos do backend local (GET /api/admin/storage/backup-attachments)
  • Restauro via ZIP (POST /api/admin/storage/restore-attachments) — migração entre instâncias sem rsync/SCP

2026-05-03

Graph View

  • Hierarquia pai/filho visualizada como arestas tracejadas com cor de destaque (--accent)
  • Toggles independentes: Hierarquia, Links entre docs, Relações com tags — oculta/exibe nós e arestas sem reiniciar a simulação de força
  • Filtro de nós por tipo de relação: ao ativar um toggle, apenas documentos com aquele tipo de relação aparecem (a menos que "Todos os docs" esteja ativo)
  • Cores distintas por tipo de aresta: azul (links), rosa (tags), accent tracejado (hierarquia)

2026-05-02

Arquivamento

  • Arquivar/desarquivar via ícone na barra do editor (sempre visível, como o cadeado)
  • Arquivar pai cascateia recursivamente para todos os filhos; auto-tranca ao arquivar
  • Desarquivar pai cascateia para todos os filhos recursivamente
  • Documento arquivado pode ser editado normalmente se destrancado individualmente
  • Auto-save desativado para documentos trancados

Links entre documentos

  • Relações passam a ser simétricas e sem direção: ambos os documentos exibem a mesma lista única de "Relacionados", sem distinção entre "Notas relacionadas" e "Referenciado por"

Graph View

  • edge_type separado em link e tag no backend, preparando suporte a toggles de visibilidade

Admin

  • Tabela de gerenciamento de links externos na aba Links (responsiva ao espaço disponível)

2026-04-21

Integração com Notas

  • Endpoint POST /api/import autenticado via Bearer token (PKD_IMPORT_TOKEN); cria documento com conteúdo HTML, tags da nota de origem e tag notas; desativado quando a variável não está configurada
  • CSRF middleware atualizado para ignorar requisições com Authorization: Bearer — chamadas server-to-server não são vulneráveis a CSRF e não precisam de cookie

Árvore lateral

  • Filtro unificado: campo de busca integrado à árvore, substituindo os dois controles anteriores (busca e filtro separados)
  • Ícones Boxicons para cada documento: seletor dinâmico com busca por nome; ícone de pasta atribuído automaticamente a documentos com filhos
  • Estado expandido/colapsado da árvore persistido no localStorage entre sessões

Editor

  • Modo foco abre o editor em uma janela separada (popup) sem a interface principal; conteúdo sincronizado de volta ao fechar

CI/CD

  • Workflow GitHub Actions adicionado: build e push automático de ghcr.io/edalcin/pkd:latest a cada push no main

v1.0 — 2026-04-19

Primeira versão estável e completa do PKD.

Editor

  • Redimensionamento de imagens inline por alça de arrastar
  • Exportação de documentos como Markdown (.md) com download direto no browser
  • Correção de race condition que causava documento vazio ao navegar rapidamente entre documentos
  • Prevenção de conflito de versão espúrio causado por saves concorrentes no auto-save

Organização

  • Reordenação manual de documentos por drag-and-drop na árvore lateral
  • Ordenação A-Z e cronológica na árvore
  • Filtro por documentos favoritos (⭐)
  • Marcar/desmarcar documentos como favoritos

Compartilhamento

  • Redesign da view pública de documentos compartilhados
  • PKD_BASE_URL para geração correta de links atrás de proxy reverso

Segurança

  • Sanitização de URLs relativas no HTML (correção de regressão)

Licença

MIT © 2026 Eduardo Dalcin

About

Personal Knowledge Database

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages