From 2d40dc08ed1fcfb1780abecc0f5aa4f163dfd10c Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 22 Oct 2026 17:05:02 +0200 Subject: [PATCH] fix(ci): strict-mode types in three cards, photo measurements marked independent, and the strict ratchet names its errors --- .../src/components/Assistant/cards/booking/bookingLook.ts | 4 +++- .../src/components/Assistant/cards/budget/budgetModel.ts | 5 ++++- client/src/components/Assistant/cards/cardLooks.ts | 2 -- server/scripts/strict-lint.mjs | 3 +++ server/src/nest/photos/photo-signals.service.ts | 7 +++++++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/client/src/components/Assistant/cards/booking/bookingLook.ts b/client/src/components/Assistant/cards/booking/bookingLook.ts index 1c4ee01d1..d98df4f3c 100644 --- a/client/src/components/Assistant/cards/booking/bookingLook.ts +++ b/client/src/components/Assistant/cards/booking/bookingLook.ts @@ -27,7 +27,9 @@ export function bookingTabOf(type: string): 'transports' | 'buchungen' { * that trip's planner. `map` shows its route on the plan instead. */ export function bookingTarget(item: BookingItem, reservationId: number, map = false): EntityTarget { - const params = map ? { booking: String(reservationId), map: '1' } : { booking: String(reservationId) }; + const params: Record = map + ? { booking: String(reservationId), map: '1' } + : { booking: String(reservationId) }; const tab = map ? 'plan' : bookingTabOf(item.type); if (item.tripTitle !== null) { return { kind: 'page', path: `/trips/${item.tripId}?${new URLSearchParams({ tab, ...params }).toString()}` }; diff --git a/client/src/components/Assistant/cards/budget/budgetModel.ts b/client/src/components/Assistant/cards/budget/budgetModel.ts index d0c0a1352..9d04aeb0b 100644 --- a/client/src/components/Assistant/cards/budget/budgetModel.ts +++ b/client/src/components/Assistant/cards/budget/budgetModel.ts @@ -79,7 +79,10 @@ export function mergePayments(entries: readonly CardEntry[]): PaymentsCard | nul /** Settle up and the share per traveller answer once: the latest card of the answer is the one shown. */ export function latestOf(entries: readonly CardEntry[], kind: 'settlement'): SettlementCard | null; export function latestOf(entries: readonly CardEntry[], kind: 'costPerPerson'): CostPerPersonCard | null; -export function latestOf(entries: readonly CardEntry[], kind: 'settlement' | 'costPerPerson') { +export function latestOf( + entries: readonly CardEntry[], + kind: 'settlement' | 'costPerPerson' +): SettlementCard | CostPerPersonCard | null { const cards = cardsOf(entries, kind); return cards[cards.length - 1] ?? null; } diff --git a/client/src/components/Assistant/cards/cardLooks.ts b/client/src/components/Assistant/cards/cardLooks.ts index 749e52af7..e3ba014bf 100644 --- a/client/src/components/Assistant/cards/cardLooks.ts +++ b/client/src/components/Assistant/cards/cardLooks.ts @@ -60,8 +60,6 @@ export const VERB_LOOK: Readonly> = { addReservation: { Icon: Ticket, tone: 'success', labelKey: 'assistant.change.verb.addReservation' }, importBooking: { Icon: Ticket, tone: 'success', labelKey: 'assistant.change.verb.importBooking' }, rememberNote: { Icon: NotebookPen, tone: 'info', labelKey: 'assistant.change.verb.rememberNote' }, - addTour: { Icon: MapPinPlus, tone: 'success', labelKey: 'assistant.change.verb.addTour' }, - updateTour: { Icon: PencilLine, tone: 'info', labelKey: 'assistant.change.verb.updateTour' }, importTour: { Icon: Plus, tone: 'success', labelKey: 'assistant.change.verb.importTour' }, genericCreate: { Icon: Plus, tone: 'success', labelKey: 'assistant.change.verb.genericCreate' }, genericUpdate: { Icon: PencilLine, tone: 'info', labelKey: 'assistant.change.verb.genericUpdate' }, diff --git a/server/scripts/strict-lint.mjs b/server/scripts/strict-lint.mjs index 9f1414f26..10d74f319 100644 --- a/server/scripts/strict-lint.mjs +++ b/server/scripts/strict-lint.mjs @@ -94,6 +94,9 @@ function check(argv) { (entry ? `its baseline is ${entry}. ` : 'a file without a baseline entry may have none. ') + 'Run npx tsc -p tsconfig.strict.json --noEmit to see them; narrow the null case or type the parameter.', ); + // The first few in the log too, so a red CI job names them without a local run. + const shown = output.split('\n').filter((line) => line.startsWith(`${file}(`)); + for (const line of shown.slice(0, 5)) console.error(` ${line.trim()}`); } const stale = staleCounts(baseline, counts); reportStale(stale, { file: 'strict-baseline.json', command: 'lint:strict', root: serverDir, error: console.error }); diff --git a/server/src/nest/photos/photo-signals.service.ts b/server/src/nest/photos/photo-signals.service.ts index 184d43d86..90ab358a3 100644 --- a/server/src/nest/photos/photo-signals.service.ts +++ b/server/src/nest/photos/photo-signals.service.ts @@ -86,6 +86,8 @@ export class PhotoSignalsService implements OnModuleDestroy { * answer what is stored for them too, and the missing ones join the line * behind the others, neither waited for nor counted: a caller asking about a * few photos of a large gallery gets those measured first. + * + * @txIndependent each photo is measured off the event loop and stored on its own; one photo's row is complete alone. */ async signalsFor( photoIds: readonly number[], @@ -127,6 +129,11 @@ export class PhotoSignalsService implements OnModuleDestroy { return done; } + /** + * Starts measuring the next photos in line, a few at a time. + * + * @txIndependent one measurement per photo, each stored by itself after a worker answered. + */ private pump(): void { while (this.active < PARALLEL_FETCHES) { const photoId = this.waiting.shift();