dsh-claude-design 2.8.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1,4 +1,4 @@
1
- // dsh-claude-design 2.8.2: built from baoyu-design.
1
+ // dsh-claude-design 2.9.0: built from baoyu-design.
2
2
  window.__ModuleLoader__.load({
3
3
  id: "dsh-claude-design",
4
4
  factory: (require) => {
@@ -36,13 +36,148 @@ window.__ModuleLoader__.load({
36
36
 
37
37
  var import_react = require("react");
38
38
 
39
- var listPath = (skill) => `/api/baoyu-design.${skill}.artifacts`;
40
- var openPath = (skill) => `/api/baoyu-design.${skill}.artifacts.open`;
39
+ var base = (skill) => `/api/baoyu-design.${skill}.artifacts`;
40
+ var listPath = base;
41
+ var openPath = (skill) => `${base(skill)}.open`;
42
+ var previewPath = (skill) => `${base(skill)}.preview`;
43
+ var releasePath = (skill) => `${base(skill)}.release`;
44
+ var thumbnailPath = (skill) => `${base(skill)}.thumbnail`;
45
+ var updatePath = (skill) => `${base(skill)}.update`;
46
+ var deletePath = (skill) => `${base(skill)}.delete`;
47
+ var restorePath = (skill) => `${base(skill)}.restore`;
48
+
49
+ function desktopBrowser() {
50
+ const carrier = globalThis.dshDesktop;
51
+ return carrier?.protocolVersion === 1 ? carrier.browser : void 0;
52
+ }
53
+ var WORKSPACE = "baoyu-design-thumbnails";
54
+ var WIDTH = 720;
55
+ var QUALITY = 0.82;
56
+ var LOAD_MS = 2e4;
57
+ var SETTLE_MS = 1800;
58
+ var BLANK_RETRY_MS = 3e3;
59
+ var REGIONS = {
60
+ // The current slide on the editor's canvas.
61
+ "claude-slides": { width: 1280, height: 800, find: `document.querySelector('.bg-editor-canvas > .shadow-lg')` },
62
+ // The first artboard.
63
+ "claude-design": { width: 1280, height: 800, find: `document.querySelector('[data-mega-artboard]')` },
64
+ // What is beside the section list: the system's cover.
65
+ "claude-design-system": {
66
+ width: 1280,
67
+ height: 800,
68
+ find: `(() => {
69
+ const nav = document.querySelector('[data-testid="ds-section-nav"]');
70
+ if (!nav || !document.querySelector('h1,h2')) return null;
71
+ const r = nav.getBoundingClientRect();
72
+ return { x: r.right, y: r.top, width: innerWidth - r.right, height: innerHeight - r.top };
73
+ })()`
74
+ },
75
+ // The page, below the bar.
76
+ "claude-dashboard": { width: 1280, height: 800, find: `document.querySelector('main')` },
77
+ // The doc's frame, below the bars, at a width where the text fills it.
78
+ "claude-docs": { width: 860, height: 700, find: `document.querySelector('iframe')` },
79
+ // The film's first frame.
80
+ "claude-motion": { width: 1280, height: 800, find: `document.querySelector('.an-stage iframe')` }
81
+ };
82
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
83
+ if (signal.aborted) return reject(signal.reason);
84
+ const timer = setTimeout(resolve, ms);
85
+ signal.addEventListener("abort", () => {
86
+ clearTimeout(timer);
87
+ reject(signal.reason);
88
+ }, { once: true });
89
+ });
90
+ async function toCanvas(image) {
91
+ const picture = new Image();
92
+ await new Promise((resolve, reject) => {
93
+ picture.onload = () => resolve();
94
+ picture.onerror = () => reject(new Error("the picture did not decode"));
95
+ picture.src = image.toDataURL();
96
+ });
97
+ const scale = Math.min(1, WIDTH / picture.naturalWidth);
98
+ const canvas = document.createElement("canvas");
99
+ canvas.width = Math.max(1, Math.round(picture.naturalWidth * scale));
100
+ canvas.height = Math.max(1, Math.round(picture.naturalHeight * scale));
101
+ const context = canvas.getContext("2d");
102
+ if (context === null) throw new Error("no canvas to draw on");
103
+ context.imageSmoothingQuality = "high";
104
+ context.drawImage(picture, 0, 0, canvas.width, canvas.height);
105
+ return canvas;
106
+ }
107
+ function isFlat(canvas) {
108
+ const { data } = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height);
109
+ const step = Math.max(4, Math.floor(data.length / 4 / 400) * 4);
110
+ for (let i = step; i + 2 < data.length; i += step) {
111
+ if (Math.abs(data[i] - data[0]) > 3 || Math.abs(data[i + 1] - data[1]) > 3 || Math.abs(data[i + 2] - data[2]) > 3) return false;
112
+ }
113
+ return true;
114
+ }
115
+ async function captureThumbnail(browser, url, skill, signal) {
116
+ const region = REGIONS[skill] ?? { width: 1280, height: 800, find: "null" };
117
+ const { lease, partition } = await browser.acquire(WORKSPACE);
118
+ const guest = document.createElement("webview");
119
+ try {
120
+ guest.setAttribute("name", lease);
121
+ guest.setAttribute("partition", partition);
122
+ guest.setAttribute("src", `about:blank#${lease}`);
123
+ guest.setAttribute("aria-hidden", "true");
124
+ guest.tabIndex = -1;
125
+ guest.style.cssText = `position:fixed;left:calc(100vw - 1px);top:0;width:${region.width}px;height:${region.height}px;opacity:.01;pointer-events:none;z-index:-1`;
126
+ const ready = new Promise((resolve) => guest.addEventListener("dom-ready", () => resolve(), { once: true }));
127
+ document.body.append(guest);
128
+ await Promise.race([ready, sleep(LOAD_MS, signal)]);
129
+ guest.setAudioMuted(true);
130
+ const loaded = new Promise((resolve) => guest.addEventListener("did-stop-loading", () => resolve(), { once: true }));
131
+ void guest.loadURL(url).catch(() => void 0);
132
+ await Promise.race([loaded, sleep(LOAD_MS, signal)]);
133
+ const measure = `(() => {
134
+ const found = ${region.find};
135
+ if (!found) return null;
136
+ const r = found.getBoundingClientRect ? found.getBoundingClientRect() : found;
137
+ if (r.width < 80 || r.height < 60) return null;
138
+ const x = Math.max(0, r.x), y = Math.max(0, r.y);
139
+ return { x, y, width: Math.min(r.width, innerWidth - x), height: Math.min(r.height, innerHeight - y) };
140
+ })()`;
141
+ let rect = null;
142
+ const deadline = Date.now() + LOAD_MS;
143
+ while (rect === null && Date.now() < deadline) {
144
+ rect = await guest.executeJavaScript(measure).catch(() => null);
145
+ if (rect === null) await sleep(250, signal);
146
+ }
147
+ await guest.insertCSS("::-webkit-scrollbar{display:none!important}").catch(() => "");
148
+ await guest.executeJavaScript("document.fonts.ready.then(() => true)").catch(() => null);
149
+ await sleep(SETTLE_MS, signal);
150
+ rect = await guest.executeJavaScript(measure).catch(() => null) ?? rect;
151
+ const area = rect && {
152
+ x: Math.round(rect.x),
153
+ y: Math.round(rect.y),
154
+ width: Math.round(rect.width),
155
+ height: Math.round(rect.height)
156
+ };
157
+ const take = async () => {
158
+ const image = await guest.capturePage(area ?? void 0);
159
+ if (image.isEmpty()) throw new Error("the page gave no picture");
160
+ return toCanvas(image);
161
+ };
162
+ let canvas = await take();
163
+ if (isFlat(canvas)) {
164
+ await sleep(BLANK_RETRY_MS, signal);
165
+ canvas = await take();
166
+ }
167
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/jpeg", QUALITY));
168
+ if (blob === null) throw new Error("the picture did not encode");
169
+ return new Uint8Array(await blob.arrayBuffer());
170
+ } finally {
171
+ guest.remove();
172
+ await browser.release(lease).catch(() => void 0);
173
+ }
174
+ }
41
175
 
42
176
  var import_jsx_runtime = require("react/jsx-runtime");
43
- var PAGE_VERSION = 1;
177
+ var PAGE_VERSION = 2;
44
178
  var PANEL_ID = "baoyu-design.artifacts";
45
179
  var NS = "baoyu-design-artifacts";
180
+ var VIEW_KEY = "baoyu-design.artifacts.view";
46
181
  var HUB = Symbol.for("baoyu-design/dsh-artifacts-page");
47
182
  function hub() {
48
183
  const g = globalThis;
@@ -112,25 +247,75 @@ window.__ModuleLoader__.load({
112
247
  "time.locale": "zh-CN",
113
248
  search: "搜索作品",
114
249
  "search.clear": "清除搜索",
115
- "filter.all": "全部",
116
250
  "type.claude-slides": "幻灯片",
117
251
  "type.claude-design": "设计",
118
252
  "type.claude-design-system": "设计系统",
119
- "type.claude-dashboard": "仪表盘",
253
+ "type.claude-dashboard": "看板",
120
254
  "type.claude-docs": "文档",
121
255
  "type.claude-motion": "动画",
256
+ "create.heading": "新建",
257
+ "create.new": "新建",
258
+ "create.session": "新会话",
259
+ "prompt.claude-slides": "帮我做一份幻灯片,主题是:",
260
+ "prompt.claude-design": "帮我设计:",
261
+ "prompt.claude-design-system": "帮我做一套设计系统,品牌是:",
262
+ "prompt.claude-dashboard": "帮我做一个数据看板,数据是:",
263
+ "prompt.claude-docs": "帮我写一份文档,内容是:",
264
+ "prompt.claude-motion": "帮我做一段动画,内容是:",
265
+ "view.list": "列表视图",
266
+ "view.grid": "网格视图",
267
+ "filter.button": "排序和筛选",
268
+ "filter.sort": "排序方式",
269
+ "filter.type": "类型",
270
+ "filter.date": "打开时间",
271
+ "filter.sharing": "共享",
272
+ "filter.pinned": "已固定",
273
+ "filter.any": "不限",
274
+ "filter.some": "{count} 项",
275
+ "filter.clear": "清除筛选",
276
+ "sort.opened": "最近打开",
277
+ "sort.updated": "最近修改",
278
+ "sort.created": "创建时间",
279
+ "sort.name": "名称",
280
+ "date.any": "不限",
281
+ "date.today": "今天",
282
+ "date.yesterday": "昨天",
283
+ "date.week": "最近 7 天",
284
+ "date.month": "最近 30 天",
285
+ "sharing.any": "不限",
286
+ "sharing.shared": "已共享",
287
+ "sharing.private": "未共享",
122
288
  "card.opened": "{time}打开",
123
289
  "card.justNow": "刚刚打开",
124
290
  "card.opening": "正在打开…",
125
- "card.in": "在“{title}”中",
291
+ "card.shared": "已在局域网共享",
292
+ "action.more": "更多操作",
293
+ "action.pin": "固定",
294
+ "action.unpin": "取消固定",
295
+ "action.rename": "重命名",
296
+ "action.copy": "复制链接",
297
+ "action.delete": "删除",
298
+ "rename.title": "重命名作品",
299
+ "rename.save": "保存",
300
+ "dialog.cancel": "取消",
301
+ "delete.title": "删除“{title}”?",
302
+ "delete.moves": "这会移除这个作品,它的文件夹移到应用数据目录下的 baoyu-design/trash。",
303
+ "delete.keeps": "这会把它从作品里移除。它的文件夹里还有别的文件,所以留在原处:{path}",
304
+ "delete.confirm": "删除",
305
+ "toast.copied": "链接已复制到剪贴板。",
306
+ "toast.deleted": "已删除“{title}”。",
307
+ "toast.removed": "已从作品里移除“{title}”,文件夹还在原处。",
308
+ "toast.undo": "撤销",
309
+ "toast.close": "关闭",
126
310
  "empty.title": "还没有作品",
127
- "empty.body": "Agent 用 baoyu-design 的技能打开幻灯片、设计稿、设计系统、仪表盘、文档或动画以后,会出现在这里。",
311
+ "empty.body": "Agent 用 baoyu-design 的技能做出来的东西都会出现在这里。点上面的入口开始做一个。",
128
312
  "empty.noMatches": "没有符合条件的作品",
129
313
  "load.error": "读取作品列表失败:{message}",
130
314
  "load.retry": "重试",
131
315
  "open.pluginOff": "{plugin} 插件没有开启,打不开这个作品。",
132
316
  "open.failed": "打不开:{message}",
133
- "open.noSession": "原来的对话已不在,编辑器在新窗口里打开了。"
317
+ "open.noSession": "原来的对话已不在,编辑器在新窗口里打开了。",
318
+ "action.failed": "没有做成:{message}"
134
319
  };
135
320
  var en = {
136
321
  panel: "Artifacts",
@@ -138,60 +323,153 @@ window.__ModuleLoader__.load({
138
323
  "time.locale": "en",
139
324
  search: "Search artifacts",
140
325
  "search.clear": "Clear search",
141
- "filter.all": "All",
142
326
  "type.claude-slides": "Slides",
143
327
  "type.claude-design": "Design",
144
- "type.claude-design-system": "Design systems",
145
- "type.claude-dashboard": "Dashboards",
328
+ "type.claude-design-system": "Design system",
329
+ "type.claude-dashboard": "Dashboard",
146
330
  "type.claude-docs": "Docs",
147
331
  "type.claude-motion": "Motion",
332
+ "create.heading": "Make something new",
333
+ "create.new": "New",
334
+ "create.session": "New session",
335
+ "prompt.claude-slides": "Make me a slide deck about: ",
336
+ "prompt.claude-design": "Design this for me: ",
337
+ "prompt.claude-design-system": "Make me a design system for this brand: ",
338
+ "prompt.claude-dashboard": "Make me a dashboard from this data: ",
339
+ "prompt.claude-docs": "Write me a doc about: ",
340
+ "prompt.claude-motion": "Make me an animation about: ",
341
+ "view.list": "List view",
342
+ "view.grid": "Grid view",
343
+ "filter.button": "Sort and filter",
344
+ "filter.sort": "Sort by",
345
+ "filter.type": "Type",
346
+ "filter.date": "Date opened",
347
+ "filter.sharing": "Sharing",
348
+ "filter.pinned": "Pinned",
349
+ "filter.any": "Any",
350
+ "filter.some": "{count} selected",
351
+ "filter.clear": "Clear filters",
352
+ "sort.opened": "Last opened",
353
+ "sort.updated": "Last edited",
354
+ "sort.created": "Date created",
355
+ "sort.name": "Name",
356
+ "date.any": "Any time",
357
+ "date.today": "Today",
358
+ "date.yesterday": "Yesterday",
359
+ "date.week": "Past 7 days",
360
+ "date.month": "Past 30 days",
361
+ "sharing.any": "Any",
362
+ "sharing.shared": "Shared",
363
+ "sharing.private": "Not shared",
148
364
  "card.opened": "Opened {time}",
149
365
  "card.justNow": "Opened just now",
150
366
  "card.opening": "Opening…",
151
- "card.in": "In “{title}”",
367
+ "card.shared": "Shared on the local network",
368
+ "action.more": "More actions",
369
+ "action.pin": "Pin",
370
+ "action.unpin": "Unpin",
371
+ "action.rename": "Rename",
372
+ "action.copy": "Copy link",
373
+ "action.delete": "Delete",
374
+ "rename.title": "Rename artifact",
375
+ "rename.save": "Save",
376
+ "dialog.cancel": "Cancel",
377
+ "delete.title": "Delete {title}?",
378
+ "delete.moves": "This removes the artifact and moves its folder to baoyu-design/trash in the app's data folder.",
379
+ "delete.keeps": "This takes it off your artifacts. Its folder holds other files too, so it stays where it is: {path}",
380
+ "delete.confirm": "Delete",
381
+ "toast.copied": "Link copied to clipboard.",
382
+ "toast.deleted": "Deleted “{title}”.",
383
+ "toast.removed": "Took “{title}” off your artifacts. Its folder stays where it is.",
384
+ "toast.undo": "Undo",
385
+ "toast.close": "Close",
152
386
  "empty.title": "No artifacts yet",
153
- "empty.body": "Slide decks, canvases, design systems, dashboards, docs and films the agent opens with a baoyu-design skill show up here.",
387
+ "empty.body": "What the agent makes with a baoyu-design skill shows up here. Start one with a tile above.",
154
388
  "empty.noMatches": "No artifacts match",
155
389
  "load.error": "Could not read the artifacts: {message}",
156
390
  "load.retry": "Try again",
157
391
  "open.pluginOff": "The {plugin} plugin is off, so this artifact cannot open.",
158
392
  "open.failed": "Could not open it: {message}",
159
- "open.noSession": "The conversation is gone, so the editor opened in a new window."
393
+ "open.noSession": "The conversation is gone, so the editor opened in a new window.",
394
+ "action.failed": "That did not work: {message}"
160
395
  };
161
- var SKILLS = ["claude-slides", "claude-design", "claude-design-system", "claude-dashboard", "claude-docs", "claude-motion"];
396
+ var SKILLS = ["claude-docs", "claude-slides", "claude-design", "claude-dashboard", "claude-design-system", "claude-motion"];
162
397
  var isSkill = (s) => SKILLS.includes(s);
398
+ var creatable = (on) => SKILLS.filter((s) => on.includes(s));
399
+ var promptFor = (skill, t) => `/${skill} ${t(`prompt.${skill}`)}`;
400
+ var NO_FILTERS = { sort: "opened", types: [], date: "any", sharing: "any", pinned: false };
401
+ var SORTS = ["opened", "updated", "created", "name"];
402
+ var DATES = ["any", "today", "yesterday", "week", "month"];
403
+ var SHARINGS = ["any", "shared", "private"];
404
+ var isFiltered = (f) => f.types.length > 0 || f.date !== "any" || f.sharing !== "any" || f.pinned;
405
+ function inRange(iso, range, now) {
406
+ if (range === "any") return true;
407
+ const then = Date.parse(iso);
408
+ if (!Number.isFinite(then)) return false;
409
+ const day = new Date(now);
410
+ const today = new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime();
411
+ const DAY = 864e5;
412
+ switch (range) {
413
+ case "today":
414
+ return then >= today;
415
+ case "yesterday":
416
+ return then >= new Date(day.getFullYear(), day.getMonth(), day.getDate() - 1).getTime() && then < today;
417
+ case "week":
418
+ return then >= now - 7 * DAY;
419
+ case "month":
420
+ return then >= now - 30 * DAY;
421
+ }
422
+ }
423
+ function sorted(views, sort, locale) {
424
+ const by = (key) => (a, b) => a[key] < b[key] ? 1 : a[key] > b[key] ? -1 : 0;
425
+ const compare = sort === "name" ? (a, b) => a.title.localeCompare(b.title, locale) : by(sort === "updated" ? "updatedAt" : sort === "created" ? "firstOpenedAt" : "openedAt");
426
+ return [...views].sort(compare);
427
+ }
163
428
  var CSS = `
164
- .bda-page{width:100%;min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);font-size:14px;line-height:1.6;display:flex;flex-direction:column;overflow:hidden}
429
+ .bda-page{position:relative;width:100%;min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);font-size:14px;line-height:1.6;display:flex;flex-direction:column;overflow:hidden}
165
430
  .bda-scroll{flex:1;min-height:0;overflow:auto;scrollbar-gutter:stable}
166
- .bda-content{max-width:1080px;margin:0 auto;padding:0 clamp(24px,4vw,48px) 48px}
167
- .bda-heading{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:16px;padding-top:28px;margin-bottom:20px}
431
+ .bda-content{max-width:1120px;margin:0 auto;padding:0 clamp(24px,4vw,48px) 56px}
432
+ .bda-heading{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px 16px;padding-top:28px;margin-bottom:24px}
168
433
  [data-platform=darwin] .bda-heading{padding-top:calc(28px + var(--dsh-frame-top-clearance,0px))}
169
434
  .bda-heading h1{margin:0;font-size:20px;font-weight:500;line-height:28px}
170
- .bda-search{display:flex;align-items:center;gap:6px;width:min(260px,100%);height:34px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;color:var(--dsw-alias-label-tertiary);transition:border-color .15s}
171
- .bda-search:hover{border-color:var(--dsw-alias-border-l2)}
435
+ .bda-tools{display:flex;align-items:center;gap:4px;margin-left:auto}
436
+ .bda-tool{position:relative;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:0;border-radius:8px;background:none;color:var(--dsw-alias-label-secondary);cursor:pointer}
437
+ .bda-tool:hover,.bda-tool[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
438
+ .bda-tool[data-active=true]::after{content:"";position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-business-primary)}
439
+ .bda-search{display:flex;align-items:center;gap:6px;width:min(240px,50vw);height:32px;padding:0 8px 0 10px;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-tertiary)}
172
440
  .bda-search:focus-within{border-color:var(--dsw-alias-state-business-primary)}
173
441
  .bda-search input{flex:1;min-width:0;height:100%;border:0;padding:0;background:none;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;outline:none}
174
442
  .bda-search input::placeholder{color:var(--dsw-alias-label-caption)}
175
443
  .bda-search input::-webkit-search-cancel-button{display:none}
176
444
  .bda-search button{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;margin-right:-4px;padding:0;border:0;border-radius:6px;background:none;color:inherit;cursor:pointer}
177
445
  .bda-search button:hover{background:var(--dsw-alias-interactive-bg-hover)}
178
- .bda-filters{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin-bottom:20px}
179
- .bda-tab{display:inline-flex;align-items:center;height:28px;padding:0 10px;border:0;border-radius:14px;background:none;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:14px;line-height:22px;white-space:nowrap;cursor:pointer}
180
- .bda-tab:hover{background:var(--dsw-alias-interactive-bg-hover)}
181
- .bda-tab[aria-pressed=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
182
- .bda-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:28px 20px;margin:0;padding:0;list-style:none}
183
- .bda-card{display:flex;flex-direction:column;gap:10px;width:100%;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer}
446
+ .bda-section-title{margin:0 0 12px;font-size:13px;font-weight:400;line-height:20px;color:var(--dsw-alias-label-tertiary)}
447
+ .bda-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(118px,1fr));gap:14px;margin:0 0 36px;padding:0;list-style:none}
448
+ .bda-tile{display:flex;flex-direction:column;gap:8px;width:100%;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer}
449
+ .bda-tile-art{position:relative;display:flex;align-items:center;justify-content:center;aspect-ratio:7/5;border-radius:14px;overflow:hidden;background:color-mix(in srgb,var(--bda-tint) 24%,var(--dsw-alias-bg-base));color:color-mix(in srgb,var(--bda-tint) 78%,var(--dsw-alias-label-primary));transition:background-color .15s}
450
+ .bda-tile:hover .bda-tile-art{background:color-mix(in srgb,var(--bda-tint) 34%,var(--dsw-alias-bg-base))}
451
+ .bda-tile:focus-visible{outline:none}
452
+ .bda-tile:focus-visible .bda-tile-art{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
453
+ .bda-tile-squiggle{position:absolute;inset:0;width:100%;height:100%;opacity:.28}
454
+ .bda-tile-label{padding-left:2px;font-size:14px;line-height:20px}
455
+ .bda-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:20px;margin:0;padding:0;list-style:none}
456
+ .bda-item{position:relative;min-width:0}
457
+ .bda-card{display:flex;flex-direction:column;width:100%;padding:0;border:.5px solid var(--dsw-alias-border-l2);border-radius:14px;overflow:hidden;background:var(--dsw-alias-bg-base);color:inherit;font:inherit;text-align:left;cursor:pointer;transition:background-color .15s}
458
+ .bda-item:hover .bda-card{background:var(--dsw-alias-interactive-bg-hover)}
184
459
  .bda-card:disabled{cursor:progress}
185
- .bda-card:focus-visible{outline:none}
186
- .bda-card:focus-visible .bda-preview{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
187
- .bda-preview{position:relative;aspect-ratio:16/10;border-radius:12px;overflow:hidden;background:color-mix(in srgb,var(--bda-tint) 14%,var(--dsw-alias-bg-base));transition:background-color .15s}
188
- .bda-card:hover .bda-preview{background:color-mix(in srgb,var(--bda-tint) 22%,var(--dsw-alias-bg-base))}
189
- .bda-paper{position:absolute;left:14%;right:14%;top:14%;bottom:-6%;border-radius:6px 6px 0 0;background:var(--dsw-alias-bg-base);box-shadow:0 0 0 .5px var(--dsw-alias-border-l2),0 6px 18px -8px rgba(0,0,0,.25);padding:9% 9% 0;display:flex;flex-direction:column;gap:6px;overflow:hidden;transition:transform .2s}
190
- .bda-card:hover .bda-paper{transform:translateY(-3px)}
460
+ .bda-card:focus-visible,.bda-row:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
461
+ .bda-preview{position:relative;display:block;width:100%;aspect-ratio:2/1;overflow:hidden;background:color-mix(in srgb,var(--bda-tint) 16%,var(--dsw-alias-bg-base))}
462
+ .bda-sheet{position:absolute;left:12%;right:12%;top:13%;bottom:-8px;border-radius:8px 8px 0 0;overflow:hidden;background:var(--dsw-alias-bg-base);box-shadow:0 0 0 .5px var(--dsw-alias-border-l2),0 6px 18px -8px rgba(0,0,0,.28);transition:transform .2s}
463
+ .bda-item:hover .bda-sheet{transform:translateY(-3px)}
464
+ .bda-sheet img{display:block;width:100%;height:100%;object-fit:cover;object-position:top center;user-select:none}
465
+ .bda-preview[data-stack=true] .bda-sheet{left:20%;right:20%}
466
+ .bda-preview[data-stack=true]::before{content:"";position:absolute;left:21%;right:19%;top:10%;bottom:-8px;border-radius:8px 8px 0 0;background:var(--dsw-alias-bg-base);box-shadow:0 0 0 .5px var(--dsw-alias-border-l2);transform:rotate(1.6deg);transform-origin:bottom center}
467
+ .bda-paper{position:absolute;left:14%;right:14%;top:13%;bottom:-8px;border-radius:8px 8px 0 0;background:var(--dsw-alias-bg-base);box-shadow:0 0 0 .5px var(--dsw-alias-border-l2),0 6px 18px -8px rgba(0,0,0,.25);padding:6% 7% 0;display:flex;flex-direction:column;gap:6px;overflow:hidden;transition:transform .2s}
468
+ .bda-item:hover .bda-paper{transform:translateY(-3px)}
191
469
  .bda-paper-title{font-size:11px;line-height:1.35;font-weight:600;color:var(--dsw-alias-label-primary);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
192
470
  .bda-line{height:5px;border-radius:3px;background:var(--dsw-alias-border-l2)}
193
- .bda-slide{position:absolute;left:12%;right:12%;top:16%;aspect-ratio:16/9;border-radius:5px;background:var(--dsw-alias-bg-base);box-shadow:0 0 0 .5px var(--dsw-alias-border-l2),0 6px 18px -8px rgba(0,0,0,.25);display:flex;flex-direction:column;justify-content:center;gap:6px;padding:0 9%;transition:transform .2s}
194
- .bda-card:hover .bda-slide{transform:translateY(-3px)}
471
+ .bda-slide{position:absolute;left:14%;right:14%;top:13%;aspect-ratio:16/9;border-radius:6px;background:var(--dsw-alias-bg-base);box-shadow:0 0 0 .5px var(--dsw-alias-border-l2),0 6px 18px -8px rgba(0,0,0,.25);display:flex;flex-direction:column;justify-content:center;gap:6px;padding:0 9%;transition:transform .2s}
472
+ .bda-item:hover .bda-slide{transform:translateY(-3px)}
195
473
  .bda-slide .bda-paper-title{font-size:12px}
196
474
  .bda-slide .bda-line{width:40%;background:var(--bda-tint);opacity:.55}
197
475
  .bda-boards{display:flex;gap:6px;flex:1;margin-top:2px}
@@ -205,39 +483,97 @@ window.__ModuleLoader__.load({
205
483
  .bda-kpi{flex:1;height:16px;border-radius:3px;box-shadow:0 0 0 .5px var(--dsw-alias-border-l2)}
206
484
  .bda-bars{display:flex;align-items:flex-end;gap:5px;height:34%;margin-top:2px}
207
485
  .bda-bar{flex:1;border-radius:2px 2px 0 0;background:var(--bda-tint);opacity:.7}
208
- .bda-film{position:absolute;left:12%;right:12%;top:16%;aspect-ratio:16/9;border-radius:5px;background:#1d1d1f;box-shadow:0 6px 18px -8px rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;transition:transform .2s;color:#fff}
209
- .bda-card:hover .bda-film{transform:translateY(-3px)}
486
+ .bda-film{position:absolute;left:14%;right:14%;top:13%;aspect-ratio:16/9;border-radius:6px;background:#1d1d1f;box-shadow:0 6px 18px -8px rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;transition:transform .2s;color:#fff}
487
+ .bda-item:hover .bda-film{transform:translateY(-3px)}
210
488
  .bda-film-bar{position:absolute;left:8%;right:8%;bottom:10%;height:3px;border-radius:2px;background:rgba(255,255,255,.25)}
211
489
  .bda-film-bar::after{content:"";position:absolute;left:0;top:0;bottom:0;width:38%;border-radius:2px;background:var(--bda-tint)}
212
490
  .bda-film-title{position:absolute;left:8%;right:8%;top:10%;font-size:10px;font-weight:600;color:rgba(255,255,255,.85);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
213
491
  .bda-busy{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:8px;background:color-mix(in srgb,var(--dsw-alias-bg-base) 70%,transparent);color:var(--dsw-alias-label-secondary);font-size:13px}
214
492
  .bda-spinner{width:14px;height:14px;border-radius:50%;border:1.5px solid currentColor;border-right-color:transparent;animation:bda-spin .8s linear infinite}
215
493
  @keyframes bda-spin{to{transform:rotate(360deg)}}
216
- .bda-info{display:flex;align-items:flex-start;gap:10px;min-width:0}
217
- .bda-glyph{flex:none;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:8px;background:color-mix(in srgb,var(--bda-tint) 16%,var(--dsw-alias-bg-base));color:color-mix(in srgb,var(--bda-tint) 80%,var(--dsw-alias-label-primary))}
494
+ .bda-info{display:flex;align-items:center;gap:12px;min-width:0;padding:12px 14px}
495
+ .bda-glyph{flex:none;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:9px;background:color-mix(in srgb,var(--bda-tint) 16%,var(--dsw-alias-bg-base));color:color-mix(in srgb,var(--bda-tint) 78%,var(--dsw-alias-label-primary))}
218
496
  .bda-text{display:flex;flex-direction:column;min-width:0;flex:1}
219
497
  .bda-name{font-weight:500;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
220
498
  .bda-meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
221
- .bda-note{margin-top:2px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}
222
- .bda-note[data-kind=error]{color:var(--dsw-alias-state-error-primary)}
223
- .bda-empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:72px 20px;text-align:center;color:var(--dsw-alias-label-tertiary)}
499
+ .bda-actions{position:absolute;top:10px;right:10px;display:flex;align-items:center;gap:6px}
500
+ .bda-act{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);cursor:pointer;opacity:0;transition:opacity .12s}
501
+ .bda-act:hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--dsw-alias-label-primary) 6%,var(--dsw-alias-bg-base))}
502
+ .bda-item:hover .bda-act,.bda-item:focus-within .bda-act,.bda-act[aria-expanded=true],.bda-act[data-on=true]{opacity:1}
503
+ .bda-act[data-on=true]{color:var(--dsw-alias-label-primary)}
504
+ .bda-list{display:flex;flex-direction:column;gap:2px;margin:0;padding:0;list-style:none}
505
+ .bda-list .bda-item{display:flex;align-items:center;border-radius:10px;padding-right:8px}
506
+ .bda-list .bda-item:hover,.bda-list .bda-item[data-menu=true]{background:var(--dsw-alias-interactive-bg-hover)}
507
+ .bda-row{display:flex;align-items:center;gap:12px;flex:1;min-width:0;padding:8px 10px;border:0;border-radius:10px;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer}
508
+ .bda-row:disabled{cursor:progress}
509
+ .bda-row .bda-name{flex:1;min-width:0}
510
+ .bda-row .bda-meta{flex:none;margin-left:16px}
511
+ .bda-row-tools{flex:none;display:flex;align-items:center;gap:2px;margin-left:4px;color:var(--dsw-alias-label-tertiary)}
512
+ .bda-shared{display:inline-flex;align-items:center;margin-right:6px;color:var(--dsw-alias-label-tertiary)}
513
+ .bda-list .bda-act{width:28px;height:28px;border:0;background:none}
514
+ .bda-list .bda-act:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 8%,transparent)}
515
+ .bda-list .bda-act-more{opacity:1}
516
+ @media (max-width:560px){.bda-row .bda-meta{display:none}}
517
+ .bda-menu{position:fixed;z-index:1000;min-width:200px;padding:6px;border:.5px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-base);box-shadow:0 8px 28px -6px rgba(0,0,0,.28);font-size:14px}
518
+ .bda-menu-item{display:flex;align-items:center;gap:10px;width:100%;min-height:34px;padding:5px 10px;border:0;border-radius:8px;background:none;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;white-space:nowrap;cursor:pointer}
519
+ .bda-menu-item:hover,.bda-menu-item:focus-visible,.bda-menu-item[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);outline:none}
520
+ .bda-menu-item[data-danger=true]{color:var(--dsw-alias-state-error-primary)}
521
+ .bda-menu-item svg{flex:none}
522
+ .bda-menu-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
523
+ .bda-menu-value{margin-left:16px;color:var(--dsw-alias-label-tertiary)}
524
+ .bda-menu-count{margin-left:16px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}
525
+ .bda-menu-key{margin-left:20px;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px}
526
+ .bda-menu-rule{height:.5px;margin:6px 4px;background:var(--dsw-alias-border-l2)}
527
+ .bda-menu-chev{margin-left:6px;color:var(--dsw-alias-label-tertiary)}
528
+ .bda-menu-row{position:relative}
529
+ .bda-submenu{position:absolute;left:calc(100% + 12px);top:-6px;z-index:1001;min-width:190px;padding:6px;border:.5px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-base);box-shadow:0 8px 28px -6px rgba(0,0,0,.28)}
530
+ .bda-submenu[data-flip=true]{left:auto;right:calc(100% + 12px)}
531
+ .bda-check{flex:none;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:1px solid var(--dsw-alias-border-l1);border-radius:5px;color:transparent}
532
+ .bda-check[data-on=true]{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary);color:#fff}
533
+ .bda-tick{flex:none;margin-left:16px;color:var(--dsw-alias-state-business-primary)}
534
+ .bda-scrim{position:fixed;inset:0;z-index:1100;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.36)}
535
+ .bda-dialog{width:min(420px,100%);padding:20px;border-radius:16px;background:var(--dsw-alias-bg-base);box-shadow:0 18px 48px -12px rgba(0,0,0,.4)}
536
+ .bda-dialog h2{margin:0 0 12px;font-size:16px;font-weight:600;line-height:24px;overflow-wrap:anywhere}
537
+ .bda-dialog p{margin:0;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:1.55;overflow-wrap:anywhere}
538
+ .bda-dialog input{box-sizing:border-box;width:100%;height:38px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:none;color:var(--dsw-alias-label-primary);font:inherit;outline:none}
539
+ .bda-dialog input:focus{border-color:var(--dsw-alias-state-business-primary)}
540
+ .bda-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}
541
+ .bda-btn{height:34px;padding:0 14px;border:0;border-radius:9px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;cursor:pointer}
542
+ .bda-btn:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 12%,var(--dsw-alias-bg-base))}
543
+ .bda-btn:disabled{color:var(--dsw-alias-label-caption);cursor:default;background:var(--dsw-alias-interactive-bg-hover)}
544
+ .bda-btn[data-primary=true]{background:var(--dsw-alias-state-business-primary);color:#fff}
545
+ .bda-btn[data-primary=true]:hover:not(:disabled){filter:brightness(1.08)}
546
+ .bda-btn[data-primary=true]:disabled{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-caption)}
547
+ .bda-btn[data-danger=true]{border:1px solid var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,var(--dsw-alias-bg-base));color:var(--dsw-alias-state-error-primary)}
548
+ .bda-btn[data-danger=true]:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 18%,var(--dsw-alias-bg-base))}
549
+ .bda-toast{position:absolute;left:50%;bottom:24px;z-index:900;display:flex;align-items:center;gap:10px;max-width:min(560px,calc(100% - 32px));padding:9px 10px 9px 14px;border:.5px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-base);box-shadow:0 8px 28px -6px rgba(0,0,0,.28);transform:translateX(-50%);font-size:14px}
550
+ .bda-toast[data-kind=error]{color:var(--dsw-alias-state-error-primary)}
551
+ .bda-toast svg{flex:none}
552
+ .bda-toast-text{min-width:0;overflow-wrap:anywhere}
553
+ .bda-toast-undo{flex:none;padding:2px 8px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-state-business-primary);font:inherit;font-weight:500;cursor:pointer}
554
+ .bda-toast-undo:hover{background:var(--dsw-alias-interactive-bg-hover)}
555
+ .bda-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-tertiary);cursor:pointer}
556
+ .bda-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover)}
557
+ .bda-empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:56px 20px;text-align:center;color:var(--dsw-alias-label-tertiary)}
224
558
  .bda-empty svg{color:var(--dsw-alias-label-caption);margin-bottom:4px}
225
559
  .bda-empty h2{margin:0;font-size:14px;font-weight:500;color:var(--dsw-alias-label-secondary)}
226
560
  .bda-empty p{margin:0;max-width:420px;font-size:13px;line-height:1.6}
227
561
  .bda-retry{margin-top:8px;height:30px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l2);border-radius:15px;background:none;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;cursor:pointer}
228
562
  .bda-retry:hover{background:var(--dsw-alias-interactive-bg-hover)}
563
+ .bda-skeleton .bda-card{cursor:default}
229
564
  .bda-skeleton .bda-preview{background:var(--dsw-alias-bg-skeleton,var(--dsw-alias-interactive-bg-hover))}
230
- .bda-skeleton .bda-line{width:60%;height:10px;margin-top:4px}
231
- @media (prefers-reduced-motion:reduce){.bda-paper,.bda-slide,.bda-film{transition:none}.bda-spinner{animation-duration:2s}}
565
+ .bda-skeleton .bda-line{width:60%;height:10px;margin:18px 14px}
566
+ @media (prefers-reduced-motion:reduce){.bda-sheet,.bda-paper,.bda-slide,.bda-film,.bda-act,.bda-tile-art,.bda-card{transition:none}.bda-spinner{animation-duration:2s}}
232
567
  `;
233
568
  var TINTS = {
234
- "claude-slides": "#e8865a",
235
- "claude-design": "#8b7cf0",
236
- "claude-design-system": "#2fa98c",
237
- "claude-dashboard": "#3f8ef0",
238
- "claude-docs": "#4a90e2",
239
- "claude-motion": "#e2557a"
569
+ "claude-slides": "#e8703a",
570
+ "claude-design": "#7a5cf0",
571
+ "claude-design-system": "#c79a1b",
572
+ "claude-dashboard": "#e5484d",
573
+ "claude-docs": "#3f8ef0",
574
+ "claude-motion": "#1fa971"
240
575
  };
576
+ var tintOf = (skill) => isSkill(skill) ? TINTS[skill] : "#8a8a8a";
241
577
  var svg = (size) => ({ display: "block", width: size, height: size, flex: "none" });
242
578
  function ArtifactsIcon({ size = 16 }) {
243
579
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", style: svg(size), children: [
@@ -245,20 +581,18 @@ window.__ModuleLoader__.load({
245
581
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8.5 6V5a2 2 0 0 1 2-2h4.5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-3.5", stroke: "currentColor", strokeWidth: "1.5" })
246
582
  ] });
247
583
  }
248
- function TypeIcon({ skill, size = 16 }) {
584
+ function TypeIcon({ skill, size = 18 }) {
249
585
  const p = { stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" };
250
586
  const body = (() => {
251
587
  switch (skill) {
252
588
  case "claude-slides":
253
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
254
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2.75", y: "4", width: "14.5", height: "9.5", rx: "1.5", ...p }),
255
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 13.5V16.5M7 16.5h6", ...p })
256
- ] });
589
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.75 3.5h14.5M4 3.5v8.25a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3.5M7.25 16.75 10 12.75l2.75 4", ...p }) });
257
590
  case "claude-design":
258
591
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
259
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2.75", y: "2.75", width: "8", height: "10", rx: "1.25", ...p }),
260
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "12.75", y: "6", width: "4.5", height: "11.25", rx: "1.25", ...p }),
261
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.75 16.25h8", ...p })
592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 2.75a7.25 7.25 0 1 0 0 14.5c1.1 0 1.75-.8 1.75-1.7 0-.5-.2-.9-.45-1.25-.25-.35-.45-.7-.45-1.15 0-.95.75-1.65 1.7-1.65h1.6a3.1 3.1 0 0 0 3.1-3.1c0-3.1-3.2-5.65-7.25-5.65Z", ...p }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6.4", cy: "9.4", r: ".9", fill: "currentColor" }),
594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8.8", cy: "6.3", r: ".9", fill: "currentColor" }),
595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12.5", cy: "6.8", r: ".9", fill: "currentColor" })
262
596
  ] });
263
597
  case "claude-design-system":
264
598
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -268,13 +602,14 @@ window.__ModuleLoader__.load({
268
602
  ] });
