---
phase: 20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n
plan: "04"
subsystem: database
tags: [rust, sqlite, github-cli, notes, dedup, optimistic-ui]

# Dependency graph
requires:
  - phase: 20.1.1-change-the-i-button-on-cards-to-show-a-scrollable-history-of
    provides: upsert_notes_for_card diff-keyed on (card_id, note_date, content); NoteEntry.author field; NoteDisplayEntry::from_note_entry
provides:
  - LiveClient resolves GH user login at construction time (gh api user --jq .login)
  - save_note writes optimistic NoteEntry with author = Some("<gh_handle>") eliminating the "You" twin row
  - view_model.rs carries D-07 explanatory comment on the "You" fallback
affects:
  - plan 06 (live-path dedup validation via BUGSWEEPER)
  - any plan touching save_note or NoteEntry author field

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "Resolve external-service identity once at service-object construction time (not per-call)"
    - "Graceful degraded mode: gh unavailable at init -> None field -> fallback rendering path"

key-files:
  created: []
  modified:
    - crates/app/src/live_client.rs
    - crates/app/src/dashboard/view_model.rs

key-decisions:
  - "D-07 dedup strategy: stamp GH handle on optimistic NoteEntry at post time so (card_id, note_date, content) match is guaranteed when GH fetch reconciles via upsert_notes_for_card"
  - "resolve_gh_user_login runs once at LiveClient::new via find_gh() + gh api user --jq .login; fails silently to None"
  - "new_for_test constructor sets gh_user_login: None; no real gh call in tests"

patterns-established:
  - "resolve_gh_user_login: free fn using integrations::github::gh_cli_client::find_gh() + Command::new().args([...]) — no shell interpolation (T-2012-01)"

requirements-completed: [D-07]

# Metrics
duration: 15min
completed: 2026-04-15
---

# Phase 20.1.1.1 Plan 04: Note Dedup Fix Summary

**LiveClient resolves GH user login once at init via `gh api user --jq .login` and stamps it as `author` on optimistic NoteEntry writes, eliminating the "You" / real-handle twin-row bug (D-07)**

## Performance

- **Duration:** ~15 min
- **Started:** 2026-04-15T00:00:00Z
- **Completed:** 2026-04-15T00:15:00Z
- **Tasks:** 2
- **Files modified:** 2

## Accomplishments

- Added `gh_user_login: Option<String>` field to `LiveClient` struct
- Added `resolve_gh_user_login()` free function using `find_gh()` + `gh api user --jq .login` (runs once at construction, never per-save)
- Populated field in `LiveClient::new`; set to `None` in `new_for_test` (no side-effects in tests)
- Changed `save_note` optimistic `NoteEntry` author from `None` to `self.gh_user_login.clone()`
- Added D-07 explanatory comment to `NoteDisplayEntry::from_note_entry` in `view_model.rs`; match arms unchanged

## Task Commits

1. **Task 1 + Task 2: Add gh_user_login resolution + D-07 view_model comment** - `7e9227f` (feat)

## Files Created/Modified

- `crates/app/src/live_client.rs` — struct field, helper fn, constructor population, save_note author fix
- `crates/app/src/dashboard/view_model.rs` — D-07 comment above author_display match arms

## Decisions Made

- Chose `resolve_gh_user_login` as a free function (not a method) so it can be called before the `LiveClient` struct is fully constructed
- Used `integrations::github::gh_cli_client::find_gh()` for binary resolution — consistent with every other gh subprocess call in the codebase
- Kept `new_for_test` with `gh_user_login: None` so existing tests need zero changes and no real network call is made during test runs
- Retained the `"You"` fallback in `view_model.rs` — it is a valid degraded path when gh is unavailable at init, not dead code

## Deviations from Plan

None - plan executed exactly as written.

## Issues Encountered

None. `cargo check -p app` produced only the pre-existing `unused_variables` warning on `assignment.rs:244` (out of scope). All 49 workspace lib tests passed without modification.

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

- D-07 fix complete. The optimistic row now carries the user's GH handle, so `upsert_notes_for_card` finds a (card_id, note_date, content) match when the targeted fetch returns and collapses to exactly one row.
- Live-path dedup validation (posting a note and seeing exactly one row after GH fetch) is deferred to Plan 06 per the plan's verification section.

---
*Phase: 20.1.1.1-phase-20-1-1-gap-closure-recipient-tab-sidebar-coexistence-n*
*Completed: 2026-04-15*
