Skip to content

feat(core,jsx): add ui.markdown widget (GFM subset) + streaming#415

Merged
RtlZeroMemory merged 4 commits into
mainfrom
feat/ui-markdown
Jun 11, 2026
Merged

feat(core,jsx): add ui.markdown widget (GFM subset) + streaming#415
RtlZeroMemory merged 4 commits into
mainfrom
feat/ui-markdown

Conversation

@RtlZeroMemory

@RtlZeroMemory RtlZeroMemory commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

Adds ui.markdown(source, props?) — an experimental-tier widget rendering a GitHub-Flavored-Markdown subset — plus createMarkdownStream() for append-only sources, completing the roadmap item.

Architecture: composition factory. A zero-dependency parser produces a frozen MarkdownDocument AST; the renderer maps it onto existing widgets (text, richText, box, row, column, divider) — no new VNode kind, no new layout/render handlers, so proven layout/theming/renderer behavior is inherited.

Supported

  • Blocks: ATX headings, paragraphs (soft joins + hard breaks), fenced/indented code with CodeEditor-tokenizer monochrome syntax emphasis, blockquotes (GitHub-style dim left bar), nested ordered/unordered/task lists, thematic breaks, pipe tables with :-/:-:/-: alignment
  • Inlines: **strong**, *em*, ~~del~~, code spans, links/autolinks/bare URLs (trailing punctuation trimmed), backslash escapes, basic entities
  • Raw HTML renders as literal text; divergences from full GFM are documented on the widget page

Streaming (createMarkdownStream)

Under append-only input, every top-level block except the last is immutable. The stream:

  • re-parses only the volatile tail block per append → appends are O(tail), not O(document)
  • caches completed blocks and their rendered VNodes with referential identity (incl. the block that just stopped being last, restored via structural comparison) → reconciliation + layout stability signatures skip untouched subtrees
  • guarantees document() deep-equals parseMarkdown(source()) at every point — chunk boundaries (split CRLF pairs, mid-word, mid-line) never change the result, pinned by fuzz

