@rahmantech/litedesign 0.2.0 → 0.2.1
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 +5 -0
- package/dist/extension/content.js +37 -20
- package/dist/extension/core.iife.js +30 -6
- package/dist/extension/manifest.json +1 -1
- package/dist/index.js +35 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -74,6 +74,11 @@ scrolled viewport.
|
|
|
74
74
|
toolbar. When the human refers to a part by name, look it up there instead of
|
|
75
75
|
guessing a selector. Labels are read-only for the agent.
|
|
76
76
|
|
|
77
|
+
The server also serves its workflow as the MCP resource
|
|
78
|
+
`skill://litedesign/workflow` — an agent that supports resources can read the
|
|
79
|
+
flow (inspect first, resolve names through labels, spotlight edits with focus)
|
|
80
|
+
without a file in the repository.
|
|
81
|
+
|
|
77
82
|
## Drivers
|
|
78
83
|
|
|
79
84
|
The server prefers Playwright when `playwright-core` is installed. It launches
|
|
@@ -553,6 +553,7 @@
|
|
|
553
553
|
let focusId = null;
|
|
554
554
|
let labels = [];
|
|
555
555
|
let labelMode = false;
|
|
556
|
+
let marksInteractive = false;
|
|
556
557
|
let toolbar;
|
|
557
558
|
const teardown = () => {
|
|
558
559
|
observer?.stop();
|
|
@@ -622,7 +623,30 @@
|
|
|
622
623
|
void saveState({ enabled: true, layers, focus: focusSelector, grid });
|
|
623
624
|
};
|
|
624
625
|
const applyMarks = () => {
|
|
625
|
-
handle?.setMarks(
|
|
626
|
+
handle?.setMarks(
|
|
627
|
+
labels.map((label) => ({ selector: label.selector, text: label.text })),
|
|
628
|
+
marksInteractive
|
|
629
|
+
);
|
|
630
|
+
};
|
|
631
|
+
const editLabel = (existing, rect) => {
|
|
632
|
+
toolbar.askLabel(
|
|
633
|
+
rect,
|
|
634
|
+
existing.text,
|
|
635
|
+
(text) => {
|
|
636
|
+
const trimmed = text.trim();
|
|
637
|
+
if (trimmed === "" || trimmed === existing.text) return;
|
|
638
|
+
void renameLabel(existing.id, trimmed).then((next) => {
|
|
639
|
+
labels = next;
|
|
640
|
+
applyMarks();
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
() => {
|
|
644
|
+
void removeLabel(existing.id).then((next) => {
|
|
645
|
+
labels = next;
|
|
646
|
+
applyMarks();
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
);
|
|
626
650
|
};
|
|
627
651
|
const onLabelClick = (event) => {
|
|
628
652
|
const target = event.target;
|
|
@@ -633,24 +657,7 @@
|
|
|
633
657
|
const selector = core2.pathOf(target);
|
|
634
658
|
const existing = labels.find((label) => label.selector === selector);
|
|
635
659
|
if (existing !== void 0) {
|
|
636
|
-
|
|
637
|
-
target.getBoundingClientRect(),
|
|
638
|
-
existing.text,
|
|
639
|
-
(text) => {
|
|
640
|
-
const trimmed = text.trim();
|
|
641
|
-
if (trimmed === "" || trimmed === existing.text) return;
|
|
642
|
-
void renameLabel(existing.id, trimmed).then((next) => {
|
|
643
|
-
labels = next;
|
|
644
|
-
applyMarks();
|
|
645
|
-
});
|
|
646
|
-
},
|
|
647
|
-
() => {
|
|
648
|
-
void removeLabel(existing.id).then((next) => {
|
|
649
|
-
labels = next;
|
|
650
|
-
applyMarks();
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
);
|
|
660
|
+
editLabel(existing, target.getBoundingClientRect());
|
|
654
661
|
return;
|
|
655
662
|
}
|
|
656
663
|
toolbar.askLabel(target.getBoundingClientRect(), "", (text) => {
|
|
@@ -670,6 +677,8 @@
|
|
|
670
677
|
} else {
|
|
671
678
|
document.removeEventListener("click", onLabelClick, true);
|
|
672
679
|
}
|
|
680
|
+
marksInteractive = labelMode;
|
|
681
|
+
applyMarks();
|
|
673
682
|
};
|
|
674
683
|
toolbar = createToolbar({
|
|
675
684
|
layers,
|
|
@@ -684,7 +693,15 @@
|
|
|
684
693
|
onClose: teardown
|
|
685
694
|
});
|
|
686
695
|
toolbar.setStats(graph.nodes.length, graph.issues.length, graph.truncated);
|
|
687
|
-
handle = core2.render(graph, {
|
|
696
|
+
handle = core2.render(graph, {
|
|
697
|
+
layers,
|
|
698
|
+
toolbar: toolbar.element,
|
|
699
|
+
grid,
|
|
700
|
+
onMarkClick: (mark, anchor) => {
|
|
701
|
+
const existing = labels.find((label) => label.selector === mark.selector);
|
|
702
|
+
if (existing !== void 0) editLabel(existing, anchor);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
688
705
|
observer = observeChanges({ root: root2, onChange: refresh });
|
|
689
706
|
void loadLabels().then((next) => {
|
|
690
707
|
labels = next;
|
|
@@ -980,9 +980,10 @@ var __litedesign = (() => {
|
|
|
980
980
|
return layer;
|
|
981
981
|
}
|
|
982
982
|
var MARK_COLOR = "rgba(217, 119, 6, 0.95)";
|
|
983
|
-
function drawMarks(layer, marks, bySelector) {
|
|
983
|
+
function drawMarks(layer, marks, bySelector, interactive = false) {
|
|
984
984
|
layer.replaceChildren();
|
|
985
|
-
|
|
985
|
+
const badges = /* @__PURE__ */ new Map();
|
|
986
|
+
if (marks.length === 0) return badges;
|
|
986
987
|
const scrollX = window.scrollX;
|
|
987
988
|
const scrollY = window.scrollY;
|
|
988
989
|
for (const mark of marks) {
|
|
@@ -990,10 +991,12 @@ var __litedesign = (() => {
|
|
|
990
991
|
if (node === void 0) continue;
|
|
991
992
|
const badge = box(
|
|
992
993
|
layer,
|
|
993
|
-
`left:${node.rect.x - scrollX}px;top:${node.rect.y - scrollY}px;transform:translateY(-100%);padding:2px 6px;border-radius:4px;background:${MARK_COLOR};color:#fff;font:10px/1.4 ui-monospace,monospace;white-space:nowrap`
|
|
994
|
+
`left:${node.rect.x - scrollX}px;top:${node.rect.y - scrollY}px;transform:translateY(-100%);padding:2px 6px;border-radius:4px;background:${MARK_COLOR};color:#fff;font:10px/1.4 ui-monospace,monospace;white-space:nowrap` + (interactive ? ";pointer-events:auto;cursor:pointer" : "")
|
|
994
995
|
);
|
|
995
996
|
badge.textContent = mark.text;
|
|
997
|
+
badges.set(badge, mark);
|
|
996
998
|
}
|
|
999
|
+
return badges;
|
|
997
1000
|
}
|
|
998
1001
|
function drawRadius(layer, node) {
|
|
999
1002
|
layer.replaceChildren();
|
|
@@ -1117,6 +1120,19 @@ var __litedesign = (() => {
|
|
|
1117
1120
|
const layers = createLayers(shadow);
|
|
1118
1121
|
const radiusLayer = createSubLayer("radius", layers.hover);
|
|
1119
1122
|
const marksLayer = createSubLayer("marks", layers.hover);
|
|
1123
|
+
if (options.onMarkClick !== void 0) {
|
|
1124
|
+
const onMarkClick = options.onMarkClick;
|
|
1125
|
+
marksLayer.addEventListener("click", (event) => {
|
|
1126
|
+
if (!marksInteractive) return;
|
|
1127
|
+
const badge = event.target;
|
|
1128
|
+
if (!(badge instanceof HTMLDivElement)) return;
|
|
1129
|
+
const mark = badgeMarks.get(badge);
|
|
1130
|
+
if (mark === void 0) return;
|
|
1131
|
+
event.preventDefault();
|
|
1132
|
+
event.stopPropagation();
|
|
1133
|
+
onMarkClick(mark, badge.getBoundingClientRect());
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1120
1136
|
if (options.toolbar !== void 0) shadow.append(options.toolbar);
|
|
1121
1137
|
document.documentElement.append(host);
|
|
1122
1138
|
let current = graph;
|
|
@@ -1127,6 +1143,8 @@ var __litedesign = (() => {
|
|
|
1127
1143
|
let focusId = null;
|
|
1128
1144
|
let visible = new Set(options.layers ?? LAYERS);
|
|
1129
1145
|
let marks = options.marks ?? [];
|
|
1146
|
+
let marksInteractive = false;
|
|
1147
|
+
let badgeMarks = /* @__PURE__ */ new Map();
|
|
1130
1148
|
let grid = options.grid ?? { step: GRID_STEP, axis: "both" };
|
|
1131
1149
|
let frame = 0;
|
|
1132
1150
|
const draw = () => {
|
|
@@ -1144,7 +1162,12 @@ var __litedesign = (() => {
|
|
|
1144
1162
|
drawDim(layers.dim, dimNode);
|
|
1145
1163
|
drawFocus(layers.focus, outlineNode);
|
|
1146
1164
|
drawRadius(radiusLayer, outlineNode);
|
|
1147
|
-
|
|
1165
|
+
badgeMarks = drawMarks(
|
|
1166
|
+
marksLayer,
|
|
1167
|
+
marks,
|
|
1168
|
+
new Map(current.nodes.map((node) => [node.selector, node])),
|
|
1169
|
+
marksInteractive
|
|
1170
|
+
);
|
|
1148
1171
|
drawHover(layers.hover, hovered === null ? null : byId.get(hovered) ?? null);
|
|
1149
1172
|
};
|
|
1150
1173
|
const schedule = () => {
|
|
@@ -1200,8 +1223,9 @@ var __litedesign = (() => {
|
|
|
1200
1223
|
focusId = id;
|
|
1201
1224
|
draw();
|
|
1202
1225
|
},
|
|
1203
|
-
setMarks(next) {
|
|
1226
|
+
setMarks(next, interactive = false) {
|
|
1204
1227
|
marks = next;
|
|
1228
|
+
marksInteractive = interactive;
|
|
1205
1229
|
draw();
|
|
1206
1230
|
},
|
|
1207
1231
|
setGrid(next) {
|
|
@@ -1224,7 +1248,7 @@ var __litedesign = (() => {
|
|
|
1224
1248
|
}
|
|
1225
1249
|
|
|
1226
1250
|
// packages/core/src/index.ts
|
|
1227
|
-
var VERSION = "0.2.
|
|
1251
|
+
var VERSION = "0.2.1";
|
|
1228
1252
|
return __toCommonJS(index_exports);
|
|
1229
1253
|
})();
|
|
1230
1254
|
//# sourceMappingURL=core.iife.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "litedesign",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "Grid, gap, and alignment on the real web page.",
|
|
6
6
|
"permissions": ["activeTab", "scripting", "storage"],
|
|
7
7
|
"host_permissions": ["http://*/*", "https://*/*"],
|
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ function isMode(value) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// packages/cli/src/version.ts
|
|
47
|
-
var VERSION = "0.1
|
|
47
|
+
var VERSION = "0.2.1";
|
|
48
48
|
|
|
49
49
|
// packages/cli/src/ws.ts
|
|
50
50
|
import { spawn } from "node:child_process";
|
|
@@ -1790,6 +1790,13 @@ function formatTree(graph, maxLines = MAX_TREE_LINES) {
|
|
|
1790
1790
|
}
|
|
1791
1791
|
return lines.join("\n");
|
|
1792
1792
|
}
|
|
1793
|
+
function labelsSummary(marks) {
|
|
1794
|
+
const names = [];
|
|
1795
|
+
for (const mark of marks) {
|
|
1796
|
+
if (!names.includes(mark.label)) names.push(mark.label);
|
|
1797
|
+
}
|
|
1798
|
+
return { count: marks.length, names };
|
|
1799
|
+
}
|
|
1793
1800
|
function issuePayload(issues, limit) {
|
|
1794
1801
|
return {
|
|
1795
1802
|
issues: issues.slice(0, limit).map((issue2) => ({
|
|
@@ -1867,6 +1874,7 @@ async function inspectTool(input) {
|
|
|
1867
1874
|
throw error;
|
|
1868
1875
|
}
|
|
1869
1876
|
const graph = await driver.graph();
|
|
1877
|
+
const labels = labelsSummary(await driver.labels());
|
|
1870
1878
|
const payload = {
|
|
1871
1879
|
status: "ready",
|
|
1872
1880
|
url: graph.url,
|
|
@@ -1874,7 +1882,8 @@ async function inspectTool(input) {
|
|
|
1874
1882
|
summary: {
|
|
1875
1883
|
nodes: graph.nodes.length,
|
|
1876
1884
|
issues: graph.issues.length,
|
|
1877
|
-
truncated: graph.truncated
|
|
1885
|
+
truncated: graph.truncated,
|
|
1886
|
+
labels
|
|
1878
1887
|
},
|
|
1879
1888
|
tree: formatTree(graph)
|
|
1880
1889
|
};
|
|
@@ -1937,6 +1946,17 @@ async function screenshotAnnotatedTool(input) {
|
|
|
1937
1946
|
return imageResult({ ...pngSize(image), bytes: image.length }, image);
|
|
1938
1947
|
}
|
|
1939
1948
|
|
|
1949
|
+
// packages/cli/src/mcp/workflow.ts
|
|
1950
|
+
var WORKFLOW_URI = "skill://litedesign/workflow";
|
|
1951
|
+
var WORKFLOW_TEXT = `# litedesign workflow
|
|
1952
|
+
|
|
1953
|
+
- Call \`inspect\` first, always. Its summary includes the labels the human pinned on the page.
|
|
1954
|
+
- If the human refers to a part of the page by name ("the login button", "the altcha box"), call \`labels\` and use the pinned selector \u2014 never guess CSS selectors.
|
|
1955
|
+
- Labels are read-only: you read them; the human creates, renames, and removes them.
|
|
1956
|
+
- To show the human what you are editing, call \`overlay\` with \`focus\`. Set \`layers\` once at the start if needed; after that, send \`focus\` only \u2014 the human owns the layer toggles in the toolbar.
|
|
1957
|
+
- Findings carry kind: "violation" is a measured fact; "candidate" is a heuristic that can be a false positive \u2014 verify it with \`measure\` before changing code.
|
|
1958
|
+
`;
|
|
1959
|
+
|
|
1940
1960
|
// packages/cli/src/mcp/server.ts
|
|
1941
1961
|
async function guard(run2) {
|
|
1942
1962
|
try {
|
|
@@ -1960,6 +1980,18 @@ async function runMcpServer() {
|
|
|
1960
1980
|
console.error(`[litedesign] extension bridge disabled: ${reason}`);
|
|
1961
1981
|
}
|
|
1962
1982
|
const server = new McpServer({ name: "litedesign", version: VERSION });
|
|
1983
|
+
server.registerResource(
|
|
1984
|
+
"workflow",
|
|
1985
|
+
WORKFLOW_URI,
|
|
1986
|
+
{
|
|
1987
|
+
title: "Workflow",
|
|
1988
|
+
description: "How the agent should work with this server: inspect first, resolve the human's names through labels, spotlight edits with focus.",
|
|
1989
|
+
mimeType: "text/markdown"
|
|
1990
|
+
},
|
|
1991
|
+
async () => ({
|
|
1992
|
+
contents: [{ uri: WORKFLOW_URI, text: WORKFLOW_TEXT, mimeType: "text/markdown" }]
|
|
1993
|
+
})
|
|
1994
|
+
);
|
|
1963
1995
|
const viewport = z.object({
|
|
1964
1996
|
w: z.number().int().positive().describe("Viewport width in CSS pixels"),
|
|
1965
1997
|
h: z.number().int().positive().describe("Viewport height in CSS pixels")
|
|
@@ -1992,7 +2024,7 @@ async function runMcpServer() {
|
|
|
1992
2024
|
"inspect",
|
|
1993
2025
|
{
|
|
1994
2026
|
title: "Inspect page",
|
|
1995
|
-
description: 'Open a page and return a tree summary. Call this before measure, find_issues, overlay, screenshot, or screenshot_annotated. No image by default: the numbers the agent needs come from the DOM, so set screenshot to true only when you really need to see the page. With the extension driver this attaches to the active tab by itself; returns "awaiting_user" when no extension is connected and "mismatch" when the active tab is a different page.',
|
|
2027
|
+
description: 'Open a page and return a tree summary. Call this before measure, find_issues, overlay, screenshot, or screenshot_annotated. The summary includes labels: the count and unique names the human pinned onto parts of the page \u2014 when the human refers to a part by name, get its selector from the labels tool. The full workflow is available as the resource skill://litedesign/workflow. No image by default: the numbers the agent needs come from the DOM, so set screenshot to true only when you really need to see the page. With the extension driver this attaches to the active tab by itself; returns "awaiting_user" when no extension is connected and "mismatch" when the active tab is a different page.',
|
|
1996
2028
|
inputSchema: {
|
|
1997
2029
|
url: z.string().url(),
|
|
1998
2030
|
viewport,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rahmantech/litedesign",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Grid, gap, and alignment on the real web page, plus an MCP bridge for agents.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Muhammad Abdurrahman",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"zod": "^4.6.7"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@litedesign/core": "0.2.
|
|
25
|
+
"@litedesign/core": "0.2.1",
|
|
26
26
|
"@types/ws": "^8.18.2"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|