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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- A janela do MVP exibe progresso, estados de sucesso/erro e pode ser fechada pelo botão visível ou pela tecla `Esc`. No Linux/Wayland, a Waybar abre a janela flutuante compacta em cerca de `432x272` no compositor (`380x220` de área interna Tauri) e fechar encerra a janela como antes; no Windows, a janela usa a mesma área interna compacta, fechar oculta a janela, mantém o app vivo na tray até o usuário escolher `Sair`, abre posicionada acima da área da tray e pode ser arrastada pela barra superior customizada. No primeiro start no Windows, o app ativa `Iniciar com Windows` automaticamente e grava um marcador local; se o usuário desativar o autostart no menu da tray, o app não reativa sozinho em starts futuros. Fonte: launcher Waybar `scripts/quickdrop-waybar`, configuração Tauri `src-tauri/tauri.conf.json`, tray/posicionamento/autostart em `src-tauri/src/lib.rs` e UI `src/desktop/App.tsx`.
- A instalação Windows por PowerShell é pública no endpoint `GET /install.ps1`; o `.exe` é baixado pelo endpoint interno `GET /windows/latest.exe`, que usa um token GitHub configurado somente no servidor para buscar o asset privado `QuickDrop_*_x64-setup.exe` da última release sem expor credenciais ao usuário final. A página principal exibe `irm https://quickdrop.eaedave.xyz/install.ps1 | iex` com botão de cópia. Após o NSIS silencioso concluir, o script abre o app instalado em modo visível no canto direito e libera o terminal. Fonte: `src/desktop/App.tsx`, `scripts/install-windows.ps1`, `src/server/index.ts` e `src/server/windows-installer-service.ts`.
- A instalação Linux por Bash é pública no endpoint `GET /install.sh` (`curl -fsSL https://quickdrop.eaedave.xyz/install.sh | bash`); o script detecta Linux/x86_64 com Waybar (e avisa se faltar Hyprland ou dependências de runtime como webkit2gtk, gtk3, wl-clipboard e libnotify), baixa o binário pré-compilado pelo endpoint interno `GET /linux/latest` — que usa o mesmo token GitHub server-side para buscar o asset privado `quickdrop_*_x86_64-linux` da última release —, instala `~/.local/bin/quickdrop` e o launcher `~/.local/bin/quickdrop-waybar` (servido por `GET /linux/quickdrop-waybar`), e registra de forma idempotente o módulo `custom/quickdrop` na Waybar com backup do config. Fonte: `scripts/install-linux.sh`, `src/server/index.ts`, `src/server/linux-installer-service.ts` e `src/server/github-release.ts`.
- O QuickDrop tem um relay de texto em tempo real para colar/compartilhar texto entre máquinas sem clipboard compartilhado (ex.: máquinas Guacamole). Pela web, o usuário cria uma sala (botão "Criar nova sala") ou entra com um código curto de 6 caracteres; um textarea grande é sincronizado ao vivo entre todos na mesma sala via WebSocket, no modelo último-a-escrever-vence (last-writer-wins) com versão monotônica. Edições simultâneas não sobrescrevem em silêncio: quando chega uma alteração remota durante uma edição local pendente, a UI mostra um aviso não destrutivo com opção de carregar. As salas ficam em memória (não sobrevivem a redeploy), expiram após inatividade (padrão 12h sem clientes) e limitam tamanho do texto (padrão 256 KB) e número de salas/clientes. A página é servida pelo mesmo backend (subdomínio `texto.*`, rota `/t` ou `?c=CÓDIGO` na raiz). Fontes: Endpoints internos `POST /api/text` (cria sala), `GET /api/text/:code` (snapshot) e `WS /api/text/:code/ws` (sync); redirects internos `GET /t` e `GET /t/:code`; store `src/server/text-session-store.ts`, serviço `src/server/text-session-service.ts`, UI `src/desktop/TextSession.tsx`.
- O QuickDrop tem um relay de texto em tempo real para colar/compartilhar texto entre máquinas sem clipboard compartilhado (ex.: máquinas Guacamole). Pela web, o usuário cria uma sala (botão "Criar nova sala") ou entra com um código curto de 6 caracteres; um textarea grande é sincronizado ao vivo entre todos na mesma sala via WebSocket, no modelo último-a-escrever-vence (last-writer-wins) com versão monotônica. Edições simultâneas não sobrescrevem em silêncio: quando chega uma alteração remota durante uma edição local pendente, a UI mostra um aviso não destrutivo com opção de carregar. As salas agora ficam persistidas no PostgreSQL, então sobrevivem a restart/deploy; continuam expirando por inatividade (padrão 12h sem clientes), limitam tamanho do texto (padrão 256 KB) e número de salas/clientes. A página é servida pelo mesmo backend (subdomínio `texto.*`, rota `/t` ou `?c=CÓDIGO` na raiz). Fontes: Endpoints internos `POST /api/text` (cria sala), `GET /api/text/:code` (snapshot) e `WS /api/text/:code/ws` (sync); redirects internos `GET /t` e `GET /t/:code`; repositório `src/server/text-rooms-repository.ts`, hub `src/server/text-session-hub.ts`, serviço `src/server/text-session-service.ts`, UI `src/desktop/TextSession.tsx`.
<!-- business-readme:business-rules:end -->

