diff --git a/icon.svg b/icon.svg
new file mode 100644
index 0000000..2e5ffbc
--- /dev/null
+++ b/icon.svg
@@ -0,0 +1,12 @@
+
diff --git a/package.json b/package.json
index ba60db7..69b330e 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,7 @@
"name": "dsh-context",
"version": "0.54.4",
"description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
+ "icon": "icon.svg",
"author": "bowenliang123",
"repository": {
"type": "git",
@@ -26,6 +27,7 @@
"./package.json": "./package.json"
},
"files": [
+ "icon.svg",
"lib/client.js",
"lib/index.js",
"lib/index.d.ts",
diff --git a/src/client/globals.d.ts b/src/client/globals.d.ts
index 83993c6..8fd1f50 100644
--- a/src/client/globals.d.ts
+++ b/src/client/globals.d.ts
@@ -17,3 +17,10 @@ declare module '*.module.css' {
const classes: Record
export default classes
}
+
+// The package emblem's static file (../icon.svg) rides the same build-time
+// channel idea: `?raw` inlines the file's markup as the default export.
+declare module '*.svg?raw' {
+ const markup: string
+ export default markup
+}
diff --git a/src/client/icon.tsx b/src/client/icon.tsx
index 3898e35..79cc123 100644
--- a/src/client/icon.tsx
+++ b/src/client/icon.tsx
@@ -2,29 +2,23 @@
* The Context emblem: the colourful document sheet, bundled rather than read
* off the harness primitives so the plugin's identity is self-contained.
*
- * It fills the right-Sidebar tab type's two glyph seats, exactly as the shipped
- * files type fills its own: the guide capsule before the tab is open
- * (`SidebarGuideEntry.icon`) and the chip title beside the label once it is
- * (`sidebar.right.pane.tab.title`). Fixed fills, never `currentColor` — the
- * sheet is deliberately polychrome on both light and dark chrome.
+ * The single graphic source is the package-root `icon.svg` — the same file
+ * package.json `icon` hands to the Host's package-meta reader, which serves
+ * it to the Plugins page's package cards. The client bundle inlines the
+ * file's markup at build time (the `?raw` channel in tsdown.config.ts) and
+ * re-renders it at every requested size. Fixed fills, never `currentColor` —
+ * the sheet is deliberately polychrome on both light and dark chrome.
*/
+import sheetMarkup from '../../icon.svg?raw'
import type { ReactElement } from 'react'
import type { Translate } from './i18n'
-/** The sheet's strokes, in paint order, as `[path, fill]` pairs. */
-const SHEET_PATHS: ReadonlyArray = [
- ['M899.984 19.873h-3.452c-26.123 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.173 47.298 47.296 47.298h3.452c26.119 0 47.297-21.171 47.297-47.298V67.169c0-26.124-21.177-47.296-47.297-47.296z', '#4A5699'],
- ['M132.643 19.873h-3.449c-26.12 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.177 47.298 47.296 47.298h3.449c26.123 0 47.299-21.171 47.299-47.298V67.169c0-26.124-21.176-47.296-47.299-47.296z', '#C45FA0'],
- ['M899.463 19.873H129.194c-26.12 0-47.296 21.172-47.296 47.296v3.377c0 26.12 21.177 47.299 47.296 47.299h770.269c26.123 0 47.296-21.179 47.296-47.299v-3.377c0-26.124-21.173-47.296-47.296-47.296z', '#6277BA'],
- ['M899.463 905.006H129.194c-26.12 0-47.296 21.17-47.296 47.29v3.381c0 26.127 21.177 47.298 47.296 47.298h770.269c26.123 0 47.296-21.171 47.296-47.298v-3.381c0-26.12-21.173-47.29-47.296-47.29z', '#C45FA0'],
- ['M717.962 543.153H542.047c-26.121 0-47.298 21.175-47.298 47.297v3.724c0 26.123 21.177 47.293 47.298 47.293h175.915c26.121 0 47.297-21.17 47.297-47.293v-3.724c0-26.122-21.176-47.297-47.297-47.297z', '#E5594F'],
- ['M689.268 198.849H513.355c-26.122 0-47.298 21.175-47.298 47.297v3.722c0 26.12 21.176 47.297 47.298 47.297h175.912c26.122 0 47.298-21.177 47.298-47.297v-3.722c0-26.122-21.175-47.297-47.297-47.297z', '#F0D043'],
- ['M757.789 353.081H261.17c-26.121 0-47.297 21.172-47.297 47.296v3.377c0 26.121 21.177 47.299 47.297 47.299h496.619c26.121 0 47.296-21.178 47.296-47.299v-3.377c0-26.125-21.175-47.296-47.296-47.296z', '#E5594F'],
- ['M762.638 726.225h-496.62c-26.12 0-47.294 21.18-47.294 47.301v3.377c0 26.12 21.174 47.3 47.294 47.3h496.62c26.122 0 47.296-21.18 47.296-47.3v-3.377c0-26.122-21.174-47.301-47.296-47.301z', '#6277BA'],
- ['M355.734 543.328H281.41c-26.122 0-47.297 21.17-47.297 47.293v3.378c0 26.118 21.175 47.297 47.297 47.297h74.324c26.123 0 47.296-21.179 47.296-47.297v-3.378c0-26.123-21.174-47.293-47.296-47.293z', '#F39A2B'],
- ['M334.85 248.006m-48.986 0a48.986 48.986 0 1 0 97.972 0 48.986 48.986 0 1 0-97.972 0Z', '#F39A2B'],
-]
+/** Everything between the file's `'))
+ .trim()
+ .replace(/>\s+<')
/** The emblem's props, matching the harness `IconProps` the guide capsule hands it. */
export interface ContextIconProps {
@@ -44,9 +38,8 @@ export function ContextIcon({ size = 20, className }: ContextIconProps): ReactEl
className={className}
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
- >
- {SHEET_PATHS.map(([d, fill]) => )}
-
+ dangerouslySetInnerHTML={{ __html: SHEET_MARKUP }}
+ />
)
}
diff --git a/tests/client/icon.spec.ts b/tests/client/icon.spec.ts
index 7f179aa..6622185 100644
--- a/tests/client/icon.spec.ts
+++ b/tests/client/icon.spec.ts
@@ -1,8 +1,11 @@
// The Context emblem (src/client/icon.tsx): the bundled polychrome document
// sheet that fills the right-Sidebar tab type's two glyph seats — the guide
// capsule and the chip title. Bundled rather than read off the harness
-// primitives, so these specs render the real component.
+// primitives, so these specs render the real component. The same sheet is
+// exported statically as the package-root icon.svg the Host's package-meta
+// reader serves to the Plugins page; a spec pins the two in lockstep.
+import { readFile } from 'node:fs/promises'
import { createElement as h } from 'react'
import assert from 'node:assert/strict'
import { describe, test } from 'vitest'
@@ -35,6 +38,23 @@ describe('ContextIcon', () => {
assert.equal(svg.getAttribute('class'), null)
await m.unmount()
})
+
+ test('the package-root icon.svg stays in lockstep with the component', async () => {
+ // The static file is what the Host's package-meta reader serves to the
+ // Plugins page (package.json `icon`), so a component edit that skips the
+ // file — or a manual file edit that skips the component — fails here.
+ const raw = await readFile('icon.svg', 'utf8')
+ const svgPaths = [...raw.matchAll(//g)].map(([, d, fill]) => ({ d, fill }))
+ const m = await mount(h(ContextIcon, {}))
+ const componentPaths = queryAll(m.container, 'path').map(p => ({
+ d: p.getAttribute('d'),
+ fill: p.getAttribute('fill'),
+ }))
+ await m.unmount()
+ assert.ok(svgPaths.length >= 10)
+ assert.deepEqual(svgPaths, componentPaths)
+ assert.match(raw, /^