annotask 0.0.3 → 0.0.5

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 CHANGED
@@ -159,6 +159,21 @@ Start your dev server, then open:
159
159
  - **Class editing** — Add, remove, or modify CSS classes on elements
160
160
  - **Theme token editing** — Edit design tokens (colors, typography, spacing, borders) from a detected design spec
161
161
 
162
+ ### Viewport preview
163
+ - **Device presets** — Quickly switch between phone, tablet, and desktop viewports (iPhone SE, iPhone 14 Pro, iPad, Desktop, etc.)
164
+ - **Custom dimensions** — Set any width/height for the preview
165
+ - **Viewport-tagged tasks** — Every task records the viewport dimensions so the AI agent generates the correct responsive CSS
166
+
167
+ ### Accessibility checker
168
+ - **Page-level scanning** — Runs axe-core WCAG analysis on the entire page from the A11y panel
169
+ - **Violation cards** — Shows impact level, rule, description, and affected element count
170
+ - **One-click fix tasks** — Create tasks from violations with full context (HTML snippets, CSS selectors, source file/line, and fix suggestions)
171
+
172
+ ### AI agent context
173
+ - **Interaction history** — Optionally track user navigation and button/link clicks in the app so the AI agent understands how the user reached the current state
174
+ - **Element context** — Optionally capture ancestor layout chain (3 levels of parent display, flex-direction, gap, grid-template) and DOM subtree (3 levels of children with tag, classes, text) for each task
175
+ - **Breakpoint detection** — `/annotask-init` detects responsive breakpoints from Tailwind, Bootstrap, CSS variables, or media query patterns and includes them in the design spec
176
+
162
177
  ### Infrastructure
163
178
  - **Change reports** — Structured JSON of all changes, ready for agents to consume
164
179
  - **Task pipeline** — Create, review, accept, or deny design change tasks
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-XLNGAH3S.js";
5
5
  import {
6
6
  createAnnotaskServer
7
- } from "./chunk-JLOSPIJ4.js";
7
+ } from "./chunk-KB74TUGE.js";
8
8
 
9
9
  // src/server/standalone.ts
10
10
  import http from "http";