<!-- business-readme:technical:start -->
Expand Down Expand Up @@ -99,7 +99,7 @@ Relay de texto em tempo real entre máquinas, no mesmo backend:
- `GET /api/text/:code` — snapshot atual `{ text, version }` (404 se a sala não existe).
- `WS /api/text/:code/ws` — o servidor envia `snapshot` ao conectar, `update` quando outro cliente escreve, `ack` ao autor após cada escrita e `error` (texto acima do limite ou sala inválida). O cliente envia `{ type: "write", text, baseVersion }`. Heartbeat ping/pong derruba conexões mortas.

Estado em memória (perde no redeploy), com varredura de TTL e limites configuráveis por ambiente:
Estado persistido no PostgreSQL (sobrevive a restart/redeploy), com expiração por TTL e limites configuráveis por ambiente:

```env
TEXT_SESSION_TTL_HOURS=12
Expand Down
5 changes: 3 additions & 2 deletions docs/LLM_CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- Regra: no Windows, QuickDrop roda residente na system tray. Clique esquerdo ou menu `Abrir QuickDrop` abre/foca a janela; botão fechar/Esc ocultam a janela; menu `Sair` encerra o processo; o primeiro start ativa autostart automaticamente com `--tray-start` e grava marcador local para não reativar sozinho se o usuário desativar depois via menu `Iniciar com Windows`. Fonte: `src-tauri/src/lib.rs`, `src-tauri/tauri.windows.conf.json` e `src/desktop/App.tsx`.
- Regra: a instalação Windows por PowerShell (`GET /install.ps1`) baixa o instalador por `GET /windows/latest.exe`; esse endpoint usa `QUICKDROP_GITHUB_TOKEN`/`GITHUB_TOKEN` somente no backend para buscar o asset privado `QuickDrop_*_x64-setup.exe` da última release GitHub, sem expor token ao script público. `GET /` mostra o mesmo comando para cópia rápida. Depois do NSIS silencioso, o script abre o `QuickDrop.exe` instalado sem `--tray-start`, deixando a janela visível no canto direito e liberando o terminal sem esperar o processo residente da tray encerrar. Fonte: `src/desktop/App.tsx`, `scripts/install-windows.ps1`, `src/server/index.ts`, `src/server/windows-installer-service.ts`, `src/server/index.test.ts`.
- Regra: a instalação Linux por Bash (`GET /install.sh`, `curl -fsSL https://quickdrop.eaedave.xyz/install.sh | bash`) detecta Linux x86_64 com Waybar (avisa se faltar Hyprland ou as dependências de runtime webkit2gtk/gtk3/wl-clipboard/libnotify), baixa o binário pré-compilado por `GET /linux/latest` (mesmo token GitHub server-side, asset privado `quickdrop_*_x86_64-linux`), instala `~/.local/bin/quickdrop` + launcher `~/.local/bin/quickdrop-waybar` (de `GET /linux/quickdrop-waybar`) e faz patch idempotente do módulo `custom/quickdrop` em `~/.config/waybar/config.jsonc` com backup e restart. Fonte: `scripts/install-linux.sh`, `src/server/index.ts`, `src/server/linux-installer-service.ts`, `src/server/github-release.ts`, `src/server/index.test.ts`, `scripts/install-linux.test.ts`.
- Regra: QuickDrop também oferece um relay de texto em tempo real para colar/copiar texto entre máquinas sem clipboard compartilhado. A web cria sala por `POST /api/text`, compartilha um código curto de 6 caracteres e sincroniza um textarea grande via `WS /api/text/:code/ws`; o snapshot inicial vem de `GET /api/text/:code`. O modelo é last-writer-wins com `version` monotônica, aviso não destrutivo quando chega atualização remota durante edição local pendente, limite padrão de 256 KB por sala, expiração após 12 horas sem clientes e armazenamento só em memória (perde no redeploy). A entrada pode acontecer por host `texto.*`, por `?c=CÓDIGO` na raiz ou por `GET /t`/`GET /t/:code`, que redirecionam para a SPA com `?c=`. Fonte: endpoints internos `/api/text`, `/api/text/:code`, `WS /api/text/:code/ws`, redirects `/t` e `/t/:code`; `src/server/text-session-store.ts`; `src/server/text-session-service.ts`; `src/desktop/TextSession.tsx`.
- Regra: QuickDrop também oferece um relay de texto em tempo real para colar/copiar texto entre máquinas sem clipboard compartilhado. A web cria sala por `POST /api/text`, compartilha um código curto de 6 caracteres e sincroniza um textarea grande via `WS /api/text/:code/ws`; o snapshot inicial vem de `GET /api/text/:code`. O modelo é last-writer-wins com `version` monotônica, aviso não destrutivo quando chega atualização remota durante edição local pendente, limite padrão de 256 KB por sala e expiração após 12 horas sem clientes. Diferente do MVP inicial, o estado da sala agora fica persistido em PostgreSQL e sobrevive a restart/deploy; o processo só mantém o hub de conexões WebSocket em memória. A entrada pode acontecer por host `texto.*`, por `?c=CÓDIGO` na raiz ou por `GET /t`/`GET /t/:code`, que redirecionam para a SPA com `?c=`. Fonte: endpoints internos `/api/text`, `/api/text/:code`, `WS /api/text/:code/ws`, redirects `/t` e `/t/:code`; `src/server/text-rooms-repository.ts`; `src/server/text-session-hub.ts`; `src/server/text-session-service.ts`; `src/desktop/TextSession.tsx`.

