plutonium 0.23.4 → 0.23.5

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +2 -2
  3. data/config/initializers/sqlite_json_alias.rb +1 -1
  4. data/docs/.vitepress/config.ts +60 -19
  5. data/docs/guide/cursor-rules.md +75 -0
  6. data/docs/guide/deep-dive/authorization.md +189 -0
  7. data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
  8. data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
  9. data/docs/guide/index.md +28 -0
  10. data/docs/guide/introduction/02-core-concepts.md +440 -0
  11. data/docs/guide/tutorial/01-project-setup.md +75 -0
  12. data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
  13. data/docs/guide/tutorial/03-defining-resources.md +90 -0
  14. data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
  15. data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
  16. data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
  17. data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
  18. data/docs/index.md +24 -31
  19. data/docs/modules/action.md +190 -0
  20. data/docs/modules/authentication.md +236 -0
  21. data/docs/modules/configuration.md +599 -0
  22. data/docs/modules/controller.md +398 -0
  23. data/docs/modules/core.md +316 -0
  24. data/docs/modules/definition.md +876 -0
  25. data/docs/modules/display.md +759 -0
  26. data/docs/modules/form.md +605 -0
  27. data/docs/modules/generator.md +288 -0
  28. data/docs/modules/index.md +167 -0
  29. data/docs/modules/interaction.md +470 -0
  30. data/docs/modules/package.md +151 -0
  31. data/docs/modules/policy.md +176 -0
  32. data/docs/modules/portal.md +710 -0
  33. data/docs/modules/query.md +287 -0
  34. data/docs/modules/resource_record.md +618 -0
  35. data/docs/modules/routing.md +641 -0
  36. data/docs/modules/table.md +293 -0
  37. data/docs/modules/ui.md +631 -0
  38. data/docs/public/plutonium.mdc +667 -0
  39. data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
  40. data/lib/plutonium/ui/display/resource.rb +7 -2
  41. data/lib/plutonium/ui/table/resource.rb +8 -3
  42. data/lib/plutonium/version.rb +1 -1
  43. metadata +36 -9
  44. data/docs/guide/getting-started/authorization.md +0 -296
  45. data/docs/guide/getting-started/core-concepts.md +0 -432
  46. data/docs/guide/getting-started/index.md +0 -21
  47. data/docs/guide/tutorial.md +0 -401
  48. /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,759 @@
