rails-markup 1.4.6 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71cb0dfae1e50403d787bb64d3760a95181fbf2e4fbd7ce6c57629d226fd2dae
4
- data.tar.gz: ba57fa0fde60fd7b8c314ad63d8b2df16c3580076269392991928e81e3a6d53a
3
+ metadata.gz: 416325893f5bb2e30eb526929e41c2aee847d77e078099f7b1530cc512549666
4
+ data.tar.gz: ee02f54d6d604e37fe2d1d32fd779d5cf1d1a0d677a127b1b6ba22e5cb226b0b
5
5
  SHA512:
6
- metadata.gz: 7bb524122341ef07f5ea882964f931e4bb766e4c493a7fb0493f0a0c1ca7fb793055ad513219df8f13bafc578b2ea5113bba0fe86f3b51eabaa15b4e12b36cb5
7
- data.tar.gz: 7f2f47ab1f1692dcb9ff5127b7691dd5ca2691a1a2944056d6e65bc9f2cf5dd998c2e58554722f23b80df36e64aa486f94b5f746b60e4a95d01e1a3f93898e16
6
+ metadata.gz: e3036a905792755633c1e5bb4f0c65bc7adf8c3d1b8d0b360200e2a959177180986ac85fa86fa20a1840d50b2067709a34d1763c6d1a1ea82793970d479694ff
7
+ data.tar.gz: 183df16f69b58db0ed2c6a3d1654073344c48c442f6fbcf43a8765b2f3c1df0bf907a6c43c488deb8d87ea9534a81f376e2bba1b1c2b97bbc0ae0eb067f6ee3f
data/README.md CHANGED
@@ -2,14 +2,16 @@
2
2
 
3
3
  Point-and-click annotation tool for Rails apps. Click any element, describe what needs to change, and your AI agent reads and acts on your feedback via MCP.
4
4
 
