A socket is not a source
T04P’s browser connection stayed healthy while one upstream feed stopped, proving that transport and knowledge needed separate state.
T04P’s first status indicator answered the wrong question accurately.
The browser maintained a Socket.IO connection to a Node.js service. While that connection was open, a green light pulsed in the corner. Updates arrived without refreshing the page. The interface looked alive.
I paused one upstream simulator and left the browser connection untouched. The green light continued pulsing. The entry count stopped changing, but a quiet event could plausibly produce the same screen. Nothing told the reader that the service had stopped receiving observations from one source.
The socket was healthy. The data was stale.
I had used transport state as a proxy for product truth. The transport could only report whether the browser and dashboard service could exchange messages. It knew nothing about the sensor adapter, polling job, source clock, or continuity of the values displayed.
The fix began by giving those facts separate names.
“Connected” had hidden a chain
The path from an event source to the screen contained several boundaries:
source → adapter → dashboard service → socket → browser state → rendered valueAn update could stop at any one of them.
The source might stop observing. The adapter might fail to poll. The service might reject or delay an event. The socket might disconnect. The browser might receive an event and fail to apply it. The UI might render an older projection.
One green light at the last transport boundary compressed the whole chain into a claim it could not support.
I wrote down what each layer could actually prove:
- Source observation time: when the source says it observed the value.
- Adapter receipt: when the dashboard-side adapter received or obtained it.
- Service revision: where the event entered the normalized sequence.
- Socket heartbeat: whether browser and service can currently exchange heartbeat messages.
- Browser revision: the last contiguous revision applied to local state.
- Render state: the revision represented by the current view.
The list looked more complicated than connected: true. The system had always contained that complexity. The boolean had hidden it from both code and interface.
T04P kept a transport heartbeat and introduced a source heartbeat.
The transport heartbeat traveled between browser and dashboard service. It answered whether that path was open and responsive within a chosen interval.
The source heartbeat came from each adapter. Depending on the source, it could mean a new observation arrived, a poll succeeded, or the upstream system emitted a health event. The adapter declared the narrow meaning.
A successful poll did not always prove the physical value had changed recently. An API could return the same cached record. The feed state therefore considered both adapter contact and latest source observation.
The distinction created useful combinations:
- Transport open, source current: ordinary operation.
- Transport open, source stale: browser can reach the service; service lacks recent source evidence.
- Transport closed, source current at service: browser is disconnected; service may still be observing.
- Transport recovering, source unavailable: both paths need attention, for different reasons.
The interface could now describe partial failure instead of turning the entire dashboard red or leaving it green.
The first source-state rule used one timeout for every feed. Entry counts usually changed several times a minute. Equipment status could remain valid for longer. Operator notes arrived only when someone had something to add.
One timeout made the slow feed look constantly broken or let the fast feed remain falsely current.
Each adapter declared an expected observation pattern and freshness thresholds. The rule could include:
- Expected maximum interval under normal operation.
- Grace interval for ordinary jitter.
- Point at which the last value becomes stale but remains useful with its age.
- Point at which the value should be replaced by an unavailable explanation.
The state was not calculated from whether the value had changed. A stable equipment status could be re-observed and remain current. The entry count needed evidence that the source was still reporting, even if the count happened not to advance.
This made freshness a domain property rather than a socket property. The network delivered evidence. The feed contract said how long that evidence supported a claim about the present.
Last known did not mean current
When a feed became stale, I first replaced its value with a blank warning. That avoided false confidence and removed information that could still help.
The revised design made a per-feed decision. A last known equipment state could remain useful if the observation time was prominent. A delayed entry count might still show the scale of recent activity. A safety-critical signal could become unavailable as soon as recency could not be defended.
The interface separated the value from its status:
lastValue
observedAt
freshness
continuityThe value did not become null merely because its interpretation changed. Freshness told the reader whether it described the present. Continuity told whether the browser knew it had received every revision.
This allowed language such as “Last observed 4 minutes ago” instead of either showing the number as current or erasing it.
The design treated stale data as data with a changed claim, not a CSS class added to the same meaning.
The adapter’s health was not the source’s health
One polling adapter returned success whenever its request completed. The upstream API sometimes returned an old snapshot with a current HTTP date. The adapter reported healthy while the source observation remained unchanged.
I split adapter contact from source progress.
Adapter contact answered whether the integration path worked. Source progress answered whether new observations or explicit reaffirmations arrived according to the feed contract.
The diagnostic view showed both. The main view translated them into current, delayed, stale, or unavailable.
I avoided calling lack of change a failure universally. A source that explicitly reported “still in the same state” was making progress. A source that repeated an old record without a new observation time was not.
This required the normalized event to preserve source identity and source time instead of stamping every polled response as a fresh observation.
The adapter could not improve evidence by relabeling it. Its job was to state what it received and what the integration path could prove.
The browser connection could recover before the data
After a short socket interruption, the browser reconnected quickly. I cleared the disconnected warning as soon as the transport opened.
The browser might have missed events. A new message after reconnect proved the feed was active and did not prove the local state was complete.
I added a recovery state between connection and current display. The browser sent its last fully applied revision. The service returned a contiguous delta if it still had every missing event or a complete snapshot otherwise.
Until that exchange completed, transport was open and continuity was recovering. The UI could receive heartbeats and remain explicit that its data projection was not yet confirmed.
This prevented one of the most seductive real-time mistakes: treating the first new event as permission to trust all existing state.
The socket library had restored delivery. The application protocol still needed to restore knowledge.
Revisions were more useful than receipt order
The browser initially applied events in the order callbacks fired. Reconnect, delayed adapters, and duplicate delivery challenged that assumption.
Each normalized feed event received a monotonic service revision. The browser tracked the last contiguous revision it had applied. A duplicate could be recognized. A gap moved continuity to unknown and triggered recovery.
Source events also retained their own identity and sequence where available. The service revision described order inside T04P; it did not rewrite the source’s history.
This distinction mattered when a late source event entered after newer events from another source. The dashboard could position it by source time in a historical view while still recording when the service learned about it.
For the current projection, feed-specific revisions were enough. I did not force all sources into one perfectly meaningful global order.
A revision did not make data true. It made delivery continuity inspectable.
The green light had encouraged the wrong reading
The pulsing indicator used motion to say “live.” It drew attention to the health of the least informative boundary.
I removed the pulse. Current data became visually quiet. A feed gained visible status as its evidence weakened: age, delayed label, stale treatment, or unavailable explanation.
Transport state moved into a small global status and diagnostic detail. A disconnected browser still needed to know why every feed had stopped updating. An open connection did not deserve celebratory emphasis.
The revised hierarchy matched the product question. The reader cared whether entry, equipment, and notes described the event. Socket status helped explain a problem and was not the product’s headline.
Color no longer carried the state alone. Text and shape distinguished delayed, stale, unavailable, and reconnecting states.
The interface became calmer and more honest at the same time.
The first diagnostic output printed socket connect and disconnect events plus every incoming message. During a problem, the log was active and unclear.
I built a small diagnostic view organized around the newly separated facts:
- Transport state and last heartbeat round trip.
- Adapter contact and latest successful poll.
- Latest source observation and expected interval.
- Service revision and browser-applied revision.
- Duplicate, skipped, and out-of-order event counts.
- Last recovery method: delta or snapshot.
- Next expected freshness transition.
The view did not need polished dashboard graphics. It needed stable labels that corresponded to the code’s state model.
Logs recorded transitions such as “entry feed moved from current to stale after observation age crossed 90 seconds.” They did not print the unchanged count every second.
The diagnostics made a failure explainable without reading the implementation. That was my first practical encounter with observability as a designed interface.
Simulating the split failures
I extended the source simulator so each boundary could fail independently.
One scenario paused a source while keeping its adapter process and browser socket alive. Another stopped the adapter while other feeds continued. A third disconnected the browser while the service kept receiving. A fourth delivered one fresh event after a revision gap.
For each scenario, I recorded the expected facts and visible state:
transport: open
adapter: reachable
source observation: stale
continuity: complete
visible result: last value with age and stale labelThe scenario assertion did not compare pixels. It checked the state transitions, then manual review checked whether the interface communicated them.
This prevented future refactoring from recombining facts accidentally. A new transport library could not simplify the product back to connected without failing the scenarios.
The simulator also exposed a limit: not every source could provide a dependable observation heartbeat. The honest state for those feeds carried wider uncertainty.
Separating source from socket did not make the source authoritative about reality.
A sensor could repeat a wrong value. An API could report its own health inaccurately. A human note could be mistaken. T04P could preserve identity, time, and continuity; it could not guarantee that the upstream observation matched the physical event.
I kept source labels and allowed an attributed operator note to sit beside automated evidence. A note did not overwrite the feed. It could say that a physical check contradicted the displayed equipment state.
The interface distinguished “source unavailable” from “source reports unavailable.” The first described T04P’s evidence path. The second was a source observation.
This avoided turning the dashboard into an oracle after removing one misleading green light. Every status had a scope.
The goal was defensible truth about what the system knew, not certainty about a world it observed indirectly.
Alerting used the same boundaries
The early alert fired when the browser socket disconnected. That was useful for the local display and did not detect the paused source that triggered the project.
I moved alerts to source freshness and adapter health, with separate severity and context. A dashboard transport alert said the shared view could not receive updates. A feed freshness alert said one observation stream no longer supported a current claim.
Thresholds matched feed expectations. A delayed event produced a lower-severity state before complete unavailability. Recovery required new source evidence or an explicit snapshot, not merely an adapter process restart.
The alert message included source, last observation, last adapter contact, expected interval, and dashboard transport state. It avoided asking the recipient to reconstruct the boundary from several tools.
I kept the alert set small. The project was a personal lab and did not need an imaginary operations center. The design exercise still benefited from precise claims.
An alert is useful when it names what evidence has weakened and which capability is affected.
The Node.js service exposed one health endpoint that returned success when the process loop responded. A load balancer could use that check, and a reader could not use it to infer feed health.
I kept the process-readiness endpoint narrow and added a diagnostic status resource whose fields matched the product model. It reported transport service readiness, adapter contact, latest source observation, revision, and current freshness classification per feed.
The resource did not return one aggregate green status by averaging the feeds. A partial failure remained partial. Consumers could decide whether their specific task required every feed.
Public display code consumed the same projection but not the diagnostic detail indiscriminately. Internal timestamps and source identifiers were reduced to the context a reader needed. The raw status resource remained constrained to the personal test environment.
This separation prevented infrastructure health from inheriting product semantics. A process could be ready to serve an honest “source unavailable” page. Marking it unhealthy and restarting it would not restore the paused source.
The correct recovery depended on the boundary. Process readiness could trigger restart. Adapter failure could trigger reconnection. Source staleness could require upstream inspection. Browser transport failure could ask the page to reconnect.
One generic health flag had made all four look like restart problems. Clear boundaries produced clearer interventions.
I used the socket’s open state as a proxy for every upstream dependency. The source pause showed that the proxy could remain healthy while the product was wrong.
I added one global freshness timeout and ignored feed cadence. Per-feed contracts replaced it.
I treated adapter contact as source observation. Preserving source time and progress separated integration health from knowledge.
I cleared warnings on reconnect before restoring event continuity. A recovery handshake created an explicit intermediate state.
I replaced stale values entirely and removed useful context. Per-feed policy kept last known data with an honest age where appropriate.
I added more status indicators before changing the hierarchy. Removing the green pulse and emphasizing feed evidence made the interface clearer with less motion.
Each mistake came from compressing a chain into one convenient word.
The reusable pattern
T04P’s first durable real-time pattern was a set of separations:
- Transport health is not source health.
- Adapter contact is not source progress.
- Recent delivery is not recent observation.
- New data after reconnect is not proven continuity.
- Last known value is not current value.
- Source evidence is not physical certainty.
The distinctions were not academic. Each supported a different failure message and recovery action.
I did not yet know how far the pattern would travel. For T04P, it was enough to stop compressing transport, continuity, freshness, and source confidence into one green light.
The scale and technology changed. The design instinct stayed the same: when a status word governs trust, unpack the facts it compresses before building the indicator.
The next article reconstructs the incident that made this lesson impossible to ignore. The dashboard did not crash or show an error. It kept doing something worse: presenting a plausible old moment as the present.