support-form.jsjavascript
/**
 * @file Support-form decision logic — the pure branches behind the Help modal's "Send to
 * support" button.
 *
 * Extracted from renderer.html's submit handler so it can be unit-tested in plain Node
 * (test/support-form.test.js) without a DOM. The renderer still owns all DOM reads/writes
 * and the IPC call; this module owns only the decisions — which submissions are
 * sendable, and which sentence each outcome earns — so the two stay in lock-step by
 * construction.
 *
 * Loaded two ways (UMD-lite): the Electron renderer loads it with a plain script tag,
 * which attaches window.SupportForm (no nodeIntegration needed; `module` is undefined
 * there); Node tests require('../src/support-form.js') and use module.exports.
 * @module support-form
 */
(function (root, factory) {
  if (typeof module === 'object' && module.exports) module.exports = factory();
  else root.SupportForm = factory();
})(typeof self !== 'undefined' ? self : this, function () {

  /**
   * Minimum message length after trimming, in characters. The server enforces 20–5000;
   * the client bounds catch both ends pre-flight with friendlier wording. Whitespace
   * doesn't count toward the minimum.
   * @type {number}
   */
  const MIN_MESSAGE_CHARS = 20;
  /** Maximum message length after trimming, in characters. @type {number} */
  const MAX_MESSAGE_CHARS = 5000;

  /**
   * Pre-flight validation of the support message.
   * @param {?string} raw Message text as typed; null/undefined treated as empty.
   * @returns {?{title: string, message: string}} Null when sendable, otherwise the error
   *   to show instead of sending.
   */
  function validateMessage(raw) {
    const message = (raw || '').trim();
    if (message.length < MIN_MESSAGE_CHARS) {
      return {
        title: 'Add a little more detail.',
        message: 'Please describe the problem in at least a sentence or two so we can better assist you.',
      };
    }
    if (message.length > MAX_MESSAGE_CHARS) {
      return {
        title: 'That message is too long to send.',
        message: 'Please keep it under 5000 characters — if you pasted logs, trim them to the part that mentions the error.',
      };
    }
    return null;
  }

  /**
   * Map main's submitSupportRequest result to what the user sees.
   * @param {?{ok: boolean, reason: string, status: number}} result Submission result
   *   from the main process.
   * @returns {?{title: string, message: string}} Null on success (the caller shows the
   *   sent notice), otherwise the error to display.
   */
  function resultMessage(result) {
    if (result && result.ok) return null;
    if (result && (result.reason === 'signed-out' || result.status === 401)) {
      return { title: 'Support submission failed.', message: 'Your session has expired — please sign in again.' };
    }
    if (result && result.reason === 'network') {
      return { title: 'Couldn’t reach the server.', message: 'Check your internet connection and try again.' };
    }
    if (result && result.status === 429) {
      return { title: 'You’re sending messages too quickly.', message: 'Please wait a minute and try again.' };
    }
    return { title: 'Support submission failed.', message: 'Something went wrong on our end — please try again in a few minutes.' };
  }

  return { MIN_MESSAGE_CHARS, MAX_MESSAGE_CHARS, validateMessage, resultMessage };
});