openui-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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/AUDIT.md +102 -0
  3. data/CHANGELOG.md +71 -0
  4. data/CODE_OF_CONDUCT.md +37 -0
  5. data/CONFIGURATION.md +79 -0
  6. data/CONTRIBUTING.md +66 -0
  7. data/LICENSE +22 -0
  8. data/README.md +471 -0
  9. data/RELEASING.md +90 -0
  10. data/SECURITY.md +48 -0
  11. data/THIRD_PARTY_NOTICES.md +45 -0
  12. data/UPGRADING.md +26 -0
  13. data/app/controllers/openui/rails/actions_controller.rb +77 -0
  14. data/app/controllers/openui/rails/application_controller.rb +10 -0
  15. data/app/javascript/openui/rails/openui_controller.js +347 -0
  16. data/app/javascript/openui/rails/openui_envelope_controller.js +38 -0
  17. data/config/routes.rb +5 -0
  18. data/lib/generators/openui/install/install_generator.rb +62 -0
  19. data/lib/generators/openui/install/templates/config/initializers/openui.rb.tt +30 -0
  20. data/lib/generators/openui/install/templates/config/openui/README.md +24 -0
  21. data/lib/openui/rails/actions/dispatcher.rb +194 -0
  22. data/lib/openui/rails/actions/idempotency/memory_store.rb +64 -0
  23. data/lib/openui/rails/actions/invocation.rb +16 -0
  24. data/lib/openui/rails/actions/registry.rb +113 -0
  25. data/lib/openui/rails/actions/schema.rb +115 -0
  26. data/lib/openui/rails/actions/submission.rb +102 -0
  27. data/lib/openui/rails/actions.rb +18 -0
  28. data/lib/openui/rails/adapters/ruby_llm.rb +113 -0
  29. data/lib/openui/rails/configuration.rb +282 -0
  30. data/lib/openui/rails/engine.rb +46 -0
  31. data/lib/openui/rails/errors.rb +41 -0
  32. data/lib/openui/rails/json_value.rb +33 -0
  33. data/lib/openui/rails/library_manifest.rb +234 -0
  34. data/lib/openui/rails/message.rb +139 -0
  35. data/lib/openui/rails/prompt_builder.rb +115 -0
  36. data/lib/openui/rails/runtime_asset.rb +65 -0
  37. data/lib/openui/rails/runtime_package.rb +74 -0
  38. data/lib/openui/rails/stream/accumulator.rb +86 -0
  39. data/lib/openui/rails/stream/bridge.rb +193 -0
  40. data/lib/openui/rails/stream/broadcaster.rb +106 -0
  41. data/lib/openui/rails/stream/envelope.rb +159 -0
  42. data/lib/openui/rails/stream/transports/action_cable.rb +50 -0
  43. data/lib/openui/rails/stream/transports/memory.rb +33 -0
  44. data/lib/openui/rails/stream/transports/turbo_streams.rb +61 -0
  45. data/lib/openui/rails/stream/transports.rb +41 -0
  46. data/lib/openui/rails/surface.rb +150 -0
  47. data/lib/openui/rails/version.rb +5 -0
  48. data/lib/openui/rails/view_helper.rb +56 -0
  49. data/lib/openui/rails.rb +33 -0
  50. data/lib/tasks/openui.rake +16 -0
  51. metadata +125 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f5365a84ab993b7d1d7efd392753a9699f28cbe17acd8482d517e0e529150a6c