## Technical map for future LLMs

Expand All @@ -25,7 +25,7 @@
- Download workflow: `src/server/download-service.ts` rejects missing/deleted, deletes+marks expired, otherwise signs R2 GET and redirects 302.
- Desktop Rust commands: `src-tauri/src/lib.rs` implements `upload_file`, `upload_files`, `read_clipboard_upload_inputs`, `copy_link`, `notify_success`, `dismiss_window`, `uses_native_clipboard_paste`; `upload_files` validates selected paths, creates a temporary `.zip` with unique entry names when there is more than 1 file, uploads that ZIP through the existing single-file backend endpoint, then removes generated temp files. `read_clipboard_upload_inputs` uses `wl-paste --list-types`/`--type` on Linux/Wayland to materialize clipboard image/text as a temporary local file for upload. Config reads `QUICKDROP_API_BASE_URL`; default is `https://quickdrop.eaedave.xyz` on Windows and `http://127.0.0.1:3000` on non-Windows unless the launcher/env overrides it.
- Desktop UI / Web Frontend: `src/desktop/App.tsx` funciona em ambos os ambientes (detectado via `isTauri`). No desktop usa drag-drop events do Tauri, seletor nativo e `Ctrl+V` via `wl-paste` somente quando `uses_native_clipboard_paste` retorna true; na web renderiza uma landing page com hero, card de instalação com abas Windows/Linux (comando + link `Ver script` para `/install.ps1` ou `/install.sh` conforme a aba selecionada via `INSTALL_PLATFORMS`), upload HTML5 por drag-and-drop/input e `Ctrl+V` via Clipboard API/eventos padrão. Mostra progresso real do upload e fase `Criando ZIP...` ao compactar múltiplos arquivos. A barra superior customizada chama `getCurrentWindow().startDragging()` para mover a janela frameless no Tauri.
- Text relay web-only: `src/desktop/main.tsx` desvia para `src/desktop/TextSession.tsx` quando `src/desktop/web-route.ts` detecta host `texto.*`, rota `/t`/`/t/:code` ou `?c=CÓDIGO`; o restante continua em `App.tsx`. `src/desktop/text-client.ts` fala com `POST /api/text`, `GET /api/text/:code` e `WS /api/text/:code/ws`, implementa reconnect com backoff e normaliza payloads sem dependências extras. `TextSession.tsx` mantém textarea local + debounce de ~150 ms, suprime eco próprio por `clientId`, mostra banner de conflito remoto e alterna a classe `quickdrop-text-page` no `<html>`. O backend usa `@fastify/websocket` em `src/server/index.ts`, `TextSessionStore` em memória e os helpers `startTextSessionSweep`/`startTextSessionHeartbeat`; `src/server/index.ts` também redireciona `/t` e `/t/:code` para `/?c=` para a SPA carregar sem fallback especial do `@fastify/static`. Envs novos: `TEXT_SESSION_TTL_HOURS`, `TEXT_SESSION_MAX_KB`, `TEXT_SESSION_CODE_LENGTH`, `TEXT_SESSION_MAX_SESSIONS`, `TEXT_SESSION_MAX_CLIENTS`.
- Text relay web-only: `src/desktop/main.tsx` desvia para `src/desktop/TextSession.tsx` quando `src/desktop/web-route.ts` detecta host `texto.*`, rota `/t`/`/t/:code` ou `?c=CÓDIGO`; o restante continua em `App.tsx`. `src/desktop/text-client.ts` fala com `POST /api/text`, `GET /api/text/:code` e `WS /api/text/:code/ws`, implementa reconnect com backoff e normaliza payloads sem dependências extras. `TextSession.tsx` mantém textarea local + debounce de ~150 ms, suprime eco próprio por `clientId`, mostra banner de conflito remoto e alterna a classe `quickdrop-text-page` no `<html>`. O backend agora separa `src/server/text-session-hub.ts` (clientes WS em memória) de `src/server/text-rooms-repository.ts` (estado persistido em Postgres via Drizzle), rearma salas abertas no restart com `rearmTextRoomsAfterRestart`, e usa `startTextSessionSweep` para marcar expiradas em DB; `src/server/index.ts` continua redirecionando `/t` e `/t/:code` para `/?c=` para a SPA carregar sem fallback especial do `@fastify/static`. Envs: `TEXT_SESSION_TTL_HOURS`, `TEXT_SESSION_MAX_KB`, `TEXT_SESSION_CODE_LENGTH`, `TEXT_SESSION_MAX_SESSIONS`, `TEXT_SESSION_MAX_CLIENTS`.
- Commands: `bun run server:dev`, `bun run server:start`, `bun run db:generate`, `bun run db:check`, `bun run db:migrate`, `bun run cleanup:run`, `bun run desktop:dev`, `bun run desktop:build:web`, `bun run desktop:build`, `bun run desktop:build:windows`, `bun run desktop:package:linux`, `bun run desktop:install`, `bun run desktop:install:local`, `bun run waybar:install`, `bun run local:server:up`, `bun run local:server:stop`, `bun run quickdrop:install`, `bun run quickdrop:install:local`, `bun run typecheck`, `bun test`.
- Waybar install: `scripts/install-desktop.ts` copies `quickdrop`; `scripts/install-waybar-module.ts` installs/refreshes `quickdrop-waybar`, idempotently patches `~/.config/waybar/config.jsonc` with `QUICKDROP_API_BASE_URL=https://quickdrop.eaedave.xyz` by default, backs it up, and restarts Waybar via `omarchy restart waybar` when available. The Tauri window uses compact `380x220` inner size; the Waybar launcher positions it as about `432x272` compositor size by default, supports `QUICKDROP_WINDOW_WIDTH`/`QUICKDROP_WINDOW_HEIGHT` overrides, and re-places the window after launch so Tauri's own initial position cannot leave the top under the bar. Override backend with `QUICKDROP_API_BASE_URL=... bun run waybar:install`; `quickdrop:install` builds+installs the desktop against the remote backend for reboot-safe daily use.
- Windows tray: `src-tauri/Cargo.toml` enables Tauri `tray-icon`/`image-png` and native plugins for clipboard, notification, and autostart. `src-tauri/src/lib.rs` builds the tray only on Windows, uses `icons/icon.png`, positions the default/opened window above the tray/work-area corner instead of at the cursor, hides instead of closes on `dismiss_window`, enables autostart once on first launch, persists `autostart-configured` under the app config dir, and autostarts with `--tray-start`. `scripts/install-windows.ps1` intentionally does not pass `--tray-start` after installation: it waits for NSIS only, then starts the installed `QuickDrop.exe` detached/visible so the PowerShell prompt returns immediately. `src-tauri/tauri.windows.conf.json` enables NSIS bundling and Windows icons.
Expand Down Expand Up @@ -60,4 +60,5 @@
- 2026-06-23: Waybar ficou mais compacta de verdade: o binário Tauri agora usa área interna `380x220`, o launcher posiciona como ~`432x272` e reaplica a posição após a janela existir para não deixar o topo sob a barra; `bun run waybar:install` também instala/atualiza `~/.local/bin/quickdrop-waybar`, não só o JSONC da Waybar. Fontes atualizadas: `src-tauri/src/lib.rs`, `scripts/quickdrop-waybar`, `scripts/install-waybar-module.ts`, `scripts/install-desktop.ts`, `scripts/install-waybar-module.test.ts`, README e LLM context.
- 2026-06-23: Adicionada instalação Linux (Hyprland + Waybar) espelhando o fluxo Windows: novo `scripts/install-linux.sh` servido por `GET /install.sh` (`curl -fsSL https://quickdrop.eaedave.xyz/install.sh | bash`) detecta ambiente, baixa o binário por `GET /linux/latest` e instala `~/.local/bin/quickdrop` + launcher (de `GET /linux/quickdrop-waybar`) com patch idempotente da Waybar via patcher Python embarcado (paridade testada com `install-waybar-module.ts`). Extraído helper compartilhado `src/server/github-release.ts` (usado por `windows-installer-service.ts` e novo `linux-installer-service.ts`); landing com abas Windows/Linux funcionais; `scripts/package-linux-release.ts` + `bun run desktop:package:linux` geram `quickdrop_<versão>_x86_64-linux`, publicado na release `v0.1.1` (latest). Fontes: `src/server/index.ts`, `src/server/index.test.ts`, `scripts/install-linux.sh`, `scripts/install-linux.test.ts`, `scripts/package-linux-release.ts`, `src/desktop/App.tsx`, `src/desktop/input.css`, `Dockerfile`, `package.json`, README e LLM context.
- 2026-06-23: Adicionado relay de texto em tempo real no mesmo backend Fastify usando `@fastify/websocket`, com salas efêmeras em memória (`POST /api/text`, `GET /api/text/:code`, `WS /api/text/:code/ws`), heartbeat/sweep configuráveis por env e testes WS reais em `src/server/text-session-service.test.ts`. No frontend web, `main.tsx` agora roteia para `TextSession.tsx` via `web-route.ts` quando o host é `texto.*`, a rota é `/t`/`/t/:code` ou há `?c=`, e `text-client.ts` faz reconnect/backoff e sincronização do textarea com banner de conflito remoto. Na sequência, `src/server/index.ts` ganhou redirects `GET /t` e `GET /t/:code` para `/?c=` porque o backend serve a SPA de `dist/` e a rota path-based precisava de entrada server-side explícita. Fontes alteradas: `src/server/index.ts`, `src/server/index.test.ts`, `src/server/config.ts`, `src/server/ids.ts`, `src/server/text-session-store.ts`, `src/server/text-session-service.ts`, `src/server/text-session-store.test.ts`, `src/server/text-session-service.test.ts`, `src/server/r2.test.ts`, `src/desktop/main.tsx`, `src/desktop/web-route.ts`, `src/desktop/text-client.ts`, `src/desktop/TextSession.tsx`, `src/desktop/input.css`, `.env.example`, README e LLM context.
- 2026-06-23: PR 1 da fase 2 do relay de texto: o estado das salas saiu do `Map` em memória e passou para PostgreSQL/Drizzle com tabela `text_rooms` (`migrations/002_create_text_rooms.sql`, idempotente, já que `migrate.ts` reaplica todos os `.sql` em cada start). O backend agora separa `TextSessionHub` (clientes WS) de `textRoomsRepository` (código/texto/version/expiração), rearma salas `expires_at = null` após restart e mantém o contrato HTTP/WS do MVP. Fontes alteradas: `src/server/schema.ts`, `src/server/text-rooms-repository.ts`, `src/server/text-session-hub.ts`, `src/server/text-session-service.ts`, `src/server/index.ts`, `migrations/002_create_text_rooms.sql`, testes do relay, README e LLM context.
<!-- business-readme:context:end -->
14 changes: 14 additions & 0 deletions migrations/002_create_text_rooms.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
create table if not exists text_rooms (
code varchar(16) primary key,
text text not null default '',
version integer not null default 0,
pin_hash text,
created_at timestamptz not null,
updated_at timestamptz not null,
expires_at timestamptz,
deleted_at timestamptz
);

create index if not exists text_rooms_expires_idx
on text_rooms (expires_at)
where deleted_at is null;
Loading