Skip to content

Commit f138303

Browse files
Isolate Waterline panel failures behind error boundaries
Waterline's dashboard and run detail screens render every panel as a sibling inside one big Vue root. A runtime error in any single panel — a bad chart input, an unexpected response shape, a downstream renderer throwing during reactivity — crashes the whole tree and leaves the operator staring at a blank page. Phase 4 calls out this gap: one broken panel should not take down the whole view. Introduce a reusable `<error-boundary label="...">` component, register it globally, and wrap each independently-rendered panel: - Dashboard: Needs Attention, Fleet Trends, Fleet Overview, Workflow Type Health, Overview, Operator Metrics - Run detail: Run Summary and Run Details When a child throws, `errorCaptured` marks the boundary faulted and renders a compact fallback card with the panel label, a Retry button that re-mounts the subtree, a "Show technical detail" toggle surfacing the original message and Vue component-hint, and a `role="alert"` announcement. The hook returns `false` so the error does not bubble up and take out neighboring panels. A final `Vue.config.errorHandler` catches anything that escapes the boundaries and logs it instead of surfacing an empty page. Built bundle refreshed.
1 parent 7d8d999 commit f138303

6 files changed

Lines changed: 121 additions & 2 deletions

File tree

public/app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/mix-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"/app.js": "/app.js?id=c144ad7f39cca91fb3ea4bb9a923411e",
2+
"/app.js": "/app.js?id=2f973f63155a376cc333a7ac5e3afd22",
33
"/app-dark.css": "/app-dark.css?id=525cb983f9bc1a2e58ffad67a5b5309c",
44
"/app.css": "/app.css?id=e4c41d7a9ff1ef3e68de8cbec886a315",
55
"/img/favicon.png": "/img/favicon.png?id=7c006241b093796d6abfa3049df93a59",

resources/js/app.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import VueRouter from 'vue-router';
66
import VueJsonPretty from 'vue-json-pretty';
77
import VueApexCharts from 'vue-apexcharts';
88
import { PrismEditor } from 'vue-prism-editor';
9+
import ErrorBoundary from './components/ErrorBoundary.vue';
910

1011
import 'vue-prism-editor/dist/prismeditor.min.css';
1112

@@ -49,6 +50,12 @@ Vue.use(VueApexCharts)
4950
Vue.component('apexchart', VueApexCharts)
5051
Vue.component('vue-json-pretty', VueJsonPretty);
5152
Vue.component('PrismEditor', PrismEditor);
53+
Vue.component('error-boundary', ErrorBoundary);
54+
55+
Vue.config.errorHandler = function (err, vm, info) {
56+
// eslint-disable-next-line no-console
57+
console.error('[Vue:errorHandler]', err, info);
58+
};
5259

