amir20/dozzle · warning · Error

No loadMoreLogEntry on first item

Error message

No loadMoreLogEntry on first item

What it means

loadOlderLogs() implements the "load more history" affordance: the first element of the messages array must be the LoadMoreLogEntry placeholder that carries the loading state and cursor. If it is not — meaning the internal message-list invariant is broken — it throws "No loadMoreLogEntry on first item" rather than silently loading nothing. This is a programming/invariant error, not a runtime data error.

Solutions

  1. Guard the call site: only invoke loadOlderLogs when messages[0] instanceof LoadMoreLogEntry.
  2. Check for race conditions where the messages array is reset (reconnect, filter, container switch) between rendering the LoadMoreLogEntry and the click; re-validate or cancel the callback on reset.
  3. Ensure code that rebuilds messages (event stream handlers) always re-inserts the LoadMoreLogEntry sentinel at index 0 when more history exists.
  4. Wrap the call in try/catch and treat the throw as "no more history available" for the current view state.

Example fix

// before
await loadOlderLogs(entry); // throws if messages[0] changed
// after
const first = messages.value[0];
if (first instanceof LoadMoreLogEntry) {
  await loadOlderLogs(first);
}
Defensive patterns

Strategy: type-guard

Type guard

function canLoadOlder(msgs: LogEntry[]): msgs is [LoadMoreLogEntry, ...LogEntry[]] {
  return msgs.length > 0 && msgs[0] instanceof LoadMoreLogEntry;
}

Try / catch

try {
  await loadOlderLogs(entry);
} catch {
  // sentinel gone: view was reset, nothing to load
}

Prevention

When it happens

Trigger: Calling loadOlderLogs(entry) when messages.value[0] is a normal log entry, a SkippedLogsEntry, or the array is empty/starts with something else — typically because a stream reset, filter change, or container switch replaced the messages array while the LoadMoreLogEntry component was still mounted and triggered its callback.

Common situations: Clicking "load older logs" right after a live-stream reconnect cleared and rebuilt the messages array; applying a search/filter that rewrites messages synchronously while the load-more item is being clicked; calling loadOlderLogs from custom code without ensuring the sentinel entry is at index 0.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/8532434c4e0c1e77. Report an issue: GitHub.

Appendix: source

Thrown at assets/composable/logLoader.ts:19

import { ShallowRef, type Ref } from "vue";
import { type LogMessage, LogEntry, LoadMoreLogEntry, SkippedLogsEntry } from "@/models/LogEntry";
import { Container } from "@/models/Container";
import { loadBetween } from "@/composable/loadBetween";
import { useAlertMerger } from "@/composable/alertMerger";

// Matches the rolling window size used for stats history
const LOG_WINDOW_FOR_DELTA = 300;

export function useLogLoader(
  messages: ShallowRef<LogEntry<LogMessage>[]>,
  containers: Ref<Container[]>,
  params: Ref<URLSearchParams>,
  loadingMore: Ref<boolean>,
) {
  const { withAlerts, decorateVisible } = useAlertMerger(messages, containers, params);

  async function loadOlderLogs(entry: LoadMoreLogEntry) {
    if (!(messages.value[0] instanceof LoadMoreLogEntry)) throw new Error("No loadMoreLogEntry on first item");
    if (containers.value.length === 0) return;

    const [loader, ...existingLogs] = messages.value;
    if (existingLogs.length === 0) return;

    const containerIDs = new Set(containers.value.map((c) => c.id));
    const earliestByContainer = new Map<string, LogEntry<LogMessage>>();
    const countByContainer = new Map<string, number>();
    const nthByContainer = new Map<string, LogEntry<LogMessage>>();
    for (const log of existingLogs) {
      const id = log.containerID;
      if (!id || !containerIDs.has(id)) continue;
      if (!earliestByContainer.has(id)) {
        earliestByContainer.set(id, log);
      }
      const count = (countByContainer.get(id) ?? 0) + 1;
      countByContainer.set(id, count);
      if (count <= LOG_WINDOW_FOR_DELTA) {

View on GitHub (pinned to d9463cbe21)