269
603
  case "claude-dashboard":
270
604
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
271
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2.75", y: "2.75", width: "14.5", height: "14.5", rx: "2", ...p }),
272
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6.5 13.5V10M10 13.5V6.5M13.5 13.5V9", ...p })
605
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2.75", y: "10", width: "4.5", height: "7.25", rx: "1", ...p }),
606
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "7.75", y: "2.75", width: "4.5", height: "14.5", rx: "1", ...p }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "12.75", y: "7", width: "4.5", height: "10.25", rx: "1", ...p })
273
608
  ] });
274
609
  case "claude-docs":
275
610
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
276
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 2.75h6.5l3.5 3.5v10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-12.5a1 1 0 0 1 1-1Z", ...p }),
277
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M7 10h6M7 13h4.5", ...p })
611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.5 2.75h6l4 4v9.5a1 1 0 0 1-1 1h-9a1 1 0 0 1-1-1V3.75a1 1 0 0 1 1-1Z", ...p }),
612
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M11.5 2.75v4h4", ...p })
278
613
  ] });
279
614
  case "claude-motion":
280
615
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -287,16 +622,49 @@ window.__ModuleLoader__.load({
287
622
  })();
288
623
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", style: svg(size), children: body });
289
624
  }
290
- function SearchIcon() {
291
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", style: svg(14), children: [
292
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "7", cy: "7", r: "4.5", stroke: "currentColor", strokeWidth: "1.3" }),
293
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m10.5 10.5 3 3", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
294
- ] });
295
- }
296
- function ClearIcon() {
297
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", style: svg(12), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m4.5 4.5 7 7m0-7-7 7", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" }) });
625
+ function Icon({ d, size = 16, fill }) {
626
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", fill: fill ? "currentColor" : "none", stroke: fill ? void 0 : "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", style: svg(size), children: typeof d === "string" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d }) : d });
298
627
  }
299
- function Preview({ skill, title }) {
628
+ var ICON = {
629
+ search: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
630
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "7", cy: "7", r: "4.5" }),
631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m10.5 10.5 3 3" })
632
+ ] }),
633
+ clear: "m4.5 4.5 7 7m0-7-7 7",
634
+ filter: "M2.5 4.5h11M4.5 8h7M6.5 11.5h3",
635
+ list: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5.75 4h7.75M5.75 8h7.75M5.75 12h7.75" }),
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.5 4h.01M2.5 8h.01M2.5 12h.01", strokeWidth: "1.8" })
638
+ ] }),
639
+ grid: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
640
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2.5", y: "2.5", width: "4.5", height: "4.5", rx: "1" }),
641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "2.5", width: "4.5", height: "4.5", rx: "1" }),
642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2.5", y: "9", width: "4.5", height: "4.5", rx: "1" }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "4.5", height: "4.5", rx: "1" })
644
+ ] }),
645
+ plus: "M8 3v10M3 8h10",
646
+ chat: "M3 3.5h10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H8.2L5.4 13.8a.3.3 0 0 1-.5-.23V11.5H3a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1Z",
647
+ pin: "M9.75 2.25 13.75 6.25M10.75 3.25 7 6.25 4.5 6.5 9.5 11.5 9.75 9 12.75 5.25M7 9l-4 4",
648
+ more: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
649
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "3.5", cy: "8", r: ".6", fill: "currentColor" }),
650
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "8", r: ".6", fill: "currentColor" }),
651
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12.5", cy: "8", r: ".6", fill: "currentColor" })
652
+ ] }),
653
+ rename: "M10.5 3 13 5.5 5.5 13 2.5 13.5 3 10.5 10.5 3Z",
654
+ link: "M6.75 9.25a2.5 2.5 0 0 0 3.54 0l2.25-2.25a2.5 2.5 0 0 0-3.54-3.54L8.25 4.2M9.25 6.75a2.5 2.5 0 0 0-3.54 0L3.46 9a2.5 2.5 0 0 0 3.54 3.54l.75-.75",
655
+ trash: "M2.75 4.25h10.5M6.25 4.25V3a.75.75 0 0 1 .75-.75h2a.75.75 0 0 1 .75.75v1.25M4 4.25l.6 8.6a1 1 0 0 0 1 .9h4.8a1 1 0 0 0 1-.9l.6-8.6M6.75 7v4M9.25 7v4",
656
+ info: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "8", r: "6" }),
658
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 7.5v3.25M8 5.25h.01" })
659
+ ] }),
660
+ chevron: "m6 3.5 4.5 4.5L6 12.5",
661
+ tick: "m3 8.5 3.25 3.25L13 4.75",
662
+ people: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
663
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6", cy: "5.5", r: "2.25" }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 13c0-2.2 1.8-3.75 4-3.75S10 10.8 10 13M10.5 3.5a2.25 2.25 0 0 1 0 4M12 9.5c1.3.45 2.25 1.75 2.25 3.5" })
665
+ ] })
666
+ };
667
+ function Drawn({ skill, title }) {
300
668
  const name = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-paper-title", children: title });
301
669
  switch (skill) {
302
670
  case "claude-slides":
@@ -346,6 +714,12 @@ window.__ModuleLoader__.load({
346
714
  ] });
347
715
  }
