Skip to content

Put report_tools blocks and table rows on their own lines (ENG-3358) - #550

Merged
StpMax merged 5 commits into
stagingfrom
feat/ENG-3358
Oct 13, 2026
Merged

StpMax merged 5 commits into
stagingfrom
feat/ENG-3358

Conversation

@StpMax

@StpMax StpMax commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Why

Pages built with report_tools put everything after </style> on one line: a 9 KB dashboard had a 6,558-character line holding the whole visible page. Line-based tools get nothing useful from that: read_text_file cannot read one section by line range (and shortens a very long line), html_lint reports every console error on the same line, and a diff shows the whole page as one changed line.

Result: each block and each table row, list item, chart bar and select option is on its own line. The rendered page is pixel-identical to before. rt.update / rt.insert keep this layout and still work on existing single-line pages.

Changes

  • Block helpers (section, para, bullets, table, bar_chart, filter_table, data) end their markup with \n; page() puts head tags, <body>, <main> and the closing tags on their own lines.
  • Containers still join parts with "": a line break between two inline parts (text and a link in a section) would render as a space. For the same reason the filter's </label><select> stay together.
  • update / insert keep the layout:
    • a kept section heading keeps its trailing line break, so an updated section is byte-identical to a freshly built one;
    • replacing a whole element does not double the line break that follows it;
    • insert(after=) puts an Html block after the element's line break; inline content and plain text stay right next to the element.
  • New markup (including the filter script) uses the page's line break, taken from its first one (a \r\n inside a shown value does not count).
  • update with rt.table(..., id=) now replaces the table's table-wrap div too. Before, every such update nested one more wrapper (pre-existing bug).
  • table and filter_table share one _table_wrap helper.
flowchart LR
  A["rt.para / rt.table / ...<br/>markup + '\n'"] --> B["rt.section / rt.page<br/>join parts with ''"]
  B --> C["one block per line<br/>no breaks inside text"]
  C --> D["rt.update / rt.insert<br/>keep line breaks around edited elements"]
Loading

Example of the new layout:

<section id="detail-section"><h2>Detail</h2>
<div class="table-wrap"><table id="d"><caption>Cap</caption>
<thead><tr><th scope="col">I</th><th scope="col" class=num>G</th></tr></thead>
<tbody>
<tr><td>A-100</td><td class=num>40</td></tr>
<tr><td>B-200</td><td class=num>0</td></tr>
</tbody></table></div>
</section>

Existing pages stay single-line until rebuilt; nothing migrates them.

Testing

  • tests/test_report_tools.py: 41 passed. Six tests that pinned exact markup were updated. New tests check that:
    • every line break in the body sits between two tags;
    • rows, bars and options are one per line;
    • update + insert produce the same bytes as rebuilding the page;
    • inline and plain-text inserts stay next to their anchor;
    • edits on a CRLF page add no bare \n, and a CRLF inside text does not make new markup CRLF;
    • repeated update of a table yields the same page as a rebuild (one wrapper).
  • Rendering: the same page was built with the old and the new module and edited with the same update / insert calls. Both were rendered in headless Electron in light and dark themes, at 1200 px and 375 px, with three filter selections. All 12 bitmaps are identical. A control run with one extra line break between </label> and <select> was reported as different in all 6 affected cases.

Evals (cowork_evals, quick check)

baseline = origin/staging 2727d049, eng-3358 = this branch 925aa781; same cowork-server (2fe11f41), mindshub_air, LLM judge sonnet, 2 repeats per variant. The variants differ only in report_tools.py and its tests. Two repeats are a smoke check for regressions, not a statistical comparison.

Task baseline judge eng-3358 judge
dialog-dashboard-refine (5 turns, built with rt.page / rt.section / rt.bar_chart, extended with rt.insert) 0.62, 0.12 0.86, 0.74
dialog-incremental-report-01 (control: builds a Python script, does not use report_tools) 0.15, 0.25 0.96, 0.15
  • dialog-dashboard-refine: all four repeats produced a dashboard. The baseline 0.12 is a wrong final answer (Europe instead of APAC), unrelated to report_tools. Program checks pass in 3 of 4 repeats (the failure is that same baseline answer).
  • Layout of the real artifacts: baseline dashboards have 14-15 lines, the longest 5,160-5,332 characters. The eng-3358 dashboard built with report_tools has 292 lines, the longest 288 characters. In the other eng-3358 repeat the agent wrote its own SVG chart, which keeps its own long line.
  • Errors in agent turns: both variants hit the same pre-existing rt.check error ("HTML tags shown as text" after passing raw HTML as a plain string) and recovered. No new errors from rt.insert / rt.check.
  • dialog-incremental-report-01 spread is the known permission-question noise of that task.

🤖 Generated with Claude Code

StpMax and others added 5 commits October 9, 2026 13:01
…-3358)

Pages built with report_tools were one long line after </style>, so
line-based tools (read_text_file ranges, html_lint line numbers, diffs)
could not address a single section. Block helpers now end their markup
with a line break, one table row, list item, chart bar and option per
line. Line breaks go only where the browser shows no whitespace;
rendering is pixel-identical.

update and insert keep that layout: a kept section heading keeps its
line break, a whole-element replacement does not double the break, and
insert(after=) puts a block after the element's line break. New markup
uses the page's line endings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-updates (ENG-3358)

- Take the page's line break from its first one: a "\r\n" inside a shown
  value no longer turns all new markup into CRLF on an LF page. Computed
  once per update.
- insert(after=) moves past the element's line break only for Html blocks;
  plain text always stays next to the element.
- update with rt.table(..., id=) replaces the table's scroll wrapper too,
  instead of nesting a new wrapper on every update.
- Docstrings describe the placement and line-ending rules; tests cover
  CRLF pages, mixed pages, plain-text inserts and repeated table updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…G-3358)

- One _EOL pattern for page line-ending detection and conversion.
- update (whole element) and insert(after=) both extend the span over the
  line break that ends a block (_block_end) instead of trimming new markup
  in one place and moving the insert point in another.
- update converts new markup to the page's line ending once per edit.
- Rename _table_wrap to _table_html so it does not read like _TABLE_WRAP.
- Simpler layout assertion in the tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@StpMax
StpMax merged commit 2267e21 into staging Oct 13, 2026
9 checks passed
@StpMax
StpMax deleted the feat/ENG-3358 branch October 13, 2026 15:12
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant