plutonium 0.49.1 → 0.50.0
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/plutonium-definition/SKILL.md +87 -2
- data/.claude/skills/plutonium-installation/SKILL.md +6 -0
- data/.claude/skills/plutonium-views/SKILL.md +59 -0
- data/CHANGELOG.md +12 -0
- data/app/assets/plutonium.css +2 -2
- data/app/assets/plutonium.js +369 -25
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +45 -45
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_resource_header.html.erb +4 -4
- data/app/views/plutonium/_resource_sidebar.html.erb +9 -9
- data/app/views/resource/_resource_grid.html.erb +1 -0
- data/config/brakeman.ignore +25 -2
- data/docs/reference/definition/actions.md +14 -1
- data/docs/reference/definition/index.md +58 -0
- data/docs/reference/views/index.md +43 -0
- data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md +841 -0
- data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md.tasks.json +103 -0
- data/docs/superpowers/specs/2026-05-07-ui-layout-overhaul-design.md +270 -0
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +1 -0
- data/lib/generators/pu/core/update/update_generator.rb +20 -0
- data/lib/generators/pu/lite/rails_pulse/rails_pulse_generator.rb +54 -5
- data/lib/plutonium/action/base.rb +44 -1
- data/lib/plutonium/action/interactive.rb +1 -1
- data/lib/plutonium/configuration.rb +4 -0
- data/lib/plutonium/definition/actions.rb +3 -0
- data/lib/plutonium/definition/base.rb +8 -0
- data/lib/plutonium/definition/metadata.rb +40 -0
- data/lib/plutonium/definition/views.rb +94 -0
- data/lib/plutonium/helpers/turbo_helper.rb +1 -1
- data/lib/plutonium/interaction/response/redirect.rb +1 -1
- data/lib/plutonium/query/base.rb +8 -0
- data/lib/plutonium/query/filters/association.rb +30 -8
- data/lib/plutonium/query/filters/boolean.rb +5 -0
- data/lib/plutonium/resource/controllers/presentable.rb +11 -2
- data/lib/plutonium/resource/definition.rb +42 -0
- data/lib/plutonium/resource/query_object.rb +64 -6
- data/lib/plutonium/testing/resource_definition.rb +2 -2
- data/lib/plutonium/ui/action_button.rb +4 -2
- data/lib/plutonium/ui/component/kit.rb +12 -0
- data/lib/plutonium/ui/display/base.rb +3 -1
- data/lib/plutonium/ui/display/resource.rb +109 -25
- data/lib/plutonium/ui/display/theme.rb +2 -1
- data/lib/plutonium/ui/dyna_frame/content.rb +8 -14
- data/lib/plutonium/ui/empty_card.rb +1 -1
- data/lib/plutonium/ui/form/base.rb +29 -1
- data/lib/plutonium/ui/form/components/hidden_wrapper.rb +25 -0
- data/lib/plutonium/ui/form/components/resource_select.rb +79 -1
- data/lib/plutonium/ui/form/components/secure_association.rb +7 -2
- data/lib/plutonium/ui/form/components/sticky_footer.rb +17 -0
- data/lib/plutonium/ui/form/resource.rb +48 -9
- data/lib/plutonium/ui/form/theme.rb +1 -1
- data/lib/plutonium/ui/frame_navigator_panel.rb +7 -4
- data/lib/plutonium/ui/grid/card.rb +235 -0
- data/lib/plutonium/ui/grid/resource.rb +149 -0
- data/lib/plutonium/ui/layout/base.rb +37 -1
- data/lib/plutonium/ui/layout/header.rb +1 -2
- data/lib/plutonium/ui/layout/icon_rail.rb +212 -0
- data/lib/plutonium/ui/layout/resource_layout.rb +10 -3
- data/lib/plutonium/ui/layout/sidebar.rb +12 -24
- data/lib/plutonium/ui/layout/topbar.rb +100 -0
- data/lib/plutonium/ui/modal/base.rb +109 -0
- data/lib/plutonium/ui/modal/centered.rb +21 -0
- data/lib/plutonium/ui/modal/slideover.rb +26 -0
- data/lib/plutonium/ui/page/base.rb +25 -6
- data/lib/plutonium/ui/page/edit.rb +13 -1
- data/lib/plutonium/ui/page/index.rb +40 -1
- data/lib/plutonium/ui/page/interactive_action.rb +8 -39
- data/lib/plutonium/ui/page/new.rb +13 -1
- data/lib/plutonium/ui/page/show.rb +8 -1
- data/lib/plutonium/ui/page_header.rb +8 -13
- data/lib/plutonium/ui/panel.rb +10 -19
- data/lib/plutonium/ui/sidebar_menu.rb +2 -25
- data/lib/plutonium/ui/tab_list.rb +29 -7
- data/lib/plutonium/ui/table/base.rb +106 -0
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +12 -4
- data/lib/plutonium/ui/table/components/filter_form.rb +171 -0
- data/lib/plutonium/ui/table/components/filter_pills.rb +89 -0
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +13 -12
- data/lib/plutonium/ui/table/components/scopes_pills.rb +67 -0
- data/lib/plutonium/ui/table/components/selection_column.rb +2 -11
- data/lib/plutonium/ui/table/components/toolbar.rb +104 -0
- data/lib/plutonium/ui/table/components/view_switcher.rb +81 -0
- data/lib/plutonium/ui/table/resource.rb +158 -89
- data/lib/plutonium/ui/table/theme.rb +14 -5
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +6 -0
- data/package.json +1 -1
- data/src/css/components.css +304 -131
- data/src/css/tokens.css +101 -85
- data/src/js/controllers/autosubmit_controller.js +24 -0
- data/src/js/controllers/bulk_actions_controller.js +15 -16
- data/src/js/controllers/capture_url_controller.js +14 -0
- data/src/js/controllers/filter_panel_controller.js +77 -19
- data/src/js/controllers/frame_navigator_controller.js +34 -6
- data/src/js/controllers/icon_rail_controller.js +22 -0
- data/src/js/controllers/icon_rail_flyout_controller.js +128 -0
- data/src/js/controllers/register_controllers.js +16 -0
- data/src/js/controllers/resource_tab_list_controller.js +56 -3
- data/src/js/controllers/row_click_controller.js +21 -0
- data/src/js/controllers/table_column_menu_controller.js +43 -0
- data/src/js/controllers/table_header_controller.js +16 -0
- data/src/js/controllers/view_switcher_controller.js +29 -0
- metadata +31 -3
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"planPath": "docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md",
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"id": 7,
|
|
6
|
+
"subject": "Task 0: Density tokens",
|
|
7
|
+
"status": "pending",
|
|
8
|
+
"description": "**Goal:** Codify balanced density scale.\n\n```json:metadata\n{\"files\":[\"src/css/tokens.css\",\"src/css/components.css\",\"lib/plutonium/ui/component/tokens.rb\"],\"verifyCommand\":\"yarn build && bundle exec appraisal rails-8.1 rake test\",\"acceptanceCriteria\":[\"density vars defined\",\"button/input sizes updated\",\"card padding 16px\",\"tests pass\"],\"requiresUserVerification\":false}\n```"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": 8,
|
|
12
|
+
"subject": "Task 1: PageHeader redesign (Stripe-style)",
|
|
13
|
+
"status": "pending",
|
|
14
|
+
"blockedBy": [7],
|
|
15
|
+
"description": "**Goal:** Tighter Stripe-style PageHeader.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/page_header.rb\",\"lib/plutonium/ui/page/base.rb\",\"test/plutonium/ui/page_header_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/ui/page_header_test.rb -v\",\"acceptanceCriteria\":[\"title 18-20px\",\"description muted\",\"actions right-aligned\",\"tabs flush\"],\"requiresUserVerification\":false}\n```"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 9,
|
|
19
|
+
"subject": "Task 2: IconRail component",
|
|
20
|
+
"status": "pending",
|
|
21
|
+
"blockedBy": [7],
|
|
22
|
+
"description": "**Goal:** 56px icon-only nav.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/layout/icon_rail.rb\",\"test/plutonium/ui/layout/icon_rail_test.rb\",\"src/css/components.css\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/ui/layout/icon_rail_test.rb -v\",\"acceptanceCriteria\":[\"aside 56px\",\"slots present\",\"active styling\",\"mobile-hidden\"],\"requiresUserVerification\":false}\n```"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": 10,
|
|
26
|
+
"subject": "Task 3: Topbar component",
|
|
27
|
+
"status": "pending",
|
|
28
|
+
"blockedBy": [7],
|
|
29
|
+
"description": "**Goal:** Sticky 48px topbar.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/layout/topbar.rb\",\"lib/plutonium/ui/breadcrumbs.rb\",\"test/plutonium/ui/layout/topbar_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/ui/layout/topbar_test.rb -v\",\"acceptanceCriteria\":[\"48px height\",\"breadcrumbs/search/actions slots\",\"hamburger wired\",\"empty-breadcrumbs handled\"],\"requiresUserVerification\":false}\n```"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 11,
|
|
33
|
+
"subject": "Task 4: Wire ResourceLayout to new shell",
|
|
34
|
+
"status": "pending",
|
|
35
|
+
"blockedBy": [9, 10],
|
|
36
|
+
"description": "**Goal:** Replace partials with IconRail+Topbar; drop legacy.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/layout/resource_layout.rb\",\"lib/plutonium/ui/layout/base.rb\",\"lib/plutonium/ui/layout/header.rb\",\"lib/plutonium/ui/layout/sidebar.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 rake test\",\"acceptanceCriteria\":[\"new shell wired\",\"old partials removed\",\"tests pass\"],\"requiresUserVerification\":false}\n```"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": 12,
|
|
40
|
+
"subject": "Task 5: Index toolbar",
|
|
41
|
+
"status": "pending",
|
|
42
|
+
"blockedBy": [7, 8],
|
|
43
|
+
"description": "**Goal:** New Toolbar component (view switcher, filter, group, search, column-config, overflow).\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/table/components/toolbar.rb\",\"lib/plutonium/ui/table/components/view_switcher.rb\",\"lib/plutonium/ui/table/resource.rb\",\"test/plutonium/ui/table/components/toolbar_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/ui/table/components/toolbar_test.rb -v\",\"acceptanceCriteria\":[\"toolbar order\",\"search wired\",\"filter popover\",\"disabled segments\"],\"requiresUserVerification\":false}\n```"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": 13,
|
|
47
|
+
"subject": "Task 6: Active filter pills + result count",
|
|
48
|
+
"status": "pending",
|
|
49
|
+
"blockedBy": [12],
|
|
50
|
+
"description": "**Goal:** Removable filter pills + result count strip.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/table/components/filter_pills.rb\",\"lib/plutonium/ui/table/resource.rb\",\"lib/plutonium/resource/query_object.rb\",\"test/plutonium/ui/table/components/filter_pills_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/ui/table/components/filter_pills_test.rb -v\",\"acceptanceCriteria\":[\"pill per filter\",\"add-filter pill\",\"clear URL\",\"result count\"],\"requiresUserVerification\":false}\n```"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": 14,
|
|
54
|
+
"subject": "Task 7: Column-header sort with multi-sort + ⋯ menu",
|
|
55
|
+
"status": "pending",
|
|
56
|
+
"blockedBy": [12],
|
|
57
|
+
"description": "**Goal:** Sort in column headers, shift-click multi-sort, per-column ⋯ menu.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/resource/query_object.rb\",\"lib/plutonium/ui/table/resource.rb\",\"lib/plutonium/ui/table/theme.rb\",\"src/js/controllers/table_controller.js\",\"test/plutonium/resource/query_object_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/resource/query_object_test.rb -v\",\"acceptanceCriteria\":[\"sort_params_for has multi_url\",\"click vs shift-click\",\"priority badges\",\"column menu\"],\"requiresUserVerification\":false}\n```"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": 15,
|
|
61
|
+
"subject": "Task 8: Floating bulk action bar",
|
|
62
|
+
"status": "pending",
|
|
63
|
+
"blockedBy": [13],
|
|
64
|
+
"description": "**Goal:** Bulk action bar replaces pills strip when rows selected.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/table/components/bulk_action_bar.rb\",\"lib/plutonium/ui/table/resource.rb\",\"src/js/controllers/bulk_actions_controller.js\",\"test/plutonium/ui/table/components/bulk_action_bar_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/ui/table/components/bulk_action_bar_test.rb -v\",\"acceptanceCriteria\":[\"bar/pills mutual exclusion\",\"selection count\",\"danger tone\",\"clear deselect\"],\"requiresUserVerification\":false}\n```"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": 16,
|
|
68
|
+
"subject": "Task 9: Show page redesign",
|
|
69
|
+
"status": "pending",
|
|
70
|
+
"blockedBy": [8],
|
|
71
|
+
"description": "**Goal:** Single-column show + reserved aside slot.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/page/show.rb\",\"lib/plutonium/ui/page/base.rb\",\"lib/plutonium/ui/display/resource.rb\",\"lib/plutonium/ui/panel.rb\",\"test/plutonium/ui/page/show_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/ui/page/show_test.rb -v\",\"acceptanceCriteria\":[\"max-width 960\",\"card panels\",\"aside slot reserved\",\"tabs flush\"],\"requiresUserVerification\":false}\n```"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": 17,
|
|
75
|
+
"subject": "Task 10: Form page redesign + sticky footer",
|
|
76
|
+
"status": "pending",
|
|
77
|
+
"blockedBy": [8],
|
|
78
|
+
"description": "**Goal:** Centered narrow form column + sticky footer + inline validation.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/ui/page/new.rb\",\"lib/plutonium/ui/page/edit.rb\",\"lib/plutonium/ui/page/interactive_action.rb\",\"lib/plutonium/ui/form/resource.rb\",\"lib/plutonium/ui/form/interaction.rb\",\"lib/plutonium/ui/form/theme.rb\",\"lib/plutonium/ui/form/components/sticky_footer.rb\",\"test/plutonium/ui/form/sticky_footer_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/ui/form -v\",\"acceptanceCriteria\":[\"580px centered\",\"sticky footer\",\"inline validation\",\"modal mode skips footer\"],\"requiresUserVerification\":false}\n```"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": 18,
|
|
82
|
+
"subject": "Task 11: Slideover modal mode + per-interaction option",
|
|
83
|
+
"status": "pending",
|
|
84
|
+
"blockedBy": [17],
|
|
85
|
+
"description": "**Goal:** Add slideover mode; opt-in via interactive_action :name, modal: :slideover.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/resource/interaction.rb\",\"lib/plutonium/interaction/base.rb\",\"src/js/controllers/remote_modal_controller.js\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/interaction -v\",\"acceptanceCriteria\":[\"modal: kwarg accepted\",\"two outer containers\",\"slideover transition\",\"mobile full-screen\"],\"requiresUserVerification\":false}\n```"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": 19,
|
|
89
|
+
"subject": "Task 12: Per-resource modal declaration",
|
|
90
|
+
"status": "pending",
|
|
91
|
+
"blockedBy": [18],
|
|
92
|
+
"description": "**Goal:** modal :slideover on resource definition for new/edit modal mode.\n\n```json:metadata\n{\"files\":[\"lib/plutonium/resource/definition.rb\",\"test/plutonium/resource/definition_test.rb\"],\"verifyCommand\":\"bundle exec appraisal rails-8.1 ruby -Itest test/plutonium/resource/definition_test.rb -v\",\"acceptanceCriteria\":[\"modal DSL on definition\",\"invalid raises\",\"modal frame uses declared mode\",\"page URLs unchanged\"],\"requiresUserVerification\":false}\n```"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": 20,
|
|
96
|
+
"subject": "Task 13: Documentation + changelog",
|
|
97
|
+
"status": "pending",
|
|
98
|
+
"blockedBy": [16, 19],
|
|
99
|
+
"description": "**Goal:** Document overhaul; update skills.\n\n```json:metadata\n{\"files\":[\"docs/guides/ui-overhaul-2026.md\",\"CHANGELOG.md\",\".claude/skills/plutonium-views.md\"],\"verifyCommand\":\"yarn docs:build\",\"acceptanceCriteria\":[\"upgrade guide written\",\"skills updated\",\"changelog entry\",\"docs build passes\"],\"requiresUserVerification\":false}\n```"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"lastUpdated": "2026-05-07"
|
|
103
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Plutonium UI Layout Overhaul — Design Spec
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-05-07
|
|
4
|
+
**Scope:** Visual + structural redesign of Plutonium's app shell, page header, index/show/form pages. Code-level component refactoring (slot APIs, hook reduction, partial-to-Phlex conversion) is intentionally out of scope here — this spec captures the *target UI* only. A separate refactor pass can re-architect the Phlex internals to deliver this target.
|
|
5
|
+
|
|
6
|
+
## Goals
|
|
7
|
+
|
|
8
|
+
1. Modernize the look and feel to match contemporary admin tools (Linear, Stripe Dashboard, Vercel, Plane).
|
|
9
|
+
2. Increase information density without sacrificing scannability.
|
|
10
|
+
3. Establish a coherent visual vocabulary across all four page types (index / show / form / interactive-action).
|
|
11
|
+
4. Leave clean extension points for upcoming features (metadata side panel, view switchers).
|
|
12
|
+
|
|
13
|
+
## Non-Goals
|
|
14
|
+
|
|
15
|
+
- Theming / token rebuild (deferred — current `--pu-*` token system stays).
|
|
16
|
+
- Component API consolidation (separate effort).
|
|
17
|
+
- Mobile-first redesign (mobile must work, but desktop is the optimization target).
|
|
18
|
+
- New colors / typography (use existing tokens unless a decision below requires a new one).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. App Shell — Icon Rail + Topbar
|
|
23
|
+
|
|
24
|
+
A narrow icon-only left rail plus a topbar replaces the current expanded sidebar.
|
|
25
|
+
|
|
26
|
+
**Left rail**
|
|
27
|
+
- Width: ~56px, fixed.
|
|
28
|
+
- Icon-only nav items with tooltips on hover.
|
|
29
|
+
- Top: brand mark / portal switcher.
|
|
30
|
+
- Middle: primary nav (resources grouped by section, dividers between groups).
|
|
31
|
+
- Bottom: settings, theme toggle.
|
|
32
|
+
- Active item: filled background, primary tone.
|
|
33
|
+
- Mobile (<lg breakpoint): rail collapses to hamburger drawer.
|
|
34
|
+
|
|
35
|
+
**Topbar**
|
|
36
|
+
- Height: ~48px, sticky.
|
|
37
|
+
- Left: breadcrumbs (resource path; replaces in-content breadcrumbs).
|
|
38
|
+
- Center: global search input (filled, ~360px max).
|
|
39
|
+
- Right: notifications, user menu.
|
|
40
|
+
|
|
41
|
+
**Removed**
|
|
42
|
+
- Current expanded sidebar (240px) — labels now live in tooltips and breadcrumbs.
|
|
43
|
+
- In-page breadcrumbs above title — moved to topbar.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. Page Header — Stripe-Style
|
|
48
|
+
|
|
49
|
+
Every page renders a unified header below the topbar.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
┌────────────────────────────────────────────────────┐
|
|
53
|
+
│ Customers [Cancel] [Save] │
|
|
54
|
+
│ Manage customer accounts and contact details │
|
|
55
|
+
├────────────────────────────────────────────────────┤
|
|
56
|
+
│ Overview │ Orders │ Invoices │ Activity │
|
|
57
|
+
└────────────────────────────────────────────────────┘
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- Title: 18–20px, semibold.
|
|
61
|
+
- Description: 13px, muted, optional, sits directly below title.
|
|
62
|
+
- Actions: right-aligned at title's vertical level. Primary as filled button, secondary as outline. Overflow into a `⋯` dropdown after 2 visible actions.
|
|
63
|
+
- Tabs: connected strip directly under header (no gap), 1px bottom border becomes the active-tab indicator's baseline.
|
|
64
|
+
|
|
65
|
+
The header is uniform across index / show / form / interactive-action.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 3. Index Page — Hybrid Toolbar + Pills + Column Sort
|
|
70
|
+
|
|
71
|
+
### Toolbar (single 36-40px row above the table)
|
|
72
|
+
|
|
73
|
+
Order, left to right:
|
|
74
|
+
1. **View switcher** — segmented control (Grid / Cards / Kanban — Cards/Kanban are placeholders for now; only Grid is wired initially).
|
|
75
|
+
2. Vertical divider.
|
|
76
|
+
3. **Filter** button (popover).
|
|
77
|
+
4. **Group** button (popover).
|
|
78
|
+
5. Spacer (`flex-grow`).
|
|
79
|
+
6. **Search input** — visible, ~220px wide, expands on focus.
|
|
80
|
+
7. Vertical divider.
|
|
81
|
+
8. **Column config** icon button (`⊞`).
|
|
82
|
+
9. **Overflow** icon button (`⋯`) — exports, advanced options.
|
|
83
|
+
|
|
84
|
+
The "Sort" button is intentionally absent — sort is column-driven (see below).
|
|
85
|
+
|
|
86
|
+
### Active Filter Strip (below toolbar, only when filters are active)
|
|
87
|
+
|
|
88
|
+
- Each active filter renders as a removable pill: `<field> <op> <value>` with `✕`.
|
|
89
|
+
- After the last pill: `+ Filter` dashed pill that opens the same popover as the toolbar Filter button.
|
|
90
|
+
- Right-aligned: result count (e.g., "147 results").
|
|
91
|
+
|
|
92
|
+
### Table — Column-Header Sort
|
|
93
|
+
|
|
94
|
+
- Click a column header: sorts asc → desc → none (cycles).
|
|
95
|
+
- Shift-click: adds a secondary/tertiary sort (multi-sort).
|
|
96
|
+
- Active sort columns show: arrow (↑/↓) + small priority badge (1, 2, 3) when more than one column is active.
|
|
97
|
+
- Each header has a `⋯` menu: Sort asc / Sort desc / Clear sort / Group by / Filter by / Hide column.
|
|
98
|
+
- Row height: 32px (balanced density). Header height: 32px.
|
|
99
|
+
- Selection: leftmost column is a 12px checkbox.
|
|
100
|
+
|
|
101
|
+
### Bulk Action Bar
|
|
102
|
+
|
|
103
|
+
- Appears as a 36px strip *replacing* the active-filter strip when ≥1 row is selected.
|
|
104
|
+
- Tinted background (primary-50 light / primary-950/30 dark).
|
|
105
|
+
- Left: count + "Clear selection".
|
|
106
|
+
- After spacer: action buttons (Export, Archive, Delete) — Delete uses danger tone.
|
|
107
|
+
|
|
108
|
+
### Pagination Footer
|
|
109
|
+
|
|
110
|
+
- Sticky-ish strip below the table.
|
|
111
|
+
- Left: "Showing N–M of Total".
|
|
112
|
+
- Right: prev / page indicator / next.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 4. Show Page — Single Column + Tabs
|
|
117
|
+
|
|
118
|
+
A single content column under the page header. Nested resources render as tabs.
|
|
119
|
+
|
|
120
|
+
### Structure
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
PageHeader (title, description, actions, tab strip)
|
|
124
|
+
└── content
|
|
125
|
+
├── [Aside slot — empty by default; reserved]
|
|
126
|
+
└── Main column
|
|
127
|
+
├── Field panel: Details
|
|
128
|
+
├── Field panel: Address
|
|
129
|
+
└── ...
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- Field panels: card-styled (1px border, radius-md, white surface) with uppercase 9px section labels.
|
|
133
|
+
- Default content max-width: ~960px, centered if rail+topbar leaves wider area.
|
|
134
|
+
- Tabs render nested resources (the existing tab strip mechanism).
|
|
135
|
+
|
|
136
|
+
### Reserved Aside Slot (Future Hook)
|
|
137
|
+
|
|
138
|
+
The page layout reserves a `render_aside` slot that is empty by default. A future `metadata` DSL on resource definitions will populate this slot:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
class CustomerDefinition < Plutonium::Resource::Definition
|
|
142
|
+
metadata do
|
|
143
|
+
field :status, badge: true
|
|
144
|
+
field :owner
|
|
145
|
+
field :created_at
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
When populated, the aside renders as a 200–240px right side panel with a sticky 16px-padded background-`surface-alt` column. Implementation of the DSL itself is a separate task — this spec only requires the layout to leave room.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 5. Form Page — Centered Narrow + Sticky Footer
|
|
155
|
+
|
|
156
|
+
For new / edit / interactive-action.
|
|
157
|
+
|
|
158
|
+
### Structure
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
PageHeader (title, description; no actions in header)
|
|
162
|
+
└── content (max-width ~580px, centered)
|
|
163
|
+
├── Card: Section 1 (uppercase 9px label + fields)
|
|
164
|
+
├── Card: Section 2
|
|
165
|
+
└── ...
|
|
166
|
+
StickyFooter (full width, right-aligned [Cancel] [Save])
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- Form column max-width: 580px.
|
|
170
|
+
- Card-style sections, same chrome as show-page panels.
|
|
171
|
+
- Inline validation: errors render as 12px danger text directly under each field. No toasts for field-level errors. Toast/flash only for form-level outcomes.
|
|
172
|
+
- Sticky footer: 56px tall, white surface, top border, sticks to viewport bottom when form scrolls.
|
|
173
|
+
- Cancel: outline button. Save: primary filled button. Right-aligned.
|
|
174
|
+
|
|
175
|
+
### Modal Variant
|
|
176
|
+
|
|
177
|
+
The same form can render in a modal when triggered as a quick-create / quick-edit (e.g., `+ New` from an index toolbar that targets the remote modal frame, or a row-edit action):
|
|
178
|
+
- No sticky footer; the modal's own footer bar holds Cancel / Save.
|
|
179
|
+
- Internal layout otherwise identical (card sections, inline validation).
|
|
180
|
+
- Modal mode (`:centered` vs `:slideover`) is configurable per resource definition — see §7.
|
|
181
|
+
- Page-level new/edit URLs always render the full page form (§5); modal rendering is invoked via the modal turbo frame.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 6. Density
|
|
186
|
+
|
|
187
|
+
**Balanced (Stripe / Vercel-class)** as the framework default.
|
|
188
|
+
|
|
189
|
+
| Token | Value |
|
|
190
|
+
|------------------|---------------|
|
|
191
|
+
| Table row height | 32px |
|
|
192
|
+
| Body text | 14px |
|
|
193
|
+
| Section gap | 16px |
|
|
194
|
+
| Field gap | 12px |
|
|
195
|
+
| Button (md) | 32px height, 14px text, 12px horizontal padding |
|
|
196
|
+
| Button (sm) | 28px, 13px, 10px |
|
|
197
|
+
| Input height | 36px (forms), 32px (toolbars) |
|
|
198
|
+
| Card padding | 16px |
|
|
199
|
+
| Page side padding | 24px |
|
|
200
|
+
|
|
201
|
+
These values become the canonical scale; spot-deviations are allowed but should be rare.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 7. Modals — Both Modes, Per-Action Opt-In
|
|
206
|
+
|
|
207
|
+
Two modal modes ship as siblings.
|
|
208
|
+
|
|
209
|
+
### Default: Centered Dialog
|
|
210
|
+
- Max-width 520px, max-height 80vh, centered, dimmed backdrop.
|
|
211
|
+
- Header: dialog title + close (✕).
|
|
212
|
+
- Body: form / content with internal scroll.
|
|
213
|
+
- Footer: 56px strip, right-aligned [Cancel] [Confirm].
|
|
214
|
+
- Use cases: short forms, confirmations, most interactive actions.
|
|
215
|
+
|
|
216
|
+
### Opt-In: Right Slide-Over Panel
|
|
217
|
+
- Slides in from right, full height, 480px wide on desktop, full-screen on mobile.
|
|
218
|
+
- Header / body / footer same as centered.
|
|
219
|
+
- Underlying list visible (dimmed); user keeps context.
|
|
220
|
+
### Configuration
|
|
221
|
+
|
|
222
|
+
**Per interaction** — defaults to `:centered`, opt into `:slideover`:
|
|
223
|
+
```ruby
|
|
224
|
+
interactive_action :reschedule, modal: :slideover
|
|
225
|
+
interactive_action :archive # implicit modal: :centered
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Per resource (for quick-create / quick-edit modals)** — definition declares the mode used when new/edit is rendered through the modal turbo frame:
|
|
229
|
+
```ruby
|
|
230
|
+
class CustomerDefinition < Plutonium::Resource::Definition
|
|
231
|
+
modal :slideover # default :centered
|
|
232
|
+
end
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The page-level new/edit URLs always render the full §5 page layout. Whether `+ New` opens a modal or navigates to the page is a per-context call-site choice (e.g., index toolbar can target the modal frame for quick-create; a "Create customer" landing CTA navigates to the full page).
|
|
236
|
+
|
|
237
|
+
Both modal modes share the same Phlex modal component; only the outer container varies.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 8. Compatibility & Migration Notes
|
|
242
|
+
|
|
243
|
+
- **`Layout::ResourceLayout`** currently uses Rails partials for `resource_header` / `resource_sidebar`. Conversion to Phlex is implicit in this work — the icon rail and topbar must be Phlex components.
|
|
244
|
+
- **`Page::Base` hook explosion** (~12 before/after hooks) — most apps don't use these. The redesign assumes apps that override `render_breadcrumbs` etc. continue to work; new slot APIs are additive. Hook deprecation is a future cleanup.
|
|
245
|
+
- **Existing CSS classes** — `.pu-input`, `.pu-btn`, `.pu-card` keep their names; sizes shift to the density table above. Apps that hard-code Tailwind utilities on top will need cosmetic touch-ups but no breakage.
|
|
246
|
+
- **Breadcrumbs** — moving from in-page to topbar means `Plutonium::UI::Breadcrumbs` becomes a topbar component. The definition-level `breadcrumbs` toggle stays; "off" means hidden in topbar (or replaced with title only).
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 9. Out-of-Scope Followups (referenced, not designed here)
|
|
251
|
+
|
|
252
|
+
- **Metadata DSL** for show-page side panel (§4).
|
|
253
|
+
- **View switcher** wiring beyond Grid (Cards, Kanban) — placeholders in toolbar; implementation deferred.
|
|
254
|
+
- **Code-level Phlex refactor** — slot API, hook reduction, asset registry — separate spec.
|
|
255
|
+
- **Token / theme rebuild** — separate spec.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 10. Acceptance Checklist
|
|
260
|
+
|
|
261
|
+
- [ ] Icon rail (56px) replaces expanded sidebar; topbar adds breadcrumbs + search + user.
|
|
262
|
+
- [ ] Page header is a single component (`PageHeader`) used by every page type, supporting title / description / actions / tabs.
|
|
263
|
+
- [ ] Index page renders the toolbar in the order of §3 with no Sort button.
|
|
264
|
+
- [ ] Active filters render as removable pills below the toolbar with a result count.
|
|
265
|
+
- [ ] Column headers sort on click (asc/desc/none) with shift-click multi-sort and priority badges.
|
|
266
|
+
- [ ] Bulk action bar replaces the filter strip when rows are selected.
|
|
267
|
+
- [ ] Show page is single-column with tab strip; an empty aside slot is reserved.
|
|
268
|
+
- [ ] Form pages use a 580px centered column with sticky footer; modal variant uses dialog footer.
|
|
269
|
+
- [ ] Density tokens (§6) are codified in CSS / Phlex constants and used consistently.
|
|
270
|
+
- [ ] Modal component supports both `:centered` (default) and `:slideover` via per-action / per-form opt-in.
|
|
@@ -9,10 +9,14 @@ module Pu
|
|
|
9
9
|
|
|
10
10
|
desc "Update Plutonium gem and npm package to the latest version"
|
|
11
11
|
|
|
12
|
+
SHELL_PIN_THRESHOLD = "0.49.1"
|
|
13
|
+
|
|
12
14
|
def start
|
|
15
|
+
@previous_gem_version = installed_gem_version
|
|
13
16
|
update_gem
|
|
14
17
|
update_npm_package
|
|
15
18
|
sync_skills_if_present
|
|
19
|
+
pin_shell_to_classic
|
|
16
20
|
rescue => e
|
|
17
21
|
exception "#{self.class} failed:", e
|
|
18
22
|
end
|
|
@@ -26,6 +30,22 @@ module Pu
|
|
|
26
30
|
Rails::Generators.invoke("pu:skills:sync", [], destination_root: Rails.root)
|
|
27
31
|
end
|
|
28
32
|
|
|
33
|
+
# Pin the shell config to :classic on apps upgrading from a version
|
|
34
|
+
# that predates the shell change so the upgrade is invisible. Newer
|
|
35
|
+
# apps installed after the shell change get :modern from the install
|
|
36
|
+
# template and shouldn't be flipped.
|
|
37
|
+
def pin_shell_to_classic
|
|
38
|
+
return unless @previous_gem_version
|
|
39
|
+
return unless SemanticRange.satisfies?(@previous_gem_version, "<=#{SHELL_PIN_THRESHOLD}")
|
|
40
|
+
|
|
41
|
+
initializer = Rails.root.join("config", "initializers", "plutonium.rb")
|
|
42
|
+
return unless File.file?(initializer)
|
|
43
|
+
return if File.read(initializer).match?(/^\s*config\.shell\s*=/)
|
|
44
|
+
|
|
45
|
+
say_status :update, "Pinning Plutonium shell to :classic...", :green
|
|
46
|
+
configure_plutonium "config.shell = :classic"
|
|
47
|
+
end
|
|
48
|
+
|
|
29
49
|
def update_gem
|
|
30
50
|
say_status :update, "Updating plutonium gem...", :green
|
|
31
51
|
run "bundle update plutonium"
|
|
@@ -70,21 +70,70 @@ module Pu
|
|
|
70
70
|
|
|
71
71
|
def setup_recurring_tasks
|
|
72
72
|
recurring_file = "config/recurring.yml"
|
|
73
|
-
|
|
73
|
+
full_path = File.expand_path(recurring_file, destination_root)
|
|
74
|
+
return unless File.exist?(full_path)
|
|
74
75
|
return if file_includes?(recurring_file, "rails_pulse")
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
content = File.read(full_path)
|
|
78
|
+
env_keys = %w[production development staging test]
|
|
79
|
+
env_scoped = content.lines.any? { |l| l.match?(/^(#{env_keys.join("|")}):\s*$/) }
|
|
77
80
|
|
|
81
|
+
if env_scoped
|
|
82
|
+
create_file recurring_file, inject_rails_pulse_under_envs(content, env_keys), force: true
|
|
83
|
+
else
|
|
84
|
+
append_to_file recurring_file, "\n" + rails_pulse_tasks_yaml(0)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def rails_pulse_tasks_yaml(indent)
|
|
89
|
+
pad = " " * indent
|
|
90
|
+
<<~YAML.gsub(/^(?=.)/, pad)
|
|
78
91
|
rails_pulse_summary:
|
|
79
92
|
class: RailsPulse::SummaryJob
|
|
80
|
-
|
|
93
|
+
queue: default
|
|
94
|
+
schedule: every hour at minute 5
|
|
95
|
+
description: "Roll up Rails Pulse raw records into summary tables"
|
|
81
96
|
|
|
82
97
|
rails_pulse_cleanup:
|
|
83
98
|
class: RailsPulse::CleanupJob
|
|
84
|
-
|
|
99
|
+
queue: default
|
|
100
|
+
schedule: every day at 1am
|
|
101
|
+
description: "Archive/purge old Rails Pulse data"
|
|
85
102
|
YAML
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def inject_rails_pulse_under_envs(content, env_keys)
|
|
106
|
+
lines = content.lines
|
|
107
|
+
env_re = /^(#{env_keys.join("|")}):\s*$/
|
|
108
|
+
|
|
109
|
+
env_starts = lines.each_with_index.select { |l, _| env_re.match?(l) }.map(&:last)
|
|
110
|
+
|
|
111
|
+
env_starts.reverse_each do |start|
|
|
112
|
+
end_idx = lines.length
|
|
113
|
+
((start + 1)...lines.length).each do |i|
|
|
114
|
+
if lines[i].match?(/^[^\s#]/)
|
|
115
|
+
end_idx = i
|
|
116
|
+
break
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
indent = 2
|
|
121
|
+
((start + 1)...end_idx).each do |i|
|
|
122
|
+
if (m = lines[i].match(/^(\s+)\S/))
|
|
123
|
+
indent = m[1].length
|
|
124
|
+
break
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
insert_at = end_idx
|
|
129
|
+
while insert_at > start + 1 && lines[insert_at - 1].strip.empty?
|
|
130
|
+
insert_at -= 1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
lines.insert(insert_at, "\n", rails_pulse_tasks_yaml(indent))
|
|
134
|
+
end
|
|
86
135
|
|
|
87
|
-
|
|
136
|
+
lines.join
|
|
88
137
|
end
|
|
89
138
|
end
|
|
90
139
|
end
|
|
@@ -16,7 +16,7 @@ module Plutonium
|
|
|
16
16
|
# @attr_reader [Symbol, nil] category The category of the action.
|
|
17
17
|
# @attr_reader [Integer] position The position of the action within its category.
|
|
18
18
|
class Base
|
|
19
|
-
attr_reader :name, :label, :description, :icon, :route_options, :confirmation, :turbo, :turbo_frame, :color, :category, :position, :return_to
|
|
19
|
+
attr_reader :name, :label, :description, :icon, :route_options, :confirmation, :turbo, :turbo_frame, :color, :category, :position, :return_to, :modal
|
|
20
20
|
|
|
21
21
|
# Initialize a new action.
|
|
22
22
|
#
|
|
@@ -57,6 +57,8 @@ module Plutonium
|
|
|
57
57
|
@resource_action = options[:resource_action] || false
|
|
58
58
|
@category = ActiveSupport::StringInquirer.new((options[:category] || :secondary).to_s)
|
|
59
59
|
@position = options[:position] || 50
|
|
60
|
+
@modal = options[:modal] || :centered
|
|
61
|
+
validate_modal!
|
|
60
62
|
|
|
61
63
|
freeze
|
|
62
64
|
end
|
|
@@ -85,8 +87,49 @@ module Plutonium
|
|
|
85
87
|
policy.allowed_to?(:"#{name}?")
|
|
86
88
|
end
|
|
87
89
|
|
|
90
|
+
# Returns a new Action with the given options merged over this one.
|
|
91
|
+
# Used by the resource definition to derive variants (e.g. dropping
|
|
92
|
+
# `turbo_frame` when `modal false` is configured) without mutating
|
|
93
|
+
# the frozen original.
|
|
94
|
+
def with(**overrides)
|
|
95
|
+
self.class.new(name, **to_options.merge(overrides))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
# Canonical representation for reconstruction via `with`. Every
|
|
101
|
+
# attribute set in `initialize` MUST appear here; otherwise
|
|
102
|
+
# `with(**overrides)` would silently drop it on round-trip.
|
|
103
|
+
# `category` is exposed as a Symbol since `initialize` re-wraps
|
|
104
|
+
# it in StringInquirer.
|
|
105
|
+
def to_options
|
|
106
|
+
{
|
|
107
|
+
label: @label,
|
|
108
|
+
description: @description,
|
|
109
|
+
icon: @icon,
|
|
110
|
+
color: @color,
|
|
111
|
+
confirmation: @confirmation,
|
|
112
|
+
route_options: @route_options,
|
|
113
|
+
turbo: @turbo,
|
|
114
|
+
turbo_frame: @turbo_frame,
|
|
115
|
+
return_to: @return_to,
|
|
116
|
+
bulk_action: @bulk_action,
|
|
117
|
+
collection_record_action: @collection_record_action,
|
|
118
|
+
record_action: @record_action,
|
|
119
|
+
resource_action: @resource_action,
|
|
120
|
+
category: @category.to_sym,
|
|
121
|
+
position: @position,
|
|
122
|
+
modal: @modal
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
88
126
|
private
|
|
89
127
|
|
|
128
|
+
def validate_modal!
|
|
129
|
+
return if [:centered, :slideover].include?(@modal)
|
|
130
|
+
raise ArgumentError, "modal must be :centered or :slideover, got #{@modal.inspect}"
|
|
131
|
+
end
|
|
132
|
+
|
|
90
133
|
# Build RouteOptions from the provided options
|
|
91
134
|
#
|
|
92
135
|
# @param [RouteOptions, Hash, nil] options The routing options
|
|
@@ -22,7 +22,7 @@ module Plutonium
|
|
|
22
22
|
options[:label] ||= interaction.label
|
|
23
23
|
options[:description] ||= interaction.description
|
|
24
24
|
options[:icon] ||= interaction.icon
|
|
25
|
-
options[:turbo_frame] =
|
|
25
|
+
options[:turbo_frame] = Plutonium::REMOTE_MODAL_FRAME unless options.key?(:turbo_frame)
|
|
26
26
|
|
|
27
27
|
super(name, **options)
|
|
28
28
|
end
|
|
@@ -27,6 +27,9 @@ module Plutonium
|
|
|
27
27
|
# @return [Float] the current defaults version
|
|
28
28
|
attr_reader :defaults_version
|
|
29
29
|
|
|
30
|
+
# @return [Symbol] :classic (legacy Header/Sidebar) or :modern (Topbar/IconRail)
|
|
31
|
+
attr_accessor :shell
|
|
32
|
+
|
|
30
33
|
# Map of version numbers to their default configurations
|
|
31
34
|
VERSION_DEFAULTS = {
|
|
32
35
|
1.0 => proc do |config|
|
|
@@ -48,6 +51,7 @@ module Plutonium
|
|
|
48
51
|
@development = parse_boolean_env("PLUTONIUM_DEV")
|
|
49
52
|
@cache_discovery = !Rails.env.development?
|
|
50
53
|
@enable_hotreload = Rails.env.development?
|
|
54
|
+
@shell = :modern
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
# Load default configuration for a specific version
|
|
@@ -32,6 +32,9 @@ module Plutonium
|
|
|
32
32
|
|
|
33
33
|
# standard CRUD actions
|
|
34
34
|
|
|
35
|
+
# turbo_frame for :new and :edit is set by
|
|
36
|
+
# Resource::Definition.configure_crud_modal_targets! based on the
|
|
37
|
+
# `modal` config. Don't hard-code it here.
|
|
35
38
|
action(:new, route_options: {action: :new},
|
|
36
39
|
resource_action: true, category: :primary,
|
|
37
40
|
icon: Phlex::TablerIcons::Plus, position: 10)
|
|
@@ -33,6 +33,8 @@ module Plutonium
|
|
|
33
33
|
include Scoping
|
|
34
34
|
include Search
|
|
35
35
|
include NestedInputs
|
|
36
|
+
include Views
|
|
37
|
+
include Metadata
|
|
36
38
|
|
|
37
39
|
class IndexPage < Plutonium::UI::Page::Index; end
|
|
38
40
|
|
|
@@ -48,6 +50,8 @@ module Plutonium
|
|
|
48
50
|
|
|
49
51
|
class Table < Plutonium::UI::Table::Resource; end
|
|
50
52
|
|
|
53
|
+
class Grid < Plutonium::UI::Grid::Resource; end
|
|
54
|
+
|
|
51
55
|
class Display < Plutonium::UI::Display::Resource; end
|
|
52
56
|
|
|
53
57
|
class QueryForm < Plutonium::UI::Form::Query; end
|
|
@@ -114,6 +118,10 @@ module Plutonium
|
|
|
114
118
|
self.class::Table
|
|
115
119
|
end
|
|
116
120
|
|
|
121
|
+
def grid_class
|
|
122
|
+
self.class::Grid
|
|
123
|
+
end
|
|
124
|
+
|
|
117
125
|
def detail_class
|
|
118
126
|
self.class::Display
|
|
119
127
|
end
|