@imenam/mcp-design 1.0.9 → 1.0.11

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.
Files changed (34) hide show
  1. package/README.md +54 -3
  2. package/dist/shared/feedback.d.ts +128 -0
  3. package/dist/shared/feedback.js +165 -0
  4. package/dist/shared/render.d.ts +7 -1
  5. package/dist/shared/render.js +32 -6
  6. package/dist/shared/schemas/feedback.schema.d.ts +185 -0
  7. package/dist/shared/schemas/feedback.schema.js +87 -0
  8. package/dist/shared/tree.d.ts +2 -0
  9. package/dist/shared/tree.js +4 -0
  10. package/dist/src/core/design-store.d.ts +1 -5
  11. package/dist/src/core/design-store.js +3 -56
  12. package/dist/src/core/feedback-store.d.ts +73 -0
  13. package/dist/src/core/feedback-store.js +308 -0
  14. package/dist/src/core/file-lock.d.ts +8 -0
  15. package/dist/src/core/file-lock.js +61 -0
  16. package/dist/src/core/screenshot.d.ts +3 -0
  17. package/dist/src/core/screenshot.js +28 -3
  18. package/dist/src/gui-worker.js +106 -3
  19. package/dist/src/index.js +175 -1
  20. package/dist/src/tools/delete-design.d.ts +3 -1
  21. package/dist/src/tools/delete-design.js +4 -1
  22. package/dist/src/tools/get-feedback.d.ts +3 -0
  23. package/dist/src/tools/get-feedback.js +62 -0
  24. package/dist/src/tools/mark-feedback-processed.d.ts +8 -0
  25. package/dist/src/tools/mark-feedback-processed.js +29 -0
  26. package/dist/src/version.d.ts +1 -1
  27. package/dist/src/version.js +1 -1
  28. package/gui/dist/assets/index-BXpZGU8f.css +1 -0
  29. package/gui/dist/assets/index-CgZSm3aX.js +15 -0
  30. package/gui/dist/assets/{search-CSwiX0lv.js → search-DnrsSC4m.js} +1 -1
  31. package/gui/dist/index.html +2 -2
  32. package/package.json +2 -2
  33. package/gui/dist/assets/index-DBZNXgF_.js +0 -10
  34. package/gui/dist/assets/index-PQqxUQLx.css +0 -1
package/dist/src/index.js CHANGED
@@ -10,6 +10,7 @@ import { GuiLauncher, createLogger, setupLogging } from "@imenam/mcp-gui-interfa
10
10
  import { APP_VERSION } from "./version.js";
11
11
  import { resolveLogDir } from "./core/log-dir.js";
12
12
  import { DesignStore } from "./core/design-store.js";
13
+ import { FeedbackStore } from "./core/feedback-store.js";
13
14
  import { NODE_DEFAULTS } from "./core/node-factory.js";
14
15
  import { EXPORT_FORMATS } from "./core/export.js";
15
16
  import { MIN_LINE_HEIGHT_PX } from "../shared/render.js";
@@ -32,6 +33,8 @@ import { groupDesigns } from "./tools/group-designs.js";
32
33
  import { ungroupNodes } from "./tools/ungroup-nodes.js";
33
34
  import { detachInstances } from "./tools/detach-instances.js";
34
35
  import { getChanges } from "./tools/get-changes.js";
36
+ import { getFeedback } from "./tools/get-feedback.js";
37
+ import { markFeedbackProcessed } from "./tools/mark-feedback-processed.js";
35
38
  import { markSynced } from "./tools/mark-synced.js";
36
39
  import { exportDesignTool } from "./tools/export-design.js";
37
40
  import { screenshotDesignTool } from "./tools/screenshot-design.js";
@@ -74,6 +77,7 @@ function getConfig() {
74
77
  }
75
78
  getConfig();
76
79
  const store = new DesignStore(rootDir);
80
+ const feedback = new FeedbackStore(store);
77
81
  const server = new Server({ name: "mcp-design", version: APP_VERSION }, { capabilities: { tools: {} } });
78
82
  // --- Cycle de vie de la GUI -------------------------------------------------
79
83
  let guiLauncher;
