Skip to content

taOStalk s1: content_blocks types + renderContent dispatcher - #2154

Merged
jaylfc merged 1 commit into
devfrom
exec/tsk-xocdcd-2
Jul 27, 2026
Merged

taOStalk s1: content_blocks types + renderContent dispatcher#2154
jaylfc merged 1 commit into
devfrom
exec/tsk-xocdcd-2

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Autonomous build of board card tsk-xocdcd.

Add ContentBlock union (text, thinking, tool_call, status, unknown)
and content_blocks field to Message/MessageRow interfaces. Add a
dispatcher in renderContent() that switches on block.kind when
content_blocks is non-empty, falling through to the markdown path
otherwise. Ship the unknown-kind fallback (dim unsupported-block line)
as the slice-2 seam; known kind cases are stubs for separate cards.

Files:
desktop/src/apps/MessagesApp.tsx | 66 +++++++++++++++++++++-
desktop/src/apps/chat/MessageList.tsx | 4 +-
.../apps/chat/tests/render-helpers.test.tsx | 41 ++++++++++++++
3 files changed, 109 insertions(+), 2 deletions(-)

Summary by CodeRabbit

  • New Features
    • Added support for rendering structured message content blocks in chat messages.
    • Messages can now display multiple content blocks, including placeholders for unsupported block types.
    • Existing markdown and code formatting remains available when structured blocks are not provided.

Add ContentBlock union (text, thinking, tool_call, status, unknown)
and content_blocks field to Message/MessageRow interfaces. Add a
dispatcher in renderContent() that switches on block.kind when
content_blocks is non-empty, falling through to the markdown path
otherwise. Ship the unknown-kind fallback (dim unsupported-block line)
as the slice-2 seam; known kind cases are stubs for separate cards.
@gitar-bot

gitar-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ed8f153-e839-4af3-9333-f651c23bbac5

📥 Commits

Reviewing files that changed from the base of the PR and between c5b1a6f and 035d502.

📒 Files selected for processing (3)
  • desktop/src/apps/MessagesApp.tsx
  • desktop/src/apps/chat/MessageList.tsx
  • desktop/src/apps/chat/__tests__/render-helpers.test.tsx

📝 Walkthrough

Walkthrough

Structured content block types are added to messages, routed through renderContent, and passed from MessageList. Non-empty blocks currently render unsupported-block placeholders, while empty or absent blocks retain legacy markdown and fenced-code rendering.

Changes

Structured message rendering

Layer / File(s) Summary
Content contract and block renderer
desktop/src/apps/MessagesApp.tsx
Adds typed content block interfaces, extends Message with optional content_blocks, and dispatches non-empty blocks through renderContentBlock while preserving legacy rendering fallback.
MessageList integration and rendering coverage
desktop/src/apps/chat/MessageList.tsx, desktop/src/apps/chat/__tests__/render-helpers.test.tsx
Propagates structured blocks into message rendering and tests supported, unknown, multiple, empty, and fallback content cases.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MessageList
  participant renderContent
  participant renderContentBlock
  MessageList->>renderContent: pass message text and content_blocks
  renderContent->>renderContentBlock: dispatch each non-empty block
  renderContentBlock-->>renderContent: return unsupported block placeholder
Loading

Possibly related PRs

  • jaylfc/taOS#2077: Introduces related structured content_blocks rendering in the same message components.
  • jaylfc/taOS#2153: Shares the ContentBlock union, renderer dispatch, MessageList wiring, and tests.
  • jaylfc/taOS#1877: Refactors the MessageList structure used by these rendering changes.

Suggested reviewers: hognek

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Clearly summarizes the new content_blocks types and renderContent dispatch logic introduced in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exec/tsk-xocdcd-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add ContentBlock union and renderContent dispatcher for structured chat turns

✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add ContentBlock union and content_blocks field to message models.
• Dispatch rendering by block.kind when structured blocks are present.
• Add tests for dispatcher behavior and markdown fallback.
Diagram