4
+ data.tar.gz: d071e4fbd4f29fefd7a074f411b30cdd0b453bc5532410bbcb7af3333184f340
5
+ SHA512:
6
+ metadata.gz: cd3efcb1e979ac3c63892e83a68fcd1b62db7d44297763e2fdc451251220c823efa2f27afc25d340902e5f56e0c8b7c53afd952df115eda9e78c95a143b8bb0f
7
+ data.tar.gz: e8a972cb1ef2663d0247263984d06e82e24cfcf1e4e972788c3dc917a111c5ea88e8cd782191f7baf19d2804660ed93bd6c8241a58074010f112b22686ce6a55
data/AUDIT.md ADDED
@@ -0,0 +1,102 @@
1
+ # Pre-publication audit: 2026-10-18
2
+
3
+ ## Outcome
4
+
5
+ The audit found real defects despite the original green test suite. The fixes
6
+ below have regression coverage. The owner authorized publication of `0.1.0`
7
+ on 2026-10-18. Publication still requires the selected tag's complete CI to
8
+ pass and the protected publishing workflow to succeed. This audit establishes
9
+ the specific behaviors below, not the absence of all bugs.
10
+
11
+ ## Confirmed defects corrected
12
+
13
+ | Area | Defect | Correction and evidence |
14
+ | --- | --- | --- |
15
+ | Identity isolation | A client idempotency key could replay another actor's result or a different action's result. | Actor/tenant-scoped keys; request digest binds action, surface, revision, args, and confirmation. Cross-actor/action/surface/tenant regression tests. |
16
+ | Canonical input | Symbol-keyed and nested arguments had incorrect digests; mutable input could change after validation. | Deep canonical JSON copies and immutable data. Mutation and nested-digest tests. |
17
+ | Claim state | A completed `nil` looked pending; an expired writer could overwrite a newer claim. | Explicit pending/replay states and per-claim tokens. Nil/false, expiry, and concurrent-claim tests. |
18
+ | Host authentication | Engine controllers bypassed the host ApplicationController; documented default actor resolution was absent. | Host controller inheritance, explicit CSRF protection, current-user fallback, and tenant-swap tests. |
19
+ | Action responses | Authentication redirects, malformed JSON, or unrelated result objects could appear successful. | Reject redirects and non-JSON/malformed/mismatched responses; browser regression tests. |
20
+ | Stream ordering | Completion skipped missing deltas; stale snapshots rewound state; a gap did not pause later deltas. | Sequence gates for terminal frames and snapshots; Ruby/browser regressions. |
21
+ | Terminal writers | Duplicate finishes and writes after errors emitted more frames. | One terminal state. Consumer/transport failures propagate instead of being relabelled provider failures. |
22
+ | Browser lifecycle | Pre-load frames could render without a runtime; reconnect/cache restoration lost source or sequence state. | Deferred rendering, connection tokens, state preservation, real Turbo visit/back tests. |
23
+ | OpenUI actions | The stub used `{action,args}`, but the real runtime emits `{type,params}`. | Official event normalization, verified with a real component and keyboard activation. |
24
+ | Browser tools | The proxy looked like an MCP client and returned the endpoint wrapper instead of the handler value. | Reserved-property handling, stable provider identity, raw tool values. Real Query and Mutation tests. |
25
+ | Renderer errors | The official renderer calls `onError([])` after successful parsing. | Empty error arrays are ignored; actual error arrays become structured renderer errors. |
26
+ | Library identity | A fingerprint existed, but the runtime library was not checked. | Server rejects persisted mismatches; browser hashes `toSpec()` before mounting. Real-bundle and negative tests. |
27
+ | Runtime supply | Fetch chose the largest JS file, ran without checksum verification, and CSS was not integrated. | Exact file allowlist, committed archive/file hashes, npm scripts disabled, no archive path extraction, stylesheet helper. Real fetch and SRI-backed browser tests. |
28
+ | RubyLLM | `Chat#complete(question)` does not exist in RubyLLM 2.1.1. | Add the user message, then call block-only complete; verify both strategies through real provider SSE parsing with mocked HTTP. |
29
+ | Message framing | CRLF, malformed markers, post-end text, and multi-line raw context differed from lang-core. | Align framing with pinned lang-core 1.0.1 and extend regression cases. No Ruby DSL parser added. |
30
+ | Packaging | Standalone require needed Rails preloaded; previous smoke checks only installed the gem. | Explicit framework loading; a fresh host uses the extracted built artifact, generator, eager load, and an HTTP render. |
31
+ | Configuration readiness | A configured filename could pass validation without an actual valid manifest or prompt file. | Explicit readiness validation reads the files and clears stale derived state. Missing-file regression tests. |
32
+ | Release workflow | A tag could publish without running tests; the supposed draft was public; environment protection was only a comment. | Reusable full CI gate, explicit manual publish, artifact handoff, real draft flag, and documented reviewer setup. |
33
+
34
+ ## Verification evidence
35
+
36
+ Local environment: Ruby 3.3.5, Rails 8.1.4, Chrome through Selenium.
37
+
38
+ - Unit/integration: `209 runs, 542 assertions`, no failures or errors.
39
+ - Deterministic browser suite: `32 runs, 104 assertions`, no failures or errors.
40
+ - Real `@openuidev/browser-bundle` 0.2.1: `5 runs, 30 assertions`, no failures or errors.
41
+ Includes matching runtime/spec identity, rendering, keyboard action, Query,
42
+ click-triggered Mutation, and unknown-component rejection.
43
+ - Real Turbo Streams broadcast over Action Cable, plus Turbo visit/back restoration.
44
+ - RubyLLM 2.1.1 with real OpenAI chat-completions SSE parsing and mocked HTTP:
45
+ `1 run, 7 assertions`, no failures or errors. The deterministic test made no
46
+ paid provider calls.
47
+ - Live-provider smoke (2026-10-18): one synthetic prompt streamed through the
48
+ gem adapter with RubyLLM 2.1.1, OpenRouter, `openai/gpt-4.1-mini`, and the
49
+ `:complete` strategy. A 16-token output cap was set. The first sandboxed
50
+ attempt returned `stream_failed` with no delta; the network-enabled retry
51
+ emitted one 2-byte delta and a completed frame. The account's usage ledger
52
+ and exact charge were not checked. Evidence is limited to this OpenRouter
53
+ path/model; direct OpenAI and other provider/model paths remain unverified.
54
+ - Checksum-locked npm runtime fetch, including CSS and upstream license: passed.
55
+ - Built artifact in a fresh generated Rails host: generator, eager load, and HTTP render passed.
56
+ - RuboCop: clean. JS syntax checks: passed.
57
+ - GitHub workflows: actionlint 1.7.12 passed. External actions are pinned to
58
+ verified full commit SHAs, not mutable major-version tags.
59
+ - `bundler-audit check --update`: no known Ruby vulnerabilities at advisory DB
60
+ commit `fa2295251efb3b0d1a300e5ce8ad700ff1f1b485` (updated 2026-10-17).
61
+ - CI additionally checks Ruby 3.2/3.3/3.4 with Rails 8.0/8.1. Consult the
62
+ linked repository workflow for the result of the latest audit commit.
63
+
64
+ Tests establish these specific behaviors, not universal provider compatibility,
65
+ complete accessibility, legal clearance, or an absence of future vulnerabilities.
66
+
67
+ ## Release setup on 2026-10-18
68
+
69
+ - The owner explicitly authorized publication, public repository visibility,
70
+ and RubyGems OIDC trust for `yorzi/openui-rails`, `release.yml`, environment
71
+ `release`.
72
+ - The repository is public. Security reports go to the address in `SECURITY.md`;
73
+ public support uses GitHub Issues.
74
+ - RubyGems has a pending Trusted Publisher for the exact workflow and
75
+ environment. The first successful publication activates the gem's publisher.
76
+ - The GitHub `release` environment requires approval by `yorzi`, allows the
77
+ `main` branch only, and disallows administrator bypass. Self-review remains
78
+ available for the single maintainer.
79
+ - Dated `0.1.0` notes are in `CHANGELOG.md`. Gem and documentation versions
80
+ are checked together before packaging.
81
+ - The built gem excludes the upstream browser bundle. A host that redistributes
82
+ the fetched bundle must review its transitive notices, including ISC-licensed
83
+ `lucide-react`. The gem's fetch task retains upstream legal comments and the
84
+ upstream license file. This is not a legal-clearance claim.
85
+
86
+ ## Boundaries hosts must enforce
87
+
88
+ - Shared atomic idempotency storage for multiple processes. TTL expiry, crashes,
89
+ and irreversible effects still need database constraints or transactions.
90
+ - Trusted confirmation UI and, where needed, signed server approvals. A client
91
+ boolean is not proof of human intent.
92
+ - Authorized stream subscriptions, scoped surface lookup, and authoritative
93
+ snapshot persistence/recovery. The gem supplies no snapshot server.
94
+ - Request/body/rate limits and provider token/time budgets.
95
+ - A compatible prompt/spec/runtime library. The default bundle contains only
96
+ the chat library; custom libraries require a matching runtime.
97
+ - CSP validation for the upstream renderer's inline styles. No strict-style-CSP
98
+ support is claimed. No human screen-reader certification is claimed.
99
+ - API-only Rails authentication/CSRF integration is not covered by this release.
100
+
101
+ The owner's publication authorization is not an independent legal, security,
102
+ or accessibility certification. Follow [RELEASING.md](RELEASING.md) for each release.
data/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to `openui-rails` are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-10-18
10
+
11
+ ### Fixed
12
+
13
+ - Scope idempotency to server actor/tenant identity and bind replay digests to
14
+ the full request. Distinguish pending claims from completed nil results and
15
+ fence expired writers with claim tokens.
16
+ - Preserve host authentication filters and default current-user resolution.
17
+ - Reject authentication redirects and malformed or mismatched action responses
18
+ instead of reporting a false success.
19
+ - Canonicalize and copy JSON input before validation/fingerprinting; prevent
20
+ mutable submissions, envelopes, surfaces, and manifests.
21
+ - Make explicit configuration readiness validation read the actual manifest
22
+ and prompt files instead of accepting filenames alone.
23
+ - Enforce stream terminal states, completion ordering, snapshot watermarks,
24
+ browser byte limits, and Turbo cache restoration.
25
+ - Use the real OpenUI `{type, params}` action contract, avoid false MCP-client
26
+ detection, return raw tool values, and ignore empty renderer-error arrays.
27
+ - Verify configured library fingerprints against the actual runtime `toSpec()`.
28
+ - Fetch exact checksum-locked JS/CSS/license files without npm lifecycle scripts.
29
+ Add `openui_stylesheet_tag` and default integrity pins.
30
+ - Correct RubyLLM's block-only `complete` call and verify 2.1.1 with real SSE parsing.
31
+ - Align message framing edge cases with lang-core 1.0.1.
32
+ - Require full CI and explicit manual approval before publication; create a
33
+ genuine draft GitHub release. See `UPGRADING.md` for pre-release changes.
34
+
35
+ ### Added
36
+
37
+ - Mountable Rails engine under the `OpenUI::Rails` namespace with the
38
+ `openui/rails` require path.
39
+ - Configuration object with validated transport, runtime-asset strategy,
40
+ library/prompt paths, stream limits and action hooks.
41
+ - Versioned component-library manifest with a canonical SHA-256 fingerprint.
42
+ - Prompt builder tying the model system instruction to the exact library
43
+ revision and stamping a combined prompt fingerprint.
44
+ - Ruby implementation of the upstream OpenUI message protocol
45
+ (`build`/`parse`/`strip`).
46
+ - Ordered stream envelopes (`openui.delta/snapshot/completed/error`), a
47
+ sequence accumulator and a coalescing stream bridge/writer.
48
+ - Server-authoritative action loop: allowlist registry, arg schema,
49
+ confirmation, stale-surface check, required authorizer and idempotency.
50
+ - `openui_surface` view helper and the `OpenUI::Rails::ActionsController`
51
+ endpoint.
52
+ - Optional RubyLLM adapter (no hard dependency).
53
+ - Stimulus mount controller and pinned runtime-asset resolution.
54
+ - `openui:install` generator and the `openui:runtime:fetch` task.
55
+ - Transport broadcasters (`ActionCable`, `TurboStreams`, `Memory`) behind
56
+ `Stream::Broadcaster`, and the `openui-envelope` Stimulus controller that
57
+ turns a Turbo Stream append into an `openui:envelope` event without inline
58
+ script.
59
+ - Browser (system) test suite: lifecycle, stream ordering/duplicate/gap,
60
+ snapshot recovery, completion/error, action/tool round-trips, destructive
61
+ confirmation, and mount accessibility (`aria-busy` lifecycle, named region).
62
+ - `aria-busy` on the mount element tracks the streaming state, and an optional
63
+ `label:` exposes the mount as a named `region` landmark.
64
+ - Tag-driven release workflow and `RELEASING.md`.
65
+ - Versioned guides and an RDoc API reference at
66
+ `https://openui-rails.wangyaodi.com`, deployed with GitHub Pages.
67
+ - Gem-scoped RubyGems Trusted Publishing through GitHub OIDC, with a protected
68
+ release environment and verified artifact handoff.
69
+
70
+ [Unreleased]: https://github.com/yorzi/openui-rails/compare/v0.1.0...main
71
+ [0.1.0]: https://github.com/yorzi/openui-rails/releases/tag/v0.1.0
@@ -0,0 +1,37 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic
9
+ status, nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment include
18
+ showing empathy and kindness, respecting differing opinions, giving and
19
+ gracefully accepting constructive feedback, and focusing on what is best for
20
+ the community.
21
+
22
+ Examples of unacceptable behavior include sexualized language or imagery,
23
+ trolling or insulting comments, public or private harassment, and publishing
24
+ others' private information without permission.
25
+
26
+ ## Enforcement
27
+
28
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
29
+ reported to the maintainers at **wangyaodi@gmail.com**. All complaints will be
30
+ reviewed and investigated promptly and fairly.
31
+
32
+ ## Attribution
33
+
34
+ This Code of Conduct is adapted from the
35
+ [Contributor Covenant](https://www.contributor-covenant.org), version 2.1,
36
+ available at
37
+ https://www.contributor-covenant.org/version/2/1/code_of_conduct/.
data/CONFIGURATION.md ADDED
@@ -0,0 +1,79 @@
1
+ # Configuration
2
+
3
+ Configure the bridge in `config/initializers/openui.rb`. Rails owns identity,
4
+ authorization, stream subscriptions, persistence, and snapshot recovery.
5
+
6
+ | Setting | Default | Contract |
7
+ | --- | --- | --- |
8
+ | `transport` | `:turbo` | Factory preference: `:turbo`, `:action_cable`, or `:none`. Pass it explicitly to `Transports.build`; it does not create a subscription. |
9
+ | `library_spec_path` | `nil` | JSON manifest from the exact runtime library. Enables server and browser fingerprint checks. |
10
+ | `prompt_path` | `nil` | Prompt generated from that same library. |
11
+ | `runtime_assets` | `:self_hosted` | Use `:external` only with explicit exact-version URLs. |
12
+ | `local_runtime_path` | `"/openui"` | Public URL directory for self-hosted assets. |
13
+ | `runtime_version` | pinned `0.2.1` | The fetch task accepts only the checksum-locked version. Other versions are unverified. |
14
+ | `runtime_url` | derived self-hosted JS URL | Explicit override for a host-built or external runtime. |
15
+ | `runtime_integrity` | pinned hash for the default runtime | Override for another JS asset. No default hash is assumed for custom URLs. |
16
+ | `runtime_stylesheet_url` | derived self-hosted CSS URL | Required when using `:external` and `openui_stylesheet_tag`. |
17
+ | `runtime_stylesheet_integrity` | pinned hash for the default runtime | Override for another CSS asset. |
18
+ | `actions_endpoint` | `"/openui-rails/actions"` | Must match the host engine mount. |
19
+ | `stream_byte_limit` | `1_048_576` | Positive integer; enforced by server writer and browser. |
20
+ | `pending_byte_limit` | `32_768` | Positive byte-based flush threshold, at most the stream limit. Lower it for smaller, more frequent updates. There is no timer-based flush. |
21
+ | `idempotency_ttl` | `900` seconds | Finite positive number. Replays are protected only within the store's lifetime and TTL. |
22
+ | `idempotency_store` | process-local `MemoryStore` | Use a shared atomic store for multiple processes. See the contract below. |
23
+ | `idempotency_scope` | derived actor/tenant identity | Optional callable receiving `Invocation`; return a stable, JSON-serializable namespace for custom identity types. |
24
+ | `actor_resolver` | host `current_user`, if available | Callable receiving the engine controller. Host authentication filters are inherited. |
25
+ | `surface_resolver` | `nil` | Callable `(surface_id, actor:, context:)`; return the scoped surface, or `nil` to deny. Without it, the authorizer owns resource/version checks. |
26
+ | `authorize_action` | `nil` | Global authorizer receiving `Invocation`. Missing authorization denies the action. Per-action `authorize:` takes precedence. |
27
+ | `on_error` | `nil` | Server stream-error hook `(error, message_id:)`. Do not log credentials or unredacted provider requests. |
28
+ | `action_registry` | lazy `Actions::Registry` | Allowlisted action definitions. |
29
+
30
+ `library_name` and `surface_id_param` are legacy metadata settings. They do not
31
+ select a runtime library or change the endpoint's `surface_id` wire key.
32
+
33
+ After you create the library files, call
34
+ `OpenUI::Rails.configuration.validate!` to check required configuration and
35
+ read the actual manifest and configured prompt files.
36
+ Boot does not call it automatically, so setup tasks can run before files exist.
37
+
38
+ ## Shared idempotency store
39
+
40
+ The dispatcher supplies a SHA-256 key scoped to server actor and tenant identity.
41
+ It supplies a digest of the entire validated request separately.
42
+
43
+ ```ruby
44
+ claim(key, ttl:, digest:)
45
+ # => [:claimed, nil, digest, claim_token] # this request owns execution
46
+ # => [:pending, nil, stored_digest, token] # execution is in progress
47
+ # => [:replay, result, stored_digest, token] # completed, including nil/false
48
+
49
+ record(key, result, claim_token: token)
50
+ ```
51
+
52
+ Implement `claim` atomically. Make `claim_token` unique for every new claim.
53
+ Ignore a `record` call whose token no longer owns the key. Preserve completed
54
+ `nil` and `false` results instead of treating them as pending.
55
+
56
+ The default identity is `[class, id]` for records and `[class, value]` for scalar
57
+ identities. Unsupported identity objects need `idempotency_scope`. Scope must
58
+ come from trusted server state, not a client-supplied user or tenant ID.
59
+
60
+ A failed handler leaves a pending claim until expiry because its side effects
61
+ may already have happened. A handler that outlives the TTL can overlap a later
62
+ claim. Choose a suitable TTL and enforce business-level uniqueness or
63
+ transactions for irreversible operations. Process-local memory does not survive
64
+ restarts and cannot coordinate separate workers.
65
+
66
+ ## Browser errors and recovery
67
+
68
+ Listen for `openui:error` to show a host-owned error or fallback. Listen for
69
+ `openui:snapshot-required` to fetch an authorized, authoritative snapshot.
70
+ The bridge emits these events; it does not implement the host's fallback UI,
71
+ snapshot endpoint, reconnection policy, or persistence.
72
+
73
+ The action bridge rejects authentication redirects and non-JSON, malformed, or
74
+ mismatched results. An expired-session redirect emits `unauthorized`; a broken
75
+ response contract emits `invalid_response`. Host callbacks may still redirect
76
+ HTTP requests, but a redirect is never a successful JSON action result.
77
+
78
+ Configure a matching manifest in production. Without one, the bridge cannot
79
+ verify that the model prompt, stored output, and runtime library agree.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,66 @@
1
+ # Contributing to openui-rails
2
+
3
+ Thanks for helping. This is an unofficial Rails integration for Thesys OpenUI
4
+ Lang; it does not reimplement the OpenUI parser or renderer.
5
+
6
+ ## Getting set up
7
+
8
+ ```bash
9
+ git clone https://github.com/yorzi/openui-rails
10
+ cd openui-rails
11
+ bundle install
12
+ bundle exec bin/rails db:test:prepare
13
+ ```
14
+
15
+ ## Before you open a pull request
16
+
17
+ ```bash
18
+ bundle exec bin/rails test # unit + integration tests
19
+ bundle exec bin/rails test:system # browser (system) tests, needs Chrome
20
+ bundle exec rubocop # style (rubocop-rails-omakase)
21
+ bundle exec gem build openui-rails.gemspec
22
+ bundle exec ruby bin/smoke-package openui-rails-0.1.0.gem
23
+ bundle exec bundler-audit check --update
24
+ bundle exec ruby -Itest test/compatibility/ruby_llm_smoke.rb
25
+ ```
26
+
27
+ The browser tests run against `test/dummy` with headless Chrome and a
28
+ deterministic stub of the OpenUI runtime
29
+ (`test/dummy/public/openui/openui-browser-bundle-0.2.1.js`), so they need no
30
+ network or real bundle.
31
+
32
+ CI also runs the real checksum-locked browser bundle. To run that probe locally:
33
+
34
+ ```bash
35
+ bundle exec rake "app:openui:runtime:fetch[$PWD/tmp/real-runtime]"
36
+ OPENUI_REAL_RUNTIME_DIR="$PWD/tmp/real-runtime" \
37
+ bundle exec ruby -Itest test/compatibility/runtime_smoke.rb
38
+ ```
39
+
40
+ The first command needs npm and network access. The browser probe uses local
41
+ assets afterward. The RubyLLM probe uses the real 2.1.1 client and SSE parser,
42
+ with WebMock and a dummy API key. It never calls a paid provider.
43
+
44
+ Keep the test suite green and add negative/security tests alongside any new
45
+ behavior. Changes to the action path or the stream protocol are expected to
46
+ include unauthorized, wrong-tenant, replay, stale-version and malformed-payload
47
+ cases.
48
+
49
+ ## Guidelines
50
+
51
+ - Prefer the official OpenUI runtime over any hand-rolled parser. Model output
52
+ is untrusted UI DSL, never executable code.
53
+ - Keep `ruby_llm` optional and isolated under `OpenUI::Rails::Adapters`; do not
54
+ reference a `RubyLLM` constant outside a guarded adapter.
55
+ - Inside `module OpenUI`, write `::Rails` for the framework.
56
+ - Do not introduce floating dependency ranges for the OpenUI JS runtime or the
57
+ prompt CLI; pin exact versions.
58
+ - Write commit messages that explain the *why*.
59
+
60
+ ## Reporting security issues
61
+
62
+ Please do not open a public issue for a security problem. See `SECURITY.md`.
63
+
64
+ ## Code of Conduct
65
+
66
+ By participating you agree to `CODE_OF_CONDUCT.md`.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Andy Wang and openui-rails contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.