linear-grab-bridge 0.23.0 → 0.24.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.
- package/linear-grab-bridge.mjs +67 -16
- package/package.json +1 -1
package/linear-grab-bridge.mjs
CHANGED
|
@@ -28,7 +28,7 @@ const flag = (name, fallback) => {
|
|
|
28
28
|
const PORT = Number(flag('--port', '4577'));
|
|
29
29
|
const DIR = flag('--dir', process.cwd());
|
|
30
30
|
const CLAUDE_BIN = flag('--claude', 'claude');
|
|
31
|
-
const VERSION = '0.
|
|
31
|
+
const VERSION = '0.24.0';
|
|
32
32
|
|
|
33
33
|
/** Best-effort command runner (git/gh introspection). Never throws. */
|
|
34
34
|
function run(cmd, args, cwd = DIR) {
|
|
@@ -674,6 +674,9 @@ createServer(async (req, res) => {
|
|
|
674
674
|
const cutoff = Date.now() - windowMs;
|
|
675
675
|
const byComponent = new Map();
|
|
676
676
|
const interactions = [];
|
|
677
|
+
let bursts = 0;
|
|
678
|
+
let worstFps = 60;
|
|
679
|
+
const pages = new Set();
|
|
677
680
|
for (const line of raw.split('\n')) {
|
|
678
681
|
if (!line) continue;
|
|
679
682
|
let e;
|
|
@@ -683,51 +686,79 @@ createServer(async (req, res) => {
|
|
|
683
686
|
continue;
|
|
684
687
|
}
|
|
685
688
|
if ((e.at ?? 0) < cutoff) continue;
|
|
689
|
+
if (e.page) pages.add(e.page);
|
|
686
690
|
if (e.kind === 'interaction') interactions.push(e);
|
|
691
|
+
if (e.kind === 'long-render') {
|
|
692
|
+
bursts += 1;
|
|
693
|
+
if (typeof e.fps === 'number' && e.fps > 0) worstFps = Math.min(worstFps, e.fps);
|
|
694
|
+
}
|
|
687
695
|
for (const c of e.components ?? []) {
|
|
688
696
|
const cur = byComponent.get(c.name) ?? {
|
|
689
697
|
name: c.name,
|
|
690
698
|
renders: 0,
|
|
691
699
|
selfTime: 0,
|
|
692
700
|
source: c.source ?? null,
|
|
693
|
-
|
|
701
|
+
element: c.element ?? null,
|
|
702
|
+
pages: new Set(),
|
|
703
|
+
memoizableRenders: 0,
|
|
694
704
|
changes: {},
|
|
695
705
|
};
|
|
696
706
|
cur.renders += c.renders ?? 1;
|
|
697
707
|
cur.selfTime += c.selfTime ?? 0;
|
|
698
|
-
|
|
699
|
-
|
|
708
|
+
cur.source ??= c.source ?? null;
|
|
709
|
+
cur.element ??= c.element ?? null;
|
|
710
|
+
if (e.page) cur.pages.add(e.page);
|
|
711
|
+
if (c.memoizable) cur.memoizableRenders += c.renders ?? 1;
|
|
700
712
|
for (const ch of c.changes ?? []) cur.changes[ch] = (cur.changes[ch] ?? 0) + 1;
|
|
701
713
|
byComponent.set(c.name, cur);
|
|
702
714
|
}
|
|
703
715
|
}
|
|
704
|
-
const components = [...byComponent.values()]
|
|
716
|
+
const components = [...byComponent.values()]
|
|
717
|
+
.sort((a, b) => b.selfTime - a.selfTime)
|
|
718
|
+
.slice(0, 25)
|
|
719
|
+
.map((c) => ({ ...c, pages: [...c.pages] }));
|
|
705
720
|
const md = [
|
|
706
721
|
`# react-scan report (last ${Math.round(windowMs / 1000)}s)`,
|
|
707
722
|
'',
|
|
723
|
+
`Target 60 FPS — worst observed: ${worstFps} FPS across ${bursts} slow-frame burst(s).` +
|
|
724
|
+
(pages.size ? ` Pages: ${[...pages].join(', ')}` : ''),
|
|
725
|
+
'',
|
|
726
|
+
'## Components (fix top-down; each line = who, where, and WHY it re-rendered)',
|
|
708
727
|
...components.map(
|
|
709
728
|
(c) =>
|
|
710
729
|
`- **${c.name}** — ${c.renders} renders · ${c.selfTime.toFixed(1)}ms self` +
|
|
711
|
-
(c.
|
|
712
|
-
|
|
730
|
+
(c.memoizableRenders
|
|
731
|
+
? ` · ${c.memoizableRenders} renders with ZERO changes (memo() candidate)`
|
|
732
|
+
: '') +
|
|
733
|
+
(c.source ? ` · src \`${c.source}\`` : '') +
|
|
734
|
+
(c.element ? ` · el \`${c.element}\`` : '') +
|
|
735
|
+
(c.pages.length ? ` · on ${c.pages.join(', ')}` : '') +
|
|
713
736
|
(Object.keys(c.changes).length
|
|
714
737
|
? ` · causes: ${Object.entries(c.changes)
|
|
715
738
|
.sort((a, b) => b[1] - a[1])
|
|
716
|
-
.slice(0,
|
|
739
|
+
.slice(0, 4)
|
|
717
740
|
.map(([k, v]) => `${k}×${v}`)
|
|
718
|
-
.join(', ')}`
|
|
741
|
+
.join(', ')} — (fn)=unstable callback→useCallback, (ref)=new identity→useMemo/hoist`
|
|
719
742
|
: ''),
|
|
720
743
|
),
|
|
721
744
|
'',
|
|
745
|
+
'## Recent interactions',
|
|
722
746
|
...interactions
|
|
723
747
|
.slice(-10)
|
|
724
748
|
.map(
|
|
725
749
|
(i) =>
|
|
726
|
-
`-
|
|
727
|
-
(i.slow ? ' ⚠ SLOW' : ''),
|
|
750
|
+
`- ${i.type ?? '?'} on ${i.target ?? '?'}${i.page ? ` (${i.page})` : ''} — ${Math.round(i.duration ?? 0)}ms` +
|
|
751
|
+
(i.slow ? ' ⚠ SLOW (>150ms INP)' : ''),
|
|
728
752
|
),
|
|
729
753
|
].join('\n');
|
|
730
|
-
return json(res, 200, {
|
|
754
|
+
return json(res, 200, {
|
|
755
|
+
report: md,
|
|
756
|
+
worstFps,
|
|
757
|
+
bursts,
|
|
758
|
+
pages: [...pages],
|
|
759
|
+
components,
|
|
760
|
+
interactions: interactions.slice(-20),
|
|
761
|
+
});
|
|
731
762
|
}
|
|
732
763
|
// Reset the staging branch: delete + recreate from the default branch.
|
|
733
764
|
if (req.method === 'POST' && url.pathname === '/branch/reset') {
|
|
@@ -880,9 +911,29 @@ createServer(async (req, res) => {
|
|
|
880
911
|
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
881
912
|
}
|
|
882
913
|
}).listen(PORT, '127.0.0.1', () => {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
914
|
+
const B = '\x1b[1m';
|
|
915
|
+
const D = '\x1b[2m';
|
|
916
|
+
const C = '\x1b[36m';
|
|
917
|
+
const G = '\x1b[32m';
|
|
918
|
+
const R = '\x1b[0m';
|
|
919
|
+
console.log('');
|
|
920
|
+
console.log(`${B}◆ linear-grab bridge${R} ${D}v${VERSION}${R}`);
|
|
921
|
+
console.log(`${D}──────────────────────────────────────────────────${R}`);
|
|
922
|
+
console.log(` repo ${C}${DIR}${R}`);
|
|
923
|
+
console.log(` listen ${C}http://127.0.0.1:${PORT}${R} ${D}(localhost only)${R}`);
|
|
924
|
+
console.log(` agent ${C}${CLAUDE_BIN} -p${R} ${D}(interactive stream-json)${R}`);
|
|
925
|
+
console.log('');
|
|
926
|
+
console.log(`${B}react-scan telemetry${R} ${D}(when the app loads react-scan-banihani)${R}`);
|
|
927
|
+
console.log(` events ${G}.lineargrab/scan.ndjson${R} ${D}— newest last, gitignored${R}`);
|
|
928
|
+
console.log(` report ${G}curl -s http://127.0.0.1:${PORT}/scan/report${R}`);
|
|
929
|
+
console.log('');
|
|
930
|
+
console.log(`${B}point Claude Code at the render logs${R} — paste one of these:`);
|
|
931
|
+
console.log(` ${D}»${R} Check the live render telemetry: read the newest lines of`);
|
|
932
|
+
console.log(` .lineargrab/scan.ndjson and summarize the slow components.`);
|
|
933
|
+
console.log(` ${D}»${R} curl -s http://127.0.0.1:${PORT}/scan/report ${D}(aggregated view)${R}`);
|
|
934
|
+
console.log(` ${D}tip: add a line to your repo's AGENTS.md/CLAUDE.md so agents find it`);
|
|
935
|
+
console.log(` on their own: "Live render telemetry: .lineargrab/scan.ndjson`);
|
|
936
|
+
console.log(` (react-scan via linear-grab bridge); aggregate: GET /scan/report"${R}`);
|
|
937
|
+
console.log(`${D}──────────────────────────────────────────────────${R}`);
|
|
887
938
|
loadHistory();
|
|
888
939
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linear-grab-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Local bridge for Linear Grab — delegate issues from the browser panel to headless Claude Code sessions running in your repo, with live status and an upload relay.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|