5
+ Built with love in Sydney by [Nauman Tariq](https://nauman.one) and Pavelabs.
6
+
5
7
  **v1.0** — Author attribution, export, search, screenshots with drawing tools, kanban board, notification hooks.
6
8
 
7
9
  ## How it works
8
10
 
9
11
  1. **Install the gem** — adds migration, toolbar, auth controller, and routes
10
12
  2. **Open your app** — the annotation toolbar appears as a floating button
11
- 3. **Click any element** — hover to highlight, click to annotate with screenshots
12
- 4. **Draw on screenshots** — arrows, rectangles, highlights on captured elements
13
+ 3. **Click any element** — hover to highlight, click to annotate, optionally attach a screenshot
14
+ 4. **Draw on screenshots** — arrows, rectangles, highlights on attached images
13
15
  5. **Agent reads it** — AI calls `rails_markup_read` via MCP
14
16
  6. **Agent fixes it** — resolves the annotation and moves on
15
17
 
@@ -17,7 +19,7 @@ Point-and-click annotation tool for Rails apps. Click any element, describe what
17
19
 
18
20
  ```ruby
19
21
  # Gemfile
20
- gem "rails-markup", github: "nauman/rails-markup", require: "rails_markup"
22
+ gem "rails-markup", require: "rails_markup"
21
23
  ```
22
24
 
23
25
  ```bash
@@ -81,14 +83,14 @@ The generator creates:
81
83
  | `config/initializers/rails_markup.rb` | Configuration |
82
84
  | `app/controllers/rails_markup_auth_controller.rb` | Auth controller |
83
85
  | `bin/markup` | CLI wrapper |
84
- | Route mount | Engine at `/admin/annotations` |
86
+ | Route mount | Engine at `/admin/rails-markup` |
85
87
  | Toolbar injection | Admin-gated `render "rails_markup/shared/toolbar"` in your layout |
86
88
 
87
89
  ### Generator options
88
90
 
89
91
  ```bash
90
92
  rails generate rails_markup:install \
91
- --mount-path=/admin/annotations \
93
+ --mount-path=/admin/rails-markup \
92
94
  --base-controller=Admin::BaseController \
93
95
  --layout=application
94
96
  ```
@@ -156,7 +158,7 @@ RailsMarkup.configure do |config|
156
158
  config.toolbar_position = "bl"
157
159
  config.toolbar_size = "default"
158
160
 
159
- # Element screenshots (default: true)
161
+ # Optional image attachments (default: true)
160
162
  config.enable_screenshots = true
161
163
 
162
164
  # Dashboard pagination (default: 25)
@@ -169,12 +171,12 @@ end
169
171
 
170
172
  ## Dashboard
171
173
 
172
- Visit `/admin/annotations` for the full dashboard:
174
+ Visit `/admin/rails-markup` for the full dashboard:
173
175
 
174
176
  - **List view** — status filters, search, author filter, load-more pagination
175
177
  - **Board view** — kanban with drag-and-drop status transitions
176
178
  - **Export** — CSV and JSON downloads (respects current filters)
177
- - **Detail panel** — full content, metadata, screenshots, thread
179
+ - **Shared feedback modal** — list and board open the same full content, context, images and thread; Escape, Close or the backdrop returns to your current view
178
180
  - **Bulk actions** — dismiss all pending/acknowledged
179
181
 
180
182
  ### Search & Filters
@@ -184,15 +186,19 @@ Visit `/admin/annotations` for the full dashboard:
184
186
  - **Page URL filter** — filter by annotated page
185
187
  - **Status pills** — pending, acknowledged, resolved, dismissed
186
188
 
189
+ Open **Options** for page/author filters, exports and the **Floating toolbar** switch. This browser preference hides or shows the entire floating control group and syncs across open tabs. Board help lives under the info icon; each card's **Move** disclosure provides a touch-friendly alternative to dragging.
190
+
187
191
  ## Screenshots & Drawing
188
192
 
189
- When you click an element, the toolbar captures a screenshot using SVG foreignObject. Before submitting, you can draw on it:
193
+ After selecting an element, choose **Capture tab** to use the browser screen-sharing picker, **Upload image** (PNG, JPEG, or WebP under 5 MB), or paste an image into the feedback field. Native capture requires browser support and permission each time; the video stream stops immediately after a single frame is captured. No image is submitted until you submit the feedback. Preview or remove an attachment before submitting, and draw on it:
190
194
 
191
195
  - **Arrow** — click and drag to draw red arrows
192
196
  - **Rectangle** — click and drag for red outline boxes
193
197
  - **Highlight** — freehand semi-transparent yellow strokes
194
198
  - **Undo** — remove the last drawing operation
195
199
 
200
+ The engine dashboard’s **Floating toolbar** switch shows or hides the entire dock for this browser, including across open tabs. Pins and synchronization remain active.
201
+
196
202
  Screenshots are stored as base64 in annotation metadata and displayed in the dashboard detail view.
197
203
 
198
204
  ## CLI
@@ -320,9 +326,30 @@ Browser Toolbar AI Agent (Claude Code, Cursor)
320
326
  ```bash
321
327
  git clone https://github.com/nauman/rails-markup
322
328
  cd rails-markup && bundle install
323
- bundle exec ruby -Ilib:test test/**/*_test.rb
329
+ bundle exec rake test
330
+ node --test test/javascript/*_test.mjs
331
+ bundle exec rake test:system # requires Chrome
332
+ ```
333
+
334
+ ### Local demo
335
+
336
+ Run the included host app with the gem loaded from this checkout:
337
+
338
+ ```bash
339
+ bundle exec ruby script/demo
324
340
  ```
325
341
 
342
+ Open [the demo login](http://127.0.0.1:4317/rails_markup_test_session/new) and
343
+ click **Authenticate**, then visit [the host page](http://127.0.0.1:4317/host).
344
+ Click the floating button once to expand the tools, click it again to annotate,
345
+ then click an element and add feedback. The same annotation appears in
346
+ [the dashboard](http://127.0.0.1:4317/feedback); resolve it there and reload the
347
+ host page to see the updated status and thread.
348
+
349
+ The demo binds to loopback, uses a test-only login, enables CSRF protection,
350
+ and keeps its SQLite database in `tmp/demo/annotations.sqlite3`. Set `PORT` to
351
+ change the port. Stop with Ctrl-C; remove that database to reset demo data.
352
+
326
353
  ## License
327
354
 
328
355
  MIT. See [LICENSE](LICENSE).
@@ -9,83 +9,125 @@
9
9
  @keyframes rm-toast-out { from{opacity:1;transform:translateY(0) scale(1)} to{opacity:0;transform:translateY(16px) scale(0.95)} }
10
10
  #rm-toolbar-root { position:fixed; inset:0; pointer-events:none; z-index:9979; }
11
11
  #rm-toolbar-root * { box-sizing:border-box; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif; }
12
- .rm-fab, .rm-panel-toggle, .rm-toolbar-settings, .rm-panel, .rm-popup { pointer-events:auto; }
13
- .rm-fab { position:fixed; z-index:9980; border-radius:50%; border:none; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all 0.2s; box-shadow:0 4px 12px rgba(0,0,0,0.15); }
14
- .rm-fab svg { width:20px; height:20px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
15
- .rm-fab-badge { position:absolute; top:-4px; right:-4px; min-width:20px; height:20px; padding:0 4px; border-radius:10px; background:#ef4444; color:#fff; font-size:11px; font-weight:700; display:none; align-items:center; justify-content:center; }
16
- .rm-panel-toggle { position:fixed; z-index:9980; width:32px; height:32px; border-radius:50%; border:1px solid #e5e7eb; background:rgba(255,255,255,0.9); cursor:pointer; display:none; align-items:center; justify-content:center; color:#6b7280; transition:all 0.2s; backdrop-filter:blur(8px); }
17
- .rm-panel-toggle:hover { color:#4361ee; }
18
- .rm-panel-toggle svg { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
19
- .rm-panel-toggle-badge { position:absolute; top:-4px; right:-4px; min-width:18px; height:18px; padding:0 4px; border-radius:9px; background:#ef4444; color:#fff; font-size:10px; font-weight:700; display:none; align-items:center; justify-content:center; }
20
- .rm-toolbar-settings { position:fixed; z-index:9980; width:32px; height:32px; border-radius:50%; border:1px solid #e5e7eb; background:#fff; cursor:pointer; display:none; align-items:center; justify-content:center; color:#6b7280; transition:all 0.2s; backdrop-filter:blur(8px); }
21
- .rm-toolbar-settings:hover { color:#374151; background:#f9fafb; }
22
- .rm-toolbar-settings svg { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
23
- .rm-toast-container { position:fixed; z-index:9983; display:flex; flex-direction:column; gap:8px; pointer-events:none; }
24
- .rm-pins-container { position:absolute; top:0; left:0; width:100%; z-index:9979; pointer-events:none; }
25
- .rm-pin { pointer-events:auto; }
26
- .rm-popup { display:none; position:fixed; z-index:9982; width:360px; max-width:calc(100vw - 24px); background:rgba(255,255,255,0.95); backdrop-filter:blur(12px); border-radius:16px; box-shadow:0 25px 50px rgba(0,0,0,0.1); border:1px solid rgba(229,231,235,0.8); padding:16px; }
12
+ #rm-toolbar-root .rm-dock, #rm-toolbar-root .rm-panel, #rm-toolbar-root .rm-popup { pointer-events:auto; }
13
+ #rm-toolbar-root .rm-dock { position:fixed; z-index:9980; display:flex; align-items:center; padding:4px; border:1px solid var(--rm-accent-hover); border-radius:14px; background:var(--rm-accent); color:#fff; box-shadow:0 6px 20px #0003,0 1px 3px #0002; }
14
+ #rm-toolbar-root .rm-dock button { position:static; display:flex; align-items:center; justify-content:center; gap:6px; flex-shrink:0; min-width:var(--rm-control-size); height:var(--rm-control-size); margin:0; padding:0 10px; border:0; border-radius:10px; background:transparent; color:#e5e7eb; cursor:pointer; box-shadow:none; appearance:none; transition:background .15s,color .15s; }
15
+ #rm-toolbar-root .rm-dock button:hover { background:#ffffff18; color:#fff; }
16
+ #rm-toolbar-root .rm-dock button:focus-visible { outline:2px solid #fff; outline-offset:2px; }
17
+ #rm-toolbar-root .rm-dock button.rm-fab { background:var(--rm-accent); color:#fff; }
18
+ #rm-toolbar-root .rm-dock button.rm-fab:hover { background:var(--rm-accent-hover); }
19
+ #rm-toolbar-root .rm-dock[data-state="annotating"] button.rm-fab { box-shadow:inset 0 0 0 2px #ffffff80; }
20
+ #rm-toolbar-root .rm-dock svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
21
+ #rm-toolbar-root .rm-dock-controls { display:flex; align-items:center; gap:2px; margin-left:4px; padding-left:4px; border-left:1px solid #ffffff26; }
22
+ #rm-toolbar-root .rm-dock-controls[hidden] { display:none; }
23
+ #rm-toolbar-root .rm-panel-toggle { order:0; }
24
+ #rm-toolbar-root .rm-toolbar-settings { order:1; }
25
+ #rm-toolbar-root .rm-dock-close { order:2; }
26
+ #rm-toolbar-root .rm-panel-toggle-badge { font-size:11px; font-weight:600; font-variant-numeric:tabular-nums; color:#e5e7eb; }
27
+ @media (prefers-reduced-motion:reduce) { #rm-toolbar-root .rm-dock button { transition:none; } }
28
+ #rm-toolbar-root .rm-toast-container { position:fixed; z-index:9983; display:flex; flex-direction:column; gap:8px; pointer-events:none; }
29
+ #rm-pins-container { position:absolute; top:0; left:0; width:100%; z-index:9979; pointer-events:none; }
30
+ #rm-pins-container .rm-pin { pointer-events:auto; }
31
+ #rm-toolbar-root .rm-popup { max-height:calc(100vh - 24px); overflow-y:auto; display:none; position:fixed; z-index:9982; width:360px; max-width:calc(100vw - 24px); background:#fff; border-radius:16px; box-shadow:0 18px 48px #18213824,0 2px 6px #1821380a; border:1px solid #dce1e8; padding:16px; }
27
32
  #rm-toolbar-root .rm-popup textarea { display:block; width:100%; max-width:100%; font-size:13px; font-weight:400; line-height:1.4; color:#1f2937; height:auto; margin:0; border:1px solid #e5e7eb; border-radius:12px; padding:12px; resize:none; outline:none; font-family:inherit; background:#fff; background-image:none; box-shadow:none; appearance:none; -webkit-appearance:none; transition:border-color 0.15s,box-shadow 0.15s; }
28
33
  #rm-toolbar-root .rm-popup textarea:focus { border:1px solid #818cf8; box-shadow:0 0 0 3px rgba(99,102,241,0.1); }
29
34
  #rm-toolbar-root .rm-menu { position:relative; display:inline-block; vertical-align:middle; }
30
- #rm-toolbar-root .rm-menu-btn { display:inline-flex; align-items:center; gap:4px; width:auto; height:auto; margin:0; font-size:11px; font-weight:500; line-height:1.4; color:#374151; border:1px solid #e5e7eb; border-radius:8px; padding:6px 8px; background:#fff; background-image:none; box-shadow:none; outline:none; text-transform:none; appearance:none; -webkit-appearance:none; cursor:pointer; }
35
+ #rm-toolbar-root .rm-menu-btn { min-height:32px; display:inline-flex; align-items:center; gap:4px; width:auto; height:auto; margin:0; font-size:11px; font-weight:500; line-height:1.4; color:#374151; border:1px solid #e5e7eb; border-radius:8px; padding:6px 8px; background:#fff; background-image:none; box-shadow:none; outline:none; text-transform:none; appearance:none; -webkit-appearance:none; cursor:pointer; }
31
36
  #rm-toolbar-root .rm-menu-btn:hover { border-color:#d1d5db; background:#fff; }
32
37
  #rm-toolbar-root .rm-menu-btn:focus { outline:none; border-color:#818cf8; box-shadow:0 0 0 3px rgba(99,102,241,0.1); }
33
38
  #rm-toolbar-root .rm-menu-btn[aria-expanded="true"] { border-color:#818cf8; }
34
39
  #rm-toolbar-root .rm-menu-chevron { width:10px; height:10px; flex-shrink:0; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; opacity:0.55; }
35
40
  #rm-toolbar-root .rm-menu-list { display:none; position:absolute; top:calc(100% + 4px); left:0; z-index:9984; min-width:100%; padding:4px; margin:0; list-style:none; background:#fff; border:1px solid #e5e7eb; border-radius:10px; box-shadow:0 10px 24px rgba(0,0,0,0.12); }
36
41
  #rm-toolbar-root .rm-menu-list.rm-menu-open { display:block; }
37
- #rm-toolbar-root .rm-menu-option { display:block; width:100%; margin:0; padding:6px 10px; font-size:11px; font-weight:500; line-height:1.4; color:#374151; text-align:left; border:none; border-radius:6px; background:transparent; background-image:none; box-shadow:none; cursor:pointer; appearance:none; -webkit-appearance:none; }
42
+ #rm-toolbar-root .rm-menu-option { min-height:32px; display:block; width:100%; margin:0; padding:6px 10px; font-size:11px; font-weight:500; line-height:1.4; color:#374151; text-align:left; border:none; border-radius:6px; background:transparent; background-image:none; box-shadow:none; cursor:pointer; appearance:none; -webkit-appearance:none; }
38
43
  #rm-toolbar-root .rm-menu-option:hover, #rm-toolbar-root .rm-menu-option:focus { background:#f3f4f6; outline:none; }
39
44
  #rm-toolbar-root .rm-menu-option-active { background:#eef2ff; color:#4338ca; }
40
- #rm-toolbar-root .rm-menu-compact .rm-menu-btn { font-size:10px; padding:2px 6px; border-radius:4px; color:#6b7280; }
45
+ #rm-toolbar-root .rm-menu-compact .rm-menu-btn { font-size:11px; min-height:28px; padding:4px 7px; border-radius:4px; color:#6b7280; }
41
46
  #rm-toolbar-root .rm-menu-compact .rm-menu-list, #rm-toolbar-root .rm-menu-list-compact { min-width:120px; right:0; left:auto; }
42
47
  #rm-toolbar-root .rm-menu-compact .rm-menu-option, #rm-toolbar-root .rm-menu-list-compact .rm-menu-option { font-size:10px; padding:5px 8px; }
43
- .rm-popup-el { font-size:11px; color:#9ca3af; font-family:monospace; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; line-height:1.4; }
44
- .rm-popup-text { font-size:12px; color:#6b7280; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; line-height:1.4; }
45
- .rm-popup-actions { display:flex; align-items:center; gap:8px; margin-top:8px; }
46
- .rm-popup-actions .rm-count { font-size:10px; color:#d1d5db; margin-left:auto; font-variant-numeric:tabular-nums; }
47
- #rm-toolbar-root .rm-btn-cancel { padding:6px 12px; font-size:12px; color:#9ca3af; background:none; background-image:none; border:none; box-shadow:none; cursor:pointer; border-radius:8px; appearance:none; -webkit-appearance:none; }
48
+ #rm-toolbar-root .rm-popup-el { font-size:11px; color:#596579; font-family:monospace; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; line-height:1.4; }
49
+ #rm-toolbar-root .rm-popup-text { font-size:12px; color:#6b7280; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; line-height:1.4; }
50
+ #rm-toolbar-root .rm-popup-actions { display:flex; justify-content:flex-end; align-items:center; gap:8px; margin-top:8px; }
51
+ #rm-toolbar-root .rm-popup-actions .rm-count { font-size:11px; color:#596579; margin-left:auto; font-variant-numeric:tabular-nums; }
52
+ #rm-toolbar-root .rm-btn-cancel { min-height:36px; padding:8px 12px; font-size:12px; color:#596579; background:none; background-image:none; border:none; box-shadow:none; cursor:pointer; border-radius:8px; appearance:none; -webkit-appearance:none; }
48
53
  #rm-toolbar-root .rm-btn-cancel:hover { color:#6b7280; }
49
- #rm-toolbar-root .rm-btn-submit { padding:6px 16px; font-size:12px; font-weight:500; color:#fff; border:none; border-radius:8px; cursor:pointer; display:inline-flex; align-items:center; gap:6px; box-shadow:none; appearance:none; -webkit-appearance:none; }
54
+ #rm-toolbar-root .rm-btn-submit { min-height:36px; padding:8px 16px; font-size:12px; font-weight:500; color:#fff; border:none; border-radius:8px; cursor:pointer; display:inline-flex; align-items:center; gap:6px; box-shadow:none; appearance:none; -webkit-appearance:none; }
50
55
  #rm-toolbar-root .rm-btn-submit kbd { font-size:9px; opacity:0.6; font-family:sans-serif; }
51
- .rm-panel { display:none; position:fixed; z-index:9981; width:380px; max-width:calc(100vw - 48px); max-height:60vh; background:rgba(255,255,255,0.95); backdrop-filter:blur(12px); border-radius:16px; box-shadow:0 25px 50px rgba(0,0,0,0.1); border:1px solid rgba(229,231,235,0.8); flex-direction:column; }
52
- .rm-panel-header { display:flex; align-items:center; justify-content:space-between; padding:12px 16px; border-bottom:1px solid #f3f4f6; }
53
- .rm-panel-header h3 { font-size:14px; font-weight:600; color:#1f2937; }
54
- .rm-panel-header-actions { display:flex; align-items:center; gap:6px; }
55
- .rm-panel-count { min-width:20px; text-align:center; padding:2px 6px; font-size:10px; font-weight:600; border-radius:10px; }
56
- .rm-settings { padding:12px 16px 4px; border-bottom:1px solid #f3f4f6; }
57
- .rm-settings[hidden] { display:none; }
58
- .rm-settings-group { display:flex; flex-direction:column; gap:6px; margin-bottom:10px; }
59
- .rm-settings-label { font-size:11px; font-weight:600; color:#6b7280; text-transform:uppercase; letter-spacing:0.05em; }
60
- .rm-settings-options { display:flex; flex-wrap:wrap; gap:6px; }
61
- .rm-panel-close { padding:6px; color:#d1d5db; background:none; border:none; cursor:pointer; border-radius:8px; }
62
- .rm-panel-close:hover { color:#6b7280; }
63
- .rm-panel-close svg { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
64
- .rm-filter-chips { display:flex; gap:6px; padding:8px 16px; border-bottom:1px solid #f9fafb; }
65
- .rm-chip { padding:4px 8px; font-size:10px; font-weight:500; border-radius:10px; cursor:pointer; transition:all 0.15s; border:none; }
66
- .rm-chip-active { color:#fff; }
67
- .rm-chip-inactive { background:#f9fafb; color:#9ca3af; }
68
- .rm-chip-inactive:hover { color:#6b7280; }
69
- .rm-panel-list { flex:1; overflow-y:auto; padding:12px; display:flex; flex-direction:column; gap:8px; }
70
- .rm-panel-footer { display:flex; align-items:center; gap:8px; padding:10px 16px; border-top:1px solid #f3f4f6; font-size:11px; color:#9ca3af; }
71
- .rm-status-dot { width:8px; height:8px; border-radius:50%; background:#d1d5db; }
72
- .rm-card { padding:12px; background:#fff; border-radius:8px; border:1px solid #f3f4f6; border-left:3px solid; cursor:pointer; transition:all 0.15s; }
73
- .rm-card:hover { box-shadow:0 2px 8px rgba(0,0,0,0.05); }
74
- .rm-card-top { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
75
- .rm-card-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
76
- .rm-card-id { font-size:10px; font-weight:600; color:#9ca3af; }
77
- .rm-card-badge { padding:2px 6px; font-size:10px; font-weight:500; border-radius:10px; }
78
- .rm-card-body { margin-top:6px; font-size:13px; line-height:1.5; color:#1f2937; }
79
- .rm-card-path { margin-top:4px; font-size:10px; color:#d1d5db; font-family:monospace; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
80
- .rm-card-thread { margin-top:8px; padding:8px; background:rgba(249,250,251,0.8); border-radius:8px; font-size:12px; color:#6b7280; border-left:2px solid; }
81
- .rm-card-thread-role { font-size:10px; font-weight:500; color:#9ca3af; text-transform:uppercase; letter-spacing:0.05em; }
82
- .rm-empty { text-align:center; padding:32px 16px; color:#9ca3af; }
83
- .rm-empty-icon { font-size:32px; margin-bottom:8px; }
84
- .rm-empty-text { font-size:13px; }
85
- .rm-pin { position:absolute; display:flex; align-items:center; justify-content:center; width:20px; height:20px; border-radius:50%; color:#fff; font-size:10px; font-weight:700; cursor:pointer; transition:transform 0.2s; z-index:9979; box-shadow:0 2px 8px rgba(0,0,0,0.2); }
86
- .rm-pin:hover { transform:scale(1.25); }
87
- .rm-pin-active { animation:rm-pulse 2s ease-in-out infinite; }
88
- .rm-toast { padding:8px 12px; border-radius:8px; border:1px solid; font-size:12px; font-weight:500; box-shadow:0 2px 8px rgba(0,0,0,0.05); animation:rm-toast-in 0.3s ease; }
56
+ #rm-toolbar-root .rm-panel { display:none; position:fixed; z-index:9981; width:380px; max-width:calc(100vw - 48px); max-height: min(560px, calc(100vh - 100px)); overflow:hidden; background:#fff; border-radius:16px; box-shadow:0 18px 48px #18213824,0 2px 6px #1821380a; border:1px solid #dce1e8; flex-direction:column; }
57
+ #rm-toolbar-root .rm-panel-header { display:flex; align-items:center; justify-content:space-between; padding:16px; border-bottom:1px solid #e5e7eb; }
58
+ #rm-toolbar-root .rm-panel-header h3 { margin:0; font-size:15px; font-weight:600; color:#1f2937; }
59
+ #rm-toolbar-root .rm-panel-header-actions { display:flex; align-items:center; gap:6px; }
60
+ #rm-toolbar-root .rm-panel-count { min-width:20px; text-align:center; padding:2px 6px; font-size:10px; font-weight:600; border-radius:10px; }
61
+ #rm-toolbar-root .rm-settings { padding:16px; overflow-y:auto; }
62
+ #rm-toolbar-root .rm-settings[hidden] { display:none; }
63
+ #rm-toolbar-root .rm-image-options { margin:8px 0; }
64
+ #rm-toolbar-root .rm-image-options:not([open]) > :not(summary) { display:none; }
65
+ #rm-toolbar-root .rm-image-options[hidden] { display:none; }
66
+ #rm-toolbar-root .rm-image-options > summary { cursor:pointer;color:#64748b;font-size:12px;padding:5px 0; }
67
+ #rm-toolbar-root .rm-screenshot-actions { display:flex;flex-wrap:wrap;gap:8px;margin:12px 0; }
68
+ #rm-toolbar-root .rm-screenshot-actions[hidden], #rm-toolbar-root .rm-screenshot-actions [hidden] { display:none; }
69
+ #rm-toolbar-root .rm-screenshot-actions button { border:1px solid #dce1e8;border-radius:7px;padding:7px 10px;background:#fff;color:#334155;font-size:12px;cursor:pointer; }
70
+ #rm-toolbar-root .rm-drawing-tools button { border:1px solid #dce1e8;border-radius:7px;background:#fff;color:#334155;cursor:pointer; }
71
+ #rm-toolbar-root .rm-screenshot-actions span { flex-basis:100%;font-size:11px;line-height:1.5;color:#596579; }
72
+
73
+ #rm-toolbar-root .rm-panel[data-view="settings"] .rm-panel-count,
74
+ #rm-toolbar-root .rm-panel[data-view="settings"] .rm-filter-chips,
75
+ #rm-toolbar-root .rm-panel[data-view="settings"] .rm-panel-list,
76
+ #rm-toolbar-root .rm-panel[data-view="settings"] .rm-panel-footer { display:none; }
77
+ #rm-toolbar-root .rm-setting-choice { border:1px solid #e2e6ec; border-radius:8px; padding:9px 12px; background:#fff; color:#475569; font-size:12px; cursor:pointer; }
78
+ #rm-toolbar-root .rm-setting-choice[aria-pressed="true"] { background:#eef1f5; border-color:#8995a5; color:#172033; box-shadow:inset 0 0 0 1px #8995a5; }
79
+ #rm-toolbar-root .rm-color-swatch { width:36px; height:36px; padding:0; border:3px solid #fff; border-radius:50%; color:#fff; font-size:17px; }
80
+ #rm-toolbar-root .rm-color-swatch[aria-pressed="true"] { color:#fff; box-shadow:0 0 0 2px #64748b; border-color:#fff; }
81
+ #rm-toolbar-root .rm-corner-choice { font-size:24px; width:54px; padding:4px; }
82
+ #rm-toolbar-root .rm-settings-group.rm-settings-switch-row { flex-direction:row; align-items:center; justify-content:space-between; padding-top:16px; border-top:1px solid #eef1f5; }
83
+ #rm-toolbar-root .rm-setting-switch { width:38px; height:22px; padding:3px; border:0; border-radius:20px; background:#94a3b8; cursor:pointer; }
84
+ #rm-toolbar-root .rm-setting-switch span { display:block; width:16px; height:16px; background:#fff; border-radius:50%; }
85
+ #rm-toolbar-root .rm-setting-switch[aria-checked="true"] { background:#475569; }
86
+ #rm-toolbar-root .rm-setting-switch[aria-checked="true"] span { transform:translateX(16px); }
87
+ #rm-toolbar-root .rm-setting-choice:focus-visible, #rm-toolbar-root .rm-setting-switch:focus-visible { outline:2px solid #475569; outline-offset:3px; }
88
+
89
+ #rm-toolbar-root .rm-settings-group { display:flex; flex-direction:column; gap:8px; margin-bottom:14px; }
90
+ #rm-toolbar-root .rm-settings-label { font-size:12px; font-weight:600; color:#516074; }
91
+ #rm-toolbar-root .rm-settings-options { display:flex; flex-wrap:wrap; gap:6px; }
92
+ #rm-toolbar-root .rm-panel-close { min-width:32px; min-height:32px; padding:8px; color:#596579; background:none; border:none; cursor:pointer; border-radius:8px; }
93
+ #rm-toolbar-root .rm-panel-close:hover { color:#6b7280; }
94
+ #rm-toolbar-root .rm-panel-close svg { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
95
+ #rm-toolbar-root .rm-filter-chips { display:flex; gap:6px; padding:10px 16px; border-bottom:1px solid #f9fafb; }
96
+ #rm-toolbar-root .rm-chip { min-height:32px; padding:6px 10px; font-size:12px; font-weight:500; border-radius:10px; cursor:pointer; transition:all 0.15s; border:none; }
97
+ #rm-toolbar-root .rm-chip-active { color:#fff; }
98
+ #rm-toolbar-root .rm-chip-inactive { background:#f9fafb; color:#596579; }
99
+ #rm-toolbar-root .rm-chip-inactive:hover { color:#6b7280; }
100
+ #rm-toolbar-root .rm-panel-list { flex:1; min-height:0; overflow-y:auto; padding:12px; display:flex; flex-direction:column; gap:8px; }
101
+ #rm-toolbar-root .rm-panel-footer { display:flex; align-items:center; gap:8px; padding:10px 16px; border-top:1px solid #f3f4f6; font-size:11px; color:#596579; }
102
+ #rm-toolbar-root .rm-status-dot { width:8px; height:8px; border-radius:50%; background:#d1d5db; }
103
+ #rm-toolbar-root .rm-card { padding:12px; background:#fff; border-radius:8px; border:1px solid #e5e7eb; border-left:3px solid; cursor:pointer; transition:all 0.15s; }
104
+ #rm-toolbar-root .rm-card:hover { box-shadow:0 2px 8px rgba(0,0,0,0.05); }
105
+ #rm-toolbar-root .rm-card-top { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
106
+ #rm-toolbar-root .rm-card-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
107
+ #rm-toolbar-root .rm-card-id { font-size:10px; font-weight:600; color:#596579; }
108
+ #rm-toolbar-root .rm-card-badge { padding:2px 6px; font-size:10px; font-weight:500; border-radius:10px; }
109
+ #rm-toolbar-root .rm-card-body { margin-top:6px; font-size:13px; line-height:1.5; color:#1f2937; }
110
+ #rm-toolbar-root .rm-card-path { margin-top:4px; font-size:11px; color:#596579; font-family:monospace; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
111
+ #rm-toolbar-root .rm-card-thread { margin-top:8px; padding:8px; background:rgba(249,250,251,0.8); border-radius:8px; font-size:12px; color:#6b7280; border-left:2px solid; }
112
+ #rm-toolbar-root .rm-card-thread-role { font-size:11px; font-weight:600; color:#596579; }
113
+ #rm-toolbar-root .rm-empty { text-align:center; padding:32px 16px; color:#596579; }
114
+ #rm-toolbar-root .rm-empty-icon { font-size:32px; margin-bottom:8px; }
115
+ #rm-toolbar-root .rm-empty-text { font-size:13px; }
116
+ #rm-pins-container .rm-pin { position:absolute; display:flex; align-items:center; justify-content:center; width:20px; height:20px; border-radius:50%; color:#fff; font-size:10px; font-weight:700; cursor:pointer; transition:transform 0.2s; z-index:9979; box-shadow:0 2px 8px rgba(0,0,0,0.2); }
117
+ #rm-pins-container .rm-pin:hover { transform:scale(1.25); }
118
+ #rm-pins-container .rm-pin-active { animation:rm-pulse 2s ease-in-out infinite; }
119
+ #rm-toolbar-root .rm-toast { padding:8px 12px; border-radius:8px; border:1px solid; font-size:12px; font-weight:500; box-shadow:0 2px 8px rgba(0,0,0,0.05); animation:rm-toast-in 0.3s ease; }
120
+ #rm-toolbar-root button, #rm-toolbar-root textarea { font-family:inherit; text-transform:none; letter-spacing:normal; }
121
+ #rm-toolbar-root button:focus-visible { outline:2px solid #6366f1; outline-offset:2px; }
122
+ #rm-toolbar-root .rm-popup-heading { margin:0 0 12px; color:#25272d; font-size:15px; font-weight:650; line-height:1.4; }
123
+ #rm-toolbar-root .rm-popup-el { color:#516074; }
124
+ #rm-toolbar-root #rm-char-count { margin-left:auto; color:#596579; font-size:11px; font-variant-numeric:tabular-nums; }
125
+ #rm-pins-container .rm-pin { font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
126
+ #rm-toolbar-root .rm-card-body { overflow-wrap:anywhere; }
127
+ #rm-toolbar-root .rm-popup textarea { line-height:1.6; border-radius:8px; }
128
+ #rm-toolbar-root .rm-empty { color:#596579; line-height:1.6; }
129
+ @media (pointer:coarse) { #rm-toolbar-root .rm-dock button, #rm-toolbar-root .rm-chip, #rm-toolbar-root .rm-menu-btn, #rm-toolbar-root .rm-menu-option, #rm-toolbar-root .rm-panel-close { min-height:44px; min-width:44px; } }
130
+ @media (prefers-reduced-motion:reduce) { #rm-pins-container .rm-pin, #rm-toolbar-root .rm-toast { animation:none; transition:none; } }
89
131
  `;
90
132
  document.head.appendChild(style);
91
133
  },
@@ -16,43 +16,55 @@
16
16
  const sizeMap = { "default": { dim: 40, icon: 18 }, compact: { dim: 36, icon: 16 }, slim: { dim: 32, icon: 16 } };
17
17
  const fabSize = sizeMap[this.size] || sizeMap["default"];
18
18
 
19
- const controlSize = 32;
20
- const controlGap = 8;
21
- const controlStart = this.fabVisible ? fabSize.dim + controlGap + 24 : 24;
22
19
  const isRight = this.position === "br" || this.position === "tr";
23
20
  const isTop = this.position === "tl" || this.position === "tr";
24
- const settingsToggleStyle = isRight ? `${isTop ? 'top' : 'bottom'}:24px;right:${controlStart}px;` : `${isTop ? 'top' : 'bottom'}:24px;left:${controlStart}px;`;
25
- const panelToggleStyle = isRight ? `${isTop ? 'top' : 'bottom'}:24px;right:${controlStart + controlSize + controlGap}px;` : `${isTop ? 'top' : 'bottom'}:24px;left:${controlStart + controlSize + controlGap}px;`;
26
21
  const panelStyle = isRight
27
- ? `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 32}px;right:24px;`
28
- : `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 32}px;left:24px;`;
22
+ ? `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 42}px;right:24px;`
23
+ : `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 42}px;left:24px;`;
29
24
  const toastStyle = isRight
30
- ? `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 32}px;right:24px;`
31
- : `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 32}px;left:24px;`;
25
+ ? `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 42}px;right:24px;`
26
+ : `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 42}px;left:24px;`;
32
27
 
33
28
  root.innerHTML = `
34
- <button class="rm-fab" id="rm-fab" style="${fabPos}width:${fabSize.dim}px;height:${fabSize.dim}px;background:${accentBg};color:#fff;${this.fabVisible ? "" : "display:none;"}" title="Toggle annotation mode" aria-label="Toggle annotation mode" aria-expanded="false" aria-controls="rm-panel">
35
- <svg viewBox="0 0 24 24" style="width:${fabSize.icon}px;height:${fabSize.icon}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
29
+ <div class="rm-dock" id="rm-dock" role="group" aria-label="Markup tools" style="${fabPos}--rm-accent:${accentBg};--rm-accent-hover:${accentBgHover};--rm-control-size:${fabSize.dim}px">
30
+ <button type="button" class="rm-fab" id="rm-fab" style="width:${fabSize.dim}px;height:${fabSize.dim}px;${this.fabVisible ? "" : "display:none;"}" title="Expand markup tools" aria-label="Expand markup tools" aria-expanded="false" aria-controls="rm-dock-controls" aria-pressed="false">
31
+ <svg viewBox="0 0 24 24" style="width:${fabSize.icon}px;height:${fabSize.icon}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;"><path d="M16 3l5 5M4 15L16 3l5 5L9 20l-6 1 1-6z"/></svg>
36
32
  </button>
37
- <button class="rm-toolbar-settings" id="rm-settings-toggle" style="${settingsToggleStyle}" title="Toolbar settings" aria-label="Toolbar settings" aria-expanded="false" aria-controls="rm-settings-panel">
38
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 8a4 4 0 100 8 4 4 0 000-8zm8.94 3.06l-1.3-.75.08-1.36a1 1 0 00-.3-.79l-1.72-1.72a1 1 0 00-.79-.3l-1.36.08-.75-1.3a1 1 0 00-.86-.5h-2.44a1 1 0 00-.86.5l-.75 1.3-1.36-.08a1 1 0 00-.79.3L4.58 8.86a1 1 0 00-.3.79l.08 1.36-1.3.75a1 1 0 00-.5.86v2.44a1 1 0 00.5.86l1.3.75-.08 1.36a1 1 0 00.3.79l1.72 1.72a1 1 0 00.79.3l1.36-.08.75 1.3a1 1 0 00.86.5h2.44a1 1 0 00.86-.5l.75-1.3 1.36.08a1 1 0 00.79-.3l1.72-1.72a1 1 0 00.3-.79l-.08-1.36 1.3-.75a1 1 0 00.5-.86v-2.44a1 1 0 00-.5-.86zm-2.24 1.44l-1.14.66a1 1 0 00-.5.86l.08 1.2-.98.98-1.2-.08a1 1 0 00-.86.5l-.66 1.14h-1.96l-.66-1.14a1 1 0 00-.86-.5l-1.2.08-.98-.98.08-1.2a1 1 0 00-.5-.86l-1.14-.66v-1.96l1.14-.66a1 1 0 00.5-.86l-.08-1.2.98-.98 1.2.08a1 1 0 00.86-.5l.66-1.14h1.96l.66 1.14a1 1 0 00.86.5l1.2-.08.98.98-.08 1.2a1 1 0 00.5.86l1.14.66v1.96z"/></svg>
39
- </button>
40
- <button class="rm-panel-toggle" id="rm-panel-toggle" style="${panelToggleStyle}" title="View annotations" aria-label="View annotations" aria-controls="rm-panel">
33
+ <div id="rm-dock-controls" class="rm-dock-controls" hidden>
34
+ <button type="button" class="rm-panel-toggle" id="rm-panel-toggle" aria-expanded="false" title="View annotations" aria-label="View annotations" aria-controls="rm-panel">
41
35
  <svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h7"/></svg>
42
36
  <span class="rm-panel-toggle-badge" id="rm-panel-toggle-badge"></span>
43
37
  </button>
38
+ <button type="button" class="rm-toolbar-settings" id="rm-settings-toggle" title="Toolbar settings" aria-label="Toolbar settings" aria-expanded="false" aria-controls="rm-settings-panel">
39
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6l1 3 3 1 2 5-2 5-3 1-1 3H9l-1-3-3-1-2-5 2-5 3-1 1-3z"/><circle cx="12" cy="12" r="3"/></svg>
40
+ </button>
41
+ <button type="button" class="rm-dock-close" id="rm-dock-close" title="Collapse markup tools" aria-label="Collapse markup tools"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
42
+ </div>
43
+ </div>
44
44
  <div class="rm-toast-container" id="rm-toast-container" style="${toastStyle}"></div>
45
45
  <div class="rm-popup" id="rm-popup" role="dialog" aria-label="Add annotation" aria-modal="false">
46
+ <h3 class="rm-popup-heading">Leave feedback</h3>
46
47
  <div style="margin-bottom:12px">
47
48
  <p class="rm-popup-el" id="rm-popup-el"></p>
48
49
  <p class="rm-popup-text" id="rm-popup-text"></p>
49
50
  </div>
50
- <textarea id="rm-popup-input" rows="3" placeholder="What should change?"></textarea>
51
- <div style="display:flex;align-items:center;gap:8px;margin-top:12px">
51
+ <details class="rm-image-options" id="rm-image-options" ${this.enableScreenshots ? "" : "hidden"}><summary>Attach image</summary>
52
+ <div id="rm-screenshot-actions" class="rm-screenshot-actions">
53
+ <button type="button" id="rm-attach-screen">Capture tab</button>
54
+ <button type="button" id="rm-attach-file">Upload image</button>
55
+ <button type="button" id="rm-remove-screenshot" hidden>Remove image</button>
56
+ <input type="file" id="rm-screenshot-file" accept="image/png,image/jpeg,image/webp" hidden>
57
+ <span id="rm-screenshot-status" role="status">Optional · capture, upload, or paste an image</span>
58
+ </div>
59
+ </details>
60
+ <textarea id="rm-popup-input" rows="3" placeholder="What should change?" aria-label="What should change?"></textarea>
61
+ <details class="rm-image-options"><summary>Type & priority</summary>
62
+ <div style="display:flex;align-items:center;gap:8px;margin-top:8px">
52
63
  ${this._menuMarkup({ inputId: "rm-intent-select", label: "Intent", value: "change", options: this._intentOptions() })}
53
64
  ${this._menuMarkup({ inputId: "rm-severity-select", label: "Severity", value: "suggestion", options: this._severityOptions() })}
54
65
  <span class="rm-count" id="rm-char-count"></span>
55
66
  </div>
67
+ </details>
56
68
  <div class="rm-popup-actions">
57
69
  <button class="rm-btn-cancel" id="rm-btn-cancel">Cancel</button>
58
70
  <button class="rm-btn-submit" id="rm-btn-submit" style="background:${accentBg}">
@@ -91,6 +103,8 @@
91
103
 
92
104
  document.body.appendChild(root);
93
105
  this.root = root;
106
+ this.dockState = this.fabVisible ? "collapsed" : "expanded";
107
+ this._renderDockState();
94
108
  // Pins container lives on body (not inside fixed root); scroll listener keeps them stuck to target elements
95
109
  const pinsContainer = document.createElement("div");
96
110
  pinsContainer.className = "rm-pins-container";
@@ -1,9 +1,19 @@
1
1
  Object.assign(RailsMarkupToolbar, {
2
2
  _bindEvents() {
3
3
  const self = this;
4
- document.getElementById("rm-fab").addEventListener("click", () => self.toggleMode());
4
+ document.getElementById("rm-attach-screen").addEventListener("click", () => self._attachScreen());
5
+ document.getElementById("rm-attach-file").addEventListener("click", () => document.getElementById("rm-screenshot-file").click());
6
+ document.getElementById("rm-screenshot-file").addEventListener("change", e => self._attachScreenshotFile(e.target.files[0]));
7
+ document.getElementById("rm-remove-screenshot").addEventListener("click", () => self._removeScreenshot());
8
+ document.getElementById("rm-popup-input").addEventListener("paste", e => {
9
+ if (!self.enableScreenshots) return;
10
+ const file = Array.from(e.clipboardData?.items || []).find(item => item.type.startsWith("image/"))?.getAsFile();
11
+ if (file) { e.preventDefault(); self._attachScreenshotFile(file); }
12
+ });
13
+ document.getElementById("rm-fab").addEventListener("click", () => self.dockState === "collapsed" ? self._expandDock() : self.toggleMode());
14
+ document.getElementById("rm-dock-close").addEventListener("click", () => self.active ? self._deactivateMode() : self._collapseDock());
5
15
  document.getElementById("rm-panel-toggle").addEventListener("click", () => self.togglePanel());
6
- document.getElementById("rm-panel-close").addEventListener("click", () => self.togglePanel());
16
+ document.getElementById("rm-panel-close").addEventListener("click", () => self._settingsPanelOpen ? self._toggleSettings() : self.togglePanel());
7
17
  document.getElementById("rm-settings-toggle").addEventListener("click", () => self._toggleSettings());
8
18
  document.getElementById("rm-btn-cancel").addEventListener("click", () => self._closePopup());
9
19
  document.getElementById("rm-btn-submit").addEventListener("click", (e) => self.submitAnnotation(e));
@@ -136,6 +146,8 @@
136
146
  const panel = document.getElementById("rm-panel");
137
147
  if (panel) panel.style.display = "none";
138
148
 
149
+ document.getElementById("rm-panel-toggle")?.setAttribute("aria-expanded", "false");
150
+
139
151
  // Rerender pins for current page (annotations are global, pins are page-specific)
140
152
  this._renderPins();
141
153
  this._updateCount();
@@ -161,11 +173,9 @@
161
173
  },
162
174
  _activateMode() {
163
175
  document.body.style.cursor = "crosshair";
164
- const fab = document.getElementById("rm-fab");
165
- const iconSize = this._fabIconSize();
166
- fab.style.transform = "scale(0.9)";
167
- fab.style.boxShadow = `0 0 0 3px ${this._accentBg()}, 0 0 0 6px rgba(99,102,241,0.2)`;
168
- fab.innerHTML = `<svg viewBox="0 0 24 24" style="width:${iconSize}px;height:${iconSize}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><path d="M6 18L18 6M6 6l12 12"/></svg>`;
176
+ this.active = true;
177
+ this.dockState = "annotating";
178
+ this._renderDockState();
169
179
  document.addEventListener("mousemove", this._boundMouseMove, true);
170
180
  document.addEventListener("mousedown", this._boundMouseDown, true);
171
181
  document.addEventListener("mouseup", this._boundMouseUp, true);
@@ -176,14 +186,8 @@
176
186
  _deactivateMode() {
177
187
  this.active = false;
178
188
  document.body.style.cursor = "";
179
- const fab = document.getElementById("rm-fab");
180
- if (fab) {
181
- const iconSize = this._fabIconSize();
182
- fab.style.transform = "";
183
- fab.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
184
- fab.innerHTML = `<svg viewBox="0 0 24 24" style="width:${iconSize}px;height:${iconSize}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>`;
185
- this._updateCount();
186
- }
189
+ if (this.dockState === "annotating") this.dockState = "expanded";
190
+ this._renderDockState();
187
191
  document.removeEventListener("mousemove", this._boundMouseMove, true);
188
192
  document.removeEventListener("mousedown", this._boundMouseDown, true);
189
193
  document.removeEventListener("mouseup", this._boundMouseUp, true);
@@ -192,16 +196,53 @@
192
196
  document.removeEventListener("touchend", this._boundTouchEnd, true);
193
197
  this._removeHighlight();
194
198
  },
199
+ _renderDockState() {
200
+ const dock = document.getElementById("rm-dock");
201
+ if (!dock) return;
202
+ dock.style.display = this.fabVisible ? "flex" : "none";
203
+ const expanded = this.dockState !== "collapsed";
204
+ dock.dataset.state = this.dockState;
205
+ document.getElementById("rm-dock-controls").hidden = !expanded;
206
+ const fab = document.getElementById("rm-fab");
207
+ const label = !expanded ? "Expand markup tools" : this.active ? "Stop annotating" : "Annotate an element";
208
+ fab.title = label;
209
+ fab.setAttribute("aria-label", label);
210
+ fab.setAttribute("aria-expanded", String(expanded));
211
+ fab.setAttribute("aria-pressed", String(this.active));
212
+ const close = document.getElementById("rm-dock-close");
213
+ close.hidden = !this.fabVisible && !this.active;
214
+ close.style.display = close.hidden ? "none" : "flex";
215
+ close.title = this.active ? "Stop annotating" : "Collapse markup tools";
216
+ close.setAttribute("aria-label", close.title);
217
+ },
218
+ _expandDock() {
219
+ if (!this.active) this.dockState = "expanded";
220
+ this._renderDockState();
221
+ },
222
+ _collapseDock() {
223
+ this._deactivateMode();
224
+ this._closePopup();
225
+ this._closeAllMenus();
226
+ document.getElementById("rm-panel").style.display = "none";
227
+ document.getElementById("rm-panel-toggle").setAttribute("aria-expanded", "false");
228
+ document.getElementById("rm-settings-panel").hidden = true;
229
+ document.getElementById("rm-settings-toggle").setAttribute("aria-expanded", "false");
230
+ this._settingsPanelOpen = false;
231
+ this.dockState = this.fabVisible ? "collapsed" : "expanded";
232
+ this._renderDockState();
233
+ if (this.fabVisible) document.getElementById("rm-fab").focus();
234
+ },
195
235
  togglePanel() {
236
+ this._expandDock();
196
237
  const panel = document.getElementById("rm-panel");
197
- const fab = document.getElementById("rm-fab");
198
- if (panel.style.display === "flex") {
199
- panel.style.display = "none";
200
- if (fab) fab.setAttribute("aria-expanded", "false");
201
- } else {
202
- panel.style.display = "flex";
203
- if (fab) fab.setAttribute("aria-expanded", "true");
204
- }
238
+ const opening = panel.style.display !== "flex" || panel.dataset.view === "settings";
239
+ panel.dataset.view = "feedback";
240
+ panel.querySelector("h3").textContent = "Feedback";
241
+ document.getElementById("rm-settings-panel").hidden = true;
242
+ document.getElementById("rm-settings-toggle").setAttribute("aria-expanded", "false");
243
+ this._settingsPanelOpen = false;
244
+ panel.style.display = opening ? "flex" : "none";
245
+ document.getElementById("rm-panel-toggle").setAttribute("aria-expanded", String(opening));
205
246
  },
206
247
  _handleMouseMove(event) {
207
248
  if (!this.active) return;
@@ -235,9 +276,7 @@
235
276
  this.selectedText = (sel && sel.toString().trim().length > 0) ? sel.toString().trim() : null;
236
277
  this._currentElement = this._identify(el);
237
278
  this._currentScreenshot = null;
238
- if (this.enableScreenshots) {
239
- this._currentScreenshot = await this._captureElement(el);
240
- }
279
+
241
280
  this._showPopup(event.clientX, event.clientY);
242
281
  this.clickedElement = null;
243
282
  },
@@ -315,6 +354,12 @@
315
354
  return;
316
355
  }
317
356
  }
357
+ if (event.key === "Escape" && this.dockState === "expanded") {
358
+ this._collapseDock();
359
+ event.preventDefault();
360
+ event.stopPropagation();
361
+ return;
362
+ }
318
363
  if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
319
364
  const popup = document.getElementById("rm-popup");
320
365
  if (popup && popup.style.display === "block") {
@@ -112,8 +112,8 @@
112
112
  this._updateCount();
113
113
  const panel = document.getElementById("rm-panel");
114
114
  if (panel) panel.style.display = "flex";
115
- const fab = document.getElementById("rm-fab");
116
- if (fab) fab.setAttribute("aria-expanded", "true");
115
+ this._expandDock();
116
+ document.getElementById("rm-panel-toggle")?.setAttribute("aria-expanded", "true");
117
117
  return false;
118
118
  },
119
119
  _localStateSnapshot() {