diff --git a/config.php b/config.php index 5065451..a9650f6 100644 --- a/config.php +++ b/config.php @@ -1,7 +1,7 @@ { for (const id of ids) { const el = board.querySelector(`[data-id="${id}"]`); if (!el) continue; - el.classList.add('card-pulse-new'); - setTimeout(() => el.classList.remove('card-pulse-new'), 2400); + el.classList.add(cls); + setTimeout(() => el.classList.remove(cls), durationMs); } }); } @@ -487,9 +489,14 @@ export function renderBoard() { // the first real load looks like "every card just arrived" and the // entire board pulses on page open. const currentIds = new Set(state.cards.map(c => String(c.id))); + const newLocalIds = new Set(); if (knownCardIds && knownCardIds.size > 0) { for (const id of currentIds) { - if (!knownCardIds.has(id)) state.unseenCardIds.add(id); + if (knownCardIds.has(id)) continue; + // Locally-created cards go into a separate set — they get a + // 1× pulse but must NOT trigger the unread dot on the all-tab. + if (state.recentlyCreatedIds.has(id)) newLocalIds.add(id); + else state.unseenCardIds.add(id); } } knownCardIds = currentIds; @@ -733,14 +740,20 @@ export function renderBoard() { // Notification banner renderNotifs(); - // Unread tracking: if we're on the all view, drain the unseen set - // by pulsing every matching card on the board (the "look at me" - // accent flash). If we're on a different view, the unseen ids stay - // accumulated and the all-tab gets a dot until the user opens it. + // Pulse locally-created cards in ANY bucket (1× subtle pulse). + // These never enter unseenCardIds so they can't trigger the all-tab + // dot — the user just made them, there's nothing "unseen" about it. + if (newLocalIds.size) { + for (const id of newLocalIds) state.recentlyCreatedIds.delete(id); + pulseCards(newLocalIds, 'card-pulse-local'); + } + // Remote (relay-synced) cards: pulse 3× only when the user is on + // the all view. On other views, the unseen ids accumulate and + // the all-tab gets a dot until the user opens it. if (state.activeBucket === 'all' && state.unseenCardIds.size) { - const toPulse = new Set(state.unseenCardIds); + const remote = new Set(state.unseenCardIds); state.unseenCardIds.clear(); - pulseCardsNew(toPulse); + pulseCards(remote, 'card-pulse-new'); } decorateAllUnreadDot(); } diff --git a/public/js/modules/context-menu.js b/public/js/modules/context-menu.js index 3b224a5..16c43e0 100644 --- a/public/js/modules/context-menu.js +++ b/public/js/modules/context-menu.js @@ -283,9 +283,11 @@ export function showContextMenu(x, y, card) { const newDate = await showDialog({ title: 'duplicate_to_date', input: 'date', defaultValue: curDate, okLabel: 'Duplicate' }); if (!newDate) return; const newTitle = card.title.replace(/^\d{4}-\d{2}-\d{2}/, newDate.trim()); - await api('api/cards', { method: 'POST', body: { title: newTitle, body: card.body, color: card.color, bucket_id: card.bucket_id, position: -1 } }); + const dup1 = await api('api/cards', { method: 'POST', body: { title: newTitle, body: card.body, color: card.color, bucket_id: card.bucket_id, position: -1 } }); + if (dup1?.id) state.recentlyCreatedIds?.add(String(dup1.id)); } else { - await api('api/cards', { method: 'POST', body: { title: card.title + ' (copy)', body: card.body, color: card.color, bucket_id: card.bucket_id, position: -1 } }); + const dup2 = await api('api/cards', { method: 'POST', body: { title: card.title + ' (copy)', body: card.body, color: card.color, bucket_id: card.bucket_id, position: -1 } }); + if (dup2?.id) state.recentlyCreatedIds?.add(String(dup2.id)); } await window._reloadAll(); } diff --git a/public/js/modules/editor.js b/public/js/modules/editor.js index c687df6..334323e 100644 --- a/public/js/modules/editor.js +++ b/public/js/modules/editor.js @@ -415,6 +415,7 @@ async function doSave(close, { animate = true } = {}) { if (created?.id) { if (!close) state.editingId = created.id; state._lastSavedId = created.id; + state.recentlyCreatedIds?.add(String(created.id)); } } await window._reloadAll(); diff --git a/public/js/modules/state.js b/public/js/modules/state.js index a8ef035..cb60024 100644 --- a/public/js/modules/state.js +++ b/public/js/modules/state.js @@ -37,6 +37,11 @@ export const state = { // accent pulse on the cards themselves once the user looks at them. // Updated by board.js renderBoard() — see the diff there. unseenCardIds: new Set(), + // Ids of cards the user just created locally (editor save, quick + // capture, duplicate, undo-recreate). These get a single-pulse (1x) + // instead of the triple-pulse (3x) that relay-synced cards get. + // Consumed by renderBoard's diff — once used, the id is removed. + recentlyCreatedIds: new Set(), // Detail / editor editingId: null,