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
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 & 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 & 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><%= context_drop_widget %></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
|