anentrypoint-design 0.0.204 → 0.0.206

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.204",
3
+ "version": "0.0.206",
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",
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as webjsx from '../../vendor/webjsx/index.js';
4
4
  import { Icon } from './shell.js';
5
+ import { sanitizeHtml } from '../markdown.js';
5
6
  const h = webjsx.createElement;
6
7
 
7
8
  // Clamp a count to a compact badge string (matches the rail's 99+ convention),
@@ -391,7 +392,13 @@ export function PageView({ title = '', html = '', isAdmin = false, onEdit } = {}
391
392
  ),
392
393
  h('div', {
393
394
  class: 'cm-page-body',
394
- ref: (el) => { if (el) el.innerHTML = html || '<p class="cm-page-empty">This page is empty.</p>'; }
395
+ // Page bodies are host/user-authored HTML, so they pass through the
396
+ // DOMPurify gate before innerHTML — never injected raw (stored-XSS gate).
397
+ ref: (el) => {
398
+ if (!el) return;
399
+ if (!html) { el.innerHTML = '<p class="cm-page-empty">This page is empty.</p>'; return; }
400
+ sanitizeHtml(html).then((clean) => { el.innerHTML = clean; });
401
+ }
395
402
  })
396
403
  );
397
404
  }
@@ -53,7 +53,11 @@ export function Row({ code, rank, title, sub, meta, active, state = 'default', o
53
53
  const isLink = kind === 'link' || (href != null && !onClick);
54
54
  const isButton = !isLink && !!onClick;
55
55
  const stateCls = state === 'disabled' ? ' row-state-disabled' : (state === 'error' ? ' row-state-error' : '');
56
- const cls = 'row' + (isActive ? ' active' : '') + stateCls + (cols ? ' row-grid' : '') + (rail ? ' rail-' + rail : '');
56
+ // With no leading/code, the title would otherwise land in the narrow code
57
+ // column and wrap; `row-nocode` collapses that column so the title gets the
58
+ // full width (meta still pinned right).
59
+ const noLead = codeVal == null && leading == null;
60
+ const cls = 'row' + (isActive ? ' active' : '') + stateCls + (cols ? ' row-grid' : '') + (noLead && !cols ? ' row-nocode' : '') + (rail ? ' rail-' + rail : '');
57
61
  const isDisabled = state === 'disabled';
58
62
  const props = { key, class: cls, style: cols ? `${style ? style + ';' : ''}grid-template-columns:${cols}` : style };
59
63
  if (isLink) {
@@ -168,7 +172,7 @@ export function WorksList({ works = [], openedIndex = -1, onToggle }) {
168
172
  // Expand affordance: a chevron icon (down when open, right when
169
173
  // collapsed) separated from the meta text by a CSS gap, not a
170
174
  // literal +/- with a double-space.
171
- meta: h('span', { class: 'ds-works-meta', style: 'display:inline-flex;align-items:center;gap:.4em' },
175
+ meta: h('span', { class: 'ds-works-meta' },
172
176
  w.meta != null ? h('span', {}, w.meta) : null,
173
177
  Icon(isOpen ? 'chevron-down' : 'chevron-right')),
174
178
  active: isOpen,
@@ -139,7 +139,7 @@ export function makeToolsPages(ctx) {
139
139
  const out = h('div', { id: 'fd-batch-out' });
140
140
  const root = ctx.root;
141
141
  return [
142
- Section({ title: '// batch runner', children: [
142
+ Section({ title: 'batch runner', children: [
143
143
  Panel({ title: 'run prompts', children: form({
144
144
  fields: [{ name: 'prompts', kind: 'textarea', placeholder: 'one prompt per line' }, { name: 'concurrency', type: 'number', value: '4' }],
145
145
  submit: 'run',
package/src/markdown.js CHANGED
@@ -52,3 +52,13 @@ export async function renderMarkdown(src) {
52
52
  const raw = _marked.parse(String(src));
53
53
  return _purify.sanitize(raw);
54
54
  }
55
+
56
+ // Sanitize already-rendered HTML before it touches innerHTML. For any surface
57
+ // that injects host/user-authored HTML (e.g. a wiki page body), this is the
58
+ // single XSS gate — DOMPurify strips scripts/handlers. If the purifier hasn't
59
+ // loaded, we safe-fail by escaping (raw tags show as text, never execute).
60
+ export async function sanitizeHtml(html) {
61
+ const ok = await ensureReady();
62
+ if (!ok) return escapeHtml(html);
63
+ return _purify.sanitize(String(html));
64
+ }
@@ -1,173 +0,0 @@
1
- // Performance test harness for markdown & Prism cache optimization.
2
- // Measures load times, render performance, and verifies correctness.
3
- // Run: node src/markdown-cache-perf-test.js
4
-
5
- import { renderMarkdownCached, highlightCodeBlockCached, initializeCachesEagerly, getCacheStats, resetCacheState } from './markdown-cache.js';
6
-
7
- const SAMPLE_MARKDOWN = `
8
- # Hello World
9
-
10
- This is **bold** and *italic* text with \`inline code\`.
11
-
12
- [Link example](https://example.com)
13
-
14
- ## Code Block Example
15
-
16
- \`\`\`python
17
- def hello():
18
- print("World")
19
- \`\`\`
20
-
21
- > Blockquote example
22
-
23
- - List item 1
24
- - List item 2
25
- - List item 3
26
- `;
27
-
28
- const CODE_SAMPLES = [
29
- { lang: 'javascript', code: 'const x = 42; console.log(x);' },
30
- { lang: 'python', code: 'def fib(n):\n return n if n < 2 else fib(n-1) + fib(n-2)' },
31
- { lang: 'bash', code: 'echo "Hello World" | grep -i hello' },
32
- ];
33
-
34
- async function measureInitialization() {
35
- console.log('\n=== Initialization Performance ===');
36
- resetCacheState();
37
-
38
- const startTotal = performance.now();
39
- await initializeCachesEagerly();
40
- const totalMs = performance.now() - startTotal;
41
-
42
- const stats = getCacheStats();
43
- console.log(`Total init time: ${totalMs.toFixed(2)}ms`);
44
- console.log(` - Markdown init: ${stats.initMs.markdown.toFixed(2)}ms`);
45
- console.log(` - Prism init: ${stats.initMs.prism.toFixed(2)}ms`);
46
- console.log(`Status: Markdown=${stats.markdownInitialized}, Prism=${stats.prismInitialized}`);
47
-
48
- // Second call should be instant (cached)
49
- const t0 = performance.now();
50
- await initializeCachesEagerly();
51
- const cacheHitMs = performance.now() - t0;
52
- console.log(`Second init (cached): ${cacheHitMs.toFixed(2)}ms`);
53
-
54
- return stats;
55
- }
56
-
57
- async function measureMarkdownRendering() {
58
- console.log('\n=== Markdown Rendering Performance ===');
59
-
60
- // Warm up
61
- await renderMarkdownCached(SAMPLE_MARKDOWN);
62
-
63
- // Measure 10 renders
64
- const times = [];
65
- for (let i = 0; i < 10; i++) {
66
- const t0 = performance.now();
67
- await renderMarkdownCached(SAMPLE_MARKDOWN);
68
- times.push(performance.now() - t0);
69
- }
70
-
71
- const avg = times.reduce((a, b) => a + b, 0) / times.length;
72
- const min = Math.min(...times);
73
- const max = Math.max(...times);
74
-
75
- console.log(`Rendered markdown 10x:`);
76
- console.log(` - Average: ${avg.toFixed(2)}ms`);
77
- console.log(` - Min: ${min.toFixed(2)}ms`);
78
- console.log(` - Max: ${max.toFixed(2)}ms`);
79
- console.log(`Sample times: [${times.map(t => t.toFixed(1)).join(', ')}]ms`);
80
-
81
- return { avg, min, max, times };
82
- }
83
-
84
- async function measureSyntaxHighlighting() {
85
- console.log('\n=== Syntax Highlighting Performance ===');
86
-
87
- const results = [];
88
-
89
- for (const sample of CODE_SAMPLES) {
90
- // Create a minimal mock element (note: in browser, this would be real DOM)
91
- const mockEl = { querySelectorAll: () => [] };
92
-
93
- // Measure highlight operation
94
- const t0 = performance.now();
95
- await highlightCodeBlockCached(mockEl);
96
- const ms = performance.now() - t0;
97
-
98
- console.log(`${sample.lang}: ${ms.toFixed(2)}ms`);
99
- results.push({ lang: sample.lang, ms });
100
- }
101
-
102
- return results;
103
- }
104
-
105
- async function measureMultipleMessages() {
106
- console.log('\n=== Simulated Message Stream (10 messages) ===');
107
-
108
- const times = [];
109
- let totalMs = 0;
110
-
111
- for (let i = 0; i < 10; i++) {
112
- const t0 = performance.now();
113
- await renderMarkdownCached(SAMPLE_MARKDOWN);
114
- const ms = performance.now() - t0;
115
- times.push(ms);
116
- totalMs += ms;
117
- process.stdout.write(`Message ${i + 1}: ${ms.toFixed(1)}ms\n`);
118
- }
119
-
120
- console.log(`\nStream Stats:`);
121
- console.log(` - Total: ${totalMs.toFixed(2)}ms`);
122
- console.log(` - Average: ${(totalMs / times.length).toFixed(2)}ms`);
123
- console.log(` - First message overhead: ${times[0].toFixed(2)}ms`);
124
- console.log(` - Steady state average: ${(times.slice(1).reduce((a, b) => a + b, 0) / (times.length - 1)).toFixed(2)}ms`);
125
- }
126
-
127
- async function runAllTests() {
128
- console.log('[247420] Markdown & Prism Cache Performance Test Suite');
129
- console.log('='.repeat(50));
130
-
131
- try {
132
- // Test 1: Initialization
133
- const initStats = await measureInitialization();
134
-
135
- // Test 2: Markdown rendering
136
- const mdStats = await measureMarkdownRendering();
137
-
138
- // Test 3: Syntax highlighting
139
- const syntaxStats = await measureSyntaxHighlighting();
140
-
141
- // Test 4: Multi-message stream
142
- await measureMultipleMessages();
143
-
144
- // Final cache stats
145
- console.log('\n=== Final Cache Statistics ===');
146
- const finalStats = getCacheStats();
147
- console.log(`Total messages rendered: ${finalStats.renderStats.count}`);
148
- console.log(`Average render time: ${finalStats.renderStats.avgTimeMs}ms`);
149
- console.log(`Min/Max render time: ${finalStats.renderStats.minTimeMs}ms / ${finalStats.renderStats.maxTimeMs}ms`);
150
-
151
- // Summary
152
- console.log('\n=== Summary ===');
153
- console.log(`Library init (one-time): ${initStats.initMs.markdown + initStats.initMs.prism}ms`);
154
- console.log(`Markdown render: ~${mdStats.avg.toFixed(1)}ms per message (cached)`);
155
- console.log(`Cache hit on subsequent init: 0ms (verified)`);
156
- console.log(`Estimated time for 100 messages without cache: ~10000+ms`);
157
- console.log(`Estimated time for 100 messages with cache: ~${(mdStats.avg * 100 + initStats.initMs.markdown + initStats.initMs.prism).toFixed(0)}ms`);
158
- const speedup = (10000 + initStats.initMs.markdown + initStats.initMs.prism) / (mdStats.avg * 100 + initStats.initMs.markdown + initStats.initMs.prism);
159
- console.log(`Performance improvement: ~${speedup.toFixed(1)}x faster`);
160
-
161
- console.log('\nPASS All tests completed successfully');
162
- } catch (err) {
163
- console.error('\nFAIL Test failed:', err.message);
164
- console.error(err.stack);
165
- process.exit(1);
166
- }
167
- }
168
-
169
- // Run tests
170
- runAllTests().then(() => process.exit(0)).catch(err => {
171
- console.error('Fatal error:', err);
172
- process.exit(1);
173
- });