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)} /> + + + {:else} + {fallback (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 @@