diff --git a/openspec/changes/profile-avatars/tasks.md b/openspec/changes/profile-avatars/tasks.md
index 1a6541d..ca413bc 100644
--- a/openspec/changes/profile-avatars/tasks.md
+++ b/openspec/changes/profile-avatars/tasks.md
@@ -2,16 +2,16 @@
## 1. Avatar component
-- [ ] 1.1 Create `src/lib/components/Avatar.svelte`: renders the avatar image for a handle (resolution URL per design D1, size prop), falling back to the textual handle presentation on load error or missing handle
-- [ ] 1.2 Add the popover variant for provenance use: focusable trigger, popover with handle on hover/focus (tap-toggle on touch), DID via accessible label/title
+- [x] 1.1 Create `src/lib/components/Avatar.svelte`: renders the avatar image for a handle (resolution URL per design D1, size prop), falling back to the textual handle presentation on load error or missing handle
+- [x] 1.2 Add the popover variant for provenance use: focusable trigger, popover with handle on hover/focus (tap-toggle on touch), DID via accessible label/title
## 2. Integration
-- [ ] 2.1 Nav "who" chip in `src/routes/(app)/+layout.svelte`: avatar beside the handle, keeping the existing `title={did}`
-- [ ] 2.2 Categorization provenance in `src/routes/(app)/ledger/+page.svelte`: replace actor handle text with the popover avatar (fallback preserves today's text)
+- [x] 2.1 Nav "who" chip in `src/routes/(app)/+layout.svelte`: avatar beside the handle, keeping the existing `title={did}`
+- [x] 2.2 Categorization provenance in `src/routes/(app)/ledger/+page.svelte`: replace actor handle text with the popover avatar (fallback preserves today's text)
## 3. Verification
-- [ ] 3.1 Verify in the browser: avatar renders in nav and provenance history; popover works via mouse hover and keyboard focus; DID present in accessible attributes
-- [ ] 3.2 Verify fallback: with the avatar URL unreachable (blocked/bogus handle), text presentation returns and no layout breaks
-- [ ] 3.3 Confirm no server-side profile fetches were introduced (avatar requests appear only in browser network log)
+- [x] 3.1 Verify in the browser: avatar renders in nav and provenance history; popover works via mouse hover and keyboard focus; DID present in accessible attributes (focus rule verified by construction — sandbox pane lacks window focus, so `:focus` can't be exercised there)
+- [x] 3.2 Verify fallback: with the avatar URL unreachable (blocked/bogus handle), text presentation returns and no layout breaks
+- [x] 3.3 Confirm no server-side profile fetches were introduced (avatar requests appear only in browser network log)
diff --git a/src/lib/components/Avatar.svelte b/src/lib/components/Avatar.svelte
new file mode 100644
index 0000000..d3643da
--- /dev/null
+++ b/src/lib/components/Avatar.svelte
@@ -0,0 +1,108 @@
+
+
+{#if src && !failed}
+ {#if popover}
+
+
+
(failed = true)} />
+ {handle}
+
+ {:else}
+
(failed = true)}
+ />
+ {/if}
+{:else if fallback && (handle ?? did)}
+ {handle ?? did}
+{/if}
+
+
diff --git a/src/lib/server/services/ledger.ts b/src/lib/server/services/ledger.ts
index 447e81b..37680e4 100644
--- a/src/lib/server/services/ledger.ts
+++ b/src/lib/server/services/ledger.ts
@@ -29,6 +29,7 @@ export interface LedgerRow {
provenance: EventSource | null;
provenanceRulePattern: string | null;
provenanceActorHandle: string | null;
+ provenanceActorDid: string | null;
}
export function monthRange(month: string): { start: number; end: number } {
@@ -73,7 +74,8 @@ export function listLedger(db: DatabaseSync, filters: LedgerFilters = {}): Ledge
${EFFECTIVE_TS} AS effective_at,
t.amount_cents, a.currency, t.description, t.payee, t.memo, t.pending,
t.category_id, c.name AS category_name,
- e.source AS prov_source, r.pattern AS prov_pattern, u.handle AS prov_handle
+ e.source AS prov_source, r.pattern AS prov_pattern, u.handle AS prov_handle,
+ e.actor_did AS prov_actor_did
FROM transactions t
JOIN accounts a ON a.id = t.account_id
LEFT JOIN categories c ON c.id = t.category_id
@@ -103,7 +105,8 @@ export function listLedger(db: DatabaseSync, filters: LedgerFilters = {}): Ledge
categoryName: r.category_name as string | null,
provenance: (r.prov_source as EventSource | null) ?? null,
provenanceRulePattern: r.prov_pattern as string | null,
- provenanceActorHandle: r.prov_handle as string | null
+ provenanceActorHandle: r.prov_handle as string | null,
+ provenanceActorDid: r.prov_actor_did as string | null
}));
}
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 68e0d08..5dbbc9f 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -1,6 +1,7 @@