348
716
  }
717
+ function Preview({ view, thumbnailUrl }) {
718
+ const url = thumbnailUrl(view);
719
+ const [broken, setBroken] = (0, import_react.useState)(null);
720
+ const shown = url !== null && broken !== url;
721
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-preview", "data-stack": shown && view.skill === "claude-docs", children: shown ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-sheet", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url, alt: "", draggable: false, onError: () => setBroken(url) }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Drawn, { skill: view.skill, title: view.title }) });
722
+ }
349
723
  function ago(iso, now, t) {
350
724
  const then = Date.parse(iso);
351
725
  if (!Number.isFinite(then)) return "";
@@ -371,50 +745,358 @@ window.__ModuleLoader__.load({
371
745
  }, []);
372
746
  return now;
373
747
  }
374
- function Card({ view, state, conversation, now, t, onOpen }) {
375
- const opening = state.opening === view.root;
376
- const failure = state.failure?.root === view.root ? state.failure.message : "";
377
- const notice = state.notice?.root === view.root ? state.notice.message : "";
378
- const tint = isSkill(view.skill) ? TINTS[view.skill] : "#8a8a8a";
379
- const when = ago(view.openedAt, now, t);
380
- const meta = conversation ? `${when} · ${t("card.in", { title: conversation })}` : when;
381
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
382
- "button",
748
+ var ITEMS = "[role^=menuitem]";
749
+ function menuKeys(event, close) {
750
+ if (event.key === "Escape") {
751
+ event.preventDefault();
752
+ event.stopPropagation();
753
+ close();
754
+ return;
755
+ }
756
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
757
+ event.preventDefault();
758
+ event.stopPropagation();
759
+ const items = [...event.currentTarget.querySelectorAll(ITEMS)].filter((item) => item.closest("[role=menu]") === event.currentTarget);
760
+ const at = items.indexOf(document.activeElement);
761
+ items[(at + (event.key === "ArrowDown" ? 1 : items.length - 1)) % items.length]?.focus();
762
+ }
763
+ function Menu({ anchor, label, onClose, onKey, children }) {
764
+ const ref = (0, import_react.useRef)(null);
765
+ const [place, setPlace] = (0, import_react.useState)({ visibility: "hidden" });
766
+ (0, import_react.useLayoutEffect)(() => {
767
+ const box = anchor.getBoundingClientRect();
768
+ const height = ref.current?.offsetHeight ?? 0;
769
+ const width = ref.current?.offsetWidth ?? 0;
770
+ const right = Math.max(8, window.innerWidth - box.right);
771
+ const fits = box.bottom + 6 + height <= window.innerHeight - 8;
772
+ setPlace({
773
+ right: window.innerWidth - right - width < 8 ? window.innerWidth - width - 8 : right,
774
+ ...fits ? { top: box.bottom + 6 } : { bottom: Math.max(8, window.innerHeight - box.top + 6) }
775
+ });
776
+ }, [anchor]);
777
+ const placed = place.visibility !== "hidden";
778
+ (0, import_react.useEffect)(() => {
779
+ if (placed) ref.current?.querySelector(ITEMS)?.focus({ preventScroll: true });
780
+ }, [placed]);
781
+ (0, import_react.useEffect)(() => {
782
+ const away = (event) => {
783
+ const target = event.target;
784
+ if (!ref.current?.contains(target) && !anchor.contains(target)) onClose();
785
+ };
786
+ const gone = () => onClose();
787
+ document.addEventListener("pointerdown", away, true);
788
+ window.addEventListener("resize", gone);
789
+ return () => {
790
+ document.removeEventListener("pointerdown", away, true);
791
+ window.removeEventListener("resize", gone);
792
+ };
793
+ }, [anchor, onClose]);
794
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
795
+ "div",
383
796
  {
384
- type: "button",
385
- className: "bda-card",
386
- style: { "--bda-tint": tint },
387
- disabled: state.opening !== null,
388
- "aria-busy": opening,
389
- title: view.root,
390
- onClick: () => onOpen(view),
391
- "data-skill": view.skill,
797
+ ref,
798
+ className: "bda-menu",
799
+ role: "menu",
800
+ "aria-label": label,
801
+ style: place,
802
+ onKeyDown: (event) => {
803
+ if (onKey && !event.metaKey && !event.ctrlKey && !event.altKey && onKey(event.key.toLowerCase())) {
804
+ event.preventDefault();
805
+ return;
806
+ }
807
+ menuKeys(event, () => {
808
+ onClose();
809
+ anchor.focus();
810
+ });
811
+ },
812
+ children
813
+ }
814
+ );
815
+ }
816
+ function ChoiceItem({ label, on, count, onPick }) {
817
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-menu-item", role: "menuitemradio", "aria-checked": on, onClick: onPick, children: [
818
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: label }),
819
+ count !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-count", children: count }),
820
+ on && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-tick", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.tick }) })
821
+ ] });
822
+ }
823
+ function SubMenu({ id, label, value, open, setOpen, children }) {
824
+ const isOpen = open === id;
825
+ const panel = (0, import_react.useRef)(null);
826
+ const [flip, setFlip] = (0, import_react.useState)(false);
827
+ (0, import_react.useLayoutEffect)(() => {
828
+ if (isOpen && panel.current) setFlip(panel.current.getBoundingClientRect().right + (flip ? panel.current.offsetWidth : 0) > window.innerWidth - 8);
829
+ }, [isOpen]);
830
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-menu-row", onMouseEnter: () => setOpen(id), children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
832
+ "button",
833
+ {
834
+ type: "button",
835
+ className: "bda-menu-item",
836
+ role: "menuitem",
837
+ "aria-haspopup": "menu",
838
+ "aria-expanded": isOpen,
839
+ onClick: () => setOpen(isOpen ? null : id),
840
+ onKeyDown: (event) => {
841
+ if (event.key === "ArrowRight" || event.key === "Enter" || event.key === " ") {
842
+ event.preventDefault();
843
+ setOpen(id);
844
+ requestAnimationFrame(() => panel.current?.querySelector(ITEMS)?.focus());
845
+ }
846
+ },
847
+ children: [
848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: label }),
849
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-value", children: value }),
850
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-chev", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.chevron, size: 12 }) })
851
+ ]
852
+ }
853
+ ),
854
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
855
+ "div",
856
+ {
857
+ ref: panel,
858
+ className: "bda-submenu",
859
+ role: "menu",
860
+ "aria-label": label,
861
+ "data-flip": flip,
862
+ onKeyDown: (event) => {
863
+ if (event.key === "ArrowLeft" || event.key === "Escape") {
864
+ event.preventDefault();
865
+ event.stopPropagation();
866
+ setOpen(null);
867
+ event.currentTarget.previousElementSibling?.focus();
868
+ return;
869
+ }
870
+ menuKeys(event, () => setOpen(null));
871
+ },
872
+ children
873
+ }
874
+ )
875
+ ] });
876
+ }
877
+ function FilterMenu({ anchor, filters, setFilters, types, counts, t, onClose }) {
878
+ const [open, setOpen] = (0, import_react.useState)(null);
879
+ const typeLabel = filters.types.length === 0 ? t("filter.any") : filters.types.length === 1 ? t(`type.${filters.types[0]}`) : t("filter.some", { count: String(filters.types.length) });
880
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Menu, { anchor, label: t("filter.button"), onClose, children: [
881
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SubMenu, { id: "sort", label: t("filter.sort"), value: t(`sort.${filters.sort}`), open, setOpen, children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChoiceItem, { label: t(`sort.${sort}`), on: filters.sort === sort, onPick: () => setFilters({ ...filters, sort }) }, sort)) }),
882
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-menu-rule", role: "separator" }),
883
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SubMenu, { id: "type", label: t("filter.type"), value: typeLabel, open, setOpen, children: types.map((type) => {
884
+ const on = filters.types.includes(type);
885
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
886
+ "button",
887
+ {
888
+ type: "button",
889
+ className: "bda-menu-item",
890
+ role: "menuitemcheckbox",
891
+ "aria-checked": on,
892
+ onClick: () => setFilters({ ...filters, types: on ? filters.types.filter((x) => x !== type) : [...filters.types, type] }),
893
+ children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-check", "data-on": on, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.tick, size: 12 }) }),
895
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: t(`type.${type}`) }),
896
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-count", children: counts.get(type) ?? 0 })
897
+ ]
898
+ },
899
+ type
900
+ );
901
+ }) }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SubMenu, { id: "date", label: t("filter.date"), value: t(`date.${filters.date}`), open, setOpen, children: DATES.map((date) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChoiceItem, { label: t(`date.${date}`), on: filters.date === date, onPick: () => setFilters({ ...filters, date }) }, date)) }),
903
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SubMenu, { id: "sharing", label: t("filter.sharing"), value: t(`sharing.${filters.sharing}`), open, setOpen, children: SHARINGS.map((sharing) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChoiceItem, { label: t(`sharing.${sharing}`), on: filters.sharing === sharing, onPick: () => setFilters({ ...filters, sharing }) }, sharing)) }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-menu-rule", role: "separator" }),
905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { onMouseEnter: () => setOpen(null), children: [
906
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-menu-item", role: "menuitemcheckbox", "aria-checked": filters.pinned, onClick: () => setFilters({ ...filters, pinned: !filters.pinned }), children: [
907
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: t("filter.pinned") }),
908
+ filters.pinned && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-tick", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.tick }) })
909
+ ] }),
910
+ isFiltered(filters) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-menu-item", role: "menuitem", onClick: () => setFilters({ ...NO_FILTERS, sort: filters.sort }), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: t("filter.clear") }) })
911
+ ] })
912
+ ] });
913
+ }
914
+ function ActionsMenu({ anchor, view, t, onClose, act }) {
915
+ const item = (what, icon, label, key) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-menu-item", role: "menuitem", "aria-keyshortcuts": key, onClick: () => act(what), children: [
916
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: icon }),
917
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: label }),
918
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("kbd", { className: "bda-menu-key", children: key })
919
+ ] });
920
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
921
+ Menu,
922
+ {
923
+ anchor,
924
+ label: t("action.more"),
925
+ onClose,
926
+ onKey: (key) => {
927
+ const what = { p: "pin", r: "rename", c: "copy" }[key];
928
+ if (what === void 0) return false;
929
+ act(what);
930
+ return true;
931
+ },
392
932
  children: [
393
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-preview", children: [
394
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Preview, { skill: view.skill, title: view.title }),
395
- opening && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-busy", children: [
396
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-spinner" }),
397
- t("card.opening")
398
- ] })
399
- ] }),
400
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-info", children: [
401
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-glyph", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypeIcon, { skill: view.skill }) }),
402
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "bda-text", children: [
403
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-name", children: view.title }),
404
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-meta", children: meta }),
405
- failure && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-note", "data-kind": "error", children: failure }),
406
- notice && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-note", children: notice })
407
- ] })
933
+ item("pin", ICON.pin, t(view.pinned ? "action.unpin" : "action.pin"), "P"),
934
+ item("rename", ICON.rename, t("action.rename"), "R"),
935
+ item("copy", ICON.link, t("action.copy"), "C"),
936
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-menu-rule", role: "separator" }),
937
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-menu-item", role: "menuitem", "data-danger": "true", onClick: () => act("delete"), children: [
938
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.trash }),
939
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: t("action.delete") })
940
+ ] })
941
+ ]
942
+ }
943
+ );
944
+ }
945
+ function CreateMenu({ anchor, skills, canCreate, t, onClose, create }) {
946
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Menu, { anchor, label: t("create.new"), onClose, children: [
947
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-menu-item", role: "menuitem", disabled: !canCreate, onClick: () => create(), children: [
948
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.chat }),
949
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: t("create.session") })
950
+ ] }),
951
+ skills.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-menu-rule", role: "separator" }),
952
+ skills.map((skill) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-menu-item", role: "menuitem", "data-skill": skill, onClick: () => create(skill), style: { "--bda-tint": tintOf(skill) }, children: [
953
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "color-mix(in srgb,var(--bda-tint) 78%,var(--dsw-alias-label-primary))" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypeIcon, { skill, size: 16 }) }),
954
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-menu-label", children: t(`type.${skill}`) })
955
+ ] }, skill))
956
+ ] });
957
+ }
958
+ function Dialog({ label, onClose, children }) {
959
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
960
+ "div",
961
+ {
962
+ className: "bda-scrim",
963
+ onPointerDown: (event) => {
964
+ if (event.target === event.currentTarget) onClose();
965
+ },
966
+ onKeyDown: (event) => {
967
+ if (event.key === "Escape") {
968
+ event.stopPropagation();
969
+ onClose();
970
+ }
971
+ },
972
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-dialog", role: "dialog", "aria-modal": "true", "aria-label": label, children })
973
+ }
974
+ );
975
+ }
976
+ function RenameDialog({ view, t, onClose, onSave }) {
977
+ const [title, setTitle] = (0, import_react.useState)(view.title);
978
+ const input = (0, import_react.useRef)(null);
979
+ (0, import_react.useEffect)(() => {
980
+ input.current?.focus();
981
+ input.current?.select();
982
+ }, []);
983
+ const clean = title.trim();
984
+ const ready = clean !== "" && clean !== view.title;
985
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog, { label: t("rename.title"), onClose, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
986
+ "form",
987
+ {
988
+ onSubmit: (event) => {
989
+ event.preventDefault();
990
+ if (ready) onSave(clean);
991
+ },
992
+ children: [
993
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("rename.title") }),
994
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ref: input, value: title, "aria-label": t("rename.title"), maxLength: 200, onChange: (event) => setTitle(event.target.value) }),
995
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-dialog-actions", children: [
996
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-btn", onClick: onClose, children: t("dialog.cancel") }),
997
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", className: "bda-btn", "data-primary": "true", disabled: !ready, children: t("rename.save") })
408
998
  ] })