Safety (untrusted input: PR bodies, agent output)

  • Parsing never throws; malformed constructs degrade to literal text
  • Nesting depth bounded; inline scanning runs on a work budget with closer memoization — adversarial input (*/[/backtick floods) cannot trigger quadratic scans
  • Fuzz suites: random input, structured docs, pathological corpus, unicode soup, random chunked appends with interleaved queries/resets

API

  • ui.markdown(source, { blockGap?, key? }) · JSX <Markdown source> (intrinsic + component, pinned by the ui-parity tests)
  • createMarkdownStream(options?)append/reset/source/document/vnode
  • parseMarkdown / renderMarkdown / renderMarkdownBlock + frozen AST types

Docs

Widget page (usage, props, supported-syntax table, divergences, Streaming section), catalog + stability rows (experimental), mkdocs nav, CHANGELOG.

Framework fixes (from live-PTY verification)

  • Per-side box borders render standalone edgesborderLeft alone previously drew nothing on boxes shorter than 2 rows (corner-row guard); corner rows are now required only when a horizontal edge is present. Blockquotes now render the intended GitHub-style dim left bar.
  • app.ready() — resolves once start()/run() finishes starting and update() is accepted; removes the startup race for timers/sockets/streams feeding state while run() blocks (found live: early update() throws ZRUI_INVALID_STATE). Rejects on startup failure or dispose-before-start.
  • Investigated, deliberately deferred: vertical-divider height hugging needs a stretch-semantics redesign (stretch reports the loose cross limit at measure time) — out of scope; documented.

Validation

  • npm run test:packages: 4,997 pass / 0 fail
  • New tests: 53 markdown (parser units, rendered frames, fuzz) + 25 streaming (units incl. CRLF-split/identity/caching, chunk-equivalence fuzz) + 6 per-side border + 5 app.ready() lifecycle
  • lint clean · typecheck + typecheck:strict clean · quality:api regenerated (core + jsx) · quality:deps no new violations · quality:guardrails OK · docs:build OK

🤖 Generated with Claude Code

Adds ui.markdown(source, props?) as an experimental-tier composition
widget: a zero-dependency, bounded GFM-subset parser plus a renderer
that maps the AST onto existing widgets, so layout, theming, and
renderer behavior are inherited rather than reimplemented.

Blocks: ATX headings, paragraphs (soft joins, hard breaks), fenced and
indented code with CodeEditor-tokenizer monochrome syntax emphasis,
blockquotes, nested ordered/unordered/task lists, thematic breaks, and
pipe tables with alignment. Inlines: strong/em/del, code spans, links,
autolinks, bare URLs with punctuation trimming, escapes, and basic
entities. Raw HTML renders as literal text.

Safety: parsing never throws, nesting depth is bounded, and inline
scanning runs on a work budget with closer memoization so adversarial
input (PR bodies, agent output) cannot trigger quadratic scans.
parseMarkdown/renderMarkdown and the frozen MarkdownDocument AST are
exported for caller-side caching of streamed content; styling is
attribute-only so output adapts to every theme.

JSX exposes <Markdown source>, registered in the intrinsic factory map
and pinned by the existing ui parity tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Experimental Markdown widget + JSX component (GFM subset) with configurable block spacing
    • Streaming markdown support for append-only sources with incremental updates and cached rendered blocks
    • Public parse/render/stream APIs for programmatic use
    • App lifecycle: new ready() promise that resolves once startup completes and updates are accepted
  • Documentation

    • Comprehensive docs, examples, roadmap and changelog updates
  • Tests

    • Extensive parser, streaming and renderer test suites, including fuzz tests
  • Bug Fixes

    • Fixed per-side border rendering affecting blockquote visuals

Walkthrough

Adds an experimental Markdown feature: frozen AST types, a bounded deterministic parser, an AST→VNode renderer, createMarkdownStream incremental streaming with vnode caching, ui.markdown helper, JSX/intrinsic support, tests (unit/fuzz/integration), and user docs/changelog/navigation updates.

Changes

Markdown Widget Core Implementation and Wiring

Layer / File(s) Summary
Markdown AST Type Contracts
packages/core/src/widgets/markdown/ast.ts
MarkdownInline, MarkdownBlock, MarkdownListItem, and MarkdownDocument types define immutable AST node shapes for inline and block constructs and the document wrapper.
Markdown Parser with Safety Guarantees
packages/core/src/widgets/markdown/parse.ts
Dependency-free, deterministic, never-throwing parser for a bounded GFM subset (headings, paragraphs, fenced/indented code, blockquotes, nested lists/task items, thematic breaks, pipe tables, links, autolinks, bare URLs, emphasis/strike/code, escapes); results are deep-frozen.
Markdown AST Renderer to Widget Tree
packages/core/src/widgets/markdown/render.ts
Converts MarkdownDocument to VNode trees: inline styling, word-wrapping, headings, tokenized code blocks, blockquotes, tight nested lists, horizontal rules, and alignment-aware tables; exports renderMarkdown and renderMarkdownBlock.
Incremental Streaming and VNode Caching
packages/core/src/widgets/markdown/stream.ts
Implements createMarkdownStream with append/reset, CRLF-safe chunking, stable+tail incremental reparse, tail-object reuse via JSON-equality, and WeakMap vnode caching for referential reuse of completed blocks.
Public Module Surface & Helper
packages/core/src/widgets/markdown/index.ts, packages/core/src/index.ts
Re-exports AST types and parse/render/stream APIs, adds markdown(source, props?) helper combining parse+render; exports MarkdownProps and markdown functions from core index.
Props and Widget Type Re-exports
packages/core/src/widgets/types/base.ts, packages/core/src/widgets/types.ts
Defines MarkdownProps (key?, source, blockGap?) and re-exports it through the widget types barrel.
Core UI Facade & Core Exports
packages/core/src/widgets/ui.ts, packages/core/src/index.ts
Imports and exposes markdown via the ui facade and core public exports.
JSX Component and Intrinsic Element Support
packages/jsx/src/components.ts, packages/jsx/src/types.ts, packages/jsx/src/createElement.ts, packages/jsx/src/index.ts
Adds Markdown JSX wrapper, MarkdownJsxProps alias, registers markdown intrinsic, and re-exports component/types through JSX runtime.
Integration, Unit, and Fuzz Tests
packages/core/src/widgets/__tests__/markdown.test.ts, packages/core/src/widgets/markdown/__tests__/parse.test.ts, packages/core/src/widgets/markdown/__tests__/parse.fuzz.test.ts, packages/core/src/widgets/markdown/__tests__/stream.test.ts, packages/core/src/widgets/markdown/__tests__/stream.fuzz.test.ts
Adds kitchen-sink integration tests, comprehensive parser unit tests, parser and stream fuzz tests, and streaming behavior tests covering parity, vnode caching, chunk-boundary determinism, reset semantics, and deep-freeze assertions.
Documentation, Changelog, Roadmap, and API Reports
docs/widgets/markdown.md, docs/widgets/index.md, docs/widgets/stability.md, mkdocs.yml, CHANGELOG.md, ROADMAP.md, packages/core/etc/core.api.md, packages/jsx/etc/jsx.api.md
Adds user-facing widget docs (supported subset, props, streaming notes, GFM deviations), registers as experimental, updates navigation, changelog, roadmap, and generated API reports.
App readiness lifecycle
packages/core/src/app/createApp.ts, packages/core/src/app/types.ts, packages/core/src/app/__tests__/appReady.test.ts, packages/core/src/app/inspectorOverlayHelper.ts
Adds app.ready() lifecycle method, implements ready waiters/settlement, updates start/dispose flows to settle waiters, adds tests and inspector overlay passthrough.
Box border rendering fix & tests
packages/core/src/renderer/renderToDrawlist/boxBorder.ts, packages/core/src/widgets/__tests__/boxBorderSides.test.ts
Refines per-side border minimum-size checks and adds tests verifying left/right/top/full border rendering edge cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • RtlZeroMemory/Rezi#203: Both PRs modify border rendering logic in packages/core/src/renderer/renderToDrawlist/boxBorder.ts (including per-side border/sizing behavior and related box-border tests).

Poem

🐰 I nibble lines and stitch them true,
Freeze each block and render through,
Streams that mend a moving tail,
Widgets hum and caches hail,
A rabbit cheers — small patch, big view.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding a markdown widget and streaming support, matching the core feature additions in the changeset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the markdown widget implementation, streaming API, supported syntax, safety considerations, and framework fixes.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/ui-markdown

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


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

Streams agent transcripts and live logs cheaply: under append-only
input every top-level block except the last is immutable, so the
stream re-parses only the volatile tail block per append and caches
completed blocks plus their rendered VNodes. Completed blocks keep
referential identity (including the block that just stopped being
last, restored via structural comparison), so reconciliation and
layout stability signatures skip untouched subtrees and appends stay
O(tail) instead of O(document).

Chunk boundaries never change the result: CRLF pairs split across
appends are carried, NUL bytes are sanitized per chunk, and fuzz
tests pin document() to deep-equal parseMarkdown(source()) under
random chunking, interleaved queries, resets, and unicode soup.

Exports createMarkdownStream/MarkdownStream/renderMarkdownBlock from
core; parseMarkdownLines stays module-internal with per-block start
tracking in the block parser.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@RtlZeroMemory RtlZeroMemory changed the title feat(core,jsx): add ui.markdown widget (GFM subset) feat(core,jsx): add ui.markdown widget (GFM subset) + streaming Jun 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/widgets/types/base.ts (1)

402-408: ⚡ Quick win

Use spacing-scale values for blockGap in MarkdownProps.

Line 407 exposes spacing as a raw number; this diverges from the spacing-key contract and from existing spacing prop patterns in this file.

♻️ Proposed API-aligned type update
 export type MarkdownProps = Readonly<{
   key?: string;
   /** Markdown source text (GitHub-Flavored-Markdown subset). */
   source: string;
   /** Vertical spacing rows between top-level blocks. Defaults to 1. */
-  blockGap?: number;
+  blockGap?: SpacingValue;
 }>;

As per coding guidelines, “Spacing values use spacing keys such as sm, md, lg rather than arbitrary values.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/widgets/types/base.ts` around lines 402 - 408, The
MarkdownProps type exposes blockGap as a raw number which breaks the project's
spacing-key contract; update MarkdownProps to use the shared spacing key type
(e.g., the project's Spacing or SpacingKey union/type) for blockGap instead of
number and adjust any related consumers (renderers or defaultProps) to
accept/resolve spacing keys via the project's spacing resolver utility;
specifically change the blockGap declaration inside MarkdownProps and ensure
places referencing MarkdownProps (render functions or default values) convert
that spacing key to an actual numeric gap using the existing spacing resolver
function.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 11: Update the CHANGELOG entry to avoid implying a built-in streaming
mode: edit the sentence that currently reads "for caller-side caching
(streaming)" to a clarifying phrase such as "for caller-side caching
(streaming-friendly)" or "for caller-side caching; streaming mode planned" so it
accurately reflects that parseMarkdown/renderMarkdown and the frozen
MarkdownDocument AST types support caller-side caching but do not provide a
built-in streaming mode; ensure the note appears alongside references to
ui.markdown and the exported symbols parseMarkdown, renderMarkdown, and
MarkdownDocument.

---

Nitpick comments:
In `@packages/core/src/widgets/types/base.ts`:
- Around line 402-408: The MarkdownProps type exposes blockGap as a raw number
which breaks the project's spacing-key contract; update MarkdownProps to use the
shared spacing key type (e.g., the project's Spacing or SpacingKey union/type)
for blockGap instead of number and adjust any related consumers (renderers or
defaultProps) to accept/resolve spacing keys via the project's spacing resolver
utility; specifically change the blockGap declaration inside MarkdownProps and
ensure places referencing MarkdownProps (render functions or default values)
convert that spacing key to an actual numeric gap using the existing spacing
resolver function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 71c7f238-c547-480e-aa8a-ad750c7202ce

📥 Commits

Reviewing files that changed from the base of the PR and between 41fc42e and 12eecd6.

📒 Files selected for processing (23)
  • CHANGELOG.md
  • ROADMAP.md
  • docs/widgets/index.md
  • docs/widgets/markdown.md
  • docs/widgets/stability.md
  • mkdocs.yml
  • packages/core/etc/core.api.md
  • packages/core/src/index.ts
  • packages/core/src/widgets/__tests__/markdown.test.ts
  • packages/core/src/widgets/markdown/__tests__/parse.fuzz.test.ts
  • packages/core/src/widgets/markdown/__tests__/parse.test.ts
  • packages/core/src/widgets/markdown/ast.ts
  • packages/core/src/widgets/markdown/index.ts
  • packages/core/src/widgets/markdown/parse.ts
  • packages/core/src/widgets/markdown/render.ts
  • packages/core/src/widgets/types.ts
  • packages/core/src/widgets/types/base.ts
  • packages/core/src/widgets/ui.ts
  • packages/jsx/etc/jsx.api.md
  • packages/jsx/src/components.ts
  • packages/jsx/src/createElement.ts
  • packages/jsx/src/index.ts
  • packages/jsx/src/types.ts

Comment thread CHANGELOG.md Outdated
The pre-push lint run piped output through tail, which masked a
non-zero exit; CI caught the two unformatted files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/core/src/widgets/markdown/stream.ts (1)

31-42: ⚡ Quick win

Use an interface for the public MarkdownStream shape.

This exported object-shape type is a Readonly<{ ... }> alias today. The repo standard for TypeScript object shapes is interface, so this public API should follow that convention for consistency.

♻️ Proposed refactor
-export type MarkdownStream = Readonly<{
-  /** Appends a source chunk. Chunks may split lines, CRLF pairs, or words. */
-  append: (chunk: string) => void;
-  /** Clears the buffer; optionally replaces it with new source. */
-  reset: (source?: string) => void;
-  /** Full (normalized) source buffered so far. */
-  source: () => string;
-  /** Parsed document; stable blocks keep referential identity across appends. */
-  document: () => MarkdownDocument;
-  /** Rendered document; stable blocks reuse cached VNodes across appends. */
-  vnode: () => VNode;
-}>;
+export interface MarkdownStream {
+  /** Appends a source chunk. Chunks may split lines, CRLF pairs, or words. */
+  readonly append: (chunk: string) => void;
+  /** Clears the buffer; optionally replaces it with new source. */
+  readonly reset: (source?: string) => void;
+  /** Full (normalized) source buffered so far. */
+  readonly source: () => string;
+  /** Parsed document; stable blocks keep referential identity across appends. */
+  readonly document: () => MarkdownDocument;
+  /** Rendered document; stable blocks reuse cached VNodes across appends. */
+  readonly vnode: () => VNode;
+}

As per coding guidelines, "Prefer interface for defining object shapes in TypeScript files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/widgets/markdown/stream.ts` around lines 31 - 42, Replace
the exported Readonly type alias for MarkdownStream with an exported interface
named MarkdownStream that declares the same members (append, reset, source,
document, vnode) as readonly method properties; locate the current symbol
MarkdownStream and update its definition so the public API uses "interface
MarkdownStream" (preserving parameter and return types for append, reset,
source, document, and vnode) to conform to the repo convention preferring
interfaces for object shapes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/widgets/markdown/__tests__/stream.test.ts`:
- Around line 29-31: The one-line chained call in renderText should be split
across multiple lines to satisfy Biome: call createTestRenderer({ viewport: {
cols: 44, rows: 36 } }) on its own line, then call .render(vnode) on the next
line, and then call .toText() on the following line; update the function
renderText accordingly so the chain is formatted across multiple lines while
keeping the same arguments and return value.

In `@packages/core/src/widgets/markdown/stream.ts`:
- Around line 78-82: The normalization chain assigned to variable normalized
spans multiple lines and fails Biome's formatting rule; collapse the chained
string operations into a single-line expression (keep the same operations:
replace CRLF to LF, replace CR to LF, split by NUL and join with "�") so the
assignment to normalized uses one line and retains the same behavior (refer to
the normalized variable, text input, and NUL constant in stream.ts).
- Around line 129-130: The source() function currently appends a raw "\r" when
pendingCR is true which breaks normalization across CRLF boundaries; update
source() (which uses variables lines and pendingCR) to append a normalized
"\r\n" instead of "\r" so the returned buffered source always represents CRLF as
"\r\n" (i.e., change the return from lines.join("\n") + (pendingCR ? "\r" : "")
to append "\r\n" when pendingCR is true).

---

Nitpick comments:
In `@packages/core/src/widgets/markdown/stream.ts`:
- Around line 31-42: Replace the exported Readonly type alias for MarkdownStream
with an exported interface named MarkdownStream that declares the same members
(append, reset, source, document, vnode) as readonly method properties; locate
the current symbol MarkdownStream and update its definition so the public API
uses "interface MarkdownStream" (preserving parameter and return types for
append, reset, source, document, and vnode) to conform to the repo convention
preferring interfaces for object shapes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b41ff6c-66d9-4724-a657-e99b398286b0

📥 Commits

Reviewing files that changed from the base of the PR and between 12eecd6 and f9bc84f.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • ROADMAP.md
  • docs/widgets/markdown.md
  • packages/core/etc/core.api.md
  • packages/core/src/index.ts
  • packages/core/src/widgets/markdown/__tests__/stream.fuzz.test.ts
  • packages/core/src/widgets/markdown/__tests__/stream.test.ts
  • packages/core/src/widgets/markdown/index.ts
  • packages/core/src/widgets/markdown/parse.ts
  • packages/core/src/widgets/markdown/render.ts
  • packages/core/src/widgets/markdown/stream.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/widgets/markdown.md
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/widgets/markdown/index.ts
  • packages/core/src/widgets/markdown/parse.ts
  • packages/core/src/widgets/markdown/render.ts

Comment thread packages/core/src/widgets/markdown/__tests__/stream.test.ts
Comment thread packages/core/src/widgets/markdown/stream.ts Outdated
Comment on lines +129 to +130
function source(): string {
return lines.join("\n") + (pendingCR ? "\r" : "");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize the pending trailing CR in source().

When the last appended chunk ends with a bare \r, source() returns that raw carriage return even though this API is documented as the normalized buffered source. That makes source() observably inconsistent on a real CRLF-split boundary.

🐛 Proposed fix
 function source(): string {
-  return lines.join("\n") + (pendingCR ? "\r" : "");
+  return lines.join("\n") + (pendingCR ? "\n" : "");
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/widgets/markdown/stream.ts` around lines 129 - 130, The
source() function currently appends a raw "\r" when pendingCR is true which
breaks normalization across CRLF boundaries; update source() (which uses
variables lines and pendingCR) to append a normalized "\r\n" instead of "\r" so
the returned buffered source always represents CRLF as "\r\n" (i.e., change the
return from lines.join("\n") + (pendingCR ? "\r" : "") to append "\r\n" when
pendingCR is true).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/core/src/widgets/markdown/__tests__/stream.test.ts (1)

