Skip to content

Commit d56b993

Browse files
committed
docs: update CLAUDE.md and README with current stack and project structure
- Clarify app description: cross-platform file sharing for Macs and PCs on LAN - Update tech stack: Tauri 2, React 19, TypeScript, Tailwind v4, Zustand, zod/mini - Expand quality rules: add cargo check, pnpm typecheck, lint, and test requirements - Enforce pnpm as sole package manager in documentation - Update cross-platform compatibility table with correct cache paths, logs, settings locations - Add clipboard operations (read/write) for both macOS and Windows - Clarify Tauri plugins in use: dialog, opener, notification - Remove SQLite from stack (replaced with JSON file persistence) - Remove React Query from stack (no longer in use) - Restructure directory tree with accurate current file organization - Add test directory with setup and fixtures - Remove subagent workflow section (not applicable to current process) - Simplify execution mode guidance for small, validatable changes - Truncate incomplete roadmap section for clarity
1 parent 1638a8f commit d56b993

2 files changed

Lines changed: 197 additions & 141 deletions

File tree

CLAUDE.md

Lines changed: 171 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,65 @@
11
# Xhare — CLAUDE.md
22

3-
App desktop de compartilhamento de arquivos em tempo real entre PCs na mesma rede local (LAN).
4-
Código em `~/js/xhare/app/`.
3+
App desktop de compartilhamento de arquivos em tempo real entre Macs e PCs na
4+
mesma LAN. Código em `~/js/xhare/app/`. Tauri 2 (Rust + WebView) + React 19 +
5+
TypeScript + Tailwind v4.
56

67
---
78

89
## Como trabalhar neste projeto
910

1011
### Modo de execução
11-
Sempre trabalhe em **versões pequenas e validáveis**. Cada versão tem escopo fechado, testes passando e app rodando antes de avançar.
12-
13-
### Subagentes
14-
Use subagentes em paralelo sempre que possível:
15-
- **Frontend agent** — implementa componentes React/UI
16-
- **Rust agent** — implementa lógica Tauri (discovery, transfer, cache, tray)
17-
- **Test agent** — escreve testes automatizados pra cada módulo
18-
- **Review agent** — revisa código gerado por outros agentes (qualidade, padrões, edge cases)
19-
- **QA agent** — testa a UI, fluxos, estados de erro, edge cases visuais
12+
Sempre trabalhe em **versões pequenas e validáveis**. Cada mudança tem escopo
13+
fechado, tests passando e app rodando antes de avançar pra próxima.
2014

2115
### Regras de qualidade
22-
- **Nunca declare uma versão pronta sem:** testes passando + TypeScript sem erros + app rodando
23-
- **Zero `any` no TypeScript** — tipe tudo corretamente
24-
- **Zero code smells** — sem duplicação, sem funções gigantes, sem lógica misturada com UI
25-
- **Componente puro = sem efeito colateral** — hooks e stores ficam fora do JSX
26-
- **Cada arquivo faz uma coisa só**
27-
- Rode `pnpm build` e `pnpm typecheck` antes de marcar qualquer coisa como concluída
16+
- **Nunca declare uma mudança pronta sem:** tests passando + TypeScript sem
17+
erros + `cargo check` limpo + app rodando.
18+
- **Zero `any` no TypeScript** — tipe tudo corretamente.
19+
- **Zero code smells** — sem duplicação, sem funções gigantes, sem lógica
20+
misturada com UI.
21+
- **Cada arquivo faz uma coisa só.**
22+
- Rode `pnpm typecheck`, `pnpm lint`, `pnpm test` antes de marcar como concluído.
23+
24+
### Package manager
25+
**SEMPRE `pnpm`.** Nunca `bun`, `npm` ou `yarn` — o lockfile é `pnpm-lock.yaml`
26+
e o CI assume isso.
2827

2928
---
3029

3130
## Compatibilidade obrigatória
3231

