{"record":{"id":"f0061d97a5d5714c","repo":"koala73/worldmonitor","slug":"quiethoursstart-must-be-an-integer-0-23","errorCode":null,"errorMessage":"quietHoursStart must be an integer 0–23","messagePattern":"quietHoursStart must be an integer 0–23","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/alertRules.ts","lineNumber":425,"sourceCode":"  },\n});\n\nconst 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\");","sourceCodeStart":407,"sourceCodeEnd":443,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/convex/alertRules.ts#L407-L443","documentation":"validateQuietHoursArgs (convex/alertRules.ts:419) runs for both setQuietHours and setQuietHoursForUser. When quietHoursStart is supplied it must be an integer in 0–23 (Number.isInteger plus bounds check); otherwise the mutation throws ConvexError 'quietHoursStart must be an integer 0–23' (string data, no code). The arg is v.optional(v.number()), so non-numeric values fail Convex's own validator first.","triggerScenarios":"setQuietHours (or setQuietHoursForUser via runMutation) with quietHoursStart: 9.5 from a fractional time picker; negative values from a TZ conversion; 24 from a '24:00' convention; a computed NaN forwarded unchecked.","commonSituations":"Quiet-hours sliders emitting floats; overnight-window math (start 22 minus offset) going negative; imported notification settings from systems using 1–24 hours; UI selects built from '00:00'–'24:00' strings.","solutions":["Pass an integer 0–23 or omit quietHoursStart.","Wrap computed values: ((hour % 24) + 24) % 24 and Math.trunc fractional input.","Build the picker options from 0–23 so out-of-range values cannot be selected.","Validate client-side with the same integer/range predicate before calling the mutation."],"exampleFix":"// before\nawait mutateAPI.alertRules.setQuietHours({ variant: 'default', quietHoursEnabled: true, quietHoursStart: 22.5, quietHoursEnd: 7 });\n// ConvexError: quietHoursStart must be an integer 0–23\n\n// after\nconst start = ((Math.trunc(pickedStart) % 24) + 24) % 24;\nawait mutateAPI.alertRules.setQuietHours({ variant: 'default', quietHoursEnabled: true, quietHoursStart: start, quietHoursEnd: 7 });","handlingStrategy":"validation","validationCode":"const isValidHour = (h: unknown): h is number =>\n  typeof h === 'number' && Number.isInteger(h) && h >= 0 && h <= 23;\n\nif (quietHoursStart !== undefined && !isValidHour(quietHoursStart)) {\n  setFieldError('quietHoursStart', '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 === 'quietHoursStart must be an integer 0–23') {\n    setFieldError('quietHoursStart', 'Pick a whole hour between 0 and 23');\n    return;\n  }\n  throw err;\n}","preventionTips":["Populate quiet-hour selects with 0–23 only.","Wrap offset math for overnight windows with modulo so values stay in range.","Truncate fractional slider output before submission.","Remember end < start is legal (crosses midnight) — only integer-ness and range are checked."],"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"}