{"record":{"id":"3b60bebcee8e2cd6","repo":"koala73/worldmonitor","slug":"quiethoursend-must-be-an-integer-0-23","errorCode":null,"errorMessage":"quietHoursEnd must be an integer 0–23","messagePattern":"quietHoursEnd must be an integer 0–23","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/alertRules.ts","lineNumber":428,"sourceCode":"const QUIET_HOURS_ARGS = {\n  variant: v.string(),\n  quietHoursEnabled: v.boolean(),\n  quietHoursStart: v.optional(v.number()),\n  quietHoursEnd: v.optional(v.number()),\n  quietHoursTimezone: v.optional(v.string()),\n  quietHoursOverride: v.optional(quietHoursOverrideValidator),\n} as const;\n\nfunction validateQuietHoursArgs(args: {\n  quietHoursStart?: number;\n  quietHoursEnd?: number;\n  quietHoursTimezone?: string;\n}) {\n  if (args.quietHoursStart !== undefined && (args.quietHoursStart < 0 || args.quietHoursStart > 23 || !Number.isInteger(args.quietHoursStart))) {\n    throw new ConvexError(\"quietHoursStart must be an integer 0–23\");\n  }\n  if (args.quietHoursEnd !== undefined && (args.quietHoursEnd < 0 || args.quietHoursEnd > 23 || !Number.isInteger(args.quietHoursEnd))) {\n    throw new ConvexError(\"quietHoursEnd must be an integer 0–23\");\n  }\n  if (args.quietHoursTimezone !== undefined) {\n    try {\n      Intl.DateTimeFormat(undefined, { timeZone: args.quietHoursTimezone });\n    } catch {\n      throw new ConvexError(\"quietHoursTimezone must be a valid IANA timezone (e.g. America/New_York)\");\n    }\n  }\n}\n\nexport const setQuietHours = mutation({\n  args: QUIET_HOURS_ARGS,\n  handler: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) throw new ConvexError(\"UNAUTHENTICATED\");\n    const userId = identity.subject;\n    await assertProEntitlement(ctx, userId);\n    validateQuietHoursArgs(args);","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/convex/alertRules.ts#L410-L446","documentation":"The second check in validateQuietHoursArgs (convex/alertRules.ts:427): when quietHoursEnd is supplied it must be an integer in 0–23, else setQuietHours / setQuietHoursForUser throw ConvexError 'quietHoursEnd must be an integer 0–23'. quietHoursEnd may legitimately be smaller than quietHoursStart (an overnight window like 22→7), so only integer-ness and range matter here.","triggerScenarios":"setQuietHours (or setQuietHoursForUser) with quietHoursEnd: 7.5, a negative value, 24, or NaN — typically from fractional pickers, timezone offset math, or '24:00'-style conventions.","commonSituations":"Same sources as quietHoursStart: float sliders, overnight arithmetic wrapping incorrectly, imported 1–24 hour systems, string parses of '7:30'.","solutions":["Pass an integer 0–23 or omit quietHoursEnd.","Normalize with ((hour % 24) + 24) % 24 and Math.trunc before sending.","Remember end < start is valid (window crosses midnight) — do not 'fix' it by adding 24.","Mirror the integer/range check client-side for inline validation."],"exampleFix":"// before\nawait mutateAPI.alertRules.setQuietHours({ variant: 'default', quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 24 });\n// ConvexError: quietHoursEnd must be an integer 0–23\n\n// after — 24:00 means midnight = 0\nawait mutateAPI.alertRules.setQuietHours({ variant: 'default', quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 0 });","handlingStrategy":"validation","validationCode":"const isValidHour = (h: unknown): h is number =>\n  typeof h === 'number' && Number.isInteger(h) && h >= 0 && h <= 23;\n\nif (quietHoursEnd !== undefined && !isValidHour(quietHoursEnd)) {\n  setFieldError('quietHoursEnd', 'Pick a whole hour between 0 and 23');\n  return;\n}\nawait mutateAPI.alertRules.setQuietHours({ variant, quietHoursEnabled, quietHoursStart, quietHoursEnd });","typeGuard":"const isValidQuietHour = (h: unknown): h is number =>\n  typeof h === 'number' && Number.isInteger(h) && h >= 0 && h <= 23;","tryCatchPattern":"try {\n  await mutateAPI.alertRules.setQuietHours(args);\n} catch (err) {\n  if (err instanceof ConvexError && err.data === 'quietHoursEnd must be an integer 0–23') {\n    setFieldError('quietHoursEnd', 'Pick a whole hour between 0 and 23');\n    return;\n  }\n  throw err;\n}","preventionTips":["Use 0 for midnight, never 24.","Do not 'fix' end < start by adding 24 — overnight windows are intentional.","Share one hour-validation helper between start and end fields."],"tags":["convex","validation","arguments","range","quiet-hours"],"backgroundTag":"number-out-of-range","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}