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
- Guard the call site: only invoke loadOlderLogs when messages[0] instanceof LoadMoreLogEntry.
- 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.
- Ensure code that rebuilds messages (event stream handlers) always re-inserts the LoadMoreLogEntry sentinel at index 0 when more history exists.
- 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
- Only bind the load-more handler to the actual LoadMoreLogEntry instance at index 0.
- Rebuild or cancel load-more state whenever messages are reset by reconnects, filters, or container switches.
- Keep the invariant that every messages rebuild re-inserts the LoadMoreLogEntry sentinel when history remains.
- Treat the throw as a benign no-op in UI code rather than letting it bubble.
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)