You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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):
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.
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.
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
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.
What
The theme now owns the
#utility_navcustomization. Per namespace it claims:utility_navigationand renders a consistent set of items — each one opt-outable, and a consumer-defined item with the same id always wins:Server clock
li#servertime, rendered by the gem's Ruby backend (ActiveAdminTheme.install!) and ticked byapp/assets/javascripts/wigu/server_clock.js, so consumer apps carry no clock code of their own.data-servertime="YYYY MM DD HH MM SS ZONE"(the documented contract, also accepted from host-rendered elements such asbody[data-servertime]), plusdata-servertime-epochanddata-servertime-zone(IANA) whenTime.zoneis set.Intl, so the clock follows the server through its own DST transitions; the wall-clock path stays for host-rendered sources, parsed viaDate.UTCso 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.//= require wigu/server_clock(Sprockets) orimport "@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
:utility_navigationsuppresses 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.a.wigu-logout) — consumer-rendered logout items keep their labels — and carries anaria-label, sincefont-size: 0text can be pruned from the accessibility tree.config.logout_link_path = nil.Current user
current_user_methodis unset, exactly like stock AA).ActiveAdminTheme.add_current_user = false— AA offers no switch for this item short of unsettingcurrent_user_method, which breaks authorization.Header and index layout
Hardening
The branch went through two max-depth agent reviews plus Copilot; every confirmed finding is fixed:
textContent— which also closes aninnerHTMLinjection sink for host-authored zone tokens and the per-second node churn.current_user_method = false, opt-outs) is honored end to end.li#servertimeitems are left untouched; malformed sources render nothing instead ofNaN:NaN:NaNor a per-tickRangeError.Time.zone = nil(the per-userTime.use_zonepattern with a nil column) degrades to the wall-clock attribute instead of a 500.after_loadare covered viaActiveAdmin::Namespace::RegisterEvent.config/initializersgets a log warning naming the fix instead of a silently missing feature.preparerebuildssrc/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-servertimemarkup, 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_navfrom 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#eachyieldsNamespaceobjects 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 brokeninstall!fails the suite.rake screenshot.Verified live on two AA 3.5.1 consumers loading this branch as a path gem:
#utility_navshows the ticking clock and logout from the theme; host apps no longer emitdata-servertimeserver-side.Preview
The clock renders as two compact lines in
#utility_nav— time + timezone over the date — with logout beside it (neutraltest/dummyapp, no consumer branding):