fix(feishu): address second-round codex bot review feedback

P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts):
  A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot
  be split atomically (e.g. a very large table). Previously such a block was
  silently added to the current batch and sent as an oversized request,
  violating the API limit. Now throws a descriptive error so callers know to
  reduce the content size.

P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts):
  Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]'
  closer. The original regex dropped the '[' character in this case, silently
  corrupting the text. Fixed by appending '|\[' to the plain-text alternative
  so any '[' that does not open a complete tag is captured as literal text.
This commit is contained in:
Elarwei
2026-02-28 12:30:26 +08:00
committed by Tak Hoffman
parent 1bed7e5621
commit 6760c1c13d
2 changed files with 17 additions and 5 deletions

View File

@@ -128,6 +128,16 @@ export async function insertBlocksInBatches(
const descendants = collectDescendants(blocks, [firstLevelId]);
const newBlocks = descendants.filter((b) => !usedBlockIds.has(b.block_id));
// A single block whose subtree exceeds the API limit cannot be split
// (a table or other compound block must be inserted atomically).
if (newBlocks.length > BATCH_SIZE) {
throw new Error(
`Block "${firstLevelId}" has ${newBlocks.length} descendants, which exceeds the ` +
`Feishu API limit of ${BATCH_SIZE} blocks per request. ` +
`Please split the content into smaller sections.`,
);
}
// If adding this first-level block would exceed limit, start new batch
if (
currentBatch.blocks.length + newBlocks.length > BATCH_SIZE &&

View File

@@ -55,11 +55,13 @@ interface Segment {
*/
export function parseColorMarkup(content: string): Segment[] {
const segments: Segment[] = [];
// Match [tag]...[/tag] or plain text between tags.
// The closing tag name is intentionally not validated against the opening tag:
// mismatched tags like [red]text[/green] are treated as [red]text[/red] —
// the opening tag's style is applied and the closing tag is consumed.
const tagPattern = /\[([^\]]+)\](.*?)\[\/(?:[^\]]+)\]|([^[]+)/gs;
// Match [tag]...[/tag], plain text, or a bare '[' that is not part of a
// complete tag pair. Without the trailing `|\[` fallback, a '[' that has no
// matching '[/...]' closer (e.g. "[Q1]" with no "[/...]") would be silently
// dropped, corrupting the surrounding text. The closing tag name is not
// validated against the opening tag: [red]text[/green] is treated as
// [red]text[/red] — opening tag style applies, closing tag is consumed.
const tagPattern = /\[([^\]]+)\](.*?)\[\/(?:[^\]]+)\]|([^[]+|\[)/gs;
let match;
while ((match = tagPattern.exec(content)) !== null) {