98-101: ⚡ Quick win

Consider adding defensive assertions for the children array.

The test assumes children exists and has at least 3 elements. While the optional chaining ?. prevents crashes, if children is undefined or has fewer elements, the assertions would pass with undefined === undefined, missing the actual cache validation.

🛡️ Proposed defensive assertions
 assert.ok(first.kind === "column" && second.kind === "column");
+assert.ok(first.children !== undefined && first.children.length === 3);
+assert.ok(second.children !== undefined && second.children.length === 3);
 assert.equal(second.children?.[0], first.children?.[0], "heading vnode cached");
 assert.equal(second.children?.[1], first.children?.[1], "paragraph vnode cached");
 assert.notEqual(second.children?.[2], first.children?.[2], "tail re-rendered");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/widgets/markdown/__tests__/stream.test.ts` around lines 98
- 101, The test assumes first.children and second.children exist and have at
least 3 entries but only uses optional chaining, which can produce false
positives; add defensive assertions that first.children and second.children are
defined and that their .length is >= 3 before the equality checks, then assert
the individual elements (first.children[0], first.children[1],
first.children[2], and corresponding second.children indexes) are not undefined
where appropriate, and keep the existing comparisons (first.kind/second.kind and
element equality/inequality) after those guards to ensure the cache validation
is meaningful.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/widgets/markdown/__tests__/stream.test.ts`:
- Around line 98-101: The test assumes first.children and second.children exist
and have at least 3 entries but only uses optional chaining, which can produce
false positives; add defensive assertions that first.children and
second.children are defined and that their .length is >= 3 before the equality
checks, then assert the individual elements (first.children[0],
first.children[1], first.children[2], and corresponding second.children indexes)
are not undefined where appropriate, and keep the existing comparisons
(first.kind/second.kind and element equality/inequality) after those guards to
ensure the cache validation is meaningful.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 29520fda-c053-4e52-8f74-a16d38ec4146

