kapi-ui 0.3.1 → 0.3.2
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 +72 -1
- package/dist/browser/comments.js +57 -5
- package/dist/browser/styles/comments.css +16 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1 +1,72 @@
|
|
|
1
|
-
|
|
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)
|
package/dist/browser/comments.js
CHANGED
|
@@ -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', () =>
|
|
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
|
|
225
|
-
//
|
|
226
|
-
// of continuously re-centering (which would
|
|
227
|
-
|
|
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
|
|
78
|
-
|
|
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(
|
|
117
|
+
transform: translateY(8px);
|
|
106
118
|
}
|
|
107
119
|
to {
|
|
108
120
|
opacity: 1;
|
|
109
|
-
transform: translateY(
|
|
121
|
+
transform: translateY(0);
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
124
|
|