fix(jira): emitir card.moved al cambiar columna + adaptive indicator polling
Bug: handleMoveCard solo emitia board.invalidated. Dispatcher mapeaba a update() (PUT summary/description/labels) NUNCA a transition(), asi que mover una card en kanban no transicionaba su Jira issue de columna. Solo los labels reflejaban el cambio. Fix backend (handlers.go): - handleMoveCard ahora lee column_id antes del MoveCard. Si la card crusa columnas (prev != new) publica 'card.moved' antes de 'board.invalidated'. El dispatcher reconoce 'card.moved' y ejecuta transition() -> Jira status cambia + labels sincronizan. - Reorder dentro de la misma columna sigue como antes: solo board.invalidated para refetch del cliente sin tocar Jira. - nuevo helper db.lookupCardColumnID(cardID). UX frontend (JiraSyncIndicator): - Polling adaptativo: 5s steady, 1s mientras inflight=true. El usuario VE el yellow durante el sync. - Listener de window CustomEvent 'kanban-card-moved' (cardId match) que fuerza un refetch inmediato (~150ms) tras drop. App.tsx dispara el evento tras api.moveCard resolve. Yellow visible casi instantaneo en lugar de esperar al proximo tick steady.
This commit is contained in:
@@ -5,11 +5,11 @@ import * as api from "../api";
|
||||
import type { CardJiraSyncState } from "../api";
|
||||
import { formatDateTimeShort } from "./format";
|
||||
|
||||
// Pull state every POLL_MS so the indicator catches up with async dispatcher
|
||||
// pushes without needing SSE. 10s is a reasonable balance: short enough that a
|
||||
// drag-to-Jira shows green within one tick, long enough that the polling is
|
||||
// not a noticeable load.
|
||||
const POLL_MS = 10000;
|
||||
// Adaptive polling: 5s steady-state, 1s while the card is mid-sync. The fast
|
||||
// cadence catches the yellow → green transition right after a column drag;
|
||||
// the slow cadence keeps the per-card load manageable when the board is idle.
|
||||
const POLL_MS_STEADY = 5000;
|
||||
const POLL_MS_INFLIGHT = 1000;
|
||||
|
||||
type Tone = "gray" | "yellow" | "green" | "red";
|
||||
|
||||
@@ -47,22 +47,43 @@ export function JiraSyncIndicator({ cardId, refreshTick }: Props) {
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
const schedule = (ms: number) => {
|
||||
if (cancelled) return;
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(load, ms);
|
||||
};
|
||||
const load = async () => {
|
||||
try {
|
||||
const s = await api.getCardJiraSync(cardId);
|
||||
if (!cancelled) {
|
||||
setState(s);
|
||||
setErr(null);
|
||||
}
|
||||
if (cancelled) return;
|
||||
setState(s);
|
||||
setErr(null);
|
||||
// Adaptive cadence: fast while the dispatcher is actively processing
|
||||
// this card, slow otherwise. Re-arms on every fetch so the moment
|
||||
// inflight flips off we drop back to the steady cadence.
|
||||
schedule(s.inflight ? POLL_MS_INFLIGHT : POLL_MS_STEADY);
|
||||
} catch (e) {
|
||||
if (!cancelled) setErr((e as Error).message);
|
||||
if (cancelled) return;
|
||||
setErr((e as Error).message);
|
||||
schedule(POLL_MS_STEADY);
|
||||
}
|
||||
};
|
||||
// Window event fired by the App's drag-end handler so the indicator
|
||||
// refetches immediately after a move (and so the user sees yellow within
|
||||
// ~200ms instead of waiting up to POLL_MS_STEADY).
|
||||
const onMoved = (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail as { cardId?: string } | undefined;
|
||||
if (detail?.cardId === cardId) {
|
||||
schedule(150);
|
||||
}
|
||||
};
|
||||
window.addEventListener("kanban-card-moved", onMoved);
|
||||
load();
|
||||
const t = setInterval(load, POLL_MS);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearInterval(t);
|
||||
if (timer) clearTimeout(timer);
|
||||
window.removeEventListener("kanban-card-moved", onMoved);
|
||||
};
|
||||
}, [cardId, refreshTick]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user