Skip to content

Customize the utility navigation - #64

Merged
Fivell merged 1 commit into
masterfrom
feat/utility-nav-server-clock
Oct 13, 2026
Merged

Fivell merged 1 commit into
masterfrom
feat/utility-nav-server-clock

Conversation

@Fivell

@Fivell Fivell commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

What

The theme now owns the #utility_nav customization. Per namespace it claims :utility_navigation and renders a consistent set of items — each one opt-outable, and a consumer-defined item with the same id always wins:

Server clock

  • A per-namespace li#servertime, rendered by the gem's Ruby backend (ActiveAdminTheme.install!) and ticked by app/assets/javascripts/wigu/server_clock.js, so consumer apps carry no clock code of their own.
  • The item label embeds the server instant per request: data-servertime="YYYY MM DD HH MM SS ZONE" (the documented contract, also accepted from host-rendered elements such as body[data-servertime]), plus data-servertime-epoch and data-servertime-zone (IANA) when Time.zone is set.
  • The JS prefers epoch + zone and formats every tick with Intl, so the clock follows the server through its own DST transitions; the wall-clock path stays for host-rendered sources, parsed via Date.UTC so the client's zone and DST never touch the math either. A skewed browser clock does not throw it off: the server instant advances by the client's elapsed time only.
  • Loading is documented in the README: //= require wigu/server_clock (Sprockets) or import "@activeadmin-plugins/active_admin_theme/src/server_clock" (npm). Note: the npm import resolves from the registry after the next npm publish; the published 3.1.1 predates the script.

Logout as an icon

  • Claiming :utility_navigation suppresses ActiveAdmin's default items, so the theme restores the logout item (at the right edge, beside the clock) and renders it as a masked sign-out icon that follows $skinMenuTextColor.
  • The icon is scoped to the theme's own anchor (a.wigu-logout) — consumer-rendered logout items keep their labels — and carries an aria-label, since font-size: 0 text can be pruned from the accessibility tree.
  • Opt-out is ActiveAdmin's own per-namespace switch, honoured by the theme: config.logout_link_path = nil.

Current user

  • The default current-user item is restored the same way (no-op when current_user_method is unset, exactly like stock AA).
  • Opt-out: ActiveAdminTheme.add_current_user = false — AA offers no switch for this item short of unsetting current_user_method, which breaks authorization.

Header and index layout

  • The header stays viewport-wide and on-screen, so wide tables no longer overlap the utility nav while scrolling sideways.
  • Index pagination stays under the table when the filter sidebar is taller than the table.

Hardening

The branch went through two max-depth agent reviews plus Copilot; every confirmed finding is fixed:

  • Survives Turbo/Turbolinks body swaps and bfcache restores: the DOM is re-queried each tick, the data span is never destroyed, and display lines update via textContent — which also closes an innerHTML injection sink for host-authored zone tokens and the per-second node churn.
  • No client-clock fallback: no server data means no clock, so a browser clock cannot masquerade as server time and server-side suppression (anonymous visitors, current_user_method = false, opt-outs) is honored end to end.
  • Host-owned li#servertime items are left untouched; malformed sources render nothing instead of NaN:NaN:NaN or a per-tick RangeError.
  • Time.zone = nil (the per-user Time.use_zone pattern with a nil column) degrades to the wall-clock attribute instead of a 500.
  • Menu callbacks register once per namespace — no accumulation across dev reloads — and namespaces created after the single production after_load are covered via ActiveAdmin::Namespace::RegisterEvent.
  • A host that loads ActiveAdmin later than config/initializers gets a log warning naming the fix instead of a silently missing feature.
  • npm prepare rebuilds src/ with a portable node one-liner, so git-dependency installs work on Windows and publish/prepare no longer duplicate each other.

Why

Consumer apps each reimplemented the same pieces by hand: a clock script, data-servertime markup, and an explicit logout menu item. Centralizing the utility-nav customization in the theme lets them delete all of that; the clock, current user and logout render top-right in #utility_nav from the theme.

Compatibility

ActiveAdmin >= 3.0, < 4.0 (gemspec). Uses only stable AA APIs: ActiveAdmin.after_load, Namespace::RegisterEvent, app.namespaces, namespace.build_menu(:utility_navigation), menu.add, add_current_user_to_menu, add_logout_button_to_menu. Namespace::Store#each yields Namespace objects across the whole supported range (verified against 3.0.0 and 3.5.2 sources), so no [name, ns] pair handling is needed.

Testing

  • rake css — the theme compiles against AA and the stylesheet guards hold.
  • rake test — a Capybara + cuprite integration test boots a minimal ActiveAdmin dummy app (test/dummy, no devise) and pins the real contract: two ticking lines (.clock-time + .clock-date, retrying tick assertion), the theme's own logout anchor (a.wigu-logout), and the hidden data span carrying the app's zone and a fresh epoch — so a silent fallback or a broken install! fails the suite.
  • Both run in CI (headless Chrome on ubuntu-latest). The same dummy sources the screenshot below via rake screenshot.

Verified live on two AA 3.5.1 consumers loading this branch as a path gem: #utility_nav shows the ticking clock and logout from the theme; host apps no longer emit data-servertime server-side.

Preview

The clock renders as two compact lines in #utility_nav — time + timezone over the date — with logout beside it (neutral test/dummy app, no consumer branding):

