dsh-claude-design-system 2.11.0 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// dsh-claude-design-system 2.11.
|
|
1
|
+
// dsh-claude-design-system 2.11.1: built from baoyu-design.
|
|
2
2
|
var define_PLUGIN_default = { name: "claude-design-system", provider: "dsh-claude-design-system", skills: [{ name: "claude-design-system", description: "Create and revise design systems in the Design System artifact format (project/design-system.json, tokens.json, a README brand book, component previews, fonts and asset groups) as local files, then open them in a local design-system page where the user can browse and edit tokens, type, components and assets. Use when the user asks for a design system or brand kit in this format, built from a code repository, a design tool or scratch, names claude-design-system or the Design System type, or wants to revise a folder that holds project/design-system.json. claude-slides and claude-design install these systems. Not for a free-form HTML style guide, UI kit or token page.", comments: true }] };
|
|
3
3
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-claude-design-system",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.1",
|
|
4
4
|
"description": "DeepSeek Harness plugin: the claude-design-system skill from baoyu-design. The agent writes design systems (tokens, type, components, assets) as files and opens them in a local design-system page.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
"icon": "./icon.svg",
|
|
25
25
|
"baoyuDesign": {
|
|
26
26
|
"releaseNotes": [
|
|
27
|
+
{
|
|
28
|
+
"version": "2.11.1",
|
|
29
|
+
"date": "2026-11-05",
|
|
30
|
+
"en": "### Features\n- **DeepSeek Harness: the Artifacts page offers the plugins you haven't installed.** Beside the tiles for the skills you have, each other baoyu-design plugin gets a dimmed tile marked **Not installed**. Click it to see what the plugin makes and install it from there, at the same version as the ones you have, usually without a restart. Once it's in, **Start** opens a session with the skill. In an app that can't install from the page, the tile points you to the Plugins page. A plugin you installed and switched off still gets no tile.\n\n### Fixes\n- With `--agent-hook`, a comment could reach the agent twice: a second send could start the hook again after the first run had finished but before the comment was marked sent.",
|
|
31
|
+
"zh": "### 新功能\n- **DeepSeek Harness:「作品」页会列出你还没装的插件。** 已装技能的入口旁边,其他 baoyu-design 插件也各有一个入口,颜色淡一些,标着 **未安装**。点开会说明这个插件做什么,可以直接在这里安装,版本和你已装的一致,多数情况不用重启。装好后点 **新建** 就用这个技能开一个会话。应用不支持在页面里安装时,会引导你去插件页。装了但被你关掉的插件,仍然没有入口。\n\n### 修复\n- 用 `--agent-hook` 时,一条评论可能发给 agent 两次:第一次运行已经结束、评论还没标成已发送时,第二次发送会再启动一次 hook。"
|
|
32
|
+
},
|
|
27
33
|
{
|
|
28
34
|
"version": "2.11.0",
|
|
29
35
|
"date": "2026-11-05",
|
|
@@ -65,12 +71,6 @@
|
|
|
65
71
|
"date": "2026-11-02",
|
|
66
72
|
"en": "### Fixes\n- `claude-slides`: in a PowerPoint export, Chinese, Japanese or Korean text could show as boxes when its East Asian font was missing on the reader's computer or held none of those characters: Arial in the basic-fonts export, a Latin family, or an embedded subset that left characters out of a long deck. That text now names a font Windows and Office have instead (Microsoft YaHei, Microsoft JhengHei, Yu Gothic or Malgun Gothic). Text the embedded font holds, text without these characters and the Google Slides export are unchanged ([#29](https://github.com/JimLiu/baoyu-design/issues/29)).",
|
|
67
73
|
"zh": "### 修复\n- `claude-slides`:导出 PowerPoint 时,如果中日韩文字用的东亚字体在对方电脑上没有,或者这个字体本身不含这些字(basic 字体导出里的 Arial、拉丁字体、长 deck 里漏了字的内嵌子集),文字可能显示成方块。这类文字现在改用 Windows 和 Office 常见的字体(Microsoft YaHei、Microsoft JhengHei、Yu Gothic 或 Malgun Gothic)。内嵌字体装得下的文字、不含这些字的文字和 Google Slides 导出都不变([#29](https://github.com/JimLiu/baoyu-design/issues/29))。"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"version": "2.8.1",
|
|
71
|
-
"date": "2026-11-02",
|
|
72
|
-
"en": "### Fixes\n- `claude-slides`, `claude-design`, `claude-design-system`, `claude-dashboard`: their descriptions no longer send one-off HTML work to the `baoyu-design` skill, which is archived and no longer installed. They now only say what each skill is not for. The DeepSeek Harness plugins carry the same descriptions.",
|
|
73
|
-
"zh": "### 修复\n- `claude-slides`、`claude-design`、`claude-design-system`、`claude-dashboard`:说明里不再把一次性的 HTML 产出交给 `baoyu-design` 技能。这个技能已经归档,不再随仓库安装。现在只写各技能不适用于什么。DeepSeek Harness 插件里的说明也一样。"
|
|
74
74
|
}
|
|
75
75
|
]
|
|
76
76
|
},
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"dsh": ">=0.2.0-0"
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
|
-
"dsh-claude-artifacts": "^2.11.
|
|
99
|
+
"dsh-claude-artifacts": "^2.11.1"
|
|
100
100
|
},
|
|
101
101
|
"dsh": {
|
|
102
102
|
"bundle": {
|
|
@@ -85,7 +85,7 @@ h2 { flex: 1; min-width: 0; margin: 0; font-size: 17px; font-weight: 600; letter
|
|
|
85
85
|
.toast p { margin: 0; font-size: 13.5px; overflow-wrap: anywhere; }
|
|
86
86
|
.toast .path { margin-top: 4px; font: 12.5px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--fg); user-select: text; -webkit-user-select: text; overflow-wrap: anywhere; }
|
|
87
87
|
.toast.err p { color: var(--danger); }
|
|
88
|
-
`;function V(){if(/\bElectron\/\d/.test(navigator.userAgent))return!0;if(window.top===window)return!1;try{return window.top.location.origin!==location.origin}catch{return!0}}function F(t){let e=/filename\*=UTF-8''([^;]+)/i.exec(t??"");if(e)try{return decodeURIComponent(e[1])}catch{}return/filename="([^"]+)"/i.exec(t??"")?.[1]??""}async function q(t){let e=await t.json().catch(()=>null);if(!t.ok||!e||typeof e.path!="string")throw new Error(e?.message||`HTTP ${t.status}`);return e}var Y=class{layer=o("div",{"data-baoyu-save-layer":""});wrap=o("div",{class:"wrap"});node=null;timer;constructor(){this.layer.attachShadow({mode:"open"}).append(o("style",{},E+Ce),this.wrap)}saved(e,n=N.hint){this.show([o("p",{},N.saved(e.name)),o("div",{class:"path"},e.shown),o("div",{class:"hint"},n)],!1)}failed(e){this.show([o("p",{},e)],!0)}show(e,n){this.layer.isConnected||document.body.append(this.layer),window.clearTimeout(this.timer),this.node?.remove();let i=o("div",{class:`toast${n?" err":""}`,role:n?"alert":"status","data-baoyu-save-toast":""},...e);this.wrap.append(i),this.node=i,this.timer=window.setTimeout(()=>{i.remove(),this.node===i&&(this.node=null)},n?1e4:12e3)}},He=null;function U(){return He??=new Y}async function Ne(t,e){return q(await fetch(`${Oe}?name=${encodeURIComponent(e)}`,{method:"POST",body:t,headers:{"Content-Type":t.type||"application/octet-stream"}}))}function _e(t){for(let e of t.composedPath())if(e instanceof HTMLAnchorElement&&e.hasAttribute("download")&&e.href)return e;return null}function ze(t){try{let e=new URL(t.href,location.href);return e.protocol==="blob:"||e.protocol==="data:"||e.origin===location.origin}catch{return!1}}function ce(t){if(!V())return!1;let e=U(),n=async(a,c,u={})=>{try{let f=await Ne(a,c);return u.quiet||e.saved(f),f}catch(f){throw u.quiet||e.failed(N.failed(c,f instanceof Error?f.message:String(f))),f}},i=a=>{let c=a.getAttribute("download")??"",u=a.href,f=fetch(u,{cache:"no-store"});(async()=>{let h=c||"download",g;try{let m=await f;if(!m.ok)throw new Error(`HTTP ${m.status}`);h=c||F(m.headers.get("Content-Disposition"))||"download",g=await m.blob()}catch(m){return e.failed(N.failed(h,m instanceof Error?m.message:String(m)))}await n(g,h).catch(()=>{})})()},r=a=>a!==null&&(!t||ze(a)),s=a=>{t?i(a):e.failed(N.guest)};window.addEventListener("click",a=>{let c=_e(a);a.defaultPrevented||!r(c)||(a.preventDefault(),s(c))},!0);let p=HTMLAnchorElement.prototype.click;return HTMLAnchorElement.prototype.click=function(){if(!this.isConnected&&this.hasAttribute("download")&&this.href&&r(this))return s(this);p.call(this)},t&&(window.__baoyuSave=n),!0}var J="/__share/archive",je={label:"Whole folder (.zip)",description:"Everything here, to back up or move"},R=y?{started:t=>`\u5F00\u59CB\u4E0B\u8F7D ${t}`,hint:"\u6574\u4E2A\u6587\u4EF6\u5939\u6253\u6210\u4E86\u4E00\u4E2A zip\uFF0C\u53EF\u4EE5\u5907\u4EFD\u3001\u6362\u7535\u8111\u6216\u53D1\u7ED9\u522B\u4EBA\u3002\u89E3\u538B\u540E\u7528\u540C\u4E00\u4E2A skill \u6253\u5F00\u5C31\u80FD\u63A5\u7740\u7528\uFF1B\u5206\u4EAB\u94FE\u63A5\u548C\u8FD9\u53F0\u7535\u8111\u4E0A\u7684\u670D\u52A1\u5668\u72B6\u6001\u4E0D\u5728\u91CC\u9762\u3002",beside:"\u8FD9\u4E2A\u6D4F\u89C8\u5668\u4E0D\u80FD\u4E0B\u8F7D\u6587\u4EF6\uFF0Czip \u5B58\u5728\u4E86\u6587\u4EF6\u5939\u65C1\u8FB9\u3002\u89E3\u538B\u540E\u7528\u540C\u4E00\u4E2A skill \u6253\u5F00\u5C31\u80FD\u63A5\u7740\u7528\u3002",tooLarge:"\u6587\u4EF6\u5939\u8D85\u8FC7\u4E86 zip \u7684\u4E0A\u9650\uFF084 GiB \u6216 65535 \u4E2A\u6587\u4EF6\uFF09\uFF0C\u8BF7\u76F4\u63A5\u590D\u5236\u6587\u4EF6\u5939\u3002",failed:"\u6CA1\u80FD\u6253\u5305\uFF1A"}:{started:t=>`Downloading ${t}`,hint:"The whole folder in one zip, to back up, move to another computer or send to someone. Unzip it and open it with the same skill to carry on; share links and this computer's server state are left out.",beside:"This browser doesn't download files, so the zip went beside the folder. Unzip it and open it with the same skill to carry on.",tooLarge:"The folder is over what a zip holds (4 GiB or 65,535 files): copy the folder itself instead.",failed:"Could not zip the folder: "},Be=`
|
|
88
|
+
`;function V(){if(window.__baoyuNativeDownloads===!0)return!1;if(/\bElectron\/\d/.test(navigator.userAgent))return!0;if(window.top===window)return!1;try{return window.top.location.origin!==location.origin}catch{return!0}}function F(t){let e=/filename\*=UTF-8''([^;]+)/i.exec(t??"");if(e)try{return decodeURIComponent(e[1])}catch{}return/filename="([^"]+)"/i.exec(t??"")?.[1]??""}async function q(t){let e=await t.json().catch(()=>null);if(!t.ok||!e||typeof e.path!="string")throw new Error(e?.message||`HTTP ${t.status}`);return e}var Y=class{layer=o("div",{"data-baoyu-save-layer":""});wrap=o("div",{class:"wrap"});node=null;timer;constructor(){this.layer.attachShadow({mode:"open"}).append(o("style",{},E+Ce),this.wrap)}saved(e,n=N.hint){this.show([o("p",{},N.saved(e.name)),o("div",{class:"path"},e.shown),o("div",{class:"hint"},n)],!1)}failed(e){this.show([o("p",{},e)],!0)}show(e,n){this.layer.isConnected||document.body.append(this.layer),window.clearTimeout(this.timer),this.node?.remove();let i=o("div",{class:`toast${n?" err":""}`,role:n?"alert":"status","data-baoyu-save-toast":""},...e);this.wrap.append(i),this.node=i,this.timer=window.setTimeout(()=>{i.remove(),this.node===i&&(this.node=null)},n?1e4:12e3)}},He=null;function U(){return He??=new Y}async function Ne(t,e){return q(await fetch(`${Oe}?name=${encodeURIComponent(e)}`,{method:"POST",body:t,headers:{"Content-Type":t.type||"application/octet-stream"}}))}function _e(t){for(let e of t.composedPath())if(e instanceof HTMLAnchorElement&&e.hasAttribute("download")&&e.href)return e;return null}function ze(t){try{let e=new URL(t.href,location.href);return e.protocol==="blob:"||e.protocol==="data:"||e.origin===location.origin}catch{return!1}}function ce(t){if(!V())return!1;let e=U(),n=async(a,c,u={})=>{try{let f=await Ne(a,c);return u.quiet||e.saved(f),f}catch(f){throw u.quiet||e.failed(N.failed(c,f instanceof Error?f.message:String(f))),f}},i=a=>{let c=a.getAttribute("download")??"",u=a.href,f=fetch(u,{cache:"no-store"});(async()=>{let h=c||"download",g;try{let m=await f;if(!m.ok)throw new Error(`HTTP ${m.status}`);h=c||F(m.headers.get("Content-Disposition"))||"download",g=await m.blob()}catch(m){return e.failed(N.failed(h,m instanceof Error?m.message:String(m)))}await n(g,h).catch(()=>{})})()},r=a=>a!==null&&(!t||ze(a)),s=a=>{t?i(a):e.failed(N.guest)};window.addEventListener("click",a=>{let c=_e(a);a.defaultPrevented||!r(c)||(a.preventDefault(),s(c))},!0);let p=HTMLAnchorElement.prototype.click;return HTMLAnchorElement.prototype.click=function(){if(!this.isConnected&&this.hasAttribute("download")&&this.href&&r(this))return s(this);p.call(this)},t&&(window.__baoyuSave=n),!0}var J="/__share/archive",je={label:"Whole folder (.zip)",description:"Everything here, to back up or move"},R=y?{started:t=>`\u5F00\u59CB\u4E0B\u8F7D ${t}`,hint:"\u6574\u4E2A\u6587\u4EF6\u5939\u6253\u6210\u4E86\u4E00\u4E2A zip\uFF0C\u53EF\u4EE5\u5907\u4EFD\u3001\u6362\u7535\u8111\u6216\u53D1\u7ED9\u522B\u4EBA\u3002\u89E3\u538B\u540E\u7528\u540C\u4E00\u4E2A skill \u6253\u5F00\u5C31\u80FD\u63A5\u7740\u7528\uFF1B\u5206\u4EAB\u94FE\u63A5\u548C\u8FD9\u53F0\u7535\u8111\u4E0A\u7684\u670D\u52A1\u5668\u72B6\u6001\u4E0D\u5728\u91CC\u9762\u3002",beside:"\u8FD9\u4E2A\u6D4F\u89C8\u5668\u4E0D\u80FD\u4E0B\u8F7D\u6587\u4EF6\uFF0Czip \u5B58\u5728\u4E86\u6587\u4EF6\u5939\u65C1\u8FB9\u3002\u89E3\u538B\u540E\u7528\u540C\u4E00\u4E2A skill \u6253\u5F00\u5C31\u80FD\u63A5\u7740\u7528\u3002",tooLarge:"\u6587\u4EF6\u5939\u8D85\u8FC7\u4E86 zip \u7684\u4E0A\u9650\uFF084 GiB \u6216 65535 \u4E2A\u6587\u4EF6\uFF09\uFF0C\u8BF7\u76F4\u63A5\u590D\u5236\u6587\u4EF6\u5939\u3002",failed:"\u6CA1\u80FD\u6253\u5305\uFF1A"}:{started:t=>`Downloading ${t}`,hint:"The whole folder in one zip, to back up, move to another computer or send to someone. Unzip it and open it with the same skill to carry on; share links and this computer's server state are left out.",beside:"This browser doesn't download files, so the zip went beside the folder. Unzip it and open it with the same skill to carry on.",tooLarge:"The folder is over what a zip holds (4 GiB or 65,535 files): copy the folder itself instead.",failed:"Could not zip the folder: "},Be=`
|
|
89
89
|
.toast { position: fixed; right: 16px; bottom: 16px; z-index: 2147483003; width: min(380px, calc(100vw - 32px)); background: var(--bg); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); padding: 12px 14px; }
|
|
90
90
|
.toast p { margin: 0; font-size: 13.5px; overflow-wrap: anywhere; }
|
|
91
91
|
.toast.err p { color: var(--danger); }
|
|
@@ -450,6 +450,7 @@ data: ${JSON.stringify(snap)}
|
|
|
450
450
|
if (agentHook) await runHook(agentHook);
|
|
451
451
|
}
|
|
452
452
|
async function runHook(command) {
|
|
453
|
+
if (typeof command !== "string") return runHandler(command);
|
|
453
454
|
const doc = await readComments(root);
|
|
454
455
|
for (const thread of pendingForAgent(doc)) {
|
|
455
456
|
if (hookRunning.has(thread.id)) continue;
|
|
@@ -474,15 +475,15 @@ data: ${JSON.stringify(snap)}
|
|
|
474
475
|
log(`comments: the agent hook failed to start: ${error.message}`);
|
|
475
476
|
});
|
|
476
477
|
child.on("exit", (code) => {
|
|
477
|
-
hookRunning.delete(thread.id);
|
|
478
478
|
if (code !== 0) {
|
|
479
|
+
hookRunning.delete(thread.id);
|
|
479
480
|
log(`comments: the agent hook exited with ${code} for ${thread.id}; the comment stays waiting${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
480
481
|
return;
|
|
481
482
|
}
|
|
482
483
|
void updateComments(root, (d) => {
|
|
483
484
|
const t = d.threads.find((x) => x.id === thread.id);
|
|
484
485
|
if (t) markDelivered(t, now(), "hook");
|
|
485
|
-
}).then(() => {
|
|
486
|
+
}).finally(() => hookRunning.delete(thread.id)).then(() => {
|
|
486
487
|
log(`comments: sent to the agent (hook): ${thread.id}`);
|
|
487
488
|
return broadcast();
|
|
488
489
|
}).catch((error) => log(`comments: ${error.message}`));
|
|
@@ -523,6 +524,44 @@ data: ${JSON.stringify(snap)}
|
|
|
523
524
|
});
|
|
524
525
|
}
|
|
525
526
|
}
|
|
527
|
+
async function runHandler(handler) {
|
|
528
|
+
for (const { id } of pendingForAgent(await readComments(root))) {
|
|
529
|
+
if (hookRunning.has(id)) continue;
|
|
530
|
+
hookRunning.add(id);
|
|
531
|
+
try {
|
|
532
|
+
const doc = await readComments(root);
|
|
533
|
+
const thread = pendingForAgent(doc).find((t) => t.id === id);
|
|
534
|
+
if (!thread) continue;
|
|
535
|
+
try {
|
|
536
|
+
await handler.thread(agentViews(doc, [thread])[0]);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
log(`comments: the agent handler failed for ${id}; the comment stays waiting: ${error.message}`);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
await updateComments(root, (d) => {
|
|
542
|
+
const t = d.threads.find((x) => x.id === id);
|
|
543
|
+
if (t) markDelivered(t, now(), "hook");
|
|
544
|
+
});
|
|
545
|
+
log(`comments: sent to the agent (handler): ${id}`);
|
|
546
|
+
await broadcast();
|
|
547
|
+
} finally {
|
|
548
|
+
hookRunning.delete(id);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const failed = [];
|
|
552
|
+
for (const request of requests.splice(0)) {
|
|
553
|
+
setRequestState(request.id, "working");
|
|
554
|
+
try {
|
|
555
|
+
await handler.request(request);
|
|
556
|
+
log(`comments: sent to the agent (handler): ${request.id}`);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
failed.push(request);
|
|
559
|
+
setRequestState(request.id, "queued");
|
|
560
|
+
log(`comments: the agent handler failed for ${request.id}; the request stays waiting: ${error.message}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
requests.unshift(...failed);
|
|
564
|
+
}
|
|
526
565
|
let debounce;
|
|
527
566
|
const changed = () => {
|
|
528
567
|
clearTimeout(debounce);
|
|
@@ -3234,8 +3273,11 @@ var ShareHost = class {
|
|
|
3234
3273
|
* their own artifact; a guest comes over a LAN address (this computer's own included: that is how the owner sees
|
|
3235
3274
|
* what a role sees).
|
|
3236
3275
|
*/
|
|
3276
|
+
isLocal(request) {
|
|
3277
|
+
return this.options.local ? this.options.local(request) : isLoopback(request.socket.remoteAddress);
|
|
3278
|
+
}
|
|
3237
3279
|
viewerOf(request) {
|
|
3238
|
-
if (
|
|
3280
|
+
if (this.isLocal(request)) return { role: "owner", user: this.options.owner };
|
|
3239
3281
|
const guest = this.on ? this.sharing.guest(this.cookie(request)) : null;
|
|
3240
3282
|
if (!guest) return null;
|
|
3241
3283
|
this.sharing.seen(guest);
|
|
@@ -3367,7 +3409,7 @@ var ShareHost = class {
|
|
|
3367
3409
|
async join(request, response, method, token2) {
|
|
3368
3410
|
const t = this.words(request);
|
|
3369
3411
|
const role = this.sharing.linkRole(token2);
|
|
3370
|
-
const local =
|
|
3412
|
+
const local = this.isLocal(request);
|
|
3371
3413
|
const form = method === "POST" ? await readBody(request, 8 << 10).then((body) => body.toString("utf8"), () => "") : "";
|
|
3372
3414
|
const title = this.options.title();
|
|
3373
3415
|
if (!role || !local && !this.on) return this.sendPage(response, 404, t.linkGoneTitle, `<h1>${esc2(t.linkGoneTitle)}</h1><p>${esc2(t.linkGone)}</p>`);
|
|
@@ -4514,7 +4556,7 @@ function watchArtifact(store, onChange, onError) {
|
|
|
4514
4556
|
};
|
|
4515
4557
|
}
|
|
4516
4558
|
var publishError = (response, status, code, message) => sendJson2(response, status, { code, message });
|
|
4517
|
-
async function mountSite(root, { log: stamp2, network, agentHook }) {
|
|
4559
|
+
async function mountSite(root, { log: stamp2, network, agentHook, local }) {
|
|
4518
4560
|
const project = path12.join(root, "project");
|
|
4519
4561
|
const editorPath = pagePathOf(KIND, root);
|
|
4520
4562
|
const projectDir = await fsp4.realpath(project);
|
|
@@ -4531,6 +4573,7 @@ async function mountSite(root, { log: stamp2, network, agentHook }) {
|
|
|
4531
4573
|
title: () => artifactTitle(root, projectDir),
|
|
4532
4574
|
log: stamp2,
|
|
4533
4575
|
uiFile: path12.join(EDITOR_DIR, "share-ui.js"),
|
|
4576
|
+
...local ? { local } : {},
|
|
4534
4577
|
// The History panel's address and what this person may do there; on a version's page (?at=n), which version.
|
|
4535
4578
|
globals: (viewer, query) => {
|
|
4536
4579
|
const at = Number(query.get("at") ?? "");
|