a2ui-rails 0.1.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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +373 -0
  3. data/lib/a2ui/modules/action_handler.rb +25 -0
  4. data/lib/a2ui/modules/data_validator.rb +22 -0
  5. data/lib/a2ui/modules/input_parser.rb +25 -0
  6. data/lib/a2ui/modules/layout_adapter.rb +25 -0
  7. data/lib/a2ui/modules/surface.rb +66 -0
  8. data/lib/a2ui/modules/surface_manager.rb +64 -0
  9. data/lib/a2ui/modules/ui_generator.rb +25 -0
  10. data/lib/a2ui/modules/ui_updater.rb +26 -0
  11. data/lib/a2ui/modules.rb +17 -0
  12. data/lib/a2ui/signatures/adapt_layout.rb +19 -0
  13. data/lib/a2ui/signatures/generate_ui.rb +20 -0
  14. data/lib/a2ui/signatures/handle_action.rb +23 -0
  15. data/lib/a2ui/signatures/parse_input.rb +21 -0
  16. data/lib/a2ui/signatures/update_ui.rb +21 -0
  17. data/lib/a2ui/signatures/validate_data.rb +18 -0
  18. data/lib/a2ui/signatures.rb +12 -0
  19. data/lib/a2ui/types/action.rb +9 -0
  20. data/lib/a2ui/types/action_response_type.rb +13 -0
  21. data/lib/a2ui/types/alignment.rb +13 -0
  22. data/lib/a2ui/types/boolean_value.rb +9 -0
  23. data/lib/a2ui/types/button_component.rb +12 -0
  24. data/lib/a2ui/types/card_component.rb +11 -0
  25. data/lib/a2ui/types/check_box_component.rb +10 -0
  26. data/lib/a2ui/types/column_component.rb +12 -0
  27. data/lib/a2ui/types/context_binding.rb +9 -0
  28. data/lib/a2ui/types/data_driven_children.rb +9 -0
  29. data/lib/a2ui/types/data_update.rb +9 -0
  30. data/lib/a2ui/types/distribution.rb +15 -0
  31. data/lib/a2ui/types/divider_component.rb +9 -0
  32. data/lib/a2ui/types/explicit_children.rb +8 -0
  33. data/lib/a2ui/types/icon_component.rb +10 -0
  34. data/lib/a2ui/types/image_component.rb +11 -0
  35. data/lib/a2ui/types/image_fit.rb +14 -0
  36. data/lib/a2ui/types/input_type.rb +16 -0
  37. data/lib/a2ui/types/list_component.rb +10 -0
  38. data/lib/a2ui/types/literal_value.rb +8 -0
  39. data/lib/a2ui/types/number_value.rb +9 -0
  40. data/lib/a2ui/types/object_value.rb +9 -0
  41. data/lib/a2ui/types/orientation.rb +11 -0
  42. data/lib/a2ui/types/path_reference.rb +8 -0
  43. data/lib/a2ui/types/row_component.rb +12 -0
  44. data/lib/a2ui/types/screen_size.rb +12 -0
  45. data/lib/a2ui/types/select_component.rb +12 -0
  46. data/lib/a2ui/types/slider_component.rb +13 -0
  47. data/lib/a2ui/types/stream_action.rb +16 -0
  48. data/lib/a2ui/types/stream_op.rb +10 -0
  49. data/lib/a2ui/types/string_value.rb +9 -0
  50. data/lib/a2ui/types/text_component.rb +10 -0
  51. data/lib/a2ui/types/text_field_component.rb +13 -0
  52. data/lib/a2ui/types/text_usage_hint.rb +16 -0
  53. data/lib/a2ui/types/user_action.rb +11 -0
  54. data/lib/a2ui/types/validation_issue.rb +10 -0
  55. data/lib/a2ui/types.rb +91 -0
  56. data/lib/a2ui/version.rb +6 -0
  57. data/lib/a2ui.rb +7 -0
  58. metadata +130 -0
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class AdaptLayout < DSPy::Signature
6
+ description 'Adapt components for screen size.'
7
+
8
+ input do
9
+ const :components, T::Array[Component]
10
+ const :root_id, String
11
+ const :screen, ScreenSize
12
+ end
13
+
14
+ output do
15
+ const :components, T::Array[Component], description: 'Adapted components'
16
+ const :hidden_ids, T::Array[String], default: [], description: 'IDs to hide'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class GenerateUI < DSPy::Signature
6
+ description 'Generate UI components from natural language.'
7
+
8
+ input do
9
+ const :request, String, description: 'What UI to create'
10
+ const :surface_id, String
11
+ const :available_data, String, default: '{}', description: 'Available data model as JSON'
12
+ end
13
+
14
+ output do
15
+ const :root_id, String, description: 'Root component ID'
16
+ const :components, T::Array[Component], description: 'Component adjacency list'
17
+ const :initial_data, T::Array[DataUpdate], default: [], description: 'Initial data values'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class HandleAction < DSPy::Signature
6
+ description 'Process user action and generate response.'
7
+
8
+ input do
9
+ const :action, UserAction
10
+ const :current_data, String, default: '{}', description: 'Surface data as JSON'
11
+ const :business_rules, String, default: '', description: 'Domain constraints'
12
+ end
13
+
14
+ output do
15
+ const :response_type, ActionResponseType
16
+ const :streams, T::Array[StreamOp], default: []
17
+ const :components, T::Array[Component], default: []
18
+ const :data_updates, T::Array[DataUpdate], default: []
19
+ const :redirect_url, String, default: '', description: 'URL if navigating'
20
+ const :message, String, default: '', description: 'User-facing message'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ParseInput < DSPy::Signature
6
+ description 'Convert natural language to data updates.'
7
+
8
+ input do
9
+ const :text, String, description: 'User natural language input'
10
+ const :target_path, String, description: 'Path to update'
11
+ const :expected_schema, String, default: '', description: 'Expected structure hint'
12
+ end
13
+
14
+ output do
15
+ const :updates, T::Array[DataUpdate]
16
+ const :confidence, Float, description: '0.0-1.0 confidence score'
17
+ const :needs_clarification, T::Boolean
18
+ const :question, String, default: '', description: 'Clarification question if needed'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class UpdateUI < DSPy::Signature
6
+ description 'Generate incremental Turbo Stream updates.'
7
+
8
+ input do
9
+ const :request, String, description: 'What to update'
10
+ const :surface_id, String
11
+ const :current_components, T::Array[Component], default: []
12
+ const :current_data, String, default: '{}', description: 'Current data as JSON'
13
+ end
14
+
15
+ output do
16
+ const :streams, T::Array[StreamOp], description: 'Turbo Stream operations'
17
+ const :components, T::Array[Component], default: [], description: 'New/updated components'
18
+ const :data_updates, T::Array[DataUpdate], default: []
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ValidateData < DSPy::Signature
6
+ description 'Validate data model against rules.'
7
+
8
+ input do
9
+ const :data, String, description: 'Data model as JSON'
10
+ const :rules, String, description: 'Validation rules'
11
+ end
12
+
13
+ output do
14
+ const :valid, T::Boolean
15
+ const :issues, T::Array[ValidationIssue], default: []
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'dspy'
5
+ require_relative 'types'
6
+
7
+ require_relative 'signatures/generate_ui'
8
+ require_relative 'signatures/update_ui'
9
+ require_relative 'signatures/handle_action'
10
+ require_relative 'signatures/validate_data'
11
+ require_relative 'signatures/parse_input'
12
+ require_relative 'signatures/adapt_layout'
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class Action < T::Struct
6
+ const :name, String, description: 'Action identifier sent to server'
7
+ const :context, T::Array[ContextBinding], default: []
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ActionResponseType < T::Enum
6
+ enums do
7
+ UpdateUI = new('update_ui')
8
+ Navigate = new('navigate')
9
+ DeleteSurface = new('delete_surface')
10
+ NoOp = new('no_op')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class Alignment < T::Enum
6
+ enums do
7
+ Start = new('start')
8
+ Center = new('center')
9
+ End = new('end')
10
+ Stretch = new('stretch')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class BooleanValue < T::Struct
6
+ const :key, String
7
+ const :boolean, T::Boolean
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ButtonComponent < T::Struct
6
+ const :id, String
7
+ const :label, Value, description: 'Button label text'
8
+ const :action, Action
9
+ const :disabled, T::Boolean, default: false
10
+ const :variant, String, default: 'primary', description: 'primary, secondary, or danger'
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class CardComponent < T::Struct
6
+ const :id, String
7
+ const :child_id, String, description: 'ID of the child component'
8
+ const :title, String, default: ''
9
+ const :elevation, Integer, default: 1
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class CheckBoxComponent < T::Struct
6
+ const :id, String
7
+ const :checked, PathReference, description: 'Path to boolean in data model'
8
+ const :label, String, default: ''
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ColumnComponent < T::Struct
6
+ const :id, String
7
+ const :children, Children
8
+ const :distribution, Distribution, default: Distribution::Start
9
+ const :alignment, Alignment, default: Alignment::Stretch
10
+ const :gap, Integer, default: 8
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ContextBinding < T::Struct
6
+ const :key, String, description: 'Key name in the action context'
7
+ const :path, String, description: 'JSON Pointer to extract value from'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class DataDrivenChildren < T::Struct
6
+ const :path, String, description: 'JSON Pointer to array in data model'
7
+ const :template_id, String, description: 'Component ID to use as template for each item'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class DataUpdate < T::Struct
6
+ const :path, String, description: 'JSON Pointer path (e.g., /reservation/guests)'
7
+ const :entries, T::Array[DataValue], description: 'Values to set at this path'
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class Distribution < T::Enum
6
+ enums do
7
+ Start = new('start')
8
+ Center = new('center')
9
+ End = new('end')
10
+ SpaceBetween = new('space_between')
11
+ SpaceAround = new('space_around')
12
+ SpaceEvenly = new('space_evenly')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class DividerComponent < T::Struct
6
+ const :id, String
7
+ const :orientation, Orientation, default: Orientation::Horizontal
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ExplicitChildren < T::Struct
6
+ const :ids, T::Array[String], description: 'List of child component IDs'
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class IconComponent < T::Struct
6
+ const :id, String
7
+ const :name, String, description: 'Material icon name (e.g., check, close, menu)'
8
+ const :size, Integer, default: 24
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ImageComponent < T::Struct
6
+ const :id, String
7
+ const :src, Value, description: 'Image URL'
8
+ const :alt, String, default: ''
9
+ const :fit, ImageFit, default: ImageFit::Contain
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ImageFit < T::Enum
6
+ enums do
7
+ Contain = new('contain')
8
+ Cover = new('cover')
9
+ Fill = new('fill')
10
+ ScaleDown = new('scale_down')
11
+ None = new('none')
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class InputType < T::Enum
6
+ enums do
7
+ Text = new('text')
8
+ Number = new('number')
9
+ Date = new('date')
10
+ Longtext = new('longtext')
11
+ Email = new('email')
12
+ Tel = new('tel')
13
+ Url = new('url')
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ListComponent < T::Struct
6
+ const :id, String
7
+ const :children, Children
8
+ const :orientation, Orientation, default: Orientation::Vertical
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class LiteralValue < T::Struct
6
+ const :value, String, description: 'The literal string value'
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class NumberValue < T::Struct
6
+ const :key, String
7
+ const :number, Float
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ObjectValue < T::Struct
6
+ const :key, String
7
+ const :entries, T::Hash[String, T.untyped], default: {}, description: 'Nested key-value pairs as JSON-like hash'
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class Orientation < T::Enum
6
+ enums do
7
+ Horizontal = new('horizontal')
8
+ Vertical = new('vertical')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class PathReference < T::Struct
6
+ const :path, String, description: 'JSON Pointer to data model (e.g., /user/name)'
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class RowComponent < T::Struct
6
+ const :id, String
7
+ const :children, Children
8
+ const :distribution, Distribution, default: Distribution::Start
9
+ const :alignment, Alignment, default: Alignment::Center
10
+ const :gap, Integer, default: 8
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ScreenSize < T::Enum
6
+ enums do
7
+ Mobile = new('mobile')
8
+ Tablet = new('tablet')
9
+ Desktop = new('desktop')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class SelectComponent < T::Struct
6
+ const :id, String
7
+ const :value, PathReference, description: 'Path to selected value'
8
+ const :options_path, PathReference, description: 'Path to array of {value, label} options'
9
+ const :label, String, default: ''
10
+ const :placeholder, String, default: 'Select...'
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class SliderComponent < T::Struct
6
+ const :id, String
7
+ const :value, PathReference, description: 'Path to numeric value'
8
+ const :min, Float, default: 0.0
9
+ const :max, Float, default: 100.0
10
+ const :step, Float, default: 1.0
11
+ const :label, String, default: ''
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class StreamAction < T::Enum
6
+ enums do
7
+ Append = new('append')
8
+ Prepend = new('prepend')
9
+ Replace = new('replace')
10
+ Update = new('update')
11
+ Remove = new('remove')
12
+ Before = new('before')
13
+ After = new('after')
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class StreamOp < T::Struct
6
+ const :action, StreamAction
7
+ const :target, String, description: 'Target element ID'
8
+ const :component_ids, T::Array[String], default: [], description: 'Components to render'
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class StringValue < T::Struct
6
+ const :key, String
7
+ const :string, String
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class TextComponent < T::Struct
6
+ const :id, String
7
+ const :content, Value, description: 'Text content'
8
+ const :usage_hint, TextUsageHint, default: TextUsageHint::Body
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class TextFieldComponent < T::Struct
6
+ const :id, String
7
+ const :value, PathReference, description: 'Path to bound value in data model'
8
+ const :input_type, InputType, default: InputType::Text
9
+ const :label, String, default: ''
10
+ const :placeholder, String, default: ''
11
+ const :is_required, T::Boolean, default: false, description: 'Whether the field is required'
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class TextUsageHint < T::Enum
6
+ enums do
7
+ H1 = new('h1')
8
+ H2 = new('h2')
9
+ H3 = new('h3')
10
+ H4 = new('h4')
11
+ H5 = new('h5')
12
+ Body = new('body')
13
+ Caption = new('caption')
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class UserAction < T::Struct
6
+ const :name, String, description: 'Action name from component'
7
+ const :surface_id, String
8
+ const :source_id, String, description: 'Component ID that triggered action'
9
+ const :context, T::Hash[String, String], default: {}, description: 'Resolved context values'
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module A2UI
5
+ class ValidationIssue < T::Struct
6
+ const :path, String, description: 'JSON Pointer to invalid field'
7
+ const :message, String
8
+ const :code, String, description: 'Machine-readable code like required, invalid_format'
9
+ end
10
+ end