@@ -58,4 +58,4 @@ async function startStandaloneServer(options) {
58
58
  export {
59
59
  startStandaloneServer
60
60
  };
61
- //# sourceMappingURL=chunk-JPNMDGZN.js.map
61
+ //# sourceMappingURL=chunk-4IZECCVO.js.map
@@ -1,5 +1,7 @@
1
1
  // src/server/api.ts
2
- var MAX_BODY_SIZE = 1048576;
2
+ import fs from "fs";
3
+ import nodePath from "path";
4
+ var MAX_BODY_SIZE = 4194304;
3
5
  var VALID_TASK_STATUSES = /* @__PURE__ */ new Set(["pending", "applied", "review", "accepted", "denied"]);
4
6
  function readBody(req) {
5
7
  return new Promise((resolve, reject) => {
@@ -31,6 +33,25 @@ function sendError(res, status, message) {
31
33
  }
32
34
  function createAPIMiddleware(options) {
33
35
  return async (req, res, next) => {
36
+ if (req.url?.startsWith("/__annotask/screenshots/") && req.method === "GET") {
37
+ const filename = req.url.replace("/__annotask/screenshots/", "").replace(/\?.*$/, "");
38
+ if (!/^[a-zA-Z0-9_-]+\.png$/.test(filename)) {
39
+ res.statusCode = 400;
40
+ res.end("Invalid filename");
41
+ return;
42
+ }
43
+ const filePath = nodePath.join(options.projectRoot, ".annotask", "screenshots", filename);
44
+ if (!fs.existsSync(filePath)) {
45
+ res.statusCode = 404;
46
+ res.end("Not found");
47
+ return;
48
+ }
49
+ res.setHeader("Content-Type", "image/png");
50
+ res.setHeader("Cache-Control", "public, max-age=3600");
51
+ res.setHeader("Access-Control-Allow-Origin", "*");
52
+ res.end(fs.readFileSync(filePath));
53
+ return;
54
+ }
34
55
  if (!req.url?.startsWith("/__annotask/api/")) return next();
35
56
  const path3 = req.url.replace("/__annotask/api/", "");
36
57
  res.setHeader("Content-Type", "application/json");
@@ -83,6 +104,28 @@ function createAPIMiddleware(options) {
83
104
  res.end(JSON.stringify(options.addTask(body), null, 2));
84
105
  return;
85
106
  }
107
+ if (path3 === "screenshots" && req.method === "POST") {
108
+ let raw;
109
+ try {
110
+ raw = await readBody(req);
111
+ } catch {
112
+ return sendError(res, 413, "Request body too large");
113
+ }
114
+ const parsed = parseJSON(raw);
115
+ if (!parsed.ok) return sendError(res, 400, "Invalid JSON body");
116
+ const body = parsed.data;
117
+ if (!body.data || typeof body.data !== "string") return sendError(res, 400, "Missing data field");
118
+ const match = body.data.match(/^data:image\/png;base64,(.+)$/);
119
+ if (!match) return sendError(res, 400, "Invalid PNG data URL");
120
+ const buffer = Buffer.from(match[1], "base64");
121
+ if (buffer.length > 2 * 1024 * 1024) return sendError(res, 413, "Screenshot too large (max 2MB)");
122
+ const filename = `screenshot-${Date.now()}-${Math.random().toString(36).slice(2, 7)}.png`;
123
+ const dir = nodePath.join(options.projectRoot, ".annotask", "screenshots");
124
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
125
+ fs.writeFileSync(nodePath.join(dir, filename), buffer);
126
+ res.end(JSON.stringify({ filename }));
127
+ return;
128
+ }
86
129
  if (path3.startsWith("tasks/") && req.method === "PATCH") {
87
130
  const id = path3.replace("tasks/", "");
88
131
  let raw;
@@ -170,7 +213,7 @@ function createWSServer() {
170
213
  }
171
214
 
172
215
  // src/server/serve-shell.ts
173
- import fs from "fs";
216
+ import fs2 from "fs";
174
217
  import path from "path";
175
218
  import { fileURLToPath } from "url";
176
219
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -193,11 +236,11 @@ function createShellMiddleware() {
193
236
  res.end("Forbidden");
194
237
  return;
195
238
  }
196
- if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
239
+ if (!fs2.existsSync(fullPath) || !fs2.statSync(fullPath).isFile()) {
197
240
  const indexPath = path.join(shellDist, "index.html");
198
- if (fs.existsSync(indexPath)) {
241
+ if (fs2.existsSync(indexPath)) {
199
242
  res.setHeader("Content-Type", "text/html");
200
- res.end(fs.readFileSync(indexPath, "utf-8"));
243
+ res.end(fs2.readFileSync(indexPath, "utf-8"));
201
244
  return;
202
245
  }
203
246
  res.statusCode = 404;
@@ -215,12 +258,12 @@ function createShellMiddleware() {
215
258
  ".ico": "image/x-icon"
216
259
  };
217
260
  res.setHeader("Content-Type", contentTypes[ext] || "application/octet-stream");
218
- res.end(fs.readFileSync(fullPath));
261
+ res.end(fs2.readFileSync(fullPath));
219
262
  };
220
263
  }
221
264
 
222
265
  // src/server/state.ts
223
- import fs2 from "fs";
266
+ import fs3 from "fs";
224
267
  import path2 from "path";
225
268
  var DEFAULT_DESIGN_SPEC = {
226
269
  initialized: false,
@@ -241,15 +284,15 @@ function createProjectState(projectRoot, broadcast) {
241
284
  if (cachedDesignSpec !== null) return cachedDesignSpec;
242
285
  const specPath = path2.join(projectRoot, ".annotask", "design-spec.json");
243
286
  try {
244
- cachedDesignSpec = { initialized: true, ...JSON.parse(fs2.readFileSync(specPath, "utf-8")) };
287
+ cachedDesignSpec = { initialized: true, ...JSON.parse(fs3.readFileSync(specPath, "utf-8")) };
245
288
  } catch {
246
289
  cachedDesignSpec = DEFAULT_DESIGN_SPEC;
247
290
  }
248
291
  if (!specWatcher) {
249
292
  const configDir = path2.join(projectRoot, ".annotask");
250
293
  try {
251
- if (!fs2.existsSync(configDir)) fs2.mkdirSync(configDir, { recursive: true });
252
- specWatcher = fs2.watch(configDir, (_, filename) => {
294
+ if (!fs3.existsSync(configDir)) fs3.mkdirSync(configDir, { recursive: true });
295
+ specWatcher = fs3.watch(configDir, (_, filename) => {
253
296
  cachedDesignSpec = null;
254
297
  if (filename === "design-spec.json") broadcast("designspec:updated", null);
255
298
  });
@@ -265,15 +308,15 @@ function createProjectState(projectRoot, broadcast) {
265
308
  }
266
309
  function readTasks() {
267
310
  try {
268
- return JSON.parse(fs2.readFileSync(tasksPath, "utf-8"));
311
+ return JSON.parse(fs3.readFileSync(tasksPath, "utf-8"));
269
312
  } catch {
270
313
  return { version: "1.0", tasks: [] };
271
314
  }
272
315
  }
273
316
  function writeTasks(data) {
274
317
  const dir = path2.dirname(tasksPath);
275
- if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
276
- fs2.writeFileSync(tasksPath, JSON.stringify(data, null, 2));
318
+ if (!fs3.existsSync(dir)) fs3.mkdirSync(dir, { recursive: true });
319
+ fs3.writeFileSync(tasksPath, JSON.stringify(data, null, 2));
277
320
  }
278
321
  function addTask(task) {
279
322
  const data = readTasks();
@@ -289,7 +332,16 @@ function createProjectState(projectRoot, broadcast) {
289
332
  const task = data.tasks.find((t) => t.id === id);
290
333
  if (!task) return { error: "Task not found" };
291
334
  Object.assign(task, updates, { updatedAt: Date.now() });
292
- if (updates.status === "accepted") data.tasks = data.tasks.filter((t) => t.id !== id);
335
+ if (updates.status === "accepted") {
336
+ if (task.screenshot) {
337
+ const screenshotPath = path2.join(projectRoot, ".annotask", "screenshots", task.screenshot);
338
+ try {
339
+ if (fs3.existsSync(screenshotPath)) fs3.unlinkSync(screenshotPath);
340
+ } catch {
341
+ }
342
+ }
343
+ data.tasks = data.tasks.filter((t) => t.id !== id);
344
+ }
293
345
  writeTasks(data);
294
346
  broadcast("tasks:updated", data);
295
347
  return task;
@@ -308,6 +360,7 @@ function createAnnotaskServer(options) {
308
360
  const wsServer = createWSServer();
309
361
  const state = createProjectState(options.projectRoot, wsServer.broadcast);
310
362
  const apiMiddleware = createAPIMiddleware({
363
+ projectRoot: options.projectRoot,
311
364
  getReport: () => wsServer.getReport(),
312
365
  getConfig: () => state.getConfig(),
313
366
  getDesignSpec: () => state.getDesignSpec(),
@@ -337,4 +390,4 @@ export {
337
390
  createProjectState,
338
391
  createAnnotaskServer
339
392
  };
340
- //# sourceMappingURL=chunk-JLOSPIJ4.js.map
393
+ //# sourceMappingURL=chunk-KB74TUGE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/api.ts","../src/server/ws-server.ts","../src/server/serve-shell.ts","../src/server/state.ts","../src/server/index.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport fs from 'node:fs'\nimport nodePath from 'node:path'\n\nexport interface APIOptions {\n projectRoot: string\n getReport: () => unknown\n getConfig: () => unknown\n getDesignSpec: () => unknown\n getTasks: () => { version: string; tasks: any[] }\n updateTask: (id: string, updates: Record<string, unknown>) => unknown\n addTask: (task: Record<string, unknown>) => unknown\n}\n\nconst MAX_BODY_SIZE = 4_194_304\nconst VALID_TASK_STATUSES = new Set(['pending', 'applied', 'review', 'accepted', 'denied'])\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = ''\n let size = 0\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > MAX_BODY_SIZE) { req.destroy(); reject(new Error('Request body too large')); return }\n body += chunk.toString()\n })\n req.on('end', () => resolve(body))\n req.on('error', reject)\n })\n}\n\nfunction parseJSON(raw: string): { ok: true; data: unknown } | { ok: false } {\n try { return { ok: true, data: JSON.parse(raw) } } catch { return { ok: false } }\n}\n\nfunction sendError(res: ServerResponse, status: number, message: string) {\n res.statusCode = status\n res.end(JSON.stringify({ error: message }))\n}\n\nexport function createAPIMiddleware(options: APIOptions) {\n return async (req: IncomingMessage, res: ServerResponse, next: () => void) => {\n // Serve screenshots (outside /api/ path)\n if (req.url?.startsWith('/__annotask/screenshots/') && req.method === 'GET') {\n const filename = (req.url.replace('/__annotask/screenshots/', '')).replace(/\\?.*$/, '')\n if (!/^[a-zA-Z0-9_-]+\\.png$/.test(filename)) {\n res.statusCode = 400; res.end('Invalid filename'); return\n }\n const filePath = nodePath.join(options.projectRoot, '.annotask', 'screenshots', filename)\n if (!fs.existsSync(filePath)) {\n res.statusCode = 404; res.end('Not found'); return\n }\n res.setHeader('Content-Type', 'image/png')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.setHeader('Access-Control-Allow-Origin', '*')\n res.end(fs.readFileSync(filePath))\n return\n }\n\n if (!req.url?.startsWith('/__annotask/api/')) return next()\n\n const path = req.url.replace('/__annotask/api/', '')\n\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Access-Control-Allow-Origin', '*')\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type')\n res.setHeader('Cache-Control', 'no-cache')\n\n if (req.method === 'OPTIONS') { res.statusCode = 200; res.end(); return }\n\n if (path === 'report' && req.method === 'GET') {\n res.end(JSON.stringify(options.getReport() ?? { version: '1.0', changes: [] }, null, 2))\n return\n }\n\n if (path === 'config' && req.method === 'GET') {\n res.end(JSON.stringify(options.getConfig(), null, 2))\n return\n }\n\n if (path === 'design-spec' && req.method === 'GET') {\n res.end(JSON.stringify(options.getDesignSpec(), null, 2))\n return\n }\n\n if (path.startsWith('tasks') && !path.startsWith('tasks/') && req.method === 'GET') {\n const urlObj = new URL(req.url!, `http://${req.headers.host || 'localhost'}`)\n const mfeFilter = urlObj.searchParams.get('mfe')\n const taskData = options.getTasks()\n if (mfeFilter) {\n const filtered = { ...taskData, tasks: taskData.tasks.filter((t: any) => t.mfe === mfeFilter) }\n res.end(JSON.stringify(filtered, null, 2))\n } else {\n res.end(JSON.stringify(taskData, null, 2))\n }\n return\n }\n\n if (path === 'tasks' && req.method === 'POST') {\n let raw: string\n try { raw = await readBody(req) } catch { return sendError(res, 413, 'Request body too large') }\n const parsed = parseJSON(raw)\n if (!parsed.ok) return sendError(res, 400, 'Invalid JSON body')\n const body = parsed.data as Record<string, unknown>\n if (!body || typeof body !== 'object' || Array.isArray(body)) return sendError(res, 400, 'Request body must be a JSON object')\n if (typeof body.type !== 'string' || !body.type) return sendError(res, 400, 'Missing required field: type (string)')\n if (typeof body.description !== 'string') return sendError(res, 400, 'Missing required field: description (string)')\n res.end(JSON.stringify(options.addTask(body), null, 2))\n return\n }\n\n if (path === 'screenshots' && req.method === 'POST') {\n let raw: string\n try { raw = await readBody(req) } catch { return sendError(res, 413, 'Request body too large') }\n const parsed = parseJSON(raw)\n if (!parsed.ok) return sendError(res, 400, 'Invalid JSON body')\n const body = parsed.data as { data: string }\n if (!body.data || typeof body.data !== 'string') return sendError(res, 400, 'Missing data field')\n const match = body.data.match(/^data:image\\/png;base64,(.+)$/)\n if (!match) return sendError(res, 400, 'Invalid PNG data URL')\n const buffer = Buffer.from(match[1], 'base64')\n if (buffer.length > 2 * 1024 * 1024) return sendError(res, 413, 'Screenshot too large (max 2MB)')\n const filename = `screenshot-${Date.now()}-${Math.random().toString(36).slice(2, 7)}.png`\n const dir = nodePath.join(options.projectRoot, '.annotask', 'screenshots')\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })\n fs.writeFileSync(nodePath.join(dir, filename), buffer)\n res.end(JSON.stringify({ filename }))\n return\n }\n\n if (path.startsWith('tasks/') && req.method === 'PATCH') {\n const id = path.replace('tasks/', '')\n let raw: string\n try { raw = await readBody(req) } catch { return sendError(res, 413, 'Request body too large') }\n const parsed = parseJSON(raw)\n if (!parsed.ok) return sendError(res, 400, 'Invalid JSON body')\n const body = parsed.data as Record<string, unknown>\n if (!body || typeof body !== 'object' || Array.isArray(body)) return sendError(res, 400, 'Request body must be a JSON object')\n if (body.status !== undefined && !VALID_TASK_STATUSES.has(body.status as string)) {\n return sendError(res, 400, `Invalid status. Must be one of: ${[...VALID_TASK_STATUSES].join(', ')}`)\n }\n res.end(JSON.stringify(options.updateTask(id, body), null, 2))\n return\n }\n\n if (path === 'status' && req.method === 'GET') {\n res.end(JSON.stringify({ status: 'ok', tool: 'annotask' }))\n return\n }\n\n res.statusCode = 404\n res.end(JSON.stringify({ error: 'Not found' }))\n }\n}\n","import { WebSocketServer, WebSocket } from 'ws'\nimport type { IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\n\nexport interface AnnotaskWSServer {\n handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void\n broadcast: (event: string, data: unknown) => void\n getReport: () => unknown\n clients: Set<WebSocket>\n}\n\nexport function createWSServer(): AnnotaskWSServer {\n let currentReport: unknown = null\n const clients = new Set<WebSocket>()\n const wss = new WebSocketServer({ noServer: true })\n\n wss.on('connection', (ws) => {\n clients.add(ws)\n if (currentReport) {\n ws.send(JSON.stringify({ event: 'report:current', data: currentReport, timestamp: Date.now() }))\n }\n\n ws.on('message', (raw) => {\n try {\n const msg = JSON.parse(raw.toString())\n if (msg.event === 'report:updated') {\n currentReport = msg.data\n for (const client of clients) {\n if (client !== ws && client.readyState === WebSocket.OPEN) {\n client.send(JSON.stringify({ event: 'report:updated', data: msg.data, timestamp: Date.now() }))\n }\n }\n }\n if (msg.event === 'changes:cleared') {\n currentReport = null\n for (const client of clients) {\n if (client !== ws && client.readyState === WebSocket.OPEN) {\n client.send(JSON.stringify({ event: 'changes:cleared', data: null, timestamp: Date.now() }))\n }\n }\n }\n if (msg.event === 'get:report') {\n ws.send(JSON.stringify({ event: 'report:current', data: currentReport, timestamp: Date.now() }))\n }\n } catch {}\n })\n\n ws.on('close', () => { clients.delete(ws) })\n })\n\n return {\n handleUpgrade(req, socket, head) {\n wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req) })\n },\n broadcast(event, data) {\n const msg = JSON.stringify({ event, data, timestamp: Date.now() })\n for (const client of clients) {\n if (client.readyState === WebSocket.OPEN) client.send(msg)\n }\n },\n getReport() { return currentReport },\n clients,\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\nfunction findShellDist(): string {\n return path.resolve(__dirname, 'shell')\n}\n\nexport function createShellMiddleware() {\n const shellDist = findShellDist()\n\n return (req: IncomingMessage, res: ServerResponse, next: () => void) => {\n if (!req.url?.startsWith('/__annotask')) return next()\n\n // CORS for cross-port access (Webpack standalone server)\n res.setHeader('Access-Control-Allow-Origin', '*')\n\n let filePath = req.url.replace('/__annotask', '') || '/'\n const queryIndex = filePath.indexOf('?')\n if (queryIndex !== -1) filePath = filePath.slice(0, queryIndex)\n if (filePath === '/' || filePath === '') filePath = '/index.html'\n\n // Skip API and WS paths\n if (filePath.startsWith('/api/') || filePath === '/ws') return next()\n\n const fullPath = path.join(shellDist, filePath)\n\n if (!fullPath.startsWith(shellDist)) {\n res.statusCode = 403\n res.end('Forbidden')\n return\n }\n\n if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {\n const indexPath = path.join(shellDist, 'index.html')\n if (fs.existsSync(indexPath)) {\n res.setHeader('Content-Type', 'text/html')\n res.end(fs.readFileSync(indexPath, 'utf-8'))\n return\n }\n res.statusCode = 404\n res.end('Annotask shell not built. Run: pnpm build:shell')\n return\n }\n\n const ext = path.extname(fullPath)\n const contentTypes: Record<string, string> = {\n '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css',\n '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon',\n }\n res.setHeader('Content-Type', contentTypes[ext] || 'application/octet-stream')\n res.end(fs.readFileSync(fullPath))\n }\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nconst DEFAULT_DESIGN_SPEC = {\n initialized: false,\n version: '1.0' as const,\n framework: null,\n colors: [],\n typography: { families: [], scale: [], weights: [] },\n spacing: [],\n borders: { radius: [] },\n icons: null,\n components: null,\n}\n\nexport interface ProjectState {\n getDesignSpec: () => unknown\n getConfig: () => unknown\n getTasks: () => { version: string; tasks: any[] }\n addTask: (task: Record<string, unknown>) => unknown\n updateTask: (id: string, updates: Record<string, unknown>) => unknown\n dispose: () => void\n}\n\nexport function createProjectState(projectRoot: string, broadcast: (event: string, data: unknown) => void): ProjectState {\n let cachedDesignSpec: unknown = null\n let specWatcher: fs.FSWatcher | null = null\n const tasksPath = path.join(projectRoot, '.annotask', 'tasks.json')\n\n function getDesignSpec(): unknown {\n if (cachedDesignSpec !== null) return cachedDesignSpec\n const specPath = path.join(projectRoot, '.annotask', 'design-spec.json')\n try {\n cachedDesignSpec = { initialized: true, ...JSON.parse(fs.readFileSync(specPath, 'utf-8')) }\n } catch {\n cachedDesignSpec = DEFAULT_DESIGN_SPEC\n }\n if (!specWatcher) {\n const configDir = path.join(projectRoot, '.annotask')\n try {\n if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true })\n specWatcher = fs.watch(configDir, (_, filename) => {\n cachedDesignSpec = null\n if (filename === 'design-spec.json') broadcast('designspec:updated', null)\n })\n } catch { cachedDesignSpec = null }\n }\n return cachedDesignSpec ?? DEFAULT_DESIGN_SPEC\n }\n\n function getConfig(): unknown {\n const spec = getDesignSpec() as any\n return { initialized: !!spec?.initialized, ...spec }\n }\n\n function readTasks(): { version: string; tasks: any[] } {\n try { return JSON.parse(fs.readFileSync(tasksPath, 'utf-8')) } catch { return { version: '1.0', tasks: [] } }\n }\n\n function writeTasks(data: { version: string; tasks: any[] }) {\n const dir = path.dirname(tasksPath)\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })\n fs.writeFileSync(tasksPath, JSON.stringify(data, null, 2))\n }\n\n function addTask(task: Record<string, unknown>) {\n const data = readTasks()\n const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`\n const newTask = { id, status: 'pending', createdAt: Date.now(), updatedAt: Date.now(), ...task }\n data.tasks.push(newTask)\n writeTasks(data)\n broadcast('tasks:updated', data)\n return newTask\n }\n\n function updateTask(id: string, updates: Record<string, unknown>) {\n const data = readTasks()\n const task = data.tasks.find((t: any) => t.id === id)\n if (!task) return { error: 'Task not found' }\n Object.assign(task, updates, { updatedAt: Date.now() })\n if (updates.status === 'accepted') {\n if (task.screenshot) {\n const screenshotPath = path.join(projectRoot, '.annotask', 'screenshots', task.screenshot)\n try { if (fs.existsSync(screenshotPath)) fs.unlinkSync(screenshotPath) } catch {}\n }\n data.tasks = data.tasks.filter((t: any) => t.id !== id)\n }\n writeTasks(data)\n broadcast('tasks:updated', data)\n return task\n }\n\n function dispose() {\n if (specWatcher) { specWatcher.close(); specWatcher = null }\n }\n\n return { getDesignSpec, getConfig, getTasks: readTasks, addTask, updateTask, dispose }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport { createAPIMiddleware } from './api.js'\nimport { createWSServer, type AnnotaskWSServer } from './ws-server.js'\nimport { createShellMiddleware } from './serve-shell.js'\nimport { createProjectState, type ProjectState } from './state.js'\n\nexport interface AnnotaskServer {\n middleware: (req: IncomingMessage, res: ServerResponse, next: () => void) => void\n handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void\n broadcast: (event: string, data: unknown) => void\n getReport: () => unknown\n dispose: () => void\n}\n\nexport interface AnnotaskServerOptions {\n projectRoot: string\n}\n\nexport function createAnnotaskServer(options: AnnotaskServerOptions): AnnotaskServer {\n const wsServer = createWSServer()\n const state = createProjectState(options.projectRoot, wsServer.broadcast)\n\n const apiMiddleware = createAPIMiddleware({\n projectRoot: options.projectRoot,\n getReport: () => wsServer.getReport(),\n getConfig: () => state.getConfig(),\n getDesignSpec: () => state.getDesignSpec(),\n getTasks: () => state.getTasks(),\n addTask: (task) => state.addTask(task),\n updateTask: (id, updates) => state.updateTask(id, updates),\n })\n\n const shellMiddleware = createShellMiddleware()\n\n const middleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => {\n // API first, then shell (shell is SPA fallback)\n apiMiddleware(req, res, () => {\n shellMiddleware(req, res, next)\n })\n }\n\n return {\n middleware,\n handleUpgrade: (req, socket, head) => wsServer.handleUpgrade(req, socket, head),\n broadcast: (event, data) => wsServer.broadcast(event, data),\n getReport: () => wsServer.getReport(),\n dispose: () => state.dispose(),\n }\n}\n\nexport { createProjectState, type ProjectState } from './state.js'\nexport { createWSServer, type AnnotaskWSServer } from './ws-server.js'\nexport { createAPIMiddleware, type APIOptions } from './api.js'\nexport { createShellMiddleware } from './serve-shell.js'\n"],"mappings":"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","names":["path","fs","fs","path"]}
@@ -235,6 +235,50 @@ function bridgeClientScript() {
235
235
  document.addEventListener('keydown', onKeyDown, { capture: true });
236
236
  document.addEventListener('contextmenu', onContextMenu, { capture: true });
237
237
 
238
+ // \u2500\u2500 User Action Tracking (interact mode) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
239
+ // Tracks meaningful page actions (link/button clicks) so the LLM
240
+ // can understand what the user did to reach the current state.
241
+
242
+ var lastActionTs = 0;
243
+ function onUserAction(e) {
244
+ var el = e.target;
245
+ if (!el || !el.closest) return;
246
+
247
+ // Debounce \u2014 ignore clicks within 50ms (same gesture)
248
+ var now = Date.now();
249
+ if (now - lastActionTs < 50) return;
250
+ lastActionTs = now;
251
+
252
+ // Find the best description of what was clicked
253
+ var actionEl = el.closest('a, button, [role="button"], [role="tab"], [role="menuitem"], [role="link"], [type="submit"], tr[class*="row"], [data-pc-section="bodyrow"]');
254
+ var descEl = actionEl || el;
255
+
256
+ var tag = descEl.tagName.toLowerCase();
257
+ // Get concise text: prefer innerText of the immediate target, fall back to closest actionable
258
+ var text = '';
259
+ if (el.innerText) text = el.innerText.trim();
260
+ if (!text && descEl.innerText) text = descEl.innerText.trim();
261
+ text = text.split('
262
+ ')[0].substring(0, 60); // first line only
263
+
264
+ var href = '';
265
+ if (tag === 'a') href = descEl.getAttribute('href') || '';
266
+ else if (el.closest('a')) href = el.closest('a').getAttribute('href') || '';
267
+
268
+ // Get component context if available
269
+ var source = findSourceElement(descEl);
270
+ var data = getSourceData(source.sourceEl);
271
+
272
+ sendToShell('user:action', {
273
+ tag: tag,
274
+ text: text,
275
+ href: href,
276
+ component: data.component || '',
277
+ });
278
+ }
279
+
280
+ document.addEventListener('click', onUserAction, { capture: true });
281
+
238
282
  // \u2500\u2500 Route Tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
239
283
  var lastRoute = window.location.pathname;
240
284
 
@@ -832,4 +876,4 @@ function bridgeClientScript() {
832
876
  export {
833
877
  bridgeClientScript
834
878
  };
835
- //# sourceMappingURL=chunk-X72ZX6VB.js.map
879
+ //# sourceMappingURL=chunk-L3WQI25M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin/bridge-client.ts"],"sourcesContent":["/**\n * Client bridge script injected into the user's app.\n * Communicates with the Annotask shell via postMessage.\n * Handles all DOM interactions: hover, click, style reads/writes,\n * element resolution, layout scanning, etc.\n *\n * Returned as an inline string (same pattern as toggle-button.ts).\n */\nexport function bridgeClientScript(): string {\n return `\n(function() {\n // Don't run inside the Annotask shell\n if (window.location.pathname.startsWith('/__annotask')) return;\n // Don't run if already initialized\n if (window.__ANNOTASK_BRIDGE__) return;\n window.__ANNOTASK_BRIDGE__ = true;\n\n // ── Element Registry ──────────────────────────────────\n var eidCounter = 0;\n var eidMap = new Map(); // eid string → WeakRef<Element>\n var elToEid = new WeakMap(); // Element → eid string\n\n function getEid(el) {\n if (!el) return null;\n var existing = elToEid.get(el);\n if (existing) return existing;\n eidCounter++;\n var eid = 'e-' + eidCounter;\n eidMap.set(eid, new WeakRef(el));\n elToEid.set(el, eid);\n return eid;\n }\n\n function getEl(eid) {\n var ref = eidMap.get(eid);\n return ref ? ref.deref() || null : null;\n }\n\n // ── PostMessage Helpers ───────────────────────────────\n var shellOrigin = '*'; // Will be tightened on first shell message\n\n function sendToShell(type, payload, id) {\n var msg = { type: type, payload: payload || {}, source: 'annotask-client' };\n if (id) msg.id = id;\n window.parent.postMessage(msg, shellOrigin);\n }\n\n function respond(id, payload) {\n sendToShell(null, payload, id);\n // type is not needed for responses — shell matches by id\n }\n\n // ── Source Element Resolution ─────────────────────────\n function hasSourceAttr(el) {\n return el.hasAttribute && (el.hasAttribute('data-annotask-file') || el.hasAttribute('data-astro-source-file'));\n }\n\n function findSourceElement(el) {\n var c = el;\n while (c) {\n if (hasSourceAttr(c)) return { sourceEl: c, targetEl: el };\n c = c.parentElement;\n }\n return { sourceEl: el, targetEl: el };\n }\n\n function getSourceData(el) {\n // Prefer data-annotask-* attributes, fall back to data-astro-source-* (Astro framework)\n var file = el.getAttribute('data-annotask-file') || '';\n var line = el.getAttribute('data-annotask-line') || '';\n var component = el.getAttribute('data-annotask-component') || '';\n\n if (!file && el.getAttribute('data-astro-source-file')) {\n var astroFile = el.getAttribute('data-astro-source-file') || '';\n // Convert absolute path to project-relative by finding src/ prefix\n var srcIdx = astroFile.indexOf('/src/');\n file = srcIdx !== -1 ? astroFile.slice(srcIdx + 1) : astroFile;\n }\n if ((!line || line === '0') && el.getAttribute('data-astro-source-loc')) {\n // data-astro-source-loc format: \"line:col\"\n line = (el.getAttribute('data-astro-source-loc') || '').split(':')[0];\n }\n if (!component && file) {\n // Derive component name from file path\n var parts = file.split('/');\n var fileName = parts[parts.length - 1] || '';\n component = fileName.replace(/\\.[^.]+$/, '');\n }\n\n var mfe = el.getAttribute('data-annotask-mfe') || '';\n\n return { file: file, line: line, component: component, mfe: mfe };\n }\n\n function getRect(el) {\n var r = el.getBoundingClientRect();\n return { x: r.x, y: r.y, width: r.width, height: r.height };\n }\n\n // ── Interaction Mode ──────────────────────────────────\n var storedMode = '';\n try { storedMode = localStorage.getItem('annotask:mode') || ''; } catch(e) {}\n var currentMode = storedMode || 'select';\n var inspectModes = { select: true, pin: true };\n\n // ── Event Handlers ────────────────────────────────────\n var lastHoverEid = null;\n var rafPending = false;\n var pendingHoverData = null;\n\n function onMouseOver(e) {\n if (!inspectModes[currentMode]) return;\n var el = e.target;\n if (!el || el === document.documentElement || el === document.body) return;\n var eid = getEid(el);\n if (eid === lastHoverEid) return;\n lastHoverEid = eid;\n\n var source = findSourceElement(el);\n var data = getSourceData(source.sourceEl);\n\n pendingHoverData = {\n eid: eid,\n tag: el.tagName.toLowerCase(),\n file: data.file,\n component: data.component,\n rect: getRect(el)\n };\n\n if (!rafPending) {\n rafPending = true;\n requestAnimationFrame(function() {\n rafPending = false;\n if (pendingHoverData) sendToShell('hover:enter', pendingHoverData);\n });\n }\n }\n\n function onMouseOut(e) {\n if (!inspectModes[currentMode]) return;\n // Only fire leave when truly leaving all elements\n if (e.relatedTarget && e.relatedTarget !== document.documentElement) return;\n lastHoverEid = null;\n pendingHoverData = null;\n sendToShell('hover:leave', {});\n }\n\n function onClick(e) {\n if (!inspectModes[currentMode]) return;\n e.preventDefault();\n e.stopPropagation();\n\n var el = e.target;\n if (!el) return;\n\n var source = findSourceElement(el);\n var data = getSourceData(source.sourceEl);\n var targetEl = source.targetEl;\n var classes = typeof targetEl.className === 'string' ? targetEl.className : '';\n\n sendToShell('click:element', {\n eid: getEid(targetEl),\n sourceEid: getEid(source.sourceEl),\n file: data.file,\n line: data.line,\n component: data.component,\n mfe: data.mfe,\n tag: targetEl.tagName.toLowerCase(),\n classes: classes,\n rect: getRect(targetEl),\n shiftKey: e.shiftKey,\n clientX: e.clientX,\n clientY: e.clientY\n });\n }\n\n function onMouseDown(e) {\n if (!inspectModes[currentMode]) return;\n e.stopPropagation();\n }\n\n function onMouseUp(e) {\n if (currentMode !== 'highlight') return;\n var sel = document.getSelection();\n var text = sel ? sel.toString().trim() : '';\n if (!text || text.length < 2) return;\n var anchorEl = sel.anchorNode ? sel.anchorNode.parentElement : null;\n if (!anchorEl) return;\n var source = findSourceElement(anchorEl);\n var data = getSourceData(source.sourceEl);\n\n sendToShell('selection:text', {\n text: text,\n eid: getEid(anchorEl),\n file: data.file,\n line: parseInt(data.line) || 0,\n component: data.component,\n mfe: data.mfe,\n tag: anchorEl.tagName.toLowerCase()\n });\n }\n\n function onContextMenu(e) {\n if (currentMode === 'interact') return;\n e.preventDefault();\n var el = e.target;\n if (!el) return;\n var source = findSourceElement(el);\n var data = getSourceData(source.sourceEl);\n var targetEl = source.targetEl;\n var classes = typeof targetEl.className === 'string' ? targetEl.className : '';\n\n sendToShell('contextmenu:element', {\n eid: getEid(targetEl),\n sourceEid: getEid(source.sourceEl),\n file: data.file,\n line: data.line,\n component: data.component,\n mfe: data.mfe,\n tag: targetEl.tagName.toLowerCase(),\n classes: classes,\n rect: getRect(targetEl),\n shiftKey: false,\n clientX: e.clientX,\n clientY: e.clientY\n });\n }\n\n function onKeyDown(e) {\n if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {\n e.preventDefault();\n sendToShell('keydown', { key: e.key, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey });\n }\n }\n\n // Install event listeners\n document.addEventListener('mouseover', onMouseOver, { capture: true });\n document.addEventListener('mouseout', onMouseOut, { capture: true });\n document.addEventListener('mousedown', onMouseDown, { capture: true });\n document.addEventListener('click', onClick, { capture: true });\n document.addEventListener('mouseup', onMouseUp, { capture: true });\n document.addEventListener('keydown', onKeyDown, { capture: true });\n document.addEventListener('contextmenu', onContextMenu, { capture: true });\n\n // ── User Action Tracking (interact mode) ───────────────\n // Tracks meaningful page actions (link/button clicks) so the LLM\n // can understand what the user did to reach the current state.\n\n var lastActionTs = 0;\n function onUserAction(e) {\n var el = e.target;\n if (!el || !el.closest) return;\n\n // Debounce — ignore clicks within 50ms (same gesture)\n var now = Date.now();\n if (now - lastActionTs < 50) return;\n lastActionTs = now;\n\n // Find the best description of what was clicked\n var actionEl = el.closest('a, button, [role=\"button\"], [role=\"tab\"], [role=\"menuitem\"], [role=\"link\"], [type=\"submit\"], tr[class*=\"row\"], [data-pc-section=\"bodyrow\"]');\n var descEl = actionEl || el;\n\n var tag = descEl.tagName.toLowerCase();\n // Get concise text: prefer innerText of the immediate target, fall back to closest actionable\n var text = '';\n if (el.innerText) text = el.innerText.trim();\n if (!text && descEl.innerText) text = descEl.innerText.trim();\n text = text.split('\\n')[0].substring(0, 60); // first line only\n\n var href = '';\n if (tag === 'a') href = descEl.getAttribute('href') || '';\n else if (el.closest('a')) href = el.closest('a').getAttribute('href') || '';\n\n // Get component context if available\n var source = findSourceElement(descEl);\n var data = getSourceData(source.sourceEl);\n\n sendToShell('user:action', {\n tag: tag,\n text: text,\n href: href,\n component: data.component || '',\n });\n }\n\n document.addEventListener('click', onUserAction, { capture: true });\n\n // ── Route Tracking ────────────────────────────────────\n var lastRoute = window.location.pathname;\n\n function checkRoute() {\n var path = window.location.pathname;\n if (path !== lastRoute) {\n lastRoute = path;\n sendToShell('route:changed', { path: path });\n }\n }\n\n window.addEventListener('popstate', checkRoute);\n window.addEventListener('hashchange', checkRoute);\n setInterval(checkRoute, 2000); // safety net for pushState\n\n // ── Message Handler ───────────────────────────────────\n window.addEventListener('message', function(event) {\n var msg = event.data;\n if (!msg || msg.source !== 'annotask-shell') return;\n // Tighten origin on first shell message\n if (shellOrigin === '*' && event.origin) shellOrigin = event.origin;\n\n var type = msg.type;\n var payload = msg.payload || {};\n var id = msg.id;\n\n // ── Mode ──\n if (type === 'mode:set') {\n currentMode = payload.mode || 'select';\n try { localStorage.setItem('annotask:mode', currentMode); } catch(e) {}\n return;\n }\n\n // ── Ping ──\n if (type === 'bridge:ping') {\n respond(id, {});\n return;\n }\n\n // ── Element Resolution ──\n if (type === 'resolve:at-point') {\n var el = document.elementFromPoint(payload.x, payload.y);\n if (!el || el === document.documentElement || el === document.body) {\n respond(id, null);\n return;\n }\n var src = findSourceElement(el);\n var srcData = getSourceData(src.sourceEl);\n respond(id, {\n eid: getEid(src.targetEl),\n file: srcData.file,\n line: srcData.line,\n component: srcData.component,\n mfe: srcData.mfe,\n tag: src.sourceEl.tagName.toLowerCase(),\n rect: getRect(src.sourceEl),\n classes: typeof src.targetEl.className === 'string' ? src.targetEl.className : ''\n });\n return;\n }\n\n if (type === 'resolve:template-group') {\n var all = document.querySelectorAll(\n '[data-annotask-file=\"' + payload.file + '\"][data-annotask-line=\"' + payload.line + '\"]'\n );\n // Also check Astro source attributes\n if (all.length === 0 && payload.file && payload.line) {\n // Try matching by astro source attributes (absolute path ends with file, loc starts with line)\n var astroAll = document.querySelectorAll('[data-astro-source-file]');\n var matched = [];\n for (var ai = 0; ai < astroAll.length; ai++) {\n var af = astroAll[ai].getAttribute('data-astro-source-file') || '';\n var al = (astroAll[ai].getAttribute('data-astro-source-loc') || '').split(':')[0];\n if (af.endsWith('/' + payload.file) && al === payload.line) matched.push(astroAll[ai]);\n }\n if (matched.length > 0) all = matched;\n }\n var eids = [];\n var rects = [];\n for (var i = 0; i < all.length; i++) {\n if (all[i].tagName.toLowerCase() === payload.tagName) {\n eids.push(getEid(all[i]));\n rects.push(getRect(all[i]));\n }\n }\n respond(id, { eids: eids, rects: rects });\n return;\n }\n\n if (type === 'resolve:rect') {\n var rEl = getEl(payload.eid);\n respond(id, rEl ? { rect: getRect(rEl) } : null);\n return;\n }\n\n if (type === 'resolve:rects') {\n var results = [];\n for (var j = 0; j < payload.eids.length; j++) {\n var re = getEl(payload.eids[j]);\n results.push(re ? getRect(re) : null);\n }\n respond(id, { rects: results });\n return;\n }\n\n // ── Style Operations ──\n if (type === 'style:get-computed') {\n var sEl = getEl(payload.eid);\n if (!sEl) { respond(id, { styles: {} }); return; }\n var cs = window.getComputedStyle(sEl);\n var styles = {};\n for (var k = 0; k < payload.properties.length; k++) {\n styles[payload.properties[k]] = cs.getPropertyValue(payload.properties[k]);\n }\n respond(id, { styles: styles });\n return;\n }\n\n if (type === 'style:apply') {\n var aEl = getEl(payload.eid);\n if (!aEl) { respond(id, { before: '' }); return; }\n var before = window.getComputedStyle(aEl).getPropertyValue(payload.property);\n aEl.style.setProperty(payload.property, payload.value);\n respond(id, { before: before });\n return;\n }\n\n if (type === 'style:apply-batch') {\n var befores = [];\n for (var m = 0; m < payload.eids.length; m++) {\n var bEl = getEl(payload.eids[m]);\n if (bEl) {\n befores.push(window.getComputedStyle(bEl).getPropertyValue(payload.property));\n bEl.style.setProperty(payload.property, payload.value);\n } else {\n befores.push('');\n }\n }\n respond(id, { befores: befores });\n return;\n }\n\n if (type === 'style:undo') {\n var uEl = getEl(payload.eid);\n if (uEl) {\n if (payload.value) uEl.style.setProperty(payload.property, payload.value);\n else uEl.style.removeProperty(payload.property);\n }\n respond(id, {});\n return;\n }\n\n if (type === 'class:get') {\n var cEl = getEl(payload.eid);\n respond(id, { classes: cEl ? (typeof cEl.className === 'string' ? cEl.className : '') : '' });\n return;\n }\n\n if (type === 'class:set') {\n var csEl = getEl(payload.eid);\n if (!csEl) { respond(id, { before: '' }); return; }\n var classBefore = typeof csEl.className === 'string' ? csEl.className : '';\n csEl.className = payload.classes;\n respond(id, { before: classBefore });\n return;\n }\n\n if (type === 'class:set-batch') {\n var classBefores = [];\n for (var n = 0; n < payload.eids.length; n++) {\n var cbEl = getEl(payload.eids[n]);\n if (cbEl) {\n classBefores.push(typeof cbEl.className === 'string' ? cbEl.className : '');\n cbEl.className = payload.classes;\n } else {\n classBefores.push('');\n }\n }\n respond(id, { befores: classBefores });\n return;\n }\n\n if (type === 'class:undo') {\n var cuEl = getEl(payload.eid);\n if (cuEl) cuEl.className = payload.classes;\n respond(id, {});\n return;\n }\n\n // ── Classification ──\n if (type === 'classify:element') {\n var clEl = getEl(payload.eid);\n if (!clEl) { respond(id, null); return; }\n var tag = clEl.tagName.toLowerCase();\n var clCs = window.getComputedStyle(clEl);\n var display = clCs.display;\n var childCount = clEl.children.length;\n var isFlex = display.includes('flex');\n var isGrid = display.includes('grid');\n var semanticContainers = ['section','main','aside','nav','header','footer','article'];\n var role = 'content';\n if (clEl.hasAttribute('data-annotask-component')) {\n var comp = clEl.getAttribute('data-annotask-component') || '';\n if (comp && comp[0] === comp[0].toUpperCase() && comp[0] !== comp[0].toLowerCase()) {\n role = 'component';\n }\n }\n if (role !== 'component' && (isFlex || isGrid) && childCount > 0) role = 'container';\n if (role !== 'component' && role !== 'container' && semanticContainers.indexOf(tag) >= 0 && childCount > 0) role = 'container';\n\n respond(id, {\n role: role,\n display: display,\n isFlexContainer: isFlex,\n isGridContainer: isGrid,\n flexDirection: isFlex ? clCs.flexDirection : undefined,\n childCount: childCount,\n isComponentUnit: role === 'component'\n });\n return;\n }\n\n // ── Layout Scan ──\n if (type === 'layout:scan') {\n var layoutResults = [];\n var allEls = document.querySelectorAll('*');\n for (var li = 0; li < allEls.length; li++) {\n var lEl = allEls[li];\n if (lEl.nodeType !== 1) continue;\n var lCs = window.getComputedStyle(lEl);\n var lDisplay = lCs.display;\n if (!lDisplay.includes('flex') && !lDisplay.includes('grid')) continue;\n var lRect = lEl.getBoundingClientRect();\n if (lRect.width < 20 || lRect.height < 20) continue;\n var lIsGrid = lDisplay.includes('grid');\n var entry = {\n eid: getEid(lEl),\n display: lIsGrid ? 'grid' : 'flex',\n direction: lIsGrid ? 'grid' : lCs.flexDirection,\n rect: { x: lRect.x, y: lRect.y, width: lRect.width, height: lRect.height },\n columnGap: parseFloat(lCs.columnGap) || 0,\n rowGap: parseFloat(lCs.rowGap) || 0\n };\n if (lIsGrid) {\n var cols = lCs.gridTemplateColumns;\n var rows = lCs.gridTemplateRows;\n entry.templateColumns = cols;\n entry.templateRows = rows;\n if (cols && cols !== 'none') {\n entry.columns = cols.split(/\\\\s+/).map(function(s) { return parseFloat(s) || 0; }).filter(function(v) { return v > 0; });\n }\n if (rows && rows !== 'none') {\n entry.rows = rows.split(/\\\\s+/).map(function(s) { return parseFloat(s) || 0; }).filter(function(v) { return v > 0; });\n }\n }\n layoutResults.push(entry);\n }\n respond(id, { containers: layoutResults });\n return;\n }\n\n if (type === 'layout:add-track') {\n var ltEl = getEl(payload.eid);\n if (!ltEl) { respond(id, { property: '', before: '', after: '' }); return; }\n var ltCs = window.getComputedStyle(ltEl);\n var cssProp = payload.axis === 'col' ? 'grid-template-columns' : 'grid-template-rows';\n var ltBefore = ltCs.getPropertyValue(cssProp) || '';\n var ltAfter = ltBefore && ltBefore !== 'none' ? ltBefore + ' 1fr' : '1fr';\n ltEl.style.setProperty(cssProp, ltAfter);\n respond(id, { property: cssProp, before: ltBefore, after: ltAfter });\n return;\n }\n\n if (type === 'layout:add-child') {\n var lcEl = getEl(payload.eid);\n if (!lcEl) { respond(id, { childEid: '' }); return; }\n var child = document.createElement('div');\n child.style.minWidth = '60px';\n child.style.minHeight = '40px';\n child.style.border = '2px dashed #a855f7';\n child.style.borderRadius = '4px';\n child.style.background = 'rgba(168,85,247,0.05)';\n child.setAttribute('data-annotask-placeholder', 'true');\n lcEl.appendChild(child);\n respond(id, { childEid: getEid(child) });\n return;\n }\n\n // ── Theme ──\n if (type === 'theme:inject-css') {\n var existing = document.getElementById(payload.styleId);\n if (existing) {\n existing.textContent = payload.css;\n } else {\n var style = document.createElement('style');\n style.id = payload.styleId;\n style.textContent = payload.css;\n document.head.appendChild(style);\n }\n respond(id, {});\n return;\n }\n\n if (type === 'theme:remove-css') {\n var toRemove = document.getElementById(payload.styleId);\n if (toRemove) toRemove.remove();\n respond(id, {});\n return;\n }\n\n // ── Color Palette ──\n if (type === 'palette:scan-vars') {\n var swatches = [];\n try {\n var rootStyles = window.getComputedStyle(document.documentElement);\n var sheets = document.styleSheets;\n for (var si = 0; si < sheets.length; si++) {\n try {\n var rules = sheets[si].cssRules;\n for (var ri = 0; ri < rules.length; ri++) {\n var rule = rules[ri];\n if (rule.selectorText === ':root' || rule.selectorText === ':root, :host') {\n for (var pi = 0; pi < rule.style.length; pi++) {\n var prop = rule.style[pi];\n if (prop.startsWith('--')) {\n var val = rootStyles.getPropertyValue(prop).trim();\n if (val && isColor(val)) {\n swatches.push({ name: prop, value: val });\n }\n }\n }\n }\n }\n } catch(e) { /* cross-origin stylesheet */ }\n }\n } catch(e) {}\n respond(id, { swatches: swatches });\n return;\n }\n\n // ── Source Mapping Check ──\n if (type === 'check:source-mapping') {\n respond(id, { hasMapping: !!(document.querySelector('[data-annotask-file]') || document.querySelector('[data-astro-source-file]')) });\n return;\n }\n\n // ── Insert Placeholder ──\n if (type === 'insert:placeholder') {\n var ipTarget = getEl(payload.targetEid);\n if (!ipTarget) { respond(id, { placeholderEid: '' }); return; }\n var ipEl = createPlaceholder(payload);\n var ipRef = ipTarget;\n switch (payload.position) {\n case 'before': ipRef.parentElement && ipRef.parentElement.insertBefore(ipEl, ipRef); break;\n case 'after': ipRef.parentElement && ipRef.parentElement.insertBefore(ipEl, ipRef.nextSibling); break;\n case 'append': ipRef.appendChild(ipEl); break;\n case 'prepend': ipRef.insertBefore(ipEl, ipRef.firstChild); break;\n }\n // Match sibling sizes in flex/grid\n var insertParent = (payload.position === 'append' || payload.position === 'prepend') ? ipRef : ipRef.parentElement;\n if (insertParent) {\n var pCs = window.getComputedStyle(insertParent);\n if (pCs.display.includes('flex') || pCs.display.includes('grid')) {\n if (!ipEl.style.width) {\n var isRow = pCs.flexDirection === 'row' || pCs.flexDirection === 'row-reverse' || pCs.display.includes('grid');\n if (isRow) ipEl.style.flex = '1';\n }\n }\n }\n respond(id, { placeholderEid: getEid(ipEl) });\n return;\n }\n\n if (type === 'insert:remove') {\n var irEl = getEl(payload.eid);\n if (irEl) {\n var unmount = irEl.__annotask_unmount;\n if (unmount) unmount();\n irEl.remove();\n }\n respond(id, {});\n return;\n }\n\n if (type === 'move:element') {\n var meEl = getEl(payload.eid);\n var meTarget = getEl(payload.targetEid);\n if (meEl && meTarget) {\n switch (payload.position) {\n case 'before': meTarget.parentElement && meTarget.parentElement.insertBefore(meEl, meTarget); break;\n case 'after': meTarget.parentElement && meTarget.parentElement.insertBefore(meEl, meTarget.nextSibling); break;\n case 'append': meTarget.appendChild(meEl); break;\n case 'prepend': meTarget.insertBefore(meEl, meTarget.firstChild); break;\n }\n }\n respond(id, {});\n return;\n }\n\n if (type === 'insert:vue-component' || type === 'insert:component') {\n var vcTarget = getEl(payload.targetEid);\n if (!vcTarget) { respond(id, { eid: '', mounted: false }); return; }\n var vcContainer = document.createElement('div');\n vcContainer.setAttribute('data-annotask-placeholder', 'true');\n switch (payload.position) {\n case 'before': vcTarget.parentElement && vcTarget.parentElement.insertBefore(vcContainer, vcTarget); break;\n case 'after': vcTarget.parentElement && vcTarget.parentElement.insertBefore(vcContainer, vcTarget.nextSibling); break;\n case 'append': vcTarget.appendChild(vcContainer); break;\n case 'prepend': vcTarget.insertBefore(vcContainer, vcTarget.firstChild); break;\n }\n var mounted = tryMountComponent(vcContainer, payload.componentName, payload.props);\n respond(id, { eid: getEid(vcContainer), mounted: mounted });\n return;\n }\n\n // ── Get source data for an eid ──\n if (type === 'resolve:source') {\n var rsEl = getEl(payload.eid);\n if (!rsEl) { respond(id, null); return; }\n var rsSrc = findSourceElement(rsEl);\n var rsData = getSourceData(rsSrc.sourceEl);\n respond(id, {\n sourceEid: getEid(rsSrc.sourceEl),\n file: rsData.file,\n line: rsData.line,\n component: rsData.component,\n tag: rsSrc.sourceEl.tagName.toLowerCase()\n });\n return;\n }\n\n // ── Route ──\n if (type === 'route:current') {\n respond(id, { path: window.location.pathname });\n return;\n }\n });\n\n // ── Helpers ───────────────────────────────────────────\n function isColor(val) {\n if (val.startsWith('#') || val.startsWith('rgb') || val.startsWith('hsl')) return true;\n // Named colors — quick check with canvas\n try {\n var ctx = document.createElement('canvas').getContext('2d');\n ctx.fillStyle = '#000000';\n ctx.fillStyle = val;\n return ctx.fillStyle !== '#000000' || val === 'black' || val === '#000000';\n } catch(e) { return false; }\n }\n\n function createPlaceholder(payload) {\n var el = document.createElement(payload.tag);\n el.setAttribute('data-annotask-placeholder', 'true');\n if (payload.classes) el.className = payload.classes;\n var tag = payload.tag.toLowerCase();\n var isComponent = payload.tag.includes('-') || (payload.tag[0] === payload.tag[0].toUpperCase() && payload.tag[0] !== payload.tag[0].toLowerCase());\n\n if (tag === 'button') {\n el.textContent = payload.textContent || 'Button';\n el.style.cssText = 'padding:8px 16px;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;border:1px solid currentColor;background:var(--accent,#3b82f6);color:white;';\n } else if (tag === 'input') {\n el.type = 'text'; el.placeholder = 'Input field...';\n el.style.cssText = 'padding:8px 12px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;max-width:300px;background:white;color:#333;';\n } else if (tag === 'textarea') {\n el.placeholder = 'Text area...';\n el.style.cssText = 'padding:8px 12px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;min-height:60px;background:white;color:#333;resize:vertical;';\n } else if (tag === 'img') {\n el.style.cssText = 'width:200px;height:120px;background:#e5e7eb;border-radius:8px;display:flex;align-items:center;justify-content:center;';\n } else if (['h1','h2','h3','h4','h5','h6'].indexOf(tag) >= 0) {\n el.textContent = payload.textContent || 'Heading';\n var sizes = { h1:'2em', h2:'1.5em', h3:'1.25em', h4:'1.1em', h5:'1em', h6:'0.9em' };\n el.style.cssText = 'font-size:' + (sizes[tag]||'1em') + ';font-weight:700;margin:0.5em 0;';\n } else if (tag === 'p') {\n el.textContent = payload.textContent || 'Paragraph text goes here.';\n el.style.cssText = 'margin:0.5em 0;line-height:1.5;';\n } else if (tag === 'a') {\n el.textContent = payload.textContent || 'Link';\n el.style.cssText = 'color:var(--accent,#3b82f6);text-decoration:underline;cursor:pointer;';\n } else if (tag === 'section' || tag === 'div' || tag === 'nav' || tag === 'header' || tag === 'footer' || tag === 'aside' || tag === 'main') {\n if (!payload.classes && !payload.textContent) {\n el.style.cssText = 'min-height:40px;padding:12px;border:1.5px dashed rgba(59,130,246,0.3);border-radius:6px;background:rgba(59,130,246,0.03);';\n } else if (payload.textContent) {\n el.textContent = payload.textContent;\n }\n if (payload.category === 'layout-preset') {\n el.style.minHeight = '60px';\n el.style.padding = el.style.padding || '12px';\n el.style.border = '1.5px dashed rgba(34,197,94,0.3)';\n el.style.borderRadius = '6px';\n el.style.background = 'rgba(34,197,94,0.03)';\n }\n } else if (isComponent) {\n var vcMounted = tryMountComponent(el, payload.tag, payload.defaultProps);\n if (!vcMounted) {\n el.style.cssText = 'min-height:80px;padding:16px;border:1px solid rgba(168,85,247,0.2);border-radius:8px;background:rgba(168,85,247,0.03);display:flex;flex-direction:column;gap:8px;overflow:hidden;';\n var hdr = document.createElement('div');\n hdr.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:4px;';\n var dot = document.createElement('span');\n dot.style.cssText = 'width:6px;height:6px;border-radius:50%;background:#a855f7;';\n hdr.appendChild(dot);\n var tagLabel = document.createElement('span');\n tagLabel.style.cssText = 'font-size:11px;font-weight:600;color:#a855f7;';\n tagLabel.textContent = payload.tag;\n hdr.appendChild(tagLabel);\n el.appendChild(hdr);\n }\n } else {\n el.textContent = payload.textContent || '';\n }\n return el;\n }\n\n function tryMountComponent(container, componentName, props) {\n // Try Vue\n if (window.__ANNOTASK_VUE__) {\n var mounted = tryMountVueComponent(container, componentName, props);\n if (mounted) return true;\n }\n // Try React\n if (window.__ANNOTASK_REACT__) {\n var mounted = tryMountReactComponent(container, componentName, props);\n if (mounted) return true;\n }\n // Try Svelte\n if (window.__ANNOTASK_SVELTE__) {\n var mounted = tryMountSvelteComponent(container, componentName, props);\n if (mounted) return true;\n }\n return false;\n }\n\n function tryMountVueComponent(container, componentName, props) {\n try {\n var appEl = document.querySelector('#app');\n var vueApp = appEl && appEl.__vue_app__;\n if (!vueApp) return false;\n var annotaskVue = window.__ANNOTASK_VUE__;\n if (!annotaskVue || !annotaskVue.createApp || !annotaskVue.h) return false;\n var component = vueApp._context.components[componentName] || (window.__ANNOTASK_COMPONENTS__ && window.__ANNOTASK_COMPONENTS__[componentName]);\n if (!component) return false;\n var mountPoint = document.createElement('div');\n container.appendChild(mountPoint);\n var miniApp = annotaskVue.createApp({\n render: function() { return annotaskVue.h(component, props || {}); }\n });\n miniApp._context = vueApp._context;\n miniApp.mount(mountPoint);\n container.setAttribute('data-annotask-mounted', 'true');\n container.__annotask_unmount = function() { try { miniApp.unmount(); } catch(e) {} };\n return true;\n } catch(e) { return false; }\n }\n\n function tryMountReactComponent(container, componentName, props) {\n try {\n var annotaskReact = window.__ANNOTASK_REACT__;\n if (!annotaskReact || !annotaskReact.createElement || !annotaskReact.createRoot) return false;\n var component = window.__ANNOTASK_COMPONENTS__ && window.__ANNOTASK_COMPONENTS__[componentName];\n if (!component) return false;\n var mountPoint = document.createElement('div');\n container.appendChild(mountPoint);\n var root = annotaskReact.createRoot(mountPoint);\n root.render(annotaskReact.createElement(component, props || {}));\n container.setAttribute('data-annotask-mounted', 'true');\n container.__annotask_unmount = function() { try { root.unmount(); } catch(e) {} };\n return true;\n } catch(e) { return false; }\n }\n\n function tryMountSvelteComponent(container, componentName, props) {\n try {\n var annotaskSvelte = window.__ANNOTASK_SVELTE__;\n if (!annotaskSvelte || !annotaskSvelte.mount) return false;\n var component = window.__ANNOTASK_COMPONENTS__ && window.__ANNOTASK_COMPONENTS__[componentName];\n if (!component) return false;\n var mountPoint = document.createElement('div');\n container.appendChild(mountPoint);\n var instance = annotaskSvelte.mount(component, { target: mountPoint, props: props || {} });\n container.setAttribute('data-annotask-mounted', 'true');\n container.__annotask_unmount = function() {\n try {\n if (annotaskSvelte.unmount) annotaskSvelte.unmount(instance);\n } catch(e) {}\n };\n return true;\n } catch(e) { return false; }\n }\n\n // ── Ready ─────────────────────────────────────────────\n sendToShell('bridge:ready', { version: '1.0' });\n})();\n`.trim()\n}\n"],"mappings":";AAQO,SAAS,qBAA6B;AAC3C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAq2BP,KAAK;AACP;","names":[]}