feat: the dot beside the brand now means the whole stack (milestone 365 step 4)
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 5s
CI / extension-version (push) Successful in 5s
Build images / build-ml (push) Successful in 7s
Build images / build-agent (push) Successful in 9s
CI / frontend-build (push) Successful in 23s
CI / backend-lint-and-test (push) Successful in 31s
Build images / build-web (push) Successful in 55s
Build images / smoke-web (push) Skipped
Build images / promote (push) Skipped
CI / integration (push) Successful in 1m43s
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 5s
CI / extension-version (push) Successful in 5s
Build images / build-ml (push) Successful in 7s
Build images / build-agent (push) Successful in 9s
CI / frontend-build (push) Successful in 23s
CI / backend-lint-and-test (push) Successful in 31s
Build images / build-web (push) Successful in 55s
Build images / smoke-web (push) Skipped
Build images / promote (push) Skipped
CI / integration (push) Successful in 1m43s
The ask was a surface AND a path. The path is the part that was missing — everything that could answer "is it running" lived inside Settings, which you only open once you already suspect something. **Re-used the indicator that already existed rather than adding a fourth.** There were three partial surfaces: TopNav's health dot, PipelineStatusChip's pulse, and the Settings Activity tab. None answered "is every part alive", and a fourth would have made the question harder to answer, not easier. TopNav's dot read /api/health — a no-DB liveness check proving only that the WEB container is serving. Green there while a worker was dead is exactly what it looked like, and a green dot beside the product name gets read as "everything is fine". It now reflects the whole-stack verdict, and it is a link: the place someone already looks when they suspect something is now also the way to the detail. The tooltip names the actual problem. "Scheduler has not checked in for 6 min" sends someone somewhere; "something is unhealthy" sends them hunting. /system is deliberately NOT in the nav row — TopNav builds that from routes with a meta.title, and a sixth top-level tab for a page visited twice a year costs more attention than it returns. It is reached from the dot. The page lists every learned part with its state as a sentence rather than a chip, and prints the staleness thresholds it was judged by, taken from the endpoint so the UI keeps no second copy of them. PipelineStatusChip still hand-rolls its own 3-minute scheduler window; that is now a duplicate of a threshold the server owns, and worth collapsing once this has been watched working. The stores stay separate on purpose: system.js is "can I reach the API", systemActivity.js is "what is the pipeline doing", systemHealth.js is "is anything broken". Running and alive fail independently — an idle stack with a dead worker looks identical to a healthy one on every activity surface, which is the whole reason this milestone exists. Not yet verified against a real stopped service. Rule 12 keeps a local stack out of it, and frontend CI has no Vue type-check or visual regression, so this needs an operator look rather than a green lane. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTjbZZ6JirCMSaJzQV1RhA
This commit is contained in:
@@ -4,7 +4,12 @@ import { useApi } from '../composables/useApi.js'
|
||||
|
||||
export const useSystemStore = defineStore('system', () => {
|
||||
const api = useApi()
|
||||
const healthy = ref(null) // null=unknown, true=ok, false=down
|
||||
// NOT what the nav dot reads any more (milestone 365): that is the
|
||||
// whole-stack verdict in systemHealth.js. /api/health only proves the web
|
||||
// container is serving, which is why a green dot here sat happily beside a
|
||||
// dead worker. refreshHealth() is still called — it is also how build/version
|
||||
// info arrives — so this stays as its by-product rather than its purpose.
|
||||
const healthy = ref(null)
|
||||
// What the instance says it is. Since milestone 318 stopped publishing
|
||||
// version image tags, this is the only answer to "which build is this?" —
|
||||
// there is no registry name left to check it against.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useApi } from '../composables/useApi.js'
|
||||
|
||||
// Whole-stack health: is every part of FabledCurator running (milestone 365)?
|
||||
//
|
||||
// Distinct from `system.js`, which polls /api/health — a no-DB liveness check
|
||||
// that only proves the web container is serving. That endpoint answers "can I
|
||||
// reach the API"; this one answers "is anything broken", which is the question
|
||||
// a green dot beside the brand was already being read as answering.
|
||||
//
|
||||
// Also distinct from `systemActivity.js`, which is about what the pipeline is
|
||||
// DOING — queue depths, running tasks, failures. Running and alive are
|
||||
// different questions and they fail independently: a perfectly idle stack with
|
||||
// a dead worker looks identical to a healthy one on the activity surfaces.
|
||||
export const useSystemHealthStore = defineStore('systemHealth', () => {
|
||||
const api = useApi()
|
||||
|
||||
const overall = ref(null) // null until the first answer: unknown ≠ ok
|
||||
const parts = ref([])
|
||||
const checkedAt = ref(null)
|
||||
const thresholds = ref(null) // server-owned, so the UI keeps no second copy
|
||||
const lastError = ref(null)
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const body = await api.get('/api/system/health')
|
||||
overall.value = body.overall
|
||||
parts.value = body.parts || []
|
||||
checkedAt.value = body.checked_at
|
||||
thresholds.value = body.thresholds || null
|
||||
lastError.value = null
|
||||
} catch (e) {
|
||||
// The endpoint is built never to fail because a dependency failed, so a
|
||||
// throw here means the API itself is unreachable — which is its own kind
|
||||
// of unhealthy and must not be shown as "ok".
|
||||
lastError.value = e.message
|
||||
overall.value = 'unknown'
|
||||
}
|
||||
return overall.value
|
||||
}
|
||||
|
||||
// The parts worth naming in a tooltip — everything that is not ok, worst
|
||||
// first. The endpoint already sorts that way.
|
||||
const problems = computed(() => parts.value.filter(p => p.state !== 'ok'))
|
||||
|
||||
return { overall, parts, checkedAt, thresholds, lastError, problems, refresh }
|
||||
})
|
||||
Reference in New Issue
Block a user