409
999
  ]
410
1000
  }
411
1001
  ) });
412
1002
  }
413
- function ArtifactsPage({ t, artifactsStore, sessionsStore, reloadArtifacts, openArtifact }) {
1003
+ function DeleteDialog({ view, t, onClose, onDelete }) {
1004
+ const confirm = (0, import_react.useRef)(null);
1005
+ (0, import_react.useEffect)(() => confirm.current?.focus(), []);
1006
+ const title = t("delete.title", { title: view.title });
1007
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Dialog, { label: title, onClose, children: [
1008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: title }),
1009
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: view.deletable ? t("delete.moves") : t("delete.keeps", { path: view.root }) }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-dialog-actions", children: [
1011
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-btn", onClick: onClose, children: t("dialog.cancel") }),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ref: confirm, type: "button", className: "bda-btn", "data-danger": "true", onClick: onDelete, children: t("delete.confirm") })
1013
+ ] })
1014
+ ] });
1015
+ }
1016
+ function ToastBar({ toast, t, onClose, onUndo }) {
1017
+ (0, import_react.useEffect)(() => {
1018
+ const timer = setTimeout(onClose, toast.undo || toast.kind === "error" ? 1e4 : 4e3);
1019
+ return () => clearTimeout(timer);
1020
+ }, [toast, onClose]);
1021
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-toast", role: toast.kind === "error" ? "alert" : "status", "data-kind": toast.kind, children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.info }),
1023
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-toast-text", children: toast.message }),
1024
+ toast.undo && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-toast-undo", onClick: () => onUndo(toast.undo), children: t("toast.undo") }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-toast-close", "aria-label": t("toast.close"), onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.clear, size: 14 }) })
1026
+ ] });
1027
+ }
1028
+ var hint = (view, conversation) => conversation ? `${view.root}
1029
+ ${conversation}` : view.root;
1030
+ function PinButton({ view, t, onPin }) {
1031
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-act", "data-on": view.pinned, "aria-pressed": view.pinned, "aria-label": t(view.pinned ? "action.unpin" : "action.pin"), title: t(view.pinned ? "action.unpin" : "action.pin"), onClick: () => onPin(view), children: view.pinned ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", style: svg(16), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9.75 2.25 13.75 6.25M10.75 3.25 7 6.25 4.5 6.5 9.5 11.5 9.75 9 12.75 5.25M7 9l-4 4", fill: "currentColor", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.pin }) });
1032
+ }
1033
+ function MoreButton({ view, t, menuOpen, onMenu, extra }) {
1034
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: `bda-act ${extra ?? ""}`, "aria-label": t("action.more"), title: t("action.more"), "aria-haspopup": "menu", "aria-expanded": menuOpen, onClick: (event) => onMenu(view, event.currentTarget), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.more }) });
1035
+ }
1036
+ function Card({ view, state, conversation, now, t, thumbnailUrl, onOpen, onPin, menuOpen, onMenu }) {
1037
+ const opening = state.opening === view.root;
1038
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "bda-item", "data-menu": menuOpen, style: { "--bda-tint": tintOf(view.skill) }, children: [
1039
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-card", disabled: state.opening !== null, "aria-busy": opening, title: hint(view, conversation), onClick: () => onOpen(view), "data-skill": view.skill, children: [
1040
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "bda-preview-wrap", style: { display: "block", position: "relative", width: "100%" }, children: [
1041
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Preview, { view, thumbnailUrl }),
1042
+ opening && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "bda-busy", children: [
1043
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-spinner" }),
1044
+ t("card.opening")
1045
+ ] })
1046
+ ] }),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "bda-info", children: [
1048
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-glyph", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypeIcon, { skill: view.skill }) }),
1049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "bda-text", children: [
1050
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-name", children: view.title }),
1051
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-meta", children: ago(view.openedAt, now, t) })
1052
+ ] })
1053
+ ] })
1054
+ ] }),
1055
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-actions", children: [
1056
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MoreButton, { view, t, menuOpen, onMenu }),
1057
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PinButton, { view, t, onPin })
1058
+ ] })
1059
+ ] });
1060
+ }
1061
+ function Row({ view, state, conversation, now, t, onOpen, onPin, menuOpen, onMenu }) {
1062
+ const opening = state.opening === view.root;
1063
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "bda-item", "data-menu": menuOpen, style: { "--bda-tint": tintOf(view.skill) }, children: [
1064
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-row", disabled: state.opening !== null, "aria-busy": opening, title: hint(view, conversation), onClick: () => onOpen(view), "data-skill": view.skill, children: [
1065
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-glyph", children: opening ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-spinner" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypeIcon, { skill: view.skill }) }),
1066
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-name", children: view.title }),
1067
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-meta", children: opening ? t("card.opening") : ago(view.openedAt, now, t) })
1068
+ ] }),
1069
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-row-tools", children: [
1070
+ view.shared && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-shared", title: t("card.shared"), "aria-label": t("card.shared"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.people }) }),
1071
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PinButton, { view, t, onPin }),
1072
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MoreButton, { view, t, menuOpen, onMenu, extra: "bda-act-more" })
1073
+ ] })
1074
+ ] });
1075
+ }
1076
+ function readView() {
1077
+ try {
1078
+ return localStorage.getItem(VIEW_KEY) === "list" ? "list" : "grid";
1079
+ } catch {
1080
+ return "grid";
1081
+ }
1082
+ }
1083
+ function writeView(view) {
1084
+ try {
1085
+ localStorage.setItem(VIEW_KEY, view);
1086
+ } catch {
1087
+ }
1088
+ }
1089
+ function ArtifactsPage(props) {
1090
+ const { t, artifactsStore, sessionsStore, reloadArtifacts, openArtifact, create, canCreate, createOptions, pin, rename, copyLink, remove, undo, thumbnailUrl, watchThumbnails, dismissToast } = props;
414
1091
  const state = (0, import_react.useSyncExternalStore)(artifactsStore.subscribe, artifactsStore.getSnapshot);
415
1092
  const sessions = (0, import_react.useSyncExternalStore)(sessionsStore.subscribe, sessionsStore.getSnapshot);
416
1093
  const [query, setQuery] = (0, import_react.useState)("");
417
- const [skill, setSkill] = (0, import_react.useState)("all");
1094
+ const [searching, setSearching] = (0, import_react.useState)(false);
1095
+ const [filters, setFilters] = (0, import_react.useState)(NO_FILTERS);
1096
+ const [layout, setLayout] = (0, import_react.useState)(readView);
1097
+ const [popup, setPopup] = (0, import_react.useState)(null);
1098
+ const [dialog, setDialog] = (0, import_react.useState)(null);
1099
+ const searchBox = (0, import_react.useRef)(null);
418
1100
  const now = useNow();
419
1101
  (0, import_react.useEffect)(() => {
420
1102
  reloadArtifacts();
@@ -424,52 +1106,190 @@ window.__ModuleLoader__.load({
424
1106
  document.addEventListener("visibilitychange", again);
425
1107
  return () => document.removeEventListener("visibilitychange", again);
426
1108
  }, [reloadArtifacts]);
427
- const present = (0, import_react.useMemo)(() => SKILLS.filter((s) => state.artifacts.some((a) => a.skill === s)), [state.artifacts]);
1109
+ (0, import_react.useEffect)(() => watchThumbnails(), [watchThumbnails]);
1110
+ (0, import_react.useEffect)(() => {
1111
+ if (searching) searchBox.current?.focus();
1112
+ }, [searching]);
1113
+ const counts = (0, import_react.useMemo)(() => {
1114
+ const by = /* @__PURE__ */ new Map();
1115
+ for (const a of state.artifacts) by.set(a.skill, (by.get(a.skill) ?? 0) + 1);
1116
+ return by;
1117
+ }, [state.artifacts]);
1118
+ const types = (0, import_react.useMemo)(() => SKILLS.filter((s) => state.skills.includes(s) || counts.has(s)), [state.skills, counts]);
1119
+ const tiles = (0, import_react.useMemo)(() => createOptions(), [state.skills, createOptions]);
428
1120
  const shown = (0, import_react.useMemo)(() => {
429
1121
  const q = query.trim().toLowerCase();
430
- return state.artifacts.filter((a) => {
431
- if (skill !== "all" && a.skill !== skill) return false;
1122
+ const kept = state.artifacts.filter((a) => {
1123
+ if (filters.types.length > 0 && !filters.types.includes(a.skill)) return false;
1124
+ if (filters.pinned && !a.pinned) return false;
1125
+ if (filters.sharing !== "any" && a.shared !== (filters.sharing === "shared")) return false;
1126
+ if (!inRange(a.openedAt, filters.date, now)) return false;
432
1127
  if (!q) return true;
433
1128
  const conversation = sessions.byId?.[a.sessionId]?.title ?? "";
434
1129
  return a.title.toLowerCase().includes(q) || conversation.toLowerCase().includes(q) || a.root.toLowerCase().includes(q);
435
1130
  });
436
- }, [state.artifacts, skill, query, sessions]);
437
- (0, import_react.useEffect)(() => {
438
- if (skill !== "all" && state.status === "ready" && !present.includes(skill)) setSkill("all");
439
- }, [skill, present, state.status]);
440
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", { className: "bda-page", "aria-label": t("title"), "data-testid": "baoyu-design-artifacts", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-content", children: [
441
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-heading", children: [
442
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: t("title") }),
443
- state.artifacts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "bda-search", children: [
444
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}),
445
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "search", value: query, placeholder: t("search"), "aria-label": t("search"), onChange: (e) => setQuery(e.target.value) }),
446
- query && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": t("search.clear"), onClick: () => setQuery(""), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClearIcon, {}) })
447
- ] })
448
- ] }),
449
- present.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-filters", role: "toolbar", children: ["all", ...present].map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-tab", "aria-pressed": skill === s, onClick: () => setSkill(s), children: s === "all" ? t("filter.all") : t(`type.${s}`) }, s)) }),
450
- state.status === "loading" && state.artifacts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "bda-grid bda-skeleton", "aria-hidden": "true", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-card", children: [
451
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-preview" }),
452
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-line" })
453
- ] }) }, i)) }) : state.status === "error" && state.artifacts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-empty", role: "alert", children: [
454
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("load.error", { message: state.error }) }),
455
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-retry", onClick: reloadArtifacts, children: t("load.retry") })
456
- ] }) : state.artifacts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-empty", children: [
457
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactsIcon, { size: 28 }),
458
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("empty.title") }),
459
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("empty.body") })
460
- ] }) : shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-empty", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("empty.noMatches") }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "bda-grid", children: shown.map((view) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
461
- Card,
1131
+ return sorted(kept, filters.sort, t("time.locale"));
1132
+ }, [state.artifacts, filters, query, sessions, now, t]);
1133
+ const closePopup = (0, import_react.useMemo)(() => () => setPopup(null), []);
1134
+ const actionsFor = popup?.kind === "actions" ? state.artifacts.find((a) => a.root === popup.root) : void 0;
1135
+ const dialogFor = dialog ? state.artifacts.find((a) => a.root === dialog.root) : void 0;
1136
+ const itemProps = (view) => ({
1137
+ view,
1138
+ state,
1139
+ conversation: sessions.byId?.[view.sessionId]?.title?.trim() ?? "",
1140
+ now,
1141
+ t,
1142
+ thumbnailUrl,
1143
+ onOpen: openArtifact,
1144
+ onPin: (v) => pin(v, !v.pinned),
1145
+ menuOpen: popup?.kind === "actions" && popup.root === view.root,
1146
+ onMenu: (v, anchor) => setPopup((p) => p?.kind === "actions" && p.root === v.root ? null : { kind: "actions", anchor, root: v.root })
1147
+ });
1148
+ const toggle = (kind, anchor) => setPopup((p) => p?.kind === kind ? null : { kind, anchor });
1149
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "bda-page", "aria-label": t("title"), "data-testid": "baoyu-design-artifacts", children: [
1150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-content", children: [
1151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-heading", children: [
1152
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: t("title") }),
1153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-tools", children: [
1154
+ searching || query ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "bda-search", children: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.search, size: 14 }),
1156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1157
+ "input",
1158
+ {
1159
+ ref: searchBox,
1160
+ type: "search",
1161
+ value: query,
1162
+ placeholder: t("search"),
1163
+ "aria-label": t("search"),
1164
+ onChange: (e) => setQuery(e.target.value),
1165
+ onBlur: () => {
1166
+ if (!query) setSearching(false);
1167
+ },
1168
+ onKeyDown: (e) => {
1169
+ if (e.key === "Escape") {
1170
+ setQuery("");
1171
+ setSearching(false);
1172
+ }
1173
+ }
1174
+ }
1175
+ ),
1176
+ query && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1177
+ "button",
1178
+ {
1179
+ type: "button",
1180
+ "aria-label": t("search.clear"),
1181
+ onClick: () => {
1182
+ setQuery("");
1183
+ searchBox.current?.focus();
1184
+ },
1185
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.clear, size: 12 })
1186
+ }
1187
+ )
1188
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-tool", "aria-label": t("search"), title: t("search"), onClick: () => setSearching(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.search, size: 18 }) }),
1189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-tool", "aria-label": t("filter.button"), title: t("filter.button"), "aria-haspopup": "menu", "aria-expanded": popup?.kind === "filter", "data-active": isFiltered(filters), onClick: (e) => toggle("filter", e.currentTarget), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.filter, size: 18 }) }),
1190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1191
+ "button",
1192
+ {
1193
+ type: "button",
1194
+ className: "bda-tool",
1195
+ "aria-label": t(layout === "grid" ? "view.list" : "view.grid"),
1196
+ title: t(layout === "grid" ? "view.list" : "view.grid"),
1197
+ onClick: () => {
1198
+ const next = layout === "grid" ? "list" : "grid";
1199
+ setLayout(next);
1200
+ writeView(next);
1201
+ },
1202
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: layout === "grid" ? ICON.list : ICON.grid, size: 18 })
1203
+ }
1204
+ ),
1205
+ canCreate() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-tool", "aria-label": t("create.new"), title: t("create.new"), "aria-haspopup": "menu", "aria-expanded": popup?.kind === "create", onClick: (e) => toggle("create", e.currentTarget), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { d: ICON.plus, size: 18 }) })
1206
+ ] })
1207
+ ] }),
1208
+ tiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "bda-section-title", children: t("create.heading") }),
1210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "bda-tiles", children: tiles.map((skill) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "bda-tile", "data-skill": skill, style: { "--bda-tint": tintOf(skill) }, onClick: () => create(skill), children: [
1211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "bda-tile-art", children: [
1212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "bda-tile-squiggle", viewBox: "0 0 140 100", preserveAspectRatio: "xMidYMid slice", fill: "none", stroke: "currentColor", strokeWidth: "1.1", strokeDasharray: "0 3.2", strokeLinecap: "round", "aria-hidden": "true", children: [
1213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 84C20 16 42 98 62 42s38 58 74-28" }),
1214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 88C24 22 44 104 66 46s36 56 72-26" }),
1215
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 80C18 12 40 92 58 38s40 58 76-28" })
1216
+ ] }),
1217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypeIcon, { skill, size: 40 })
1218
+ ] }),
1219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bda-tile-label", children: t(`type.${skill}`) })
1220
+ ] }) }, skill)) })
1221
+ ] }),
1222
+ state.status === "loading" && state.artifacts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "bda-grid bda-skeleton", "aria-hidden": "true", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "bda-item", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-card", children: [
1223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-preview" }),
1224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "bda-line" })
1225
+ ] }) }, i)) }) : state.status === "error" && state.artifacts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-empty", role: "alert", children: [
1226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("load.error", { message: state.error }) }),
1227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-retry", onClick: reloadArtifacts, children: t("load.retry") })
1228
+ ] }) : state.artifacts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-empty", children: [
1229
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactsIcon, { size: 28 }),
1230
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("empty.title") }),
1231
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("empty.body") })
1232
+ ] }) : shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bda-empty", children: [
1233
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("empty.noMatches") }),
1234
+ isFiltered(filters) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "bda-retry", onClick: () => setFilters({ ...NO_FILTERS, sort: filters.sort }), children: t("filter.clear") })
1235
+ ] }) : layout === "grid" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "bda-grid", children: shown.map((view) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { ...itemProps(view) }, view.root)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "bda-list", children: shown.map((view) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, { ...itemProps(view) }, view.root)) })
1236
+ ] }) }),
1237
+ popup?.kind === "filter" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterMenu, { anchor: popup.anchor, filters, setFilters, types, counts, t, onClose: closePopup }),
1238
+ popup?.kind === "create" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1239
+ CreateMenu,
462
1240
  {
463
- view,
464
- state,
465
- conversation: sessions.byId?.[view.sessionId]?.title?.trim() ?? "",
466
- now,
1241
+ anchor: popup.anchor,
1242
+ skills: tiles,
1243
+ canCreate: canCreate(),
467
1244
  t,
468
- onOpen: openArtifact
469
- },
470
- view.root
471
- )) })
472
- ] }) }) });
1245
+ onClose: closePopup,
1246
+ create: (skill) => {
1247
+ setPopup(null);
1248
+ create(skill);
1249
+ }
1250
+ }
1251
+ ),
1252
+ popup?.kind === "actions" && actionsFor && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1253
+ ActionsMenu,
1254
+ {
1255
+ anchor: popup.anchor,
1256
+ view: actionsFor,
1257
+ t,
1258
+ onClose: closePopup,
1259
+ act: (what) => {
1260
+ setPopup(null);
1261
+ if (what === "pin") pin(actionsFor, !actionsFor.pinned);
1262
+ else if (what === "copy") copyLink(actionsFor);
1263
+ else setDialog({ kind: what, root: actionsFor.root });
1264
+ }
1265
+ }
1266
+ ),
1267
+ dialog?.kind === "rename" && dialogFor && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1268
+ RenameDialog,
1269
+ {
1270
+ view: dialogFor,
1271
+ t,
1272
+ onClose: () => setDialog(null),
1273
+ onSave: (title) => {
1274
+ setDialog(null);
1275
+ rename(dialogFor, title);
1276
+ }
1277
+ }
1278
+ ),
1279
+ dialog?.kind === "delete" && dialogFor && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1280
+ DeleteDialog,
1281
+ {
1282
+ view: dialogFor,
1283
+ t,
1284
+ onClose: () => setDialog(null),
1285
+ onDelete: () => {
1286
+ setDialog(null);
1287
+ remove(dialogFor);
1288
+ }
1289
+ }
1290
+ ),
1291
+ state.toast && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToastBar, { toast: state.toast, t, onClose: dismissToast, onUndo: undo }, state.toast.id)
1292
+ ] });
473
1293
  }