5360
Vue.mixin(Base);
5461

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<template>
2+
<div>
3+
<div v-if="hasError" class="card mb-4 error-boundary-panel" role="alert" aria-live="assertive">
4+
<div class="card-body">
5+
<h6 class="mb-1">
6+
<span class="badge badge-danger mr-2" aria-hidden="true">!</span>
7+
{{ panelLabel }} did not render
8+
</h6>
9+
<p class="small text-muted mb-2">
10+
This panel ran into an unexpected error. Other panels on the page are not affected.
11+
Retry re-mounts the panel without reloading the page.
12+
</p>
13+
<p v-if="showDetails" class="small text-muted mb-2 error-boundary-detail">
14+
<code>{{ errorMessage }}</code>
15+
</p>
16+
<div>
17+
<button type="button" class="btn btn-sm btn-outline-secondary mr-2" @click="retry">
18+
Retry
19+
</button>
20+
<button type="button" class="btn btn-sm btn-link" @click="showDetails = !showDetails">
21+
{{ showDetails ? 'Hide' : 'Show' }} technical detail
22+
</button>
23+
</div>
24+
</div>
25+
</div>
26+
<template v-else>
27+
<slot />
28+
</template>
29+
</div>
30+
</template>
31+
32+
<script>
33+
export default {
34+
name: 'ErrorBoundary',
35+
36+
props: {
37+
label: {
38+
type: String,
39+
default: '',
40+
},
41+
},
42+
43+
data() {
44+
return {
45+
hasError: false,
46+
errorMessage: '',
47+
retryKey: 0,
48+
showDetails: false,
49+
};
50+
},
51+
52+
computed: {
53+
panelLabel() {
54+
return this.label ? this.label : 'This panel';
55+
},
56+
},
57+
58+
errorCaptured(err, vm, info) {
59+
this.hasError = true;
60+
this.errorMessage = this.describeError(err, info);
61+
62+
// Surface to devtools so developers can still see the original stack.
63+
// eslint-disable-next-line no-console
64+
console.error('[ErrorBoundary:' + this.panelLabel + ']', err, info);
65+
66+
// Stop propagation — the parent view should stay mounted.
67+
return false;
68+
},
69+
70+
methods: {
71+
describeError(err, info) {
72+
const message = err && err.message ? err.message : String(err);
73+
return info ? message + ' (' + info + ')' : message;
74+
},
75+
76+
retry() {
77+
this.hasError = false;
78+
this.errorMessage = '';
79+
this.showDetails = false;
80+
this.retryKey += 1;
81+
this.$emit('retry');
82+
},
83+
},
84+
};
85+
</script>
86+
87+
<style scoped>
88+
.error-boundary-panel {
89+
border-left: 3px solid var(--danger, #aa2e28);
90+
}
91+
92+
.error-boundary-detail code {
93+
word-break: break-word;
94+
white-space: pre-wrap;
95+
}
96+
</style>

resources/js/screens/dashboard.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@
566566
</div>
567567
568568
<!-- Needs Attention Alerts (WCAG 4.1.3: Status Messages) -->
569+
<error-boundary label="Needs Attention">
569570
<div class="card mb-4"
570571
v-if="stats.needs_attention && stats.needs_attention.total_alerts > 0"
571572
role="alert"
@@ -588,9 +589,11 @@
588589
</div>
589590
</div>
590591
</div>
592+
</error-boundary>
591593
592594
<!-- Fleet Overview Trends -->
593595
<!-- Fleet Trends Chart -->
596+
<error-boundary label="Fleet Trends">
594597
<div class="card mb-4" v-if="stats.fleet_trends_series && stats.fleet_trends_series.timestamps.length > 0">
595598
<div class="card-header">
596599
<h5>Fleet Trends</h5>
@@ -609,7 +612,9 @@
609612
</div>
610613
</div>
611614
</div>
615+
</error-boundary>
612616
617+
<error-boundary label="Fleet Overview">
613618
<div class="card mb-4" v-if="stats.fleet_overview">
614619
<div class="card-header">
615620
<h5>Fleet Overview</h5>
@@ -665,8 +670,10 @@
665670
</div>
666671
</div>
667672
</div>
673+
</error-boundary>
668674
669675
<!-- Workflow Type Health -->
676+
<error-boundary label="Workflow Type Health">
670677
<div class="card mb-4" v-if="stats.workflow_type_health && stats.workflow_type_health.length > 0">
671678
<div class="card-header">
672679
<h5>Workflow Type Health</h5>
@@ -747,7 +754,9 @@
747754
</div>
748755
</div>
749756
</div>
757+
</error-boundary>
750758
759+
<error-boundary label="Overview">
751760
<div class="card">
752761
<div class="card-header d-flex align-items-center justify-content-between">
753762
<h5>
@@ -868,7 +877,9 @@
868877
869878
</div>
870879
</div>
880+
</error-boundary>
871881
882+
<error-boundary label="Operator Metrics">
872883
<div class="card mt-4" v-if="stats.operator_metrics">
873884
<div class="card-header d-flex align-items-center justify-content-between">
874885
<h5>Operator Metrics</h5>
@@ -1169,6 +1180,7 @@
11691180
</div>
11701181
</div>
11711182
</div>
1183+
</error-boundary>
11721184
11731185
</template>
11741186
</div>

resources/js/screens/flows/flow.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<template>
22
<div>
3+
<error-boundary label="Run Summary">
34
<div class="card" v-if="ready" aria-labelledby="runSummaryHeading">
45
<div class="card-header d-flex align-items-center justify-content-between flex-wrap">
56
<div class="d-flex align-items-center mr-3">
@@ -131,7 +132,9 @@
131132
</div>
132133
</div>
133134
</div>
135+
</error-boundary>
134136

137+
<error-boundary label="Run Details">
135138
<div :class="ready ? 'card mt-4' : 'card'" :aria-labelledby="ready ? 'runDetailsHeading' : null">
136139
<div class="card-header d-flex align-items-center justify-content-between">
137140
<h5 v-if="!ready" class="mb-0">Run Detail</h5>
@@ -1615,6 +1618,7 @@
16151618
</table>
16161619
</div>
16171620
</div>
1621+
</error-boundary>
16181622
</div>
16191623
</template>
16201624

0 commit comments

Comments
 (0)