@satanwagen/reviewkit 0.1.0 → 0.1.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/cli/emblema-sync.mjs +47 -8
- package/package.json +3 -3
package/cli/emblema-sync.mjs
CHANGED
|
@@ -86,8 +86,10 @@ export function toFinding(item, seq) {
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/** Emblema status -> ReviewKit ItemStatus.
|
|
90
|
-
|
|
89
|
+
/** Emblema status -> ReviewKit ItemStatus. `open` REOPENS a finding (a fix
|
|
90
|
+
was abandoned or resolved by mistake) — written into the room, so the
|
|
91
|
+
site pin reverts live. */
|
|
92
|
+
export const STATUS_MAP = { open: 'open', accepted: 'accepted', dismissed: 'rejected', fixed: 'applied' };
|
|
91
93
|
|
|
92
94
|
function statePath(cwd) {
|
|
93
95
|
return join(cwd, STATE_DIR, STATE_FILE);
|
|
@@ -294,11 +296,15 @@ export function cmdServe(cwd) {
|
|
|
294
296
|
findings.set(item.id, toFinding(item, seq));
|
|
295
297
|
queuePush(item);
|
|
296
298
|
};
|
|
299
|
+
// Tombstone unconditionally: a delete for an id this process never saw (a
|
|
300
|
+
// ghost row left in the Emblema tile by an older/restarted agent) must still
|
|
301
|
+
// be reportable under `deleted`, or the row can never be cleared.
|
|
297
302
|
const dropItem = (id) => {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
303
|
+
// Already tombstoned and not live: nothing changed — don't churn the
|
|
304
|
+
// cursor (rooms replay their `deleted` list on every reconnect).
|
|
305
|
+
if (!findings.delete(id) && deletedIds.has(id)) return;
|
|
306
|
+
seq += 1;
|
|
307
|
+
deletedIds.set(id, seq);
|
|
302
308
|
};
|
|
303
309
|
|
|
304
310
|
/* -- realtime push to the Emblema intake (headers per contract v2) -- */
|
|
@@ -436,7 +442,7 @@ export function cmdServe(cwd) {
|
|
|
436
442
|
/* -- HTTP API -- */
|
|
437
443
|
const CORS = {
|
|
438
444
|
'access-control-allow-origin': '*',
|
|
439
|
-
'access-control-allow-methods': 'GET, POST, OPTIONS',
|
|
445
|
+
'access-control-allow-methods': 'GET, POST, DELETE, OPTIONS',
|
|
440
446
|
'access-control-allow-headers': 'content-type, authorization, x-reviewkit-key-id',
|
|
441
447
|
};
|
|
442
448
|
const send = (res, status, body) => {
|
|
@@ -472,6 +478,13 @@ export function cmdServe(cwd) {
|
|
|
472
478
|
url: state.url,
|
|
473
479
|
lastAck: state.lastAck ?? null,
|
|
474
480
|
findings: findings.size,
|
|
481
|
+
// Additive: realtime-bridge health for status UIs. "off" = no
|
|
482
|
+
// syncToken configured (bridge disabled by design), "connected" =
|
|
483
|
+
// at least one room socket joined, "connecting" = configured but
|
|
484
|
+
// not joined (booting, reconnect backoff, or a bad token).
|
|
485
|
+
realtime: !state.settings.syncToken
|
|
486
|
+
? { state: 'off', rooms: 0 }
|
|
487
|
+
: { state: sockets.size > 0 ? 'connected' : 'connecting', rooms: sockets.size },
|
|
475
488
|
});
|
|
476
489
|
return;
|
|
477
490
|
}
|
|
@@ -546,7 +559,15 @@ export function cmdServe(cwd) {
|
|
|
546
559
|
return send(res, 400, { ok: false, error: 'invalid JSON' });
|
|
547
560
|
}
|
|
548
561
|
const mapped = STATUS_MAP[status];
|
|
549
|
-
if (!mapped)
|
|
562
|
+
if (!mapped) {
|
|
563
|
+
// Echo what arrived: the live 400s on `open` were an OLD installed
|
|
564
|
+
// agent, and a bare "must be …" message made that undiagnosable.
|
|
565
|
+
return send(res, 400, {
|
|
566
|
+
ok: false,
|
|
567
|
+
error: `status must be open|accepted|dismissed|fixed (received ${JSON.stringify(status ?? null)})`,
|
|
568
|
+
received: status ?? null,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
550
571
|
const finding = findings.get(decodeURIComponent(statusMatch[1]));
|
|
551
572
|
if (!finding) return send(res, 404, { ok: false, error: 'unknown finding' });
|
|
552
573
|
const ws = sockets.get(finding._route);
|
|
@@ -558,6 +579,24 @@ export function cmdServe(cwd) {
|
|
|
558
579
|
});
|
|
559
580
|
return;
|
|
560
581
|
}
|
|
582
|
+
// DELETE /findings/{id} (POST …/delete for clients that cannot send DELETE).
|
|
583
|
+
// IDEMPOTENT: an unknown id still answers 200 and is tombstoned, so a ghost
|
|
584
|
+
// row left in the tile by an older/restarted agent can always be cleared.
|
|
585
|
+
// No body is read — the id is entirely in the path.
|
|
586
|
+
const deleteMatch =
|
|
587
|
+
(req.method === 'DELETE' && url.pathname.match(/^\/api\/emblema\/findings\/([^/]+)$/)) ||
|
|
588
|
+
(req.method === 'POST' && url.pathname.match(/^\/api\/emblema\/findings\/([^/]+)\/delete$/));
|
|
589
|
+
if (deleteMatch) {
|
|
590
|
+
const id = decodeURIComponent(deleteMatch[1]);
|
|
591
|
+
const finding = findings.get(id);
|
|
592
|
+
// Write-back first, while _route is still known: the site pin must
|
|
593
|
+
// disappear for every reviewer, not just in the tile.
|
|
594
|
+
const ws = finding && sockets.get(finding._route);
|
|
595
|
+
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ t: 'item:delete', id }));
|
|
596
|
+
dropItem(id);
|
|
597
|
+
send(res, 200, { ok: true, id, known: !!finding });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
561
600
|
send(res, 404, { ok: false, error: 'not found' });
|
|
562
601
|
});
|
|
563
602
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@satanwagen/reviewkit",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Drop-in visual review & feedback layer for React sites. Reviewers annotate the live page; nothing is ever applied
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Drop-in visual review & feedback layer for React sites. Reviewers annotate the live page; nothing is ever applied — changes are recorded as a portable session for a coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -69,4 +69,4 @@
|
|
|
69
69
|
"bin": {
|
|
70
70
|
"reviewkit-emblema": "./cli/emblema-sync.mjs"
|
|
71
71
|
}
|
|
72
|
-
}
|
|
72
|
+
}
|