1
+ ---
2
+ title: Display Module
3
+ ---
4
+
5
+ # Display Module
6
+
7
+ The Display module provides a comprehensive system for rendering and displaying data values in Plutonium applications. Built on top of `Phlexi::Display`, it offers specialized components for different data types, consistent theming, and intelligent value rendering.
8
+
9
+ ::: tip
10
+ The Display module is located in `lib/plutonium/ui/display/`.
11
+ :::
12
+
13
+ ## Overview
14
+
15
+ - **Value Rendering**: Intelligent rendering of different data types.
16
+ - **Specialized Components**: Purpose-built components for associations, attachments, markdown, etc.
17
+ - **Theme System**: Consistent styling across all display components.
18
+ - **Type Inference**: Automatic component selection based on data types.
19
+ - **Resource Integration**: Seamless integration with resource definitions.
20
+ - **Responsive Design**: Mobile-first responsive display layouts.
21
+
22
+ ## Core Components
23
+
24
+ ### Base Display (`lib/plutonium/ui/display/base.rb`)
25
+
26
+ This is the foundation that all display components inherit from. It extends `Phlexi::Display::Base` with Plutonium's specific behaviors and custom display components.
27
+
28
+ ::: details Base Display Implementation
29
+ ```ruby
30
+ class Plutonium::UI::Display::Base < Phlexi::Display::Base
31
+ include Plutonium::UI::Component::Behaviour
32
+
33
+ # Enhanced builder with Plutonium-specific components
34
+ class Builder < Builder
35
+ include Plutonium::UI::Display::Options::InferredTypes
36
+
37
+ def association_tag(**options, &block)
38
+ create_component(Plutonium::UI::Display::Components::Association, :association, **options, &block)
39
+ end
40
+
41
+ def markdown_tag(**options, &block)
42
+ create_component(Plutonium::UI::Display::Components::Markdown, :markdown, **options, &block)
43
+ end
44
+
45
+ def attachment_tag(**options, &block)
46
+ create_component(Plutonium::UI::Display::Components::Attachment, :attachment, **options, &block)
47
+ end
48
+
49
+ def phlexi_render_tag(**options, &block)
50
+ create_component(Plutonium::UI::Display::Components::PhlexiRender, :phlexi_render, **options, &block)
51
+ end
52
+ alias_method :phlexi_tag, :phlexi_render_tag
53
+ end
54
+ end
55
+ ```
56
+ :::
57
+
58
+ ### Resource Display (`lib/plutonium/ui/display/resource.rb`)
59
+
60
+ This is a specialized component for displaying resource objects, automatically rendering fields and associations based on the resource's definition.
61
+
62
+ ```ruby
63
+ class PostDisplay < Plutonium::UI::Display::Resource
64
+ def initialize(post, resource_fields:, resource_associations:, resource_definition:)
65
+ super(
66
+ post,
67
+ resource_fields: resource_fields,
68
+ resource_associations: resource_associations,
69
+ resource_definition: resource_definition
70
+ )
71
+ end
72
+
73
+ def display_template
74
+ render_fields # Render configured fields
75
+ render_associations if present_associations? # Render associations
76
+ end
77
+ end
78
+ ```
79
+
80
+ ## Display Components
81
+
82
+ ### Association Component
83
+
84
+ Renders associated objects with automatic linking to the resource's show page if it's a registered resource.
85
+
86
+ ::: code-group
87
+ ```ruby [Usage]
88
+ # Automatically used for association fields
89
+ render field(:author).association_tag
90
+ ```
91
+ ```ruby [Implementation]
92
+ class Plutonium::UI::Display::Components::Association
93
+ def render_value(value)
94
+ if registered_resources.include?(value.class)
95
+ # Create link to resource
96
+ href = resource_url_for(value, parent: appropriate_parent)
97
+ a(class: themed(:link), href: href) { display_name_of(value) }
98
+ else
99
+ # Plain text display
100
+ display_name_of(value)
101
+ end
102
+ end
103
+ end
104
+ ```
105
+ :::
106
+
107
+ ### Attachment Component
108
+
109
+ Provides a rich display for file attachments with thumbnails for images and icons for other file types.
110
+
111
+ ::: code-group
112
+ ```ruby [Basic Usage]
113
+ # Automatically used for attachment fields
114
+ render field(:featured_image).attachment_tag
115
+ ```
116
+ ```ruby [With Options]
117
+ render field(:documents).attachment_tag(caption: false)
118
+ render field(:gallery).attachment_tag(
119
+ caption: ->(attachment) { attachment.description }
120
+ )
121
+ ```
122
+ :::
123
+
124
+ ::: details Attachment Component Implementation
125
+ ```ruby
126
+ class Plutonium::UI::Display::Components::Attachment
127
+ def render_value(attachment)
128
+ div(
129
+ class: "attachment-preview",
130
+ data: {
131
+ controller: "attachment-preview",
132
+ attachment_preview_mime_type_value: attachment.content_type,
133
+ attachment_preview_thumbnail_url_value: attachment_thumbnail_url(attachment)
134
+ }
135
+ ) do
136
+ render_thumbnail(attachment) # Image or file type icon
137
+ render_caption(attachment) # Filename or custom caption
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def render_thumbnail(attachment)
144
+ if attachment.representable?
145
+ img(src: attachment_thumbnail_url(attachment), class: "w-full h-full object-cover")
146
+ else
147
+ # File type icon
148
+ div(class: "file-icon") { ".#{attachment_extension(attachment)}" }
149
+ end
150
+ end
151
+ end
152
+ ```
153
+ :::
154
+
155
+ ### Markdown Component
156
+
157
+ Securely renders markdown content with syntax highlighting for code blocks.
158
+
159
+ ::: code-group
160
+ ```ruby [Usage]
161
+ # Automatically used for :markdown fields
162
+ render field(:description).markdown_tag
163
+ ```
164
+ ```ruby [Implementation]
165
+ class Plutonium::UI::Display::Components::Markdown
166
+ RENDERER = Redcarpet::Markdown.new(
167
+ Redcarpet::Render::HTML.new(
168
+ safe_links_only: true,
169
+ with_toc_data: true,
170
+ hard_wrap: true,
171
+ link_attributes: { rel: :nofollow, target: :_blank }
172
+ ),
173
+ autolink: true,
174
+ tables: true,
175
+ fenced_code_blocks: true,
176
+ strikethrough: true,
177
+ footnotes: true
178
+ )
179
+
180
+ def render_value(value)
181
+ article(class: themed(:markdown)) do
182
+ raw(safe(render_markdown(value)))
183
+ end
184
+ end
185
+ end
186
+ ```
187
+ :::
188
+
189
+ ### PhlexiRender Component
190
+
191
+ Renders a given value using a custom Phlex component, allowing for complex, specialized displays.
192
+
193
+ ::: code-group
194
+ ```ruby [Block Syntax (Recommended)]
195
+ # Render with conditional logic
196
+ render field(:chart_data) do |f|
197
+ if f.value.present?
198
+ render ChartComponent.new(data: f.value, class: f.dom.css_class)
199
+ else
200
+ span(class: "text-gray-500") { "No chart data" }
201
+ end
202
+ end
203
+
204
+ # Simple component rendering
205
+ render field(:status_badge) do |f|
206
+ render StatusBadgeComponent.new(status: f.value, class: f.dom.css_class)
207
+ end
208
+ ```
209
+ ```ruby [Implementation]
210
+ class Plutonium::UI::Display::Components::PhlexiRender
211
+ def render_value(value)
212
+ phlexi_render(build_phlexi_component(value)) do
213
+ # Fallback rendering if component fails
214
+ p(class: themed(:string)) { value }
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ def build_phlexi_component(value)
221
+ @builder.call(value, attributes)
222
+ end
223
+ end
224
+ ```
225
+ :::
226
+
227
+ ## Type Inference
228
+
229
+ The display system automatically selects the appropriate component based on the field's type, but you can always override it manually.
230
+
231
+ ::: code-group
232
+ ```ruby [Automatic Inference]
233
+ # Based on Active Record column types or Active Storage attachments
234
+ render field(:title).string_tag # -> :string
235
+ render field(:content).text_tag # -> :text
236
+ render field(:published_at).datetime_tag # -> :datetime
237
+ render field(:author).association_tag # -> :association
238
+ render field(:featured_image).attachment_tag # -> :attachment
239
+ render field(:description).markdown_tag # -> :markdown (if configured in definition)
240
+ ```
241
+ ```ruby [Manual Override]
242
+ render field(:title).string_tag
243
+ render field(:content).markdown_tag
244
+ render field(:author).association_tag
245
+ ```
246
+ :::
247
+
248
+ ::: details Type Mapping Implementation
249
+ ```ruby
250
+ module Plutonium::UI::Display::Options::InferredTypes
251
+ private
252
+
253
+ def infer_field_component
254
+ case inferred_field_type
255
+ when :attachment
256
+ :attachment
257
+ when :association
258
+ :association
259
+ when :boolean
260
+ :boolean
261
+ # ... and so on for all standard types
262
+ else
263
+ :string
264
+ end
265
+ end
266
+ end
267
+ ```
268
+ :::
269
+
270
+ ## Theme System
271
+
272
+ ### Display Theme (`lib/plutonium/ui/display/theme.rb`)
273
+
274
+ Comprehensive theming for consistent visual appearance:
275
+
276
+ ```ruby
277
+ class Plutonium::UI::Display::Theme < Phlexi::Display::Theme
278
+ def self.theme
279
+ super.merge({
280
+ # Layout
281
+ fields_wrapper: "p-6 grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-6 gap-y-10 grid-flow-row-dense",
282
+ value_wrapper: "max-h-[300px] overflow-y-auto",
283
+
284
+ # Typography
285
+ label: "text-base font-bold text-gray-500 dark:text-gray-400 mb-1",
286
+ string: "text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
287
+ text: "text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
288
+
289
+ # Interactive elements
290
+ link: "text-primary-600 dark:text-primary-500 whitespace-pre-line",
291
+ email: "flex items-center text-md text-primary-600 dark:text-primary-500 mb-1",
292
+ phone: "flex items-center text-md text-primary-600 dark:text-primary-500 mb-1",
293
+
294
+ # Special content
295
+ markdown: "format dark:format-invert format-primary",
296
+ json: "text-sm text-gray-900 dark:text-white mb-1 whitespace-pre font-mono shadow-inner p-4",
297
+
298
+ # Attachments
299
+ attachment_value_wrapper: "grid grid-cols-[repeat(auto-fill,minmax(0,180px))]",
300
+
301
+ # Colors
302
+ color: "flex items-center text-md text-gray-900 dark:text-white mb-1",
303
+ color_indicator: "w-10 h-10 rounded-full mr-2"
304
+ })
305
+ end
306
+ end
307
+ ```
308
+
309
+ ### Table Display Theme (`lib/plutonium/ui/table/display_theme.rb`)
310
+
311
+ Specialized theming for table contexts:
312
+
313
+ ```ruby
314
+ class Plutonium::UI::Table::DisplayTheme < Phlexi::Table::DisplayTheme
315
+ def self.theme
316
+ super.merge({
317
+ # Compact display for tables
318
+ value_wrapper: "max-h-[150px] overflow-y-auto",
319
+ prefixed_icon: "w-4 h-4 mr-1",
320
+
321
+ # Table-specific styles
322
+ email: "flex items-center text-primary-600 dark:text-primary-500 whitespace-nowrap",
323
+ phone: "flex items-center text-primary-600 dark:text-primary-500 whitespace-nowrap",
324
+ attachment_value_wrapper: "flex flex-wrap gap-1"
325
+ })
326
+ end
327
+ end
328
+ ```
329
+
330
+ ## Usage Patterns
331
+
332
+ ### Basic Display
333
+
334
+ ```ruby
335
+ # Simple field display
336
+ class PostDisplay < Plutonium::UI::Display::Base
337
+ def display_template
338
+ render field(:title).string_tag
339
+ render field(:content).text_tag
340
+ render field(:published_at).datetime_tag
341
+ render field(:author).association_tag
342
+ end
343
+ end
344
+ ```
345
+
346
+ ### Field Rendering and Wrappers
347
+
348
+ Fields must be explicitly rendered using the `render` method. You can also use wrappers to control the layout and styling:
349
+
350
+ ```ruby
351
+ class PostDisplay < Plutonium::UI::Display::Base
352
+ def display_template
353
+ # Basic field rendering
354
+ render field(:title).string_tag
355
+
356
+ # Field with wrapper options
357
+ render field(:content).wrapped(class: "col-span-full prose") do |f|
358
+ render f.markdown_tag
359
+ end
360
+
361
+ # Field with custom wrapper and styling
362
+ render field(:author).wrapped(
363
+ class: "border rounded-lg p-4",
364
+ data: { controller: "tooltip" }
365
+ ) do |f|
366
+ render f.association_tag
367
+ end
368
+
369
+ # Multiple fields with consistent wrapper
370
+ [:created_at, :updated_at].each do |field_name|
371
+ render field(field_name).wrapped(class: "text-sm text-gray-500") do |f|
372
+ render f.datetime_tag
373
+ end
374
+ end
375
+ end
376
+ end
377
+ ```
378
+
379
+ ### Resource Display
380
+
381
+ ```ruby
382
+ # Automatic resource display based on definition
383
+ class PostsController < ApplicationController
384
+ def show
385
+ @post = Post.find(params[:id])
386
+ @display = Plutonium::UI::Display::Resource.new(
387
+ @post,
388
+ resource_fields: current_definition.defined_displays.keys,
389
+ resource_associations: [],
390
+ resource_definition: current_definition
391
+ )
392
+ end
393
+ end
394
+
395
+ # In view
396
+ <%= render @display %>
397
+ ```
398
+
399
+ ### Custom Display Components
400
+
401
+ ```ruby
402
+ # Create custom display component
403
+ class StatusBadgeComponent < Plutonium::UI::Component::Base
404
+ def initialize(status, **options)
405
+ @status = status
406
+ @options = options
407
+ end
408
+
409
+ def view_template
410
+ span(class: badge_classes) { @status.humanize }
411
+ end
412
+
413
+ private
414
+
415
+ def badge_classes
416
+ base_classes = "px-2 py-1 text-xs font-medium rounded-full"
417
+ case @status
418
+ when 'active'
419
+ "#{base_classes} bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300"
420
+ when 'inactive'
421
+ "#{base_classes} bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300"
422
+ else
423
+ "#{base_classes} bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300"
424
+ end
425
+ end
426
+ end
427
+
428
+ # Use in display
429
+ render field(:status) do |f|
430
+ render StatusBadgeComponent.new(status: f.value)
431
+ end
432
+ ```
433
+
434
+ ### Conditional Display
435
+
436
+ You can conditionally show or hide display fields using the `:condition` option in your resource definition. This is useful for creating dynamic views that adapt to the state of your data.
437
+
438
+ **Note:** Conditional display is for cosmetic or state-based logic. For controlling data visibility based on user roles or permissions, use **policies**.
439
+
440
+ ```ruby
441
+ # app/definitions/post_definition.rb
442
+ class PostDefinition < Plutonium::Resource::Definition
443
+ # Show a field only when the object is in a certain state.
444
+ display :published_at, condition: -> { object.published? }
445
+ display :reason_for_rejection, condition: -> { object.rejected? }
446
+ display :scheduled_for, condition: -> { object.scheduled? }
447
+
448
+ # Show a field based on the object's attributes.
449
+ display :comments, condition: -> { object.comments_enabled? }
450
+
451
+ # Show debug information only in development.
452
+ display :debug_info, condition: -> { Rails.env.development? }
453
+ end
454
+ ```
455
+
456
+ ::: tip Condition Context
457
+ `condition` procs for `display` fields are evaluated in the display rendering context, which means they have access to:
458
+ - `object` - The record being displayed
459
+ - All helper methods available in the display context
460
+
461
+ This allows for dynamic field visibility based on the record's state or other contextual information.
462
+ :::
463
+
464
+ You can also implement custom conditional logic by overriding the rendering methods:
465
+
466
+ ```ruby
467
+ class PostDisplay < Plutonium::UI::Display::Resource
468
+ private
469
+
470
+ def render_resource_field(name)
471
+ # Only render if user has permission
472
+ when_permitted(name) do
473
+ # Get field and display options from definition
474
+ field_options = resource_definition.defined_fields[name]&.dig(:options) || {}
475
+ display_definition = resource_definition.defined_displays[name] || {}
476
+ display_options = display_definition[:options] || {}
477
+
478
+ # Render field with appropriate component
479
+ field(name, **field_options).wrapped(**wrapper_options) do |f|
480
+ render_field_component(f, display_options)
481
+ end
482
+ end
483
+ end
484
+
485
+ def when_permitted(name, &block)
486
+ return unless @resource_fields.include?(name)
487
+ return unless policy_allows_field?(name)
488
+
489
+ yield
490
+ end
491
+ end
492
+ ```
493
+
494
+ ### Responsive Layouts
495
+
496
+ ```ruby
497
+ # Grid layout with responsive columns
498
+ class PostDisplay < Plutonium::UI::Display::Base
499
+ private
500
+
501
+ def fields_wrapper(&block)
502
+ div(class: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6") do
503
+ yield
504
+ end
505
+ end
506
+ end
507
+
508
+ # Full-width fields
509
+ render field(:description).wrapped(class: "col-span-full") do |f|
510
+ render f.markdown_tag
511
+ end
512
+
513
+ # Compact display
514
+ render field(:tags).wrapped(class: "col-span-1") do |f|
515
+ render f.collection_tag
516
+ end
517
+ ```
518
+
519
+ ## Helper Integration
520
+
521
+ ### Display Helpers (`lib/plutonium/helpers/display_helper.rb`)
522
+
523
+ Rich helper methods for value formatting:
524
+
525
+ ```ruby
526
+ module Plutonium::Helpers::DisplayHelper
527
+ # Generic field display with helper support
528
+ def display_field(value:, helper: nil, **options)
529
+ return "-" unless value.present?
530
+
531
+ if value.respond_to?(:each) && stack_multiple
532
+ # Handle collections
533
+ tag.ul(class: "list-unstyled") do
534
+ value.each do |val|
535
+ concat tag.li(display_field_value(value: val, helper: helper))
536
+ end
537
+ end
538
+ else
539
+ display_field_value(value: value, helper: helper, **options)
540
+ end
541
+ end
542
+
543
+ # Specialized display methods
544
+ def display_association_value(association)
545
+ display_name = display_name_of(association)
546
+ if registered_resources.include?(association.class)
547
+ link_to display_name, resource_url_for(association),
548
+ class: "font-medium text-primary-600 dark:text-primary-500"
549
+ else
550
+ display_name
551
+ end
552
+ end
553
+
554
+ def display_datetime_value(value)
555
+ timeago(value)
556
+ end
557
+
558
+ def display_boolean_value(value)
559
+ tag.input(type: :checkbox, checked: value, disabled: true)
560
+ end
561
+
562
+ def display_name_of(obj, separator: ", ")
563
+ return unless obj.present?
564
+ return obj.map { |i| display_name_of(i) }.join(separator) if obj.is_a?(Array)
565
+
566
+ # Try common display methods
567
+ %i[to_label name title].each do |method|
568
+ name = obj.public_send(method) if obj.respond_to?(method)
569
+ return name if name.present?
570
+ end
571
+
572
+ # Fallback for Active Record objects
573
+ return "#{resource_name(obj.class)} ##{obj.id}" if obj.respond_to?(:id)
574
+
575
+ obj.to_s
576
+ end
577
+ end
578
+ ```
579
+
580
+ ## Advanced Features
581
+
582
+ ### Attachment Previews
583
+
584
+ ```ruby
585
+ # Automatic attachment preview with JavaScript enhancement
586
+ field(:documents).attachment_tag
587
+
588
+ # Generates:
589
+ # - Thumbnail images for representable files
590
+ # - File type indicators for non-representable files
591
+ # - Click-to-preview functionality
592
+ # - Download links
593
+ # - Responsive grid layout
594
+
595
+ # JavaScript controller provides:
596
+ # - Preview modal/lightbox
597
+ # - Keyboard navigation
598
+ # - Touch/swipe support
599
+ # - Loading states
600
+ ```
601
+
602
+ ### Markdown Processing
603
+
604
+ ```ruby
605
+ # Secure markdown with custom renderer
606
+ class CustomMarkdownRenderer < Redcarpet::Render::HTML
607
+ def initialize(options = {})
608
+ super(options.merge(
609
+ safe_links_only: true,
610
+ with_toc_data: true,
611
+ hard_wrap: true,
612
+ link_attributes: { rel: :nofollow, target: :_blank }
613
+ ))
614
+ end
615
+
616
+ def block_code(code, language)
617
+ # Custom syntax highlighting
618
+ "<pre><code class=\"language-#{language}\">#{highlight_code(code, language)}</code></pre>"
619
+ end
620
+ end
621
+
622
+ # Use custom renderer
623
+ CUSTOM_RENDERER = Redcarpet::Markdown.new(
624
+ CustomMarkdownRenderer.new,
625
+ autolink: true,
626
+ tables: true,
627
+ fenced_code_blocks: true
628
+ )
629
+ ```
630
+
631
+ ### Performance Optimizations
632
+
633
+ ```ruby
634
+ # Lazy loading for expensive displays
635
+ class PostDisplay < Plutonium::UI::Display::Base
636
+ def display_template
637
+ render field(:title).string_tag
638
+
639
+ # Only render associations if not in turbo frame
640
+ if current_turbo_frame.nil?
641
+ render field(:comments_count).number_tag
642
+ render field(:recent_comments).collection_tag
643
+ end
644
+ end
645
+ end
646
+
647
+ # Conditional rendering based on permissions
648
+ def render_resource_field(name)
649
+ return unless authorized_to_view_field?(name)
650
+
651
+ # Cache expensive field computations
652
+ @field_cache ||= {}
653
+ @field_cache[name] ||= compute_field_display(name)
654
+
655
+ render @field_cache[name]
656
+ end
657
+ ```
658
+
659
+ ## Testing
660
+
661
+ ### Component Testing
662
+
663
+ ```ruby
664
+ RSpec.describe Plutonium::UI::Display::Components::Association do
665
+ let(:user) { create(:user, name: "John Doe") }
666
+ let(:component) { described_class.new(field_for(user, :author)) }
667
+
668
+ context "when association is a registered resource" do
669
+ before { allow(component).to receive(:registered_resources).and_return([User]) }
670
+
671
+ it "renders a link to the resource" do
672
+ html = render(component)
673
+ expect(html).to include('href="/users/')
674
+ expect(html).to include("John Doe")
675
+ end
676
+ end
677
+
678
+ context "when association is not registered" do
679
+ before { allow(component).to receive(:registered_resources).and_return([]) }
680
+
681
+ it "renders plain text" do
682
+ html = render(component)
683
+ expect(html).not_to include('href=')
684
+ expect(html).to include("John Doe")
685
+ end
686
+ end
687
+ end
688
+ ```
689
+
690
+ ### Integration Testing
691
+
692
+ ```ruby
693
+ RSpec.describe "Display Integration", type: :system do
694
+ let(:post) { create(:post, :with_attachments, :with_author) }
695
+
696
+ it "displays all field types correctly" do
697
+ visit post_path(post)
698
+
699
+ # Text fields
700
+ expect(page).to have_content(post.title)
701
+ expect(page).to have_content(post.content)
702
+
703
+ # Associations
704
+ expect(page).to have_link(post.author.name, href: user_path(post.author))
705
+
706
+ # Attachments
707
+ expect(page).to have_css(".attachment-preview")
708
+ expect(page).to have_link(href: rails_blob_path(post.featured_image))
709
+
710
+ # Timestamps
711
+ expect(page).to have_content("ago") # timeago formatting
712
+ end
713
+
714
+ it "handles responsive layout" do
715
+ visit post_path(post)
716
+
717
+ # Desktop layout
718
+ expect(page).to have_css(".md\\:grid-cols-2")
719
+
720
+ # Mobile layout (resize viewport)
721
+ page.driver.browser.manage.window.resize_to(375, 667)
722
+ expect(page).to have_css(".grid-cols-1")
723
+ end
724
+ end
725
+ ```
726
+
727
+ ## Best Practices
728
+
729
+ ### Component Design
730
+
731
+ 1. **Single Responsibility**: Each component should handle one display type
732
+ 2. **Consistent API**: Follow the same patterns for all display components
733
+ 3. **Theme Integration**: Use themed classes for consistent styling
734
+ 4. **Accessibility**: Include proper ARIA attributes and semantic HTML
735
+ 5. **Performance**: Avoid expensive operations in render methods
736
+
737
+ ### Value Processing
738
+
739
+ 1. **Null Safety**: Always handle nil/empty values gracefully
740
+ 2. **Type Checking**: Verify value types before processing
741
+ 3. **Sanitization**: Sanitize user-generated content (especially HTML/markdown)
742
+ 4. **Formatting**: Use consistent formatting for dates, numbers, etc.
743
+ 5. **Localization**: Support internationalization for display text
744
+
745
+ ### Responsive Design
746
+
747
+ 1. **Mobile First**: Design for mobile, enhance for desktop
748
+ 2. **Flexible Layouts**: Use CSS Grid/Flexbox for adaptive layouts
749
+ 3. **Content Priority**: Show most important content first on small screens
750
+ 4. **Touch Friendly**: Ensure interactive elements are touch-accessible
751
+ 5. **Performance**: Optimize images and assets for different screen sizes
752
+
753
+ ### Security
754
+
755
+ 1. **Input Sanitization**: Always sanitize user-generated content
756
+ 2. **XSS Prevention**: Use safe HTML rendering methods
757
+ 3. **Link Security**: Add `rel="nofollow"` to user-generated links
758
+ 4. **File Security**: Validate file types and sizes for attachments
759
+ 5. **Permission Checks**: Verify user permissions before displaying sensitive data