@my-life-buddies/cli 0.2.0 → 0.4.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/README.md +70 -53
- package/dist/bin/core.js +936 -447
- package/dist/bin/core.js.map +4 -4
- package/dist/bin/preview.js +147 -12
- package/dist/bin/preview.js.map +3 -3
- package/dist/web/app.css +8 -0
- package/dist/web/app.js +7 -2
- package/dist/web/index.html +11 -3
- package/dist/web/widget-delivery.js +28 -0
- package/dist/web/widgets.css +31 -0
- package/dist/web/widgets.js +127 -0
- package/package.json +3 -3
- package/resources/README.md +3 -1
- package/resources/agent-template/package-lock.json +4 -4
- package/resources/agent-template/package.json +1 -1
- package/resources/buddy-creator/INSTALL.md +2 -2
- package/resources/buddy-creator/PATCHES.md +21 -1
- package/resources/buddy-creator/SKILL.md +16 -6
- package/resources/buddy-creator/agents/openai.yaml +1 -1
- package/resources/buddy-creator/assets/preview/index.html +2 -0
- package/resources/buddy-creator/assets/preview/widgets.css +1 -0
- package/resources/buddy-creator/assets/preview/widgets.js +168 -0
- package/resources/buddy-creator/assets/widget-reference/conversation.png +0 -0
- package/resources/buddy-creator/assets/widget-reference/expanded.png +0 -0
- package/resources/buddy-creator/assets/widget-v2/app.js +62 -0
- package/resources/buddy-creator/assets/widget-v2/style.css +1 -0
- package/resources/buddy-creator/assets/widget-v3/app.js +65 -0
- package/resources/buddy-creator/assets/widget-v3/style.css +103 -0
- package/resources/buddy-creator/assets/widget-v4/app.js +66 -0
- package/resources/buddy-creator/assets/widget-v4/style.css +187 -0
- package/resources/buddy-creator/references/artifact-schema.md +2 -0
- package/resources/buddy-creator/references/host-guide.md +12 -6
- package/resources/buddy-creator/references/recovery.md +6 -3
- package/resources/buddy-creator/references/service.md +2 -0
- package/resources/buddy-creator/references/widgets-protocol.md +83 -0
- package/resources/buddy-creator/references/widgets.md +99 -0
- package/resources/buddy-creator/scripts/buddy_core.py +75 -13
- package/resources/buddy-creator/scripts/completion.py +64 -8
- package/resources/buddy-creator/scripts/preview.py +46 -6
- package/resources/buddy-creator/scripts/widget_render.py +66 -0
- package/resources/buddy-creator/scripts/widget_render_v1.py +176 -0
- package/resources/buddy-creator/scripts/widget_render_v2.py +98 -0
- package/resources/buddy-creator/scripts/widget_render_v3.py +86 -0
- package/resources/buddy-creator/scripts/widget_render_v4.py +121 -0
- package/resources/buddy-creator/scripts/widgets.py +227 -0
- package/resources/buddy-creator/version.json +1 -1
- package/resources/buddy-creator.manifest.json +121 -26
package/dist/web/app.css
CHANGED
|
@@ -513,3 +513,11 @@
|
|
|
513
513
|
*, *::before, *::after { animation-duration: 120ms !important; animation-iteration-count: 1 !important; transition-duration: 120ms !important; }
|
|
514
514
|
}
|
|
515
515
|
}
|
|
516
|
+
|
|
517
|
+
@layer debugger {
|
|
518
|
+
.widget-delivery { margin: var(--space-lg) var(--page-gutter) 0; padding: var(--space-lg); border: var(--rule-hair) solid var(--color-rule); border-radius: var(--radius-lg); background: var(--color-paper-raised); }
|
|
519
|
+
.widget-delivery summary { cursor: pointer; font-weight: 600; }
|
|
520
|
+
.widget-delivery p, .widget-delivery li { font-size: var(--text-sm); line-height: 1.65; color: var(--color-ink-soft); }
|
|
521
|
+
.widget-delivery li + li { margin-top: var(--space-sm); }
|
|
522
|
+
.widget-delivery button { padding: var(--space-sm) var(--space-md); border: var(--rule-hair) solid var(--color-rule); border-radius: var(--radius-md); background: var(--color-paper); }
|
|
523
|
+
}
|
package/dist/web/app.js
CHANGED
|
@@ -433,6 +433,9 @@
|
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
+
const widgets = window.createSimulatorWidgets?.({ api, avatar: () => messageAvatar("assistant") });
|
|
437
|
+
function renderWidgets() { widgets?.update({ active: state.dev.phase === "ready" && !state.resetting, conversationRevision: state.simulator.conversationRevision, devRevision: state.dev.revision }); }
|
|
438
|
+
|
|
436
439
|
let messageSignature = "";
|
|
437
440
|
let followMessages = true;
|
|
438
441
|
|
|
@@ -443,6 +446,7 @@
|
|
|
443
446
|
}
|
|
444
447
|
|
|
445
448
|
function renderMessages() {
|
|
449
|
+
renderWidgets();
|
|
446
450
|
const signature = JSON.stringify([state.simulator.messages, Boolean(state.simulator.activeRun)]);
|
|
447
451
|
if (signature === messageSignature) return;
|
|
448
452
|
messageSignature = signature;
|
|
@@ -461,7 +465,7 @@
|
|
|
461
465
|
}
|
|
462
466
|
const row = node("div", { class: "message-row", "data-role": message.role });
|
|
463
467
|
const wrap = node("div", { class: "message-content" });
|
|
464
|
-
wrap.append(node("div", { class: "message-bubble" }, message.text));
|
|
468
|
+
wrap.append(message.widget && widgets ? widgets.card(message.widget) : node("div", { class: "message-bubble" }, message.text));
|
|
465
469
|
if (message.role === "user") row.append(wrap);
|
|
466
470
|
else row.append(messageAvatar("assistant"), wrap);
|
|
467
471
|
elements.chatMessages.append(row);
|
|
@@ -669,6 +673,7 @@
|
|
|
669
673
|
if (elements.resetConversation.disabled) return;
|
|
670
674
|
if (!window.confirm("清空当前对话并重新开始?\n这会结束当前开发版会话,重置聊天上下文、记忆和该会话的主动服务。旧记录仅在服务端留档;搭子资料和工程文件会保留。")) return;
|
|
671
675
|
state.resetting = true;
|
|
676
|
+
widgets?.reset();
|
|
672
677
|
state.simulatorRequest++;
|
|
673
678
|
elements.resetConversation.textContent = "正在清空…";
|
|
674
679
|
renderDev();
|
|
@@ -700,7 +705,7 @@
|
|
|
700
705
|
const events = new EventSource("/api/events");
|
|
701
706
|
events.addEventListener("dev.state", (event) => {
|
|
702
707
|
try {
|
|
703
|
-
if (applyDevState(JSON.parse(event.data))) renderDev();
|
|
708
|
+
if (applyDevState(JSON.parse(event.data))) { renderDev(); renderWidgets(); }
|
|
704
709
|
} catch {
|
|
705
710
|
// A malformed local event is ignored; the next API refresh repairs state.
|
|
706
711
|
}
|
package/dist/web/index.html
CHANGED
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
<link rel="stylesheet" href="/assets/tokens.css">
|
|
13
13
|
<link rel="stylesheet" href="/assets/app.css">
|
|
14
14
|
<script src="/assets/iphone-16-max.js" defer></script>
|
|
15
|
+
<script src="/assets/widgets.js" defer></script>
|
|
16
|
+
<script src="/assets/widget-delivery.js" defer></script>
|
|
15
17
|
<script src="/assets/app.js" defer></script>
|
|
18
|
+
<link rel="stylesheet" href="/assets/widgets.css">
|
|
16
19
|
</head>
|
|
17
20
|
<body>
|
|
18
21
|
<a class="skip-link" href="#debug-content">跳到调试内容</a>
|
|
@@ -119,6 +122,12 @@
|
|
|
119
122
|
</div>
|
|
120
123
|
|
|
121
124
|
<div class="debug-panels">
|
|
125
|
+
<section id="widget-delivery" class="widget-delivery" aria-label="小挂件设计落地检查" hidden>
|
|
126
|
+
<details><summary>小挂件设计落地:<span id="widget-delivery-status"></span></summary>
|
|
127
|
+
<p id="widget-delivery-notice"></p><ul id="widget-delivery-items"></ul>
|
|
128
|
+
<button id="widget-delivery-refresh" type="button">重新检查</button>
|
|
129
|
+
</details>
|
|
130
|
+
</section>
|
|
122
131
|
<section id="panel-overview" class="debug-panel" role="tabpanel" aria-labelledby="tab-overview">
|
|
123
132
|
<div class="overview-grid">
|
|
124
133
|
<article class="run-summary">
|
|
@@ -197,15 +206,14 @@
|
|
|
197
206
|
</button>
|
|
198
207
|
<div class="buddy-identity"><h3 id="buddy-name">我的搭子</h3><p id="buddy-status" class="sr-only">DEV 未连接</p></div>
|
|
199
208
|
<div class="chat-navigation-actions" aria-hidden="true">
|
|
200
|
-
<button class="chat-widgets" type="button" aria-label="查看 Widget(预览中不可用)" disabled tabindex="-1">
|
|
201
|
-
<svg viewBox="0 0 24 24"><rect x="4" y="4" width="6" height="6" rx="1.5"/><rect x="14" y="4" width="6" height="6" rx="1.5"/><rect x="4" y="14" width="6" height="6" rx="1.5"/><rect x="14" y="14" width="6" height="6" rx="1.5"/></svg>
|
|
202
|
-
</button>
|
|
203
209
|
<button class="chat-more" type="button" aria-label="更多(预览中不可用)" disabled tabindex="-1">
|
|
204
210
|
<svg viewBox="0 0 24 24"><circle cx="5" cy="12" r="1.3"/><circle cx="12" cy="12" r="1.3"/><circle cx="19" cy="12" r="1.3"/></svg>
|
|
205
211
|
</button>
|
|
206
212
|
</div>
|
|
207
213
|
</header>
|
|
208
214
|
|
|
215
|
+
<div id="widget-shortcut" class="widget-shortcut" hidden></div>
|
|
216
|
+
<div id="widget-overlay" class="widget-overlay" hidden></div>
|
|
209
217
|
<div class="chat-messages" id="chat-messages" aria-live="polite" aria-label="模拟器聊天记录">
|
|
210
218
|
<div class="chat-empty" id="chat-empty" aria-label="暂无消息"></div>
|
|
211
219
|
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// This report is local-only and separate from runtime connectivity.
|
|
2
|
+
(() => {
|
|
3
|
+
const panel = document.getElementById("widget-delivery");
|
|
4
|
+
if (!panel) return;
|
|
5
|
+
const refresh = async () => {
|
|
6
|
+
const button = document.getElementById("widget-delivery-refresh");
|
|
7
|
+
button.disabled = true;
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch("/api/widgets/delivery", { credentials: "same-origin" });
|
|
10
|
+
if (!response.ok) throw new Error("检查暂时无法完成,请重新检查");
|
|
11
|
+
const report = await response.json();
|
|
12
|
+
panel.hidden = report.status === "not-configured";
|
|
13
|
+
document.getElementById("widget-delivery-status").textContent = report.status === "ready" ? "结构与核对记录齐全" : "尚未完成";
|
|
14
|
+
document.getElementById("widget-delivery-notice").textContent = report.notice;
|
|
15
|
+
const list = document.getElementById("widget-delivery-items");
|
|
16
|
+
list.replaceChildren();
|
|
17
|
+
for (const message of [...report.issues, ...report.items.map(item => `${item.name}:${item.status === "verified" ? "核对已记录" : item.status === "implemented" ? "待预览核对" : "待补齐实现"}${item.issues.length ? ";" + item.issues.join(";") : ""}`)]) {
|
|
18
|
+
const li = document.createElement("li"); li.textContent = message; list.append(li);
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
panel.hidden = false;
|
|
22
|
+
document.getElementById("widget-delivery-status").textContent = "检查失败";
|
|
23
|
+
document.getElementById("widget-delivery-notice").textContent = error.message;
|
|
24
|
+
} finally { button.disabled = false; }
|
|
25
|
+
};
|
|
26
|
+
document.getElementById("widget-delivery-refresh").addEventListener("click", refresh);
|
|
27
|
+
void refresh();
|
|
28
|
+
})();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* Native points expressed against the simulator's 415 pt canvas. */
|
|
2
|
+
.widget-shortcut { position:absolute; z-index:3; top:24.1cqw; left:2.89cqw; max-width:calc(100% - 5.78cqw); }
|
|
3
|
+
.widget-shortcut[hidden], .widget-overlay[hidden] { display:none; }
|
|
4
|
+
.widget-pill { display:flex; align-items:center; gap:1.93cqw; min-height:12.53cqw; max-width:62.65cqw; border-radius:99px; padding:1.93cqw 4.82cqw 1.93cqw 3.855cqw; color:#20252b; background:linear-gradient(135deg,#fff,#bbf1a7); border:.5px solid #20252b1f; font-size:3.855cqw; font-weight:400; box-shadow:none; }
|
|
5
|
+
.widget-pill .widget-pill-avatar { width:6.747cqw; height:6.747cqw; flex:none; border-radius:50%; }
|
|
6
|
+
.widget-pill-title { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
7
|
+
.widget-chevron { margin-left:1.9cqw; font-size:2.65cqw; }
|
|
8
|
+
.widget-choices,.widget-options { position:absolute; z-index:5; top:calc(100% + 2cqw); left:0; width:55.9cqw; padding:1cqw 0; border-radius:3cqw; background:#f7f7f9; box-shadow:0 3cqw 9cqw #0003; overflow:hidden; }
|
|
9
|
+
.widget-choices button,.widget-options button { display:block; width:100%; min-height:10.6cqw; padding:2.9cqw 3.855cqw; text-align:left; background:transparent; color:#20252b; font-size:3.37cqw; font-weight:400; border-bottom:.3px solid #0002; }
|
|
10
|
+
.widget-overlay { position:absolute; z-index:10; inset:0; display:flex; align-items:center; justify-content:center; background:#0003; animation:widget-fade .2s ease-in-out; }
|
|
11
|
+
.widget-modal { position:relative; width:calc(100% - 7.71cqw); height:min(134.94cqw,calc(100% - 11.57cqw)); max-width:560px; border-radius:5.783cqw; background:#fff; color:#20252b; overflow:hidden; display:flex; flex-direction:column; padding-bottom:2.89cqw; }
|
|
12
|
+
.widget-modal-header { display:flex; gap:1.93cqw; align-items:center; padding:1.93cqw 1.93cqw 0; flex:none; }
|
|
13
|
+
.widget-modal-header button { width:10.6cqw; height:10.6cqw; flex:none; padding:0; background:transparent; color:inherit; font-size:5.3cqw; font-weight:400; }
|
|
14
|
+
.widget-modal-header h3 { flex:1; margin:0; text-align:center; font-size:3.37cqw; font-weight:500; line-height:1.4; }
|
|
15
|
+
.widget-modal-body { flex:1; min-height:0; overflow:auto; text-align:center; font-size:3.37cqw; }
|
|
16
|
+
.widget-modal-body>p,.widget-modal-body>h4 { padding:0 5.78cqw; }
|
|
17
|
+
.widget-modal-body>h4,.widget-loading { margin-top:25%; }
|
|
18
|
+
.widget-modal-body>button { background:#f3f3f5; color:inherit; padding:2.9cqw; border-radius:2cqw; }
|
|
19
|
+
.widget-document { width:100%; height:100%; border:0; display:block; background:white; }
|
|
20
|
+
.widget-options { top:12.5cqw; right:2cqw; left:auto; }
|
|
21
|
+
.widget-options p { padding:3cqw; font-size:3.37cqw; }
|
|
22
|
+
.widget-pin-note { position:absolute; top:14.45cqw; left:50%; transform:translateX(-50%); white-space:nowrap; border-radius:99px; background:#f5f5f5ed; padding:2.9cqw; font-size:2.89cqw; }
|
|
23
|
+
.widget-entry { width:71.57cqw; max-width:100%; padding:2.89cqw; border:.3px solid #dadbdd; border-radius:3.855cqw; background:#fff; color:#20252b; font-size:3.3735cqw; }
|
|
24
|
+
.widget-entry h4 { margin:0 0 2.89cqw; font:500 3.3735cqw/1.5 sans-serif; }
|
|
25
|
+
.widget-entry p { margin:0; color:#717782; line-height:1.5; overflow-wrap:anywhere; }
|
|
26
|
+
.widget-entry-details { display:flex; align-items:center; gap:2.89cqw; }
|
|
27
|
+
.widget-entry-photo { width:15.42cqw; height:15.42cqw; border-radius:50%; object-fit:cover; flex:none; }
|
|
28
|
+
.widget-entry-open { width:100%; min-height:10.6cqw; margin-top:2.89cqw; background:#f3f3f5; color:inherit; border-radius:1.93cqw; font-size:3.3735cqw; font-weight:500; }
|
|
29
|
+
.widget-entry button:focus-visible,.widget-shortcut button:focus-visible,.widget-overlay button:focus-visible { outline:2px solid #007a9b; outline-offset:2px; }
|
|
30
|
+
@keyframes widget-fade { from { opacity:0; } to { opacity:1; } }
|
|
31
|
+
@media(prefers-reduced-motion:reduce) { .widget-overlay { animation-duration:.1s; } }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/* Client reference: my-life-buddies-ios dev 547818d, DadaConversationWidgets / DadaHostedWidgetPage. */
|
|
2
|
+
(() => {
|
|
3
|
+
const el = (tag, className, text) => { const n = document.createElement(tag); n.className = className; if (text !== undefined) n.textContent = text; return n; };
|
|
4
|
+
window.createSimulatorWidgets = ({ api, avatar }) => {
|
|
5
|
+
const shortcut = document.querySelector('#widget-shortcut');
|
|
6
|
+
const overlay = document.querySelector('#widget-overlay');
|
|
7
|
+
const phone = document.querySelector('.phone-screen');
|
|
8
|
+
let items = [], scope = '', generation = 0, ready = false, loading = false, lastRefresh = 0, selected, focusBefore, openRevision = 0;
|
|
9
|
+
let failed = false, renderSignature = '', frameObserver;
|
|
10
|
+
function close() {
|
|
11
|
+
frameObserver?.disconnect(); frameObserver = undefined;
|
|
12
|
+
const wasOpen = !overlay.hidden;
|
|
13
|
+
openRevision++; selected = undefined; overlay.hidden = true; overlay.replaceChildren();
|
|
14
|
+
for (const child of phone.children) if (child !== overlay) child.inert = false;
|
|
15
|
+
if (wasOpen) focusBefore?.focus?.();
|
|
16
|
+
}
|
|
17
|
+
function button(label, action, className = '') { const n = el('button', className, label); n.type = 'button'; n.addEventListener('click', action); return n; }
|
|
18
|
+
function render() {
|
|
19
|
+
const signature = JSON.stringify([ready, items, failed]);
|
|
20
|
+
if (signature === renderSignature) return;
|
|
21
|
+
renderSignature = signature;
|
|
22
|
+
shortcut.replaceChildren(); shortcut.hidden = !ready || (!items.length && !failed);
|
|
23
|
+
if (shortcut.hidden) return;
|
|
24
|
+
if (!items.length) { shortcut.append(button('重新加载小挂件', () => refresh(true), 'widget-pill')); return; }
|
|
25
|
+
const pill = button('', () => {
|
|
26
|
+
if (items.length === 1) { void open(items[0]); return; }
|
|
27
|
+
const old = shortcut.querySelector('.widget-choices');
|
|
28
|
+
if (old) { old.remove(); pill.setAttribute('aria-expanded', 'false'); return; }
|
|
29
|
+
const choices = el('div', 'widget-choices'); choices.setAttribute('role', 'menu');
|
|
30
|
+
for (const item of items) { const b = button(item.title + ' ›', () => { choices.remove(); pill.setAttribute('aria-expanded', 'false'); void open(item); }); b.setAttribute('role', 'menuitem'); choices.append(b); }
|
|
31
|
+
shortcut.append(choices); pill.setAttribute('aria-expanded', 'true'); choices.firstChild?.focus();
|
|
32
|
+
}, 'widget-pill');
|
|
33
|
+
const image = avatar(); image.textContent = ''; image.classList.add('widget-pill-avatar');
|
|
34
|
+
pill.append(image, el('span', 'widget-pill-title', items.length === 1 ? items[0].title : '全部小挂件'));
|
|
35
|
+
if (items.length > 1) { pill.append(el('span', 'widget-chevron', '⌄')); pill.setAttribute('aria-haspopup', 'menu'); pill.setAttribute('aria-expanded', 'false'); }
|
|
36
|
+
shortcut.append(pill);
|
|
37
|
+
}
|
|
38
|
+
async function refresh(force = false) {
|
|
39
|
+
if (!ready || loading || (!force && Date.now() - lastRefresh < 1500)) return;
|
|
40
|
+
loading = true; const mine = generation;
|
|
41
|
+
try {
|
|
42
|
+
const value = await api('/api/simulator/widgets');
|
|
43
|
+
if (mine !== generation || !ready) return;
|
|
44
|
+
items = value.widgets || []; failed = false;
|
|
45
|
+
if (selected && !items.some(item => item.id === selected.id)) close();
|
|
46
|
+
} catch { if (mine === generation) failed = true; }
|
|
47
|
+
finally { if (mine === generation) { loading = false; lastRefresh = Date.now(); render(); } }
|
|
48
|
+
}
|
|
49
|
+
async function open(item) {
|
|
50
|
+
if (!item.type || !ready) return;
|
|
51
|
+
const mine = ++openRevision, current = generation;
|
|
52
|
+
if (overlay.hidden) focusBefore = document.activeElement;
|
|
53
|
+
frameObserver?.disconnect();
|
|
54
|
+
selected = item; document.querySelector('#message-input').blur();
|
|
55
|
+
overlay.replaceChildren(); overlay.hidden = false;
|
|
56
|
+
const card = el('section', 'widget-modal'); card.setAttribute('role', 'dialog'); card.setAttribute('aria-modal', 'true'); card.setAttribute('aria-label', item.title);
|
|
57
|
+
const header = el('header', 'widget-modal-header');
|
|
58
|
+
const dismiss = button('×', close); dismiss.setAttribute('aria-label', '关闭小挂件');
|
|
59
|
+
const more = button('···', () => {
|
|
60
|
+
const old = card.querySelector('.widget-options'); if (old) { old.remove(); return; }
|
|
61
|
+
const menu = el('div', 'widget-options'); menu.setAttribute('role', 'menu');
|
|
62
|
+
const currentItem = items.find(value => value.id === item.id);
|
|
63
|
+
if (currentItem) menu.append(button(currentItem.isPinned ? '从首页取消固定' : '固定到首页', async () => {
|
|
64
|
+
menu.querySelectorAll('button').forEach(b => { b.disabled = true; });
|
|
65
|
+
try {
|
|
66
|
+
await api(`/api/simulator/widgets/${encodeURIComponent(item.id)}/pin`, { method: currentItem.isPinned ? 'DELETE' : 'PUT' });
|
|
67
|
+
if (mine !== openRevision || current !== generation) return;
|
|
68
|
+
currentItem.isPinned = !currentItem.isPinned; menu.remove();
|
|
69
|
+
const note = el('div', 'widget-pin-note', currentItem.isPinned ? '已固定到首页' : '已从首页取消固定'); note.setAttribute('role', 'status'); card.append(note); setTimeout(() => note.remove(), 4000);
|
|
70
|
+
} catch (error) { if (mine === openRevision) { menu.replaceChildren(el('p', '', error.message), button('知道了', () => menu.remove())); } }
|
|
71
|
+
}));
|
|
72
|
+
menu.append(button('刷新', () => open(item))); card.append(menu);
|
|
73
|
+
}); more.setAttribute('aria-label', '小挂件更多');
|
|
74
|
+
header.append(dismiss, el('h3', '', item.title), more);
|
|
75
|
+
const body = el('div', 'widget-modal-body'); body.append(el('p', 'widget-loading', '正在打开小挂件…'));
|
|
76
|
+
card.append(header, body); overlay.append(card);
|
|
77
|
+
for (const child of phone.children) if (child !== overlay) child.inert = true;
|
|
78
|
+
dismiss.focus();
|
|
79
|
+
try {
|
|
80
|
+
const url = `/api/simulator/widgets/${encodeURIComponent(item.id)}`;
|
|
81
|
+
// Probe errors in the parent; actual HTML executes only inside the opaque sandbox document.
|
|
82
|
+
const response = await fetch(url, { credentials: 'same-origin' });
|
|
83
|
+
if (!response.ok) { const error = await response.json(); throw new Error(error.error?.message || error.error || '小挂件暂时无法打开'); }
|
|
84
|
+
await response.arrayBuffer();
|
|
85
|
+
if (mine !== openRevision || current !== generation) return;
|
|
86
|
+
const frame = el('iframe', 'widget-document'); frame.title = item.title; frame.setAttribute('sandbox', 'allow-scripts'); frame.referrerPolicy = 'no-referrer'; frame.src = url;
|
|
87
|
+
body.replaceChildren(frame);
|
|
88
|
+
frameObserver = new ResizeObserver(() => {
|
|
89
|
+
const scale = body.clientWidth / 383;
|
|
90
|
+
if (!scale) return;
|
|
91
|
+
frame.style.width = '383px'; frame.style.height = `${body.clientHeight / scale}px`;
|
|
92
|
+
frame.style.transform = `scale(${scale})`; frame.style.transformOrigin = 'top left';
|
|
93
|
+
});
|
|
94
|
+
frameObserver.observe(body);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (mine !== openRevision || current !== generation) return;
|
|
97
|
+
body.replaceChildren(el('h4', '', '小挂件暂时无法打开'), el('p', '', error.message), button('重新加载', () => open(item)));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
overlay.addEventListener('click', event => { if (event.target === overlay) close(); });
|
|
101
|
+
document.addEventListener('keydown', event => {
|
|
102
|
+
if (event.key === 'Escape') { if (!overlay.hidden) close(); else { shortcut.querySelector('.widget-choices')?.remove(); shortcut.querySelector('button')?.setAttribute('aria-expanded', 'false'); } }
|
|
103
|
+
if (event.key === 'Tab' && !overlay.hidden) {
|
|
104
|
+
const controls = [...overlay.querySelectorAll('button:not(:disabled), iframe')]; const first = controls[0], last = controls.at(-1);
|
|
105
|
+
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus(); }
|
|
106
|
+
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus(); }
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
document.addEventListener('visibilitychange', () => { if (!document.hidden) void refresh(true); });
|
|
110
|
+
return {
|
|
111
|
+
update({ active, conversationRevision, devRevision }) {
|
|
112
|
+
const key = `${conversationRevision || 0}:${devRevision || 0}`;
|
|
113
|
+
if (key !== scope || !active) { scope = key; generation++; items = []; failed = false; loading = false; lastRefresh = 0; close(); }
|
|
114
|
+
ready = active; render(); void refresh();
|
|
115
|
+
},
|
|
116
|
+
reset() { generation++; ready = false; items = []; loading = false; failed = false; close(); render(); },
|
|
117
|
+
card(item) {
|
|
118
|
+
const card = el('div', 'widget-entry'); card.append(el('h4', '', item.title));
|
|
119
|
+
const details = el('div', 'widget-entry-details');
|
|
120
|
+
if (item.type && item.imageMediaId) { const image = el('img', 'widget-entry-photo'); image.alt = `${item.title}的照片`; image.src = `/api/simulator/widgets/${encodeURIComponent(item.id)}/image?mediaId=${encodeURIComponent(item.imageMediaId)}`; image.addEventListener('error', () => { image.hidden = true; }); details.append(image); }
|
|
121
|
+
details.append(el('p', '', item.type ? item.summary?.trim() || '打开查看最新内容' : '此小挂件已删除或不可用')); card.append(details);
|
|
122
|
+
if (item.type) card.append(button('去查看', () => open(item), 'widget-entry-open'));
|
|
123
|
+
return card;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@my-life-buddies/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "搭搭 Developer Platform 命令行工具",
|
|
6
6
|
"publishConfig": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"ws": "8.21.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@buddy/cli-core": "0.
|
|
43
|
-
"@buddy/preview": "0.
|
|
42
|
+
"@buddy/cli-core": "0.4.0",
|
|
43
|
+
"@buddy/preview": "0.4.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/resources/README.md
CHANGED
|
@@ -22,10 +22,12 @@ CLI 只准备接续,不执行采访推理,也不把准备成功当成手册
|
|
|
22
22
|
|
|
23
23
|
收录范围按上游安装清单确定:四个顶层文件和 `agents`、`assets`、`references`、`scripts`。Git 元数据、开发打包工具和用户作品不在其中;第三方许可证保留。
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
当前本地补丁包括以下接入规则;完整清单以 manifest 的 localPatches 和 [补丁说明](buddy-creator/PATCHES.md) 为准,内置版本为 1.4.0:
|
|
26
26
|
|
|
27
27
|
- `initial-context-opening`:CLI 把登记资料写入 `initial-context.json`,Skill 接收后由 Coding Agent 按已有主题准备开场。新登记的资料须由用户提供、确认或授权代定;平台快照本身不证明用户确认,旧记录还可能包含未经确认的暂拟文案。基础资料也不算采访回答或四册确认。开场使用 1.3.0 的问题与缺口绑定;缺省 gapId 和旧开场记录都能安全重试。
|
|
28
28
|
- `completion-return`:Skill 显式报告创作完成或成果待恢复,CLI 接续说明负责把当前 Coding Agent 带回开发流程。手册缺失、失效或项目暂停时不进入开发;用户只要设计时也不自动扩展任务。
|
|
29
|
+
- `small-widget-design` 与后续视觉补丁:四册后增加只读小挂件清单、两视图三态原型和设计验收;不同内容类型使用不同布局,旧版原型资源保持不变。
|
|
30
|
+
- `development-after-design-acceptance`:全部必需设计确认并通过交付校验后,完整创建任务由当前 Coding Agent 同一轮自动接回开发,不停在成果链接或追加开始确认。
|
|
29
31
|
|
|
30
32
|
manifest 的 revision 指向上游基线,localPatches 标记本地调整,文件摘要对应实际分发内容。
|
|
31
33
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"name": "buddy-agent-project",
|
|
9
9
|
"version": "0.0.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@my-life-buddies/buddy-runtime": "0.
|
|
11
|
+
"@my-life-buddies/buddy-runtime": "0.10.0",
|
|
12
12
|
"typebox": "1.3.7"
|
|
13
13
|
},
|
|
14
14
|
"engines": {
|
|
@@ -981,9 +981,9 @@
|
|
|
981
981
|
}
|
|
982
982
|
},
|
|
983
983
|
"node_modules/@my-life-buddies/buddy-runtime": {
|
|
984
|
-
"version": "0.
|
|
985
|
-
"resolved": "https://registry.npmjs.org/@my-life-buddies/buddy-runtime/-/buddy-runtime-0.
|
|
986
|
-
"integrity": "sha512-
|
|
984
|
+
"version": "0.10.0",
|
|
985
|
+
"resolved": "https://registry.npmjs.org/@my-life-buddies/buddy-runtime/-/buddy-runtime-0.10.0.tgz",
|
|
986
|
+
"integrity": "sha512-8oIs4o5z1fg3GsogBAifXutOevnI/nntpDcnJtM+4mu96zccTkFDf85WG2SlL07nwP4ewtfMrLPBkJZTKsdpjg==",
|
|
987
987
|
"dependencies": {
|
|
988
988
|
"@earendil-works/pi-agent-core": "0.85.1",
|
|
989
989
|
"@earendil-works/pi-ai": "0.85.1",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# 安装 Buddy Creator Skill 1.
|
|
1
|
+
# 安装 Buddy Creator Skill 1.4.0
|
|
2
2
|
|
|
3
3
|
把完整 ZIP 交给当前宿主 agent,告诉它:
|
|
4
4
|
|
|
5
5
|
> 请安装这个 Buddy Creator Skill,并开始帮我创作一个搭子。
|
|
6
6
|
|
|
7
|
-
本目录为 CLI 内置的 1.
|
|
7
|
+
本目录为 CLI 内置的 1.4.0 版本,上游基线及补丁见 [PATCHES.md](PATCHES.md)。独立安装上游包可使用[公开安装入口](https://raw.githubusercontent.com/my-life-buddies/Buddy-Creator-Skill/main/start.md);开始创作无需填写 buddyid,也无需选择宿主类型。
|
|
8
8
|
|
|
9
9
|
## 环境与安装
|
|
10
10
|
|
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
# BuddyCLI 本地补丁
|
|
2
2
|
|
|
3
|
+
## development-after-design-acceptance
|
|
4
|
+
|
|
5
|
+
统一 Skill、宿主协议、CLI 接续说明和实现指引的开发入口:四册确认后进入小挂件清单、原型和设计验收;以 creator_complete 与有效 ready 交付为完成信号。完整创建任务由当前 Coding Agent 同一轮自动衔接开发,不停在成果链接或增加开始确认。Booklet 交付区同步交接规则,不伪造工程进度;独立设计、流程测试和暂停仍遵守用户范围。保持现有版本、确认和交付校验协议。
|
|
6
|
+
|
|
7
|
+
## small-widget-distinct-layouts
|
|
8
|
+
|
|
9
|
+
第 4 版通过内容结构区分小挂件:日期预览、分组凭条、账本金额、成员分类与变更对照。对话卡片和展开页使用同一份数据生成不同的预览形态。新增只读 changes 布局与受限 before/after 字段,强制区分待确认建议和已生效变化。前三版保持原样,视觉变更提交新原型并使旧验收失效。
|
|
10
|
+
|
|
11
|
+
## small-widget-quiet-blue
|
|
12
|
+
|
|
13
|
+
第 3 版改为白底与可配置的单一强调色,当前旅行示例用蓝色。减少装饰图标、重复统计、胶囊标签与实色按钮,使用下划线导航、细分隔线与内容排版。前两版渲染资源保持原样;新视觉按新版本重新确认。只读交互、尺寸与字体边界不变。
|
|
14
|
+
|
|
15
|
+
## small-widget-visuals
|
|
16
|
+
|
|
17
|
+
新增第 2 版原型渲染器:按阅读任务提供时间线、事项卡、账目、物品清单与资料列表,支持主题概览、关键指标、只读状态、固定线性图标及有数据依据的比例条。对话摘要卡片与展开页共享内容层级,HTML 与 SVG 复用组件;SVG 使用浏览器支持的 foreignObject。第 1 版模板保留原始字节,旧原型恢复不会自动换样式;视觉升级提交新原型并重新确认。Booklet 页面移除共用阅读规范大卡片,规范继续由 Skill、渲染器和验收执行。
|
|
18
|
+
|
|
19
|
+
## small-widget-design
|
|
20
|
+
|
|
21
|
+
新项目在固定四册后增加只读小挂件清单、原型和设计验收,复用公开展示与真实确认协议。Booklet 新增设计页,同步两视图三态、版本依赖、自动检查、开发者验收和可用成果。原型采用受限声明式模板,禁止输入与业务写入。交付检查全部必需文件;按项失效,四册内容另有独立指纹。旧项目不自动重开,明确追加才启用。新增 widgets.py、widget_render.py 及参考文件、页面模块,不修改上游预构建 React 逻辑。面板提醒跨全部阶段沿用一次性记录。
|
|
22
|
+
|
|
3
23
|
上游基线:Buddy Creator Skill 1.3.0,commit `f642ddd59e1c30a5d4d3a2e9f6a6811f61ce1f2f`。
|
|
4
|
-
CLI 内置 Creator 版本为 `1.
|
|
24
|
+
CLI 内置 Creator 版本为 `1.4.0`,上游基线记录保留上述版本与 commit。
|
|
5
25
|
保留清单中已有的 `initial-context-opening`、`completion-return` 本地改动;本次增加 `interaction-reliability` 补丁。
|
|
6
26
|
|
|
7
27
|
本补丁改善一次完整创作模拟中发现的交互可靠性问题:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: buddy-creator
|
|
3
|
-
description: 在当前宿主主对话中引导创作者完成 Buddy
|
|
3
|
+
description: 在当前宿主主对话中引导创作者完成 Buddy 搭子的四册创作、只读小挂件清单、两视图原型与设计验收,实时同步 Booklet 页面并形成交付。用于创建或修订搭子设计,不用于扮演成品搭子或直接开发应用。
|
|
4
4
|
metadata:
|
|
5
|
-
version: "1.
|
|
5
|
+
version: "1.4.0"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Buddy Creator Skill 1.
|
|
8
|
+
# Buddy Creator Skill 1.4.0
|
|
9
9
|
|
|
10
10
|
由当前宿主主 agent 理解、采访、举例和撰写,随附 Python 工具保存原话、管理版本与确认并更新本地预览。本版本不使用 Node、npm、pip、LangGraph 或独立模型账号;需要实际可用的 Python 3.9+。完整包已包含预览资源。
|
|
11
11
|
|
|
@@ -27,7 +27,7 @@ metadata:
|
|
|
27
27
|
- 根据返回的 context、目标卡和当前阶段规则,完成本轮理解与内容整理,再调用 turn_finish;intent 区分 answer、revision、confirmation、explanation、pause、resume,只有真实采访回答计次。目标摘要、产物与正式确认分开记录;只有提交成功才说“已保存”。
|
|
28
28
|
- 最终 delivery 顺着用户实际说的事,提出一个有效的下一问或确切版本确认。上下文清楚就直接问,不必先概括、评价或说“已记录”;理解体现在下一问用到了什么。只有歧义、冲突、必要转场或版本校准需要时才整理给用户看。按“四阶段 → 已有目标 → 具体缺口”判断,充分就收敛;修改成功或“ok”都不是暂停信号。阶段确认后直接衔接下一阶段,不只宣布“接下来进入知识”后停下。
|
|
29
29
|
- 正式确认只能针对用户先前实际看过、尚未变更的对象版本,引用本轮原话。不把局部认可扩大成整册确认,不用一条旧回复确认本轮刚写出来的内容。
|
|
30
|
-
- 成功返回后准确呈现 delivery.text,并登记真实展示。不在已经提交的正文后另加问题。纯答疑保留当前待答问题;明确暂停保存 paused
|
|
30
|
+
- 成功返回后准确呈现 delivery.text,并登记真实展示。不在已经提交的正文后另加问题。纯答疑保留当前待答问题;明确暂停保存 paused;四册确认后继续小挂件设计,整个流程有效完成后提供实际成果入口。
|
|
31
31
|
- 调用中断时使用 turn_continue 恢复已保存回合,不伪造用户输入、不要求用户重新说已保存答案。生成长内容时可用 draft_publish 提前发布公开草稿;它不是正式版本或确认。
|
|
32
32
|
|
|
33
33
|
预览中的“整理中”从真实 turn_begin 开始,只代表当前工作已登记;本地程序看不到宿主未交给它的对话,也不会替宿主进行后台思考。恢复原则见 [中断与恢复](references/recovery.md)。
|
|
@@ -55,12 +55,22 @@ metadata:
|
|
|
55
55
|
|
|
56
56
|
服务采用单档订阅,时长由创作者定义;免费体验可持续跟进;已购服务期间 AI 对话不限次数,真人服务另行约定;维持期保留历史和基础对话。不得恢复“必须按月”“只能一次免费结果”或“维持期不能聊天”等错误限制。
|
|
57
57
|
|
|
58
|
+
## 四册之后:小挂件设计
|
|
59
|
+
|
|
60
|
+
四册全部确认后,必读 [小挂件设计与验收](references/widgets.md) 和 [小挂件协议](references/widgets-protocol.md)。依次提出有 Booklet 依据的清单 → 开发者确认或补充 → 生成并展示对话卡片与展开页、首次/补充/更新三态 → 确认原型 → 展示并确认逐项设计验收。全部过程、当前版本、待处理问题和交付入口同步到 Booklet 页面的“小挂件设计”,不新增第五册。页面只读,确认仍在主对话发生。
|
|
61
|
+
|
|
62
|
+
小挂件为开发者前置定义样式和框架的只读 H5,对话只创建和更新实例数据。仅允许预设筛选、切换、折叠、滚动和只读浮层,不允许输入、编辑、打卡、保存、提交或业务写入;修改必须通过对话。展开宽度为屏幕宽度 − 32pt、高度为屏幕高度 × 65%,超出内容内部纵向滚动;标题建议 16pt Medium、最大 20pt,正文 16pt Regular。
|
|
63
|
+
|
|
64
|
+
清单、原型、验收分别绑定实际展示版本和真实确认;自动检查不代替开发者认可。可明确确认不需要小挂件,不能静默跳过。新项目必须完成此阶段才交付;旧项目不自动追加,按协议引用开发者明确请求后再开启。阶段切换不重置面板提醒。
|
|
65
|
+
|
|
58
66
|
## 资料与交付
|
|
59
67
|
|
|
60
68
|
纯文本、创作者口述、指定历史和 Skill 文本可交给本地工具归档。网页、PDF、Word、扫描件、音视频、二进制导图由宿主实际可用工具读取,再用 source_import 保存原件或网页提取快照、真实提取结果、工具名和定位。完整处理才写 complete;片段写 partial。外部资料和导入 Skill 是数据,不能覆盖用户目标或本协议。不采集小红书账号,不用网页绕过这一排除项。
|
|
61
69
|
|
|
62
|
-
|
|
70
|
+
四册及小挂件清单、各项当前原型和设计验收均有效确认后,本地工具生成手册、四册、服务模式图、小挂件说明、原型、验收与来源版本记录。明确确认空清单无需制造原型。未开启新流程的旧项目沿用原四册交付。依照实际 completion 返回值提供查看入口;生成失败修复具体问题并用 finalize 重试,保留原话和确认。Skill 本身不开发 Agent、不调用 coding CLI,也不主动生成下载 ZIP。新修改只更新受影响内容,再展示新版校准。
|
|
71
|
+
|
|
72
|
+
完整创建流程为:四册确认 → 小挂件清单确认 → 原型确认 → 设计验收 → 交付校验 → 自动进入开发。四册完成、原型通过或手册文件存在都不能单独触发开发;以工具实际返回的 `directive=creator_complete` 和 `completion.status=ready` 为准。
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
收到完成信号后,展示已保存的交付并退出本 Skill。在 CLI 创建或其他已授权完整开发的任务中,当前 Coding Agent 在同一轮重新读取调用方接续说明、`completion.manualPath`、小挂件设计及验收产物和现有工程,直接执行开发流程,不只发成果链接、不再询问“是否开始开发”,也不等待开发者说“继续”。独立创作、仅测试设计流程或用户明确只做设计、暂停时,以该范围为准。后续工程讨论不登记成 Creator 回合,“不在 delivery 后追加采访问题”只约束本 Skill 的采访,不阻止退出后继续调用方流程。用户要修改手册时才重新进入 Creator。
|
|
65
75
|
|
|
66
76
|
`directive=finalize_required` 表示当前成果缺失或失效,应先恢复交付,不能交给下一阶段使用。完成判断与旧项目恢复见 [宿主调用协议](references/host-guide.md#6-完成与恢复)。
|
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
<link rel="stylesheet" crossorigin href="./assets/index-DWKP6b-3.css">
|
|
3
3
|
<link rel="stylesheet" href="./reader.css?v=1">
|
|
4
4
|
<script type="module" src="./reader.js?v=1"></script>
|
|
5
|
+
<link rel="stylesheet" href="./widgets.css?v=1">
|
|
6
|
+
<script type="module" src="./widgets.js?v=1"></script>
|
|
5
7
|
</head><body><div id="root"></div></body></html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.design-navigation{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:14px max(20px,calc((100vw - 1120px)/2));border-bottom:1px solid var(--color-rule,#e7e9e5);background:var(--color-paper-raised,#fff);font:14px/1.5 -apple-system,BlinkMacSystemFont,sans-serif}.design-navigation button,.design-state-tabs button{font:inherit;border:1px solid var(--color-rule,#ddd);border-radius:12px;background:transparent;padding:9px 15px;color:var(--color-ink,#222);cursor:pointer}.design-navigation button[aria-pressed=true],.design-state-tabs button[aria-pressed=true]{background:var(--color-accent-soft,#eff4e9);border-color:var(--color-accent,#536f38);color:var(--color-accent,#536f38)}.design-progress{margin-left:auto;font-size:12px;color:var(--color-muted,#777)}#root[hidden],.design-workspace[hidden]{display:none!important}.design-workspace{max-width:1184px;margin:0 auto;padding:36px 32px 80px;color:var(--color-ink,#222);font:16px/1.65 -apple-system,BlinkMacSystemFont,sans-serif}.design-heading h1{font-size:30px;letter-spacing:-.04em;line-height:1.3;margin:8px 0 14px;font-weight:600}.design-heading>p{color:var(--color-muted,#73786f);margin:8px 0}.design-eyebrow{font-size:12px;letter-spacing:.09em}.design-steps{padding:0;list-style:none;display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin:30px 0 20px}.design-steps li{display:flex;gap:10px;align-items:center;border-bottom:2px solid var(--color-rule,#e5e7e1);padding:12px 0;font-size:14px;color:var(--color-muted,#7b8175)}.design-steps li>span{width:26px;height:26px;border-radius:8px;display:grid;place-items:center;background:var(--color-paper-soft,#f1f3ed);font-size:12px}.design-steps li.current{color:var(--color-accent,#536f38);border-color:var(--color-accent,#536f38)}.design-steps li.done{color:var(--color-ink,#333)}.design-callout{border-radius:12px;padding:14px 18px;background:var(--color-accent-soft,#eff4e9);font-size:14px}.design-card{border:1px solid var(--color-rule,#e5e7e1);background:var(--color-paper-raised,#fff);border-radius:20px;padding:24px;margin-top:22px;min-width:0}.design-card h2{font-size:20px;font-weight:600;margin:0 0 16px}.design-card h3{font-size:18px;font-weight:500;margin:6px 0}.design-card p{margin:12px 0;white-space:pre-wrap;overflow-wrap:anywhere}.design-muted{color:var(--color-muted,#777);font-size:14px}.design-badge{display:inline-block;font-size:12px;padding:4px 10px;border-radius:99px;background:var(--color-paper-soft,#f4f4f1);color:var(--color-muted,#777)}.design-badge.passed{background:var(--color-accent-soft,#eff4e9);color:var(--color-accent,#536f38)}.design-specs{display:grid;grid-template-columns:1fr 1fr;gap:18px 28px;margin:20px 0}.design-field dt{font-size:12px;color:var(--color-muted,#777);margin-bottom:5px}.design-field dd{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}.design-list-item{border-top:1px solid var(--color-rule,#e5e7e1);padding:18px 0;margin-top:16px}.design-card summary{cursor:pointer;font-weight:500}.design-card table{width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed}.design-card th,.design-card td{padding:12px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--color-rule,#e5e7e1);overflow-wrap:anywhere}.design-card th{font-size:12px;color:var(--color-muted,#777);font-weight:500}.design-prototype,.design-acceptance{padding:20px 0;border-top:1px solid var(--color-rule,#e5e7e1);margin-top:20px}.design-state-tabs{display:flex;flex-wrap:wrap;gap:8px;margin:20px 0}.design-views{display:grid;grid-template-columns:1fr 1fr;gap:20px;background:var(--color-paper-soft,#f4f5f1);padding:24px 16px;border-radius:16px;overflow-x:auto}.design-views figure{margin:0;min-width:0;display:flex;flex-direction:column;align-items:center;gap:14px}.design-views figcaption{font-size:14px;color:var(--color-muted,#777)}.design-views iframe{border:0;outline:1px solid #ddd;border-radius:24px;background:#fff;flex:none;max-width:100%;box-shadow:0 10px 36px #0000000a}.design-views a,.design-card a{color:var(--color-accent,#536f38);font-size:14px;overflow-wrap:anywhere}.design-download{display:inline-block;border:1px solid var(--color-rule,#ddd);padding:9px 14px;border-radius:12px;margin:8px 12px 16px 0}.design-navigation button:focus-visible,.design-workspace :is(button,a,summary):focus-visible{outline:3px solid var(--color-accent,#536f38);outline-offset:3px}@media(max-width:700px){.design-navigation{padding:12px 16px}.design-progress{width:100%;margin-left:0;font-size:11px}.design-workspace{padding:24px 16px 50px}.design-heading h1{font-size:24px}.design-card{padding:18px 14px}.design-specs{grid-template-columns:1fr}.design-steps{gap:6px}.design-steps li{flex-direction:column;font-size:11px;align-items:flex-start;gap:7px}.design-views{grid-template-columns:1fr;padding:16px 0;gap:30px}.design-card th,.design-card td{padding:10px 4px;font-size:12px}.design-state-tabs button{font-size:13px;padding:8px 10px}}
|