aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-15 17:25:47 -0700
committerAdam Soltys <[email protected]>2026-08-15 17:25:47 -0700
commitceab8e8aecc29ac84e184886312a27c835cad8a9 (patch)
treec0f4c1f739bbd63ad6a6be7d41a71b8153edf2f6 /src
parent18786c7774e817b6a55c27017c8f719c2197afcb (diff)
downloadcoinosv3-ceab8e8aecc29ac84e184886312a27c835cad8a9.tar.gz
coinosv3-ceab8e8aecc29ac84e184886312a27c835cad8a9.zip
The profile form stops arriving fashionably late: it paints on the first frame from the local cache, and the posts area holds its ground while the relays answer
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
Diffstat (limited to 'src')
-rw-r--r--src/features/messages.js38
1 files changed, 27 insertions, 11 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 08ec8cf..fd08760 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -1128,7 +1128,7 @@ export function messagesFeature(ctx) {
function openProfile(pk) {
ui.profilePk = pk;
- ui.profEdit = null;
+ ui.profEdit = null; ui.profEditFilled = false;
render();
fetchFullProfile(pk);
notesFor(pk);
@@ -1424,7 +1424,7 @@ export function messagesFeature(ctx) {
if (!ok) throw new Error(t('msgSendFailed'));
fullProfiles.set(id.pubkey, merged);
profiles.set(id.pubkey, { name: merged.name || null, picture: merged.picture || null });
- ui.profEdit = null;
+ ui.profEdit = null; ui.profEditFilled = false;
toast(t('profSaved'));
} catch (err) {
toast(err.message || String(err));
@@ -1439,19 +1439,32 @@ export function messagesFeature(ctx) {
const mine = isMe(pk) || (ctx.shownPubkey && pk === ctx.shownPubkey());
const logoutBtn = () => h('button', {
class: 'btn-block', style: 'color:var(--red,#c0392b);display:flex;align-items:center;justify-content:center;gap:8px',
- onClick: () => { ui.profilePk = null; ui.profEdit = null; ctx.logout && ctx.logout(); },
+ onClick: () => { ui.profilePk = null; ui.profEdit = null; ui.profEditFilled = false; ctx.logout && ctx.logout(); },
},
h('span', { style: 'display:flex', html: '<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>' }),
t('logout'));
const full = fullProfiles.get(pk);
- // Your own profile IS the editor — the form appears prefilled as soon as
- // the published kind 0 arrives, no Edit step.
- if (mine && !ui.profEdit && full !== undefined) {
+ // Your own profile IS the editor — and it renders IMMEDIATELY, seeded
+ // from the local name/picture cache, so the page never reflows when the
+ // published kind 0 arrives. saveProfile re-fetches the newest kind 0
+ // before merging, so a stale seed can't clobber anything.
+ if (mine && !ui.profEdit) {
+ const p = (full === undefined ? profileOf(pk) : full) || {};
ui.profEdit = {
- name: full.display_name || full.name || '',
- about: full.about || '',
- picture: full.picture || '',
+ name: (full || {}).display_name || p.name || '',
+ about: p.about || '',
+ picture: p.picture || '',
};
+ ui.profEditFilled = full !== undefined;
+ }
+ // Once the real kind 0 lands, top up fields still sitting empty — never
+ // ones the user (or the cache) already filled.
+ if (mine && ui.profEdit && !ui.profEditFilled && full !== undefined) {
+ ui.profEditFilled = true;
+ const e = ui.profEdit;
+ if (!e.name) e.name = full.display_name || full.name || '';
+ if (!e.about) e.about = full.about || '';
+ if (!e.picture) e.picture = full.picture || '';
}
const name = displayName(pk);
const npub = npubOf(pk) || pk;
@@ -1521,7 +1534,10 @@ export function messagesFeature(ctx) {
// Their latest public notes, zappable in place.
(() => {
const c = notesFor(pk);
- if (c.status === 'loading') return null; // prefetched almost always; never a spinner
+ // while loading: an invisible copy of the empty-state line holds the
+ // height, so the page doesn't jump when the answer lands (no spinner)
+ if (c.status === 'loading')
+ return h('div', { class: 'small faint', style: 'text-align:center;visibility:hidden' }, t('profNotesNone'));
if (!c.notes.length)
return h('div', { class: 'small faint', style: 'text-align:center' }, t('profNotesNone'));
return h('div', { class: 'col', style: 'gap:8px' },
@@ -1532,7 +1548,7 @@ export function messagesFeature(ctx) {
noteRow(pk, ev, name),
])));
})(),
- h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.profilePk = null; ui.profEdit = null; render(); } }, t('back')));
+ h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.profilePk = null; ui.profEdit = null; ui.profEditFilled = false; render(); } }, t('back')));
}
const backBtn = (onClick) => h('button', { class: 'iconbtn chat-back', onClick }, '‹');