layered-ui-rails 0.18.2 → 0.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/skills/layered-ui-rails/SKILL.md +16 -6
- data/.claude/skills/layered-ui-rails/references/CSS.md +4 -3
- data/.claude/skills/layered-ui-rails/references/HELPERS.md +16 -0
- data/AGENTS.md +3 -2
- data/CHANGELOG.md +20 -0
- data/README.md +2 -2
- data/app/assets/tailwind/layered_ui/engine.css +23 -2
- data/app/helpers/layered/ui/body_helper.rb +23 -0
- data/app/views/layouts/layered_ui/application.html.erb +1 -1
- data/lib/layered/ui/engine.rb +1 -0
- data/lib/layered/ui/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 62247f3f61f465b4c4c576b807cf69c1f39b2bba73f758b2f2756210515ecbfc
|
|
4
|
+
data.tar.gz: 5c935329c2269e3f160f2a835958db103f40cb2cfe168ae57199a4aa81447f3e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17ea18aaf95c45154f42f6e565ed7e4685e26ac668542195041f55bfd6a1059521c878148bb100e26e03dc97023a08ae4e055f4c0318b8d70acb6b3d2d55ab00
|
|
7
|
+
data.tar.gz: 8dce1dac58aaee5f41ac5df38494122102f05c92ad71286940e49d47daedf47f84e952101feb805e8ea063b3c734d8d1b69d515460ae079682ec08109c09485e
|
|
@@ -22,10 +22,10 @@ bin/rails generate layered:ui:install
|
|
|
22
22
|
|
|
23
23
|
The generator adds `@import "../builds/tailwind/layered_ui";` to `application.css` (the engine's CSS is served straight from the gem via tailwindcss-rails' engine support), creates a `layered_ui_overrides.css` file for theme customisations, and adds the JS import to `application.js`.
|
|
24
24
|
|
|
25
|
-
Then render the engine layout from your application layout. Place all `content_for` blocks **above** the render call - the engine layout reads them when it renders, so they must be defined first:
|
|
25
|
+
Then render the engine layout from your application layout. Place all `content_for` blocks and `l_ui_add_body_class` calls **above** the render call - the engine layout reads them when it renders, so they must be defined first:
|
|
26
26
|
|
|
27
27
|
```erb
|
|
28
|
-
<%
|
|
28
|
+
<% l_ui_add_body_class "l-ui-body--always-show-navigation" %>
|
|
29
29
|
|
|
30
30
|
<% content_for :l_ui_navigation_items do %>
|
|
31
31
|
<%= l_ui_navigation_item("Dashboard", dashboard_path) %>
|
|
@@ -69,10 +69,8 @@ Populate layout regions with `content_for` (always above the render call):
|
|
|
69
69
|
<meta name="google-site-verification" content="...">
|
|
70
70
|
<% end %>
|
|
71
71
|
|
|
72
|
-
<%# Add CSS classes to <body> %>
|
|
73
|
-
<%
|
|
74
|
-
l-ui-body--always-show-navigation
|
|
75
|
-
<% end %>
|
|
72
|
+
<%# Add CSS classes to <body> (call multiple times or pass several; they compose) %>
|
|
73
|
+
<% l_ui_add_body_class "l-ui-body--always-show-navigation" %>
|
|
76
74
|
|
|
77
75
|
<%# Override logos %>
|
|
78
76
|
<% content_for :l_ui_logo_light do %>
|
|
@@ -129,7 +127,19 @@ Populate layout regions with `content_for` (always above the render call):
|
|
|
129
127
|
> The controller renders CSS that overrides the design tokens (`--accent`,
|
|
130
128
|
> etc.) - Turbo- and CSP-friendly, and it keeps styling out of the markup.
|
|
131
129
|
|
|
130
|
+
#### Layout modes
|
|
131
|
+
|
|
132
|
+
The engine layout has two header/navigation modes, both selected through the body class (`l_ui_add_body_class`). The default - setting nothing - is a **full-width header with the sidebar shown only on toggle**. Add `l-ui-body--header-contained` for a centred, contained header (pair it with `l-ui-page__contained` to constrain the body width), or `l-ui-body--always-show-navigation` to pin the sidebar open on desktop. The modes compose, and because the layout reads the body class at render time, a page that sets nothing gets the full-width default.
|
|
133
|
+
|
|
134
|
+
The two intended defaults:
|
|
135
|
+
|
|
136
|
+
- **Landing / marketing pages** - contained header and contained page width: `l-ui-body--header-contained` plus `l-ui-page__contained` on the body.
|
|
137
|
+
- **Admin / back-office pages** - full-width with the menu pinned open: `l-ui-body--always-show-navigation`, and no `--header-contained` (full-width is the default).
|
|
138
|
+
|
|
139
|
+
Which pages get which mode (per-section layouts, controllers, etc.) is an application concern, not something the gem dictates.
|
|
140
|
+
|
|
132
141
|
Body class modifiers:
|
|
142
|
+
- `l-ui-body--header-contained` - centres the header's inner row at `--l-ui-contained-width` (pair with `l-ui-page__contained`); landing pages
|
|
133
143
|
- `l-ui-body--always-show-navigation` - pins navigation as a sidebar on desktop
|
|
134
144
|
- `l-ui-body--hide-header` - hides the header and collapses its space
|
|
135
145
|
- `l-ui-body--glass-header` - glass header (translucent + blur); content scrolls under it
|
|
@@ -19,7 +19,7 @@ This gives author-written links inside long-form content (Markdown, prose, ad-ho
|
|
|
19
19
|
|
|
20
20
|
## Body modifiers
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Registered with the `l_ui_add_body_class` helper (call from any template or layout above the engine layout render; calls accumulate) to toggle layout-level behaviour:
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
.l-ui-body--always-show-navigation Pin sidebar navigation open on desktop
|
|
@@ -37,12 +37,13 @@ Applied to `<body>` via the `:l_ui_body_class` yield to toggle layout-level beha
|
|
|
37
37
|
.l-ui-page__vertically-centered Centred layout element (e.g. login pages)
|
|
38
38
|
.l-ui-page__narrow Narrow column (md:max-w-sm, ~384px) for any compact, centred content
|
|
39
39
|
.l-ui-page__contained Centred column capped at --l-ui-contained-width; aligns with the contained header
|
|
40
|
+
.l-ui-page__contained--no-gutter Contained column flush to the page edge until the cap, then centred
|
|
40
41
|
.l-ui-bleed Breaks a child out to the page edge (full-bleed hero etc.)
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
`.l-ui-page` (and `--with-navigation`) is applied by the engine layout around your view's `yield` - you do not add it yourself. Wrapping your view content in another `.l-ui-page` nests two containers. The `__vertically-centered` and `__narrow` elements are used *inside* views for centred, compact content (the Devise auth pages are one example, but they are general-purpose). `__narrow` caps width at `md:max-w-sm` (~384px). For a wider content area, wrap your content in `.l-ui-page__contained`: it caps width at the `--l-ui-contained-width` token (default `80rem`) and centres with `mx-auto`. Because the contained header (`.l-ui-body--header-contained`) reads the same token, the two line up automatically - override `--l-ui-contained-width` once to move both. Left to itself, `.l-ui-page` holds content to the full available width.
|
|
44
|
+
`.l-ui-page` (and `--with-navigation`) is applied by the engine layout around your view's `yield` - you do not add it yourself. Wrapping your view content in another `.l-ui-page` nests two containers. The `__vertically-centered` and `__narrow` elements are used *inside* views for centred, compact content (the Devise auth pages are one example, but they are general-purpose). `__narrow` caps width at `md:max-w-sm` (~384px). For a wider content area, wrap your content in `.l-ui-page__contained`: it caps width at the `--l-ui-contained-width` token (default `80rem`) and centres with `mx-auto`. Because the contained header (`.l-ui-body--header-contained`) reads the same token, the two line up automatically - override `--l-ui-contained-width` once to move both. Left to itself, `.l-ui-page` holds content to the full available width. By default `.l-ui-page__contained` keeps the page gutter; add `.l-ui-page__contained--no-gutter` to opt out, so the column runs flush to the page edge until it reaches `--l-ui-contained-width`, then centres and caps (useful for tables, media, or full-width cards that should run edge-to-edge on small screens without losing the cap on large ones).
|
|
44
45
|
|
|
45
|
-
The page gutter (horizontal and bottom padding) is the `--l-ui-gutter` custom property (default `1rem`),
|
|
46
|
+
The page gutter (horizontal and bottom padding) is the `--l-ui-gutter` custom property (default `1rem`), owned by the `.l-ui-page` and `.l-ui-header-container` shells - the contained header and `.l-ui-page__contained` then cap their inner content within that gutter the same way, so they always align by construction. Override it on a container to change the gutter in one place rather than reverse-engineering padding values. To take a single child edge-to-edge (a full-bleed hero, a banner), add `.l-ui-bleed` to it - it cancels exactly the current gutter, so no negative-margin guesswork. Note `.l-ui-bleed` only handles the horizontal edges; content still sits below the page's top padding (header offset + gutter). To take the *top* edge too - a hero that starts at the very top of the viewport, behind the header - add `.l-ui-body--flush-top`, which zeroes the page's top padding. On its own over the opaque header that would hide content, so pair it with `.l-ui-body--glass-header` (the header turns translucent and content shows through the blur) or `.l-ui-body--hide-header`. Give the hero its own internal top padding (at least `--header-height`) so its content clears the floating header.
|
|
46
47
|
|
|
47
48
|
`.l-ui-page` is a flex column (`flex flex-1 flex-col`) and uses `overflow-x-clip` to hold its content to the available width: intrinsically wide content (tables, code blocks, long unbroken strings) is clipped rather than expanding the page or adding a horizontal page scrollbar. So make such content scroll internally (e.g. wrap a table in an `overflow-x-auto` element) instead of expecting the page to grow.
|
|
48
49
|
|
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All helpers are prefixed `l_ui_` and are available in all views automatically.
|
|
4
4
|
|
|
5
|
+
## Body modifiers
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
l_ui_add_body_class(*modifiers)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Registers one or more body modifier classes (see the [body modifiers](CSS.md) list). Call it from any template or layout **above** the engine layout render; calls accumulate, so a shared layout and an individual page can each contribute without clobbering one another. Pass full class names:
|
|
12
|
+
|
|
13
|
+
```erb
|
|
14
|
+
<% l_ui_add_body_class "l-ui-body--always-show-navigation" %>
|
|
15
|
+
<% l_ui_add_body_class "l-ui-body--glass-header", "l-ui-body--flush-top" %>
|
|
16
|
+
<% l_ui_add_body_class "l-ui-body--hide-header" if minimal_chrome? %>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The resulting classes are deduplicated and space-joined, so repeated modifiers are harmless.
|
|
20
|
+
|
|
5
21
|
## Navigation
|
|
6
22
|
|
|
7
23
|
```ruby
|
data/AGENTS.md
CHANGED
|
@@ -10,8 +10,8 @@ Guidance for AI agents working in this repository.
|
|
|
10
10
|
- **CSS `@apply`:** Multi-line with grouping, following the Prettier Tailwind plugin order: layout → sizing → spacing → typography → backgrounds → borders → effects → transitions → interactivity. Within each group, follow Tailwind's own ordering (not alphabetical). State variants (`hover:`, `focus:`, `active:`, `disabled:`) and responsive prefixes (`sm:`, `md:`, `lg:`) are grouped with their base utility. Single utilities may stay on one line.
|
|
11
11
|
- **Generators:** `bin/rails generate layered:ui:install` (import engine CSS via `@import "../builds/tailwind/layered_ui"`, create overrides file, import JS)
|
|
12
12
|
- **JS** `app/javascript/layered_ui/`: Stimulus controllers registered as `l-ui--theme`, `l-ui--navigation`, `l-ui--panel`, `l-ui--modal`, `l-ui--tabs`
|
|
13
|
-
- **Layout yields** (prefixed `l_ui_`): `:l_ui_navigation_items`, `:l_ui_panel_heading`, `:l_ui_panel_body
|
|
14
|
-
|
|
13
|
+
- **Layout yields** (prefixed `l_ui_`): `:l_ui_navigation_items`, `:l_ui_panel_heading`, `:l_ui_panel_body`
|
|
14
|
+
- **Body modifiers:** register via the `l_ui_add_body_class(*modifiers)` helper (calls accumulate, deduped + space-joined). Modifiers: `l-ui-body--always-show-navigation` (pins nav as sidebar on desktop), `l-ui-body--hide-header` (hides header and collapses its space). Legacy `content_for :l_ui_body_class` is still honoured as a temporary bridge but is deprecated.
|
|
15
15
|
|
|
16
16
|
## Testing
|
|
17
17
|
|
|
@@ -29,6 +29,7 @@ Guidance for AI agents working in this repository.
|
|
|
29
29
|
- Importmap for JS (no bundler)
|
|
30
30
|
- Put new changes on a branch with a meaningful name, but do not commit; the user will ask when ready to commit
|
|
31
31
|
- We are currently in pre-release, so breaking changes may be introduced. Flag any expected breaking changes clearly before making them, but do not avoid necessary improvements solely to preserve backwards compatibility
|
|
32
|
+
- Do not overthink basic tasks. Match the effort to the request: a one-line change is a one-line change. Avoid unnecessary investigation, refactoring, or generalisation beyond what was asked.
|
|
32
33
|
- CRITICAL: Retain WCAG 2.2 AA compliance. Do not be too pedantic though as this introduces audit loops which are undesirable.
|
|
33
34
|
- A WCAG 2.2 AA table looks like this:
|
|
34
35
|
```
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. This project follows [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [0.18.4] - 2026-06-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `l_ui_add_body_class(*modifiers)` helper for registering `<body>` modifier classes. Calls accumulate across templates and layouts, so a shared layout and an individual page can each contribute modifiers without clobbering one another; the result is deduplicated and space-joined.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The engine layout now builds its `<body>` class via `l_ui_add_body_class` instead of `content_for :l_ui_body_class`. The legacy `content_for :l_ui_body_class` is still honoured as a temporary bridge but is deprecated; migrate to `l_ui_add_body_class`.
|
|
14
|
+
|
|
15
|
+
## [0.18.3] - 2026-06-08
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `l-ui-page__contained--no-gutter`: opts the contained column out of the page gutter. The column runs flush to the page edge while narrower than `--l-ui-contained-width`, then centres and caps once there is room - useful for tables, media, or full-width cards that should run edge-to-edge on small screens without losing the cap on large ones.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- The page gutter is now owned by the `.l-ui-page` and `.l-ui-header-container` shells, with the contained header and `.l-ui-page__contained` capping their inner content within that gutter the same way. Header and contained page content now align by construction at every width. The horizontal padding moved from `.l-ui-header` to `.l-ui-header-container`; the header's bottom border still spans edge to edge.
|
|
24
|
+
|
|
5
25
|
## [0.18.2] - 2026-06-08
|
|
6
26
|
|
|
7
27
|
### Added
|
data/README.md
CHANGED
|
@@ -86,10 +86,10 @@ To let AI coding agents work with `layered-ui-rails` in your project, install th
|
|
|
86
86
|
bin/rails generate layered:ui:install_agent_skill
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
Then update your application layout to render the engine layout. Place any `content_for` blocks **above** the render call - the engine layout reads them when it renders, so they must be defined first:
|
|
89
|
+
Then update your application layout to render the engine layout. Place any `content_for` blocks and `l_ui_add_body_class` calls **above** the render call - the engine layout reads them when it renders, so they must be defined first:
|
|
90
90
|
|
|
91
91
|
```erb
|
|
92
|
-
<%
|
|
92
|
+
<% l_ui_add_body_class "l-ui-body--always-show-navigation" %>
|
|
93
93
|
|
|
94
94
|
<% content_for :l_ui_navigation_items do %>
|
|
95
95
|
<%= l_ui_navigation_item "Home", root_path %>
|
|
@@ -485,17 +485,36 @@
|
|
|
485
485
|
my-auto;
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
-
/* Centred content column capped at --l-ui-contained-width
|
|
488
|
+
/* Centred content column capped at --l-ui-contained-width. The page owns the gutter (px on
|
|
489
|
+
.l-ui-page), so the gutter sits OUTSIDE this cap and the column's content spans the full
|
|
490
|
+
--l-ui-contained-width. The contained header mirrors this exactly (gutter on its container, cap
|
|
491
|
+
on the child), so the two align by construction at every width. */
|
|
489
492
|
.l-ui-page__contained {
|
|
490
493
|
@apply w-full max-w-[var(--l-ui-contained-width)]
|
|
491
494
|
mx-auto;
|
|
492
495
|
}
|
|
493
496
|
|
|
497
|
+
/* Opt out of the page gutter: reclaim it (as .l-ui-bleed does) so the column can reach the page
|
|
498
|
+
edge, then re-cap and re-centre the content with padding. Flush to the edge while narrower than
|
|
499
|
+
--l-ui-contained-width; centred and capped once there is room. */
|
|
500
|
+
.l-ui-page__contained--no-gutter {
|
|
501
|
+
@apply w-auto max-w-none
|
|
502
|
+
mx-[calc(var(--l-ui-gutter)*-1)];
|
|
503
|
+
/* +gutter cancels the negative margin's effect on the % basis, so this nets to
|
|
504
|
+
max(0, (page - contained) / 2): centred when there's room, flush when not. */
|
|
505
|
+
padding-inline: max(0px, calc((100% - var(--l-ui-contained-width)) / 2 + var(--l-ui-gutter)));
|
|
506
|
+
}
|
|
507
|
+
|
|
494
508
|
/* Header */
|
|
495
509
|
|
|
510
|
+
/* The container owns the gutter (mirroring .l-ui-page), so .l-ui-header fills the gutter-inset
|
|
511
|
+
content box by default and the contained header re-centres within the same frame as
|
|
512
|
+
.l-ui-page__contained. The bottom border still spans edge to edge (border sits outside the
|
|
513
|
+
padding). */
|
|
496
514
|
.l-ui-header-container {
|
|
497
515
|
@apply fixed top-0 left-0 right-0
|
|
498
516
|
z-50
|
|
517
|
+
px-[var(--l-ui-gutter)]
|
|
499
518
|
bg-background
|
|
500
519
|
border-b border-border;
|
|
501
520
|
}
|
|
@@ -513,9 +532,11 @@
|
|
|
513
532
|
.l-ui-header {
|
|
514
533
|
@apply flex items-center justify-between
|
|
515
534
|
h-[var(--header-height)]
|
|
516
|
-
|
|
535
|
+
py-3;
|
|
517
536
|
}
|
|
518
537
|
|
|
538
|
+
/* Mirror of .l-ui-page__contained: the gutter lives on the container, the cap lives here. Same
|
|
539
|
+
formula, same reference frame, so header and contained page content share the same edges. */
|
|
519
540
|
.l-ui-body--header-contained .l-ui-header {
|
|
520
541
|
@apply w-full max-w-[var(--l-ui-contained-width)]
|
|
521
542
|
mx-auto;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Layered
|
|
2
|
+
module Ui
|
|
3
|
+
module BodyHelper
|
|
4
|
+
# Register one or more body modifier classes from any template or layout.
|
|
5
|
+
# Calls accumulate, so a shared layout and an individual page can each
|
|
6
|
+
# contribute without clobbering one another. Pass full class names, e.g.
|
|
7
|
+
# l_ui_add_body_class "l-ui-body--hide-header", "l-ui-body--glass-header".
|
|
8
|
+
def l_ui_add_body_class(*modifiers)
|
|
9
|
+
(@_l_ui_body_classes ||= []).concat(modifiers.flatten.compact)
|
|
10
|
+
nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Render the full <body> class string, including the base l-ui-body class.
|
|
14
|
+
# Deduplicates and space-joins, so repeated or multi-token modifiers are safe.
|
|
15
|
+
#
|
|
16
|
+
# The legacy `content_for :l_ui_body_class` is still honoured as a temporary
|
|
17
|
+
# bridge so existing host apps keep working; prefer l_ui_add_body_class.
|
|
18
|
+
def l_ui_body_classes
|
|
19
|
+
token_list("l-ui-body", *(@_l_ui_body_classes || []), content_for(:l_ui_body_class))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<%= javascript_importmap_tags %>
|
|
26
26
|
</head>
|
|
27
27
|
|
|
28
|
-
<body class="
|
|
28
|
+
<body class="<%= l_ui_body_classes %>" data-controller="l-ui--navigation" data-action="click@window->l-ui--navigation#close keydown.esc@window->l-ui--navigation#close">
|
|
29
29
|
<a href="#main-content" class="l-ui-skip-link">Skip to main content</a>
|
|
30
30
|
<div id="l-ui-live-region" class="l-ui-sr-only" aria-live="polite" aria-atomic="true"></div>
|
|
31
31
|
<%= render "layouts/layered_ui/header" %>
|
data/lib/layered/ui/engine.rb
CHANGED
|
@@ -27,6 +27,7 @@ module Layered
|
|
|
27
27
|
initializer "layered-ui-rails.helpers" do
|
|
28
28
|
ActiveSupport.on_load(:action_controller) do
|
|
29
29
|
helper Layered::Ui::AuthenticationHelper
|
|
30
|
+
helper Layered::Ui::BodyHelper
|
|
30
31
|
helper Layered::Ui::BreadcrumbsHelper
|
|
31
32
|
helper Layered::Ui::NavigationHelper
|
|
32
33
|
helper Layered::Ui::PagyHelper
|
data/lib/layered/ui/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: layered-ui-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.18.
|
|
4
|
+
version: 0.18.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- layered.ai
|
|
@@ -211,6 +211,7 @@ files:
|
|
|
211
211
|
- app/assets/images/layered_ui/panel_icon_light.svg
|
|
212
212
|
- app/assets/tailwind/layered_ui/engine.css
|
|
213
213
|
- app/helpers/layered/ui/authentication_helper.rb
|
|
214
|
+
- app/helpers/layered/ui/body_helper.rb
|
|
214
215
|
- app/helpers/layered/ui/breadcrumbs_helper.rb
|
|
215
216
|
- app/helpers/layered/ui/form_helper.rb
|
|
216
217
|
- app/helpers/layered/ui/header_helper.rb
|