anentrypoint-design 0.0.205 → 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.205",
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,
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
+ }