( section: string, completeRows: string[], )
| 546 | } |
| 547 | |
| 548 | function extractPartialRow( |
| 549 | section: string, |
| 550 | completeRows: string[], |
| 551 | ): string | null { |
| 552 | if (!section) { |
| 553 | return null; |
| 554 | } |
| 555 | |
| 556 | let after = stripCompleteRows(section); |
| 557 | for (const row of completeRows) { |
| 558 | after = after.replace(row, ""); |
| 559 | } |
| 560 | |
| 561 | const match = after.match(/<tr[^>]*>[\s\S]*$/i); |
| 562 | if (!match) { |
| 563 | return null; |
| 564 | } |
| 565 | |
| 566 | const partial = match[0]!; |
| 567 | if (!/<tr/i.test(partial)) { |
| 568 | return null; |
| 569 | } |
| 570 | |
| 571 | // Ignore auto-closed empty rows — wait for cell content |
| 572 | if (!CELL_RE.test(partial)) { |
| 573 | return null; |
| 574 | } |
| 575 | |
| 576 | return partial; |
| 577 | } |
| 578 | |
| 579 | /** |
| 580 | * Incrementally patch live DOM during streaming. |
no test coverage detected