docs(04): create phase plan for notifications et i3bar
This commit is contained in:
248
.planning/phases/04-notifications-et-i3bar/04-02-PLAN.md
Normal file
248
.planning/phases/04-notifications-et-i3bar/04-02-PLAN.md
Normal file
@@ -0,0 +1,248 @@
|
||||
---
|
||||
phase: 04-notifications-et-i3bar
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified: [i3bar.go, i3bar_test.go, main.go]
|
||||
autonomous: false
|
||||
requirements: [I3-03]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "vmux i3bar affiche le statut des sessions en format i3bar JSON sur stdout"
|
||||
- "Le format est vmux: auth[!] portal[W] neia[I] avec noms courts"
|
||||
- "Quand aucune session n'attend: vmux: all working (3)"
|
||||
- "Couleur rouge si >= 1 session attend, vert sinon"
|
||||
- "Le widget wrap i3status pour garder les infos systeme"
|
||||
artifacts:
|
||||
- path: "i3bar.go"
|
||||
provides: "I3BarBlock, formatI3BarBlocks, shortName, runI3Bar"
|
||||
exports: ["I3BarBlock", "formatI3BarBlocks", "runI3Bar"]
|
||||
- path: "i3bar_test.go"
|
||||
provides: "Tests formatage i3bar"
|
||||
key_links:
|
||||
- from: "i3bar.go"
|
||||
to: "protocol.go"
|
||||
via: "formatI3BarBlocks consomme []SessionInfo"
|
||||
pattern: "formatI3BarBlocks.*SessionInfo"
|
||||
- from: "main.go"
|
||||
to: "i3bar.go"
|
||||
via: "case i3bar appelle runI3Bar"
|
||||
pattern: "case \"i3bar\""
|
||||
---
|
||||
|
||||
<objective>
|
||||
Widget i3bar affichant le statut des sessions vmux en temps reel.
|
||||
|
||||
Purpose: L'utilisateur voit dans sa barre i3 quelles sessions Claude Code ont besoin de lui, sans ouvrir vmux.
|
||||
Output: i3bar.go avec formatage et boucle i3bar protocol, sous-commande `vmux i3bar`, wrapping i3status.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/04-notifications-et-i3bar/04-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Existing code contracts needed by executor -->
|
||||
|
||||
From protocol.go:
|
||||
```go
|
||||
type SessionInfo struct {
|
||||
PID int `json:"pid"`
|
||||
SessionID string `json:"session_id"`
|
||||
Cwd string `json:"cwd"`
|
||||
GitBranch string `json:"git_branch"`
|
||||
State string `json:"state"`
|
||||
Preview string `json:"preview"`
|
||||
Workspace string `json:"workspace"`
|
||||
Label string `json:"label,omitempty"`
|
||||
WaitType string `json:"wait_type,omitempty"`
|
||||
WaitingSince *time.Time `json:"waiting_since,omitempty"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Action string `json:"action"`
|
||||
Args json.RawMessage `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Sessions []SessionInfo `json:"sessions,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
From client.go (inferred from main.go usage):
|
||||
```go
|
||||
func NewClient(sockPath string) *Client
|
||||
func (c *Client) Send(req Request) (*Response, error)
|
||||
```
|
||||
|
||||
From main.go:
|
||||
```go
|
||||
// CLI dispatch: switch filteredArgs[0] { case "list", "switch", "label", "stop", "daemon" }
|
||||
// runDaemon(sockPath) starts the daemon
|
||||
// printUsage() shows help
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Formatage i3bar et tests</name>
|
||||
<files>i3bar.go, i3bar_test.go</files>
|
||||
<read_first>
|
||||
- protocol.go (SessionInfo struct)
|
||||
- display.go (pattern formatage existant, shortName si deja defini)
|
||||
- notify.go (shortName si cree en plan 01, sinon le creer ici)
|
||||
</read_first>
|
||||
<behavior>
|
||||
- TestFormatI3BarBlocks_MixedStates: 3 sessions (Working, Needs Input, Idle) -> full_text="vmux: auth[!] portal[W] neia[I]", color="#ff0000"
|
||||
- TestFormatI3BarBlocks_AllWorking: 3 sessions Working -> full_text="vmux: all working (3)", color="#00ff00" (per D-07)
|
||||
- TestFormatI3BarBlocks_NoSessions: [] -> full_text="vmux: no sessions", color="#00ff00"
|
||||
- TestFormatI3BarBlocks_ColorRed: >= 1 Needs Input -> color="#ff0000" (per D-08)
|
||||
- TestFormatI3BarBlocks_ColorGreen: 0 Needs Input -> color="#00ff00" (per D-08)
|
||||
- TestFormatI3BarBlocks_UsesLabel: session avec Label="auth" -> "auth[W]" pas le cwd (per D-06)
|
||||
- TestFormatI3BarBlocks_UsesCwdBase: session sans label, cwd="/home/pierre/Code/vibe/vmux" -> "vmux[W]" (per D-06)
|
||||
- TestFormatI3BarBlocks_NeedsInputMarker: session Needs Input -> "[!]" (per D-06)
|
||||
- TestFormatI3BarBlocks_IdleMarker: session Idle -> "[I]" (per D-06)
|
||||
</behavior>
|
||||
<action>
|
||||
Creer 2 fichiers. TDD: tests d'abord.
|
||||
|
||||
**i3bar.go:**
|
||||
- `I3BarBlock` struct avec champs JSON: `full_text`, `short_text` (omitempty), `color`, `name`, `markup` (="none").
|
||||
- `formatI3BarBlocks(sessions []SessionInfo) []I3BarBlock`:
|
||||
- Iterer les sessions. Pour chaque, `shortName(s)` (fonction de notify.go ou locale si plan 01 pas encore execute) + suffixe selon State:
|
||||
- "Needs Input" -> `[!]`, set `hasWaiting = true`
|
||||
- "Working" -> `[W]`
|
||||
- "Idle" -> `[I]`
|
||||
- Si `!hasWaiting && len(sessions) > 0`: text = `vmux: all working (N)` per D-07
|
||||
- Si `len(sessions) == 0`: text = `vmux: no sessions`
|
||||
- Sinon: text = `vmux: ` + parts jointes par espace per D-06
|
||||
- Color: `#ff0000` si hasWaiting, `#00ff00` sinon per D-08
|
||||
- Retourner un seul bloc `[]I3BarBlock{{FullText: text, Color: color, Name: "vmux"}}`
|
||||
|
||||
**Note fichiers partages avec plan 01:** Si plan 01 est execute en parallele et definit `shortName` dans notify.go, cette fonction sera deja disponible. Sinon, definir `shortName` dans i3bar.go (et deplacer plus tard si doublon). Le plan 01 a la priorite sur shortName.
|
||||
|
||||
**Pitfall stdout buffering (de RESEARCH):** Pas concerne dans cette tache (formatage pur). Gere en Task 2.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/pierre/Code/vibe/vmux && nix-shell -p go --run "go test -run 'TestFormatI3Bar' -count=1 -v"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- grep -q "type I3BarBlock struct" i3bar.go
|
||||
- grep -q "func formatI3BarBlocks" i3bar.go
|
||||
- grep -q "all working" i3bar.go
|
||||
- grep -q "#ff0000" i3bar.go
|
||||
- grep -q "#00ff00" i3bar.go
|
||||
- grep -q "TestFormatI3BarBlocks" i3bar_test.go
|
||||
</acceptance_criteria>
|
||||
<done>formatI3BarBlocks produit le format compact D-06/D-07/D-08. Tous les tests passent.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Boucle i3bar protocol et CLI</name>
|
||||
<files>i3bar.go, main.go</files>
|
||||
<read_first>
|
||||
- i3bar.go (I3BarBlock et formatI3BarBlocks crees en Task 1)
|
||||
- main.go (switch CLI, sockPath, EnsureDaemon pattern)
|
||||
- protocol.go (Request/Response)
|
||||
- 04-RESEARCH.md (pattern i3bar protocol, i3status wrapping, pitfall stdout buffering)
|
||||
</read_first>
|
||||
<action>
|
||||
**i3bar.go — runI3Bar(sockPath, i3statusCmd string):**
|
||||
Boucle infinie qui parle le protocole i3bar v1 sur stdout. Deux modes selon i3statusCmd:
|
||||
|
||||
**Mode standalone (i3statusCmd vide):**
|
||||
1. Ecrire header: `{"version":1}` + newline
|
||||
2. Ecrire `[` + newline
|
||||
3. Boucle: query daemon via socket (`action: "list"`), formater avec `formatI3BarBlocks`, json.Marshal le tableau de blocs, ecrire `,` (sauf premier) + ligne JSON + newline. `os.Stdout.Write()` + flush explicite (per pitfall stdout buffering de RESEARCH).
|
||||
4. Sleep 2 secondes entre chaque iteration.
|
||||
5. Si le daemon est injoignable, afficher bloc `vmux: daemon offline` en gris.
|
||||
|
||||
**Mode wrap i3status (i3statusCmd non-vide):**
|
||||
1. Lancer i3statusCmd en subprocess (`exec.Command(i3statusCmd)`)
|
||||
2. Lire et forwarder le header JSON (`{"version":1}`)
|
||||
3. Lire et forwarder le `[`
|
||||
4. Pour chaque ligne lue de i3status: trim le `,` initial, json.Unmarshal en `[]I3BarBlock`, prepend le bloc vmux (query daemon), re-Marshal, ecrire avec `,` prefix
|
||||
5. Si i3status se termine, continuer en mode standalone
|
||||
|
||||
**main.go:**
|
||||
- Ajouter case "i3bar" dans le switch CLI:
|
||||
- `EnsureDaemon(sockPath)` pour s'assurer que le daemon tourne
|
||||
- Detecter i3statusCmd: chercher `i3status` dans PATH. Si trouve, utiliser comme wrapping. Sinon, mode standalone.
|
||||
- Appeler `runI3Bar(sockPath, i3statusCmd)`
|
||||
- Ajouter dans printUsage: ` i3bar Output i3bar JSON (use as status_command in i3 config)`
|
||||
|
||||
**Pitfall stdout buffering:** Utiliser `os.Stdout.Write(data)` suivi de `os.Stdout.Sync()` ou un `bufio.Writer` avec Flush() apres chaque ligne complete.
|
||||
|
||||
**Pitfall i3status wrapping (de RESEARCH):** i3status peut prefixer les lignes de blocs avec `,`. Le parser doit les trimmer avant json.Unmarshal.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/pierre/Code/vibe/vmux && nix-shell -p go --run "go build -o /dev/null . && echo 'build OK'"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- grep -q "func runI3Bar" i3bar.go
|
||||
- grep -q "version.*1" i3bar.go
|
||||
- grep -q 'case "i3bar"' main.go
|
||||
- grep -q "i3bar" main.go
|
||||
- grep -q "i3status" i3bar.go
|
||||
- grep -q "Stdout" i3bar.go
|
||||
</acceptance_criteria>
|
||||
<done>vmux i3bar fonctionne en mode standalone et wrap i3status. Le protocole i3bar v1 est respecte (header + array JSON). La sous-commande est accessible.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 3: Verification visuelle du widget i3bar</name>
|
||||
<files>i3bar.go, main.go</files>
|
||||
<action>
|
||||
Verification humaine du widget i3bar. Pas de code a ecrire.
|
||||
|
||||
Ce qui a ete construit: Widget i3bar vmux avec format compact, couleurs, wrapping i3status.
|
||||
|
||||
Etapes de verification:
|
||||
1. Compiler: `nix-shell -p go --run "go build -o vmux ."`
|
||||
2. S'assurer que le daemon tourne: `./vmux list`
|
||||
3. Tester le mode standalone: `./vmux i3bar` (Ctrl+C apres quelques lignes)
|
||||
- Verifier: header `{"version":1}`, puis `[`, puis lignes JSON avec bloc vmux
|
||||
- Format attendu: `vmux: auth[!] portal[W]` ou `vmux: all working (N)`
|
||||
4. Configurer i3bar (optionnel):
|
||||
- Dans `~/.config/i3/config`, remplacer `status_command i3status` par `status_command /chemin/vers/vmux i3bar`
|
||||
- Recharger i3: `i3-msg reload`
|
||||
- Verifier que le bloc vmux apparait dans la barre avec les infos systeme
|
||||
5. Verifier les couleurs: rouge si session attend, vert sinon
|
||||
</action>
|
||||
<verify>User confirms: "approved" or describes issues</verify>
|
||||
<done>L'utilisateur a valide visuellement que le widget i3bar fonctionne correctement dans sa barre i3.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `nix-shell -p go --run "go test ./... -count=1 -race"` passe sans erreur
|
||||
- `nix-shell -p go --run "go build -o /dev/null ."` compile sans erreur
|
||||
- `vmux i3bar` produit du JSON i3bar valide sur stdout
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
1. `vmux i3bar` produit un flux JSON i3bar protocol v1 valide
|
||||
2. Le format compact respecte D-06 (noms courts + suffixes [!] [W] [I])
|
||||
3. "all working (N)" quand aucune session n'attend (D-07)
|
||||
4. Couleurs rouge/vert selon urgence (D-08)
|
||||
5. Le widget wrap i3status pour conserver les infos systeme
|
||||
6. Tous les tests passent avec -race
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-notifications-et-i3bar/04-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user