📥 Commits

Reviewing files that changed from the base of the PR and between f9bc84f and 88140a1.

📒 Files selected for processing (2)
  • packages/core/src/widgets/markdown/__tests__/stream.test.ts
  • packages/core/src/widgets/markdown/stream.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/widgets/markdown/stream.ts

Per-side borders: a box with only vertical edges enabled (for example
borderLeft alone) previously rendered nothing when shorter than two
rows, because the minimum-size guard always required corner rows.
Corner rows are now required only when a horizontal edge is present,
so left/right bars render from h >= 1. Markdown blockquotes use this
to render a GitHub-style dim left bar instead of a rounded box.

app.ready(): resolves once start()/run() has finished starting and
the app accepts update() calls — removes the startup race for timers,
sockets, and streams that feed state while run() blocks (calling
update() during startup throws ZRUI_INVALID_STATE, found while
verifying the markdown stream in a live PTY). Resolves immediately
when already running; rejects on startup failure or dispose before
start. Wired through the inspector overlay app wrapper.

Vertical-divider height hugging was investigated and deliberately not
changed: stack stretch reports the loose cross limit at measure time,
so hugging requires a stretch-semantics redesign out of scope here;
the per-side border fix covers the bar use cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/core/src/widgets/__tests__/boxBorderSides.test.ts (1)

