anentrypoint-design 0.0.195 → 0.0.196
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/app-shell.css +13 -2
- package/dist/247420.css +13 -2
- package/dist/247420.js +12 -12
- package/package.json +1 -1
- package/src/components/agent-chat.js +5 -1
- package/src/components/chat.js +42 -8
- package/src/components/content.js +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.196",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
|
@@ -129,7 +129,11 @@ export function AgentChat(props = {}) {
|
|
|
129
129
|
// accumulated source and swaps the entire bubble innerHTML on every frame
|
|
130
130
|
// (O(n^2) over the turn, with a visible reflow). Downgrade md -> text
|
|
131
131
|
// mid-stream; the settled turn below renders real markdown once.
|
|
132
|
-
|
|
132
|
+
// Carry a `mdShell` flag so the streaming-text bubble uses the same
|
|
133
|
+
// container shape (.chat-md padding/spacing) the settled markdown will
|
|
134
|
+
// use — only the inner content swaps on settle, so the bubble box does
|
|
135
|
+
// not reflow/jump when the turn finishes and renders real markdown.
|
|
136
|
+
if (isStreaming && part.kind === 'md') parts.push({ kind: 'text', text: part.text, mdShell: true });
|
|
133
137
|
else parts.push(part);
|
|
134
138
|
}
|
|
135
139
|
}
|
package/src/components/chat.js
CHANGED
|
@@ -19,6 +19,21 @@ export function fmtBytes(n) {
|
|
|
19
19
|
return (n / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// Reject dangerous URL schemes (javascript:, data:, vbscript:, file:) so an
|
|
23
|
+
// inline markdown link or an image src built from untrusted text can't smuggle a
|
|
24
|
+
// script-executing or data-exfiltrating URL past the inline renderer (which does
|
|
25
|
+
// NOT pass through DOMPurify the way the full md path does). http(s), mailto,
|
|
26
|
+
// protocol-relative, root/relative, and anchor links are allowed.
|
|
27
|
+
export function safeUrl(url) {
|
|
28
|
+
const s = String(url == null ? '' : url).trim();
|
|
29
|
+
if (!s) return null;
|
|
30
|
+
// Allow relative / anchor / protocol-relative without a scheme.
|
|
31
|
+
if (/^(\/|\.|#|\?)/.test(s) || s.startsWith('//')) return s;
|
|
32
|
+
const scheme = (s.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/) || [])[1];
|
|
33
|
+
if (!scheme) return s; // schemeless relative
|
|
34
|
+
return /^(https?|mailto|tel)$/i.test(scheme) ? s : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
// Inline-only markdown subset; safe for chat bubbles.
|
|
23
38
|
export function renderInline(text) {
|
|
24
39
|
if (text == null) return [];
|
|
@@ -31,7 +46,13 @@ export function renderInline(text) {
|
|
|
31
46
|
if (m[2] != null) push(h('strong', { key: 's' + i }, m[2]));
|
|
32
47
|
else if (m[3] != null) push(h('em', { key: 's' + i }, m[3]));
|
|
33
48
|
else if (m[4] != null) push(h('code', { key: 's' + i, class: 'chat-tick' }, m[4]));
|
|
34
|
-
else if (m[5] != null)
|
|
49
|
+
else if (m[5] != null) {
|
|
50
|
+
const safe = safeUrl(m[6]);
|
|
51
|
+
// A link with a rejected (unsafe) scheme degrades to its plain label
|
|
52
|
+
// text rather than a clickable, scheme-smuggling anchor.
|
|
53
|
+
if (safe) push(h('a', { key: 's' + i, href: safe, target: '_blank', rel: 'noopener noreferrer' }, m[5]));
|
|
54
|
+
else push(h('span', { key: 's' + i }, m[5]));
|
|
55
|
+
}
|
|
35
56
|
last = m.index + m[0].length; i += 1;
|
|
36
57
|
}
|
|
37
58
|
if (last < text.length) push(h('span', { key: 's' + i + 'a' }, text.slice(last)));
|
|
@@ -133,7 +154,13 @@ function ToolCallNode(p) {
|
|
|
133
154
|
h('pre', { class: 'chat-tool-pre' }, h('code', {}, argsText))),
|
|
134
155
|
resultText ? h('div', { class: 'chat-tool-section' },
|
|
135
156
|
h('div', { class: 'chat-tool-section-label' }, p.error ? 'error' : 'result'),
|
|
136
|
-
h('pre', { class: 'chat-tool-pre' + (p.error ? ' is-error' : '') }, h('code', {}, resultText)))
|
|
157
|
+
h('pre', { class: 'chat-tool-pre' + (p.error ? ' is-error' : '') }, h('code', {}, resultText)))
|
|
158
|
+
// A finished tool with no output would otherwise render no result
|
|
159
|
+
// section, reading identically to a still-running tool. Show an
|
|
160
|
+
// explicit placeholder so "done, empty" is distinguishable.
|
|
161
|
+
: (status === 'done' ? h('div', { class: 'chat-tool-section' },
|
|
162
|
+
h('div', { class: 'chat-tool-section-label' }, 'result'),
|
|
163
|
+
h('pre', { class: 'chat-tool-pre chat-tool-empty' }, h('code', {}, '(no output)'))) : null)
|
|
137
164
|
)
|
|
138
165
|
);
|
|
139
166
|
}
|
|
@@ -146,16 +173,24 @@ function ThinkingNode(p) {
|
|
|
146
173
|
}
|
|
147
174
|
|
|
148
175
|
const PART_RENDERERS = {
|
|
149
|
-
text: (p) => h('div', { class: 'chat-bubble' }, ...renderInline(p.text || '')),
|
|
176
|
+
text: (p) => h('div', { class: 'chat-bubble' + (p.mdShell ? ' chat-md' : '') }, ...renderInline(p.text || '')),
|
|
150
177
|
md: (p) => MdNode(p),
|
|
151
178
|
code: (p) => CodeNode(p),
|
|
152
179
|
tool: (p) => ToolCallNode(p),
|
|
153
180
|
tool_call: (p) => ToolCallNode(p),
|
|
154
181
|
tool_result: (p) => ToolCallNode({ ...p, name: p.name || 'tool_result', result: p.text != null ? p.text : p.result }),
|
|
155
182
|
thinking: (p) => ThinkingNode(p),
|
|
156
|
-
image: (p) =>
|
|
157
|
-
|
|
158
|
-
|
|
183
|
+
image: (p) => {
|
|
184
|
+
// Guard both the wrapping link and the img src against unsafe schemes
|
|
185
|
+
// (e.g. a data:text/html src) so an embedded-image part from untrusted
|
|
186
|
+
// markdown can't smuggle an active payload.
|
|
187
|
+
const imgSrc = safeUrl(p.src);
|
|
188
|
+
const linkHref = safeUrl(p.href || p.src);
|
|
189
|
+
if (!imgSrc) return h('span', { class: 'chat-image-blocked' }, p.alt || 'image blocked (unsafe url)');
|
|
190
|
+
return h('a', { class: 'chat-image', href: linkHref || imgSrc, target: '_blank', rel: 'noopener noreferrer', 'aria-label': p.alt || `embedded image: ${imgSrc}` },
|
|
191
|
+
h('img', { src: imgSrc, alt: p.alt || `embedded image from ${imgSrc}`, loading: 'lazy' }),
|
|
192
|
+
p.caption ? h('span', { class: 'cap' }, p.caption) : null);
|
|
193
|
+
},
|
|
159
194
|
pdf: (p) => h('div', { class: 'chat-pdf' },
|
|
160
195
|
h('div', { class: 'chat-pdf-head' },
|
|
161
196
|
h('span', { class: 'glyph', 'aria-hidden': 'true' }, Icon('file-pdf', { size: 18 })),
|
|
@@ -171,7 +206,7 @@ const PART_RENDERERS = {
|
|
|
171
206
|
h('span', { class: 'size' }, [p.kindLabel || (p.name || '').split('.').pop().toUpperCase(), p.size != null ? fmtBytes(p.size) : null].filter(Boolean).join(' · '))
|
|
172
207
|
),
|
|
173
208
|
h('span', { class: 'go', 'aria-hidden': 'true' }, Icon('arrow-down'))),
|
|
174
|
-
link: (p) => h('a', { class: 'chat-link', href: p.href, target: '_blank', rel: 'noopener', 'aria-label': `link: ${p.title || p.href}` },
|
|
209
|
+
link: (p) => h('a', { class: 'chat-link', href: safeUrl(p.href) || '#', target: '_blank', rel: 'noopener noreferrer', 'aria-label': `link: ${p.title || p.href}` },
|
|
175
210
|
p.thumb ? h('img', { class: 'thumb', src: p.thumb, alt: `preview for ${p.title || p.href}` }) : null,
|
|
176
211
|
h('span', { class: 'meta' },
|
|
177
212
|
h('span', { class: 'host' }, p.host || (() => { try { return new URL(p.href).host; } catch { return ''; } })()),
|
|
@@ -293,7 +328,6 @@ export function Chat({ title = 'chat', sub, messages = [], composer, header, sug
|
|
|
293
328
|
const msgCount = messages.length;
|
|
294
329
|
return h('div', { class: 'chat' },
|
|
295
330
|
header || h('div', { class: 'chat-head', role: 'banner' },
|
|
296
|
-
h('span', { class: 'dot', 'aria-hidden': 'true' }),
|
|
297
331
|
h('h2', { class: 'ds-chat-title' }, title),
|
|
298
332
|
sub ? h('span', { class: 'sub', 'aria-label': `subtitle: ${sub}` }, ' · ' + sub) : null,
|
|
299
333
|
h('span', { class: 'spread' }),
|
|
@@ -253,8 +253,11 @@ export function ProjectView({ project = {}, copied, onCopy } = {}) {
|
|
|
253
253
|
].filter(Boolean).flat();
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
export function PageHeader({ title, lede, eyebrow, right }) {
|
|
257
|
-
|
|
256
|
+
export function PageHeader({ title, lede, eyebrow, right, compact }) {
|
|
257
|
+
// `compact` drops the large leading/trailing section margins so a PageHeader
|
|
258
|
+
// used as a page's first element top-aligns cleanly without the consumer
|
|
259
|
+
// having to !important-override the .ds-section margin.
|
|
260
|
+
return h('section', { class: 'ds-section' + (compact ? ' ds-section-compact' : '') },
|
|
258
261
|
eyebrow ? h('span', { class: 'eyebrow' }, eyebrow) : null,
|
|
259
262
|
title != null ? h('h1', {}, title) : null,
|
|
260
263
|
lede != null ? h('p', { class: 'lede' }, lede) : null,
|