@@ -97,6 +101,20 @@ function reply(msg, type, data) {
97
101
  function replyError(msg, type, error) {
98
102
  broadcastToGUI({ type, correlationId: msg.correlationId, error, timestamp: new Date().toISOString() });
99
103
  }
104
+ /** État des remarques renvoyé à la GUI après chaque lecture ou modification. */
105
+ function remarksState() {
106
+ return { remarks: feedback.remarks(), rounds: feedback.rounds(), file: feedback.getFilePath() };
107
+ }
108
+ /** Prévient les autres onglets de la GUI que les remarques ont changé. */
109
+ function notifyRemarksChanged() {
110
+ broadcastToGUI({ type: "REMARKS_UPDATED", data: {}, timestamp: new Date().toISOString() });
111
+ }
112
+ /** Supprime un design et ses remarques. */
113
+ function deleteDesignAndRemarks(id) {
114
+ store.delete(id);
115
+ feedback.deleteDesign(id);
116
+ notifyRemarksChanged();
117
+ }
100
118
  /** Notifie la GUI qu'un design a changé côté agent, pour qu'elle se rafraîchisse. */
101
119
  function notifyDesignChanged(designId) {
102
120
  broadcastToGUI({
@@ -163,7 +181,7 @@ function handleIPCMessage(msg) {
163
181
  return;
164
182
  case "DELETE_DESIGN":
165
183
  try {
166
- store.delete(String(msg.data?.id));
184
+ deleteDesignAndRemarks(String(msg.data?.id));
167
185
  reply(msg, "DELETE_DESIGN_RESPONSE", { designs: store.list() });
168
186
  }
169
187
  catch (err) {
@@ -181,6 +199,108 @@ function handleIPCMessage(msg) {
181
199
  replyError(msg, "SET_DESIGN_GROUP_RESPONSE", err.message);
182
200
  }
183
201
  return;
202
+ case "LIST_REMARKS":
203
+ try {
204
+ reply(msg, "LIST_REMARKS_RESPONSE", remarksState());
205
+ }
206
+ catch (err) {
207
+ replyError(msg, "LIST_REMARKS_RESPONSE", err.message);
208
+ }
209
+ return;
210
+ case "ADD_REMARK":
211
+ try {
212
+ const remark = feedback.add({
213
+ designId: String(msg.data?.designId ?? ""),
214
+ target: msg.data?.target,
215
+ text: String(msg.data?.text ?? ""),
216
+ capture: msg.data?.capture === true,
217
+ });
218
+ notifyRemarksChanged();
219
+ reply(msg, "ADD_REMARK_RESPONSE", { remark, ...remarksState() });
220
+ }
221
+ catch (err) {
222
+ replyError(msg, "ADD_REMARK_RESPONSE", err.message);
223
+ }
224
+ return;
225
+ case "UPDATE_REMARK":
226
+ try {
227
+ const remark = feedback.updateText(String(msg.data?.id ?? ""), String(msg.data?.text ?? ""));
228
+ notifyRemarksChanged();
229
+ reply(msg, "UPDATE_REMARK_RESPONSE", { remark, ...remarksState() });
230
+ }
231
+ catch (err) {
232
+ replyError(msg, "UPDATE_REMARK_RESPONSE", err.message);
233
+ }
234
+ return;
235
+ case "DELETE_REMARK":
236
+ try {
237
+ feedback.delete(String(msg.data?.id ?? ""));
238
+ notifyRemarksChanged();
239
+ reply(msg, "DELETE_REMARK_RESPONSE", remarksState());
240
+ }
241
+ catch (err) {
242
+ replyError(msg, "DELETE_REMARK_RESPONSE", err.message);
243
+ }
244
+ return;
245
+ case "SUBMIT_FEEDBACK":
246
+ try {
247
+ const ids = Array.isArray(msg.data?.remarkIds) ? msg.data.remarkIds.map(String) : [];
248
+ const round = feedback.submit(ids, String(msg.data?.title ?? ""));
249
+ notifyRemarksChanged();
250
+ reply(msg, "SUBMIT_FEEDBACK_RESPONSE", { round, ...remarksState() });
251
+ }
252
+ catch (err) {
253
+ replyError(msg, "SUBMIT_FEEDBACK_RESPONSE", err.message);
254
+ }
255
+ return;
256
+ case "RENAME_ROUND":
257
+ try {
258
+ feedback.rename(Number(msg.data?.round), String(msg.data?.title ?? ""));
259
+ notifyRemarksChanged();
260
+ reply(msg, "RENAME_ROUND_RESPONSE", remarksState());
261
+ }
262
+ catch (err) {
263
+ replyError(msg, "RENAME_ROUND_RESPONSE", err.message);
264
+ }
265
+ return;
266
+ case "SET_REMARK_PROCESSED":
267
+ try {
268
+ feedback.setProcessed({
269
+ round: Number(msg.data?.round),
270
+ remarkIds: [String(msg.data?.remarkId ?? "")],
271
+ processed: msg.data?.processed === true,
272
+ by: "user",
273
+ });
274
+ notifyRemarksChanged();
275
+ reply(msg, "SET_REMARK_PROCESSED_RESPONSE", remarksState());
276
+ }
277
+ catch (err) {
278
+ replyError(msg, "SET_REMARK_PROCESSED_RESPONSE", err.message);
279
+ }
280
+ return;
281
+ case "SET_ROUNDS_PROCESSED":
282
+ try {
283
+ const numbers = msg.data?.round === undefined || msg.data?.round === null
284
+ ? feedback.rounds().map((round) => round.number)
285
+ : [Number(msg.data.round)];
286
+ for (const round of numbers) {
287
+ feedback.setProcessed({ round, processed: msg.data?.processed === true, by: "user" });
288
+ }
289
+ notifyRemarksChanged();
290
+ reply(msg, "SET_ROUNDS_PROCESSED_RESPONSE", remarksState());
291
+ }
292
+ catch (err) {
293
+ replyError(msg, "SET_ROUNDS_PROCESSED_RESPONSE", err.message);
294
+ }
295
+ return;
296
+ case "GET_CAPTURE":
297
+ try {
298
+ reply(msg, "GET_CAPTURE_RESPONSE", { png: feedback.readCapture(String(msg.data?.name ?? "")).toString("base64") });
299
+ }
300
+ catch (err) {
301
+ replyError(msg, "GET_CAPTURE_RESPONSE", err.message);
302
+ }
303
+ return;
184
304
  case "GET_CONFIG":
185
305
  reply(msg, "GET_CONFIG_RESPONSE", { dataDir: store.getDataDir(), version: APP_VERSION });
186
306
  return;
@@ -694,6 +814,51 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
694
814
  required: ["design_ids", "group"],
695
815
  },
696
816
  },
817
+ {
818
+ name: "get_feedback",
819
+ description: "Read the feedback the user wrote on the designs in the GUI. In \"Remarques\" mode the user pins remarks on a whole page, " +
820
+ "on an element or on a rectangular area, then clicks \"Terminer les retours\": the selected remarks are frozen into a numbered, " +
821
+ "named feedback round. Call this tool when the user says their feedback is done. By default it returns the oldest round that still " +
822
+ "has unprocessed remarks (the latest one once everything is processed): every remark with its id, its page, the element it targets " +
823
+ "(id, name, path, box) or the area and the elements inside it, its processing state, and the captures of elements and areas as " +
824
+ "images. It also lists every round with its progress. Reading never changes anything: once you have applied remarks, report it " +
825
+ "with mark_feedback_processed.",
826
+ inputSchema: {
827
+ type: "object",
828
+ properties: {
829
+ round: {
830
+ type: "number",
831
+ description: "Number of the feedback round to read (default: the oldest round still to process, else the latest one).",
832
+ },
833
+ captures: {
834
+ type: "boolean",
835
+ description: "Include the captures of elements and areas as images (default true). Set false to save context.",
836
+ },
837
+ },
838
+ },
839
+ },
840
+ {
841
+ name: "mark_feedback_processed",
842
+ description: "Report that you have handled remarks of a feedback round read with get_feedback. The user follows this progress in the GUI's " +
843
+ "feedback page, round by round: call it once the changes are actually made, with the ids of the remarks you handled and a short " +
844
+ "note of what you changed. Leave remark_ids out to mark every remark of the round.",
845
+ inputSchema: {
846
+ type: "object",
847
+ properties: {
848
+ round: { type: "number", description: "Number of the feedback round, as shown by get_feedback." },
849
+ remark_ids: {
850
+ type: "array",
851
+ items: { type: "string" },
852
+ description: "Ids of the handled remarks (rm_...), as listed by get_feedback. Omit to mark the whole round.",
853
+ },
854
+ note: {
855
+ type: "string",
856
+ description: "What you changed, in one or two sentences. Shown to the user next to each marked remark.",
857
+ },
858
+ },
859
+ required: ["round"],
860
+ },
861
+ },
697
862
  {
698
863
  name: "get_changes",
699
864
  description: "Show every node added, modified, removed or reordered since the last mark_synced — this is how you pick up what the user changed by hand in the GUI. Reading changes never acknowledges them; call mark_synced once the changes are integrated into the code.",
@@ -820,6 +985,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
820
985
  }
821
986
  case "delete_design": {
822
987
  const result = deleteDesign(store, params);
988
+ feedback.deleteDesign(result.designId);
989
+ notifyRemarksChanged();
823
990
  broadcastToGUI({ type: "DESIGNS_UPDATED", data: { designs: store.list() }, timestamp: new Date().toISOString() });
824
991
  return result;
825
992
  }
@@ -870,6 +1037,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
870
1037
  }
871
1038
  case "get_changes":
872
1039
  return getChanges(store, params);
1040
+ case "get_feedback":
1041
+ return getFeedback(feedback, params);
1042
+ case "mark_feedback_processed": {
1043
+ const result = markFeedbackProcessed(feedback, params);
1044
+ notifyRemarksChanged();
1045
+ return result;
1046
+ }
873
1047
  case "mark_synced": {
874
1048
  const result = markSynced(store, params);
875
1049
  notifyDesignChanged(result.design.id);
@@ -1,3 +1,5 @@
1
1
  import type { DesignStore } from "../core/design-store.js";
2
2
  import { type ToolResult } from "./types.js";
3
- export declare function deleteDesign(store: DesignStore, args: Record<string, unknown> | undefined): ToolResult;
3
+ export declare function deleteDesign(store: DesignStore, args: Record<string, unknown> | undefined): ToolResult & {
4
+ designId: string;
5
+ };
@@ -3,5 +3,8 @@ export function deleteDesign(store, args) {
3
3
  const id = requireString(args, "design_id");
4
4
  const design = store.get(id);
5
5
  store.delete(id);
6
- return textResult(`Design "${design.name}" (${id}) deleted, along with its ${design.nodes.length} node(s).`);
6
+ return {
7
+ ...textResult(`Design "${design.name}" (${id}) deleted, along with its ${design.nodes.length} node(s) and its remarks.`),
8
+ designId: id,
9
+ };
7
10
  }
@@ -0,0 +1,3 @@
1
+ import type { FeedbackStore } from "../core/feedback-store.js";
2
+ import type { ImageToolResult } from "./types.js";
3
+ export declare function getFeedback(feedback: FeedbackStore, args: Record<string, unknown> | undefined): ImageToolResult;
@@ -0,0 +1,62 @@
1
+ import { formatFeedbackRound, roundProgress } from "../../shared/feedback.js";
2
+ /**
3
+ * Retour de l'utilisateur : les remarques qu'il a transmises depuis la GUI avec
4
+ * « Terminer les retours », figées dans un retour numéroté et nommé.
5
+ *
6
+ * Sans numéro demandé, c'est le plus ancien retour encore à traiter, ou le
7
+ * dernier quand tout est traité. Lire un retour ne change rien : seul
8
+ * `mark_feedback_processed` déclare des remarques traitées.
9
+ */
10
+ const STATUS_LABELS = { pending: "to process", in_progress: "in progress", done: "processed" };
11
+ function roundLine(round) {
12
+ const progress = roundProgress(round);
13
+ return ` - round ${round.number} "${round.title}" (${round.submittedAt}): ${progress.done}/${progress.total} processed — ${STATUS_LABELS[progress.status]}`;
14
+ }
15
+ export function getFeedback(feedback, args) {
16
+ const rounds = feedback.rounds();
17
+ const drafts = feedback.remarks().filter((remark) => remark.status === "draft").length;
18
+ const draftNote = drafts > 0
19
+ ? `${drafts} remark(s) are still drafts in the GUI and are not part of any round yet.`
20
+ : "No draft remark is waiting in the GUI.";
21
+ if (rounds.length === 0) {
22
+ return {
23
+ content: [
24
+ {
25
+ type: "text",
26
+ text: `No feedback round has been submitted yet. ${draftNote} ` +
27
+ `Ask the user to switch the GUI to "Remarques" mode and click "Terminer les retours" once their remarks are written.`,
28
+ },
29
+ ],
30
+ };
31
+ }
32
+ const firstOpen = rounds.find((candidate) => roundProgress(candidate).status !== "done");
33
+ const requested = args?.round === undefined ? (firstOpen ?? rounds[rounds.length - 1]).number : Number(args.round);
34
+ const round = rounds.find((candidate) => candidate.number === requested);
35
+ if (!round) {
36
+ throw new Error(`Feedback round ${args?.round} not found. Rounds 1 to ${rounds.length} exist.`);
37
+ }
38
+ const withCaptures = args?.captures !== false;
39
+ const footer = [
40
+ `Feedback rounds (${rounds.length}):`,
41
+ ...rounds.map(roundLine),
42
+ draftNote,
43
+ withCaptures ? "" : "Captures were left out (captures=false).",
44
+ "Apply the remarks, then call mark_feedback_processed with the round and the ids of the remarks you handled, " +
45
+ "with a short note of what you changed: the user follows that progress in the GUI.",
46
+ ]
47
+ .filter((line) => line !== "")
48
+ .join("\n");
49
+ const content = [{ type: "text", text: `${formatFeedbackRound(round)}\n\n${footer}` }];
50
+ if (withCaptures) {
51
+ let image = 0;
52
+ for (const remark of round.remarks) {
53
+ if (!remark.capture)
54
+ continue;
55
+ image += 1;
56
+ const label = remark.number === null ? "whole page" : `#${remark.number}`;
57
+ content.push({ type: "text", text: `Image ${image} — remark ${label} on "${remark.designName}" (${remark.designId}).` });
58
+ content.push({ type: "image", data: feedback.readCapture(remark.capture).toString("base64"), mimeType: "image/png" });
59
+ }
60
+ }
61
+ return { content };
62
+ }
@@ -0,0 +1,8 @@
1
+ import type { FeedbackStore } from "../core/feedback-store.js";
2
+ import { type ToolResult } from "./types.js";
3
+ /**
4
+ * L'agent déclare traitées des remarques d'un retour. C'est ce que l'utilisateur
5
+ * suit dans la page des retours : lire un retour avec `get_feedback` ne marque
6
+ * rien, seul cet outil le fait.
7
+ */
8
+ export declare function markFeedbackProcessed(feedback: FeedbackStore, args: Record<string, unknown> | undefined): ToolResult;
@@ -0,0 +1,29 @@
1
+ import { roundProgress } from "../../shared/feedback.js";
2
+ import { textResult } from "./types.js";
3
+ /**
4
+ * L'agent déclare traitées des remarques d'un retour. C'est ce que l'utilisateur
5
+ * suit dans la page des retours : lire un retour avec `get_feedback` ne marque
6
+ * rien, seul cet outil le fait.
7
+ */
8
+ export function markFeedbackProcessed(feedback, args) {
9
+ const number = Number(args?.round);
10
+ if (!Number.isInteger(number) || number < 1)
11
+ throw new Error(`"round" is required and must be a feedback round number.`);
12
+ const rawIds = args?.remark_ids;
13
+ if (rawIds !== undefined && (!Array.isArray(rawIds) || rawIds.some((id) => typeof id !== "string"))) {
14
+ throw new Error(`"remark_ids" must be an array of remark ids, as listed by get_feedback.`);
15
+ }
16
+ const note = typeof args?.note === "string" ? args.note : null;
17
+ const round = feedback.setProcessed({
18
+ round: number,
19
+ remarkIds: rawIds,
20
+ processed: true,
21
+ by: "agent",
22
+ note,
23
+ });
24
+ const progress = roundProgress(round);
25
+ const marked = rawIds === undefined ? "Every remark" : `${rawIds.length} remark(s)`;
26
+ return textResult(`${marked} of feedback round ${round.number} "${round.title}" marked as processed. ` +
27
+ `${progress.done} of ${progress.total} remark(s) of this round are now processed` +
28
+ (progress.status === "done" ? " — the round is complete." : "."));
29
+ }
@@ -1 +1 @@
1
- export declare const APP_VERSION = "1.0.9";
1
+ export declare const APP_VERSION = "1.0.11";
@@ -1,2 +1,2 @@
1
1
  // Auto-generated version file - do not edit manually
2
- export const APP_VERSION = '1.0.9';
2
+ export const APP_VERSION = '1.0.11';
@@ -0,0 +1 @@
1
+ :root{--ink:#16161a;--ink-soft:#5c5c66;--ink-faint:#8e8e99;--paper:#fff;--shell:#f4f4f2;--line:#e0e0dc;--line-strong:#c9c9c3;--accent:#1b4dd6;--accent-soft:#e8edfc;--danger:#b4231f;--ok:#1f7a45;color:var(--ink);-webkit-font-smoothing:antialiased;font-family:Inter,system-ui,-apple-system,Segoe UI,sans-serif;font-size:14px}*{box-sizing:border-box}body,html,#root{height:100%;margin:0}body{background:var(--shell)}button{font:inherit;color:inherit;cursor:pointer}input,select,textarea{font:inherit;color:inherit}h1,h2{letter-spacing:.02em;margin:0;font-size:13px;font-weight:600}code{background:var(--shell);border-radius:3px;padding:0 3px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.app{grid-template-columns:232px 5px minmax(0,1fr) 5px 268px;height:100%;display:grid}.panel-resizer{background:var(--line);cursor:col-resize;touch-action:none;position:relative}.panel-resizer:after{content:"";position:absolute;inset:0 -3px}.panel-resizer:hover,.panel-resizer:active{background:var(--accent)}.sidebar,.right-rail{background:var(--paper);overflow-y:auto}.sidebar{border-right:1px solid var(--line);padding:14px}.right-rail{border-left:1px solid var(--line);flex-direction:column;display:flex}.workspace{flex-direction:column;min-width:0;display:flex;position:relative}.workspace-empty{color:var(--ink-faint);flex:1;place-items:center;display:grid}.toolbar{background:var(--paper);border-bottom:1px solid var(--line);align-items:center;gap:16px;padding:8px 14px;display:flex}.tool-grid{grid-template-columns:repeat(2,auto);align-content:center;justify-items:stretch;gap:4px;display:grid}.tool-row{align-items:center;gap:4px;display:flex}.tool-button{background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;width:30px;height:30px;padding:0;line-height:1;display:inline-flex}.tool-button:hover:not(:disabled){background:var(--shell)}.tool-button[data-active]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}.tool-button:disabled{opacity:.35;cursor:default}.tool-glyph{text-align:center;flex-shrink:0;width:15px;font-size:15px}.zoom-control{color:var(--ink-soft);align-items:center;gap:6px;font-size:12px;display:inline-flex}.zoom-control select{border:1px solid var(--line);background:var(--paper);border-radius:6px;padding:4px 6px}.toolbar-status{align-items:center;gap:8px;min-width:0;margin-left:auto;font-size:12px;display:flex}.status-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:260px;font-weight:600;overflow:hidden}.status-chip{white-space:nowrap;background:var(--shell);color:var(--ink-soft);border-radius:999px;padding:3px 8px}.status-chip[data-state=saved]{color:var(--ok)}.status-chip[data-state=error]{color:var(--danger)}.tool-palette{z-index:10;background:var(--paper);border:1px solid var(--line);border-radius:10px;flex-direction:column;align-items:center;gap:4px;padding:4px;display:flex;position:absolute;bottom:12px;left:12px;box-shadow:0 2px 10px #00000014}.tool-palette[data-collapsed]{opacity:.7}.tool-palette[data-collapsed]:hover,.tool-palette[data-collapsed]:focus-within{opacity:1}.tool-palette-collapse{width:100%;color:var(--ink-faint);background:0 0;border:none;border-radius:4px;padding:0;font-size:12px;line-height:14px}.tool-palette-collapse:hover{background:var(--shell);color:var(--ink)}.tool-palette .icon-picker{top:auto;bottom:calc(100% + 6px)}.canvas-viewport{touch-action:none;overscroll-behavior:contain;flex:1;position:relative;overflow:hidden}.canvas-viewport[data-pan],.canvas-viewport[data-pan] .canvas-surface,.canvas-viewport[data-pan] .canvas-node{cursor:grab}.canvas-stage{transform-origin:0 0;position:absolute;top:0;left:0;box-shadow:0 1px 2px #00000014,0 12px 32px #00000014}.canvas-surface{transform-origin:0 0;position:absolute;top:0;left:0;overflow:hidden}.canvas-surface[data-tool=select]{cursor:default}.canvas-surface:not([data-tool=select]){cursor:crosshair}.canvas-svg{display:block}.canvas-node[data-hidden]{visibility:hidden}.canvas-group{pointer-events:none}.canvas-group .canvas-node{pointer-events:auto}.canvas-surface[data-tool=select] .canvas-node{cursor:move}.selection-frame{fill:none;stroke:var(--accent);stroke-width:1px;pointer-events:none}.creation-preview{fill:#1b4dd614;stroke:var(--accent);stroke-width:1px;stroke-dasharray:4 3;pointer-events:none}.marquee{fill:#1b4dd60f;stroke:var(--accent);stroke-width:1px;pointer-events:none}.handle{background:var(--paper);border:1px solid var(--accent);border-radius:2px;width:9px;height:9px;margin:-5px 0 0 -5px;position:absolute}.text-editor{border:1px solid var(--accent);resize:none;white-space:pre-wrap;background:#ffffffeb;outline:none;margin:0;padding:0;position:absolute;overflow:hidden}.design-tree{border-radius:6px;gap:2px;min-height:60px;display:grid}.design-tree[data-drop]{box-shadow:inset 0 0 0 1px var(--accent)}.design-group{gap:2px;display:grid}.design-group-header{width:100%;color:var(--ink-soft);text-align:left;background:0 0;border:1px solid #0000;border-radius:6px;align-items:center;gap:6px;padding:5px 6px;font-size:12px;font-weight:600;display:flex}.design-group-header:hover{background:var(--shell)}.design-group[data-drop]{background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent);border-radius:6px}.design-group[data-drop]>.design-group-header,.design-group[data-drop]>.design-group-header .design-group-caret,.design-group[data-drop]>.design-group-header .design-group-count{color:var(--accent)}.design-group-caret{width:10px;color:var(--ink-faint)}.design-group-name{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.design-group-count{color:var(--ink-faint);font-size:10px;font-weight:500}.sidebar-header{justify-content:space-between;align-items:center;margin-bottom:12px;display:flex}.ghost-button{border:1px solid var(--line-strong);background:0 0;border-radius:6px;padding:4px 8px;font-size:12px}.ghost-button:hover{background:var(--shell)}.primary-button{background:var(--accent);color:#fff;border:none;border-radius:6px;padding:6px 10px;font-size:12px}.primary-button:disabled{opacity:.4;cursor:default}.create-form{background:var(--shell);border-radius:8px;gap:6px;margin-bottom:12px;padding:10px;display:grid}.create-form input,.create-form select{border:1px solid var(--line);background:var(--paper);border-radius:6px;padding:6px 8px}.design-list{gap:2px;margin:0;padding:0;list-style:none;display:grid}.design-list li{align-items:center;gap:2px;display:flex}.design-item{text-align:left;background:0 0;border:1px solid #0000;border-radius:6px;flex:1;gap:2px;min-width:0;padding:8px;display:grid}.design-item:hover{background:var(--shell)}.design-item[data-active]{background:var(--accent-soft);border-color:var(--accent)}.design-name{text-overflow:ellipsis;white-space:nowrap;font-weight:500;overflow:hidden}.design-meta{color:var(--ink-faint);font-size:11px}.design-badge{color:#8a5a00;background:#fdf1d6;border-radius:999px;justify-self:start;padding:1px 6px;font-size:10px}.icon-button{color:var(--ink-faint);background:0 0;border:none;border-radius:4px;padding:4px 6px;line-height:1}.icon-button:hover:not(:disabled){background:var(--shell);color:var(--ink)}.icon-button:disabled{opacity:.3;cursor:default}.empty-hint{color:var(--ink-faint);margin:12px 0 0;font-size:12px;line-height:1.5}.inspector{border-bottom:1px solid var(--line);align-content:start;gap:10px;padding:14px;display:grid}.inspector h2{justify-content:space-between;align-items:baseline;gap:8px;display:flex;overflow:hidden}.inspector-type{color:var(--ink-faint);flex-shrink:0;font-size:11px;font-weight:400}.field{gap:4px;min-width:0;display:grid}.field>span{color:var(--ink-soft);font-size:11px}.field input,.field select,.field textarea{border:1px solid var(--line);background:var(--paper);border-radius:6px;width:100%;padding:5px 7px}.field textarea{resize:vertical;font-family:inherit}.field-row{grid-template-columns:1fr 1fr;gap:8px;display:grid}.color-input{align-items:center;gap:6px;display:flex}.color-input input[type=color]{flex-shrink:0;width:30px;height:28px;padding:2px}.toggle-button{border:1px solid var(--line);background:var(--paper);border-radius:6px;padding:5px 7px}.toggle-button[data-active]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}.segmented{grid-template-columns:repeat(3,1fr);gap:4px;display:grid}.segmented button{border:1px solid var(--line);background:var(--paper);border-radius:6px;padding:5px 0}.segmented button[data-active]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}.inspector-actions{grid-template-columns:1fr 1fr;gap:6px;margin-top:4px;display:grid}.inspector-actions button{border:1px solid var(--line-strong);background:var(--paper);border-radius:6px;padding:6px 4px;font-size:12px}.inspector-actions button:hover{background:var(--shell)}.inspector-actions .danger{color:var(--danger);border-color:#e8c4c3}.layers{flex-direction:column;min-height:0;padding:14px;display:flex}.layers-header{justify-content:space-between;align-items:center;margin-bottom:8px;display:flex}.layers-actions{gap:2px;display:flex}.layers ul{align-content:start;gap:1px;margin:0;padding:0;list-style:none;display:grid;overflow-y:auto}.layer-row{align-items:center;gap:2px;display:flex}.layer-twisty{width:14px;color:var(--ink-faint);text-align:center;background:0 0;border:none;flex-shrink:0;padding:0;font-size:10px;line-height:1}.layer-twisty:hover{color:var(--ink)}.layer-count{color:var(--ink-faint);background:var(--shell);border-radius:999px;flex-shrink:0;margin-left:auto;padding:0 5px;font-size:10px}.layer-item[data-group] .layer-name{font-weight:600}.layer-item{text-align:left;background:0 0;border:1px solid #0000;border-radius:5px;align-items:baseline;gap:8px;width:100%;min-width:0;padding:5px 7px;display:flex}.layer-item:hover{background:var(--shell)}.layer-item[data-active]{background:var(--accent-soft);border-color:var(--accent)}.layer-kind{color:var(--ink-faint);flex-shrink:0;width:58px;font-size:10px}.layer-name{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.error-toast{background:var(--ink);color:#fff;border-radius:8px;align-items:center;gap:12px;padding:10px 14px;font-size:12px;display:flex;position:fixed;bottom:20px;left:50%;transform:translate(-50%);box-shadow:0 8px 24px #0003}.error-toast button{color:inherit;opacity:.7;background:0 0;border:none}.tool-popover-anchor{display:inline-flex;position:relative}.tool-grid>.tool-popover-anchor{display:flex}.icon-picker{z-index:20;background:var(--paper);border:1px solid var(--line-strong);border-radius:8px;width:320px;padding:10px;position:absolute;top:calc(100% + 6px);left:0;box-shadow:0 12px 32px #16161a2e}.inspector .icon-picker{left:auto;right:0}.icon-picker-search{border:1px solid var(--line);background:var(--paper);border-radius:6px;width:100%;padding:6px 8px;font-size:12px}.icon-picker-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:2px;max-height:300px;margin-top:8px;display:grid;overflow-y:auto}.icon-picker-cell{color:var(--ink);background:0 0;border:1px solid #0000;border-radius:6px;flex-direction:column;align-items:center;gap:4px;padding:8px 2px;display:flex}.icon-picker-cell:hover{background:var(--shell)}.icon-picker-cell[data-active]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}.icon-picker-cell span{text-overflow:ellipsis;white-space:nowrap;max-width:100%;color:var(--ink-faint);font-size:10px;overflow:hidden}.icon-field .toggle-button{justify-content:center;width:100%}:root{--remark:#6d3fd8;--remark-strong:#8b5cf6;--remark-soft:#efe7ff;--remark-line:#cdb8f5;--remark-sent:#a99bc9}.ui-icon{vertical-align:middle;flex-shrink:0;display:inline-block}.mode-switch{background:var(--shell);border:1px solid var(--line);border-radius:7px;gap:2px;padding:2px;display:inline-flex}.mode-switch button{color:var(--ink-soft);background:0 0;border:1px solid #0000;border-radius:5px;align-items:center;gap:6px;padding:4px 10px;font-size:12px;display:inline-flex}.mode-switch button:disabled{opacity:.4;cursor:default}.mode-switch button[data-active]{background:var(--paper);border-color:var(--line-strong);color:var(--ink);font-weight:600}.mode-switch button[data-mode=remarks][data-active]{border-color:var(--remark-line);color:var(--remark)}.mode-count{background:var(--remark);color:#fff;text-align:center;border-radius:999px;min-width:18px;padding:0 5px;font-size:10px;font-weight:700;line-height:16px}.remark-badge{background:var(--remark-soft);color:var(--remark);vertical-align:1px;border-radius:999px;align-items:center;gap:3px;margin-left:6px;padding:0 6px;font-size:10px;font-weight:600;line-height:16px;display:inline-flex}.canvas-surface[data-mode=remarks]{cursor:crosshair}.canvas-surface[data-mode=remarks] .canvas-node{cursor:pointer}.remark-shapes rect{pointer-events:none}.remark-frame{fill:#6d3fd80d;stroke:var(--remark-strong);stroke-width:1.5px}.remark-frame[data-status=sent]{fill:none;stroke:var(--remark-sent);stroke-dasharray:5 4}.remark-frame[data-highlighted]{fill:#6d3fd81a;stroke:var(--remark);stroke-width:2px;stroke-dasharray:none}.remark-hover{fill:none;stroke:var(--remark-strong);stroke-width:1.5px}.remark-aim{fill:#6d3fd812;stroke:var(--remark);stroke-width:2px}.remark-area-draft{fill:#8b5cf624;stroke:var(--remark-strong);stroke-width:1.5px;stroke-dasharray:5 3;pointer-events:none}.remark-hover-tag{background:var(--remark);color:#fff;white-space:nowrap;text-overflow:ellipsis;pointer-events:none;border-radius:4px;max-width:320px;padding:2px 7px;font-size:11px;font-weight:500;position:absolute;overflow:hidden;transform:translateY(calc(-100% - 2px))}.remark-pin{z-index:2;background:var(--remark);color:#fff;border:2px solid #fff;border-radius:50%;width:24px;height:24px;margin:-12px 0 0 -12px;padding:0;font-size:11px;font-weight:700;line-height:20px;position:absolute;box-shadow:0 1px 4px #00000040}.remark-pin[data-status=sent]{border-color:var(--remark-sent);color:var(--remark-sent);background:#fff}.remark-pin[data-missing]{border-style:dashed}.remark-pin[data-highlighted]{box-shadow:0 0 0 3px #6d3fd859,0 1px 4px #00000040}.remark-page-chip{border:1px solid var(--remark-line);background:var(--remark-soft);color:var(--remark);border-radius:999px;align-items:center;gap:6px;padding:3px 10px;font-size:11px;font-weight:500;display:inline-flex;position:absolute;top:-30px;left:0}.remark-help{z-index:10;background:var(--ink);color:#fff;white-space:nowrap;pointer-events:none;border-radius:999px;padding:7px 16px;font-size:11.5px;position:absolute;bottom:12px;left:50%;transform:translate(-50%)}.remark-composer{z-index:15;background:var(--paper);border:1px solid var(--line);cursor:default;border-radius:8px;gap:10px;width:340px;padding:0 14px 14px;display:grid;position:absolute;box-shadow:0 12px 32px #16161a2e}.remark-composer-head{border-bottom:1px solid var(--line);color:var(--remark);align-items:center;gap:8px;margin:0 -14px;padding:8px 8px 8px 14px;display:flex}.remark-composer-head h2{color:var(--ink);flex:1}.remark-composer-section{gap:6px;display:grid}.remark-breadcrumb{flex-wrap:wrap;align-items:center;gap:4px;font-size:12px;display:flex}.remark-breadcrumb-sep{color:var(--ink-faint);margin-left:4px}.link-button{color:var(--accent);background:0 0;border:none;padding:0}.link-button:hover{text-decoration:underline}.remark-chip{white-space:nowrap;background:var(--remark-soft);color:var(--remark);border-radius:999px;flex-shrink:0;align-items:center;gap:5px;padding:2px 9px;font-size:11.5px;font-weight:600;display:inline-flex}.remark-hint{color:var(--ink-faint);margin:0;font-size:11px}.section-label{letter-spacing:.04em;text-transform:uppercase;color:var(--ink-faint);font-size:10.5px;font-weight:600}.remark-preview{object-fit:contain;background:var(--shell);border:1px solid var(--line);border-radius:6px;width:100%;max-height:140px;display:block}.remark-hits-head{justify-content:space-between;align-items:baseline;margin-top:4px;display:flex}.remark-hits{gap:2px;max-height:140px;margin:0;padding:0;list-style:none;display:grid;overflow-y:auto}.remark-hits label,.remark-check{align-items:center;gap:8px;font-size:12px;display:flex}.remark-hits label[data-off]{color:var(--ink-faint)}.remark-hit-name{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.remark-hit-kind{color:var(--ink-faint);flex-shrink:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10.5px}.remark-textarea{border:1px solid var(--line-strong);background:var(--paper);resize:vertical;border-radius:6px;width:100%;padding:7px 9px;font-size:12.5px;line-height:1.45}.remark-textarea:focus{border-color:var(--accent);outline:none}.remark-composer-foot{align-items:center;gap:6px;display:flex}.remark-composer-foot .remark-hint{flex:1}.remarks-panel{flex-direction:column;flex:1;min-height:0;display:flex}.remarks-header{border-bottom:1px solid var(--line);justify-content:space-between;align-items:baseline;padding:14px;display:flex}.remarks-body{flex:1;align-content:start;gap:10px;min-height:0;padding:14px;display:grid;overflow-y:auto}.remark-add-page{border:1px solid var(--line-strong);background:var(--paper);text-align:left;border-radius:6px;align-items:center;gap:8px;padding:7px 10px;font-size:12.5px;display:flex}.remark-add-page:hover{background:var(--shell)}.remark-card{border:1px solid var(--line);background:var(--paper);border-radius:8px;gap:8px;padding:10px 12px;display:grid}.remark-card[data-status=sent]{background:#fafaf9}.remark-card[data-status=sent] .remark-text{color:var(--ink-soft)}.remark-card[data-highlighted]{border-color:var(--remark-strong);box-shadow:inset 0 0 0 1px var(--remark-strong);background:#faf7ff}.remark-card[data-editing]{border-color:var(--accent)}.remark-card-head{align-items:center;gap:8px;min-width:0;display:flex}.remark-number{background:var(--remark);color:#fff;text-align:center;border-radius:50%;flex-shrink:0;width:20px;height:20px;font-size:10.5px;font-weight:700;line-height:20px}.remark-number[data-status=sent]{box-shadow:inset 0 0 0 1.5px var(--remark-sent);color:var(--remark-sent);background:#fff}.remark-target{min-width:0;color:var(--ink-soft);flex:1;align-items:center;gap:5px;font-size:11.5px;font-weight:500;display:flex}.remark-target span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.remark-card-tools{flex-shrink:0;align-items:center;gap:2px;margin-left:auto;display:flex}.remark-status{background:var(--shell);color:var(--ink-soft);white-space:nowrap;border-radius:999px;justify-self:start;padding:1px 7px;font-size:10.5px}.remark-text{white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:12.5px;line-height:1.45}.remark-area-info{align-items:flex-start;gap:10px;display:flex}.remark-area-info img{object-fit:contain;background:var(--shell);border:1px solid var(--line);border-radius:4px;flex-shrink:0;width:112px;max-height:64px}.remark-card-actions{justify-content:flex-end;gap:6px;display:flex}.remarks-footer{border-top:1px solid var(--line);text-align:center;gap:8px;padding:14px;display:grid}.remarks-footer .primary-button{justify-content:center;align-items:center;gap:8px;padding:9px 10px;font-size:13px;font-weight:600;display:inline-flex}.dialog-backdrop{z-index:50;background:#16161a6b;place-items:center;padding:24px;display:grid;position:fixed;inset:0}.dialog{background:var(--paper);border-radius:10px;grid-template-columns:minmax(0,1fr);gap:10px;width:min(680px,100%);max-height:100%;padding:24px 28px;display:grid;overflow-y:auto;box-shadow:0 24px 64px #00000040}.dialog-head{justify-content:space-between;align-items:center;display:flex}.dialog-head h2{letter-spacing:0;font-size:18px}.dialog-intro{color:var(--ink-soft);margin:0 0 6px;font-size:12.5px}.scope-choice{grid-template-columns:1fr 1fr;gap:10px;margin-bottom:6px;display:grid}.scope-choice button{border:1px solid var(--line);background:var(--paper);color:var(--ink-faint);text-align:left;border-radius:8px;align-items:center;gap:12px;padding:10px 14px;display:flex}.scope-choice button span{gap:2px;display:grid}.scope-choice strong{color:var(--ink);font-weight:500}.scope-choice button[data-active]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}.scope-choice button:disabled{opacity:.4;cursor:default}.dialog-row-head{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.dialog-row-head .section-label{flex-shrink:0}.finish-list{border:1px solid var(--line);border-radius:8px;max-height:240px;margin:0 0 6px;padding:0;list-style:none;overflow-y:auto}.finish-list li+li{border-top:1px solid #efefeb}.finish-list label{color:var(--ink-faint);align-items:center;gap:12px;padding:11px 14px;font-size:12.5px;display:flex}.finish-list .empty-hint{margin:0;padding:12px 14px}.kind-pill{background:var(--remark-soft);min-width:80px;color:var(--remark);text-align:center;border-radius:999px;flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:500}.kind-pill[data-kind=node]{background:var(--accent-soft);color:var(--accent)}.kind-pill[data-kind=area]{color:#8a5a00;background:#fdf1d6}.finish-list label[data-off] .kind-pill{background:var(--shell);color:var(--ink-faint)}.finish-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--ink);flex:1;overflow:hidden}.finish-list label[data-off] .finish-text{color:var(--ink-faint)}.finish-where{text-overflow:ellipsis;white-space:nowrap;flex-shrink:0;max-width:200px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow:hidden}.finish-file{text-overflow:ellipsis;white-space:nowrap;direction:rtl;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden}.finish-preview{border:1px solid var(--line);background:var(--shell);white-space:pre-wrap;border-radius:8px;max-height:180px;margin:0;padding:12px 14px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.6;overflow:auto}.dialog-foot{justify-content:flex-end;align-items:center;gap:8px;margin-top:8px;display:flex}.dialog-foot>.remark-hint{flex:1;align-items:center;gap:6px;display:inline-flex}.dialog-foot .primary-button,.dialog-foot .ghost-button{align-items:center;gap:8px;padding:8px 14px;font-size:12.5px;display:inline-flex}.dialog-done{color:var(--ok);justify-items:start;gap:8px;display:grid}.dialog-done p{color:var(--ink);margin:0}.dialog-done .dialog-foot{justify-self:stretch}.round-title-field{gap:6px;margin-bottom:6px;display:grid}.round-title-field input{border:1px solid var(--line-strong);border-radius:6px;padding:8px 10px;font-size:13px}.round-title-field input:focus{border-color:var(--accent);outline:none}.remark-status[data-processed]{color:var(--ok);background:#e6f3ec}.rounds-button{border:1px solid var(--line);background:var(--paper);color:var(--ink-soft);border-radius:7px;align-items:center;gap:6px;padding:5px 10px;font-size:12px;display:inline-flex}.rounds-button:hover{background:var(--shell);color:var(--ink)}.rounds-count{color:#8a5a00;text-align:center;background:#fdf1d6;border-radius:999px;min-width:18px;padding:0 5px;font-size:10px;font-weight:700;line-height:16px}.feedback-page{z-index:40;background:var(--shell);flex-direction:column;display:flex;position:fixed;inset:0}.feedback-page-head{background:var(--paper);border-bottom:1px solid var(--line);align-items:center;gap:16px;padding:10px 16px;display:flex}.feedback-page-head h1{letter-spacing:0;font-size:15px}.feedback-empty{max-width:480px;color:var(--ink-faint);text-align:center;margin:48px auto}.feedback-page-body{flex:1;grid-template-columns:320px minmax(0,1fr);min-height:0;display:grid}.round-list{background:var(--paper);border-right:1px solid var(--line);align-content:start;gap:8px;margin:0;padding:14px;list-style:none;display:grid;overflow-y:auto}.round-row{border:1px solid var(--line);background:var(--paper);text-align:left;border-radius:8px;gap:6px;width:100%;padding:10px 12px;display:grid}.round-row:hover{background:var(--shell)}.round-row[data-active]{background:var(--accent-soft);border-color:var(--accent)}.round-row-title{text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.round-progress{align-items:center;gap:8px;display:flex}.round-status{color:#8a5a00;background:#fdf1d6;border-radius:999px;flex-shrink:0;padding:1px 8px;font-size:10.5px;font-weight:600}.round-status[data-status=in_progress]{background:var(--accent-soft);color:var(--accent)}.round-status[data-status=done]{color:var(--ok);background:#e6f3ec}.round-bar{background:var(--line);border-radius:2px;flex:1;height:4px;overflow:hidden}.round-bar span{background:var(--ok);height:100%;display:block}.round-detail{align-content:start;gap:10px;padding:20px 28px 40px;display:grid;overflow-y:auto}.round-title-input{background:0 0;border:1px solid #0000;border-radius:6px;width:100%;max-width:640px;margin-left:-8px;padding:4px 8px;font-size:20px;font-weight:600}.round-title-input:hover{border-color:var(--line)}.round-title-input:focus{border-color:var(--accent);background:var(--paper);outline:none}.round-detail-progress{align-items:center;gap:16px;display:flex}.round-detail-progress .round-progress{flex:1;max-width:420px}.round-detail-progress button,.feedback-page-all{align-items:center;gap:6px;display:inline-flex}.feedback-page-all{margin-left:auto}.round-page{gap:8px;margin-top:12px;display:grid}.round-page-head{align-items:baseline;gap:12px;display:flex}.round-remarks{gap:8px;margin:0;padding:0;list-style:none;display:grid}.round-remark{border:1px solid var(--line);background:var(--paper);border-radius:8px;align-items:flex-start;gap:12px;padding:12px 14px;display:flex}.round-remark[data-processed]{background:#fafaf9}.round-remark[data-processed] .remark-text{color:var(--ink-soft)}.round-remark-check input{cursor:pointer;width:16px;height:16px;margin:2px 0 0}.round-remark-body{flex:1;gap:6px;min-width:0;display:grid}.round-remark-head{align-items:center;gap:10px;min-width:0;display:flex}.round-remark-target{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--ink-soft);font-size:11.5px;overflow:hidden}.round-remark-state{color:#8a5a00;align-items:center;gap:6px;margin:0;font-size:11.5px;display:flex}.round-remark[data-processed] .round-remark-state{color:var(--ok)}.round-remark-note{color:var(--ink-soft)}.round-remark-capture img{object-fit:contain;background:var(--shell);border:1px solid var(--line);border-radius:6px;width:160px;max-height:90px;display:block}