Home mosaic, multi-select delete, offline banner and loading states
- Home puts the dashboards beside the flows: two equal-height columns, capped and scrollable, most recently worked on first. Each tile is a schematic footprint built from the stored widget placements. - Flows and dashboards can be picked by long press or ctrl-click; the create button becomes a trash and one dialog covers the batch. - The offline banner is drawn on the body so it centres on the viewport, and the live socket now releases the offline latch a stray 503 set. - A boot spinner before React's first commit, a router pending screen for code-split pages, and skeletons where an empty list used to flash. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tULRZJUkZsw7rMJ3h4xvu
This commit is contained in:
@@ -16,6 +16,16 @@ type Connection = {
|
||||
}
|
||||
|
||||
let state: Connection = { offline: false, lastSeen: null }
|
||||
/**
|
||||
* Whether the live socket is up right now.
|
||||
*
|
||||
* The socket is the continuous signal; a failed request is only a lagging one.
|
||||
* Latching `offline` on a single refused request is what used to leave the
|
||||
* banner up for good: the socket had never dropped, so no reconnect was coming
|
||||
* to clear it, and nothing else on a quiet page refetches. Reloading was the
|
||||
* only way out.
|
||||
*/
|
||||
let socketOpen = false
|
||||
const listeners = new Set<() => void>()
|
||||
|
||||
function emit(next: Connection) {
|
||||
@@ -28,6 +38,10 @@ function emit(next: Connection) {
|
||||
|
||||
export const connectionStore = {
|
||||
setOffline(lastSeen: string | number | null) {
|
||||
// The socket is up, so the installation is reachable: this was one bad
|
||||
// answer rather than a lost tunnel, and the banner would have nothing to
|
||||
// take it back down again.
|
||||
if (socketOpen) return
|
||||
const at =
|
||||
typeof lastSeen === "string"
|
||||
? Date.parse(lastSeen)
|
||||
@@ -39,6 +53,17 @@ export const connectionStore = {
|
||||
setOnline() {
|
||||
emit({ offline: false, lastSeen: null })
|
||||
},
|
||||
/**
|
||||
* The live socket opened or closed.
|
||||
*
|
||||
* An open socket is proof the installation can be heard, so it also clears
|
||||
* the banner — and since the socket reconnects on its own, that is the one
|
||||
* release the offline state can always count on.
|
||||
*/
|
||||
setSocketOpen(open: boolean) {
|
||||
socketOpen = open
|
||||
if (open) emit({ offline: false, lastSeen: null })
|
||||
},
|
||||
subscribe(listener: () => void) {
|
||||
listeners.add(listener)
|
||||
return () => listeners.delete(listener)
|
||||
|
||||
Reference in New Issue
Block a user