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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +337 -0
- data/Rakefile +20 -0
- data/SPEC.md +532 -0
- data/app/assets/javascripts/context_drop/context_drop.js +3508 -0
- data/app/assets/stylesheets/context_drop/application.css +2 -0
- data/app/controllers/context_drop/application_controller.rb +114 -0
- data/app/controllers/context_drop/attachments_controller.rb +31 -0
- data/app/controllers/context_drop/configs_controller.rb +20 -0
- data/app/controllers/context_drop/dashboard/feedbacks_controller.rb +107 -0
- data/app/controllers/context_drop/feedbacks_controller.rb +208 -0
- data/app/controllers/context_drop/health_controller.rb +9 -0
- data/app/controllers/context_drop/mcp_controller.rb +67 -0
- data/app/helpers/context_drop/widget_helper.rb +62 -0
- data/app/javascript/context_drop/__tests__/widget.test.ts +319 -0
- data/app/javascript/context_drop/api-client.ts +87 -0
- data/app/javascript/context_drop/element-picker.ts +1004 -0
- data/app/javascript/context_drop/icons.ts +35 -0
- data/app/javascript/context_drop/index.ts +11 -0
- data/app/javascript/context_drop/sanitizer.ts +454 -0
- data/app/javascript/context_drop/screenshot.ts +214 -0
- data/app/javascript/context_drop/styles.ts +690 -0
- data/app/javascript/context_drop/types.ts +110 -0
- data/app/javascript/context_drop/widget.ts +1120 -0
- data/app/models/context_drop/application_record.rb +7 -0
- data/app/models/context_drop/attachment.rb +30 -0
- data/app/models/context_drop/element_reference.rb +47 -0
- data/app/models/context_drop/feedback.rb +94 -0
- data/app/services/context_drop/metadata_sanitizer.rb +215 -0
- data/app/services/context_drop/upload_validator.rb +217 -0
- data/app/views/context_drop/dashboard/feedbacks/index.html.erb +86 -0
- data/app/views/context_drop/dashboard/feedbacks/show.html.erb +121 -0
- data/app/views/layouts/context_drop/application.html.erb +215 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20261109000001_create_context_drop_feedbacks.rb +23 -0
- data/db/migrate/20261109000002_create_context_drop_element_references.rb +29 -0
- data/db/migrate/20261109000003_create_context_drop_attachments.rb +24 -0
- data/lib/context_drop/base64_util.rb +15 -0
- data/lib/context_drop/configuration.rb +155 -0
- data/lib/context_drop/engine.rb +32 -0
- data/lib/context_drop/mcp/dispatcher.rb +197 -0
- data/lib/context_drop/mcp/prompts/investigate_feedback.rb +83 -0
- data/lib/context_drop/mcp/server.rb +25 -0
- data/lib/context_drop/mcp/stdio_runner.rb +62 -0
- data/lib/context_drop/mcp/tools/get_feedback.rb +133 -0
- data/lib/context_drop/mcp/tools/get_feedback_attachment.rb +71 -0
- data/lib/context_drop/mcp/tools/list_feedbacks.rb +127 -0
- data/lib/context_drop/mcp/tools/update_feedback_status.rb +72 -0
- data/lib/context_drop/version.rb +5 -0
- data/lib/context_drop.rb +32 -0
- data/lib/generators/context_drop/install/install_generator.rb +52 -0
- data/lib/generators/context_drop/install/templates/initializer.rb.tt +55 -0
- data/lib/tasks/context_drop_tasks.rake +14 -0
- 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.
|