Conversation
Two sidebar surfaces that shouldn't have been top-level pages. Both touch App.jsx + sidebarItems.js, so they land together. Test Sandbox: the distro-matrix console shipped visible and routable on every production install. Add a shared useDevMode() hook (module-scope cache + listeners, same shape as useModules) resolving `import.meta.env.DEV || (isAdmin && dev_mode)`. The sidebar item gates on requiresCondition: 'devMode' and the route on the same hook, so a visible nav entry can never redirect to the dashboard. The guard waits on `resolved` first, or an admin deep link would bounce before the dev_mode fetch lands. Settings' toggle calls refreshDevMode() so the sidebar reacts without a reload. Webhooks: the inbound receive/verify/forward console is server configuration, not a daily ops surface, so it becomes a Settings -> Admin tab next to the outbound subscriptions it complements. /webhooks and /secrets/webhooks redirect there; the sidebar item is gone, which empties ADVANCED_ITEM_IDS down to the queue console. The command palette entry drops its navId (a Settings tab has no nav item for the workspace permission gate to consult) and settingsIndex.js gains the three entries check-settings-index.mjs requires for a new tab. The native confirm() on delete becomes the shared useConfirm() dialog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page kept a shape the rest of the app moved off: a four-card KPI strip, its own in-body search input, a hand-rolled table, four icon buttons per row, and an edit modal on row click. It now matches /domains: the segmented filter carries the counts (no KPI strip), SearchField lives in the top bar, the list is the shared DataTable, and a row opens the DS Drawer. The top bar keeps its title — /cron has no tab group, and the reference mock shows a titled bar there; the body was what was stale. Last run / Next run / Status columns need data list_jobs() never returned: it skipped the schedule_human / next_run / run-tracking joins that jobs_for_application() has done since plan 34, so the admin list could only ever show enabled-vs-disabled. Apply the same enrichment, with tests covering the schedule join, the last-run join, and the no-history case. The drawer's run history uses GET /cron/jobs/<id>/runs, which already existed and only ScheduledTasksCard consumed. It's admin-only and empty until a job carries the tracking shim, so both cases are stated in the drawer (with an enable-tracking action) rather than rendering a blank table. Also fixes styles that never applied: .run-output-*, .spinner-inline and the modal field overrides were nested under .cron-page, but Radix Dialog and Sheet portal to <body>. Hoisted to top level. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With /cron, /webhooks and /test-sandbox off it, the old <div class="page-header"><h1>...</h1></div> block had one live user left: Docker's Docker-not-installed branch, where the panel below it already supplies its own heading. Remove that, and the class is dead. Delete the base rule and the overrides that outlived it in _docker.scss, _marketplace.scss, _wordpress.scss, _wordpress-pipeline.scss and components/_tabs.scss, plus the unused .docker-page-header. The base rule's slot keeps a comment saying what replaced it. StyleGuide's "Page Headers" section still described this as "the canonical pattern, use this for all pages", which is how it kept getting picked for new pages. It now demos the two live shapes — the titleless TabGroupLayout bar (the default) and the titled standalone bar — and names .page-header as retired. StyleGuide's own header moves to PageTopbar. Docker and the Database Explorer intentionally carry no page bar at all; that is unchanged and deliberate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A StatusComponent was already a full synthetic check (http/tcp/dns/smtp/ping, target, interval, timeout, HealthCheck history, uptime windows, auto-incidents) — it was just framed as "a component of a status page" and its engine lived inside the serverkit-status extension, so watching a site required publishing a status page. Promote it: - migration 081 makes status_components.page_id and status_incidents.page_id nullable, and adds the columns backing the monitor UI (is_paused, check_method, expected_status, keyword, follow_redirects, verify_tls, retries, consecutive_failures, cert_issuer, cert_expires_at) - new core app/services/monitor_service.py owns the check engine; the extension keeps page publishing and delegates - new /api/v1/monitors (CRUD, check, pause, history, uptime buckets, incidents) - new builtin.monitor_check sweep on a 30s tick — until now checks only ran when someone pressed "Check now", so a configured monitor never noticed an outage Check-engine defects fixed while moving it: - ping ignored the process return code and could only ever report 'up' - ping used Linux-only flags with no guard, breaking the Windows dev server - http ignored its own method/expected_status/follow_redirects/verify_tls - smtp was documented on the column but unimplemented; keyword did not exist - run_check never recomputed uptime and never opened or resolved an incident — only the WordPress health-sync path did, so network monitors never paged - TLS issuer/expiry are now captured (throttled to one read per 6h) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Monitoring only ever described the machine the panel runs on. These are the screens for the things it is supposed to be watching: - /monitoring/monitors — the check list on the Domains/Cron pattern: clickable KPI tiles as the status filter, topbar SearchField + FilterDrawer, and a table with a response sparkline, 30-day uptime and a live next-check countdown. Creating a monitor picks a check type (HTTP / keyword / port / ping / DNS / SMTP) and its probe options. - /monitoring/monitors/:id — the drill-down, outside the tab group so it carries its own breadcrumb header. Sections are a SegControl, not a second tab strip: Performance (p50 chart + stat strip), Uptime (90-day bars + certificate), Check log (freezable so a streaming table stays readable), Configuration. - /monitoring/incidents — absorbs the old Alerts tab. A CPU threshold crossing and a monitor going down are the same question asked of two subjects, and splitting them is what made alerting look like it only described this server. Host threshold alerting is unchanged, just rendered in the same timeline. - UptimeBars renders a day with no samples as its own "unwatched" state rather than a full green bar — "we weren't looking" and "it was fine" are different facts, and conflating them is how uptime widgets lie about their history. The list sparkline is fed by one bulk query for the whole page, not one per row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The group's information architecture had drifted. The Events tab pointed at /telemetry, which was nested inside the monitoring TabGroupLayout but ALSO rendered its own PageTopbar — two stacked headers. Jobs sat as a top-level sidebar entry while being the same class of thing as Events, so the two read as rival pages. - MONITOR_TABS is now Overview / Monitors / Incidents / Rules / Capacity / Events / Jobs / Doctor (+ Status Pages from the extension). Alerts is gone as a tab; /monitoring/alerts and /monitoring/fleet-alerts redirect to Incidents, and FleetAlertsPanel moved there with its own ack/resolve lifecycle intact. - Jobs moves to /monitoring/jobs and loses its sidebar entry; /jobs redirects. Activity/Scheduled becomes an in-page SegControl since the group's bar already owns the tab row. JOBS_TABS is deleted. - Events (Telemetry) rebuilt on the standard pattern: no PageTopbar of its own, KpiBand + MetricCard severity filters instead of bespoke telemetry-stat divs, topbar SearchField + FilterDrawer instead of an inline collapsible panel, DataTable + DS Drawer. Every capability is kept — correlation drill-down, all filters, test-event emit, cleanup, pagination. - The Overview leads with a monitors summary (availability, live check feed, monitor list); host resources keep everything they had, one section down. - Status pages can now publish a monitor that already exists, and detach it again without destroying it — deleting a component now deletes a real monitor. FilterDrawer grows `children` + `onClear` so a host can add non-list filters (free text, date bounds) and still have "Clear all" reach them; without onClear it would silently reset only the chip groups. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
_maybe_attach_certificate throttled on last_check_at, which is the probe clock. An active monitor is checked every 30s, so that condition was never older than the 6h refresh window: the certificate would be read once and then never refreshed, leaving a stale expiry date on the detail page forever. Adds cert_checked_at (migration 081) and throttles on that instead. The stamp is written even when the read fails, so an unreachable TLS endpoint is retried on the same cadence rather than on every single probe. Also serves the Monitors list sparkline from one bulk query per page instead of one per row, bounded by a time window so a busy monitor's history cannot crowd out a quiet one's the way a single global LIMIT would. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Walks the AST of monitor_service and the monitors blueprint to assert neither imports the plugin loader nor reaches for StatusPageService, and that the routes register without the extension. This is the property the promotion exists to provide — watching a site must keep working on a lean panel with serverkit-status uninstalled — so it should fail loudly if it regresses. AST rather than a source grep: the modules' own comments discuss the extension, which a text match would trip over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- The Events severity chip passed a MetricCard tone to Pill. The two token sets overlap but are not the same — Pill has no "accent", so info events rendered as bare text with no chip while every other severity had one. Split the config into `tone` (tile) and `pill` (chip). - "128 ms" wrapped onto two lines in the Monitors table's narrow response column. Both were only visible in a rendered page, not in lint or the build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Monitors, a monitor's detail and 90-day uptime strip, Incidents, Events and Jobs. These double as the render smoke test for the pages this round added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Promotes the existing status-page “components” synthetic-check engine into core, first-class Monitors that run on a scheduler, auto-open/resolve incidents, and surface new Monitoring UI (Monitors list, Monitor detail, unified Incidents timeline), while also restructuring Monitoring navigation and rebuilding /cron to match the Domains pattern.
Changes:
- Backend: promote monitors into core (
MonitorService+/api/v1/monitors), add a 30s builtin scheduler sweep, and migrate schema to support pageless monitors/incidents and new probe options. - Frontend: add Monitors/MonitorDetail/Incidents pages, refactor Monitoring tabs + Jobs/Webhooks/TestSandbox navigation, and update shared DS primitives (FilterDrawer, styles).
- Cron: enrich
cron_service.list_jobs()data to support “Last run / Next run / Status” in the admin list UI.
Reviewed changes
Copilot reviewed 61 out of 68 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| VERSION | Bumps version to 1.7.75. |
| ROADMAP.md | Documents monitors + monitoring navigation as recently completed. |
| frontend/src/styles/pages/_wordpress.scss | Removes legacy .page-header styling from WordPress page styles. |
| frontend/src/styles/pages/_wordpress-pipeline.scss | Removes legacy .page-header styling from WP pipeline page styles. |
| frontend/src/styles/pages/_test-sandbox.scss | Removes unused .ts-intro styling. |
| frontend/src/styles/pages/_telemetry.scss | Rewrites Telemetry/Events styles for DS primitives + new markup. |
| frontend/src/styles/pages/_secrets.scss | Extends shared secrets styles to Settings→Admin Webhooks tab container. |
| frontend/src/styles/pages/_monitors.scss | Adds full styling for monitors list/detail/incidents + uptime strip. |
| frontend/src/styles/pages/_monitoring.scss | Broadens shared monitoring root selectors for mon-* vocabulary. |
| frontend/src/styles/pages/_marketplace.scss | Removes legacy .page-header styles from Marketplace page. |
| frontend/src/styles/pages/_jobs.scss | Adds in-page Activity/Scheduled view switch styling for Jobs tab. |
| frontend/src/styles/pages/_docker.scss | Removes legacy page-header CSS; updates workspace comment wording. |
| frontend/src/styles/pages/_cron.scss | Rebuilds Cron page styling (topbar search, table widths, drawer/modal rules). |
| frontend/src/styles/main.scss | Imports new _monitors.scss. |
| frontend/src/styles/layout/_main-content.scss | Deletes global .page-header styles; documents PageTopbar as replacement. |
| frontend/src/styles/components/_tabs.scss | Updates .tab-embedded header-hiding rules to remove .page-header selectors. |
| frontend/src/styles/components/_design-system.scss | Adds FilterDrawer “extra fields” styling block. |
| frontend/src/services/api/monitors.js | Adds frontend API methods for monitors + monitor incidents. |
| frontend/src/services/api/index.js | Registers the new monitors API module in ApiService. |
| frontend/src/pages/TestSandbox.jsx | Removes intro paragraph (now dev-gated elsewhere). |
| frontend/src/pages/StyleGuide.jsx | Migrates StyleGuide header + docs from .page-header to PageTopbar. |
| frontend/src/pages/StatusPages.jsx | Adds attach/detach existing monitors to status pages; updates actions and modal. |
| frontend/src/pages/Settings.jsx | Adds Settings→Admin Webhooks tab and refreshes dev-mode cache on change. |
| frontend/src/pages/Monitors.jsx | Adds Monitors list page with KPIs, filters, polling, and create drawer. |
| frontend/src/pages/Monitoring.jsx | Removes Alerts tab; routes users to Incidents conceptually. |
| frontend/src/pages/MonitorDetail.jsx | Adds monitor drill-down page (performance/uptime/log/config) with polling. |
| frontend/src/pages/Jobs.jsx | Moves Jobs into Monitoring group and replaces tab strip with SegControl switch. |
| frontend/src/pages/Incidents.jsx | Adds unified incidents + host alerts timeline with drawer and update posting. |
| frontend/src/pages/Docker.jsx | Removes legacy .page-header rendering from Docker page. |
| frontend/src/hooks/useDevMode.js | Introduces shared dev-mode flag + cache for sidebar and route guarding. |
| frontend/src/data/settingsIndex.js | Adds command-palette entries for inbound webhooks under Settings→Webhooks tab. |
| frontend/src/data/palettePages.js | Updates command palette page routes (Jobs/Monitors/Incidents + settings webhooks). |
| frontend/src/components/sidebarItems.js | Moves Jobs into Monitoring; removes Webhooks sidebar item; gates Test Sandbox by devMode. |
| frontend/src/components/Sidebar.jsx | Wires useDevMode() into sidebar runtime conditions. |
| frontend/src/components/settings/WebhooksTab.jsx | Converts inbound Webhooks page into Settings tab content + confirm dialog hook. |
| frontend/src/components/organization/organizationTabs.jsx | Updates comments re: inbound webhooks location. |
| frontend/src/components/monitoring/UptimeBars.jsx | Adds 90‑day uptime strip component with “none” state semantics. |
| frontend/src/components/monitoring/monitorTabs.jsx | Updates Monitoring group tabs (Monitors/Incidents/Jobs) and narrative comments. |
| frontend/src/components/monitoring/MonitorsSummary.jsx | Adds monitors KPIs + feed + list section to Monitoring overview. |
| frontend/src/components/monitoring/monitorShared.jsx | Centralizes monitor status/type vocab + incident constants. |
| frontend/src/components/monitoring/MonitoringOverview.jsx | Adds MonitorsSummary and adjusts section headings (“All hosts”). |
| frontend/src/components/monitoring/FleetAlertsPanel.jsx | Updates comments to reflect Incidents tab hosting. |
| frontend/src/components/jobs/jobsTabs.jsx | Deletes obsolete Jobs tab-strip definitions. |
| frontend/src/components/ds/FilterDrawer.jsx | Adds children + onClear to support host-supplied extra filter fields. |
| frontend/src/components/dashboard/widgets/renderers.jsx | Updates Jobs quick action route to /monitoring/jobs. |
| frontend/src/components/backups/ProtectionPanel.jsx | Updates “View Jobs” navigation to /monitoring/jobs. |
| frontend/src/components/backups/BackupDetailDrawer.jsx | Updates job link to /monitoring/jobs. |
| frontend/src/App.jsx | Adds monitors/incidents/jobs routes under Monitoring group, adds DevOnlyRoute guard, redirects old routes. |
| builtin-extensions/serverkit-status/backend/status_page_service.py | Delegates check/incident/component operations to core MonitorService; expands public-hidden fields. |
| backend/tests/test_monitor_service.py | Adds extensive MonitorService coverage (status parsing, probes, scheduler, incidents, cert logic). |
| backend/tests/test_jobs.py | Updates builtin schedule counts and asserts new builtin.monitor_check exists. |
| backend/tests/test_cron_runs.py | Adds tests for CronService.list_jobs() enrichment (next_run/last_run/status joins). |
| backend/migrations/versions/081_monitors_first_class.py | Makes page_id nullable for components/incidents and adds monitor option columns. |
| backend/app/services/cron_service.py | Enriches list_jobs() with schedule_human/next_run/last-run tracking. |
| backend/app/models/status_page.py | Makes page_id nullable, adds monitor option columns, and adds derived next_check_at. |
| backend/app/jobs/builtin_handlers.py | Adds run_monitor_checks() and registers builtin.monitor_check on a 30s tick; updates WP health sync. |
| backend/app/api/monitors.py | Adds core monitors API (CRUD, check, pause, history, uptime, stats, incidents). |
| backend/app/init.py | Registers the monitors blueprint under /api/v1/monitors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+151
to
+161
| def update(monitor_id, data): | ||
| monitor = StatusComponent.query.get(monitor_id) | ||
| if not monitor: | ||
| return None | ||
| if 'check_type' in data and data['check_type'] not in StatusComponent.CHECK_TYPES: | ||
| raise ValueError(f"Unknown check type: {data['check_type']}") | ||
| for field in MonitorService.WRITABLE_FIELDS: | ||
| if field in data: | ||
| setattr(monitor, field, data[field]) | ||
| db.session.commit() | ||
| return monitor |
Comment on lines
+49
to
+58
| @monitors_bp.route('', methods=['POST']) | ||
| @monitors_bp.route('/', methods=['POST']) | ||
| @jwt_required() | ||
| def create_monitor(): | ||
| data = request.get_json() or {} | ||
| try: | ||
| monitor = MonitorService.create(data) | ||
| except ValueError as e: | ||
| return {'error': str(e)}, 400 | ||
| return jsonify(monitor.to_dict()), 201 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Monitoring has spent its whole life describing exactly one machine — the one it runs on. Lots of CPU graphs, no way to answer "is the shop actually up?", which is the question most people open a control panel to ask. It turned out the answer was already half-built:
StatusComponentwas a complete synthetic check — HTTP/TCP/DNS/ping, target, interval, uptime history, auto-incidents — filed under "a component of a status page" and living inside theserverkit-statusextension, so watching a site required publishing a status page. This PR promotes it instead of building a second engine: one nullable column, a core service, and the thing you could always half-do becomes the thing the page is about. The bigger surprise was that nothing ever ran the checks — there was no scheduler, so a configured monitor could only notice an outage if you personally clicked "Check now"; the ping probe, separately, was structurally incapable of reporting a failure at all. Also riding along from the same branch: the/cronrebuild, and two nav corrections that took a dev-only console off production installs.Highlights
Technical changes
Promoting monitors
081_monitors_first_classmakesstatus_components.page_idandstatus_incidents.page_idnullable. The second one matters: a pageless monitor's auto-opened incident has no page either, and without it the outage path raisesIntegrityError. Addsis_paused,check_method,expected_status,keyword,follow_redirects,verify_tls,retries,consecutive_failures,cert_issuer,cert_expires_at,cert_checked_at— each backing one surfaced control, nothing speculative.app/services/monitor_service.pyowns the check engine, moved out of the extension.StatusPageServicekeeps page publishing, branding, the public JSON and the badge, and delegates check-running so its existing callers and blueprint are unchanged.app/api/monitors.pyat/api/v1/monitors: CRUD,POST /:id/check,POST /:id/pause,GET /:id/history,GET /:id/uptime(per-day buckets),GET /stats, plus incident CRUD. Incidents are core because the scheduler opens them with or without the extension installed.StatusComponent.next_check_atis derived, not stored, so changing an interval takes effect immediately.The scheduler that never existed
builtin.monitor_checkadded to_BUILTINSon a 30s tick. The tick bounds lateness;MonitorService.due_monitors()decides what is actually due, skipping paused monitors and those driven by the WordPress health sweep. Per-monitortry/exceptmirrorsrun_health_checksso one unreachable target can't stall the sweep.run_health_checksno longer reaches throughget_installed_extension_attrfor the component sync — the engine is core now, so a lean panel keeps its site-bound monitors current.Check-engine defects fixed while moving it
pingcalledrun_commandand then unconditionally setstatus='up', discarding the return code — only an exception could mark it down. It also used Linux-only-c/-Wflags with no guard, breaking the Windows dev server.httpignored its own configuration: no method, no expected-status range, no redirect control,verify=Truehardcoded.smtpwas documented in the column comment but unimplemented and fell through todown;keyworddidn't exist. Both implemented — a 200 carrying the wrong body is now an outage, which is the entire point of a keyword check.run_checknever recomputed uptime and never opened or resolved an incident; only the WordPresssync_component_from_healthpath did. Both now funnel through one_recordso a network probe and a health verdict produce identical bookkeeping.cert_checked_at— gating onlast_check_atmeant an active 30s monitor's probe clock was never stale, so the certificate would be read once and never refreshed.MonitorService.recent_response_timesfeeds the list sparkline from one bulk query per page rather than one per row, bounded by a time window so a busy monitor's history can't crowd out a quiet one's the way a single globalLIMITwould.Frontend
pages/Monitors.jsxon the Domains/Cron pattern: clickableMetricCardKPIs as the status filter,SearchField+FilterDrawerpublished to the group bar viauseTopbarActions,DataTablewith aSparklineresponse column and a live next-check countdown.pages/MonitorDetail.jsxat/monitoring/monitors/:id, deliberately outside the tab group (like/queue/:group/:queue) so it carries its own breadcrumbPageTopbar. Its sections are aSegControl, not a second tab strip.components/monitoring/UptimeBars.jsx— a day with no samples renders as its own--nonestate rather than a full green bar, so "we weren't looking" and "it was fine" stay distinguishable.pages/Incidents.jsxmerges monitor incidents withgetAlertHistory/active_alertsinto one sorted timeline, with an incident drawer carrying the update timeline and a post-an-update form.FleetAlertsPanelmoved here and keeps its own ack/resolve lifecycle.Telemetry.jsxrewritten: dropped the<PageTopbar>it was rendering insideTabGroupLayout(the double header),telemetry-statdivs →KpiBand/MetricCard, inline collapsible filters → topbarSearchField+FilterDrawer, list →DataTable, bespoke drawer → DSDrawer. Correlation drill-down, every filter, test-event emit, cleanup and pagination all retained.MONITOR_TABSbecomes Overview / Monitors / Incidents / Rules / Capacity / Events / Jobs / Doctor.Alertsis gone as a tab;/monitoring/alertsand/monitoring/fleet-alertsredirect to Incidents. Jobs moves to/monitoring/jobswith/jobsredirecting, loses itssidebarItemsentry, and its Activity/Scheduled sub-nav becomes an in-pageSegControl;jobsTabs.jsxis deleted.MonitoringOverviewgains aMonitorsSummarysection above host health — availability KPIs, a live check feed and a monitor list. The fleet grid's heading renames to "All hosts" so it doesn't collide.StatusPages.jsxgains attach/detach against the core monitors API, because deleting a component now deletes a real monitor and its history — unpublishing needed its own action.FilterDrawergrowschildrenandonClear. WithoutonClear, "Clear all" resets only the chip groups and silently leaves host-supplied fields applied._monitoring.scss's root selector list widened to the new page roots so they inherit the sharedmon-*vocabulary instead of duplicating it; new_monitors.scss;_telemetry.scssrewritten against the new markup.tone(MetricCard) andpill(Pill). The two token sets overlap but aren't identical —Pillhas noaccent, so info events had been rendering with no chip at all.Also in this branch
/cronrebuilt on the Domains pattern, andcron_service.list_jobs()gained theschedule_human/next_run/ run-tracking joinsjobs_for_application()had carried since the run-history work — without them the admin list could only ever show enabled-vs-disabled.useDevMode()hook on both the sidebar item and the route, so a visible nav entry can never redirect to the dashboard; the distro-matrix console had been shipping visible on every production install./webhooksand/secrets/webhooksredirecting..page-headerlayout deleted along with the overrides that outlived it, and the StyleGuide section that kept recommending it.