fix(ci): collection lookups typed as partial, card formatting, two unused exports, and the format check says when Prettier gave up

This commit is contained in:
Maurice
2026-10-22 17:19:59 +02:00
parent 6099615337
commit 9b08da8e0b
22 changed files with 87 additions and 77 deletions
+8 -1
View File
@@ -31,11 +31,16 @@ export async function isFormatted(path) {
if (!options) throw new RatchetError(`no Prettier config applies to ${path}`);
try {
return await prettier.check(readText(path), { ...options, filepath: path });
} catch {
} catch (err) {
stopped.set(path, String(err instanceof Error ? err.message : err).split('
')[0]);
return false;
}
}
/** Why Prettier gave up on a file, by its path, so the failure line can say it was not about style. */
const stopped = new Map();
/** The files under root that Prettier would rewrite, keyed by their path from root. */
export async function unformatted(root) {
const found = [];
@@ -74,6 +79,8 @@ export async function check({
if (list) for (const key of found) log(`${allowed.has(key) ? 'listed' : 'new '} ${key}`);
for (const key of fresh) {
error(`FAIL ${key} is not formatted. Run: npx prettier --write ${key}`);
const why = stopped.get(join(root, key));
if (why) error(` Prettier could not format it: ${why}`);
}
for (const key of stale) {
error(`FAIL ${key} is listed in scripts/format-baseline.json but is formatted now or gone.`);
@@ -33,10 +33,7 @@ const passport: AtlasPassportCard = {
kind: 'atlasPassport',
countries: { visited: 23, planned: 4, idea: 0 },
totals: { trips: 48, days: 410, places: 990, cities: 312, flownKm: 182_300 },
flags: [
...CODES.map((code) => ({ code, status: 'visited' as const })),
{ code: 'MA', status: 'planned' as const },
],
flags: [...CODES.map((code) => ({ code, status: 'visited' as const })), { code: 'MA', status: 'planned' as const }],
moreFlags: 4,
continents: [
{ name: 'Europe', visited: 14, planned: 2 },
@@ -19,14 +19,13 @@ import type { ChangeItem } from '../../assistantParts';
*/
/** How a country's or a region's standing reads. A table, so each word is named where it is read. */
export const STATUS_LOOK: Readonly<Record<AtlasStatusAfter, { Icon: LucideIcon; tone: PillTone; labelKey: string }>> =
{
visited: { Icon: Check, tone: 'success', labelKey: 'assistant.card.atlas.status.visited' },
planned: { Icon: CalendarClock, tone: 'info', labelKey: 'assistant.card.atlas.status.planned' },
idea: { Icon: Lightbulb, tone: 'neutral', labelKey: 'assistant.card.atlas.status.idea' },
none: { Icon: CircleDashed, tone: 'neutral', labelKey: 'assistant.card.atlas.status.none' },
unknown: { Icon: Briefcase, tone: 'info', labelKey: 'assistant.card.atlas.status.unknown' },
};
export const STATUS_LOOK: Readonly<Record<AtlasStatusAfter, { Icon: LucideIcon; tone: PillTone; labelKey: string }>> = {
visited: { Icon: Check, tone: 'success', labelKey: 'assistant.card.atlas.status.visited' },
planned: { Icon: CalendarClock, tone: 'info', labelKey: 'assistant.card.atlas.status.planned' },
idea: { Icon: Lightbulb, tone: 'neutral', labelKey: 'assistant.card.atlas.status.idea' },
none: { Icon: CircleDashed, tone: 'neutral', labelKey: 'assistant.card.atlas.status.none' },
unknown: { Icon: Briefcase, tone: 'info', labelKey: 'assistant.card.atlas.status.unknown' },
};
/** The continents in the Atlas page's own words. */
export const CONTINENT_LABEL: Readonly<Record<AtlasCardContinent, { labelKey: string }>> = {
@@ -126,7 +126,9 @@ export default function ExpensesCard({ cards }: { cards: readonly CardEntry[] })
actions={<CardOpenButton target={costsTarget()} labelKey="assistant.card.budget.open" />}
/>
<CardRows>
{card.rows.map((row, i) => <ExpenseRow key={row.id ?? `new-${i}`} row={row} />)}
{card.rows.map((row, i) => (
<ExpenseRow key={row.id ?? `new-${i}`} row={row} />
))}
</CardRows>
<ExpensesFoot card={card} />
</CardFrame>
@@ -65,7 +65,9 @@ export default function PaymentsCard({ cards }: { cards: readonly CardEntry[] })
actions={<CardOpenButton target={costsTarget()} labelKey="assistant.card.budget.open" />}
/>
<CardRows>
{card.rows.map((row, i) => <PaymentRow key={row.id ?? `new-${i}`} row={row} />)}
{card.rows.map((row, i) => (
<PaymentRow key={row.id ?? `new-${i}`} row={row} />
))}
</CardRows>
<CardMore count={card.more} target={costsTarget()} />
</CardFrame>
@@ -118,6 +118,3 @@ export interface CardView<K extends TrekCardKind> {
runnable?(card: TrekCardOf<K>): boolean;
startsExcluded?(card: TrekCardOf<K>): boolean;
}
/** The view of a kind not drawn yet: it renders nothing, so its cards are left out until a package fills it. */
export const PENDING_VIEW = {} satisfies CardView<TrekCardKind>;
@@ -181,7 +181,9 @@ function Words({ bubble, spot, own }: Pick<BubbleProps, 'bubble' | 'spot' | 'own
reply={bubble.reply}
text={<MessageText text={bubble.text} plain />}
>
{bubble.files.map((file) => <ChatFile key={file.fileId} file={file} tripId={tripId} />)}
{bubble.files.map((file) => (
<ChatFile key={file.fileId} file={file} tripId={tripId} />
))}
</ChatBubble>
);
}
@@ -146,7 +146,9 @@ export default function CollabNotesBlock({ cards }: { cards: readonly CardEntry[
actions={<CardOpenButton target={NOTES_TARGET} labelKey="assistant.card.collab.openNotes" />}
/>
<div className={`mt-2 gap-2 ${density === 'roomy' ? 'grid grid-cols-2' : 'flex flex-col'}`}>
{notes.map((note, index) => <NoteView key={note.noteId ?? `new-${index}`} note={note} state={null} />)}
{notes.map((note, index) => (
<NoteView key={note.noteId ?? `new-${index}`} note={note} state={null} />
))}
</div>
<CardMore count={more} labelKey="assistant.card.collab.moreNotes" target={NOTES_TARGET} />
</CardFrame>
@@ -157,7 +159,9 @@ export default function CollabNotesBlock({ cards }: { cards: readonly CardEntry[
export function CollabNotesBody({ card, item }: { card: NotesCard; item: ChangeItem }) {
return (
<div className="flex flex-col gap-2">
{card.notes.map((note, index) => <NoteView key={note.noteId ?? `new-${index}`} note={note} state={item.state} />)}
{card.notes.map((note, index) => (
<NoteView key={note.noteId ?? `new-${index}`} note={note} state={item.state} />
))}
</div>
);
}
@@ -133,7 +133,9 @@ export default function CollabPollsBlock({ cards }: { cards: readonly CardEntry[
actions={<CardOpenButton target={POLLS_TARGET} labelKey="assistant.card.collab.openPolls" />}
/>
<div className="mt-2 flex flex-col gap-2">
{polls.map((poll, index) => <PollView key={poll.pollId ?? `new-${index}`} poll={poll} state={null} />)}
{polls.map((poll, index) => (
<PollView key={poll.pollId ?? `new-${index}`} poll={poll} state={null} />
))}
</div>
<CardMore count={more} labelKey="assistant.card.collab.morePolls" target={POLLS_TARGET} />
</CardFrame>
@@ -144,7 +146,9 @@ export default function CollabPollsBlock({ cards }: { cards: readonly CardEntry[
export function CollabPollsBody({ card, item }: { card: PollsCard; item: ChangeItem }) {
return (
<div className="flex flex-col gap-2">
{card.polls.map((poll, index) => <PollView key={poll.pollId ?? `new-${index}`} poll={poll} state={item.state} />)}
{card.polls.map((poll, index) => (
<PollView key={poll.pollId ?? `new-${index}`} poll={poll} state={item.state} />
))}
</div>
);
}
@@ -40,7 +40,7 @@ function placesCardOf(card: TrekCard | null): CollectionPlacesCard | null {
}
/** The saved place a change card is about: its only one. */
export function firstPlace(card: TrekCard | null): CollectionCardPlace | null {
function firstPlace(card: TrekCard | null): CollectionCardPlace | null {
const places = placesCardOf(card)?.places ?? [];
return places.length === 1 ? (places[0] ?? null) : null;
}
@@ -91,7 +91,9 @@ export function JourneyEntryFacts({ item }: { card: JourneyEntriesCard; item: Ch
tone="info"
/>
))}
{states.map((row) => <StateChange key={row.field ?? row.labelKey} row={row} />)}
{states.map((row) => (
<StateChange key={row.field ?? row.labelKey} row={row} />
))}
</>
);
}
@@ -37,10 +37,7 @@ const row = (id: number | null, name: string, over: Partial<PackingCardItem> = {
...over,
});
const packing = (
groups: Array<[string, PackingCardItem[]]>,
over: Partial<PackingListCard> = {}
): PackingListCard => ({
const packing = (groups: Array<[string, PackingCardItem[]]>, over: Partial<PackingListCard> = {}): PackingListCard => ({
kind: 'packingList',
source: { type: 'trip' },
groupBy: 'category',
@@ -139,15 +139,7 @@ export interface RunEntry {
}
/** A change in a run: the round include choice, the tile of what it does, and the row. */
function RunRow({
entry,
excluded,
onToggle,
}: {
entry: RunEntry;
excluded: boolean;
onToggle: (id: string) => void;
}) {
function RunRow({ entry, excluded, onToggle }: { entry: RunEntry; excluded: boolean; onToggle: (id: string) => void }) {
const { t } = useTranslation();
const { variant } = useAssistantView();
const glyph = ROW_GLYPH[entry.change];
@@ -305,9 +305,7 @@ export function PackingGroup({
))}
</ul>
)}
{group.items.length > 0 && (
<MoreLine label={hidden > 0 ? t('tours.packing.more', { count: hidden }) : null} />
)}
{group.items.length > 0 && <MoreLine label={hidden > 0 ? t('tours.packing.more', { count: hidden }) : null} />}
</section>
);
}
@@ -20,15 +20,7 @@ import { settingLabel } from './roadtripSettings';
*/
/** One way of driving it, as a row to pick: saved or tried, how far and how long, how much to look at. */
function VariantRow({
card,
picked,
onPick,
}: {
card: RoadtripCard;
picked: boolean;
onPick: () => void;
}) {
function VariantRow({ card, picked, onPick }: { card: RoadtripCard; picked: boolean; onPick: () => void }) {
const { t } = useTranslation();
const format = useCardFormat();
const warnings = card.days.filter(dayHasWarning).length;
@@ -55,9 +47,7 @@ function VariantRow({
})}
</span>
{warnings > 0 && (
<span className="flex-none text-warning">
{t('assistant.card.roadtrip.warnings', { count: warnings })}
</span>
<span className="flex-none text-warning">{t('assistant.card.roadtrip.warnings', { count: warnings })}</span>
)}
</button>
</li>
@@ -76,7 +76,9 @@ export default function TourRouteActions({
)}
</div>
{take.visible && caption && (
<p className="m-0 text-end text-caption text-content-muted" role="status">{caption}</p>
<p className="m-0 text-end text-caption text-content-muted" role="status">
{caption}
</p>
)}
</div>
);
@@ -70,7 +70,11 @@ export default function VacayChangeGroup({
/>
)}
{placed.length > 0 && (
<ul className="sr-only">{placed.map((entry) => <li key={entry.toolCallId}>{labelOf(entry)}</li>)}</ul>
<ul className="sr-only">
{placed.map((entry) => (
<li key={entry.toolCallId}>{labelOf(entry)}</li>
))}
</ul>
)}
{twice && (
<div className={PILLS}>
@@ -96,7 +96,13 @@ function Cell({
{marks}
</span>
);
return cell.holiday ? <Tooltip label={cell.holiday.name} placement="top">{body}</Tooltip> : body;
return cell.holiday ? (
<Tooltip label={cell.holiday.name} placement="top">
{body}
</Tooltip>
) : (
body
);
}
export default function VacayMiniMonth({ view, month, placed, today, size, onToggle, labelOf }: MonthProps) {
@@ -109,7 +115,9 @@ export default function VacayMiniMonth({ view, month, placed, today, size, onTog
<p className={TITLE}>{title}</p>
<div className={GRID[size]}>
{weekdays.map((name, column) => (
<span key={`weekday-${column}`} aria-hidden="true" className={WEEKDAY}>{name}</span>
<span key={`weekday-${column}`} aria-hidden="true" className={WEEKDAY}>
{name}
</span>
))}
{cells.map((cell, index) =>
cell ? (
@@ -70,7 +70,9 @@ export default function VacayRowFacts({ card }: { card: VacayCard; item: ChangeI
</SoftPill>
)}
{proposal.notes.map((note) => (
<SoftPill key={note} tone={NOTE_LOOK[note].tone}>{t(NOTE_LOOK[note].labelKey)}</SoftPill>
<SoftPill key={note} tone={NOTE_LOOK[note].tone}>
{t(NOTE_LOOK[note].labelKey)}
</SoftPill>
))}
</>
);
@@ -33,7 +33,7 @@ export function NoteHeadBand({
}) {
return (
<div
className={`flex items-center gap-2 py-1.5 ps-3 pe-1.5 ${bordered ? 'border-b border-edge-faint' : ''}`}
className={`flex items-center gap-2 py-1.5 pe-1.5 ps-3 ${bordered ? 'border-b border-edge-faint' : ''}`}
style={{ background: tintOf(color) }}
>
{/* The category is only its colour here; the name is in the tooltip and the filter above. */}
@@ -122,7 +122,7 @@ function labelApplied({ result, preview }: AppliedInput): CollectionPlacesCard |
return { ...card, labels: [{ ...shown, id: positiveId(label.id), name, color }] };
}
export const COLLECTION_APPLIED: Readonly<Record<string, (input: AppliedInput) => TrekCard | null>> = {
export const COLLECTION_APPLIED: Readonly<Partial<Record<string, (input: AppliedInput) => TrekCard | null>>> = {
save_place_to_collection: savedApplied,
update_collection_place: placeApplied,
set_collection_place_status: placeApplied,
@@ -137,22 +137,23 @@ export const COLLECTION_APPLIED: Readonly<Record<string, (input: AppliedInput) =
};
/** The ids a receipt names where the answer does not name them under the usual keys. */
export const COLLECTION_APPLIED_IDS: Readonly<Record<string, (args: ToolArgs, result: unknown) => number[]>> = {
create_collection: (_args, result) => idsOf(positiveId(recordOf(recordOf(result).collection).id)),
update_collection: (args) => idsOf(argId(args, 'collectionId')),
delete_collection: (args) => idsOf(argId(args, 'collectionId')),
save_trip_places_to_collection: (args) => idsOf(argId(args, 'collectionId')),
accept_collection_invite: (args) => idsOf(argId(args, 'collectionId')),
decline_collection_invite: (args) => idsOf(argId(args, 'collectionId')),
cancel_collection_invite: (args) => idsOf(argId(args, 'collectionId')),
create_collection_label: (args) => idsOf(argId(args, 'collection_id')),
update_collection_label: (_args, result) => idsOf(positiveId(recordOf(recordOf(result).label).collection_id)),
// The new trip places, so "Show on map" opens the first of them.
copy_collection_places_to_trip: (_args, result) =>
rowsOf(recordOf(result).created)
.map((row) => positiveId(row.id))
.filter((id): id is number => id !== null),
};
export const COLLECTION_APPLIED_IDS: Readonly<Partial<Record<string, (args: ToolArgs, result: unknown) => number[]>>> =
{
create_collection: (_args, result) => idsOf(positiveId(recordOf(recordOf(result).collection).id)),
update_collection: (args) => idsOf(argId(args, 'collectionId')),
delete_collection: (args) => idsOf(argId(args, 'collectionId')),
save_trip_places_to_collection: (args) => idsOf(argId(args, 'collectionId')),
accept_collection_invite: (args) => idsOf(argId(args, 'collectionId')),
decline_collection_invite: (args) => idsOf(argId(args, 'collectionId')),
cancel_collection_invite: (args) => idsOf(argId(args, 'collectionId')),
create_collection_label: (args) => idsOf(argId(args, 'collection_id')),
update_collection_label: (_args, result) => idsOf(positiveId(recordOf(recordOf(result).label).collection_id)),
// The new trip places, so "Show on map" opens the first of them.
copy_collection_places_to_trip: (_args, result) =>
rowsOf(recordOf(result).created)
.map((row) => positiveId(row.id))
.filter((id): id is number => id !== null),
};
function idsOf(id: number | null): number[] {
return id === null ? [] : [id];
@@ -80,7 +80,7 @@ const oneLabel: Loader = async (args, read) => {
return { ...NO_LOOKUP, items, collection: await collectionOf(read, positiveId(recordOf(label).collection_id)) };
};
export const COLLECTION_LOADS: Readonly<Record<string, Loader>> = {
export const COLLECTION_LOADS: Readonly<Partial<Record<string, Loader>>> = {
save_place_to_collection: async (args, read) => {
const query = membershipQuery(args);
const [collection, membership] = await Promise.all([