@zenodinh/pi-render 0.1.9 → 0.1.10

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/package.json CHANGED
@@ -67,5 +67,5 @@
67
67
  "typescript": "^7.0.2",
68
68
  "vitest": "^5.0.3"
69
69
  },
70
- "version": "0.1.9"
70
+ "version": "0.1.10"
71
71
  }
package/src/core/paint.ts CHANGED
@@ -75,7 +75,8 @@ export function createRowPaint(theme: HostTheme): RowPaint {
75
75
  * Region 2 — the content factory. `rule` degrades hr → quoteBorder → plain; every role ends in plain.
76
76
  *
77
77
  * The markdown theme's own closures supply the ink: `code` (`mdCode`) under `bold` for the table header,
78
- * `heading` (`mdHeading`, the accent purple) for the table body's cells, `bold`/`italic` for spans.
78
+ * `heading` (`mdHeading`, the accent purple) for a body cell's code-span highlight, `bold`/`italic` for
79
+ * spans.
79
80
  */
80
81
  // shape: closure returning an object literal — trigger #4, eight stateless content roles over the
81
82
  // captured markdown theme.
@@ -94,6 +95,6 @@ export function createContentPaint(mdTheme: MarkdownTheme): ContentPaint {
94
95
  strong: (text) => tryStyle(mdTheme.bold, text) ?? text,
95
96
  em: (text) => tryStyle(mdTheme.italic, text) ?? text,
96
97
  header,
97
- cell: (text) => tryStyle(mdTheme.heading, text) ?? text,
98
+ cellHighlight: (text) => tryStyle(mdTheme.heading, text) ?? text,
98
99
  };
99
100
  }
@@ -69,8 +69,9 @@ export interface ContentPaint {
69
69
  */
70
70
  header(text: string): string;
71
71
  /**
72
- * One table body cell: the theme's `heading` closure (`mdHeading`) wrapped around the whole cell, so
73
- * a code span inside adds no ink of its own. Degrades to plain text without the closure. Required.
72
+ * One highlighted span inside a table BODY cell — a code span today: the theme's `heading` closure
73
+ * (`mdHeading`, purple in `dracula-soft`). The cell's regular text stays the default ink (#64).
74
+ * Degrades to plain text without the closure. Required.
74
75
  */
75
- cell(text: string): string;
76
+ cellHighlight(text: string): string;
76
77
  }
@@ -7,12 +7,12 @@
7
7
  /**
8
8
  * table.ts — the table surface: a markdown table becomes a boxed box with inked text.
9
9
  *
10
- * The header row's cells take the header ink — bold over `mdCode`; the body cells take the cell ink,
11
- * the theme's `mdHeading` purple. Each cell's whole text carries that one ink, so a code span inside a
12
- * cell adds no green or gray of its own. Nothing paints a background: the owner removed the full-bleed
13
- * band on a live look-check (2026-10-09, #62). When the header row is empty (`| | |`), that row and its
14
- * separator are dropped and the first column's labels take the header ink instead. `**strong**` and
15
- * `*em*` cells route through their own roles.
10
+ * The header row's cells take the header ink — bold over `mdCode`, over the whole cell, so a code span
11
+ * inside a header cell adds no ink of its own. A body cell's own text keeps the default ink; only a
12
+ * code span inside it takes the cell highlight — the theme's `mdHeading` purple (owner look-check,
13
+ * 2026-10-09, #64). Nothing paints a background: the band was removed the same day (#62). When the
14
+ * header row is empty (`| | |`), that row and its separator are dropped and the first column's labels
15
+ * take the header ink instead. `**strong**` and `*em*` cells route through their own roles.
16
16
  *
17
17
  * Boundary: lane-local. The transformer passes markdown in and splices the returned markdown back
18
18
  * out, so every escape in the output originates in the injected ContentPaint (T-08) — the #22 fix.
@@ -87,15 +87,15 @@ function inlineText(token: InlineToken, paint: ContentPaint): string {
87
87
  }
88
88
 
89
89
  /**
90
- * The cell's text with its inline spans composed. A code span adds no ink: its wrapper is the
91
- * identity, so the cell's own role wraps the whole composed text (owner ruling, #62).
90
+ * The cell's text with its inline spans composed, `codeSpanInk` drawing each code span. The caller picks
91
+ * it per region: the identity where the cell's own role inks the whole text (header cells, key-column
92
+ * labels), the highlight role where the text stays default and only the code span is inked (#64).
92
93
  */