474
1294
  var SIDEBAR_WAIT_MS = 5e3;
475
1295
  async function openInSidebar(sidebar, sessionId, url) {
@@ -500,10 +1320,27 @@ window.__ModuleLoader__.load({
500
1320
  disposers.push(ctx.locale.register(NS, { zh, en }));
501
1321
  const t = ctx.locale.bind?.(NS) ?? ((key) => en[key] ?? key);
502
1322
  const skillsOn = () => [...new Set([...h.members.values()].map((m) => m.skill))];
503
- const store = createStore({ status: "loading", artifacts: [], error: "", skills: skillsOn(), opening: null, failure: null, notice: null });
1323
+ const store = createStore({ status: "loading", artifacts: [], error: "", skills: skillsOn(), opening: null, toast: null });
504
1324
  const onMembers = () => store.set({ skills: skillsOn() });
505
1325
  h.listeners.add(onMembers);
506
1326
  disposers.push(() => h.listeners.delete(onMembers));
1327
+ let toastId = 0;
1328
+ const toast = (message, kind = "info", undo2) => store.set({ toast: { id: ++toastId, message, kind, ...undo2 ? { undo: undo2 } : {} } });
1329
+ const failed = (error) => toast(t("action.failed", { message: error.message }), "error");
1330
+ const patchView = (root, patch) => store.set({ artifacts: store.getSnapshot().artifacts.map((a) => a.root === root ? { ...a, ...patch } : a) });
1331
+ const call = async (path, params, init = { method: "POST" }) => {
1332
+ const response = await fetch(`${route(path)}?${new URLSearchParams(params)}`, init);
1333
+ const body = await response.json().catch(() => ({}));
1334
+ if (!response.ok) throw new Error(typeof body.error === "string" ? body.error : `HTTP ${response.status}`);
1335
+ return body;
1336
+ };
1337
+ const ownPluginOn = (view) => {
1338
+ if (skillsOn().includes(view.skill)) return true;
1339
+ toast(t("open.pluginOff", { plugin: view.plugin }), "error");
1340
+ return false;
1341
+ };
1342
+ let pump = () => {
1343
+ };
507
1344
  let loading = null;
508
1345
  const reload = () => {
509
1346
  if (loading) return;
@@ -518,6 +1355,7 @@ window.__ModuleLoader__.load({
518
1355
  const body = await response.json();
519
1356
  const artifacts = Array.isArray(body.artifacts) ? body.artifacts.filter((a) => a && typeof a.root === "string") : [];
520
1357
  store.set({ status: "ready", artifacts, error: "" });
1358
+ pump();
521
1359
  return;
522
1360
  } catch (error) {
523
1361
  message ||= error.message;
@@ -530,11 +1368,8 @@ window.__ModuleLoader__.load({
530
1368
  };
531
1369
  const open = (view) => {
532
1370
  if (store.getSnapshot().opening !== null) return;
533
- if (!skillsOn().includes(view.skill)) {
534
- store.set({ failure: { root: view.root, message: t("open.pluginOff", { plugin: view.plugin }) }, notice: null });
535
- return;
536
- }
537
- store.set({ opening: view.root, failure: null, notice: null });
1371
+ if (!ownPluginOn(view)) return;
1372
+ store.set({ opening: view.root, toast: null });
538
1373
  void (async () => {
539
1374
  try {
540
1375
  const response = await fetch(`${route(openPath(view.skill))}?${new URLSearchParams({ root: view.root })}`, { method: "POST" });
@@ -555,14 +1390,137 @@ window.__ModuleLoader__.load({
555
1390
  store.set({ opening: null });
556
1391
  } else {
557
1392
  window.open(url, "_blank", "noopener,noreferrer");
558
- store.set({ opening: null, notice: { root: view.root, message: t("open.noSession") } });
1393
+ store.set({ opening: null });
1394
+ toast(t("open.noSession"));
559
1395
  }
560
1396
  reload();
561
1397
  } catch (error) {
562
- store.set({ opening: null, failure: { root: view.root, message: t("open.failed", { message: error.message }) } });
1398
+ store.set({ opening: null });
1399
+ toast(t("open.failed", { message: error.message }), "error");
1400
+ }
1401
+ })();
1402
+ };
1403
+ const workspaceNav = () => ctx.get("uiWorkspace");
1404
+ const canCreate = () => typeof workspaceNav()?.startSession === "function";
1405
+ const createOptions = () => canCreate() ? creatable(skillsOn()) : [];
1406
+ const create = (skill) => {
1407
+ const nav = workspaceNav();
1408
+ if (typeof nav?.startSession !== "function") return;
1409
+ if (skill === void 0 || !isSkill(skill)) return nav.startSession();
1410
+ nav.startSession(void 0, { prompt: promptFor(skill, t), whenDraftExists: "append" });
1411
+ };
1412
+ const pin = (view, pinned) => {
1413
+ if (!ownPluginOn(view)) return;
1414
+ patchView(view.root, { pinned });
1415
+ call(updatePath(view.skill), { root: view.root }, { method: "POST", body: JSON.stringify({ pinned }) }).catch((error) => {
1416
+ patchView(view.root, { pinned: view.pinned });
1417
+ failed(error);
1418
+ });
1419
+ };
1420
+ const rename = (view, title) => {
1421
+ if (!ownPluginOn(view)) return;
1422
+ patchView(view.root, { title });
1423
+ call(updatePath(view.skill), { root: view.root }, { method: "POST", body: JSON.stringify({ title }) }).then(reload, (error) => {
1424
+ patchView(view.root, { title: view.title });
1425
+ failed(error);
1426
+ });
1427
+ };
1428
+ const copyText = async (text) => {
1429
+ try {
1430
+ await navigator.clipboard.writeText(text);
1431
+ } catch {
1432
+ const area = document.createElement("textarea");
1433
+ area.value = text;
1434
+ area.style.cssText = "position:fixed;opacity:0";
1435
+ document.body.append(area);
1436
+ area.select();
1437
+ const done = document.execCommand("copy");
1438
+ area.remove();
1439
+ if (!done) throw new Error("the clipboard refused it");
1440
+ }
1441
+ };
1442
+ const copyLink = (view) => {
1443
+ if (!ownPluginOn(view)) return;
1444
+ void (async () => {
1445
+ try {
1446
+ const { url } = await call(previewPath(view.skill), { root: view.root });
1447
+ await call(releasePath(view.skill), { root: view.root, keep: "1" });
1448
+ if (typeof url !== "string") throw new Error("no address");
1449
+ await copyText(url);
1450
+ toast(t("toast.copied"));
1451
+ } catch (error) {
1452
+ failed(error);
563
1453
  }
564
1454
  })();
565
1455
  };
1456
+ const remove = (view) => {
1457
+ if (!ownPluginOn(view)) return;
1458
+ void (async () => {
1459
+ try {
1460
+ const { trash, moved } = await call(deletePath(view.skill), { root: view.root });
1461
+ store.set({ artifacts: store.getSnapshot().artifacts.filter((a) => a.root !== view.root) });
1462
+ undoVia.set(String(trash), view.skill);
1463
+ toast(t(moved === true ? "toast.deleted" : "toast.removed", { title: view.title }), "info", String(trash));
1464
+ } catch (error) {
1465
+ failed(error);
1466
+ }
1467
+ })();
1468
+ };
1469
+ const undoVia = /* @__PURE__ */ new Map();
1470
+ const undo = (id) => {
1471
+ store.set({ toast: null });
1472
+ call(restorePath(undoVia.get(id) ?? self), { trash: id }).then(reload, failed);
1473
+ };
1474
+ const thumbnailUrl = (view) => view.thumbnail === null ? null : `${route(thumbnailPath(self))}?${new URLSearchParams({ root: view.root, v: view.thumbnail })}`;
1475
+ const browser = desktopBrowser();
1476
+ const tried = /* @__PURE__ */ new Set();
1477
+ let watching = null;
1478
+ let taking = false;
1479
+ const takeOne = async (view, signal) => {
1480
+ const { url } = await call(previewPath(view.skill), { root: view.root });
1481
+ if (typeof url !== "string") throw new Error("no address");
1482
+ try {
1483
+ const bytes = await captureThumbnail(browser, url, view.skill, signal);
1484
+ const saved = await call(thumbnailPath(view.skill), { root: view.root }, { method: "POST", headers: { "content-type": "image/jpeg" }, body: new Uint8Array(bytes) });
1485
+ if (typeof saved.thumbnail === "string") patchView(view.root, { thumbnail: saved.thumbnail, updatedAt: saved.thumbnail });
1486
+ } finally {
1487
+ await call(releasePath(view.skill), { root: view.root }).catch(() => null);
1488
+ }
1489
+ };
1490
+ pump = () => {
1491
+ if (taking || watching === null || browser === void 0) return;
1492
+ const signal = watching.signal;
1493
+ taking = true;
1494
+ void (async () => {
1495
+ try {
1496
+ for (; ; ) {
1497
+ if (signal.aborted) return;
1498
+ if (document.visibilityState !== "visible") return;
1499
+ const on = skillsOn();
1500
+ const next = store.getSnapshot().artifacts.find((a) => a.thumbnail !== a.updatedAt && on.includes(a.skill) && !tried.has(`${a.root}\0${a.updatedAt}`));
1501
+ if (next === void 0) return;
1502
+ tried.add(`${next.root}\0${next.updatedAt}`);
1503
+ try {
1504
+ await takeOne(next, signal);
1505
+ } catch (error) {
1506
+ if (!signal.aborted) console.warn(`baoyu-design Artifacts page: no thumbnail for ${next.root}:`, error);
1507
+ }
1508
+ }
1509
+ } finally {
1510
+ taking = false;
1511
+ }
1512
+ })();
1513
+ };
1514
+ const watchThumbnails = () => {
1515
+ const controller = new AbortController();
1516
+ watching = controller;
1517
+ pump();
1518
+ return () => {
1519
+ controller.abort(new Error("the page closed"));
1520
+ if (watching === controller) watching = null;
1521
+ };
1522
+ };
1523
+ disposers.push(() => watching?.abort(new Error("the plugin stopped")));
566
1524
  const sessionsStore = () => {
567
1525
  const list = ctx.get("sessions")?.list;
568
1526
  return isStore(list) ? list : EMPTY_SESSIONS;
@@ -574,7 +1532,23 @@ window.__ModuleLoader__.load({
574
1532
  name: "main",
575
1533
  key: PANEL_ID,
576
1534
  locale: NS,
577
- inject: () => ({ artifactsStore: store, sessionsStore: sessionsStore(), reloadArtifacts: reload, openArtifact: open })
1535
+ inject: () => ({
1536
+ artifactsStore: store,
1537
+ sessionsStore: sessionsStore(),
1538
+ reloadArtifacts: reload,
1539
+ openArtifact: open,
1540
+ create,
1541
+ canCreate,
1542
+ createOptions,
1543
+ pin,
1544
+ rename,
1545
+ copyLink,
1546
+ remove,
1547
+ undo,
1548
+ thumbnailUrl,
1549
+ watchThumbnails,
1550
+ dismissToast: () => store.set({ toast: null })
1551
+ })
578
1552
  },
579
1553
  ArtifactsPage
580
1554
  )