dsh-markdown-bubble 0.1.3 → 0.1.4
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/README.md +2 -0
- package/package.json +1 -1
- package/src/client.js +47 -14
- package/src/index.js +1 -1
package/README.md
CHANGED
|
@@ -95,6 +95,7 @@ const a = 1
|
|
|
95
95
|
|
|
96
96
|
### 修复记录
|
|
97
97
|
|
|
98
|
+
- **0.1.4** —— 按评审意见把降级路径**合成一个函数**:此前「合成失败」「宿主投影抛错」「最后兜底」各自写了一份降级逻辑(一份调宿主投影、一份内联拼裸文本),三处要人工保持同步。现在只有 `degradedBody()` 一处:先试宿主自己的投影(保住引用 chip 等宿主渲染),投影也抛才退到裸文本;三条降级路径全部走它。内容优先这一点写进函数契约:裸文本是最后一步,只有宿主自己拒绝投影时才到。新增回归:断言 `projectUserText` 全仓只有一次调用、裸文本 span 只有一处构造、降级调用方都走同一函数(再造一份降级该测试转红)。
|
|
98
99
|
- **0.1.3** —— 把「座位 render 绝不抛」这条不变式补完:0.1.2 守住了合成与文案查询,但仍有形状会在**座位自己的渲染里**抛错(`node` 缺失 → `node.data`、`props` 缺失 → 解构本身、`new Date(time)` 遇到 Symbol / BigInt / 抛错的 valueOf、宿主图片渲染器抛错)。这些同样冒泡到槽位机制、退掉整条条目,症状与 0.1.2 修的完全一样。现在三层兜底:座位本体一层 try 并把 `node` / `data` 的取用改成防御式;时钟与附件卡在各自调用点兜底;最后一层 `renderPlainRow` 保证最坏情况只是这一行退回纯文本,而操作条与兄弟插件锚点仍在。新增回归:21 种恶意宿主形状逐一断言不抛、且行与操作条都在。
|
|
99
100
|
- **0.1.2** — 座位组件不再可能因渲染期异常被槽位机制摘除:`composeUserMarkdown` / `buildMarkdownLabels` 在座位渲染过程中运行,抛错不会被错误边界接住(错误边界只能接住其后代,接不住创建它的那次渲染),会冒泡到槽位机制并把该条目整条退掉——屏幕上就是气泡交还给宿主、Markdown 变回源码。现在合成与所有文案查询都在座位内部兜底:任何宿主形状不符(节点结构异常、投影返回不可识别、locale 席位不可调用)都退回宿主原始投影,气泡外壳、操作条与兄弟插件锚点全部保留。`data.content` 非数组、`t` 不可调用等情形一并加进回归测试。
|
|
100
101
|
- **0.1.1** — 修复两个引用 chip 之间只有空白时该空白被当作空 Markdown 块丢弃的问题:相邻 chip 不再粘成 `@a@b`,chip 之间的换行也按宿主 `pre-wrap` 气泡的样子保留为换行(空白段渲染为 `data-dshmb-gap` 的预换行节点,仍不产生空 Markdown 块);自建的操作条 / 复制按钮补上 `data-dshmb-actions` / `data-dshmb-action` 命名空间标记;座位优先级 `-1` 的宿主依据(宿主默认 0、最低者渲染、同优先级抛错)与三个兄弟插件依赖的锚点结构一并加进回归测试。
|
|
@@ -179,6 +180,7 @@ Queued-message previews, the composer itself and the goal panel stay plain text
|
|
|
179
180
|
|
|
180
181
|
### Fixes
|
|
181
182
|
|
|
183
|
+
- **0.1.4** — synthesizes the degradation paths into ONE function, as the reviewer asked: composition failure, a throwing host projection and the last-resort net each used to carry their own copy (one calling the host projection, one building the raw-text span inline), all kept in step by hand. There is now a single `degradedBody()`: it tries the host's own projection first — so reference chips and the host's own inline pieces survive — and only a projection that itself throws degrades to the raw text. Content first is now the function's contract: raw text is the last step, reached only when the host refuses to project. New regression: exactly one `projectUserText` call in the whole file, one raw-text span construction, and every degradation caller sharing the function (re-introducing a second copy turns it red).
|
|
182
184
|
- **0.1.3** — completes the "the seat render never throws" invariant: 0.1.2 guarded composition and the copy lookups, but shapes still threw in the seat's own render (an absent `node` -> `node.data`, absent props -> the destructuring itself, a `time` the Date constructor refuses — Symbol, BigInt, a `valueOf` that throws — and a host image renderer that throws). Each escaped to the slot machinery and retired the entry, the same symptom 0.1.2 fixed. Three layers now: one try around the seat body with defensive `node` / `data` access, per-call guards for the clock and the attachment card, and a last-resort `renderPlainRow` so the worst case is one row falling back to plain text with its action strip and the sibling anchors intact. Regression: 21 hostile host shapes, each asserted not to throw and to keep the row and its strip.
|
|
183
185
|
- **0.1.2** — the seat component can no longer be retired by a render-time exception: `composeUserMarkdown` / `buildMarkdownLabels` run while the seat renders, and a throw there is not caught by the error boundary (a boundary catches its descendants, not the render that creates it) — it escapes to the slot machinery, which drops the whole entry and hands the row back to the host, so the bubble reverts to raw source on screen. Composition and every copy lookup now degrade inside the seat: any host-shape mismatch (an unexpected node, an unrecognised projection result, a locale seat that is not callable) falls back to the host's plain projection while the bubble shell, the action strip and the sibling-plugin anchors stay intact. `data.content` that is not an array and an uncallable `t` are covered by regression tests.
|
|
184
186
|
- **0.1.1** — fixed whitespace-only plain runs between two reference chips being dropped as an empty Markdown block: adjacent chips no longer glue into `@a@b`, and a newline between two chips stays a line break the way the host's `pre-wrap` bubble shows it (the run renders as a `data-dshmb-gap` pre-wrap node, still never an empty Markdown block); the self-created action strip and copy button now carry the `data-dshmb-actions` / `data-dshmb-action` namespace markers; and the host evidence behind `priority: -1` (host default 0, lowest renders, same priority throws) plus the anchor structure the three sibling plugins rely on are covered by regression tests.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-markdown-bubble",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Markdown rendering for DeepSeek Harness sent user messages: every message bubble renders lists, code, tables, quotes and inline formatting through the host's own Markdown pipeline, while queues, previews and the composer keep their plain projection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/client.js
CHANGED
|
@@ -49,7 +49,7 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
const NS = 'dsh-markdown-bubble'
|
|
50
50
|
|
|
51
51
|
/** Keep in sync with package.json and src/index.js. */
|
|
52
|
-
const PLUGIN_VERSION = '0.1.
|
|
52
|
+
const PLUGIN_VERSION = '0.1.4'
|
|
53
53
|
|
|
54
54
|
/** Presence marker the live verifier reads. */
|
|
55
55
|
const DEBUG_KEY = '__DSH_MARKDOWN_BUBBLE__'
|
|
@@ -249,12 +249,32 @@ window.__ModuleLoader__.load({
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
/**
|
|
253
|
-
|
|
252
|
+
/**
|
|
253
|
+
* The ONE degraded projection: what this plugin shows whenever the markdown
|
|
254
|
+
* pipeline cannot be used.
|
|
255
|
+
*
|
|
256
|
+
* The host's own projection is tried first — it is the rendering the host
|
|
257
|
+
* would have used, so reference chips and inline pieces survive it — and a
|
|
258
|
+
* projection that throws degrades once more, to the raw text. Composition
|
|
259
|
+
* failure (see {@link safeMarkdownBody}), a boundary catch and the
|
|
260
|
+
* last-resort net (see {@link renderPlainRow}) ALL answer here, so the ways
|
|
261
|
+
* this plugin degrades cannot drift apart: there is no second copy of the
|
|
262
|
+
* fallback for someone to keep in step.
|
|
263
|
+
*
|
|
264
|
+
* Content comes first: the raw-text step is the last step, reached only when
|
|
265
|
+
* the host itself refuses to project.
|
|
266
|
+
*
|
|
267
|
+
* @param text - the message text.
|
|
268
|
+
* @param referenceLabels - session recall labels.
|
|
269
|
+
* @param skillNames - skill names.
|
|
270
|
+
* @param references - file/skill open callbacks.
|
|
271
|
+
* @returns the degraded body, never null and never a throw.
|
|
272
|
+
*/
|
|
273
|
+
function degradedBody(text, referenceLabels, skillNames, references) {
|
|
254
274
|
try {
|
|
255
275
|
return primitives.projectUserText(text, referenceLabels, skillNames, 'skill', references)
|
|
256
276
|
} catch (error) {
|
|
257
|
-
|
|
277
|
+
warnOnce('the host user-text projection failed; showing raw text', error)
|
|
258
278
|
return h('span', { className: 'dshmb-plain' }, text)
|
|
259
279
|
}
|
|
260
280
|
}
|
|
@@ -269,7 +289,7 @@ window.__ModuleLoader__.load({
|
|
|
269
289
|
* the row back to the host's raw renderer; on screen that is
|
|
270
290
|
* indistinguishable from "the plugin stopped working". A host-shape
|
|
271
291
|
* mismatch (an unexpected node shape, an unrecognised projection result)
|
|
272
|
-
* therefore falls back to
|
|
292
|
+
* therefore falls back to {@link degradedBody} right here, inside our own
|
|
273
293
|
* render, so the bubble keeps its shell, its actions and the sibling
|
|
274
294
|
* plugins' injection anchors.
|
|
275
295
|
* @param text - the message text.
|
|
@@ -284,7 +304,7 @@ window.__ModuleLoader__.load({
|
|
|
284
304
|
const labels = buildMarkdownLabels(t)
|
|
285
305
|
return composeUserMarkdown(text, labels, referenceLabels, skillNames, references)
|
|
286
306
|
} catch (error) {
|
|
287
|
-
|
|
307
|
+
warnOnce('markdown composition failed; showing the degraded projection', error)
|
|
288
308
|
return null
|
|
289
309
|
}
|
|
290
310
|
}
|
|
@@ -326,7 +346,9 @@ window.__ModuleLoader__.load({
|
|
|
326
346
|
*/
|
|
327
347
|
function composeUserMarkdown(text, labels, referenceLabels, skillNames, references) {
|
|
328
348
|
if (typeof text !== 'string' || text === '') return null
|
|
329
|
-
|
|
349
|
+
// The same single degraded projection, so a projection that throws inside
|
|
350
|
+
// composition degrades exactly as one that throws on the healthy path.
|
|
351
|
+
const projected = degradedBody(text, referenceLabels, skillNames, references)
|
|
330
352
|
const pieces =
|
|
331
353
|
react.isValidElement(projected) && projected.type === Fragment && Array.isArray(projected.props.children)
|
|
332
354
|
? projected.props.children
|
|
@@ -627,7 +649,7 @@ window.__ModuleLoader__.load({
|
|
|
627
649
|
return renderMarkdownBubble(props)
|
|
628
650
|
} catch (error) {
|
|
629
651
|
// Last-resort net. Everything that can throw is guarded where it is
|
|
630
|
-
// called (see safeMarkdownBody,
|
|
652
|
+
// called (see safeMarkdownBody, degradedBody, formatMessageClock,
|
|
631
653
|
// AttachmentEntry); this exists so an UNKNOWN host shape degrades one
|
|
632
654
|
// row to plain text instead of retiring the renderer. The fallback still
|
|
633
655
|
// renders the action strip, because the three sibling plugins anchor
|
|
@@ -659,8 +681,10 @@ window.__ModuleLoader__.load({
|
|
|
659
681
|
const showBubble = text !== '' || rest.length > 0
|
|
660
682
|
|
|
661
683
|
// Never let composition throw out of this render — see safeMarkdownBody.
|
|
684
|
+
// One degraded body per render, shared by the composition guard and the
|
|
685
|
+
// boundary's own fallback; the last-resort net calls it too.
|
|
662
686
|
const body = safeMarkdownBody(text, t, referenceLabels, skillNames, references)
|
|
663
|
-
const
|
|
687
|
+
const degraded = degradedBody(text, referenceLabels, skillNames, references)
|
|
664
688
|
|
|
665
689
|
// The action strip leads the row in DOM order and is pushed below the
|
|
666
690
|
// bubble by flex `order`. Sibling plugins pick their bar with
|
|
@@ -693,7 +717,7 @@ window.__ModuleLoader__.load({
|
|
|
693
717
|
h(
|
|
694
718
|
'div',
|
|
695
719
|
{ className: 'dshmb-bubble' },
|
|
696
|
-
h(MarkdownBoundary, { key: text, fallback }, body === null ?
|
|
720
|
+
h(MarkdownBoundary, { key: text, fallback: degraded }, body === null ? degraded : body),
|
|
697
721
|
rest.map((block, index) =>
|
|
698
722
|
h(primitives.JsonBlock, {
|
|
699
723
|
key: 'rest' + index,
|
|
@@ -725,19 +749,28 @@ window.__ModuleLoader__.load({
|
|
|
725
749
|
const safeProps = props !== null && typeof props === 'object' ? props : {}
|
|
726
750
|
const node = safeProps.node !== null && typeof safeProps.node === 'object' ? safeProps.node : {}
|
|
727
751
|
const data = node.data !== null && typeof node.data === 'object' ? node.data : {}
|
|
728
|
-
let
|
|
752
|
+
let parts
|
|
729
753
|
try {
|
|
730
|
-
|
|
754
|
+
parts = contentParts(Array.isArray(data.content) ? data.content : [])
|
|
731
755
|
} catch {
|
|
732
|
-
|
|
756
|
+
parts = { text: '', attachments: [], rest: [] }
|
|
733
757
|
}
|
|
758
|
+
const text = parts.text
|
|
759
|
+
// The same degraded projection the healthy path uses, so a row that
|
|
760
|
+
// degraded in one place looks exactly like a row that degraded in another.
|
|
761
|
+
const degraded = degradedBody(
|
|
762
|
+
text,
|
|
763
|
+
Array.isArray(data.referenceLabels) ? data.referenceLabels : [],
|
|
764
|
+
Array.isArray(data.skillNames) ? data.skillNames : [],
|
|
765
|
+
{ openFile: safeProps.openFile, openSkill: safeProps.openSkill },
|
|
766
|
+
)
|
|
734
767
|
return h(
|
|
735
768
|
'div',
|
|
736
769
|
{ className: 'dshmb-row', 'data-dshmb-row': '1', 'data-dshmb-degraded': '1' },
|
|
737
770
|
h(MessageActions, { text, time: data.time, t: safeProps.t }),
|
|
738
771
|
text === ''
|
|
739
772
|
? null
|
|
740
|
-
: h('div', { className: 'dshmb-stack' }, h('div', { className: 'dshmb-bubble' },
|
|
773
|
+
: h('div', { className: 'dshmb-stack' }, h('div', { className: 'dshmb-bubble' }, degraded)),
|
|
741
774
|
)
|
|
742
775
|
}
|
|
743
776
|
|
package/src/index.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
export const PLUGIN_ID = 'dsh-markdown-bubble'
|
|
16
16
|
|
|
17
17
|
/** Keep in sync with package.json and src/client.js. */
|
|
18
|
-
export const PLUGIN_VERSION = '0.1.
|
|
18
|
+
export const PLUGIN_VERSION = '0.1.4'
|
|
19
19
|
|
|
20
20
|
/** Cordis plugin name; the bundle patch resolves the package by this row. */
|
|
21
21
|
export const name = PLUGIN_ID
|