feat(terminal): read from where the panel got to, so no log line is missed - #40
Open
MorningLightMountain713 wants to merge 3 commits into
Open
feat(terminal): read from where the panel got to, so no log line is missed#40MorningLightMountain713 wants to merge 3 commits into
MorningLightMountain713 wants to merge 3 commits into
Conversation
…issed The panel asked for the last 100 lines every ten seconds and replaced its view with them, so a server writing more than 100 lines between two polls lost the rest with nothing to show it had happened. serverReadiness already recorded the consequence in its own comment - "applogpolling returns the tail of the log, so both are best-effort by nature". The node now hands back a position and answers it with only what came after, so the panel appends rather than replaces, and drains immediately when the node says more was waiting than one answer carried. The position is opaque and is never read here: nodes and this site upgrade independently, so reading it would make its shape a contract. It is sent as a query parameter because the route takes three optional path segments and a fourth would 404 on a node that predates this. A node that does not answer positions returns no cursor, and against that node the panel keeps doing exactly what it does today. A position belongs to ONE node's container - the same server on another node has its own timestamps - so it is dropped whenever masterIp or the container name changes. serverReadiness is deliberately untouched: it asks whether a server has finished starting, which is a tail question. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 4, 2026
…hind the line count The node answers two questions. `hasMore` is what lies ahead of the position just stored - the only thing another pass can fetch. `truncated` is the log holding more than the line count asked for, which is behind this panel and unreachable with a cursor, and which is true on nearly every first page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he poll when it does not A node that carries /applogs pushes lines as the server writes them, so the panel has no ten-second tick and no position arithmetic - a stream has no gap between polls to lose lines in. A node that predates it refuses the namespace and the poll simply carries on, which is permanent rather than transitional: the network runs several FluxOS versions at once and always will. The poll stands down only once the node has answered `subscribed`, so a node without the namespace is never left unpolled, and it resumes on a stream that ends or fails. The drain loop goes. A node answers a position with everything waiting for it, so there is never a second page to come straight back for - a panel further behind than one answer holds is moved to the end of the log and told, and no number of extra passes would reach the lines it went past. `skipped` says so, alongside the rolled-over notice where the lines are gone rather than declined.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reads an app's logs from where the viewer got to, so no line written between two polls is missed — and drops the response time of every poll from ~1500 ms to milliseconds.
Needs the FluxOS side first: RunOnFlux/flux#1794. Works unchanged against every node that does not have it yet — see Backwards compatibility below.
The problem
The viewer asked for the last N lines every few seconds and replaced its view with them. Anything an app wrote beyond N between two polls was never shown to anyone and could not be fetched afterwards.
sincedid not help. It was a box a user typed into, it never advanced, and docker appliestailaftersinceanyway — so a burst of 500 lines withtail: 100answers a reader asking for "everything since T" with the last 100 and no sign the rest existed.What changes
The panel takes whichever of three things the node it is talking to can offer, decided per node and per request with no version check anywhere.
A node that streams pushes lines over
/applogsas the server writes them. There is no tick and no position arithmetic — a stream has no gap between polls to lose lines in. The poll stands down only once the node has answeredsubscribed, and resumes if the stream ends or fails, so a node without the namespace is never left unpolled.A node that answers positions hands back an opaque position; the panel hands it back and is answered with only what came after, appending instead of replacing.
A node that does neither returns no
cursor, and against it the panel behaves exactly as it does today.Markers rather than silent gaps:
The position is never read here. Nodes and this app upgrade independently, so parsing it would make its shape a permanent contract. It is sent as a query parameter, because the route takes three optional path segments and a fourth would 404 on a node that predates this.
There is no drain loop. A node answers a position with everything waiting for it, so there is never a second page to come straight back for.
A position belongs to one node's container. The same app on another node has its own log with its own timestamps, so carrying a position across would ask that node about a moment that means nothing there and then skip lines to match a count taken from somewhere else. It is dropped whenever the target changes.
Backwards compatibility
A node that does not answer positions returns no
cursor, and against that node this behaves exactly as it does today. A node with no/applogsrefuses the connection and the poll simply carries on. No version check anywhere — the presence of the field, and the success of the connection, are the capability signals, decided per node and per request.The network runs several FluxOS versions at once and always will, so both fallbacks are permanent rather than transitional.
Deployment order
The reverse order does nothing harmful — a viewer shipped first would send a cursor every node ignores, get no cursor back, and sit in the fallback path — but there is no reason to do it.
Sibling PRs
The same change lands in three viewers, because all three had the same defect:
LogViewer.vueServerTerminal.jsxServerTerminal.jsxTesting
Proved end to end in a browser: this branch served by a dev server, pointed at a real node running flux#1794, against an app deployed for the purpose writing a numbered line every second — numbered so a gap and a repeat are both visible in the sequence.
The stream, proved from both ends at once. The live toggle is the only variable:
Polling cannot deliver a line without HTTP, and at the fallback interval there would have been several requests; there were none. The extra docker connection appears when the viewer subscribes and goes when it leaves — the node's follow stream, observed on the node itself.
233 lines contiguous, 0 duplicates, 0 gaps, appending rather than replacing, well past the line limit the replace behaviour could never exceed.
The fallback still answers. Against that same node, every request shape a client without streaming sends:
…/100→ 100 linestruncated: true;…/all→ 105,146 linestruncated: false;…/100/<date>→ 100 linestruncated: true; and a cursor four seconds old → exactly 4 lines.The node side carries its own unit and fleet coverage — 98 suites, 0 failures; see flux#1794.
🤖 Generated with Claude Code