openui-rails 0.1.0 → 0.3.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/AUDIT.md +24 -4
  3. data/CHANGELOG.md +52 -1
  4. data/CONFIGURATION.md +9 -77
  5. data/CONTRIBUTING.md +29 -1
  6. data/DEVELOPMENT.md +89 -0
  7. data/README.md +50 -439
  8. data/RELEASING.md +30 -8
  9. data/THIRD_PARTY_NOTICES.md +10 -0
  10. data/UPGRADING.md +7 -24
  11. data/app/javascript/openui/rails/openui_controller.js +14 -1
  12. data/examples/support_desk/README.md +18 -0
  13. data/examples/support_desk/app/controllers/support_desk/base_controller.rb +37 -0
  14. data/examples/support_desk/app/controllers/support_desk/conversations_controller.rb +47 -0
  15. data/examples/support_desk/app/controllers/support_desk/sessions_controller.rb +19 -0
  16. data/examples/support_desk/app/javascript/support_desk.js +109 -0
  17. data/examples/support_desk/app/models/support_desk/conversation.rb +108 -0
  18. data/examples/support_desk/app/models/support_desk/demo.rb +15 -0
  19. data/examples/support_desk/app/models/support_desk/receipt.rb +6 -0
  20. data/examples/support_desk/app/models/support_desk/ticket.rb +6 -0
  21. data/examples/support_desk/app/models/support_desk/user.rb +6 -0
  22. data/examples/support_desk/app/views/layouts/support_desk/application.html.erb +18 -0
  23. data/examples/support_desk/app/views/support_desk/conversations/index.html.erb +6 -0
  24. data/examples/support_desk/app/views/support_desk/conversations/show.html.erb +16 -0
  25. data/examples/support_desk/boot.rb +21 -0
  26. data/examples/support_desk/config/importmap.rb +4 -0
  27. data/examples/support_desk/config/initializers/openui.rb +32 -0
  28. data/examples/support_desk/config/routes.rb +12 -0
  29. data/examples/support_desk/db/migrate/20261019000000_create_support_desk.rb +33 -0
  30. data/examples/support_desk/install.rb +15 -0
  31. data/lib/generators/openui/install/install_generator.rb +6 -5
  32. data/lib/generators/openui/install/templates/config/initializers/openui.rb.tt +2 -2
  33. data/lib/generators/openui/install/templates/config/openui/README.md +19 -4
  34. data/lib/openui/rails/default_library/library.prompt.txt +198 -0
  35. data/lib/openui/rails/default_library/library.spec.json +4390 -0
  36. data/lib/openui/rails/default_library/openui.LICENSE.txt +21 -0
  37. data/lib/openui/rails/default_library/provenance.json +15 -0
  38. data/lib/openui/rails/default_library.rb +85 -0
  39. data/lib/openui/rails/doctor.rb +143 -0
  40. data/lib/openui/rails/library_manifest.rb +2 -1
  41. data/lib/openui/rails/surface.rb +22 -4
  42. data/lib/openui/rails/version.rb +1 -1
  43. data/lib/openui/rails.rb +2 -0
  44. data/lib/tasks/openui.rake +19 -0
  45. metadata +28 -2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2011-2024 Thesys Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,15 @@
