pulse: 1× local vs 3× remote, local cards don't trigger all-tab dot
Locally-created cards (editor, quick capture, duplicate) now get a single subtle pulse instead of the triple-pulse reserved for relay- synced cards. Local cards also never trigger the unread dot on the fixed "all" bucket tab — the user just created them, there's nothing "unseen" about their own work. state.js: new state.recentlyCreatedIds Set. Populated by editor.js doSave (POST), app.js quick capture (POST), and context-menu.js duplicate (both date-rename and copy paths). board.js renderBoard diff: new card ids are checked against recentlyCreatedIds at diff time. Local ids go into a renderBoard- local newLocalIds set (never into state.unseenCardIds). Remote ids go into unseenCardIds as before. board.js drain block (end of renderBoard): two-phase drain. Phase 1: newLocalIds → pulseCards(local, 'card-pulse-local') with 900ms timeout, works in any bucket. Consumed recentlyCreatedIds entries are deleted. Phase 2: remaining unseenCardIds (remote only) → pulseCards(remote, 'card-pulse-new') with 2400ms timeout, only on activeBucket=all. decorateAllUnreadDot() reads only state.unseenCardIds which now contains exclusively remote ids → no dot from local card creation. style.css: new .card-pulse-local class — same @keyframes but animation-iteration-count:1. Anim-fast/slow overrides added. chore: bump version to 0.9.16 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
088c225202
commit
ce6ca73ea0
7 files changed
+51
-17
No files matched your search
+26
-13
@@ -38,17 +38,19 @@ const draggingCardIds = new Set();
|
||||
// the all view) or wait for the next visit to the all tab to pulse.
|
||||
let knownCardIds = null;
|
||||
|
||||
/** Add an accent pulse class to the matching board cards and strip it
|
||||
* again after the animation finishes. The animation itself is in
|
||||
* public/css/style.css `@keyframes card-pulse-new`. */
|
||||
function pulseCardsNew(ids) {
|
||||
/** Add a pulse class to matching board cards and strip it after the
|
||||
* animation. `cls` controls the variant: 'card-pulse-new' (3×, for
|
||||
* relay-synced cards) or 'card-pulse-local' (1×, for cards the user
|
||||
* just created themselves). */
|
||||
function pulseCards(ids, cls = 'card-pulse-new') {
|
||||
if (!ids || !ids.size) return;
|
||||
const durationMs = cls === 'card-pulse-local' ? 900 : 2400;
|
||||
requestAnimationFrame(() => {
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user