10-106: ⚡ Quick win

Test coverage validates per-side border behavior.

The test suite effectively validates the new vertical-only h=1 capability and provides regression coverage for horizontal-only and full borders. All tests follow behavior-first design by asserting on rendered text output.

📋 Optional: Add test for top+bottom h=1 edge case

Consider adding a test case for top=true, bottom=true, left=false, right=false at h=1 to document the intended behavior for the overlap scenario flagged in the boxBorder.ts review:

test("top and bottom without vertical edges at h=1", () => {
  const lines = render(
    ui.box(
      {
        border: "single",
        borderTop: true,
        borderRight: false,
        borderBottom: false,
        borderLeft: false,
        width: 6,
        height: 1,
      },
      [],
    ),
  );
  // Assert expected behavior (currently unclear whether this should render both edges overlapping or be prevented)
});

This would clarify the contract for reviewers and future maintainers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/widgets/__tests__/boxBorderSides.test.ts` around lines 10 -
106, Add a test case named "top and bottom without vertical edges at h=1" to the
existing boxBorderSides.test suite that uses ui.box(border: "single", borderTop:
true, borderBottom: true, borderLeft: false, borderRight: false, width: 6,
height: 1) with empty content and calls render(ui.box(...)); assert the rendered
output documents the current behavior (e.g., a single-line horizontal rule at
h=1 — check lines.length === 1 and that lines[0] startsWith the horizontal
glyphs like "──" and does not contain vertical bar "│") so reviewers and future
maintainers have an explicit test for the top+bottom overlap case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/widgets/__tests__/boxBorderSides.test.ts`:
- Around line 10-106: Add a test case named "top and bottom without vertical
edges at h=1" to the existing boxBorderSides.test suite that uses ui.box(border:
"single", borderTop: true, borderBottom: true, borderLeft: false, borderRight:
false, width: 6, height: 1) with empty content and calls render(ui.box(...));
assert the rendered output documents the current behavior (e.g., a single-line
horizontal rule at h=1 — check lines.length === 1 and that lines[0] startsWith
the horizontal glyphs like "──" and does not contain vertical bar "│") so
reviewers and future maintainers have an explicit test for the top+bottom
overlap case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 81ea0028-f7b4-4090-a3d3-3dca7328c4ab

📥 Commits

Reviewing files that changed from the base of the PR and between 88140a1 and 95f1873.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • docs/guide/lifecycle-and-updates.md
  • docs/widgets/markdown.md
  • packages/core/etc/core.api.md
  • packages/core/src/app/__tests__/appReady.test.ts
  • packages/core/src/app/createApp.ts
  • packages/core/src/app/inspectorOverlayHelper.ts
  • packages/core/src/app/types.ts
  • packages/core/src/renderer/renderToDrawlist/boxBorder.ts
  • packages/core/src/widgets/__tests__/boxBorderSides.test.ts
  • packages/core/src/widgets/__tests__/markdown.test.ts
  • packages/core/src/widgets/markdown/render.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/guide/lifecycle-and-updates.md
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/core/src/widgets/tests/markdown.test.ts
  • docs/widgets/markdown.md
  • packages/core/etc/core.api.md
  • packages/core/src/widgets/markdown/render.ts

@RtlZeroMemory
RtlZeroMemory merged commit 8be5f04 into main Jun 11, 2026
34 checks passed
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