Two-line server clock in the utility nav

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Timezone handling, duplicate logout entries, Ruby 3.1 task compatibility, and missing asset-loading guidance must be addressed.

5 open findings
What changed in this PR

Adds a theme-provided server clock and logout control to ActiveAdmin’s utility navigation.

Changes:

  • Injects clock/logout menu items and adds clock JavaScript and styling.
  • Packages and documents the clock asset.
  • Adds a Rails dummy app, browser integration test, screenshot task, and CI job.
File Description
.github/​workflows/​ci.yml Adds browser-test CI job.
.gitignore Ignores generated test artifacts.
Gemfile Adds integration-test dependencies.
README.md Documents dark-mode disabling and clock.
Rakefile Adds test and screenshot tasks.
package.json Packages the clock script.
lib/​active_admin_theme.rb Injects clock and logout items.
app/​assets/​javascripts/​wigu/​server_clock.js Implements the ticking clock.
app/​assets/​stylesheets/​wigu/​active_admin_theme.scss Styles clock and logout icon.
test/​capybara_boot.rb Configures Cuprite and Capybara.
test/​clock_test.rb Tests clock rendering and ticking.
test/​screenshot.rb Generates the clock screenshot.
test/​dummy/​config/​application.rb Defines the dummy Rails app.
test/​dummy/​config/​database.yml Configures in-memory SQLite.
test/​dummy/​config/​environment.rb Initializes the dummy app.
test/​dummy/​config/​routes.rb Defines dummy routes.
test/​dummy/​config/​initializers/​active_admin.rb Configures ActiveAdmin for testing.
test/​dummy/​app/​admin/​dashboard.rb Adds a test dashboard.
test/​dummy/​app/​assets/​config/​manifest.js Links dummy assets.
test/​dummy/​app/​assets/​javascripts/​active_admin.js Loads theme scripts.
test/​dummy/​app/​assets/​stylesheets/​active_admin.scss Loads theme styles.
test/​dummy/​app/​controllers/​application_controller.rb Adds the base controller.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Rakefile Outdated
Comment thread app/assets/javascripts/wigu/server_clock.js Outdated
Comment thread app/assets/javascripts/wigu/server_clock.js Outdated
Comment thread lib/active_admin_theme.rb Outdated
Comment thread README.md
@Fivell
Fivell force-pushed the feat/utility-nav-server-clock branch from 10ec2dc to 5c600d0 Compare October 12, 2026 15:56
@Fivell
Fivell requested a balanced review from Copilot October 13, 2026 07:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Older supported ActiveAdmin namespace iteration can raise before the clock menu is registered.

2 open findings
5 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Low severity Update README to reflect script-side utility navigation insertion

README.md:192

This new description contradicts README.md:128-132, which still says a gem cannot add a utility-navigation item. The engine/after_load implementation now does exactly that, so the earlier theme-toggle guidance needs to be updated to explain that script-side insertion is a choice rather than a limitation.

🧠 Review effort: Balanced

Comment on lines +1776 to +1780
// Give the content column its own block-formatting context so #index_footer's
// `clear: both` (below) only clears the pagination floats INSIDE the table
// area — not the floated #sidebar. Without this, a tall filter sidebar pushes
// the pagination far below a short table (it clears the sidebar's float too).
#main_content { display: flow-root; }
Comment thread lib/active_admin_theme.rb Outdated
# survive unload! (it only clears the built menus), so once is enough.
seen = {}.compare_by_identity
::ActiveAdmin.after_load do |app|
app.namespaces.each do |namespace|
@Fivell
Fivell force-pushed the feat/utility-nav-server-clock branch from 8831124 to 6a52306 Compare October 13, 2026 07:53
The theme now owns the #utility_nav customization per namespace: a server
clock, the restored current-user item, and logout rendered as a masked
icon — each opt-outable, with consumer-defined items always winning.

The clock's Ruby backend embeds the server instant in the item label (the
documented wall-clock string, plus epoch + IANA zone when Time.zone is
set) and server_clock.js ticks it client-side, following the server
through its own DST transitions via Intl. Opt-outs: current user via
ActiveAdminTheme.add_current_user = false; logout via AA's per-namespace
config.logout_link_path = nil. The logout icon is scoped to the theme's
own anchor and named via aria-label.

The JS survives Turbo/Turbolinks body swaps, renders nothing without
server data (a client clock must not masquerade as the server's), never
touches consumer-owned menu items, tolerates malformed sources, and writes
textContent only. Menu callbacks register once per namespace, including
namespaces created after the single production after_load
(Namespace::RegisterEvent); a nil Time.zone degrades gracefully; a late
ActiveAdmin load logs a warning instead of silently no-oping. npm prepare
rebuilds src/ with a portable node one-liner so git installs work on
Windows.

Also: the header stays viewport-wide and on-screen so wide tables don't
overlap the utility nav, and index pagination stays under the table when
the filter sidebar is tall.

A Capybara integration test (test/dummy) pins the server contract; README
documents loading the script and both opt-outs.
@Fivell
Fivell force-pushed the feat/utility-nav-server-clock branch from 6a52306 to 26dda82 Compare October 13, 2026 07:56
@Fivell Fivell changed the title Add server clock to the utility navigation Customize the utility navigation Oct 13, 2026
@Fivell
Fivell merged commit 8dc4ce6 into master Oct 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants