anentrypoint-design 0.0.280 → 0.0.281

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.280",
3
+ "version": "0.0.281",
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",
@@ -161,6 +161,9 @@ export function injectCodeCopy(container) {
161
161
  });
162
162
  }
163
163
 
164
+ const MD_STREAM_THROTTLE_MS = 120;
165
+ const MD_STREAM_MIN_DELTA_CHARS = 40;
166
+
164
167
  function MdNode(p) {
165
168
  const refSink = (el) => {
166
169
  if (!el) return;
@@ -169,7 +172,27 @@ function MdNode(p) {
169
172
  // once the loader recovers, instead of staying plain-escaped forever.
170
173
  const srcKey = (isMarkdownDegraded() ? '~degraded~' : '') + (p.text || '');
171
174
  if (el.dataset.mdSrc === srcKey) return;
175
+ // While streaming (text still growing, not the final settle), a full
176
+ // re-parse of the WHOLE accumulated text on every rAF tick is the
177
+ // dominant cost of a long stream. Throttle: skip the parse unless
178
+ // enough time or enough new characters landed since the last one.
179
+ // p.streamingCaret (already threaded by the host for the stream-head
180
+ // caret) marks "still streaming"; its absence forces the final parse
181
+ // so nothing is left un-parsed once the turn settles.
182
+ const parsedLen = el.dataset.mdParsedLen ? Number(el.dataset.mdParsedLen) : 0;
183
+ const lastParseAt = el.dataset.mdLastParseAt ? Number(el.dataset.mdLastParseAt) : 0;
184
+ const now = (typeof performance !== 'undefined' ? performance.now() : Date.now());
185
+ if (p.streamingCaret && parsedLen > 0 &&
186
+ (now - lastParseAt) < MD_STREAM_THROTTLE_MS &&
187
+ (srcKey.length - parsedLen) < MD_STREAM_MIN_DELTA_CHARS) {
188
+ // Not enough new content/time yet — paint raw text so the latest
189
+ // characters are visible, but defer the expensive re-parse.
190
+ el.textContent = p.text || '';
191
+ return;
192
+ }
172
193
  el.dataset.mdSrc = srcKey;
194
+ el.dataset.mdParsedLen = String(srcKey.length);
195
+ el.dataset.mdLastParseAt = String(now);
173
196
  // Markdown stack still loading (or down): paint the raw text
174
197
  // synchronously so streamed tokens are visible the same frame they
175
198
  // arrive (an empty bubble until the CDN import resolves reads as a
@@ -215,8 +238,20 @@ function CodeNode(p) {
215
238
  // surrounding ChatMessage chrome (avatar/meta/reactions) stays consistent.
216
239
  function ToolCallNode(p) {
217
240
  const status = p.status || (p.error ? 'error' : (p.result != null ? 'done' : 'running'));
218
- const argsText = typeof p.args === 'string' ? p.args : JSON.stringify(p.args || {}, null, 2);
219
- const resultText = p.result == null ? '' : (typeof p.result === 'string' ? p.result : JSON.stringify(p.result, null, 2));
241
+ // Args/result are re-stringified on every rAF re-render while any part of
242
+ // the turn is streaming, even for collapsed cards whose own args/result
243
+ // haven't changed since the last frame. Cache by identity on the part
244
+ // object itself so an unchanged args/result skips the stringify.
245
+ if (p._argsCache !== p.args) {
246
+ p._argsTextCache = typeof p.args === 'string' ? p.args : JSON.stringify(p.args || {}, null, 2);
247
+ p._argsCache = p.args;
248
+ }
249
+ const argsText = p._argsTextCache;
250
+ if (p._resultCache !== p.result) {
251
+ p._resultTextCache = p.result == null ? '' : (typeof p.result === 'string' ? p.result : JSON.stringify(p.result, null, 2));
252
+ p._resultCache = p.result;
253
+ }
254
+ const resultText = p._resultTextCache;
220
255
  const hasArgs = p.args != null && argsText !== '{}' && argsText.trim() !== '';
221
256
  // Default-open while running or on error so the user sees live progress / failure detail;
222
257
  // collapse on success unless the caller explicitly overrides with open:true.
@@ -214,7 +214,14 @@ export function Topbar({ brand = '247420', leaf = '', items = [], active = '', o
214
214
  Brand({ name: brand, leaf }),
215
215
  search ? h('label', { class: 'app-search' },
216
216
  h('span', { class: 'icon', 'aria-hidden': 'true' }, 'search'),
217
- h('input', { type: 'search', name: 'q', placeholder: search, 'aria-label': `search ${search}` })
217
+ // `search` is either a plain placeholder string (renders the
218
+ // default uncontrolled input) or a caller-built VElement (has
219
+ // .type/.props — e.g. a controlled <input> wired to app state)
220
+ // rendered as-is. Stringifying a VElement into placeholder/
221
+ // aria-label previously produced literal "[object Object]" text.
222
+ (search && typeof search === 'object' && 'type' in search)
223
+ ? search
224
+ : h('input', { type: 'search', name: 'q', placeholder: search, 'aria-label': `search ${search}` })
218
225
  ) : null,
219
226
  h('nav', { 'aria-label': 'main navigation' }, ...items.map(([label, href]) => {
220
227
  const cleanLabel = String(label).replace(' ->', '');