feat(client): brand the Plugins page cards with the package icon.svg
The Plugins page's package cards draw each bundle's icon from the Host's package metadata (manifest-relative SVG/PNG/JPEG/WebP, inlined as a data URI), which we never declared - the cards fell back to the default artwork. The Context emblem's single graphic source is now the package-root icon.svg: the package.json "icon" field points the Host's reader at it, and the client build inlines the same file through a "*.svg?raw" channel (the bundled ContextIcon re-renders it at every requested size), so the code carries no second copy of the paths. A spec pins the file and the component in lockstep; the files list ships the asset.
This commit is contained in:
1 parent
0016d9c62f
commit
bdb63a1aed
6 files changed
+72
-23
No files matched your search
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path d="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" fill="#4A5699"/>
|
||||
<path d="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" fill="#C45FA0"/>
|
||||
<path d="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" fill="#6277BA"/>
|
||||
<path d="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" fill="#C45FA0"/>
|
||||
<path d="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" fill="#E5594F"/>
|
||||
<path d="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" fill="#F0D043"/>
|
||||
<path d="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" fill="#E5594F"/>
|
||||
<path d="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" fill="#6277BA"/>
|
||||
<path d="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" fill="#F39A2B"/>
|
||||
<path d="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" fill="#F39A2B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -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",
|
||||
|
||||
Vendored
+7
@@ -17,3 +17,10 @@ declare module '*.module.css' {
|
||||
const classes: Record<string, string>
|
||||
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
|
||||
}
|
||||
+14
-21
@@ -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<readonly [string, string]> = [
|
||||
['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 `<svg>` tags: the sheet's strokes in paint order, whitespace-folded. */
|
||||
const SHEET_MARKUP = sheetMarkup
|
||||
.slice(sheetMarkup.indexOf('>') + 1, sheetMarkup.lastIndexOf('</svg>'))
|
||||
.trim()
|
||||
.replace(/>\s+</g, '><')
|
||||
|
||||
/** 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]) => <path key={d} d={d} fill={fill} />)}
|
||||
</svg>
|
||||
dangerouslySetInnerHTML={{ __html: SHEET_MARKUP }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(/<path d="([^"]+)" fill="([^"]+)"\/>/g)].map(([, d, fill]) => ({ d, fill }))
|
||||
const m = await mount(h(ContextIcon, {}))
|
||||
const componentPaths = queryAll<SVGPathElement>(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, /^<svg xmlns="http:\/\/www\.w3\.org\/2000\/svg" viewBox="0 0 1024 1024">/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('makeContextTabTitle — the chip-title seat', () => {
|
||||
|
||||
+16
-1
@@ -226,7 +226,22 @@ export default defineConfig([
|
||||
),
|
||||
},
|
||||
plugins: [{
|
||||
name: 'dsh-client-bundle-purity', resolveId(source: string) {
|
||||
name: 'dsh-svg-raw',
|
||||
// `*.svg?raw` inlines a file's markup as its default export — the
|
||||
// emblem (icon.svg) is the package's single graphic source, shared
|
||||
// with the Host's package-meta reader through package.json `icon`.
|
||||
resolveId(source: string, importer: string | undefined) {
|
||||
if (!source.endsWith('.svg?raw')) return null
|
||||
const target = source.slice(0, -'?raw'.length)
|
||||
return importer === undefined ? target : resolvePath(dirname(importer), target) + '?raw'
|
||||
},
|
||||
load(id: string) {
|
||||
if (!id.endsWith('.svg?raw')) return null
|
||||
return `export default ${JSON.stringify(readFileSync(id.slice(0, -'?raw'.length), 'utf8'))}`
|
||||
},
|
||||
}, {
|
||||
name: 'dsh-client-bundle-purity',
|
||||
resolveId(source: string) {
|
||||
if (!source.startsWith('@deepseek-ai/')) return null
|
||||
if (isRequested(source)) return null
|
||||
if (VENDORED_LIBRARY.test(source)) return null
|
||||
|
||||
Reference in new issue
Block a user