1
+ {
2
+ "runtime_package": "@openuidev/browser-bundle",
3
+ "runtime_version": "0.2.1",
4
+ "runtime_integrity": "sha384-GNNBzicRNkuscleA9e7/4cz6JwE9PE4BRKJ1udKWNzBh4DTD2/zpfOz4p91n8sW0",
5
+ "library": "openuiChatLibrary",
6
+ "exports": {
7
+ "library.prompt.txt": "prompt()",
8
+ "library.spec.json": "toSpec()"
9
+ },
10
+ "sha256": {
11
+ "library.prompt.txt": "7a334672957298e60543d80650615102b934bc88f69115e8602770a988514c3d",
12
+ "library.spec.json": "2c36842674a3d1f3a06deddd90304c67da65fc83686dd45e51d1b67d4a2178ad",
13
+ "openui.LICENSE.txt": "b6bdd2d3d161722fb3f3afc039114cfa9f3236f036e1c98e0853bc3a85f6cd9e"
14
+ }
15
+ }
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "tempfile"
5
+
6
+ module OpenUI
7
+ module Rails
8
+ # Task implementation for installing exports of the pinned default library.
9
+ class DefaultLibrary # :nodoc:
10
+ DIRECTORY = File.expand_path("default_library", __dir__)
11
+ NAMES = %w[library.spec.json library.prompt.txt openui.LICENSE.txt].freeze
12
+
13
+ def self.files(directory: DIRECTORY)
14
+ provenance = JSON.parse(File.read(File.join(directory, "provenance.json")))
15
+ integrity = RuntimePackage::FILES.fetch("package/dist/openui-bundle.min.js").last
16
+ unless provenance.fetch("runtime_version") == RuntimeAsset::PINNED_VERSION && provenance.fetch("runtime_integrity") == integrity
17
+ raise ConfigurationError, "default library exports do not match the pinned runtime"
18
+ end
19
+
20
+ NAMES.to_h do |name|
21
+ content = File.binread(File.join(directory, name))
22
+ unless Digest::SHA256.hexdigest(content) == provenance.fetch("sha256").fetch(name)
23
+ raise ConfigurationError, "default library export checksum mismatch; reinstall the gem"
24
+ end
25
+ [ name, content ]
26
+ end
27
+ rescue JSON::ParserError, KeyError, SystemCallError
28
+ raise ConfigurationError, "default library exports are unavailable or invalid; reinstall the gem"
29
+ end
30
+
31
+ def self.prepare(root:, configuration: OpenUI::Rails.configuration, force: false)
32
+ root = File.realpath(root)
33
+ exports = files
34
+ targets = [ configuration.library_spec_path, configuration.prompt_path ].map { |path| safe_path(path, root) }
35
+ targets << safe_path(File.join(File.dirname(targets.last), "openui.LICENSE.txt"), root)
36
+ if targets.uniq.size != targets.size || targets.any? { |path| targets.any? { |other| path.start_with?("#{other}/") } }
37
+ raise ConfigurationError, "prompt and spec need distinct file paths"
38
+ end
39
+ contents = targets.zip(NAMES.map { |name| exports.fetch(name) }).to_h
40
+ if !force && contents.any? { |path, content| File.exist?(path) && File.binread(path) != content }
41
+ raise ConfigurationError, "existing library files differ; preserve your custom library or explicitly use openui:library:prepare[true]"
42
+ end
43
+
44
+ contents.each do |path, content|
45
+ next if File.file?(path) && File.binread(path) == content
46
+
47
+ FileUtils.mkdir_p(File.dirname(path))
48
+ Tempfile.create(".openui-library-", File.dirname(path)) do |temporary|
49
+ temporary.chmod(0o644)
50
+ temporary.binmode
51
+ temporary.write(content)
52
+ temporary.flush
53
+ if force
54
+ File.rename(temporary.path, path)
55
+ else
56
+ File.link(temporary.path, path)
57
+ end
58
+ end
59
+ end
60
+ configuration.reset!
61
+ targets
62
+ rescue Errno::EEXIST
63
+ raise ConfigurationError, "library file appeared during preparation; rerun without overwriting it"
64
+ end
65
+
66
+ def self.safe_path(path, root)
67
+ raise ConfigurationError, "configure both library_spec_path and prompt_path before preparing the library" if path.nil?
68
+
69
+ target = File.expand_path(path, root)
70
+ unless !File.symlink?(target) && (!File.exist?(target) || File.file?(target))
71
+ raise ConfigurationError, "library outputs must be regular files inside the application root"
72
+ end
73
+
74
+ ancestor = File.dirname(target)
75
+ ancestor = File.dirname(ancestor) until File.exist?(ancestor) || File.symlink?(ancestor)
76
+ real = File.realpath(ancestor)
77
+ unless File.directory?(ancestor) && (real == root || real.start_with?("#{root}/"))
78
+ raise ConfigurationError, "library output directory resolves outside the application root"
79
+ end
80
+ File.join(real, target.delete_prefix(ancestor).delete_prefix("/"))
81
+ end
82
+ private_class_method :safe_path
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenUI
4
+ module Rails
5
+ # Read-only task diagnostics. Never executes application authorization,
6
+ # makes HTTP requests, or prints file contents and exception messages.
7
+ class Doctor # :nodoc:
8
+ Check = Struct.new(:name, :status, :message, keyword_init: true)
9
+ Report = Struct.new(:checks, keyword_init: true) do
10
+ def ok? = checks.none? { |check| check.status == :fail }
11
+
12
+ def to_s
13
+ lines = checks.map { |check| "[#{check.status.to_s.upcase}] #{check.name}: #{check.message}" }
14
+ lines << (ok? ? "Required local checks passed. Review all UNVERIFIED checks." : "Setup has required failures. Fix them and rerun openui:doctor.")
15
+ lines.join("\n")
16
+ end
17
+ end
18
+
19
+ def initialize(root:, configuration: OpenUI::Rails.configuration, routes:)
20
+ @root = File.expand_path(root)
21
+ @configuration = configuration
22
+ @routes = routes
23
+ end
24
+
25
+ def run
26
+ @checks = []
27
+ valid = check_configuration
28
+ check_route
29
+ check_assets
30
+ check_library_identity if valid
31
+ record("browser", :unverified, "Verify Stimulus registration, rendering, stylesheet loading, and event handling in a browser.")
32
+ record("authorization", :unverified, "Review actor resolution, tenant-scoped surfaces, and global or per-action authorizers. Callbacks were not executed; missing authorization denies actions.")
33
+ record("idempotency", :unverified, "Verify atomic shared storage for multiple workers, TTL limits, and business constraints.")
34
+ record("recovery", :unverified, "Verify authorized subscriptions, canonical snapshots, and host-owned fallback UI.")
35
+ Report.new(checks: @checks.freeze)
36
+ end
37
+
38
+ private
39
+
40
+ def record(name, status, message)
41
+ @checks << Check.new(name: name, status: status, message: message).freeze
42
+ end
43
+
44
+ def check_configuration
45
+ @configuration.validate!
46
+ prompt = @configuration.prompt_path
47
+ raise ConfigurationError if prompt.nil? || !File.file?(prompt) || File.zero?(prompt)
48
+
49
+ record("configuration", :pass, "Library files and numeric limits are valid.")
50
+ true
51
+ rescue OpenUI::Rails::Error, SystemCallError, ArgumentError, TypeError
52
+ record("configuration", :fail, "Review library paths, nonempty prompt, manifest JSON, asset settings, and numeric limits. For the default library, run openui:library:prepare.")
53
+ false
54
+ end
55
+
56
+ def check_route
57
+ endpoint = @configuration.actions_endpoint
58
+ raise ConfigurationError unless endpoint.is_a?(String) && endpoint.start_with?("/") && !endpoint.start_with?("//") && !endpoint.match?(/[?#]/)
59
+
60
+ raise ConfigurationError unless endpoint.end_with?("/actions")
61
+
62
+ mounts = @routes.routes.select { |route| route.app.respond_to?(:app) && route.app.app == Engine }
63
+ mount_path = endpoint.delete_suffix("/actions")
64
+ mount_path = "/" if mount_path.empty?
65
+ matching = mounts.any? do |route|
66
+ route.path.spec.to_s == mount_path && (route.verb.empty? || route.verb.include?("POST"))
67
+ end
68
+ if matching
69
+ record("actions route", :pass, "Static engine mount matches actions_endpoint. Request constraints were not executed.")
70
+ elsif mounts.any? { |route| route.path.spec.to_s.match?(/[:*]/) }
71
+ record("actions route", :unverified, "The engine has a dynamic mount. Verify actions_endpoint and request constraints in the host.")
72
+ else
73
+ raise ConfigurationError
74
+ end
75
+ rescue OpenUI::Rails::Error, ArgumentError
76
+ record("actions route", :fail, "Mount OpenUI::Rails::Engine and match actions_endpoint to that mount's /actions path.")
77
+ end
78
+
79
+ def default_assets?
80
+ @configuration.runtime_assets == :self_hosted && @configuration.runtime_url.nil?
81
+ end
82
+
83
+ def check_assets
84
+ unless default_assets? && @configuration.runtime_stylesheet_url.nil?
85
+ urls = [ RuntimeAsset.url(@configuration), RuntimeAsset.stylesheet_url(@configuration) ]
86
+ hashes = [ RuntimeAsset.integrity(@configuration), RuntimeAsset.stylesheet_integrity(@configuration) ]
87
+ raise ConfigurationError unless urls.all? { |url| url.is_a?(String) && !url.empty? } &&
88
+ hashes.all? { |value| value.is_a?(String) && value.match?(/\Asha(?:256|384|512)-[A-Za-z0-9+\/=]+\z/) }
89
+
90
+ record("runtime assets", :unverified, "Custom or external URLs and integrity settings exist. No network request or content verification was performed.")
91
+ return
92
+ end
93
+
94
+ raise ConfigurationError unless RuntimeAsset.version(@configuration) == RuntimeAsset::PINNED_VERSION
95
+
96
+ public_root = File.join(@root, "public")
97
+ url = @configuration.local_runtime_path
98
+ raise ConfigurationError unless url.is_a?(String) && url.start_with?("/") && !url.match?(/[?#]/)
99
+
100
+ directory = File.expand_path(url.delete_prefix("/"), public_root)
101
+ raise ConfigurationError unless directory.start_with?("#{public_root}/")
102
+
103
+ RuntimePackage::FILES.each_value do |suffix, expected|
104
+ path = File.join(directory, "#{RuntimeAsset::FILE_PREFIX}-#{RuntimeAsset::PINNED_VERSION}.#{suffix}")
105
+ inside = File.file?(path) && File.realpath(path).start_with?("#{File.realpath(@root)}/public/")
106
+ verified = inside && "sha384-#{Digest::SHA384.file(path).base64digest}" == expected
107
+ sri = case suffix
108
+ when "js" then RuntimeAsset.integrity(@configuration)
109
+ when "css" then RuntimeAsset.stylesheet_integrity(@configuration)
110
+ else expected
111
+ end
112
+ verified &&= valid_integrity?(path, sri)
113
+ record("runtime #{suffix}", verified ? :pass : :fail,
114
+ verified ? "Matches the pinned checksum and configured integrity." : "Missing asset, checksum mismatch, or incorrect integrity override. Run openui:runtime:fetch and review integrity settings.")
115
+ end
116
+ rescue OpenUI::Rails::Error, SystemCallError, ArgumentError, TypeError
117
+ record("runtime assets", :fail, "Review runtime version, paths, URLs, stylesheet, and integrity hashes. Keep default assets inside public/ and run openui:runtime:fetch.")
118
+ end
119
+
120
+ def valid_integrity?(path, value)
121
+ return false unless value.is_a?(String) && value.match?(/\Asha(?:256|384|512)-[A-Za-z0-9+\/=]+\z/)
122
+
123
+ algorithm = value.split("-", 2).first
124
+ "#{algorithm}-#{Digest.const_get(algorithm.upcase).file(path).base64digest}" == value
125
+ end
126
+
127
+ def check_library_identity
128
+ unless default_assets?
129
+ record("library identity", :unverified, "Verify the configured prompt/spec against the actual custom runtime library.")
130
+ return
131
+ end
132
+
133
+ files = DefaultLibrary.files
134
+ matching = @configuration.library_fingerprint == LibraryManifest.parse(files.fetch("library.spec.json")).fingerprint &&
135
+ File.binread(@configuration.prompt_path) == files.fetch("library.prompt.txt")
136
+ record("library identity", matching ? :pass : :fail,
137
+ matching ? "Prompt and spec match the pinned default chat library." : "The default runtime needs its matching prompt/spec. Preserve custom files or use openui:library:prepare; custom libraries need a matching custom runtime.")
138
+ rescue OpenUI::Rails::Error, SystemCallError, ArgumentError, TypeError
139
+ record("library identity", :fail, "Cannot verify the pinned default library exports. Review the gem installation and configured files.")
140
+ end
141
+ end
142
+ end
143
+ end
@@ -123,7 +123,7 @@ module OpenUI
123
123
  # Human-readable identifier, e.g. "support@1.2.0+1a2b3c4d5e6f".
124
124
  attr_reader :identifier
125
125
 
126
- # Return the names declared in the components collection.
126
+ # Return names from an array or name-keyed components collection.
127
127
  #
128
128
  # @return [Array<String>]
129
129
  def component_names = names_for(:components)
@@ -178,6 +178,7 @@ module OpenUI
178
178
  keys = COLLECTION_KEYS.fetch(collection)
179
179
  keys.each do |key|
180
180
  value = document[key]
181
+ return value.keys if value.is_a?(Hash)
181
182
  next unless value.is_a?(Array)
182
183
 
183
184
  return value.filter_map { |entry| name_of(entry) }
@@ -8,10 +8,10 @@ module OpenUI
8
8
  # opaque identifier plus the current revision and the source to render.
9
9
  # A surface can be built explicitly or coerced from a host record that
10
10
  # exposes `id` (and, optionally, `version`, `library_version`, `response`,
11
- # `context`, `message_id`).
11
+ # `context`, `message_id`, `next_sequence`, `completed`).
12
12
  class Surface
13
13
  # Surface values copied from an explicit object, hash, or host record.
14
- ATTRIBUTES = %i[id version library_version response context streaming message_id].freeze
14
+ ATTRIBUTES = %i[id version library_version response context streaming message_id next_sequence completed].freeze
15
15
 
16
16
  attr_reader(*ATTRIBUTES)
17
17
 
@@ -72,15 +72,28 @@ module OpenUI
72
72
  #
73
73
  # @param message_id [String, nil] stream identity; defaults to id
74
74
  #
75
+ # @param next_sequence [Integer] next expected envelope sequence after persisted output
76
+ #
77
+ # @param completed [Boolean] ignore later non-snapshot envelopes for a terminal response
78
+ #
75
79
  # @return [Surface]
76
80
  def initialize(id:, version: 0, library_version: nil, response: nil, context: nil,
77
- streaming: false, message_id: nil)
81
+ streaming: false, message_id: nil, next_sequence: 0, completed: false)
78
82
  raise ArgumentError, "surface id is required" if id.nil? || id.to_s.empty?
79
83
  unless version.nil? || (version.is_a?(Integer) && version >= 0)
80
84
  raise ArgumentError, "surface version must be a non-negative Integer"
81
85
  end
82
86
  raise ArgumentError, "surface response must be a String" unless response.nil? || response.is_a?(String)
83
87
 
88
+ unless next_sequence.is_a?(Integer) && next_sequence.between?(0, 9_007_199_254_740_991)
89
+ raise ArgumentError, "next_sequence must be a non-negative JavaScript-safe Integer"
90
+ end
91
+ unless [ true, false ].include?(completed) && !(completed && streaming)
92
+ raise ArgumentError, "completed must be boolean and cannot coexist with streaming"
93
+ end
94
+
95
+ @next_sequence = next_sequence
96
+ @completed = completed
84
97
  @id = id.to_s.dup.freeze
85
98
  @version = version || 0
86
99
  @library_version = library_version&.to_s&.dup&.freeze
@@ -102,7 +115,8 @@ module OpenUI
102
115
  def to_h
103
116
  {
104
117
  id: id, version: version, library_version: library_version,
105
- response: response, context: context, streaming: streaming, message_id: message_id
118
+ response: response, context: context, streaming: streaming, message_id: message_id,
119
+ next_sequence: next_sequence, completed: completed
106
120
  }
107
121
  end
108
122
 
@@ -124,10 +138,14 @@ module OpenUI
124
138
  #
125
139
  # @return [Hash] HTML data attributes with string values
126
140
  def to_mount_data(stream: false, actions_endpoint:, initial_state: nil)
141
+ raise ArgumentError, "completed surfaces cannot be mounted as streaming" if completed && stream
142
+
127
143
  {
128
144
  controller: "openui",
129
145
  openui_surface_id: id,
130
146
  openui_message_id: message_id,
147
+ openui_next_sequence: next_sequence.to_s,
148
+ openui_completed: completed.to_s,
131
149
  openui_version: version.to_s,
132
150
  openui_library_version: library_version,
133
151
  openui_streaming: (streaming? || stream) ? "true" : "false",
@@ -1,5 +1,5 @@
1
1
  module OpenUI
2
2
  module Rails
3
- VERSION = "0.1.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/lib/openui/rails.rb CHANGED
@@ -27,7 +27,9 @@ require "openui/rails/actions"
27
27
  require "openui/rails/adapters/ruby_llm"
28
28
  require "openui/rails/runtime_asset"
29
29
  require "openui/rails/runtime_package"
30
+ require "openui/rails/default_library"
30
31
  require "openui/rails/surface"
31
32
  require "openui/rails/view_helper"
32
33
  require "openui/rails/configuration"
34
+ require "openui/rails/doctor"
33
35
  require "openui/rails/engine"
@@ -1,6 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :openui do
4
+ desc "Check local OpenUI setup without writes or network requests"
5
+ task doctor: :environment do
6
+ report = OpenUI::Rails::Doctor.new(root: ::Rails.root, routes: ::Rails.application.routes).run
7
+ puts report
8
+ exit 1 unless report.ok?
9
+ end
10
+
11
+ namespace :library do
12
+ desc "Prepare the pinned default chat prompt and spec (pass true to overwrite)"
13
+ task :prepare, [ :force ] => :environment do |_task, args|
14
+ unless [ nil, "true", "false" ].include?(args[:force])
15
+ raise OpenUI::Rails::ConfigurationError, "force must be true or false"
16
+ end
17
+ paths = OpenUI::Rails::DefaultLibrary.prepare(root: ::Rails.root, force: args[:force] == "true")
18
+ paths.each { |path| puts "Prepared #{path}" }
19
+ puts "Default library ready; commit the prompt and spec together."
20
+ end
21
+ end
22
+
4
23
  namespace :runtime do
5
24
  desc "Vendor the checksum-locked OpenUI JS, CSS and license (requires npm)"
6
25
  task :fetch, [ :destination ] => :environment do |_task, args|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openui-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Wang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-18 00:00:00.000000000 Z
11
+ date: 2026-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -49,6 +49,7 @@ files:
49
49
  - CODE_OF_CONDUCT.md
50
50
  - CONFIGURATION.md
51
51
  - CONTRIBUTING.md
52
+ - DEVELOPMENT.md
52
53
  - LICENSE
53
54
  - README.md
54
55
  - RELEASING.md
@@ -60,6 +61,25 @@ files:
60
61
  - app/javascript/openui/rails/openui_controller.js
61
62
  - app/javascript/openui/rails/openui_envelope_controller.js
62
63
  - config/routes.rb
64
+ - examples/support_desk/README.md
65
+ - examples/support_desk/app/controllers/support_desk/base_controller.rb
66
+ - examples/support_desk/app/controllers/support_desk/conversations_controller.rb
67
+ - examples/support_desk/app/controllers/support_desk/sessions_controller.rb
68
+ - examples/support_desk/app/javascript/support_desk.js
69
+ - examples/support_desk/app/models/support_desk/conversation.rb
70
+ - examples/support_desk/app/models/support_desk/demo.rb
71
+ - examples/support_desk/app/models/support_desk/receipt.rb
72
+ - examples/support_desk/app/models/support_desk/ticket.rb
73
+ - examples/support_desk/app/models/support_desk/user.rb
74
+ - examples/support_desk/app/views/layouts/support_desk/application.html.erb
75
+ - examples/support_desk/app/views/support_desk/conversations/index.html.erb
76
+ - examples/support_desk/app/views/support_desk/conversations/show.html.erb
77
+ - examples/support_desk/boot.rb
78
+ - examples/support_desk/config/importmap.rb
79
+ - examples/support_desk/config/initializers/openui.rb
80
+ - examples/support_desk/config/routes.rb
81
+ - examples/support_desk/db/migrate/20261019000000_create_support_desk.rb
82
+ - examples/support_desk/install.rb
63
83
  - lib/generators/openui/install/install_generator.rb
64
84
  - lib/generators/openui/install/templates/config/initializers/openui.rb.tt
65
85
  - lib/generators/openui/install/templates/config/openui/README.md
@@ -73,6 +93,12 @@ files:
73
93
  - lib/openui/rails/actions/submission.rb
74
94
  - lib/openui/rails/adapters/ruby_llm.rb
75
95
  - lib/openui/rails/configuration.rb
96
+ - lib/openui/rails/default_library.rb
97
+ - lib/openui/rails/default_library/library.prompt.txt
98
+ - lib/openui/rails/default_library/library.spec.json
99
+ - lib/openui/rails/default_library/openui.LICENSE.txt
100
+ - lib/openui/rails/default_library/provenance.json
101
+ - lib/openui/rails/doctor.rb
76
102
  - lib/openui/rails/engine.rb
77
103
  - lib/openui/rails/errors.rb
78
104
  - lib/openui/rails/json_value.rb