context_drop 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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.md +337 -0
  4. data/Rakefile +20 -0
  5. data/SPEC.md +532 -0
  6. data/app/assets/javascripts/context_drop/context_drop.js +3508 -0
  7. data/app/assets/stylesheets/context_drop/application.css +2 -0
  8. data/app/controllers/context_drop/application_controller.rb +114 -0
  9. data/app/controllers/context_drop/attachments_controller.rb +31 -0
  10. data/app/controllers/context_drop/configs_controller.rb +20 -0
  11. data/app/controllers/context_drop/dashboard/feedbacks_controller.rb +107 -0
  12. data/app/controllers/context_drop/feedbacks_controller.rb +208 -0
  13. data/app/controllers/context_drop/health_controller.rb +9 -0
  14. data/app/controllers/context_drop/mcp_controller.rb +67 -0
  15. data/app/helpers/context_drop/widget_helper.rb +62 -0
  16. data/app/javascript/context_drop/__tests__/widget.test.ts +319 -0
  17. data/app/javascript/context_drop/api-client.ts +87 -0
  18. data/app/javascript/context_drop/element-picker.ts +1004 -0
  19. data/app/javascript/context_drop/icons.ts +35 -0
  20. data/app/javascript/context_drop/index.ts +11 -0
  21. data/app/javascript/context_drop/sanitizer.ts +454 -0
  22. data/app/javascript/context_drop/screenshot.ts +214 -0
  23. data/app/javascript/context_drop/styles.ts +690 -0
  24. data/app/javascript/context_drop/types.ts +110 -0
  25. data/app/javascript/context_drop/widget.ts +1120 -0
  26. data/app/models/context_drop/application_record.rb +7 -0
  27. data/app/models/context_drop/attachment.rb +30 -0
  28. data/app/models/context_drop/element_reference.rb +47 -0
  29. data/app/models/context_drop/feedback.rb +94 -0
  30. data/app/services/context_drop/metadata_sanitizer.rb +215 -0
  31. data/app/services/context_drop/upload_validator.rb +217 -0
  32. data/app/views/context_drop/dashboard/feedbacks/index.html.erb +86 -0
  33. data/app/views/context_drop/dashboard/feedbacks/show.html.erb +121 -0
  34. data/app/views/layouts/context_drop/application.html.erb +215 -0
  35. data/config/routes.rb +17 -0
  36. data/db/migrate/20261109000001_create_context_drop_feedbacks.rb +23 -0
  37. data/db/migrate/20261109000002_create_context_drop_element_references.rb +29 -0
  38. data/db/migrate/20261109000003_create_context_drop_attachments.rb +24 -0
  39. data/lib/context_drop/base64_util.rb +15 -0
  40. data/lib/context_drop/configuration.rb +155 -0
  41. data/lib/context_drop/engine.rb +32 -0
  42. data/lib/context_drop/mcp/dispatcher.rb +197 -0
  43. data/lib/context_drop/mcp/prompts/investigate_feedback.rb +83 -0
  44. data/lib/context_drop/mcp/server.rb +25 -0
  45. data/lib/context_drop/mcp/stdio_runner.rb +62 -0
  46. data/lib/context_drop/mcp/tools/get_feedback.rb +133 -0
  47. data/lib/context_drop/mcp/tools/get_feedback_attachment.rb +71 -0
  48. data/lib/context_drop/mcp/tools/list_feedbacks.rb +127 -0
  49. data/lib/context_drop/mcp/tools/update_feedback_status.rb +72 -0
  50. data/lib/context_drop/version.rb +5 -0
  51. data/lib/context_drop.rb +32 -0
  52. data/lib/generators/context_drop/install/install_generator.rb +52 -0
  53. data/lib/generators/context_drop/install/templates/initializer.rb.tt +55 -0
  54. data/lib/tasks/context_drop_tasks.rake +14 -0
  55. metadata +246 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5bc93ce0b4c5324da60fbf4c153de9c014b31713a675d81a77ba5341c25fecef
4
+ data.tar.gz: b576481d6c94f9a4f154251a29ac7c4db68bfef5bc07b3dc6920c3472a15b26a
5
+ SHA512:
6
+ metadata.gz: d11821d98ea7218e3806d59cb14c555ccb25e3a8455e27ed7cafe807dbdcd9972aba85f194b7be2d20c843fb21969b4740d99d81a306544d89c7ef8c03a67742
7
+ data.tar.gz: 3b9f7d4b7b4c1bc9a4414b2d571f203ad60cf158a86689588785be3d4d1133f0fff8a50a8e9018731f05fc42ac47180c2c128cff5d8c39f07e5b9af70f374199
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kemal Elmizan
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,337 @@
1
+ <p align="center">
2
+ <img src="docs/images/context-drop-hero.svg" alt="ContextDrop — Self-hosted contextual feedback engine, tactile DOM pin-drop widget, and IDE MCP server for Ruby on Rails" width="100%" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ <strong>Stop decoding vague bug reports. Let users and teammates pin the exact DOM element right on your Rails app.</strong><br />
7
+ Self-hosted Mountable Rails Engine · Shadow DOM Widget · Overlapping Layer Picker · Active Storage · Built-in MCP Server
8
+ </p>
9
+
10
+ <p align="center">
11
+ <a href="#60-second-quickstart">Quickstart</a> ·
12
+ <a href="#see-it-in-action">Live Demo</a> ·
13
+ <a href="#precision-dom-pinning--layer-disambiguation">Layer Picker</a> ·
14
+ <a href="#connect-your-ide--ai-coding-agent-mcp">MCP Server</a> ·
15
+ <a href="#zero-trust-privacy--redaction">Privacy &amp; Redaction</a> ·
16
+ <a href="#configuration">Configuration</a>
17
+ </p>
18
+
19
+ ---
20
+
21
+ ## Why
22
+
23
+ When someone reports *"the button on the checkout card isn't working,"* engineers usually spend 20 minutes hunting down which view partial, feature flag, viewport width, or overlapping toast banner caused the problem.
24
+
25
+ **ContextDrop** (`context_drop`) embeds a tactile, zero-bleed **Shadow DOM feedback composer and DOM pin selector** directly into your Ruby on Rails application (`Rails >= 7.1`, including `Rails 8+`). Reporters click **Pin Element**, point at any UI component—even underneath floating badges or cascading overlays—attach a privacy-redacted screenshot, and send.
26
+
27
+ <p align="center">
28
+ <img src="docs/images/demo-pinning-layers.jpg" alt="ContextDrop Pin Mode highlighting an overlapping DOM layer with live dimensions and the Compact Pinning Bar" width="100%" />
29
+ </p>
30
+
31
+ > **Above:** Active **Pin Mode** on the live demo page. While hovering over a stacked UI card (`section #scenario-overlapping-layers · 681×350 · 1/6 layers`), ContextDrop frames the target in Inspector Cobalt, displays layer depth for mouse-wheel cycling, and collapses the chatbox into the **Compact Pinning Bar** (`Pinning`, live DOM ancestor breadcrumbs, Up/Down layer controls, and `Chat` toggle) so the host page stays completely unobstructed.
32
+
33
+ <table>
34
+ <thead>
35
+ <tr>
36
+ <th width="50%" align="left">1. Open the Floating Composer or Pin Dock</th>
37
+ <th width="50%" align="left">2. Pin DOM Layers &amp; Capture Element Screenshots</th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ <tr>
42
+ <td valign="top">
43
+ <img src="docs/images/demo-chatbox-composer.jpg" alt="ContextDrop floating chatbox composer with top-left Pin Element trigger and toolbar" width="100%" />
44
+ <p>
45
+ Drop <code>&lt;%= context_drop_widget %&gt;</code> into your layout to render a draggable, Shadow DOM-isolated feedback composer. Click the top-left <strong>Pin Element</strong> button (or the freely floating pin dock) to start selecting elements on the page.
46
+ </p>
47
+ </td>
48
+ <td valign="top">
49
+ <img src="docs/images/demo-pinned-element-chip.png" alt="ContextDrop chatbox with a pinned DOM element chip, camera screenshot button, and confirmation banner" width="100%" />
50
+ <p>
51
+ Every pinned element is added to the context tray (`#scenario-overlapping-layers`) with a live count badge (`Pin Element 1`). Hover the chip to highlight the element on the page, click the camera icon to crop a screenshot of that exact node, and hit <strong>Send</strong>.
52
+ </p>
53
+ </td>
54
+ </tr>
55
+ </tbody>
56
+ </table>
57
+
58
+ ---
59
+
60
+ ## Why ContextDrop
61
+
62
+ | Capability | Typical SaaS Feedback Widgets | Manual Slack / Issue Reports | **ContextDrop (`context_drop`)** |
63
+ |---|---|---|---|
64
+ | **Data & Storage Ownership** | Exfiltrates DOM & screenshots to 3rd-party cloud servers | Scattered across chat threads | **100% self-hosted** in your Rails DB (`context_drop_*`) & **Active Storage** |
65
+ | **Overlapping DOM Layers** | Gets stuck on topmost wrapper, modal backdrop, or toast | Requires reporter to open DevTools | **Scroll-wheel depth cycling**, **Click-to-Pick Layer Popover**, & **DOM Breadcrumbs** |
66
+ | **Host Page Safety** | Injects global CSS; clicks can accidentally fire host buttons | N/A | **Strict Shadow DOM isolation** + capture-phase event suppression during Pin Mode |
67
+ | **Sensitive Data Protection** | Captures raw DOM or requires complex PII scrubbing rules | Risk of unredacted screenshots | **Zero-trust redaction** before rasterization (`[data-feedback-sensitive]`, passwords, cards) |
68
+ | **Authentication Model** | Separate vendor accounts & seats | Manual identity lookup | **Host-agnostic polymorphic `submitter`** (Devise, Rails 8 Auth, Sorcery, custom, or anonymous) |
69
+ | **Developer Resolution Flow** | Copy-paste from external vendor dashboard | Back-and-forth reproduction questions | **Built-in Rails Triage Dashboard** + **Native MCP Server** (`stdio` & HTTP) for Cursor / Claude Code / Antigravity |
70
+
71
+ > [!IMPORTANT]
72
+ > **Core Architectural Principle: ContextDrop owns its domain. Your Rails app owns its identity and infrastructure.**
73
+ > ContextDrop mounts cleanly as an isolated Rails Engine (`isolate_namespace ContextDrop`) with zero mandatory frontend framework dependencies (no React, Vue, or Tailwind required).
74
+
75
+ ---
76
+
77
+ ## Quickstart
78
+
79
+ ### Requirements
80
+
81
+ - **Ruby** `>= 3.1`
82
+ - **Ruby on Rails** `>= 7.1` (`7.1`, `7.2`, `8.0+`)
83
+ - **Active Storage** installed in the host application (`bin/rails active_storage:install`)
84
+
85
+ ### 1. Install the Gem & Run the Generator
86
+
87
+ ```bash
88
+ bundle add context_drop
89
+ bin/rails generate context_drop:install
90
+ bin/rails db:migrate
91
+ ```
92
+
93
+ What `bin/rails generate context_drop:install` sets up for you:
94
+ 1. Creates [`config/initializers/context_drop.rb`](lib/generators/context_drop/install/templates/initializer.rb.tt).
95
+ 2. Mounts `mount ContextDrop::Engine => "/context_drop"` in `config/routes.rb`.
96
+ 3. Copies three engine-owned migrations (`context_drop_feedbacks`, `context_drop_element_references`, `context_drop_attachments`) into `db/migrate/`.
97
+ 4. Verifies that Active Storage migrations are present.
98
+
99
+ ### 2. Render the Widget in Your Layout
100
+
101
+ Add `<%= context_drop_widget %>` right before `</body>` in `app/views/layouts/application.html.erb`:
102
+
103
+ ```erb
104
+ <%= context_drop_widget %>
105
+ ```
106
+
107
+ Need page-specific copy or release metadata? Override options per view without mutating global configuration:
108
+
109
+ ```erb
110
+ <%= context_drop_widget(
111
+ position: :bottom_left,
112
+ placeholder: "Spot an issue with checkout? Pin the element and let us know...",
113
+ metadata: { release: ENV["GIT_COMMIT_SHA"], feature_flag: "checkout_v2" }
114
+ ) %>
115
+ ```
116
+
117
+ ### 3. Enable & Launch
118
+
119
+ ```bash
120
+ export CONTEXT_DROP_ENABLED=true
121
+ bin/rails server
122
+ ```
123
+
124
+ Visit any page in your Rails app to use the floating widget, or open `/context_drop/dashboard/feedbacks` to triage submissions.
125
+
126
+ ---
127
+
128
+ ## Interactive Playground
129
+
130
+ Want to test the floating Pin selector, overlapping-layer disambiguation, draggable chatbox, and live payload inspector before installing the gem into a Rails app? Run the standalone playground in this repository:
131
+
132
+ ```bash
133
+ # Start the local interactive demo server at http://127.0.0.1:4173
134
+ npm run demo
135
+
136
+ # Or open demo/index.html directly in your browser (works offline via built-in payload interceptor)
137
+ open demo/index.html
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Feedback-to-Fix Loop
143
+
144
+ ```mermaid
145
+ flowchart LR
146
+ subgraph Browser["Host Rails App (Browser)"]
147
+ Widget["<context-drop-widget>\n(Shadow DOM + Layer Picker)"]
148
+ end
149
+
150
+ subgraph Engine["ContextDrop::Engine (Self-Hosted in Your Rails App)"]
151
+ Controller["FeedbacksController\n+ MetadataSanitizer"]
152
+ DB[("context_drop_feedbacks\ncontext_drop_element_references\ncontext_drop_attachments")]
153
+ Storage[("Host Active Storage\n(Redacted Screenshots)")]
154
+ Dashboard["Triage Dashboard\n/context_drop/dashboard/feedbacks"]
155
+ MCP["ContextDrop::MCP::Server\n(stdio & Streamable HTTP)"]
156
+ end
157
+
158
+ subgraph IDE["Developer IDE / Coding Agent"]
159
+ Agent["Cursor · Claude Code · Antigravity\nWindsurf · VS Code"]
160
+ end
161
+
162
+ Widget -->|"POST /context_drop/feedback\n(CSRF + Rate Limit)"| Controller
163
+ Controller --> DB
164
+ Controller --> Storage
165
+ DB --> Dashboard
166
+ Storage --> Dashboard
167
+ DB --> MCP
168
+ Storage --> MCP
169
+ MCP -->|"list_feedbacks / get_feedback\n(Selectors + Multimodal Images)"| Agent
170
+ ```
171
+
172
+ Every submission captures a rich, sanitized engineering payload:
173
+ - **`ContextDrop::Feedback` (`context_drop_feedbacks`):** Polymorphic `submitter` (`User`, `Employee`, `AdminUser`, or anonymous), `message`, `page_url`, `user_agent`, `viewport` & custom `metadata`, and workflow `status` (`new`, `reviewed`, `resolved`, `dismissed`).
174
+ - **`ContextDrop::ElementReference` (`context_drop_element_references`):** Point-in-time DOM snapshot including `tag_name`, best-effort CSS `selector`, structural `dom_path`, truncated `text_content`, allowlisted `attributes_snapshot` (`id`, `class`, `role`, `aria-*`, `data-testid`), and CSS pixel `bounding_rect` (`{ x, y, width, height }`).
175
+ - **`ContextDrop::Attachment` (`context_drop_attachments`):** Viewport screenshots, element-cropped screenshots (linked via `element_reference_id`), and uploaded images stored in your app's **Active Storage**.
176
+
177
+ ---
178
+
179
+ ## DOM Pinning & Layer Disambiguation
180
+
181
+ Real-world Rails interfaces have sticky navbars, floating promo ribbons, translucent toast notifications, and deeply nested Flexbox/Grid containers. ContextDrop makes selecting the exact underlying element effortless:
182
+
183
+ 1. **Top-Left Pin Selector & Freely Draggable Dock:** Click **Pin Element** in the top-left corner of the chatbox (or from the floating dock). The chatbox collapses into the **Compact Pinning Bar** so it never blocks the page.
184
+ 2. **Scroll-Wheel & Keyboard Depth Cycling:** While hovering any coordinate, roll your mouse wheel (`wheel`) or press `ArrowUp` / `ArrowDown` (`[` / `]`) to cycle through overlapping sibling layers (`document.elementsFromPoint`), child nodes, and parent containers while keeping a dashed ghost outline on the original leaf node.
185
+ 3. **Click-to-Pick Layer Stack Popover:** Clicking a multi-layer spot anchors the pin and opens a Figma-style **Layer & Hierarchy Picker** displaying every candidate layer (`Topmost`, `Overlap`, `Child`, `Parent` badges with pixel dimensions) with live hover preview on the page. Hold `Shift+Click` or press `Enter` to pin immediately.
186
+ 4. **Interactive DOM Breadcrumb Bar:** Both the Compact Pinning Bar and the Layer Popover render an interactive ancestor trail (`main.demo-shell > section#scenario > button`)—hover any segment to preview or click to pin it directly.
187
+ 5. **Capture-Phase Host Protection:** All `pointerdown`, `mousedown`, `mouseup`, `click`, and `wheel` events are intercepted in the capture phase during Pin Mode so underlying links, forms, and buttons are never triggered by mistake.
188
+
189
+ ---
190
+
191
+ ## MCP
192
+
193
+ ContextDrop ships with a built-in **Model Context Protocol (MCP)** server (`ContextDrop::MCP::Server`) so you or your AI coding agent can pull bug reports, inspect exact CSS selectors and DOM paths, view multimodal screenshots, and mark issues resolved without leaving your editor.
194
+
195
+ - **Exposed Tools:** `list_feedbacks`, `get_feedback` (includes base64 multimodal `image` content blocks), `get_feedback_attachment`, `update_feedback_status`
196
+ - **Exposed Resources:** `contextdrop://feedbacks/new`, `contextdrop://feedbacks/{id}`
197
+ - **Built-in Prompt:** `investigate_feedback`
198
+
199
+ ### Option 1: Local `stdio` Transport (Zero-Config Local Development)
200
+
201
+ Add this to `.cursor/mcp.json`, `.mcp.json` (Claude Code), Antigravity `mcp_config.json`, Windsurf, or VS Code:
202
+
203
+ ```json
204
+ {
205
+ "mcpServers": {
206
+ "context-drop": {
207
+ "command": "bin/rails",
208
+ "args": ["context_drop:mcp"]
209
+ }
210
+ }
211
+ }
212
+ ```
213
+
214
+ ### Option 2: Remote Streamable HTTP Transport (Staging & Production)
215
+
216
+ Enable MCP on your staging or production server with a bearer token (`CONTEXT_DROP_MCP_ENABLED=true` and `CONTEXT_DROP_MCP_API_KEY=<your-secret-token>`) and point your IDE to `POST /context_drop/mcp`:
217
+
218
+ ```json
219
+ {
220
+ "mcpServers": {
221
+ "context-drop-staging": {
222
+ "url": "https://staging.example.com/context_drop/mcp",
223
+ "headers": {
224
+ "Authorization": "Bearer <your-secret-token>"
225
+ }
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ Once connected, prompt your coding agent directly in your IDE:
232
+ > *"List the new ContextDrop feedbacks on `/checkout`, inspect the pinned element selector and screenshot for the overlapping button issue, fix the CSS in our Rails view, and mark the feedback resolved."*
233
+
234
+ ---
235
+
236
+ ## Zero-Trust Privacy & Redaction
237
+
238
+ ContextDrop is engineered for production environments where customer privacy and credential safety are non-negotiable:
239
+
240
+ - **Declarative Redaction Attributes:** Add `data-feedback-sensitive` or `data-feedback-ignore` to any HTML element. ContextDrop blocks the element (and **all of its descendants**) from being pinned and masks its bounding box with an opaque redaction block *before* any screenshot is rasterized:
241
+ ```html
242
+ <div data-feedback-sensitive>
243
+ <span>Account Balance: $42,100.00</span>
244
+ <span>Card ending in 4242</span>
245
+ </div>
246
+ ```
247
+ - **Automatic Credential & Card Exclusions:** `input[type="password"]`, `input[type="hidden"]`, and `[autocomplete^="cc-"]` are always excluded and redacted automatically.
248
+ - **No Raw DOM or Input Value Leaks:** Form control `value` properties and raw `outerHTML` are **never** serialized. Only allowlisted structural attributes (`id`, `class`, `role`, `aria-*`, `data-testid`, `name`, `type`, `href`) are stored.
249
+ - **Server-Side Upload Hardening:** Binary attachments are verified via magic-byte MIME detection (`Marcel`), extension matching, byte-size limits, image dimension bounds, CSRF protection, and configurable rate limiting.
250
+
251
+ ---
252
+
253
+ ## Self-Hosted Triage Dashboard
254
+
255
+ ContextDrop includes a zero-dependency triage interface mounted inside your Rails application at `/context_drop/dashboard/feedbacks`:
256
+
257
+ - **Filter & Search:** Filter submissions by status (`new`, `reviewed`, `resolved`, `dismissed`), originating `page_url`, or free-text query across messages, CSS selectors, and DOM paths.
258
+ - **Inspect Full Engineering Context:** View submitter attribution, browser/viewport metadata, inline Active Storage screenshot galleries, and structured `ElementReference` cards (`selector`, `dom_path`, `bounding_rect`, `attributes_snapshot`).
259
+ - **One-Click Triage Actions:** Transition feedback status between `Mark Reviewed`, `Mark Resolved`, and `Dismiss`.
260
+
261
+ ---
262
+
263
+ ## Configuration
264
+
265
+ Customize every aspect of ContextDrop in `config/initializers/context_drop.rb`:
266
+
267
+ ```ruby
268
+ ContextDrop.configure do |config|
269
+ # Master switch (disables widget helper, dashboard, and HTTP ingestion endpoints when false)
270
+ config.enabled = ENV.fetch("CONTEXT_DROP_ENABLED", "false") == "true"
271
+
272
+ # Initial widget corner position: :bottom_right, :bottom_left, :top_right, :top_left
273
+ config.position = :bottom_right
274
+
275
+ # Allow unauthenticated visitors to submit feedback
276
+ config.allow_anonymous = true
277
+
278
+ # Polymorphic submitter attribution — return any ActiveRecord model (User, Employee, AdminUser) or nil
279
+ config.current_submitter = ->(controller) { controller.try(:current_user) }
280
+
281
+ # Authorization hooks
282
+ config.authorize_submission = ->(_controller) { true }
283
+ config.authorize_dashboard = ->(controller) {
284
+ controller.try(:current_user)&.try(:admin?) || Rails.env.development?
285
+ }
286
+
287
+ # Limits & upload validation
288
+ config.max_attachments = 10
289
+ config.max_element_references = 10
290
+ config.max_attachment_size = 10.megabytes
291
+ config.allowed_content_types = %w[image/png image/jpeg image/webp image/gif]
292
+ config.max_image_dimensions = [4096, 4096]
293
+
294
+ # Additional CSS selectors to exclude from DOM selection and redact in screenshots
295
+ config.excluded_selectors = [
296
+ "[data-feedback-ignore]",
297
+ "[data-feedback-sensitive]",
298
+ "input[type='password']",
299
+ "input[type='hidden']",
300
+ "[autocomplete^='cc-']"
301
+ ]
302
+
303
+ # Lifecycle callback invoked after feedback persistence (ideal for Slack, Linear, or GitHub webhooks)
304
+ config.on_feedback_created = ->(feedback) {
305
+ Rails.logger.info("New ContextDrop Feedback ##{feedback.id} on #{feedback.page_url}")
306
+ }
307
+
308
+ # Model Context Protocol (MCP) Server settings
309
+ config.mcp_enabled = ENV.fetch("CONTEXT_DROP_MCP_ENABLED", "false") == "true"
310
+ config.mcp_api_key = ENV["CONTEXT_DROP_MCP_API_KEY"]
311
+ config.authorize_mcp = nil
312
+ end
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Contributing & Development
318
+
319
+ ```bash
320
+ # Build the decoupled TypeScript widget into app/assets/javascripts/context_drop/context_drop.js
321
+ npm run build
322
+
323
+ # Run frontend TypeScript unit & interaction tests
324
+ npm test
325
+
326
+ # Run backend Ruby engine & MCP specs
327
+ ruby spec/run_all.rb
328
+ ```
329
+
330
+ Further architectural documentation:
331
+ - [Technical Specification (`SPEC.md`)](SPEC.md)
332
+ - [Design System (`DESIGN.md`)](DESIGN.md)
333
+ - [Product Brief (`PRODUCT.md`)](PRODUCT.md)
334
+
335
+ ## License
336
+
337
+ Released under the [MIT License](MIT-LICENSE).
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "lib"
8
+ t.libs << "spec"
9
+ t.pattern = "spec/**/*_spec.rb"
10
+ t.verbose = false
11
+ end
12
+
13
+ namespace :assets do
14
+ desc "Build the decoupled TypeScript widget into app/assets/javascripts/context_drop/context_drop.js"
15
+ task :build do
16
+ sh "npm run build"
17
+ end
18
+ end
19
+
20
+ task default: :test