93
- function cellText(cell: CellLike | undefined, paint: ContentPaint): string {
94
+ function cellText(cell: CellLike | undefined, paint: ContentPaint, codeSpanInk: (text: string) => string): string {
94
95
  const tokens = cell?.tokens;
95
- const uninked: ContentPaint = { ...paint, code: (text) => text };
96
96
  const text =
97
97
  Array.isArray(tokens) && tokens.length > 0
98
- ? tokens.map((token) => inlineText(token, uninked)).join("")
98
+ ? tokens.map((token) => inlineText(token, { ...paint, code: codeSpanInk })).join("")
99
99
  : (cell?.text ?? "");
100
100
  // Soft wraps inside a cell are one logical line before the width solve.
101
101
  return text.replace(/\s+/g, " ").trim();
@@ -108,8 +108,16 @@ function padCell(content: string, width: number): string {
108
108
 
109
109
  /** shape: none — one greedy width solve plus a largest-remainder fill; a table is small and the rule is exact. */
110
110
  function renderTable(token: TableToken, width: number, paint: ContentPaint): string[] {
111
- const header = token.header.map((cell) => cellText(cell, paint));
112
- const rows = token.rows.map((row) => row.map((cell) => cellText(cell, paint)));
111
+ /** A label cell (the header row, or a key-column table's first column): the whole text takes the header ink. */
112
+ const label = (cell: CellLike | undefined): string => cellText(cell, paint, (text) => text);
113
+ /** A body cell: its text stays default, and a code span inside it takes the highlight ink. */
114
+ const body = (cell: CellLike | undefined): string => cellText(cell, paint, (text) => paint.cellHighlight(text));
115
+
116
+ const header = token.header.map(label);
117
+ // An all-empty header (`| | |`) means the table carries its labels in the first column: the blank
118
+ // header row and its separator are dropped, and those labels take the header ink instead.
119
+ const keyColumn = header.every((cell) => cell === "");
120
+ const rows = token.rows.map((row) => row.map((cell, index) => (keyColumn && index === 0 ? label(cell) : body(cell))));
113
121
  const columns = Math.max(1, header.length);
114
122
  const natural: number[] = [];
115
123
  for (let index = 0; index < columns; index++) {
@@ -147,10 +155,6 @@ function renderTable(token: TableToken, width: number, paint: ContentPaint): str
147
155
  }
148
156
  }
149
157
 
150
- // An all-empty header (`| | |`) means the table carries its labels in the first column: the blank
151
- // header row and its separator are dropped, and those labels take the header ink instead.
152
- const keyColumn = header.every((cell) => cell === "");
153
-
154
158
  // A light box around and between everything: the frame is what keeps a wrapped cell visually
155
159
  // inside the table instead of trailing off its right edge.
156
160
  const ruleLine = (left: string, middle: string, right: string): string =>
@@ -158,7 +162,7 @@ function renderTable(token: TableToken, width: number, paint: ContentPaint): str
158
162
  const bar = paint.rule("│");
159
163
  const physical: string[] = [];
160
164
 
161
- /** One logical row, wrapped into physical lines; `ink` picks the role each cell's text is drawn in. */
165
+ /** One logical row, wrapped into physical lines; `ink` wraps each cell's text line by line. */
162
166
  const renderRow = (cells: string[], ink: (text: string, index: number) => string): void => {
163
167
  const wrapped = cells.map((cell, index) => wrapTextWithAnsi(cell, widths[index] ?? MIN_COLUMN));
164
168
  const height = Math.max(1, ...wrapped.map((lines) => lines.length));
@@ -174,14 +178,14 @@ function renderTable(token: TableToken, width: number, paint: ContentPaint): str
174
178
  if (keyColumn) {
175
179
  rows.forEach((row, index) => {
176
180
  if (index > 0) physical.push(ruleLine("├", "┼", "┤"));
177
- renderRow(row, (text, column) => (column === 0 ? paint.header(text) : paint.cell(text)));
181
+ renderRow(row, (text, column) => (column === 0 ? paint.header(text) : text));
178
182
  });
179
183
  } else {
180
184
  renderRow(header, (text) => paint.header(text));
181
185
  physical.push(ruleLine("├", "┼", "┤"));
182
186
  rows.forEach((row, index) => {
183
187
  if (index > 0) physical.push(ruleLine("├", "┼", "┤"));
184
- renderRow(row, (text) => paint.cell(text));
188
+ renderRow(row, (text) => text);
185
189
  });
186
190
  }
187
191
  physical.push(ruleLine("└", "┴", "┘"));