@ai-matrx/rich-content 0.2.162 → 0.2.163

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 0.2.162
3
+ ## 0.2.163
4
4
  ### Unreleased
5
5
 
6
6
  - **THE RENDER RATCHET** (`display/chat-markdown/block-registry/render-ratchet.ts`; Arman, 2026-10-14: "once you go forward, you do not go back"). `BlockRenderer` keeps, per mounted block, the furthest render stage it drew (text → loader → generic → component) and a backward frame mid-stream (a bridge declining a half-arrived item, a mid-string parse, an emptied/retracted frame, a late row) keeps the last good decision on screen instead of swapping the component for its loader and back (each swap was an unmount + remount). It lets go when the block settles (the final value is the truth), when a different block takes the slot, or mid-stream only after `RATCHET_RELEASE_MIN_FRAMES` (120) distinct frames AND `RATCHET_RELEASE_MIN_MS` (8 s). Consumer action: none.
@@ -302,6 +302,7 @@ const BlockRendererGated = ({
302
302
  const settledBlock = !isStreamActive || inputBlock.isStreamingBlock === false;
303
303
  const ratcheted = suppressLoadingGate ? { state: ratchetRef.current, decision: frameDecision } : advanceRatchet(ratchetRef.current, frameDecision, {
304
304
  settled: settledBlock,
305
+ messageEnded: !isStreamActive,
305
306
  now: Date.now()
306
307
  });
307
308
  ratchetRef.current = ratcheted.state;
@@ -64,13 +64,18 @@ export declare const EMPTY_RATCHET: RatchetState;
64
64
  export interface RatchetInput {
65
65
  /** The block settled: its own stream closed, or the message stream ended. */
66
66
  settled: boolean;
67
+ /**
68
+ * The MESSAGE stream is over. An emptied block while the message still streams is a
69
+ * retraction or a re-split, never a final value — even when its frame says "complete".
70
+ */
71
+ messageEnded?: boolean;
67
72
  /** Wall clock (ms). */
68
73
  now: number;
69
74
  }
70
75
  /**
71
76
  * One frame through the ratchet: the state to keep and the decision to draw.
72
77
  */
73
- export declare function advanceRatchet(state: RatchetState, next: BlockRenderDecision, { settled, now }: RatchetInput): {
78
+ export declare function advanceRatchet(state: RatchetState, next: BlockRenderDecision, { settled, now, messageEnded }: RatchetInput): {
74
79
  state: RatchetState;
75
80
  decision: BlockRenderDecision;
76
81
  };
@@ -39,17 +39,17 @@ function accept(next, stage, content) {
39
39
  disagreeSince: null
40
40
  };
41
41
  }
42
- function advanceRatchet(state, next, { settled, now }) {
42
+ function advanceRatchet(state, next, { settled, now, messageEnded = false }) {
43
43
  const stage = renderStageOf(next);
44
44
  const content = contentOf(next);
45
45
  const take = () => {
46
46
  const accepted = accept(next, stage, content);
47
47
  return { state: accepted, decision: next };
48
48
  };
49
- if (!state.held || settled) return take();
50
- if (!content.trim() && state.heldContent.trim()) {
49
+ if (state.held && !messageEnded && !content.trim() && state.heldContent.trim()) {
51
50
  return { state: { ...state, lastContent: content }, decision: state.held };
52
51
  }
52
+ if (!state.held || settled) return take();
53
53
  if (!continues(state.heldContent, content)) return take();
54
54
  if (stage >= state.heldStage) return take();
55
55
  const isNewFrame = content !== state.lastContent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/rich-content",
3
- "version": "0.2.162",
3
+ "version": "0.2.163",
4
4
  "description": "The AI Matrx formatted-text engine: the one markdown renderer, streaming markdown, the block splitter and renderer, the generic blocks, <RichContent> levels, the rich-document action registry and menus, copy, and the selection toolbar — as portable React.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -130,21 +130,21 @@
130
130
  "unist-util-visit": "^5.1.0",
131
131
  "unist-util-visit-parents": "^6.0.2",
132
132
  "yaml": "^2.9.1",
133
- "@ai-matrx/alchemy": "latest",
134
133
  "@ai-matrx/api": "latest",
135
134
  "@ai-matrx/canvas": "latest",
136
135
  "@ai-matrx/content-ir": "latest",
137
136
  "@ai-matrx/content-ir-react": "latest",
137
+ "@ai-matrx/agents": "latest",
138
+ "@ai-matrx/alchemy": "latest",
138
139
  "@ai-matrx/data": "latest",
139
140
  "@ai-matrx/design-system": "latest",
140
- "@ai-matrx/icons": "latest",
141
- "@ai-matrx/agents": "latest",
141
+ "@ai-matrx/diff": "latest",
142
142
  "@ai-matrx/media": "latest",
143
143
  "@ai-matrx/print": "latest",
144
144
  "@ai-matrx/records": "latest",
145
145
  "@ai-matrx/records-ui": "latest",
146
146
  "@ai-matrx/kit": "latest",
147
- "@ai-matrx/diff": "latest"
147
+ "@ai-matrx/icons": "latest"
148
148
  },
149
149
  "peerDependencies": {
150
150
  "react": ">=19.0.0",
@@ -205,5 +205,5 @@
205
205
  "verify:modules-tarball": "node ./scripts/verify-modules-tarball.mjs",
206
206
  "verify:tarball": "node ./scripts/verify-tarball.mjs"
207
207
  },
208
- "gitHead": "3260b81ccf1721d1a6a0ba08b238fdfa7d609ba5"
208
+ "gitHead": "bd8cc634aca9fe3503e752a2ae4167d8cbc68460"
209
209
  }