Source: 18-dailylog.js

// ── 18-dailylog.js — Daily Log feed builder + note input ──

/**
 * Builds a chronologically sorted array of feed items for the Log view.
 * Merges time entries, log notes, and task status comments for the given day.
 *
 * Item types:
 *  - `'entry'`        — a time-tracked entry; includes `entryId`.
 *  - `'note'`         — a freeform log note; text is wrapped in `<em>`.
 *  - `'session-note'` — a note attached to a running/completed entry;
 *                       includes `parentEntryId`. Renderers nest these
 *                       inside their parent entry row rather than as
 *                       standalone timeline rows.
 *  - `'task'`         — a plan-task status comment.
 *
 * @param {string} dateKey - YYYY-MM-DD date string.
 * @returns {Array<{ts: number, type: string, entryId: (string|undefined), parentEntryId: (string|undefined), color: string, text: string, sub: string}>}
 */
function buildDailyLogItems(dateKey) {
  const items = [];

  entries
    .filter((e) => e.date === dateKey)
    .forEach((e) => {
      const cat = getCat(e.tag);
      items.push({
        ts: e.ts,
        type: 'entry',
        entryId: e.id,
        color: cat.color,
        text: escHtml(e.text),
        sub: `${escHtml(cat.label)} · ${e.tsEnd ? fmtDur(e.tsEnd - e.ts) : 'ongoing'} · ${sigSymbol(e)}`,
      });
    });

  logNotes
    .filter((n) => n.date === dateKey)
    .forEach((n) => {
      if (n.type === 'session-note') {
        // Session-notes render nested under their parent entry, not as standalone rows.
        items.push({
          ts: n.ts,
          type: 'session-note',
          parentEntryId: n.entryId,
          color: 'var(--bg3)',
          text: escHtml(n.text),
          sub: '',
        });
      } else {
        items.push({
          ts: n.ts,
          type: 'note',
          color: 'var(--bg3)',
          text: `<em>${escHtml(n.text)}</em>`,
          sub: 'Note',
        });
      }
    });

  planTasks
    .filter((t) => t.date === dateKey && Array.isArray(t.statusComments))
    .forEach((t) => {
      t.statusComments.forEach((c) => {
        if (dk(new Date(c.ts)) === dateKey) {
          items.push({
            ts: c.ts,
            type: 'task',
            color: '#ef9f27',
            text: `<span class="tl-task-name">${escHtml(t.text)}</span> — ${escHtml(c.text)}`,
            sub: 'Task update',
          });
        }
      });
    });

  return items.sort((a, b) => a.ts - b.ts);
}

/** Reads the note input, appends a note to logNotes, persists, and re-renders. */
function addLogNote() {
  const inp = document.getElementById('dailyLogNoteInput');
  const text = inp ? inp.value.trim() : '';
  if (!text) {
    wlLog.info('addLogNote: rejected — empty input');
    return;
  }
  logNotes.push({ id: Date.now() + '', text, ts: Date.now(), date: dk(new Date()), type: 'note' });
  saveLogNotes();
  if (inp) inp.value = '';
  wlLog.info('addLogNote: note saved', { length: text.length });
  renderTodayFlow();
}