feat(mcp): nested-struct render + bundle htpy + cells-as-presentation#803
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Contributor
Blast radius
1 added, 0 removed pie showData title Rebuilt checks by category
"rust" : 20
"image" : 13
"blast" : 1
"lint" : 1
flowchart LR
c0["htpy-26.5.1.tar.gz"]
c1["ix-mcp-view-python-module"]
c2["ix-notebook-mcp-module"]
c3["blast-radius-test"]
c4["lint"]
c5["rust-mcp.viewSmoke"]
c0 --> k1["image-kernel-dev"]
c0 --> k2["image-minecraft"]
c0 --> k3["image-minecraft-bedrock"]
c0 --> k4["image-minecraft-status"]
c0 --> k5["image-minecraft_1.21.11-fabric"]
c1 --> k1["image-kernel-dev"]
c1 --> k2["image-minecraft"]
c1 --> k3["image-minecraft-bedrock"]
c1 --> k4["image-minecraft-status"]
c1 --> k5["image-minecraft_1.21.11-fabric"]
c2 --> k1["image-kernel-dev"]
c2 --> k2["image-minecraft"]
c2 --> k3["image-minecraft-bedrock"]
c2 --> k4["image-minecraft-status"]
c2 --> k5["image-minecraft_1.21.11-fabric"]
changed checks (34)
|
The view df_html renderer fell back to a 60-char-truncated str(value) for
any non-scalar dtype, so Struct and List(Struct) columns showed a clipped
python repr ("[{'mount': '/', ...") instead of the data. Add a recursive
nested renderer: a Struct becomes a 2-col key/value table, a List(Struct)
becomes a real table (one column per field, one row per element), and a
list of scalars becomes a single-column table, capped at 50 rows. Matches
how nushell shows nested data.
Also sharpen the MCP instructions: cells is the final presentation of the
current state, prune stale cells rather than appending a log.
The no-recursive-update ast-grep rule (added with the deep-merge helpers) flags lib.recursiveUpdate because it silently rhs-replaces at leaf collisions. The settingsDefaults merge wants exactly rhs-wins (defaults layered on top win), so switch to the explicit ix.deepMerge.rhs helper. Unblocks repo-wide nix run .#lint.
Extend the view smoke test (viewTestPy) to cover the new nested renderer: a List(Struct) cell must surface its inner field values in the HTML and must not fall back to a truncated str(value) repr.
- S1 (security): the column-header dtype string was interpolated unescaped; for nested dtypes it embeds struct field names verbatim, so an agent building a frame from untrusted data could inject live HTML into the dashboard. Escape it (_html.escape(str(dt))) and add a regression test. - Fix OCI image eval break: claude-code is called via callPackageWith with no 'ix' in scope (image-development-base/-symphony-codex), so requiring an 'ix' arg failed eval. Import lib/util/deep-merge.nix directly instead. - Harden the nested-render test: assert sub-table structure, not the '…' glyph the renderer legitimately emits.
Hand-rolled f-string HTML is where escaping gets forgotten (the dtype-header XSS this set just patched). Bundle htpy (htpy.dev): build HTML as div(class_='x')[...] with every text node and attribute auto-escaped via markupsafe. Not in nixpkgs, so package it inline (pure Python, one dep). Add an import+auto-escape smoke test and point the kernel instructions at it as the preferred way to compose dashboard HTML.
263d842 to
e732f83
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
1. Nested
Struct/List(Struct)cells render as boxed sub-tables. The dashboard's polars renderer (view.df_html) truncated any non-scalar cell to a 60-charstr(value), so struct/list columns showed a clipped python repr. Now they render nushell-style:Struct→ key/value table,List(Struct)→ a table (column per field, row per element), scalar list → single column, capped at 50 rows.2. Bundle
htpyfor auto-escaping HTML. Hand-rolled f-string HTML is where escaping gets forgotten (this PR had to patch exactly such an XSS — see below). htpy builds HTML asdiv(class_="x")[...]with every text node and attribute auto-escaped via markupsafe. Not in nixpkgs, so packaged inline (pure Python, one dep). The kernel instructions now point at it as the preferred way to compose dashboard HTML.3. MCP instructions:
cellsis the final presentation of current state, not an append-only log — prune stale cells (cells.set/.remove/.clear).4. Drive-by lint fix (
packages/claude-code/default.nix):lib.recursiveUpdate→ directimport lib/util/deep-merge.nix.rhs. Theno-recursive-updaterule was failing repo-widenix run .#lintonmain(from #799). Imported directly (not via theixscope) soclaude-codestill evaluates from the OCI-image call sites that inject noix.Security
Adversarial review found a stored-XSS blocker (now fixed): the column-header dtype string was interpolated unescaped, and for nested dtypes that embeds struct field names verbatim — an agent building a frame from untrusted data could inject live HTML into the dashboard. Now
_html.escaped, with a regression test. The htpy bundling addresses the root cause (no more hand-escaped f-strings).Validation
nix build .#mcp,.#claude-codenix build .#mcp.tests.viewSmoke(nested render + S1 escape) and.#mcp.tests.htpyBundled(import + auto-escape)nix run .#lint(5/5, previously red on main)🤖 Generated with Claude Code (Opus 4.8)
Note
Render nested Struct/List DataFrame cells as HTML sub-tables with htpy bundled
_fmt_nestedin view/init.py to renderStruct,List, andArraydtype cells as nested HTML tables instead of truncated string representations; lists are capped at 50 rows via_MAX_NESTED_ROWS.htpyPython package (v26.5.1) into the MCP environment via a new Nix derivation in default.nix, providing auto-escaping HTML construction.htpyover f-string HTML.<table>elements and are left-aligned; previously they displayed as truncated strings.Macroscope summarized e732f83.