33-
O app **deve rodar identicamente em macOS e Windows**. Nunca use APIs exclusivas de um SO sem o equivalente no outro.
32+
O app **deve rodar identicamente em macOS e Windows**. Nunca use APIs
33+
exclusivas de um SO sem o equivalente no outro.
3434

3535
| Concern | macOS | Windows |
3636
|---|---|---|
37-
| Cache | `~/Library/Caches/Xhare/` | `%APPDATA%\Xhare\cache\` |
37+
| Cache | `~/Library/Caches/Xhare/` | `%LOCALAPPDATA%\Xhare\` |
38+
| Logs | `~/Library/Application Support/com.felipe.xhare/logs/` | `%APPDATA%\com.felipe.xhare\logs\` |
39+
| Settings | `~/Library/Application Support/com.felipe.xhare/settings.json` | `%APPDATA%\com.felipe.xhare\settings.json` |
3840
| Tray | Menu bar (top) | System tray (bottom right) |
39-
| Abrir arquivo | `open` | `explorer` / `start` |
40-
| Separador de path | `/` | use `path::join` no Rust, nunca hardcode |
41-
| Fonte | SF Pro (sistema) | Segoe UI (sistema) |
41+
| Abrir path | `open` | `explorer` / `start` |
42+
| Reveal in folder | `open -R` | `explorer /select,` |
43+
| Clipboard files (read) | `osascript` + `NSPasteboard` via AppleScriptObjC | `Get-Clipboard -Format FileDropList` (PowerShell) |
44+
| Clipboard files (write) | `NSPasteboard.writeObjects:[NSURL]` | `System.Windows.Forms.Clipboard.SetFileDropList` |
4245
| Notificação | macOS Notification Center | Windows Toast |
46+
| Separador de path | `/` | use `path::join` no Rust, nunca hardcode |
4347

44-
No Rust: use `dirs` crate pra caminhos de sistema. No frontend: Tauri abstrai tudo — nunca acesse paths diretamente no TypeScript.
48+
No Rust: use `dirs` crate pra caminhos de sistema. No frontend: Tauri abstrai
49+
tudo — nunca acesse paths diretamente no TypeScript.
4550

4651
---
4752

4853
## Stack
4954

50-
- **Shell:** Tauri 2 (Rust + WebView)
51-
- **Frontend:** React 19 + TypeScript + Tailwind CSS v4
52-
- **Package manager:** pnpm
53-
- **State:** Zustand (global) · Context API (local/subtree) · React Query (async/Tauri invokes)
54-
- **UI primitivos:** Radix UI + Lucide React
55-
- **DB local:** SQLite via `rusqlite` (metadata, peers, settings)
56-
- **Plataformas MVP:** macOS + Windows
55+
- **Shell:** Tauri 2 (Rust + WebView). Plugins: `dialog`, `opener`, `notification`.
56+
- **Frontend:** React 19 + TypeScript + Tailwind CSS v4.
57+
- **State:** Zustand. Nada de React Query (removido — não havia uso).
58+
- **Forms:** react-hook-form + `zod/mini` (não a `zod` cheia).
59+
- **UI:** Radix UI primitives + Lucide icons + Sonner (toasts).
60+
- **Persistência local:** arquivos JSON em config dir (settings) e logs em
61+
arquivos diários. Sem SQLite — overkill pra escopo atual.
62+
- **Plataformas:** macOS + Windows.
5763

5864
---
5965

@@ -62,114 +68,127 @@ No Rust: use `dirs` crate pra caminhos de sistema. No frontend: Tauri abstrai tu
6268
```
6369
app/
6470
├── src/
65-
│ ├── components/ ← primitivos de UI reutilizáveis (Button, Input, Dialog, etc.)
71+
│ ├── components/ ← primitivos: Button, Dialog, Toast, Tooltip, AlertDialog, …
6672
│ ├── features/
67-
│ │ ├── devices/ ← DeviceList, useDevices, devicesStore
68-
│ │ ├── file-feed/ ← FileFeed, FileRow, FileRowActions, DragOverlay, useFiles, filesStore
69-
│ │ └── settings/ ← SettingsForm, settingsStore
70-
│ ├── hooks/ ← hooks compartilhados (useHotkey, useDragDrop, useClipboard)
71-
│ ├── services/ ← wrappers de Tauri commands (mock em V1, real em V2+)
72-
│ ├── stores/ ← stores Zustand que cruzam features
73-
│ ├── types/ ← tipos compartilhados
74-
│ ├── mock/ ← dados mockados para V1
75-
│ └── utils/ ← helpers (cn, format, etc.)
73+
│ │ ├── devices/ ← DeviceList, DeviceItem, useDevices, NewDeviceDialog
74+
│ │ ├── file-feed/ ← FileFeed, FileRow, BulkActionBar, DragOverlay, useTransferSubscription
75+
│ │ ├── logs/ ← LogViewer
76+
│ │ └── settings/ ← SettingsForm, settingsSchema
77+
│ ├── hooks/ ← useClipboardPaste, useDragDrop, useNativeDragDrop,
78+
│ │ useCopySelectedShortcut, useSelectAllShortcut,
79+
│ │ useUnreadBadge, useWindowDrag, useWindowControls,
80+
│ │ usePlatform, useLocalIp
81+
│ ├── services/ ← wrappers de Tauri commands (files, transfer, devices,
82+
│ │ network, settings, logs, pickFolder)
83+
│ ├── stores/ ← Zustand (filesStore, devicesStore, settingsStore,
84+
│ │ connectionStore)
85+
│ ├── types/ ← Device, SharedFile, Settings
86+
│ ├── utils/ ← cn, formatSize, fileType, timeAgo, uniqueName
87+
│ ├── test/ ← setup.ts (mocks de Tauri), fixtures, renderWithTooltip
88+
│ └── App.tsx
7689
├── src-tauri/
7790
│ └── src/
78-
│ ├── main.rs
79-
│ ├── discovery.rs ← mDNS + IP manual
80-
│ ├── transfer.rs ← TCP chunks + progress + cancel
81-
│ ├── cache.rs ← cache em disco + TTL
82-
│ ├── tray.rs ← ícone, menu, drop target, badge
83-
│ └── hotkeys.rs ← atalhos globais
91+
│ ├── lib.rs ← entrypoint, invoke_handler, window event handling
92+
│ ├── main.rs ← thin wrapper
93+
│ ├── discovery.rs ← mDNS + ARP + heartbeat reconciliation
94+
│ ├── transfer.rs ← TCP protocol, folder zip, clipboard ops, cache cleanup
95+
│ ├── lan_scan.rs ← cross-platform `arp -a` parser
96+
│ ├── settings.rs ← JSON settings (atomic writes)
97+
│ ├── tray.rs ← native tray icon + unread badge
98+
│ └── logger.rs ← file + terminal logger with daily rotation
99+
└── scripts/
100+
└── release.mjs ← `pnpm release:patch|minor|major`
84101
```
85102

86103
---
87104

88-
## Roadmap de versões
89-
90-
### V1 — UI completa com mocks ← ATUAL
91-
**Objetivo:** app visualmente completo, todos os estados funcionando, sem Rust real.
92-
93-
Escopo:
94-
- [ ] Feed de arquivos (`FileFeed` + `FileRow`) com mock de dados
95-
- [ ] Estados: vazio, novo/não-lido, lido, enviando (progress), recebendo (progress), erro
96-
- [ ] Hover actions por linha: Salvar · Abrir · Copiar · Mostrar · Descartar (recebido) / Reenviar · Cancelar (enviado)
97-
- [ ] `DragOverlay` — overlay fullscreen ao arrastar arquivo pra janela
98-
- [ ] `SettingsDialog` completo — pasta destino + TTL do cache
99-
- [ ] `NewDeviceDialog` com validação de IP (regex)
100-
- [ ] Sidebar com estados: conectando, sem wifi, sem dispositivos, lista de dispositivos
101-
- [ ] Zustand stores: `filesStore`, `devicesStore`, `settingsStore`
102-
- [ ] `services/` com mock que simula eventos Tauri (receber arquivo, progresso, etc.)
103-
- [ ] Testes: cada componente, cada store, cada hook
104-
105-
**Done when:** `pnpm build` passa, zero erros TypeScript, todos os estados visíveis no app.
106-
107-
---
108-
109-
### V2 — Descoberta real (Rust + mDNS)
110-
- mDNS via crate `mdns-sd`
111-
- Tauri commands: `get_devices`, `add_device_by_ip`, `remove_device`
112-
- Tauri events: `device-discovered`, `device-lost`, `device-status-changed`
113-
- Substituir mock de devices por dados reais
114-
- Testes de integração Rust
115-
116-
**Done when:** dois PCs na mesma rede se descobrem automaticamente.
105+
## Status
106+
107+
Tudo abaixo está **implementado e funcionando** (V0.1.0 — primeira release):
108+
109+
- **Discovery:** mDNS (`_xhare._tcp.local.`) via `mdns-sd` + ARP fallback +
110+
TCP heartbeat. Host claim usa `xhare-<hostname>.local.` pra evitar conflito
111+
com o LocalHostName do macOS (que bumpa pra `-1`, `-2`, … quando colide).
112+
IP picker prefere o /24 da própria máquina (Windows multi-NIC).
113+
- **Transferência:** TCP chunks de 256KB + CRC32 + JSON header. Frontend gera
114+
o UUID **antes** do invoke pra evitar race com eventos. Folder → auto-zip
115+
com progresso real (pré-scan + emit por bytes lidos).
116+
- **UI completa:** drag-drop, paste (clipboard files via Rust + inline blobs),
117+
multi-select com bottom action bar, Cmd/Ctrl+A toggle, Cmd/Ctrl+C copia
118+
arquivo real pro clipboard, browser-style name dedupe (`notes (1).txt`),
119+
thumbnails reservados pra futuro.
120+
- **Tray + notifications:** menu bar (macOS) / system tray (Windows). Fechar a
121+
janela esconde + flipa pra `ActivationPolicy::Accessory` no macOS (some do
122+
dock). Notificação OS suprimida quando janela focada. Batch debounce 1.5s.
123+
- **Logs:** file logger com rotação diária, viewer in-app, limpeza automática
124+
>2 dias.
125+
126+
**Roadmap futuro** (não-prioridade):
127+
- Thumbnails de imagem/vídeo no feed
128+
- Pause / resume / cancel mid-transfer
129+
- Hotkey global Cmd/Ctrl+Shift+X (pulado a pedido do usuário)
130+
- Drop no ícone do tray (hard cross-platform — pulado)
131+
- Code signing + notarização
117132

118133
---
119134

120-
### V3 — Transferência real (Rust TCP)
121-
- Protocolo TCP com chunks de 256KB + checksum + resume
122-
- Cache em `~/Library/Caches/Xhare/` (macOS) / `%APPDATA%\Xhare\cache\` (Windows)
123-
- TTL: limpeza automática baseada em `settingsStore.ttl`
124-
- Tauri events: `transfer-progress`, `transfer-complete`, `transfer-error`
125-
- Drag-drop dispara envio real
126-
- Paste de clipboard (imagem/arquivo)
127-
128-
**Done when:** arquivo vai de um PC pro outro com progress real.
129-
130-
---
135+
## Decisões de UX
131136

132-
### V4 — Tray + notificações + hotkeys
133-
- Tray nativo (macOS menu bar / Windows system tray)
134-
- Drop target no ícone do tray
135-
- Badge quando há arquivos não lidos
136-
- Notificação nativa ao receber arquivo
137-
- Hotkey global `Cmd/Ctrl+Shift+X` pra abrir janela
137+
- **Broadcast automático** — arquivo enviado vai pra todos os peers online.
138+
- **Fire and forget** — quem está offline não recebe; lista mostra com bolinha
139+
cinza.
140+
- **Cache, não disco** — arquivo recebido fica em cache até o usuário clicar
141+
"Salvar". Cache é wipe no exit do app.
142+
- **Não lido** — fundo levemente destacado + borda esquerda azul 2px.
143+
- **Hover actions** — botões de ação aparecem alinhados à direita no hover da
144+
linha; em modo de seleção (>=1 checkbox marcado), action bar flutuante
145+
aparece no rodapé com Salvar/Apagar/Limpar.
146+
- **Notificação suprimida quando focado** — se janela está visível+focada,
147+
Rust skipa o OS notification. Em-app toast também só aparece se desfocado.
148+
- **Batch de 1.5s** — receber 10 arquivos seguidos não gera 10 notifs; agrupa
149+
em "Felipe enviou 10 arquivos".
138150

139151
---
140152

141-
## Design
142-
143-
Arquivo Figma: **"Design Xhare desktop app"** (acessível via Figma MCP `figma-desktop`).
153+
## Comandos
144154

145-
Ao implementar UI:
146-
1. Use a skill `figma:figma-implement-design` ANTES de codificar qualquer tela
147-
2. Leia variáveis de design, estrutura do frame e tire screenshot via MCP
148-
3. Implemente pixel-perfect, depois peça ao Review agent validar contra o Figma
155+
```bash
156+
# Dev
157+
pnpm tauri dev # app completo com hot reload
158+
pnpm dev # só Vite (sem Tauri)
159+
pnpm build # build de produção (frontend)
160+
pnpm tauri build # gera .dmg / .msi locais
161+
162+
# Verificação
163+
pnpm typecheck # tsc --noEmit
164+
pnpm lint # eslint .
165+
pnpm lint:fix # eslint . --fix
166+
pnpm test # vitest run
167+
pnpm test:watch # vitest em watch
168+
169+
# Release (script automatizado)
170+
pnpm release:patch # 0.1.0 → 0.1.1
171+
pnpm release:minor # 0.1.5 → 0.2.0
172+
pnpm release:major # 0.4.2 → 1.0.0
173+
```
149174

150175
---
151176

152-
## Decisões de UX
153-
154-
- **Broadcast automático** — arquivo enviado vai pra todos os peers online
155-
- **Fire and forget** — quem estava offline não recebe; mostra `⏸ offline` na linha
156-
- **Cache, não disco** — arquivo recebido fica em cache até o usuário escolher Salvar
157-
- **Não lido** — linha com fundo levemente destacado + borda esquerda 2px azul
158-
- **Lido** — linha escura, texto cinza
159-
- **Hover actions** — ações aparecem alinhadas à direita no hover da linha
177+
## Release process
160178

161-
---
179+
`pnpm release:<kind>` em `scripts/release.mjs`:
162180

163-
## Comandos
181+
1. Valida ambiente — recusa se não está em `main`, working tree suja, ou local
182+
desincado com `origin/main`.
183+
2. Bumpa versão em `package.json`, `tauri.conf.json`, `Cargo.toml`, refresca
184+
`Cargo.lock`.
185+
3. Commit `release: vX.Y.Z` + tag anotada + push.
186+
4. Tag push dispara `.github/workflows/release.yml` que builda `.dmg`
187+
universal (macOS) + `.msi` (Windows) em paralelo e publica GitHub Release.
164188

165-
```bash
166-
pnpm dev # inicia app em modo dev
167-
pnpm build # build de produção
168-
pnpm tauri dev # alias pro dev com hot reload
169-
pnpm typecheck # type check sem build (tsc --noEmit)
170-
pnpm test # roda testes unitários (vitest)
171-
pnpm test:watch # vitest em modo watch
172-
```
189+
CI separado em `.github/workflows/ci.yml` roda em todo push/PR pra `main`:
190+
typecheck + lint + tests no frontend, `cargo check` + `cargo clippy -D
191+
warnings` no Rust.
173192

174193
---
175194

@@ -178,30 +197,54 @@ pnpm test:watch # vitest em modo watch
178197
```typescript
179198
// src/types/Device.ts
180199
type Device = {
200+
id: string // self:<fullname> | mdns:<fullname> | lan:<ip> | manual:<ip>
181201
name: string
182202
address: string
183203
status: 'ONLINE' | 'OFFLINE'
204+
isSelf: boolean
184205
}
185206

