Source: 05-entries.js

/* ── Entry add ── */

/**
 * Creates a new log entry from the capture input's current value.
 * Optionally starts the timer on the new entry (`withTimer = true`), in which
 * case any running timer is stopped first and the matching plan task is
 * auto-promoted to "in progress".
 * @param {boolean} withTimer - If true, start the timer on the new entry.
 */
function addEntry(withTimer) {
  const inp = document.getElementById('captureInput');
  const text = inp.value.trim();
  if (!text) {
    inp.focus();
    return;
  }
  if (withTimer && activeTimer) stopTimer();
  const entry = {
    id: Date.now() + '',
    text,
    tag: selectedTag,
    ts: safeRoundedStart(),
    date: dk(new Date()),
  };
  entries.push(entry);
  inp.value = '';
  viewDate = new Date();
  save();
  if (withTimer) {
    // Auto In progress on matching plan task
    const todayKey = dk(new Date());
    const task = planTasks.find(
      (planTask) => planTask.date === todayKey && planTask.text.toLowerCase() === text.toLowerCase()
    );
    if (task && task.status === 'todo') {
      task.status = 'inprogress';
      savePlan();
    }
    startTimer(entry.id);
  }
  render();
  inp.focus();
}

/* ── Billable rule ── */

/**
 * Determines whether a log entry is billable, using a three-tier lookup:
 * 1. The entry's own `billable` flag (if explicitly set).
 * 2. The matching plan task's `billable` flag.
 * 3. The category default.
 *
 * Assumption: entries and tasks where `billable` is `undefined` are treated as
 * billable by default. This preserves backward compatibility with data created
 * before the billable flag was introduced — older entries must not silently
 * disappear from billing reports after an upgrade.
 * If the default should change to non-billable, a migration of existing
 * localStorage data is required (see DATA.md § wl_entries).
 *
 * @param {Object} entry - Log entry object.
 * @returns {boolean} True if the entry should be counted as billable.
 */
function isEntryBillable(entry) {
  if (entry.signifier === 'cancelled') return false;
  if (entry.billable !== undefined) return entry.billable;
  const task = planTasks.find(
    (planTask) => planTask.text.toLowerCase().trim() === entry.text.toLowerCase().trim()
  );
  // `!== false` (not `=== true`) — undefined means billable (see Assumption above).
  if (task) return task.billable !== false;
  // Same `!== false` convention for categories — undefined → billable.
  return getCat(entry.tag || 'other').billable !== false;
}