graph TD
  A["MessageList"] --> B["renderContent(text, blocks)"] --> C["renderContentBlock dispatcher"] --> D["Fallback: unsupported block"]
  B --> E["Markdown rendering path"]
  F["Message/MessageRow content_blocks"] --> B
  G["ContentBlock union"] --> F

  subgraph Legend
    direction LR
    _ui["UI component"] ~~~ _fn["Function"] ~~~ _type["Type/Interface"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Implement real renderers for known kinds immediately
  • ➕ Avoids shipping a UI path that always shows 'unsupported block' for structured sessions
  • ➕ Ensures structured content is user-meaningful from day one
  • ➖ Bigger PR surface area and review scope
  • ➖ Higher risk of UI/UX churn while block schemas are still evolving
2. Renderer registry (kind -> component) instead of switch
  • ➕ Easier incremental addition of new block kinds without editing a central switch
  • ➕ Can enable plugin-like extensibility and simpler testing per kind
  • ➖ Slightly more abstraction than needed for a small number of kinds
  • ➖ Still needs a well-defined fallback and typing strategy for unknown kinds

Recommendation: Current approach (type union + dispatcher + unknown-kind fallback) is a good incremental seam: it preserves the existing markdown path and enables structured blocks without forcing all block UIs to land at once. When dedicated block components are introduced, consider adding per-kind cases (or a registry) with an explicit fallback for truly unknown kinds, and keep the markdown fallback only for legacy messages without content_blocks.

Files changed (3) +109 / -2

Enhancement (2) +68 / -2
MessagesApp.tsxIntroduce ContentBlock union and structured rendering dispatcher +65/-1

Introduce ContentBlock union and structured rendering dispatcher

• Adds a 'ContentBlock' discriminated union (text/thinking/tool_call/status + unknown) and introduces an optional 'content_blocks' field on 'Message'. Updates 'renderContent' to prefer 'content_blocks' when provided and route each block through a 'renderContentBlock' dispatcher, currently rendering an 'unsupported block' fallback for all kinds as an incremental seam.

desktop/src/apps/MessagesApp.tsx

MessageList.tsxThread content_blocks through MessageRow and into renderContent +3/-1

Thread content_blocks through MessageRow and into renderContent

• Extends 'MessageRow' with optional 'content_blocks' and passes it into 'renderContent(msg.content, msg.content_blocks)' during message rendering. Imports the 'ContentBlock' type for consistent typing across the app boundary.

desktop/src/apps/chat/MessageList.tsx

Tests (1) +41 / -0
render-helpers.test.tsxAdd tests for content_blocks dispatch and markdown fallback +41/-0

Add tests for content_blocks dispatch and markdown fallback

• Adds test coverage verifying that non-empty 'content_blocks' triggers the dispatcher, empty arrays fall back to markdown rendering, unknown kinds render the fallback line, and one fallback line is produced per block.

desktop/src/apps/chat/tests/render-helpers.test.tsx

@jaylfc

jaylfc commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

nemotron-super review

VERDICT: Pass
No blocking issues found

Automated first-pass review by the nemotron-super lane. The lead still reviews before merge.

@jaylfc

jaylfc commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-orB review

VERDICT: Ready with minor concerns — placeholder fallback renders for all known block kinds; catch-all type allows arbitrary keys.

  • MessagesApp.tsx:258-277 — renderContentBlock explicit cases for "text" | "thinking" | "tool_call" | "status" fall through to default, so all known kinds render "unsupported block" (intentional placeholder per comment, but easy to miss when implementing real renderers)
  • MessagesApp.tsx:247 — ContentBlock union includes { kind: string; [key: string]: unknown } catch-all; arbitrary keys accepted — validate/sanitize if blocks come from untrusted input (taOStalk sessions)
  • MessagesApp.tsx:279 — renderContent(text, content_blocks?) signature change is breaking for existing callers — verify no other call sites exist (grep shows only MessageList.tsx updated)
  • MessagesApp.tsx:212 — content_blocks?: ContentBlock[] added to Message interface but not marked readonly — consider readonly for consistency with other optional fields
  • render-helpers.test.tsx:77-93 — Tests verify fallback behavior but don't assert per-block keys (index-based key block-${index} could cause React key warnings if array reordered; acceptable for placeholder)
  • render-helpers.test.tsx:95-107 — "renders one fallback line per block" test uses regex on textContentbrittle; prefer querying rendered elements directly
  • MessageList.tsx:540 — Passes msg.content_blocks to renderContent but no null/undefined guard at call site (handled inside renderContent, but explicit check would be clearer)

Automated first-pass review by the nemotron-ultra-orB lane. The lead still reviews before merge.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 35 rules

Grey Divider


Action required

1. Blocks hide real content 🐞 Bug ≡ Correctness
Description
renderContent() takes the content_blocks path whenever it is non-empty, but
renderContentBlock() currently renders every kind (including "text") as an "unsupported block"
placeholder. Any message that arrives with populated content_blocks will therefore display
placeholders instead of its actual content.
Code

desktop/src/apps/MessagesApp.tsx[R266-284]

+function renderContentBlock(block: ContentBlock, index: number): React.ReactElement {
+  switch (block.kind) {
+    case "text":
+    case "thinking":
+    case "tool_call":
+    case "status":
+    default:
+      return (
+        <div key={`block-${index}`} className="text-shell-text-tertiary text-[12px]">
+          unsupported block: {block.kind}
+        </div>
+      );
+  }
+}
+
+export function renderContent(text: string, content_blocks?: ContentBlock[]) {
+  if (content_blocks && content_blocks.length > 0) {
+    return content_blocks.map((block, i) => renderContentBlock(block, i));
+  }
Relevance

⭐⭐ Medium

PR explicitly stubs known kinds to unsupported fallback; unclear if team will require real rendering
now.

PR-#303
PR-#266

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code short-circuits to content_blocks.map(...) when non-empty, and the dispatcher returns the
same unsupported placeholder for all kinds; MessageList now passes msg.content_blocks through,
and tests assert the placeholder output for known kinds.

desktop/src/apps/MessagesApp.tsx[259-305]
desktop/src/apps/chat/MessageList.tsx[526-544]
desktop/src/apps/chat/tests/render-helpers.test.tsx[61-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`renderContent()` prioritizes `content_blocks` when present, but the dispatcher currently returns the unsupported-block placeholder for all kinds (even known kinds like `text`). This makes structured messages unreadable.

### Issue Context
- `MessageList` now passes `msg.content_blocks` into `renderContent()`.
- Unit tests added in this PR lock in placeholder output for known kinds.

### Fix Focus Areas
- Implement minimal renderers for known kinds (at least `text` -> render `block.text` through existing markdown/inline pipeline), and reserve the placeholder only for truly unknown kinds.
- Alternatively, gate the `content_blocks` path: if a block kind is not supported yet, fall back to the legacy markdown rendering using the `text` argument.

#### References
- desktop/src/apps/MessagesApp.tsx[266-305]
- desktop/src/apps/chat/MessageList.tsx[526-544]
- desktop/src/apps/chat/__tests__/render-helpers.test.tsx[61-90]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Catch-all block overlaps kinds 🐞 Bug ⚙ Maintainability
Description
ContentBlock includes a { kind: string; ... } catch-all member that overlaps all known kind
literals, so malformed shapes like { kind: 'text' } can type-check as ContentBlock. This weakens
compile-time guarantees for future per-kind renderers and encourages scattered runtime validation.
Code

desktop/src/apps/MessagesApp.tsx[R192-197]

+export type ContentBlock =
+  | TextContentBlock
+  | ThinkingContentBlock
+  | ToolCallContentBlock
+  | StatusContentBlock
+  | { kind: string; [key: string]: unknown };
Relevance

⭐⭐ Medium

Catch-all may be intentional for unknown kinds; no close repo precedent on tightening discriminated
unions.

PR-#266
PR-#2122

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The union’s final member widens kind to string and allows any shape, overlapping known
discriminants; this reduces the value of strict TypeScript checking in this code path.

desktop/src/apps/MessagesApp.tsx[161-198]
desktop/tsconfig.json[1-20]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The current `ContentBlock` union ends with a broad member (`{ kind: string; [key: string]: unknown }`) that can also match known kinds. This undermines the union’s ability to enforce required fields for known blocks.

### Issue Context
The project is in `strict` mode, so preserving discriminated-union validation is valuable for future block-specific renderers.

### Fix Focus Areas
- Replace the overlapping catch-all with an explicit `UnknownContentBlock` that does not overlap known kinds, e.g. `{ kind: 'unknown'; raw_kind: string; raw: Record<string, unknown> }`.
- Convert/validate API data at the boundary into `ContentBlock` (known blocks) or `UnknownContentBlock` (fallback) so renderers can rely on required fields.

#### References
- desktop/src/apps/MessagesApp.tsx[161-198]
- desktop/tsconfig.json[1-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +266 to +284
function renderContentBlock(block: ContentBlock, index: number): React.ReactElement {
switch (block.kind) {
case "text":
case "thinking":
case "tool_call":
case "status":
default:
return (
<div key={`block-${index}`} className="text-shell-text-tertiary text-[12px]">
unsupported block: {block.kind}
</div>
);
}
}

export function renderContent(text: string, content_blocks?: ContentBlock[]) {
if (content_blocks && content_blocks.length > 0) {
return content_blocks.map((block, i) => renderContentBlock(block, i));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Blocks hide real content 🐞 Bug ≡ Correctness

renderContent() takes the content_blocks path whenever it is non-empty, but
renderContentBlock() currently renders every kind (including "text") as an "unsupported block"
placeholder. Any message that arrives with populated content_blocks will therefore display
placeholders instead of its actual content.
Agent Prompt
### Issue description
`renderContent()` prioritizes `content_blocks` when present, but the dispatcher currently returns the unsupported-block placeholder for all kinds (even known kinds like `text`). This makes structured messages unreadable.

### Issue Context
- `MessageList` now passes `msg.content_blocks` into `renderContent()`.
- Unit tests added in this PR lock in placeholder output for known kinds.

### Fix Focus Areas
- Implement minimal renderers for known kinds (at least `text` -> render `block.text` through existing markdown/inline pipeline), and reserve the placeholder only for truly unknown kinds.
- Alternatively, gate the `content_blocks` path: if a block kind is not supported yet, fall back to the legacy markdown rendering using the `text` argument.

#### References
- desktop/src/apps/MessagesApp.tsx[266-305]
- desktop/src/apps/chat/MessageList.tsx[526-544]
- desktop/src/apps/chat/__tests__/render-helpers.test.tsx[61-90]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +192 to +197
export type ContentBlock =
| TextContentBlock
| ThinkingContentBlock
| ToolCallContentBlock
| StatusContentBlock
| { kind: string; [key: string]: unknown };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Catch-all block overlaps kinds 🐞 Bug ⚙ Maintainability

ContentBlock includes a { kind: string; ... } catch-all member that overlaps all known kind
literals, so malformed shapes like { kind: 'text' } can type-check as ContentBlock. This weakens
compile-time guarantees for future per-kind renderers and encourages scattered runtime validation.
Agent Prompt
### Issue description
The current `ContentBlock` union ends with a broad member (`{ kind: string; [key: string]: unknown }`) that can also match known kinds. This undermines the union’s ability to enforce required fields for known blocks.

### Issue Context
The project is in `strict` mode, so preserving discriminated-union validation is valuable for future block-specific renderers.

### Fix Focus Areas
- Replace the overlapping catch-all with an explicit `UnknownContentBlock` that does not overlap known kinds, e.g. `{ kind: 'unknown'; raw_kind: string; raw: Record<string, unknown> }`.
- Convert/validate API data at the boundary into `ContentBlock` (known blocks) or `UnknownContentBlock` (fallback) so renderers can rely on required fields.

#### References
- desktop/src/apps/MessagesApp.tsx[161-198]
- desktop/tsconfig.json[1-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@jaylfc

jaylfc commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-kilo review

VERDICT: Acceptable with architectural debt — known content block kinds render as "unsupported block" placeholders per slice-2 seam design, but no TODO/FIXME marks the intentional deferral.

  • MessagesApp.tsx:236-255 — renderContentBlock switch lists all four known kinds (text, thinking, tool_call, status) but falls through to default for every one. The comment calls this the "slice-2 seam" but there's no // TODO: or // FIXME: to flag that dedicated block components are pending. Future maintainers may mistake this for a bug.

  • MessagesApp.tsx:205-206 — Message.content_blocks added alongside existing content_type with no documentation on how they relate (deprecation? coexistence? validation?). Consider a comment or JSDoc linking the two.

  • MessagesApp.tsx:197 — ContentBlock union includes catch-all { kind: string; [key: string]: unknown } but renderContentBlock only handles 4 kinds. Unknown kinds correctly hit fallback, but the type permits arbitrary keys without validation — could silently accept malformed blocks at runtime.

  • render-helpers.test.tsx:72-95 — Tests verify current fallback behavior for known kinds, which is correct for the seam, but no test asserts that dedicated components will replace these fallbacks (i.e., no snapshot or contract test to catch regressions when slice-2 lands).

  • MessageList.tsx:540 — renderContent(msg.content, msg.content_blocks) now returns React.ReactElement[] when blocks exist vs (string | React.ReactElement)[] for markdown path. JSX handles both, but the mixed return type isn't reflected in the function signature — consider narrowing or documenting.
    VERDICT: Acceptable with architectural debt — known content block kinds render as "unsupported block" placeholders per slice-2 seam design, but no TODO/FIXME marks the intentional deferral.

  • MessagesApp.tsx:236-255 — renderContentBlock switch lists all four known kinds (text, thinking, tool_call, status) but falls through to default for every one. The comment calls this the "slice-2 seam" but there's no // TODO: or // FIXME: to flag that dedicated block components are pending. Future maintainers may mistake this for a bug.

  • MessagesApp.tsx:205-206 — Message.content_blocks added alongside existing content_type with no documentation on how they relate (deprecation? coexistence? validation?). Consider a comment or JSDoc linking the two.

  • MessagesApp.tsx:197 — ContentBlock union includes catch-all { kind: string; [key: string]: unknown } but renderContentBlock only handles 4 kinds. Unknown kinds correctly hit fallback, but the type permits arbitrary keys without validation — could silently accept malformed blocks at runtime.

  • render-helpers.test.tsx:72-95 — Tests verify current fallback behavior for known kinds, which is correct for the seam, but no test asserts that dedicated components will replace these fallbacks (i.e., no snapshot or contract test to catch regressions when slice-2 lands).

  • MessageList.tsx:540 — renderContent(msg.content, msg.content_blocks) now returns React.ReactElement[] when blocks exist vs (string | React.ReactElement)[] for markdown path. JSX handles both, but the mixed return type isn't reflected in the function signature — consider narrowing or documenting.

Automated first-pass review by the nemotron-ultra-kilo lane. The lead still reviews before merge.

@jaylfc

jaylfc commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

Merging. Verified against the frozen content-blocks wire contract, not just the green tick: typed blocks (text/thinking/tool_call/status) plus the open {kind: string; [key]: unknown} for unknown kinds, and renderContentBlock switches on block.kind with a default case - so an unknown kind degrades to the fallback rather than throwing, which is exactly the contract requirement (taosmd adds kinds without a client release). The known cases currently fall through to the same fallback by design (this is the slice-1 seam; #2164 adds the dedicated renderers). Content is never lost because the message body is always the flattened plain-text per the contract. Has tests. Good foundation.

@jaylfc
jaylfc merged commit b5b2d75 into dev Jul 27, 2026
10 of 11 checks passed
hognek pushed a commit to hognek/tinyagentos that referenced this pull request Jul 29, 2026
…2154)

Add ContentBlock union (text, thinking, tool_call, status, unknown)
and content_blocks field to Message/MessageRow interfaces. Add a
dispatcher in renderContent() that switches on block.kind when
content_blocks is non-empty, falling through to the markdown path
otherwise. Ship the unknown-kind fallback (dim unsupported-block line)
as the slice-2 seam; known kind cases are stubs for separate cards.
hognek pushed a commit to hognek/tinyagentos that referenced this pull request Jul 30, 2026
…2154)

Add ContentBlock union (text, thinking, tool_call, status, unknown)
and content_blocks field to Message/MessageRow interfaces. Add a
dispatcher in renderContent() that switches on block.kind when
content_blocks is non-empty, falling through to the markdown path
otherwise. Ship the unknown-kind fallback (dim unsupported-block line)
as the slice-2 seam; known kind cases are stubs for separate cards.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant