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
data/SPEC.md ADDED
@@ -0,0 +1,532 @@
1
+ # ContextDrop — Technical Specification
2
+
3
+ **Status:** Draft (v0.1.0-spec)
4
+ **Project Type:** Open-source Ruby Gem (`context_drop`)
5
+ **Architecture:** Mountable Ruby on Rails Engine (`isolate_namespace ContextDrop`) + Decoupled TypeScript Frontend Widget + MCP Server
6
+ **License:** MIT (Proposed / TBD)
7
+ **Working Name:** ContextDrop
8
+
9
+ > **Core Design Principle:**
10
+ > **ContextDrop owns its domain. The host application owns its identity and infrastructure.**
11
+ > ContextDrop functions as an independent, self-contained subsystem inside the host Rails application without knowing whether the host uses Devise, Sorcery, Authlogic, Rails 8 built-in authentication, or custom session logic.
12
+
13
+ ---
14
+
15
+ ## 1. Overview
16
+
17
+ **ContextDrop** is a self-hosted contextual feedback library for Ruby on Rails applications.
18
+
19
+ It provides an embeddable feedback interface inspired by modern AI prompt composers, an engine-owned persistence layer, a built-in self-hosted triage dashboard, and a Model Context Protocol (MCP) server for IDEs. Users can submit natural-language feedback while attaching visual and structural context from the application page they are currently using.
20
+
21
+ Users can:
22
+ - Describe bugs, UX issues, feature requests, or general feedback in a multiline prompt composer.
23
+ - Upload images as supporting evidence.
24
+ - Capture screenshots of the current browser viewport or specific DOM elements.
25
+ - Select individual DOM elements interactively and attach them to feedback.
26
+ - Attach multiple files, screenshots, and DOM element references to a single feedback submission.
27
+
28
+ ContextDrop stores feedback, attachments, and element references in **engine-owned database tables** and binary files in the host application's **Active Storage**. It requires **no external SaaS account, hosted backend, or AI provider**.
29
+
30
+ ---
31
+
32
+ ## 2. Goals & Non-Goals
33
+
34
+ ### 2.1 Primary Goals
35
+
36
+ 1. **Encapsulated Mountable Engine:** Keep all feedback functionality encapsulated inside the gem (models, persistence, submission controllers, self-hosted dashboard views, attachment handling, routes, and JavaScript widget).
37
+ 2. **Prompt-Style UX:** Make feedback submission feel as fluid and intuitive as interacting with an AI prompt composer.
38
+ 3. **High-Signal Context Preservation:** Preserve the visual state (screenshots), viewport geometry, URL, and sanitized DOM metadata (`ElementReference`) surrounding user feedback.
39
+ 4. **Dual-Environment Support:** Support both internal employee applications (authenticated) and public-facing customer applications (authenticated or anonymous).
40
+ 5. **Clean Ownership Boundary:** Own dedicated, namespaced database tables and migrations (`context_drop_*`) while delegating user identity, authentication, authorization policies, and physical database/storage infrastructure to the host application.
41
+ 6. **Minimal Integration Effort:** Installable via standard Rails generators (`bin/rails generate context_drop:install`) and an explicit view helper (`<%= context_drop_widget %>`).
42
+ 7. **IDE & Agent Ready (Phase 2):** Provide an MCP server (`stdio` and HTTP) so developers and coding agents can query feedback, inspect screenshots/selectors, and resolve issues directly from their IDE.
43
+
44
+ ### 2.2 Non-Goals (Initial Release)
45
+
46
+ The initial release (`v0.1.0`) will **not** provide:
47
+ - An external hosted feedback collection service or multi-tenant cloud SaaS dashboard.
48
+ - Project management or sprint planning functionality.
49
+ - Built-in third-party issue tracker integrations (Jira, Linear, GitHub Issues — supported via callbacks instead).
50
+ - AI-powered categorization or summarization.
51
+ - Session replay or continuous screen recording.
52
+ - Real-time collaborative feedback.
53
+ - Automatic bug reproduction.
54
+ - A user-facing conversation history.
55
+
56
+ ---
57
+
58
+ ## 3. Target Environments & Users
59
+
60
+ | Environment | Primary Users | Typical Use Cases | Identity Model |
61
+ |---|---|---|---|
62
+ | **Internal Applications** | Employees, QA, Operations, Support | Application bugs, confusing workflows, data inconsistencies, missing functionality, operational issues | Authenticated submitter linked via polymorphic `submitter` (`User`, `Employee`, `AdminUser`, etc.) |
63
+ | **Public-Facing Applications** | Customers, beta testers, guests | UI problems, checkout issues, unexpected behavior, feature requests, general product feedback | Authenticated customer or anonymous submission (`allow_anonymous = true`) |
64
+
65
+ The host application remains responsible for deciding whether authentication is required for submission and who is authorized to access the engine's feedback dashboard or retrieval APIs.
66
+
67
+ ---
68
+
69
+ ## 4. User Experience
70
+
71
+ ### 4.1 Freely Floating Pin Dock & Draggable Chatbox
72
+
73
+ ContextDrop provides a freely draggable floating Pin dock and chatbox on configured application pages:
74
+ - **Positioning:** `position: fixed` relative to the viewport, starting at the configured corner (`:top_left`, `:bottom_right`, `:bottom_left`, `:top_right`) and freely draggable anywhere within viewport bounds.
75
+ - **Pin-Shaped Selector:** Available both on the floating dock (`.cd-floating-pin-btn`) and anchored at the **top-left corner of the chatbox header** (`.cd-pin-corner-btn`) with an asymmetric pushpin silhouette (`11px 11px 11px 3px`).
76
+ - **Minimize to Compact Bar:** While pinning elements on the page (or when clicking the header minimize button), the chatbox collapses into a compact floating bar (`.cd-compact-bar`) with live DOM breadcrumbs, layer Up/Down buttons, and an expand-to-chat toggle so the host page remains unobstructed.
77
+
78
+ ### 4.2 Chatbox Feedback Composer
79
+
80
+ The chatbox uses a conversational prompt interface containing:
81
+ - A top-left Pin-shaped element selector button and draggable header bar.
82
+ - A scrollable session chat thread displaying submitted feedback drops in the current session with element pills and timestamps.
83
+ - A context chip tray where hovering a pinned element chip highlights the live DOM node on the page.
84
+ - A multiline text input (with clipboard image paste and drag-and-drop image upload).
85
+ - Toolbar controls for Pin Element, Viewport Screenshot, Image Upload, and Send (`Cmd/Ctrl+Enter` or click).
86
+
87
+ Supports text-only feedback, feedback with uploaded images, feedback with screenshots, feedback with selected DOM elements, and multiple attachments + multiple DOM elements in a single submission. Users can review and remove any item before submitting.
88
+
89
+ ### 4.3 Attachment & Element Reference Interaction
90
+
91
+ Attached items are represented as individual chips/cards within the composer tray:
92
+ - **Visual Preview:** Image thumbnail for uploaded images and screenshots; `<tag#id.class>` Pin badge for DOM element references (hovering highlights the element on the page, and clicking the camera icon captures an element-cropped screenshot).
93
+ - **Type Indicator:** Clearly distinguishes `Pin`, `Shot`, and `Image`.
94
+ - **Removable:** Each item can be removed prior to submission.
95
+
96
+ ### 4.4 Precision DOM Element Selection (Overlapping & Cascading Disambiguation)
97
+
98
+ 1. User activates the Pin-shaped selector in the top-left corner of the chatbox or on the floating pin dock.
99
+ 2. ContextDrop collapses the chatbox into the Compact Pinning Bar and enters selection mode (`crosshair` cursor with a cursor-following pushpin marker).
100
+ 3. Hovering over an element displays a primary bounding-box highlight overlay and a live HUD showing `<tag#selector>`, pixel dimensions, and layer depth (`1/5 layers · ↕ scroll`).
101
+ 4. **Disambiguating Overlapping & Cascading Elements:**
102
+ - **Scroll-Wheel & `↑`/`↓` Arrow Key Depth Cycling:** Scrolling the mouse wheel (`wheel`) or pressing `ArrowUp`/`ArrowDown` (`[`/`]`) smoothly cycles through the candidate stack at that point (`target`, overlapping sibling elements via `document.elementsFromPoint`, direct children, and DOM ancestors) while rendering a dashed ghost outline around the original leaf element.
103
+ - **Click-to-Pick Layer Popover:** Clicking a location with multiple cascading or overlapping layers anchors the floating pin and opens a floating Layer & Hierarchy Picker Popover listing all candidate layers at that coordinate with live hover preview on the page (`Shift+Click` or `Enter` pins immediately).
104
+ - **Interactive DOM Breadcrumb Bar:** Both the Layer Popover and the Compact Pinning Bar render an interactive ancestor path (`section > div.card > button`) where hovering previews and clicking pins any ancestor or target element directly.
105
+ 5. Selection mode remains active so additional elements can be pinned in one session.
106
+ 6. Users can exit selection mode explicitly (`Escape` or returning to `Chat`).
107
+
108
+ **Host Non-Interference:** Click and pointer events in selection mode MUST be intercepted in the capture phase (`preventDefault`, `stopPropagation`, `stopImmediatePropagation`) so underlying application links, buttons, and forms are never triggered.
109
+
110
+ **Supported DOM Structures:** Nested DOM elements, accessible open Shadow DOM, `position: fixed` / `sticky` elements, elements inside scrollable containers, and dynamically rendered content.
111
+
112
+ ### 4.5 Screenshot Capture
113
+
114
+ ContextDrop supports two screenshot operations:
115
+ - **Viewport Capture:** Captures the currently visible browser viewport, preserving visual state at capture time while hiding ContextDrop's own UI.
116
+ - **Element Capture:** Captures a selected DOM element and its visible contents (plus configurable surrounding context padding), optionally linked to the corresponding `ElementReference`.
117
+
118
+ Screenshot capture works independently of DOM element attachment and degrades gracefully when browser APIs or CSP rules restrict canvas capture.
119
+
120
+ ---
121
+
122
+ ## 5. Technical Architecture
123
+
124
+ ### 5.1 Subsystem & Directory Structure
125
+
126
+ Generated as a mountable Rails plugin (`rails plugin new context_drop --mountable`) with decoupled frontend widget packaging:
127
+
128
+ ```text
129
+ context_drop/
130
+ ├── app/
131
+ │ ├── controllers/
132
+ │ │ └── context_drop/
133
+ │ │ ├── application_controller.rb
134
+ │ │ ├── configs_controller.rb
135
+ │ │ ├── feedbacks_controller.rb
136
+ │ │ ├── attachments_controller.rb
137
+ │ │ ├── health_controller.rb
138
+ │ │ ├── mcp_controller.rb
139
+ │ │ └── dashboard/
140
+ │ │ └── feedbacks_controller.rb
141
+ │ ├── helpers/
142
+ │ │ └── context_drop/
143
+ │ │ └── widget_helper.rb
144
+ │ ├── models/
145
+ │ │ └── context_drop/
146
+ │ │ ├── application_record.rb
147
+ │ │ ├── feedback.rb
148
+ │ │ ├── attachment.rb
149
+ │ │ └── element_reference.rb
150
+ │ ├── services/
151
+ │ │ └── context_drop/
152
+ │ │ └── metadata_sanitizer.rb
153
+ │ ├── views/
154
+ │ │ └── context_drop/
155
+ │ │ └── dashboard/
156
+ │ │ └── feedbacks/
157
+ │ │ ├── index.html.erb
158
+ │ │ └── show.html.erb
159
+ │ ├── javascript/
160
+ │ │ └── context_drop/ # TypeScript/JS widget source (button, composer, picker, screenshot)
161
+ │ └── assets/
162
+ │ ├── javascripts/
163
+ │ │ └── context_drop/ # Pre-built self-contained browser bundle
164
+ │ └── stylesheets/
165
+ │ └── context_drop/
166
+ ├── config/
167
+ │ └── routes.rb
168
+ ├── db/
169
+ │ └── migrate/ # Engine-owned migrations
170
+ ├── lib/
171
+ │ ├── context_drop.rb
172
+ │ ├── context_drop/
173
+ │ │ ├── configuration.rb
174
+ │ │ ├── engine.rb
175
+ │ │ ├── version.rb
176
+ │ │ └── mcp/ # Model Context Protocol server (stdio + HTTP)
177
+ │ └── generators/
178
+ │ └── context_drop/
179
+ │ └── install/
180
+ │ ├── install_generator.rb
181
+ │ └── templates/
182
+ │ └── initializer.rb.tt
183
+ └── spec/ # Engine test suite + dummy Rails app
184
+ ```
185
+
186
+ ### 5.2 Domain Ownership Boundaries
187
+
188
+ | Component | Ownership | Notes |
189
+ |---|---|---|
190
+ | `ContextDrop::Feedback` (`context_drop_feedbacks`) | **ContextDrop Engine** | Owns feedback records, status lifecycle, and context metadata |
191
+ | `ContextDrop::Attachment` (`context_drop_attachments`) | **ContextDrop Engine** | Owns attachment metadata (`image`, `screenshot`); delegates binary storage to Active Storage |
192
+ | `ContextDrop::ElementReference` (`context_drop_element_references`) | **ContextDrop Engine** | Owns sanitized point-in-time DOM element snapshots |
193
+ | Self-Hosted Dashboard Views (`app/views/context_drop/`) | **ContextDrop Engine** | Built-in triage UI mounted under engine routes |
194
+ | Floating Widget (`<context-drop-widget>`) | **ContextDrop Frontend** | Framework-free TypeScript Web Component isolated via Shadow DOM |
195
+ | User / Submitter Model | **Host Application** | Linked via `belongs_to :submitter, polymorphic: true, optional: true` |
196
+ | Authentication | **Host Application** | Resolved via `config.current_submitter` proc |
197
+ | Authorization Policies | **Configurable Integration** | Enforced via `config.authorize_submission`, `config.authorize_dashboard`, and `config.authorize_mcp` |
198
+ | Physical Database & Active Storage | **Host Application** | Host app owns DB connection and `active_storage_blobs` / `active_storage_attachments` |
199
+
200
+ ### 5.3 Rails Engine & Installation Workflow
201
+
202
+ ```ruby
203
+ module ContextDrop
204
+ class Engine < ::Rails::Engine
205
+ isolate_namespace ContextDrop
206
+ end
207
+ end
208
+ ```
209
+
210
+ **Expected Developer Installation:**
211
+ ```bash
212
+ bundle add context_drop
213
+ bin/rails generate context_drop:install
214
+ bin/rails db:migrate
215
+ ```
216
+
217
+ **What `bin/rails generate context_drop:install` handles:**
218
+ 1. **Initializer:** Generates `config/initializers/context_drop.rb` with environment variable integration (`CONTEXT_DROP_ENABLED`).
219
+ 2. **Route Mounting:** Mounts `mount ContextDrop::Engine => "/context_drop"` in the host's `config/routes.rb`.
220
+ 3. **Database Migrations:** Copies engine-owned migrations (`rails context_drop:install:migrations`) into the host application's `db/migrate/` directory.
221
+ 4. **Active Storage Check:** Verifies Active Storage tables are present and guides/runs `rails active_storage:install` if missing.
222
+ 5. **Frontend Integration:** Exposes the widget asset for Propshaft, Sprockets, Importmaps, or JS bundlers without requiring layout rewrites.
223
+
224
+ ### 5.4 Decoupled Frontend Architecture
225
+
226
+ The frontend widget is decoupled from the Rails Engine's backend internals:
227
+ - Implemented in **TypeScript** using standard browser APIs and Web Components (`<context-drop-widget>`).
228
+ - Zero mandatory dependencies on React, Vue, Angular, or CSS frameworks.
229
+ - Uses **Shadow DOM** (`mode: 'open'`) for strict CSS isolation—no global CSS resets and no style bleed to or from the host application.
230
+ - Distributed both as a pre-compiled zero-config asset inside the gem (`app/assets/javascripts/context_drop/`) and modular ESM source (`app/javascript/context_drop/`) compatible with Importmaps or JS bundlers.
231
+
232
+ ---
233
+
234
+ ## 6. Configuration Specification
235
+
236
+ ```ruby
237
+ # config/initializers/context_drop.rb
238
+
239
+ ContextDrop.configure do |config|
240
+ # Environment-based master switch (disables widget, dashboard, and ingestion endpoints when false)
241
+ config.enabled = ENV.fetch("CONTEXT_DROP_ENABLED", "false") == "true"
242
+
243
+ # Widget position: :bottom_right, :bottom_left, :top_right, :top_left
244
+ config.position = :bottom_right
245
+
246
+ # Attachment storage backend
247
+ config.storage = :active_storage
248
+ config.active_storage_service = nil # nil uses host app's default Active Storage service
249
+
250
+ # Submission permissions
251
+ config.allow_anonymous = true
252
+
253
+ # Attachment & element limits
254
+ config.max_attachments = 10
255
+ config.max_element_references = 10
256
+ config.max_attachment_size = 10.megabytes
257
+ config.allowed_content_types = %w[image/png image/jpeg image/webp image/gif]
258
+ config.max_image_dimensions = [4096, 4096]
259
+
260
+ # Widget feature flags
261
+ config.element_selection = true
262
+ config.screenshot_capture = true
263
+ config.element_capture_padding = 16
264
+
265
+ # DOM selectors excluded from selection and redacted in screenshots
266
+ config.excluded_selectors = [
267
+ "[data-feedback-ignore]",
268
+ "[data-feedback-sensitive]",
269
+ "input[type='password']",
270
+ "input[type='hidden']",
271
+ "[autocomplete^='cc-']"
272
+ ]
273
+
274
+ # Host authentication integration (returns any ActiveRecord model for polymorphic `submitter`, or nil)
275
+ config.current_submitter = ->(controller) { controller.try(:current_user) }
276
+
277
+ # Authorization policies
278
+ config.authorize_submission = ->(_controller) { true }
279
+ config.authorize_dashboard = ->(controller) { controller.try(:current_user)&.try(:admin?) || Rails.env.development? }
280
+
281
+ # Rate limiting on submission/upload endpoints
282
+ config.rate_limit = { max_requests: 20, period: 1.hour }
283
+
284
+ # Optional lifecycle callback invoked after feedback creation
285
+ config.on_feedback_created = ->(feedback) { }
286
+
287
+ # Model Context Protocol (MCP) server settings (Phase 2)
288
+ config.mcp_enabled = ENV.fetch("CONTEXT_DROP_MCP_ENABLED", "false") == "true"
289
+ config.mcp_api_key = ENV["CONTEXT_DROP_MCP_API_KEY"]
290
+ config.authorize_mcp = nil
291
+ end
292
+ ```
293
+
294
+ ---
295
+
296
+ ## 7. Data Model & Engine-Owned Schema
297
+
298
+ All models inherit from `ContextDrop::ApplicationRecord` and use explicit table names (`context_drop_*`). JSON columns automatically use `jsonb` on PostgreSQL and `json` on SQLite/MySQL.
299
+
300
+ ```mermaid
301
+ erDiagram
302
+ CONTEXT_DROP_FEEDBACKS ||--o{ CONTEXT_DROP_ATTACHMENTS : "has_many"
303
+ CONTEXT_DROP_FEEDBACKS ||--o{ CONTEXT_DROP_ELEMENT_REFERENCES : "has_many"
304
+ CONTEXT_DROP_ELEMENT_REFERENCES ||--o{ CONTEXT_DROP_ATTACHMENTS : "optional element screenshot"
305
+
306
+ CONTEXT_DROP_FEEDBACKS {
307
+ bigint id PK
308
+ string submitter_type "Polymorphic (nullable)"
309
+ bigint submitter_id "Polymorphic (nullable)"
310
+ string session_id "Nullable"
311
+ text message
312
+ string page_url
313
+ string user_agent
314
+ jsonb metadata
315
+ string status "new | reviewed | resolved | dismissed"
316
+ datetime created_at
317
+ datetime updated_at
318
+ }
319
+
320
+ CONTEXT_DROP_ATTACHMENTS {
321
+ bigint id PK
322
+ bigint feedback_id FK
323
+ bigint element_reference_id FK "Nullable (for element screenshots)"
324
+ string attachment_type "image | screenshot | file"
325
+ jsonb metadata
326
+ datetime created_at
327
+ datetime updated_at
328
+ }
329
+
330
+ CONTEXT_DROP_ELEMENT_REFERENCES {
331
+ bigint id PK
332
+ bigint feedback_id FK
333
+ string client_ref "Nullable correlation key"
334
+ string tag_name
335
+ string selector
336
+ string dom_path
337
+ text text_content
338
+ jsonb attributes_snapshot
339
+ jsonb bounding_rect
340
+ jsonb viewport
341
+ jsonb metadata
342
+ datetime captured_at
343
+ datetime created_at
344
+ datetime updated_at
345
+ }
346
+ ```
347
+
348
+ ### 7.1 `ContextDrop::Feedback` (`context_drop_feedbacks`)
349
+
350
+ ```ruby
351
+ module ContextDrop
352
+ class Feedback < ApplicationRecord
353
+ self.table_name = "context_drop_feedbacks"
354
+
355
+ belongs_to :submitter, polymorphic: true, optional: true
356
+
357
+ has_many :attachments,
358
+ class_name: "ContextDrop::Attachment",
359
+ foreign_key: :feedback_id,
360
+ dependent: :destroy
361
+
362
+ has_many :element_references,
363
+ class_name: "ContextDrop::ElementReference",
364
+ foreign_key: :feedback_id,
365
+ dependent: :destroy
366
+ end
367
+ end
368
+ ```
369
+
370
+ | Attribute | Type | Null | Default | Description |
371
+ |---|---|---|---|---|
372
+ | `id` | `bigint` | `false` | auto | Primary identifier |
373
+ | `submitter_type` | `string` | `true` | `nil` | Polymorphic class name of host user model (e.g., `"User"`, `"Employee"`) |
374
+ | `submitter_id` | `bigint` / `string` | `true` | `nil` | Polymorphic primary key of host submitter |
375
+ | `session_id` | `string` | `true` | `nil` | Opaque/hashed session identifier |
376
+ | `message` | `text` | `true` | `nil` | User-submitted feedback message |
377
+ | `page_url` | `string` | `false` | — | Originating page URL |
378
+ | `user_agent` | `string` | `true` | `nil` | Browser `User-Agent` |
379
+ | `metadata` | `jsonb` / `json` | `false` | `{}` | Contextual metadata (`viewport`, `locale`, `timezone`, custom fields) |
380
+ | `status` | `string` | `false` | `"new"` | Workflow status (`new`, `reviewed`, `resolved`, `dismissed`) |
381
+ | `created_at` | `datetime` | `false` | — | Submission timestamp |
382
+ | `updated_at` | `datetime` | `false` | — | Last modification timestamp |
383
+
384
+ ### 7.2 `ContextDrop::Attachment` (`context_drop_attachments`)
385
+
386
+ Represents binary file attachments (`image`, `screenshot`, and future `file` types) backed by Active Storage.
387
+
388
+ ```ruby
389
+ module ContextDrop
390
+ class Attachment < ApplicationRecord
391
+ self.table_name = "context_drop_attachments"
392
+
393
+ belongs_to :feedback, class_name: "ContextDrop::Feedback"
394
+ belongs_to :element_reference, class_name: "ContextDrop::ElementReference", optional: true
395
+
396
+ has_one_attached :file
397
+ end
398
+ end
399
+ ```
400
+
401
+ | Attribute | Type | Null | Default | Description |
402
+ |---|---|---|---|---|
403
+ | `id` | `bigint` | `false` | auto | Primary identifier |
404
+ | `feedback_id` | `bigint` | `false` | — | Parent `ContextDrop::Feedback` ID |
405
+ | `element_reference_id` | `bigint` | `true` | `nil` | Optional link to `ContextDrop::ElementReference` when screenshot is an element capture |
406
+ | `attachment_type` | `string` | `false` | — | Classification: `"image"`, `"screenshot"`, `"file"` (future) |
407
+ | `metadata` | `jsonb` / `json` | `false` | `{}` | Screenshot/image metadata (`capture_type`, `image_dimensions`, `viewport`, `captured_at`) |
408
+ | `created_at` | `datetime` | `false` | — | Creation timestamp |
409
+ | `updated_at` | `datetime` | `false` | — | Last modification timestamp |
410
+
411
+ ### 7.3 `ContextDrop::ElementReference` (`context_drop_element_references`)
412
+
413
+ Represents a sanitized point-in-time snapshot of a DOM element selected by the user.
414
+
415
+ ```ruby
416
+ module ContextDrop
417
+ class ElementReference < ApplicationRecord
418
+ self.table_name = "context_drop_element_references"
419
+
420
+ belongs_to :feedback, class_name: "ContextDrop::Feedback"
421
+ has_many :attachments, class_name: "ContextDrop::Attachment", dependent: :nullify
422
+ end
423
+ end
424
+ ```
425
+
426
+ | Attribute | Type | Null | Default | Description |
427
+ |---|---|---|---|---|
428
+ | `id` | `bigint` | `false` | auto | Primary identifier |
429
+ | `feedback_id` | `bigint` | `false` | — | Parent `ContextDrop::Feedback` ID |
430
+ | `client_ref` | `string` | `true` | `nil` | Client-side correlation token within the submission |
431
+ | `tag_name` | `string` | `false` | — | Lowercase HTML tag name (e.g., `"button"`) |
432
+ | `selector` | `string` | `false` | — | Best-effort CSS selector at capture time (e.g., `"#checkout-button"`) |
433
+ | `dom_path` | `string` | `true` | `nil` | Structural ancestor path (`"html > body > main > button"`) |
434
+ | `text_content` | `text` | `true` | `nil` | Sanitized, truncated visible text content |
435
+ | `attributes_snapshot` | `jsonb` / `json` | `false` | `{}` | Sanitized allowlisted HTML attributes (`id`, `class`, `role`, `aria-*`, `data-testid`) |
436
+ | `bounding_rect` | `jsonb` / `json` | `false` | `{}` | Viewport-relative CSS pixel coordinates (`{ x, y, width, height }`) |
437
+ | `viewport` | `jsonb` / `json` | `false` | `{}` | Viewport dimensions at capture time (`{ width, height }`) |
438
+ | `metadata` | `jsonb` / `json` | `false` | `{}` | Additional extensible element metadata |
439
+ | `captured_at` | `datetime` | `true` | `nil` | Client timestamp when the element was selected |
440
+ | `created_at` | `datetime` | `false` | — | Creation timestamp |
441
+ | `updated_at` | `datetime` | `false` | — | Last modification timestamp |
442
+
443
+ ---
444
+
445
+ ## 8. Security & Privacy Requirements
446
+
447
+ ### 8.1 DOM Sanitization
448
+ - Mandatory default exclusions: `input[type="password"]`, `input[type="hidden"]`, `[autocomplete^="cc-"]`, `[data-feedback-ignore]`, `[data-feedback-sensitive]`, and `config.excluded_selectors`.
449
+ - Exclusions apply **recursively** to all descendants.
450
+ - Form control `value` properties and complete `outerHTML` are **never** captured by default.
451
+
452
+ ### 8.2 Screenshot Privacy
453
+ - Excluded elements (`[data-feedback-ignore]`, `[data-feedback-sensitive]`, `input[type="password"]`, `config.excluded_selectors`) and cross-origin iframes are masked with an opaque redaction box before screenshot rasterization.
454
+ - ContextDrop's own widget UI is hidden during screenshot capture.
455
+
456
+ ### 8.3 Upload Validation
457
+ The backend validates:
458
+ - Server-side MIME type via binary magic bytes (`Marcel`).
459
+ - File extension matching the detected MIME type.
460
+ - File size (`<= config.max_attachment_size`).
461
+ - Image dimensions (`<= config.max_image_dimensions`).
462
+ - Attachment and element reference counts (`<= config.max_attachments`, `<= config.max_element_references`).
463
+
464
+ ### 8.4 Request & Dashboard Security
465
+ - Respects Rails CSRF protection on all browser mutation endpoints.
466
+ - Applies configurable rate limiting (`429 Too Many Requests`).
467
+ - Enforces `config.authorize_dashboard` on all engine dashboard views and administrative actions.
468
+ - Enforces `config.enabled` across both frontend helpers and backend routes.
469
+
470
+ ---
471
+
472
+ ## 9. Public API & Self-Hosted Dashboard
473
+
474
+ ### 9.1 View Helper
475
+ ```erb
476
+ <%= context_drop_widget %>
477
+ <%= context_drop_widget(position: :bottom_left) %>
478
+ ```
479
+
480
+ ### 9.2 Self-Hosted Feedback Dashboard Views (`app/views/context_drop/`)
481
+ Mounted under `/context_drop/dashboard` (or `/context_drop/feedbacks`) and protected by `config.authorize_dashboard`:
482
+ - **Index View (`GET /context_drop/dashboard/feedbacks`):** Filterable table/list of feedback records by `status` (`new`, `reviewed`, `resolved`, `dismissed`), search query, and originating `page_url`, showing submitter info, timestamp, and counts of attachments and element references.
483
+ - **Detail View (`GET /context_drop/dashboard/feedbacks/:id`):** Displays the full feedback message, page URL, user agent, viewport metadata, inline screenshot/image gallery (via Active Storage representations), structured `ElementReference` cards (`selector`, `dom_path`, `bounding_rect`, `attributes_snapshot`), and status transition buttons (`Mark Reviewed`, `Mark Resolved`, `Dismiss`).
484
+
485
+ ### 9.3 Ruby Model API
486
+ ```ruby
487
+ ContextDrop::Feedback.new_records
488
+ ContextDrop::Feedback.includes(:submitter, :element_references, attachments: { file_attachment: :blob })
489
+ ```
490
+
491
+ ---
492
+
493
+ ## 10. HTTP Endpoints Specification
494
+
495
+ | Method | Endpoint | Purpose | Protection |
496
+ |---|---|---|---|
497
+ | `GET` | `/context_drop/config` | Retrieve public widget configuration | `enabled` + `authorize_submission` |
498
+ | `POST` | `/context_drop/feedback` | Submit feedback, element references, and attachments | `enabled` + `authorize_submission` + CSRF + Rate Limit |
499
+ | `POST` | `/context_drop/attachments` | Pre-upload binary image/screenshot attachment | `enabled` + `authorize_submission` + CSRF + Rate Limit |
500
+ | `GET` | `/context_drop/health` | Engine health check | `enabled` |
501
+ | `GET` | `/context_drop/dashboard/feedbacks` | Self-hosted Feedback Dashboard list view | `enabled` + `authorize_dashboard` |
502
+ | `GET` | `/context_drop/dashboard/feedbacks/:id` | Self-hosted Feedback Dashboard detail view | `enabled` + `authorize_dashboard` |
503
+ | `PATCH` | `/context_drop/dashboard/feedbacks/:id` | Update feedback status from the dashboard | `enabled` + `authorize_dashboard` + CSRF |
504
+ | `POST` | `/context_drop/mcp` | Model Context Protocol (MCP) JSON-RPC 2.0 endpoint | `mcp_enabled` + `mcp_api_key` / `authorize_mcp` |
505
+
506
+ ---
507
+
508
+ ## 11. Model Context Protocol (MCP) Server for IDEs & Coding Agents (Phase 2)
509
+
510
+ ContextDrop provides a built-in MCP server (`ContextDrop::MCP::Server`) supporting:
511
+ 1. **Local `stdio` Transport (`bin/rails context_drop:mcp`):** Spawns locally from the developer's IDE (`Cursor`, `Claude Code`, `Antigravity`, `Windsurf`, `VS Code`) and reads `ContextDrop::Feedback`, `ContextDrop::Attachment`, and `ContextDrop::ElementReference` directly from the local Rails environment.
512
+ 2. **Remote Streamable HTTP Transport (`POST /context_drop/mcp`):** Authenticated via `Authorization: Bearer <CONTEXT_DROP_MCP_API_KEY>` so developers can query staging/production feedback directly from their IDE.
513
+
514
+ **Exposed MCP Tools:**
515
+ - `list_feedbacks`: Filter by `status`, `page_url`, `query` (searches `message`, `selector`, `dom_path`, `text_content`), `limit`, `offset`.
516
+ - `get_feedback`: Returns the full `Feedback`, all `ElementReference` records, and `Attachment` screenshots/images as base64 MCP `image` content blocks.
517
+ - `get_feedback_attachment`: Returns a specific `ContextDrop::Attachment` binary as an MCP `image` content block.
518
+ - `update_feedback_status`: Updates a feedback record's `status` (`new`, `reviewed`, `resolved`, `dismissed`).
519
+
520
+ ---
521
+
522
+ ## 12. MVP Acceptance Criteria
523
+
524
+ 1. Generated and structured as a mountable Rails Engine (`isolate_namespace ContextDrop`) with engine-owned migrations (`context_drop_feedbacks`, `context_drop_attachments`, `context_drop_element_references`).
525
+ 2. `bin/rails generate context_drop:install` installs the initializer, mounts routes, and copies migrations (`rails context_drop:install:migrations`).
526
+ 3. Polymorphic `submitter` (`belongs_to :submitter, polymorphic: true, optional: true`) integrates with any host user model without hard dependencies on `User` or Devise.
527
+ 4. Widget can be enabled/disabled via `CONTEXT_DROP_ENABLED` and rendered via `<%= context_drop_widget %>`.
528
+ 5. Decoupled TypeScript frontend widget uses Shadow DOM isolation and supports text, image uploads, viewport screenshots, element screenshots, and multi-element DOM selection.
529
+ 6. DOM metadata (`ElementReference`) and screenshots enforce recursive privacy exclusions (`[data-feedback-ignore]`, `[data-feedback-sensitive]`, `input[type="password"]`).
530
+ 7. Binary files are stored via the host application's Active Storage (`ContextDrop::Attachment#file`).
531
+ 8. Self-hosted Feedback Dashboard views (`/context_drop/dashboard/feedbacks`) allow authorized users to inspect and triage feedback inside the app.
532
+ 9. Backend enforces CSRF protection, rate limiting, and magic-byte MIME validation.