186207
// src/types/SharedFile.ts
187-
type FileStatus = 'sending' | 'receiving' | 'sent' | 'received' | 'error'
208+
type FileStatus = 'zipping' | 'sending' | 'receiving' | 'sent' | 'received' | 'error'
209+
type FileKind = 'file' | 'folder' | 'image' | 'video'
188210

189211
type SharedFile = {
190212
id: string
191-
name: string
192-
size: number // bytes
193-
type: 'file' | 'folder' | 'image' | 'video'
213+
name: string // display name (pode ter "(1)" pra dedupe)
214+
size: number // bytes
215+
kind: FileKind
194216
extension?: string
195-
thumbnailUrl?: string // só pra imagem/vídeo
196-
from: string // device name ou 'você'
217+
thumbnailUrl?: string
218+
from: string // device name ou 'você'
219+
fromAddress?: string // IP capturado no receive (fallback de match no FromCell)
197220
sentAt: Date
198221
status: FileStatus
199-
progress?: number // 0-100, só quando sending/receiving
200-
speedBps?: number // bytes/s, só quando sending/receiving
201-
deliveredTo?: string[] // devices que confirmaram recebimento
202-
failedTo?: string[] // devices offline no momento
222+
progress?: number // 0-100 durante zipping/sending/receiving
223+
speedBps?: number // só durante transfer
203224
isRead: boolean
204225
isPinned: boolean
205-
cachedPath?: string // caminho no cache local
226+
cachedPath?: string // cache OS, set on received
227+
savedPath?: string // após "Salvar"
228+
sourcePath?: string // source do envio (usado pra resend)
206229
}
207230
```
231+
232+
---
233+
234+
## Gotchas conhecidos
235+
236+
- **mDNS hostname bumping** — registre como `xhare-<host>.local.`, nunca `<host>.local.`. macOS Bonjour fica numa briga eterna.
237+
- **Race UUID frontend↔Rust** — gere o id no JS antes do invoke; senão eventos
238+
podem chegar antes do `addFile` e ficam órfãos (linha trava em 0%).
239+
- **WebKit drag** — `getCurrentWindow().startDragging()` precisa ser chamada
240+
**sincronamente** do mousedown handler; nada de `await` antes (microtask
241+
break mata o gesto). Static import only.
242+
- **Tauri v2 tray** — não expõe drop target. Drop no ícone do tray não é
243+
viável sem código nativo pesado (objc2 + win32 hacks).
244+
- **Notificação OS em dev** — vem com nome/ícone do Terminal (macOS) ou
245+
PowerShell (Windows) porque o processo herda do parent. Em build instalado
246+
(.dmg/.msi) vem como "Xhare" corretamente.
247+
- **Tailwind v4 sintaxe** — `p-0!` (sufixo) e não `!p-0` (prefixo).
248+
- **Tailwind v4 + .gitignore** — content auto-detection respeita .gitignore;
249+
se houver entrada conflitando com pasta real (ex: `logs/`), classes podem
250+
sumir. Solução: `@source "./**/*.{ts,tsx,html}"` no CSS.

0 commit comments

Comments
 (0)