kapi-ui 0.3.1 → 0.3.3

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.
package/README.md CHANGED
@@ -1 +1,72 @@
1
- # kapi
1
+ Kapi UI is a visual commenting tool. Click on an element, leave a comment, and send to your agent. No need for copy-pasting file paths or describing your target element.
2
+
3
+ It currently works with Vue projects (Vite + Vue or Nuxt) paired with Claude Code or Codex. Support for other frameworks and agents coming soon.
4
+
5
+ ## Requirements
6
+ ---
7
+ - A Vue project (Vite + Vue or Nuxt)
8
+ - Claude Code/Codex CLI
9
+
10
+ ## How it works
11
+ ---
12
+ At its core, Kapi UI is a Vite plugin. It injects the UI overlay into your dev app and uses the HMR websocket to send your comments to your agent. You can also opt to manually copy your comments and paste them into any coding agent of your choice.
13
+
14
+ ## Automatic Installation
15
+ ---
16
+ From your project root:
17
+ ```bash
18
+ npx kapi-ui
19
+ ```
20
+
21
+ Useful flags:
22
+ ```bash
23
+ npx kapi-ui --vite # force Vite + Vue setup (skip auto-detection)
24
+ npx kapi-ui --nuxt # force Nuxt setup
25
+ npx kapi-ui --agent=claude # force Claude Code
26
+ npx kapi-ui --agent=codex # force Codex (experimental)
27
+ npx kapi-ui --manual # copy/paste workflow, no agent
28
+ ```
29
+
30
+ ## Manual Installation
31
+ ---
32
+ 1. Install the package as a dev dependency:
33
+ ```bash
34
+ npm install kapi-ui -D
35
+ ```
36
+ 2. Wire it into your config
37
+
38
+ **Vite + Vue** (`vite.config.ts`):
39
+
40
+ ```ts
41
+ import kapi from 'kapi-ui/vite-plugin'
42
+
43
+ export default defineConfig({
44
+ plugins: [kapi({ agent: 'claude' })],
45
+ })
46
+ ```
47
+
48
+ **Nuxt** (`nuxt.config.ts`):
49
+
50
+ ```ts
51
+ export default defineNuxtConfig({
52
+ modules: ['kapi-ui/nuxt'],
53
+ kapi: { agent: 'claude' },
54
+ })
55
+ ```
56
+
57
+ The `agent` option is required — use `'claude'`, `'codex'`, or `false` (copy/paste only, no agent spawned).
58
+
59
+ 2. Run your dev server as usual. The UI overlay is injected and your agent session starts automatically.
60
+
61
+ ## Configuration
62
+ ---
63
+ The only option, on both the Vite plugin and the Nuxt module:
64
+
65
+ | Option | Type | Default | What it does |
66
+ | ------- | ------------------------------ | ------------ | -------------------------------------------------------------------- |
67
+ | `agent` | `'claude' \| 'codex' \| false` | — (required) | Which agent Kapi drives. `false` = copy/paste only, no agent spawned |
68
+ Kapi UI uses Vite's HMR websocket to start a Claude Code and Codex Sessions.
69
+
70
+ ## License
71
+ ---
72
+ [MIT](https://github.com/jcebermudo/kapi-ui/blob/main/LICENSE)
@@ -101,6 +101,23 @@ function ensureRoot() {
101
101
  function clamp(value, min, max) {
102
102
  return Math.min(Math.max(value, min), max);
103
103
  }
104
+ const PANEL_MARGIN = 8;
105
+ // The tooltip/composer defaults to the marker's right, vertically centered
106
+ // on it. Flips it to the marker's left if that would run past the right
107
+ // edge of the viewport, and clamps its vertical centering so it can't run
108
+ // past the top/bottom edge either — both are recalculated on every reveal
109
+ // and on scroll/resize (see repositionAll), since the marker's on-screen
110
+ // position can move independently of the panel's fixed size.
111
+ function adjustPanelPosition(marker, panel) {
112
+ const markerRect = marker.getBoundingClientRect();
113
+ const panelRect = panel.getBoundingClientRect();
114
+ const overflowsRight = markerRect.right + 8 + panelRect.width + PANEL_MARGIN > window.innerWidth;
115
+ panel.classList.toggle('kapi-comment-panel-left', overflowsRight);
116
+ const idealTop = markerRect.top + markerRect.height / 2 - panelRect.height / 2;
117
+ const clampedTop = clamp(idealTop, PANEL_MARGIN, window.innerHeight - panelRect.height - PANEL_MARGIN);
118
+ panel.style.top = `${clampedTop - markerRect.top}px`;
119
+ panel.style.transform = 'none';
120
+ }
104
121
  function position(target, wrapper) {
105
122
  const rect = target.el.getBoundingClientRect();
106
123
  const x = rect.left + target.ratioX * rect.width;
@@ -156,7 +173,10 @@ function renderMarker(entry, label) {
156
173
  countEl.textContent = `applies to ${entry.targets.length} elements`;
157
174
  tooltip.appendChild(countEl);
158
175
  }
159
- marker.addEventListener('mouseenter', () => wrapper.classList.add('kapi-hovering'));
176
+ marker.addEventListener('mouseenter', () => {
177
+ wrapper.classList.add('kapi-hovering');
178
+ adjustPanelPosition(marker, tooltip);
179
+ });
160
180
  marker.addEventListener('mouseleave', () => wrapper.classList.remove('kapi-hovering'));
161
181
  marker.addEventListener('click', () => beginEdit(entry));
162
182
  wrapper.append(marker, tooltip);
@@ -221,10 +241,11 @@ function renderComposer(label, target, initialText = '') {
221
241
  input.focus();
222
242
  input.setSelectionRange(input.value.length, input.value.length);
223
243
  autoGrow();
224
- // Lock the composer's vertical position to its initial (single-line) height,
225
- // centered on the marker, so later growth only extends it downward instead
226
- // of continuously re-centering (which would grow it upward too).
227
- composer.style.top = `${MARKER_RADIUS - composer.getBoundingClientRect().height / 2}px`;
244
+ // Lock the composer's position to its initial (single-line) height and
245
+ // whichever side/vertical offset fits the viewport, so later growth only
246
+ // extends it downward instead of continuously re-centering (which would
247
+ // grow it upward too) or re-flipping sides mid-type.
248
+ adjustPanelPosition(marker, composer);
228
249
  });
229
250
  return wrapper;
230
251
  }
@@ -281,6 +302,19 @@ function repositionAll() {
281
302
  const target = targets[i];
282
303
  if (target)
283
304
  position(target, wrapper);
305
+ // Re-flip/re-clamp any panel currently on screen — the marker's position
306
+ // just moved, so a fit that held before this scroll/resize might not now.
307
+ const marker = wrapper.querySelector('.kapi-comment-marker');
308
+ if (!marker)
309
+ return;
310
+ const composer = wrapper.querySelector('.kapi-comment-composer');
311
+ if (composer) {
312
+ adjustPanelPosition(marker, composer);
313
+ return;
314
+ }
315
+ const tooltip = wrapper.querySelector('.kapi-comment-tooltip');
316
+ if (tooltip && wrapper.classList.contains('kapi-hovering'))
317
+ adjustPanelPosition(marker, tooltip);
284
318
  });
