From 375bec5f2ee22b1f69bb9f4a963efb12a68aef05 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 2 Aug 2026 18:43:56 -0700 Subject: Profiles: view pages, editable own kind 0, zap recipients in history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tapping any avatar or author name (chat rooms, DM threads, the chat home) opens a profile page: avatar, name, nip05, lightning address, about, npub, with Message and Pay actions (Pay runs the npub payment cascade). Your own profile — reachable from your avatar on the chat home — gets an editor for name, about, picture, and lightning address (prefilled with your coinos address); saving merges over the newest published kind 0 so fields we don't understand survive the round trip. Zaps now remember who they went to: the ark feature keys the recipient by what the movement already records (destination address or invoice) and history rows wear a clickable avatar chip that opens the profile. The messages feature exposes showProfile/profileChip seams so any feature can do the same. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi --- src/features/ark.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'src/features/ark.js') diff --git a/src/features/ark.js b/src/features/ark.js index 3613b02..c81faa5 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -645,6 +645,7 @@ export function arkFeature(ctx) { const settle = (a) => { if (a.step === 'done') { ui.arkLnPaid = { amountSat: a.amountSat, meta: p.meta }; + if (p.meta && p.meta.pk) noteZap('inv:' + p.invoice, p.meta.pk); ui.arkLnPay = null; } else if (ui.arkLnPay) { ui.arkLnPay.status = 'ready'; @@ -732,6 +733,22 @@ export function arkFeature(ctx) { } + // Who a zap went to, keyed by what the movement records (the ark address + // for direct zaps, the invoice for Lightning ones) — enough to put a face + // on history rows without touching the manager's movement schema. + const zapNotes = () => wallet.loadFeatureState('zapnotes', {}); + function noteZap(key, pk) { + const s = zapNotes(); + s[key] = pk; + const keys = Object.keys(s); + if (keys.length > 200) for (const k of keys.slice(0, keys.length - 200)) delete s[k]; + wallet.saveFeatureState('zapnotes', s); + } + const zapNoteFor = (m) => + m.type === 'send' && m.to ? zapNotes()['to:' + m.to] + : m.type === 'ln-send' && m.invoice ? zapNotes()['inv:' + m.invoice] + : null; + function arkHistoryItem(m) { const incoming = !['send', 'offboard', 'exit', 'ln-send'].includes(m.type); const label = m.type === 'receive' ? t('received') : m.type === 'board' ? t('arkBoarded') @@ -746,8 +763,9 @@ export function arkFeature(ctx) { ? h('div', { class: `ico ${incoming ? 'in' : 'out'}` }, '⚡') : h('div', { class: `ico ${incoming ? 'in' : 'out'}`, html: ARK_MARK(15) }), h('div', { class: 'grow' }, - h('div', { class: 'row gap6' }, + h('div', { class: 'row gap6', style: 'align-items:center' }, label, + (() => { const pk = zapNoteFor(m); return pk ? ctx.hook('profileChip', pk) : null; })(), m.status !== 'complete' ? h('span', { class: 'tag pending' }, m.status) : null), h('div', { class: 'small faint' }, timeAgo(m.ts / 1000))), h('div', { style: 'text-align:right' }, @@ -973,6 +991,7 @@ export function arkFeature(ctx) { const action = mgr.state.actions.find((a) => a.id === actionId); if (!action || action.step === 'failed') throw new Error(action?.error || t('claimFailed')); const vtxoId = decodeVtxo(hex.decode((action.destBytesList || [action.destBytes])[0])).id; + noteZap('to:' + z.address, z.pk); // the receipt is best-effort: the sats are already delivered via mailbox await wallet.nostrPublish({ kind: ARK_ZAP_KIND, -- cgit v1.2.3