A Rails engine that integrates a host app with a Meta Control server: contextual user-stories drawer + anonymous feedback channel.
Status:
0.1.0— Spike A + Spike B. The client, the engine, the drawer flow (vanilla-JS fetch + server-rendered fragment from MC), and async feedback delivery are all wired up end-to-end. SeeCHANGELOG.md.
Replace the legacy <script src="…/app_script"> injection (jQuery +
iframe + admin-cookie auth) with a proper gem: per-app Bearer token,
server-to-server transport, server-rendered drawer, no end-user
identity bridging. Background and decisions are in the host project's
docs/plans/2026_04_app_integration/.
# Gemfile
gem "meta_control_client"bin/rails g meta_control_client:installThen in config/routes.rb:
mount MetaControlClient::Engine, at: "/meta_control"And in your layout:
<%= meta_control_drawer %>Set MC_API_KEY (format mc_<env>_<hex>) and MC_URL in your env.
require "meta_control_client"
MetaControlClient.configure do |c|
c.api_key = ENV.fetch("MC_API_KEY") # e.g. "mc_dev_3f8c…"
c.app_slug = "my-app"
c.mc_url = "http://localhost:3000"
end
MetaControlClient::Client.new.post_feedback(
{
comment: "Refund button is hidden on mobile.",
context: {
schema: "mc.context/v1",
app_slug: "my-app",
environment: "development",
request: {
id: SecureRandom.uuid, method: "GET", path: "/orders/42",
namespace: "", controller: "orders", action: "show",
referrer: nil
},
client: { gem_version: MetaControlClient::VERSION,
ruby: RUBY_VERSION, rails: nil }
}
}
)This exercises:
- Bearer-token authentication.
Idempotency-Keyheader (auto-generated UUID per call; passidempotency_key:to override for retries).- HTTP error mapping →
MetaControlClient::Errors::*.
The host-side Meta Control endpoint (POST /api/v1/feedbacks) is
itself part of a separate work-stream; for the spike, point mc_url
at a Webmock stub or a hand-rolled local server.
The gem mounts at /meta_control and ships a vanilla-JS drawer that
fetches the MC-rendered fragment on click. End-to-end exercised by
the request specs under spec/requests/ against the dummy Rails 8
host app under spec/dummy/. Verified flows:
GET /meta_control/drawerbuilds the §8 context (app + route, no user data), proxies to MC, and returns the HTML fragment to the browser (200).- When MC is unreachable, the engine returns a minimal fallback fragment with just a feedback form — the drawer never appears broken to the user.
POST /meta_control/feedbacks(sync mode) proxies the submission to MC and forwards MC's response.POST /meta_control/feedbacks(async mode, the production default) enqueuesDeliverFeedbackJoband returns 202 immediately.- The
meta_control_drawerview helper, when included in the host's layout, injects an offcanvas shell with a stabledata-*API consumed byapp/assets/javascripts/meta_control_client.js.
Host Rails app Meta Control server
───────────── ───────────────────
POST /api/v1/feedbacks
MetaControlClient::Client ─── HTTPS ─────► GET /api/v1/drawer
(Bearer mc_<env>_<hex>, GET /api/v1/health
Idempotency-Key: <uuid>)
Full design: 01_design.md in the Meta Control repo.
bundle install
bundle exec rspecSpike A spec: spec/lib/meta_control_client/client_spec.rb.
WebMock-based stubs live in spec/support/stub_meta_control.rb.
MIT — see LICENSE.