inquirex-ui 0.2.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.
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "timeout"
6
+ require "yard"
7
+
8
+ def shell(*args)
9
+ puts "running: #{args.join(" ")}"
10
+ system(args.join(" "))
11
+ end
12
+
13
+ task :clean do
14
+ shell("rm -rf pkg/ tmp/ coverage/ doc/ ")
15
+ end
16
+
17
+ task gem: [:build] do
18
+ shell("gem install pkg/*")
19
+ end
20
+
21
+ task permissions: [:clean] do
22
+ shell("chmod -v o+r,g+r * */* */*/* */*/*/* */*/*/*/* */*/*/*/*/*")
23
+ shell("find . -type d -exec chmod o+x,g+x {} \\;")
24
+ end
25
+
26
+ task build: :permissions
27
+
28
+ YARD::Rake::YardocTask.new(:doc) do |t|
29
+ t.files = %w[lib/**/*.rb exe/*.rb - README.md LICENSE.txt CHANGELOG.md]
30
+ t.options.unshift("--title", '"FlowEngine — DSL + AST for buildiong complex flows in Ruby."')
31
+ t.after = -> { exec("open doc/index.html") } if RUBY_PLATFORM =~ /darwin/
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:spec)
35
+
36
+ task default: :spec
data/exe/inquirex-ui ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "inquirex/ui"
data/justfile ADDED
@@ -0,0 +1,38 @@
1
+ set shell := ["bash", "-lc"]
2
+
3
+ rbenv := 'eval "$(rbenv init -)"'
4
+
5
+ [no-exit-message]
6
+ recipes:
7
+ @just --choose
8
+
9
+ # Sync all dependencies
10
+ install:
11
+ {{rbenv}} && bin/setup
12
+
13
+ # Lint and reformat files
14
+ lint-fix *args:
15
+ {{rbenv}} && bundle exec rubocop -a
16
+
17
+ alias format := lint-fix
18
+
19
+ # Lint and reformat files
20
+ lint:
21
+ {{rbenv}} && bundle exec rubocop
22
+
23
+ # Run all the tests
24
+ test *args:
25
+ {{rbenv}} && ENVIRONMENT=test bundle exec rspec {{args}}
26
+
27
+ # Run tests with coverage
28
+ test-coverage *args:
29
+ ENVIRONMENT=test COVERAGE=true bundle exec rspec
30
+
31
+ clean:
32
+ #!/usr/bin/env bash
33
+ find . -name .DS_Store -delete -print || true
34
+ rm -rf tmp/*
35
+
36
+ # Run all lefthook pre-commit hooks
37
+ ci:
38
+ {{rbenv}} && lefthook run pre-commit --all-files
data/lefthook.yml ADDED
@@ -0,0 +1,35 @@
1
+ output:
2
+ - summary
3
+ - failure
4
+
5
+ pre-commit:
6
+ parallel: true
7
+ jobs:
8
+ - name: lint
9
+ run: bundle exec rubocop -c .rubocop.yml {staged_files}
10
+ glob: "*.{rb,Gemfile}"
11
+ stage_fixed: true
12
+
13
+ - name: check for conflict markers and whitespace issues
14
+ run: git --no-pager diff --check
15
+
16
+ # If tests take >1 second, move this (or just the long-running tests) to pre-push.
17
+ - name: run tests
18
+ run: just test
19
+
20
+ - name: fix rubocop formatting issues
21
+ run: bundle exec rubocop -a {staged_files}
22
+ glob: "*.{rb,Gemfile,gemspec}"
23
+ stage_fixed: true
24
+
25
+ - name: spell check
26
+ run: codespell {staged_files}
27
+ glob: "*.{rb,md,gemspec}"
28
+
29
+ - name: format markdown
30
+ run: mdformat {staged_files}
31
+ glob: "*.md"
32
+ stage_fixed: true
33
+
34
+ - name: scan for secrets
35
+ run: detect-secrets-hook --baseline .secrets.baseline
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inquirex
4
+ module UI
5
+ module DSL
6
+ # Extends Inquirex::DSL::FlowBuilder to use UI::DSL::StepBuilder for all steps,
7
+ # ensuring every node produced by Inquirex::UI.define carries widget hints.
8
+ class FlowBuilder < Inquirex::DSL::FlowBuilder
9
+ private
10
+
11
+ # Overrides the parent's add_step to instantiate UI::DSL::StepBuilder
12
+ # instead of the base StepBuilder, so widget hints are available in blocks.
13
+ def add_step(id, verb, &block)
14
+ builder = UI::DSL::StepBuilder.new(verb)
15
+ builder.instance_eval(&block) if block
16
+ @nodes[id.to_sym] = builder.build(id)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inquirex
4
+ module UI
5
+ module DSL
6
+ # Extends Inquirex::DSL::StepBuilder with widget rendering hint methods.
7
+ # Used by FlowBuilder whenever a step block is evaluated inside Inquirex::UI.define.
8
+ #
9
+ # Additional DSL methods:
10
+ # widget :radio_group, columns: 2 # desktop/default hint
11
+ # widget_mobile :dropdown # mobile-specific hint
12
+ class StepBuilder < Inquirex::DSL::StepBuilder
13
+ def initialize(verb)
14
+ super
15
+ @widget_hints = {}
16
+ end
17
+
18
+ # Sets a rendering hint for the given target context.
19
+ # Call once per target. Recognized targets: :desktop, :mobile (and any
20
+ # future targets adapters may define).
21
+ #
22
+ # @param target [Symbol] rendering context (default: :desktop)
23
+ # @param type [Symbol] widget type (see WidgetRegistry::WIDGET_TYPES)
24
+ # @param opts [Hash] widget-specific options (e.g. columns: 2)
25
+ #
26
+ # @example
27
+ # widget target: :desktop, type: :radio_group, columns: 2
28
+ # widget target: :mobile, type: :dropdown
29
+ def widget(type:, target: :desktop, **opts)
30
+ @widget_hints[target.to_sym] = WidgetHint.new(type:, options: opts)
31
+ end
32
+
33
+ # Builds a UI::Node with widget hints (explicit or registry defaults).
34
+ #
35
+ # @param id [Symbol]
36
+ # @return [UI::Node]
37
+ def build(id)
38
+ effective_type = resolve_type
39
+ hints = @widget_hints.dup
40
+
41
+ # Fill in registry defaults for any targets not explicitly set.
42
+ %i[desktop mobile].each do |target|
43
+ hints[target] ||= WidgetRegistry.default_hint_for(effective_type, context: target)
44
+ end
45
+ hints.compact!
46
+
47
+ UI::Node.new(
48
+ id:,
49
+ verb: @verb,
50
+ type: effective_type,
51
+ question: @question,
52
+ text: @text,
53
+ options: @options,
54
+ transitions: @transitions,
55
+ skip_if: @skip_if,
56
+ default: @default,
57
+ widget_hints: hints.empty? ? nil : hints
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inquirex
4
+ module UI
5
+ # Enriched node that extends Inquirex::Node with widget rendering hints.
6
+ # Hints are stored as a Hash keyed by target context (e.g. :desktop, :mobile).
7
+ # Additional targets can be introduced by adapters without changing this class.
8
+ #
9
+ # The hints are included in JSON serialization and consumed by frontend adapters
10
+ # (JS widget, TTY renderer, etc.). This class does NOT render anything itself.
11
+ #
12
+ # @attr_reader widget_hints [Hash{Symbol => WidgetHint}, nil]
13
+ # Map of target → hint. nil for display nodes (say/header/btw/warning).
14
+ class Node < Inquirex::Node
15
+ attr_reader :widget_hints
16
+
17
+ # @param widget_hints [Hash{Symbol => WidgetHint}, nil]
18
+ # @param kwargs [Hash] all other params forwarded to Inquirex::Node
19
+ def initialize(widget_hints: nil, **)
20
+ # Set widget attr BEFORE calling super — super calls freeze.
21
+ @widget_hints = widget_hints&.freeze
22
+ super(**)
23
+ end
24
+
25
+ # Returns the explicit hint for the given target, or nil when not set.
26
+ #
27
+ # @param target [Symbol] e.g. :desktop, :mobile
28
+ # @return [WidgetHint, nil]
29
+ def widget_hint_for(target: :desktop)
30
+ widget_hints&.fetch(target.to_sym, nil)
31
+ end
32
+
33
+ # Returns the explicit hint for the given target, falling back to the
34
+ # registry default for this node's type when no explicit hint is set.
35
+ #
36
+ # @param target [Symbol] e.g. :desktop, :mobile
37
+ # @return [WidgetHint, nil]
38
+ def effective_widget_hint_for(target: :desktop)
39
+ widget_hint_for(target:) || WidgetRegistry.default_hint_for(type, context: target)
40
+ end
41
+
42
+ # Serializes to a plain Hash. When widget_hints are present, they are nested
43
+ # under the "widget" key as { "desktop" => {...}, "mobile" => {...} }.
44
+ #
45
+ # @return [Hash]
46
+ def to_h
47
+ hash = super
48
+ if widget_hints && !widget_hints.empty?
49
+ hash["widget"] = widget_hints.transform_keys(&:to_s)
50
+ .transform_values(&:to_h)
51
+ end
52
+ hash
53
+ end
54
+
55
+ # Deserializes from a plain Hash (string or symbol keys).
56
+ # Parses the nested "widget" target map in addition to all base Node fields.
57
+ #
58
+ # @param id [Symbol, String]
59
+ # @param hash [Hash]
60
+ # @return [UI::Node]
61
+ def self.from_h(id, hash)
62
+ widget_data = hash["widget"] || hash[:widget]
63
+
64
+ widget_hints =
65
+ if widget_data.is_a?(Hash) && widget_data.any? { |_, v| v.is_a?(Hash) }
66
+ # New format: { "desktop" => { "type" => "...", ... }, ... }
67
+ widget_data.each_with_object({}) do |(target, hint_hash), acc|
68
+ acc[target.to_sym] = WidgetHint.from_h(hint_hash)
69
+ end
70
+ end
71
+
72
+ verb = hash["verb"] || hash[:verb]
73
+ type = hash["type"] || hash[:type]
74
+ question = hash["question"] || hash[:question]
75
+ text = hash["text"] || hash[:text]
76
+ raw_options = hash["options"] || hash[:options]
77
+ transitions_data = hash["transitions"] || hash[:transitions] || []
78
+ skip_if_data = hash["skip_if"] || hash[:skip_if]
79
+ default = hash["default"] || hash[:default]
80
+
81
+ transitions = transitions_data.map { |t| Inquirex::Transition.from_h(t) }
82
+ skip_if = skip_if_data ? Inquirex::Rules::Base.from_h(skip_if_data) : nil
83
+ options = deserialize_options(raw_options)
84
+
85
+ new(
86
+ id:,
87
+ verb:,
88
+ type:,
89
+ question:,
90
+ text:,
91
+ options:,
92
+ transitions:,
93
+ skip_if:,
94
+ default:,
95
+ widget_hints:
96
+ )
97
+ end
98
+
99
+ private_class_method def self.deserialize_options(raw)
100
+ return nil if raw.nil?
101
+ return raw unless raw.is_a?(Array)
102
+ return raw if raw.empty?
103
+
104
+ raw.to_h { |o| [(o["value"] || o[:value]).to_s, (o["label"] || o[:label]).to_s] }
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inquirex
4
+ module UI
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inquirex
4
+ module UI
5
+ # Immutable rendering hint attached to a step node.
6
+ # Carries a widget type (e.g. :radio_group) and an optional options hash
7
+ # (e.g. { columns: 2 }). Framework-agnostic — consumed by the JS widget,
8
+ # TTY adapter, or any other frontend renderer.
9
+ #
10
+ # @example
11
+ # hint = WidgetHint.new(type: :radio_group, options: { columns: 2 })
12
+ # hint.to_h # => { "type" => "radio_group", "columns" => 2 }
13
+ WidgetHint = Data.define(:type, :options) do
14
+ def initialize(type:, options: {})
15
+ super(type: type.to_sym, options: options.transform_keys(&:to_sym).freeze)
16
+ end
17
+
18
+ # Serializes to a flat hash: type key + option keys merged in.
19
+ #
20
+ # @return [Hash<String, Object>]
21
+ def to_h
22
+ { "type" => type.to_s }.merge(options.transform_keys(&:to_s))
23
+ end
24
+
25
+ # Deserializes from a plain hash (string or symbol keys).
26
+ #
27
+ # @param hash [Hash]
28
+ # @return [WidgetHint]
29
+ def self.from_h(hash)
30
+ type = hash["type"] || hash[:type]
31
+ options = hash.reject { |k, _| k.to_s == "type" }
32
+ .transform_keys(&:to_sym)
33
+ new(type:, options:)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inquirex
4
+ module UI
5
+ # Canonical mapping from Inquirex data types to default widget types per rendering context.
6
+ #
7
+ # Desktop defaults lean toward richer controls (radio groups, checkbox groups).
8
+ # Mobile defaults lean toward compact controls (dropdowns, toggles).
9
+ #
10
+ # Adapters (TTY, JS, Rails) use this to pick an appropriate renderer when
11
+ # the DSL author has not specified an explicit `widget` hint.
12
+ module WidgetRegistry
13
+ # All recognized widget type symbols. Adapters may not support every widget;
14
+ # they should fall back gracefully to a simpler one.
15
+ WIDGET_TYPES = %i[
16
+ text_input
17
+ textarea
18
+ number_input
19
+ currency_input
20
+ toggle
21
+ yes_no_buttons
22
+ yes_no
23
+ radio_group
24
+ dropdown
25
+ checkbox_group
26
+ multi_select_dropdown
27
+ select
28
+ multi_select
29
+ enum_select
30
+ multiline
31
+ mask
32
+ slider
33
+ date_picker
34
+ email_input
35
+ phone_input
36
+ ].freeze
37
+
38
+ # Default widget per data type and rendering context.
39
+ # Each context key (:desktop, :mobile, :tty, …) maps to a widget type symbol.
40
+ # Adapters consult this table when no explicit `widget` hint was given.
41
+ #
42
+ # TTY defaults align with tty-prompt methods:
43
+ # text_input → prompt.ask
44
+ # multiline → prompt.multiline
45
+ # number_input → prompt.ask (with numeric conversion)
46
+ # yes_no → prompt.yes?
47
+ # select → prompt.select
48
+ # multi_select → prompt.multi_select
49
+ DEFAULTS = {
50
+ string: { desktop: :text_input, mobile: :text_input, tty: :text_input },
51
+ text: { desktop: :textarea, mobile: :textarea, tty: :multiline },
52
+ integer: { desktop: :number_input, mobile: :number_input, tty: :number_input },
53
+ decimal: { desktop: :number_input, mobile: :number_input, tty: :number_input },
54
+ currency: { desktop: :currency_input, mobile: :currency_input, tty: :number_input },
55
+ boolean: { desktop: :toggle, mobile: :yes_no_buttons, tty: :yes_no },
56
+ enum: { desktop: :radio_group, mobile: :dropdown, tty: :select },
57
+ multi_enum: { desktop: :checkbox_group, mobile: :checkbox_group, tty: :multi_select },
58
+ date: { desktop: :date_picker, mobile: :date_picker, tty: :text_input },
59
+ email: { desktop: :email_input, mobile: :email_input, tty: :text_input },
60
+ phone: { desktop: :phone_input, mobile: :phone_input, tty: :text_input }
61
+ }.freeze
62
+
63
+ # Returns the default widget type for a given data type and context.
64
+ #
65
+ # @param type [Symbol, String, nil] Inquirex data type (e.g. :enum, :integer)
66
+ # @param context [Symbol] :desktop or :mobile (default: :desktop)
67
+ # @return [Symbol, nil] widget type symbol, or nil if type is unknown
68
+ def self.default_for(type, context: :desktop)
69
+ DEFAULTS.dig(type&.to_sym, context)
70
+ end
71
+
72
+ # Returns a WidgetHint with the default widget for the given type + context,
73
+ # or nil when no default exists (e.g. for display verbs with no type).
74
+ #
75
+ # @param type [Symbol, String, nil]
76
+ # @param context [Symbol]
77
+ # @return [WidgetHint, nil]
78
+ def self.default_hint_for(type, context: :desktop)
79
+ widget_type = default_for(type, context:)
80
+ widget_type ? WidgetHint.new(type: widget_type) : nil
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "inquirex"
4
+ require_relative "ui/version"
5
+ require_relative "ui/widget_hint"
6
+ require_relative "ui/widget_registry"
7
+ require_relative "ui/node"
8
+ require_relative "ui/dsl/step_builder"
9
+ require_relative "ui/dsl/flow_builder"
10
+
11
+ module Inquirex
12
+ # UI enrichment layer for Inquirex flows.
13
+ #
14
+ # Extends the core DSL with `widget` and `widget_mobile` rendering hints.
15
+ # Every node built via Inquirex::UI.define carries a WidgetHint describing
16
+ # the preferred UI control for that step's data type.
17
+ #
18
+ # Widget hints are:
19
+ # - Framework-agnostic (no HTML/JS generated here)
20
+ # - Included in JSON serialization
21
+ # - Consumed by adapters: inquirex-tty, inquirex-js, inquirex-rails
22
+ #
23
+ # When no explicit hint is provided, WidgetRegistry supplies a sensible default
24
+ # for each data type × rendering context (desktop / mobile).
25
+ #
26
+ # @example
27
+ # definition = Inquirex::UI.define id: "tax-2025" do
28
+ # start :filing_status
29
+ #
30
+ # ask :filing_status do
31
+ # type :enum
32
+ # question "What is your filing status?"
33
+ # options single: "Single", married_jointly: "Married Filing Jointly"
34
+ # widget target: :desktop, type: :radio_group, columns: 2
35
+ # widget target: :mobile, type: :dropdown
36
+ # transition to: :next_step
37
+ # end
38
+ #
39
+ # ask :income do
40
+ # type :currency
41
+ # question "Estimated annual income?"
42
+ # # No widget call — WidgetRegistry provides :currency_input by default
43
+ # transition to: :done
44
+ # end
45
+ #
46
+ # say :done do
47
+ # text "All done."
48
+ # end
49
+ # end
50
+ #
51
+ # definition.step(:filing_status).widget_hint_for(target: :desktop)
52
+ # # => #<data Inquirex::UI::WidgetHint type=:radio_group, options={columns: 2}>
53
+ #
54
+ # definition.step(:income).effective_widget_hint_for(target: :desktop)
55
+ # # => #<data Inquirex::UI::WidgetHint type=:currency_input, options={}>
56
+ #
57
+ # definition.to_json
58
+ # # => '{"start":"filing_status","steps":{"filing_status":{...,"widget":{"type":"radio_group","columns":2},...}}}'
59
+ #
60
+ module UI
61
+ # Builds an immutable Definition where every node is a UI::Node with widget hints.
62
+ # Accepts the same arguments as Inquirex.define.
63
+ #
64
+ # @param id [String, nil] optional flow identifier
65
+ # @param version [String] semver (default: "1.0.0")
66
+ # @yield context of UI::DSL::FlowBuilder
67
+ # @return [Inquirex::Definition]
68
+ def self.define(id: nil, version: "1.0.0", &)
69
+ builder = UI::DSL::FlowBuilder.new(id:, version:)
70
+ builder.instance_eval(&)
71
+ builder.build
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,6 @@
1
+ module Inquirex
2
+ module Ui
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inquirex-ui
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Gredeskoul
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: inquirex
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.1.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.1.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec-its
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.21'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.21'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: simplecov
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.22'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.22'
96
+ - !ruby/object:Gem::Dependency
97
+ name: yard
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.9'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.9'
110
+ description: Enriches Inquirex flow definitions with framework-agnostic widget rendering
111
+ metadata. Adds `widget` and `widget_mobile` DSL verbs that attach WidgetHint objects
112
+ to nodes. Consumed by inquirex-tty, inquirex-js, and inquirex-rails adapters. Produces
113
+ enriched JSON — no HTML or JavaScript generated.
114
+ email:
115
+ - kigster@gmail.com
116
+ executables:
117
+ - inquirex-ui
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - ".relaxed_rubocop.yml"
122
+ - ".ruby-version"
123
+ - ".secrets.baseline"
124
+ - CHANGELOG.md
125
+ - LICENSE.txt
126
+ - README.md
127
+ - Rakefile
128
+ - exe/inquirex-ui
129
+ - justfile
130
+ - lefthook.yml
131
+ - lib/inquirex/ui.rb
132
+ - lib/inquirex/ui/dsl/flow_builder.rb
133
+ - lib/inquirex/ui/dsl/step_builder.rb
134
+ - lib/inquirex/ui/node.rb
135
+ - lib/inquirex/ui/version.rb
136
+ - lib/inquirex/ui/widget_hint.rb
137
+ - lib/inquirex/ui/widget_registry.rb
138
+ - sig/inquirex/ui.rbs
139
+ homepage: https://github.com/inquirex/inquirex-ui
140
+ licenses:
141
+ - MIT
142
+ metadata:
143
+ allowed_push_host: https://rubygems.org
144
+ homepage_uri: https://github.com/inquirex/inquirex-ui
145
+ source_code_uri: https://github.com/inquirex/inquirex-ui
146
+ changelog_uri: https://github.com/inquirex/inquirex-ui/blob/main/CHANGELOG.md
147
+ rubygems_mfa_required: 'true'
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: 4.0.0
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubygems_version: 4.0.10
163
+ specification_version: 4
164
+ summary: Widget rendering hints for Inquirex flow definitions
165
+ test_files: []