{"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/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/alertRules.ts#L410-L446","documentation":"Thrown by validateQuietHoursArgs when args.quietHoursEnd is present but outside [0,23] or is not an integer. quietHoursEnd is the hour-of-day (0–23) at which the do-not-disturb window ends. Same validation shape as quietHoursStart.","triggerScenarios":"Calling setQuietHours or setQuietHoursForUser with quietHoursEnd = 24, -1, 6.25, or any non-integer.","commonSituations":"UI picker emitting 1-based or 12-hour values; passing a Date.getHours()+1 that overflows to 24 at 23:00; float inputs.","solutions":["Coerce quietHoursEnd to an integer in [0,23] before submitting.","Ensure the picker uses 24-hour 0-based output and wraps 24→0.","Ensure quietHoursEnd differs from quietHoursStart when the window is enabled (separate check at line 461/571)."],"exampleFix":"// before\nawait setQuietHours({ variant, quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 24 });\n// after\nawait setQuietHours({ variant, quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 0 }); // midnight wrap","handlingStrategy":"validation","validationCode":"function isValidHour(h) {\n  return typeof h === 'number' && Number.isInteger(h) && h >= 0 && h <= 23;\n}\nif (args.quietHoursEnd !== undefined && !isValidHour(args.quietHoursEnd)) {\n  // surface error before submit\n}","typeGuard":"function isQuietHour(n): n is number {\n  return typeof n === 'number' && Number.isInteger(n) && n >= 0 && n <= 23;\n}","tryCatchPattern":"try {\n  await setQuietHours(args);\n} catch (e) {\n  if (e instanceof ConvexError && /quietHoursEnd must be an integer/.test(String(e.message))) {\n    // coerce end and retry\n  } else throw e;\n}","preventionTips":["Use a 0-based 24-hour integer picker that wraps 24→0.","Coerce with Math.floor and clamp to [0,23].","Verify end differs from start when quiet hours are enabled."],"tags":["convex","validation","notifications","datetime","quiet-hours"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}