@kolkrabbi/kol-shell 0.31.0 → 0.33.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-shell",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "private": false,
5
5
  "description": "KOL application shell — fixed 48px NavRail + AppShell layout root, PageShell/PageHeader scaffolds, ContentFilters catalog organism, GridCard, SettingsScaffold, WalkthroughPanel, ShortcutsOverlay. App chrome (kol-framework owns site chrome). Nav items, content, shortcuts and settings are consumer-injected. Sits above @kolkrabbi/kol-{theme,component,framework}.",
6
6
  "license": "MIT",
@@ -20,10 +20,10 @@
20
20
  "react-dom": "^18.3.0 || ^19.0.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@kolkrabbi/kol-component": "^0.145.0",
23
+ "@kolkrabbi/kol-component": "^0.151.0",
24
24
  "@kolkrabbi/kol-icons": "^0.25.0",
25
- "@kolkrabbi/kol-theme": "^0.112.0",
26
- "@kolkrabbi/kol-framework": "^0.36.0"
25
+ "@kolkrabbi/kol-framework": "^0.36.0",
26
+ "@kolkrabbi/kol-theme": "^0.119.0"
27
27
  },
28
28
  "files": [
29
29
  "src",
@@ -111,8 +111,20 @@ export default function CatalogPage({
111
111
  if (open) return null
112
112
  const cards = rows.map((item) => toCard ? toCard(item, { view: viewMode, layout }) : { title: item.title ?? item.name, detail: item.detail })
113
113
  const hidden = layout === 'list' ? new Set() : computeHiddenSet(rows, cards.findIndex((c) => c.expanded))
114
+ /* COLS IS A CEILING, NOT A COMMAND (CatalogPageMobileColumns,
115
+ * kol-monitor 2026-09-01) — the third home of the defect
116
+ * ContentCollectionMinColumnWidth and ContentGridMinColumnWidth
117
+ * closed, unreachable by either because this grid is drawn inline.
118
+ * `repeat(6, 1fr)` computed six 29px slivers at 390. Same idiom as
119
+ * ContentCollection:144 — up to 6 (grid) / 4 (list) columns, and a
120
+ * track may not go under the floor (160 grid / 240 list) nor demand
121
+ * more than the container. At monitor's desktop widths the sixth-
122
+ * share clears the floor, so nothing moves there. NB the 2×2
123
+ * expanded-card neighbour math (computeHiddenSet) stays a
124
+ * six-column ruling — below the ceiling the hide-set is
125
+ * desktop-only geometry. */
114
126
  return (
115
- <div style={{ display: 'grid', gridTemplateColumns: layout === 'list' ? 'repeat(4, 1fr)' : 'repeat(6, 1fr)', gap: layout === 'list' ? 8 : 24 }}>
127
+ <div style={{ display: 'grid', gridTemplateColumns: layout === 'list' ? 'repeat(auto-fill, minmax(min(100%, max(240px, calc((100% - 3 * 8px) / 4))), 1fr))' : 'repeat(auto-fill, minmax(min(100%, max(160px, calc((100% - 5 * 24px) / 6))), 1fr))', gap: layout === 'list' ? 8 : 24 }}>
116
128
  {rows.map((item, i) => {
117
129
  if (hidden.has(i)) return null
118
130
  const c = cards[i]
package/src/Logomark.jsx CHANGED
@@ -5,10 +5,39 @@ import { useState, useEffect } from 'react'
5
5
  * <img> renders a currentColor mark black in dark mode (invisible). Ported
6
6
  * verbatim from the kol-mirror cut (module-level cache; async resolve even on
7
7
  * cache hit — sync setState in an effect cascades renders).
8
+ *
9
+ * The fetched markup is SANITIZED before injection — `<style>`/`<script>`
10
+ * stripped, `on*` attributes dropped (LogomarkInlineStyleLeak, kol-chess
11
+ * 2026-09-01). An SVG *document* may legitimately carry its own `<style>` — a
12
+ * theme-aware favicon does — but once inlined, that `<style>` is
13
+ * DOCUMENT-GLOBAL: kol-chess pointed `svgUrl` at its favicon and every <svg>
14
+ * in the app took OS-keyed ink and ignored `data-theme`, presenting two
15
+ * packages away as "the theme toggle is broken". A mark that needs its own
16
+ * styling inlines it as attributes, not a stylesheet.
8
17
  */
9
18
 
10
19
  const svgCache = new Map()
11
20
 
21
+ /* strip what must never escape into the host document: stylesheets (global
22
+ * once inlined), scripts and event handlers (this is a fetch → innerHTML
23
+ * boundary). DOMParser is the correct tool; the regex fallback only runs if
24
+ * the markup does not even parse as SVG. */
25
+ function sanitizeSvg(svg) {
26
+ try {
27
+ const doc = new DOMParser().parseFromString(svg, 'image/svg+xml')
28
+ if (doc.querySelector('parsererror')) throw new Error('unparseable')
29
+ doc.querySelectorAll('style, script').forEach((el) => el.remove())
30
+ doc.querySelectorAll('*').forEach((el) => {
31
+ for (const a of [...el.attributes]) if (/^on/i.test(a.name)) el.removeAttribute(a.name)
32
+ })
33
+ return new XMLSerializer().serializeToString(doc)
34
+ } catch {
35
+ return svg
36
+ .replace(/<(style|script)[\s\S]*?<\/\1\s*>/gi, '')
37
+ .replace(/\son[a-z]+\s*=\s*("[^"]*"|'[^']*')/gi, '')
38
+ }
39
+ }
40
+
12
41
  export default function Logomark({ svgUrl, size = 20, className = '', ...props }) {
13
42
  const [svgContent, setSvgContent] = useState(() => svgCache.get(svgUrl) || null)
14
43
 
@@ -16,7 +45,7 @@ export default function Logomark({ svgUrl, size = 20, className = '', ...props }
16
45
  if (!svgUrl) return
17
46
  let active = true
18
47
  Promise.resolve(
19
- svgCache.get(svgUrl) ?? fetch(svgUrl).then(res => res.text()).then(svg => { svgCache.set(svgUrl, svg); return svg })
48
+ svgCache.get(svgUrl) ?? fetch(svgUrl).then(res => res.text()).then(raw => { const svg = sanitizeSvg(raw); svgCache.set(svgUrl, svg); return svg })
20
49
  )
21
50
  .then(svg => { if (active) setSvgContent(svg) })
22
51
  .catch(() => {})
@@ -15,6 +15,12 @@ import { SettingsRow } from '@kolkrabbi/kol-component'
15
15
  * token by nature — the value cell is `whitespace-nowrap` and the label yields
16
16
  * (SettingsShortcutsComboWrap, kol-monitor 2026-08-27: `⌘ K` broke as `⌘` over
17
17
  * `K` in the six-column grid — the cell was squeezed to min-content, not full).
18
+ * `labelWidth="auto"` is what MAKES the label yield
19
+ * (SettingsShortcutsComboOverflow, kol-monitor 2026-09-01): the row's default
20
+ * is a fixed 160px label, which in this grid's ~176px columns left the combo
21
+ * cell 4px — every combo was painting into the 48px column gap, and the first
22
+ * one longer than the gap (`⌥ then 1–5`) reached the neighbour's labels. The
23
+ * label truncates, the combo hugs, an over-long combo clips at the column edge.
18
24
  *
19
25
  * @param {Array} sections `[{ section, items: [{ id, label, combo }] }]`
20
26
  * @param {Function} comboLabel (combo) => string (default: the combo as given)
@@ -26,7 +32,7 @@ export default function SettingsShortcuts({ sections = [], comboLabel = (c) => c
26
32
  <div key={section} style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
27
33
  <span className="kol-eyebrow text-strong mb-2">{section}</span>
28
34
  {items.map((k) => (
29
- <SettingsRow key={k.id ?? k.label} label={k.label} align="fill">
35
+ <SettingsRow key={k.id ?? k.label} label={k.label} align="fill" labelWidth="auto">
30
36
  <span className="text-fg-32 kol-helper-12 whitespace-nowrap">{comboLabel(k.combo)}</span>
31
37
  </SettingsRow>
32
38
  ))}