fix(quality): clear the SonarQube findings on the 4.0.0 branch (#2062)

The day-swipe move handler mixed classifying the gesture with following the
finger, which put it at cognitive complexity 20. The classifying half is a
pure question about a gesture, so it moves out as classifyLock: null keeps
waiting for the next move, dead and h end the same way they did before.

Also reads the reduce-motion flag through dataset, seeds the closest-year
reduce with years[0] so it cannot throw if the empty guard above it ever
loosens, and compares the in-flight preview entry against undefined instead
of leaning on a promise being truthy.
This commit is contained in:
Maurice
2026-08-27 12:38:14 +02:00
committed by GitHub
parent de9bd94279
commit 4784a1e5dd
3 changed files with 30 additions and 18 deletions
@@ -79,11 +79,34 @@ function band(dx: number, free: number, factor: number, max: number): number {
return Math.sign(dx) * Math.min(a <= free ? a : free + (a - free) * factor, max)
}
/**
* Which way this finger is going, once it has moved far enough to tell.
* `null` means it has not, so the caller waits for the next move.
*/
function classifyLock(
g: Gesture,
dx: number,
dy: number,
now: number,
card: HTMLElement | null,
s: { dragging: boolean; editing: boolean },
): Lock {
// The browser has already committed this gesture to scrolling, so obey it.
// scrollLeft counts too: overflow-y-auto on its own leaves overflow-x
// computing to auto, and a wide markdown table in a day note fills it.
if (card && (card.scrollTop !== g.scrollTop0 || card.scrollLeft !== g.scrollLeft0)) return 'dead'
if (Math.abs(dx) <= CLASSIFY_PX && Math.abs(dy) <= CLASSIFY_PX) return null
// Edit mode only: past the bridge's press window this finger belongs to a
// reorder drag. Go mode has no bridge, so a slow swipe still counts.
if (s.dragging || (s.editing && now - g.t0 > PRESS_WINDOW_MS)) return 'dead'
return Math.abs(dx) >= Math.abs(dy) * AXIS_BIAS ? 'h' : 'dead'
}
/** The media query plus the app's own appearance toggle, which writes the same
* attribute PluginFrame reads. */
function reducedMotion(): boolean {
if (typeof window === 'undefined') return false
if (document.documentElement.hasAttribute('data-reduce-motion')) return true
if (document.documentElement.dataset.reduceMotion !== undefined) return true
return window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false
}
@@ -297,21 +320,8 @@ export function useMPlanDaySwipe({
while (g.samples.length > 2 && now - g.samples[0].t > SAMPLE_MS) g.samples.shift()
if (g.lock === null) {
const card = cardRef.current
// The browser has already committed this gesture to scrolling, so obey it.
// scrollLeft counts too: overflow-y-auto on its own leaves overflow-x
// computing to auto, and a wide markdown table in a day note fills it.
if (card && (card.scrollTop !== g.scrollTop0 || card.scrollLeft !== g.scrollLeft0)) {
g.lock = 'dead'
return
}
if (Math.abs(dx) <= CLASSIFY_PX && Math.abs(dy) <= CLASSIFY_PX) return
const s = latest.current
// Edit mode only: past the bridge's press window this finger belongs to a
// reorder drag. Go mode has no bridge, so a slow swipe still counts.
if (s.dragging || (s.editing && now - g.t0 > PRESS_WINDOW_MS)) { g.lock = 'dead'; return }
g.lock = Math.abs(dx) >= Math.abs(dy) * AXIS_BIAS ? 'h' : 'dead'
if (g.lock === 'dead') return
g.lock = classifyLock(g, dx, dy, now, cardRef.current, latest.current)
if (g.lock !== 'h') return
}
if (reducedMotion()) return // classify and commit, but never follow the finger
+3 -1
View File
@@ -110,9 +110,11 @@ export function currentPeriodYear(s: VacayYearSettings = DEFAULT_YEAR_SETTINGS,
export function defaultPeriodYear(years: number[], s: VacayYearSettings = DEFAULT_YEAR_SETTINGS, today = new Date()): number {
const current = currentPeriodYear(s, today)
if (years.length === 0 || years.includes(current)) return current
// Seeded with years[0] rather than left to reduce's implicit first element:
// same result, and it cannot throw if the guard above ever loosens.
return years.reduce((best, year) => {
const distance = Math.abs(year - current)
const bestDistance = Math.abs(best - current)
return distance < bestDistance || (distance === bestDistance && year > best) ? year : best
})
}, years[0])
}
+1 -1
View File
@@ -545,7 +545,7 @@ export class CollabService {
// entry yet, since the first has not answered. Joining the running fetch keeps
// that a single outbound request instead of twenty.
const running = this.inFlight.get(url);
if (running) return { ...(await running), url };
if (running !== undefined) return { ...(await running), url };
// Charged per outbound fetch rather than per request, which is what the
// budget is actually protecting. Without a user there is no one to charge —