diff --git a/.gitignore b/.gitignore
index e3f4cf7..4531a25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,8 @@
/pkg/
/tmp/
/spec/dummy/db/*.sqlite3
+/spec/dummy/db/*.sqlite3-shm
+/spec/dummy/db/*.sqlite3-wal
/spec/dummy/log/
/spec/dummy/tmp/
/coverage/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index adf76b0..9a5585a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+
+- Hamburger toggle nav for viewports narrower than 576px — three-bar button opens a full-width dropdown with vertically stacked links; no JS file required
+- `sqd-grid-2` utility class for responsive two-column layouts (collapses to one column at ≤768px)
+- `.sqd-sr-only` utility class for visually-hidden text
+- `:focus-visible` focus ring (2px primary blue) for keyboard navigation
+- `aria-expanded` on the mobile nav toggle, kept in sync on open/close
+- `role="status"` on notice flash messages and `role="alert"` on alert flash messages
+- `aria-label="Main"` on the primary navigation landmark
+- `aria-current="page"` on the active navigation link
+- `scope="col"` on all table header cells
+- Visually-hidden "Actions" label on empty action column headers
+
+### Changed
+
+- Navbar title and links constrained to the same max-width as page content so they align horizontally with the dashboard
+- Page headers stack vertically on mobile (≤640px)
+- Stat grid uses a smaller minimum cell width on mobile
+- Cards scroll horizontally on mobile to accommodate wide tables
+- Main content padding reduced on mobile
+
## [0.4.0] - 2026-05-18
### Added
diff --git a/app/assets/stylesheets/solid_queue_web/application.css b/app/assets/stylesheets/solid_queue_web/application.css
index 72cf657..96342e0 100644
--- a/app/assets/stylesheets/solid_queue_web/application.css
+++ b/app/assets/stylesheets/solid_queue_web/application.css
@@ -4,6 +4,24 @@
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+.sqd-sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+:focus-visible {
+ outline: 2px solid var(--primary);
+ outline-offset: 2px;
+ border-radius: 2px;
+}
+
:root {
--bg: #f8f9fa;
--surface: #ffffff;
@@ -30,6 +48,11 @@ body {
.sqd-header {
background: var(--surface);
border-bottom: 1px solid var(--border);
+}
+
+.sqd-header__inner {
+ max-width: 1200px;
+ margin: 0 auto;
padding: 0 1.5rem;
display: flex;
align-items: center;
@@ -67,6 +90,28 @@ body {
color: var(--text);
}
+.sqd-nav-toggle {
+ display: none;
+ flex-direction: column;
+ justify-content: center;
+ gap: 5px;
+ width: 36px;
+ height: 36px;
+ padding: 6px;
+ margin-left: auto;
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ cursor: pointer;
+}
+
+.sqd-nav-toggle span {
+ display: block;
+ height: 2px;
+ background: var(--text);
+ border-radius: 1px;
+}
+
.sqd-main {
max-width: 1200px;
margin: 0 auto;
@@ -344,8 +389,84 @@ nav.pagy a[aria-disabled="true"] { color: var(--muted); cursor: default; }
gap: 1.5rem;
}
+.sqd-grid-2 {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
@media (max-width: 768px) {
.sqd-detail-grid { grid-template-columns: 1fr; }
+ .sqd-grid-2 { grid-template-columns: 1fr; }
+}
+
+@media (max-width: 640px) {
+ .sqd-main {
+ padding: 1.5rem 1rem;
+ }
+
+ .sqd-page-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+ }
+
+ .sqd-card {
+ overflow-x: auto;
+ }
+
+ .sqd-card__header {
+ flex-wrap: wrap;
+ }
+
+ .sqd-stats {
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ }
+
+ .sqd-truncate {
+ max-width: 160px;
+ }
+}
+
+@media (max-width: 576px) {
+ .sqd-header {
+ position: relative;
+ }
+
+ .sqd-header__inner {
+ padding: 0 1rem;
+ }
+
+ .sqd-nav-toggle {
+ display: flex;
+ }
+
+ .sqd-nav-wrapper {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ background: var(--surface);
+ border-bottom: 1px solid var(--border);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+ z-index: 50;
+ padding: 0.5rem;
+ }
+
+ .sqd-nav-wrapper.sqd-nav--open {
+ display: block;
+ }
+
+ .sqd-nav {
+ flex-direction: column;
+ gap: 0.25rem;
+ }
+
+ .sqd-nav a {
+ padding: 0.5rem 0.75rem;
+ font-size: 14px;
+ }
}
.sqd-detail-section { padding: 1.25rem; }
diff --git a/app/views/layouts/solid_queue_web/application.html.erb b/app/views/layouts/solid_queue_web/application.html.erb
index 9e3b061..d11ad2f 100644
--- a/app/views/layouts/solid_queue_web/application.html.erb
+++ b/app/views/layouts/solid_queue_web/application.html.erb
@@ -11,24 +11,34 @@
<% if notice.present? %>
- <%= notice %>
+ <%= notice %>
<% end %>
<% if alert.present? %>
- <%= alert %>
+ <%= alert %>
<% end %>
<%= yield %>
diff --git a/app/views/solid_queue_web/failed_jobs/index.html.erb b/app/views/solid_queue_web/failed_jobs/index.html.erb
index 59ab09d..28b9d24 100644
--- a/app/views/solid_queue_web/failed_jobs/index.html.erb
+++ b/app/views/solid_queue_web/failed_jobs/index.html.erb
@@ -21,11 +21,11 @@
- | Job Class |
- Queue |
- Error |
- Failed At |
- |
+ Job Class |
+ Queue |
+ Error |
+ Failed At |
+ Actions |
diff --git a/app/views/solid_queue_web/jobs/index.html.erb b/app/views/solid_queue_web/jobs/index.html.erb
index 0993472..f7f6089 100644
--- a/app/views/solid_queue_web/jobs/index.html.erb
+++ b/app/views/solid_queue_web/jobs/index.html.erb
@@ -29,12 +29,12 @@
- | Job Class |
- Queue |
- Priority |
- Scheduled At |
- Enqueued At |
- <% if discardable %> | <% end %>
+ Job Class |
+ Queue |
+ Priority |
+ Scheduled At |
+ Enqueued At |
+ <% if discardable %>Actions | <% end %>
diff --git a/app/views/solid_queue_web/processes/index.html.erb b/app/views/solid_queue_web/processes/index.html.erb
index f1505d6..7d1f0ba 100644
--- a/app/views/solid_queue_web/processes/index.html.erb
+++ b/app/views/solid_queue_web/processes/index.html.erb
@@ -7,13 +7,13 @@
- | Kind |
- Name |
- PID |
- Host |
- Details |
- Last Heartbeat |
- Status |
+ Kind |
+ Name |
+ PID |
+ Host |
+ Details |
+ Last Heartbeat |
+ Status |
diff --git a/app/views/solid_queue_web/queues/index.html.erb b/app/views/solid_queue_web/queues/index.html.erb
index 8878542..b86670b 100644
--- a/app/views/solid_queue_web/queues/index.html.erb
+++ b/app/views/solid_queue_web/queues/index.html.erb
@@ -7,11 +7,11 @@
- | Name |
- Size |
- Latency |
- Status |
- |
+ Name |
+ Size |
+ Latency |
+ Status |
+ Actions |