refactor replace breadcrumbs with inline page headers

Remove the breadcrumb toolbar bar; pages now render their own sticky title and controls. Navbar active state provides sufficient context; aligns all pages with the weather/insights pattern.

Commit
e92743e43d221d6f00b2ff4440e7db2040670307
Author
gpt-5.6-sol high <agent@openai.com>
Author date
Committer
gpt-5.6-sol high <agent@openai.com>
Committer date
Changed files
roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Pages.pm
index 617b1b19..0cf81846 100644..100644
@@ -5,33 +5,24 @@
5 5
6 6 sub index ($self) {
7 7 $self->render(
8 Removed: template => 'dashboard/index',
9 Removed: probes => $self->probes,
10 Removed: breadcrumbs => [],
8 Added: template => 'dashboard/index',
9 Added: probes => $self->probes,
11 10 );
12 11 }
13 12
14 13 sub status ($self) {
15 14 $self->render(
16 Removed: template => 'dashboard/status',
17 Removed: items => $self->status_items,
18 Removed: nav_page => 'probes',
19 Removed: breadcrumbs => [
20 Removed: { label => 'Home', href => '/' },
21 Removed: { label => 'Probes', href => '/probes' },
22 Removed: { label => 'Status' },
23 Removed: ],
15 Added: template => 'dashboard/status',
16 Added: items => $self->status_items,
17 Added: nav_page => 'probes',
24 18 );
25 19 }
26 20
27 21 sub probe_list ($self) {
28 22 $self->render(
29 Removed: template => 'dashboard/probes',
30 Removed: probes => $self->probes,
31 Removed: nav_page => 'probes',
32 Removed: breadcrumbs =>
33 Removed: [ { label => 'Home', href => '/' }, { label => 'Probes' }, ],
34 Removed: graph_control_aria_label => 'All probe chart timeframes',
23 Added: template => 'dashboard/probes',
24 Added: probes => $self->probes,
25 Added: nav_page => 'probes',
35 26 );
36 27 }
37 28
@@ -46,34 +37,28 @@
46 37 $oldest =~ s/T.*\z//;
47 38
48 39 $self->render(
49 Removed: template => 'dashboard/downloads',
50 Removed: from => $from,
51 Removed: month_from => $month_from,
52 Removed: to => $to,
53 Removed: oldest => $oldest,
54 Removed: probes => $self->probes,
55 Removed: nav_page => 'downloads',
56 Removed: breadcrumbs =>
57 Removed: [ { label => 'Home', href => '/' }, { label => 'Downloads' }, ],
40 Added: template => 'dashboard/downloads',
41 Added: from => $from,
42 Added: month_from => $month_from,
43 Added: to => $to,
44 Added: oldest => $oldest,
45 Added: probes => $self->probes,
46 Added: nav_page => 'downloads',
58 47 );
59 48 }
60 49
61 50 sub insights ($self) {
62 51 $self->render(
63 Removed: template => 'dashboard/insights',
64 Removed: probes => $self->probes,
65 Removed: nav_page => 'insights',
66 Removed: breadcrumbs =>
67 Removed: [ { label => 'Home', href => '/' }, { label => 'Insights' }, ],
52 Added: template => 'dashboard/insights',
53 Added: probes => $self->probes,
54 Added: nav_page => 'insights',
68 55 );
69 56 }
70 57
71 58 sub weather ($self) {
72 59 $self->render(
73 Removed: template => 'dashboard/weather',
74 Removed: nav_page => 'weather',
75 Removed: breadcrumbs =>
76 Removed: [ { label => 'Home', href => '/' }, { label => 'Weather' }, ],
60 Added: template => 'dashboard/weather',
61 Added: nav_page => 'weather',
77 62 );
78 63 }
79 64
@@ -88,12 +73,6 @@
88 73 probe => $probe,
89 74 nav_page => 'probes',
90 75 single_probe => 1,
91 Removed: breadcrumbs => [
92 Removed: { label => 'Home', href => '/' },
93 Removed: { label => 'Probes', href => '/probes' },
94 Removed: { label => $probe->{label} },
95 Removed: ],
96 Removed: graph_control_aria_label => "$probe->{label} chart timeframe",
97 76 );
98 77 }
99 78
roles/dashboard/t/01-graph-page.t
index d5abd44a..ee0a5b69 100644..100644
@@ -11,19 +11,12 @@
11 11
12 12 $t->get_ok('/probes/ph')
13 13 ->status_is(200)
14 Removed: ->element_exists(
15 Removed: 'nav[aria-label="Breadcrumb"] a[href="/"]')
16 Removed: ->text_is( 'nav[aria-label="Breadcrumb"] a[href="/"]', 'Home' )
17 Removed: ->text_is( 'nav[aria-label="Breadcrumb"] a[href="/probes"]', 'Probes' )
18 Removed: ->text_is(
19 Removed: 'nav[aria-label="Breadcrumb"] [aria-current="page"]', 'pH' )
14 Added: ->text_is( 'h1', 'pH' )
15 Added: ->element_exists('.sticky-controls .graph-control')
20 16 ->text_is( '.card-header h1 a[href="/probes/ph"]', 'pH' )
21 17 ->element_exists('.navbar-nav a.nav-link.active.dropdown-toggle')
22 18 ->element_exists('section#probe-ph[data-panel="ph"] canvas#chart-ph')
23 19 ->element_exists_not('[data-status="ph"]')
24 Removed: ->element_exists(
25 Removed: 'div.toolbar-items div.graph-control .timeframe-control[role="group"]'
26 Removed: )
27 20 ->text_is( 'a[data-raw-download][data-probe="ph"]', 'Download data' )
28 21 ->element_exists(
29 22 'button[data-timeframe="day"].timeframe-button.btn-outline-secondary'
@@ -47,13 +40,10 @@
47 40
48 41 $t->get_ok('/probes')
49 42 ->status_is(200)
50 Removed: ->element_exists(
51 Removed: 'nav[aria-label="Breadcrumb"] a[href="/"]')
52 Removed: ->text_is(
53 Removed: 'nav[aria-label="Breadcrumb"] [aria-current="page"]', 'Probes' )
43 Added: ->text_is( 'h1', 'Probes' )
44 Added: ->element_exists('.sticky-controls .graph-control')
54 45 ->element_exists('main.container')
55 46 ->element_exists('div.probes-grid')
56 Removed: ->element_exists('div.toolbar-items div.graph-control')
57 47 ->element_exists('.navbar-nav a.nav-link.active.dropdown-toggle');
58 48
59 49 for my $probe (qw(ph do orp ec)) {
roles/dashboard/t/05-status-page.t
index 7d9e308d..6744b806 100644..100644
@@ -11,11 +11,7 @@
11 11
12 12 $t->get_ok('/probes/status')
13 13 ->status_is(200)
14 Removed: ->element_exists(
15 Removed: 'nav[aria-label="Breadcrumb"] a[href="/"]')
16 Removed: ->text_is(
17 Removed: 'nav[aria-label="Breadcrumb"] [aria-current="page"]', 'Status' )
18 Removed: ->text_is( 'h1', 'DAQ Status' )
14 Added: ->text_is( 'h1', 'Status' )
19 15 ->element_exists('main.container')
20 16 ->element_exists('.list-group');
21 17
roles/dashboard/t/06-downloads.t
index 6c9b0b5b..7c2cdd75 100644..100644
@@ -11,11 +11,7 @@
11 11
12 12 $t->get_ok('/downloads')
13 13 ->status_is(200)
14 Removed: ->element_exists(
15 Removed: 'nav[aria-label="Breadcrumb"] a[href="/"]')
16 Removed: ->text_is(
17 Removed: 'nav[aria-label="Breadcrumb"] [aria-current="page"]', 'Downloads' )
18 Removed: ->text_is( 'h1', 'Downloads' )
14 Added: ->text_is( 'h1', 'Downloads' )
19 15 ->element_exists('.navbar-nav a.nav-link.active[href="/downloads"][aria-current="page"]')
20 16 ->element_exists('form.downloads-form[action="/downloads/readings.csv"]')
21 17 ->element_exists('[data-downloads-error]')
roles/dashboard/t/08-insights.t
index 8ed12731..94ef847c 100644..100644
@@ -13,8 +13,7 @@
13 13 $t->get_ok('/insights')
14 14 ->status_is(200)
15 15 ->text_is('title', 'FAPG DAQ Insights')
16 Removed: ->element_exists('nav[aria-label="Breadcrumb"]')
17 Removed: ->text_like('.breadcrumb-item.active span', qr/Insights/)
16 Added: ->text_is('h1', 'Insights')
18 17 ->element_exists('.navbar-nav a.nav-link.active[href="/insights"]')
19 18 ->element_exists('[data-default-timeframe="day"]');
20 19 };
roles/dashboard/templates/dashboard/downloads.html.ep
index a567027b..bf3c4f7f 100644..100644
@@ -1,12 +1,14 @@
1 1 % layout 'default';
2 2 % title 'FAPG DAQ Downloads';
3 3
4 Removed: <div class="card">
5 Removed: <div class="card-header">
6 Removed: <h1 class="h5 mb-0">Downloads</h1>
7 Removed: <p class="text-body-secondary small mb-0 mt-1">Download recorded DAQ readings as a CSV or Excel file.</p>
4 Added: <section class="mb-4" aria-label="Data downloads">
5 Added: <div class="mb-3">
6 Added: <h1 class="h4 fw-bold mb-1">Downloads</h1>
7 Added: <p class="text-body-secondary small mb-0">Download recorded DAQ readings as a CSV or Excel file.</p>
8 8 </div>
9 9
10 Added: <div class="card">
11 Added:
10 12 <div class="card-body">
11 13 <div class="alert alert-danger d-none" role="alert" id="downloads-error" data-downloads-error></div>
12 14 <form action="/downloads/readings.csv" method="get" class="downloads-form d-flex flex-column gap-3">
@@ -71,5 +73,7 @@
71 73 </form>
72 74 </div>
73 75 </div>
76 Added:
77 Added: </section>
74 78
75 79 <script src="/js/downloads.js"></script>
roles/dashboard/templates/dashboard/graph.html.ep
index 1ffd99e7..b2f15199 100644..100644
@@ -2,6 +2,12 @@
2 2 % title "FAPG DAQ $probe->{label}";
3 3 % stash use_charts => 1;
4 4
5 Added: <section class="mb-4" aria-label="<%= $probe->{label} %> probe detail">
6 Added: <div class="d-flex flex-column flex-sm-row align-items-start align-items-sm-center justify-content-between gap-2 mb-3 sticky-controls">
7 Added: <h1 class="h4 fw-bold mb-0"><%= $probe->{label} %></h1>
8 Added: %= include 'dashboard/graph-control', aria_label => "$probe->{label} chart timeframe", default_timeframe => 'day'
9 Added: </div>
10 Added:
5 11 %= include 'dashboard/probe-card', probe => $probe
6 12
7 13 <div class="card mt-3" aria-label="Last ten reading messages for <%= $probe->{label} %>">
@@ -29,3 +35,5 @@
29 35 </div>
30 36 </div>
31 37 </div>
38 Added:
39 Added: </section>
roles/dashboard/templates/dashboard/probes.html.ep
index da7bd580..95ac1a41 100644..100644
@@ -2,10 +2,17 @@
2 2 % title 'FAPG DAQ Probes';
3 3 % stash use_charts => 1;
4 4
5 Removed: <div class="row g-3 probes-grid">
6 Removed: % for my $probe ($probes->@*) {
7 Removed: <div class="col-12 col-md-6 col-xl-3">
8 Removed: %= include 'dashboard/probe-card', probe => $probe
5 Added: <section class="mb-4" aria-label="Probe readings">
6 Added: <div class="d-flex flex-column flex-sm-row align-items-start align-items-sm-center justify-content-between gap-2 mb-3 sticky-controls">
7 Added: <h1 class="h4 fw-bold mb-0">Probes</h1>
8 Added: %= include 'dashboard/graph-control', aria_label => 'All probe chart timeframes', default_timeframe => 'day'
9 9 </div>
10 Removed: % }
11 Removed: </div>
10 Added:
11 Added: <div class="row g-3 probes-grid">
12 Added: % for my $probe ($probes->@*) {
13 Added: <div class="col-12 col-md-6 col-xl-3">
14 Added: %= include 'dashboard/probe-card', probe => $probe
15 Added: </div>
16 Added: % }
17 Added: </div>
18 Added: </section>
roles/dashboard/templates/dashboard/status.html.ep
index 6c1ca846..749edfd4 100644..100644
@@ -1,12 +1,14 @@
1 1 % layout 'default';
2 2 % title 'FAPG DAQ Status';
3 3
4 Removed: <div class="card">
5 Removed: <div class="card-header">
6 Removed: <h1 class="h5 mb-0">DAQ Status</h1>
7 Removed: <p class="text-body-secondary small mb-0 mt-1">Current connection status for the hub and sensor probes.</p>
4 Added: <section class="mb-4" aria-label="DAQ status">
5 Added: <div class="mb-3">
6 Added: <h1 class="h4 fw-bold mb-1">Status</h1>
7 Added: <p class="text-body-secondary small mb-0">Current connection status for the hub and sensor probes.</p>
8 8 </div>
9 9
10 Added: <div class="card">
11 Added:
10 12 <div class="list-group list-group-flush">
11 13 % for my $item ($items->@*) {
12 14 <div class="list-group-item" data-status-page-item="<%= $item->{key} %>">
@@ -43,3 +45,5 @@
43 45 % }
44 46 </div>
45 47 </div>
48 Added:
49 Added: </section>
roles/dashboard/templates/dashboard/toolbar.html.ep
index 939f97cf..ed65fbb1 100644..100644
@@ -1,24 +1,7 @@
1 Removed: <div id="toolbar" class="d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-2 mb-3 toolbar">
2 Removed: % if ($breadcrumbs->@*) {
3 Removed: <nav aria-label="Breadcrumb">
4 Removed: <ol class="breadcrumb mb-0 small">
5 Removed: % for my $index (0 .. $breadcrumbs->$#*) {
6 Removed: % my $breadcrumb = $breadcrumbs->[$index];
7 Removed: % my $is_last = $index == $breadcrumbs->$#*;
8 Removed: <li class="breadcrumb-item<%= $is_last ? ' active' : '' %>">
9 Removed: % if (defined $breadcrumb->{href}) {
10 Removed: <a href="<%= $breadcrumb->{href} %>"><%= $breadcrumb->{label} %></a>
11 Removed: % } else {
12 Removed: <span aria-current="page"><%= $breadcrumb->{label} %></span>
13 Removed: % }
14 Removed: </li>
15 Removed: % }
16 Removed: </ol>
17 Removed: </nav>
18 Removed: % }
19 Removed: % if (defined $graph_control_aria_label) {
1 Added: <% if (defined $graph_control_aria_label) { %>
2 Added: <div id="toolbar" class="d-flex align-items-center justify-content-end mb-3">
20 3 <div class="toolbar-items d-flex align-items-center gap-2 ms-md-auto overflow-x-auto mw-100">
21 4 %= include 'dashboard/graph-control', aria_label => $graph_control_aria_label
22 5 </div>
23 Removed: % }
24 6 </div>
7 Added: <% } %>