285
319
  }
286
320
  window.addEventListener('resize', repositionAll);
@@ -434,6 +468,22 @@ export function beginComment(el, clientX, clientY) {
434
468
  lockHighlightOn(el);
435
469
  render();
436
470
  }
471
+ // Tracks whether Shift is currently held so the composer can stay hidden
472
+ // while the user is still shift-clicking to grow the multi-selection —
473
+ // opening/refreshing it on every click steals focus and discards typing.
474
+ // The composer only appears once Shift is released, showing the final batch.
475
+ let shiftHeld = false;
476
+ document.addEventListener('keydown', (e) => {
477
+ if (e.key === 'Shift')
478
+ shiftHeld = true;
479
+ });
480
+ document.addEventListener('keyup', (e) => {
481
+ if (e.key !== 'Shift')
482
+ return;
483
+ shiftHeld = false;
484
+ if (draft?.els)
485
+ render();
486
+ });
437
487
  // Called on every shift-click/drag-select change in inspector.ts. Keeps one
438
488
  // shared composer live as the selection grows or shrinks, so no separate
439
489
  // "commit" click is needed. On submit, one CommentEntry per selected element
@@ -463,6 +513,8 @@ export function updateSelection(els) {
463
513
  draft = { el: anchor, ratioX: 0.5, ratioY: 0.5, els };
464
514
  }
465
515
  lockWithoutHighlight();
516
+ if (shiftHeld)
517
+ return;
466
518
  render();
467
519
  }
468
520
  function deleteComment(id) {
@@ -74,8 +74,11 @@
74
74
  display: none;
75
75
  position: absolute;
76
76
  left: calc(var(--kapi-marker-size) + 8px);
77
- top: 50%;
78
- transform: translateY(-50%);
77
+ /* top/transform are set in JS on every reveal (see adjustPanelPosition in
78
+ comments.ts), which vertically centers on the marker by default and
79
+ clamps within the viewport otherwise — these are just a pre-JS fallback. */
80
+ top: 0;
81
+ transform: none;
79
82
  width: var(--kapi-panel-width);
80
83
  box-sizing: border-box;
81
84
  border-radius: 12px;
@@ -87,6 +90,15 @@
87
90
  pointer-events: auto;
88
91
  }
89
92
 
93
+ /* Flips the panel to the marker's left — set in JS (see adjustPanelPosition
94
+ in comments.ts) when the default right-of-marker position would run past
95
+ the right edge of the viewport. */
96
+ .kapi-comment-tooltip.kapi-comment-panel-left,
97
+ .kapi-comment-composer.kapi-comment-panel-left {
98
+ left: auto;
99
+ right: calc(var(--kapi-marker-size) + 8px);
100
+ }
101
+
90
102
  .kapi-comment-tooltip {
91
103
  flex-direction: column;
92
104
  gap: 2px;
@@ -102,11 +114,11 @@
102
114
  @keyframes kapi-tooltip-in {
103
115
  from {
104
116
  opacity: 0;
105
- transform: translateY(-50%) translateY(8px);
117
+ transform: translateY(8px);
106
118
  }
107
119
  to {
108
120
  opacity: 1;
109
- transform: translateY(-50%);
121
+ transform: translateY(0);
110
122
  }
111
123
  }
112
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kapi-ui",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Visually prompt your UI",
5
5
  "scripts": {
6
6
  "build": "tsc && npm run copy:css",