Merge PR #8101: fix(utils): mask routed lines beneath edge labels (@roboomp)
# Conflicts: # packages/utils/test/mermaid-ascii.test.ts
This commit is contained in:
@@ -21,6 +21,9 @@
|
|||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Reduced partial JSON parsing work for long streaming tool-call arguments by scaling the parse throttle geometrically with buffer size.
|
- Reduced partial JSON parsing work for long streaming tool-call arguments by scaling the parse throttle geometrically with buffer size.
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Mermaid ASCII multi-word edge labels allowing routed lines to show through spaces ([#8098](https://github.com/can1357/oh-my-pi/issues/8098)).
|
||||||
|
|
||||||
## [17.2.11] - 2026-08-07
|
## [17.2.11] - 2026-08-07
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import type { Canvas, DrawingCoord, RoleCanvas, CharRole, AsciiTheme, ColorMode } from './types'
|
import type { Canvas, DrawingCoord, RoleCanvas, CharRole, AsciiTheme, ColorMode } from './types'
|
||||||
import { colorizeLine, DEFAULT_ASCII_THEME } from './ansi'
|
import { colorizeLine, DEFAULT_ASCII_THEME } from './ansi'
|
||||||
import { displayWidth, toCells, WIDE_PAD } from '../text-metrics'
|
import { displayWidth, LABEL_SPACE, toCells, WIDE_PAD } from '../text-metrics'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a blank canvas filled with spaces.
|
* Create a blank canvas filled with spaces.
|
||||||
@@ -189,7 +189,7 @@ export function isJunctionChar(c: string): boolean {
|
|||||||
* letter/digit test misses.
|
* letter/digit test misses.
|
||||||
*/
|
*/
|
||||||
function isLabelChar(c: string): boolean {
|
function isLabelChar(c: string): boolean {
|
||||||
return c === WIDE_PAD || displayWidth(c) === 2 || /[\p{L}\p{N}]/u.test(c)
|
return c === LABEL_SPACE || c === WIDE_PAD || displayWidth(c) === 2 || /[\p{L}\p{N}]/u.test(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,7 +268,7 @@ export function mergeCanvases(
|
|||||||
for (let x = 0; x < overlay.length; x++) {
|
for (let x = 0; x < overlay.length; x++) {
|
||||||
for (let y = 0; y < overlay[0]!.length; y++) {
|
for (let y = 0; y < overlay[0]!.length; y++) {
|
||||||
const c = overlay[x]![y]!
|
const c = overlay[x]![y]!
|
||||||
// WIDE_PAD cells are written atomically with their lead below
|
// Spaces are transparent; WIDE_PAD cells are written atomically with their lead below
|
||||||
if (c === ' ' || c === WIDE_PAD) continue
|
if (c === ' ' || c === WIDE_PAD) continue
|
||||||
const mx = x + offset.x
|
const mx = x + offset.x
|
||||||
const my = y + offset.y
|
const my = y + offset.y
|
||||||
@@ -327,8 +327,8 @@ export function canvasToString(canvas: Canvas, options?: CanvasToStringOptions):
|
|||||||
let line = ''
|
let line = ''
|
||||||
for (let x = 0; x <= maxX; x++) {
|
for (let x = 0; x <= maxX; x++) {
|
||||||
const c = canvas[x]![y]!
|
const c = canvas[x]![y]!
|
||||||
// Skip wide-glyph continuation cells: the glyph itself spans 2 columns
|
// Skip wide-glyph continuation cells and restore opaque label spaces.
|
||||||
if (c !== WIDE_PAD) line += c
|
if (c !== WIDE_PAD) line += c === LABEL_SPACE ? ' ' : c
|
||||||
}
|
}
|
||||||
lines.push(line)
|
lines.push(line)
|
||||||
} else {
|
} else {
|
||||||
@@ -338,7 +338,7 @@ export function canvasToString(canvas: Canvas, options?: CanvasToStringOptions):
|
|||||||
for (let x = 0; x <= maxX; x++) {
|
for (let x = 0; x <= maxX; x++) {
|
||||||
const c = canvas[x]![y]!
|
const c = canvas[x]![y]!
|
||||||
if (c === WIDE_PAD) continue
|
if (c === WIDE_PAD) continue
|
||||||
chars.push(c)
|
chars.push(c === LABEL_SPACE ? ' ' : c)
|
||||||
roles.push(roleCanvas[x]?.[y] ?? null)
|
roles.push(roleCanvas[x]?.[y] ?? null)
|
||||||
}
|
}
|
||||||
lines.push(colorizeLine(chars, roles, theme, colorMode))
|
lines.push(colorizeLine(chars, roles, theme, colorMode))
|
||||||
|
|||||||
+2
-2
@@ -21,7 +21,7 @@ import { gridToDrawingCoord, lineToDrawing } from './grid'
|
|||||||
import { splitLines } from './multiline-utils'
|
import { splitLines } from './multiline-utils'
|
||||||
import { getCorners } from './shapes/corners'
|
import { getCorners } from './shapes/corners'
|
||||||
import { getShapeAttachmentPoint } from './shapes/index'
|
import { getShapeAttachmentPoint } from './shapes/index'
|
||||||
import { displayWidth, toCells, WIDE_PAD } from '../text-metrics'
|
import { displayWidth, LABEL_SPACE, toCells, WIDE_PAD } from '../text-metrics'
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Node drawing — renders a node using shape-aware rendering
|
// Node drawing — renders a node using shape-aware rendering
|
||||||
@@ -679,7 +679,7 @@ function drawTextOnLine(canvas: Canvas, line: DrawingCoord[], label: string, isU
|
|||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const lineText = lines[i]!
|
const lineText = lines[i]!
|
||||||
const startX = middleX - Math.floor(displayWidth(lineText) / 2)
|
const startX = middleX - Math.floor(displayWidth(lineText) / 2)
|
||||||
drawText(canvas, { x: startX, y: startY + i }, lineText)
|
drawText(canvas, { x: startX, y: startY + i }, lineText.replaceAll(' ', LABEL_SPACE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
*/
|
*/
|
||||||
export const WIDE_PAD = '\u0000'
|
export const WIDE_PAD = '\u0000'
|
||||||
|
|
||||||
|
/** Opaque label-space placeholder that serializes back to a regular space. */
|
||||||
|
export const LABEL_SPACE = '\u0001'
|
||||||
|
|
||||||
const graphemeSegmenter = new Intl.Segmenter()
|
const graphemeSegmenter = new Intl.Segmenter()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,6 +37,24 @@ describe("renderMermaidAscii", () => {
|
|||||||
expect(rows[markerRow + 1]).toMatch(/╰─+╯/);
|
expect(rows[markerRow + 1]).toMatch(/╰─+╯/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("masks routed lines behind spaces in edge labels", () => {
|
||||||
|
const horizontal = renderMermaidAscii(
|
||||||
|
["graph LR", " A[Agent] -->|on Mac| B[Server]", " A -->|on Linux| C[Cluster]"].join("\n"),
|
||||||
|
{ colorMode: "none", useAscii: false },
|
||||||
|
);
|
||||||
|
const vertical = renderMermaidAscii("graph TD\n A[Agent] -->|to c| C[Cluster]", {
|
||||||
|
colorMode: "none",
|
||||||
|
useAscii: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(horizontal).toContain("on Mac");
|
||||||
|
expect(horizontal).toContain("on Linux");
|
||||||
|
expect(horizontal).not.toContain("on─Mac");
|
||||||
|
expect(horizontal).not.toContain("on─Linux");
|
||||||
|
expect(vertical).toContain("to c");
|
||||||
|
expect(vertical).not.toContain("to│c");
|
||||||
|
});
|
||||||
|
|
||||||
it("returns a bounded fallback for declaration orders that make a clean route unreachable", () => {
|
it("returns a bounded fallback for declaration orders that make a clean route unreachable", () => {
|
||||||
const rendered = renderMermaidAsciiSafe(
|
const rendered = renderMermaidAsciiSafe(
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user