Skip to content

feat: interactive Iceberg Mode Explorer with real C++→WASM engine#404

Open
JakeSCahill wants to merge 1 commit into
mainfrom
jake/iceberg-mode-explorer
Open

feat: interactive Iceberg Mode Explorer with real C++→WASM engine#404
JakeSCahill wants to merge 1 commit into
mainfrom
jake/iceberg-mode-explorer

Conversation

@JakeSCahill

Copy link
Copy Markdown
Contributor

What

Turns the standalone v26.2 "Iceberg Mode Explorer" TOI artifact into a docs-integrated, versioned interactive tool — and swaps its hand-written JavaScript rules for Redpanda's real C++ translation code compiled to WebAssembly.

An author drops the [iceberg-explorer] block (companion PR below) on a page; this bundle hydrates it into the tool: config controls, the DSL config string, the resulting Iceberg table schema, and a sample translated record.

Why WASM

The original re-typed the product's translation rules by hand, so it drifts the moment Redpanda changes. Running the real code in the browser (as the Bloblang playground already does) means the rule-bearing output can't drift, and a per-release build keeps every docs version correct with no manual upkeep.

Engine coverage (deliberate)

Output Engine Currency
Iceberg table schema real C++→WASM (iceberg::type_to_iceberg) built per Redpanda release tag
Config DSL string JS, pinned to iceberg_mode_test.cc vectors conformance test in CI
Translated record illustrative sample (as the original) n/a — original never computed it either

The record path is Seastar-coroutine + serde-parser code with no synchronous entry point; a real-WASM port is a large, separate effort. Everything sits behind a translate() seam, so any part can move to WASM later without UI changes.

Changes

  • src/js/27-iceberg-explorer.js + src/css/iceberg-explorer.css — hydration module and theme-aware styling (follows the site's light/dark tokens).
  • iceberg-editor/ — Emscripten build of the real mapper. wasm-spike/ is the reproducible feasibility proof (pinned toolchain: fmt 12.1.0, redpanda-data/avro fork + patches, thin Seastar shim); wasm/ is the browser engine build (build.sh + Embind). Fetched sources, third-party deps, and build outputs are git-ignored and reproducible from the scripts.
  • Version-aware loader: loads iceberg-engine-<version> for the page's doc version → bundle asset → interim JS fallback. <body data-version> added to default.hbs.
  • tests/iceberg-dsl/ — config-string conformance vectors copied from Redpanda's iceberg_mode_test.cc; caught and fixed two drift bugs (missing value layout, reversed option order).
  • CI: build-iceberg-engine.yml (build versioned engine per release tag) and iceberg-dsl-conformance.yml (run vectors + diff upstream).

Companion PR

Needs the block macro: redpanda-data/docs-extensions-and-macros#219.

Test / preview

  • npm run test:iceberg-dsl → 7/7 conformance vectors pass.
  • gulp previewhttp://localhost:5252/iceberg-explorer-test.html (verified: real-engine schema, correct config strings, light/dark, two independent instances).
  • Engine build: iceberg-editor/wasm/build.sh {node|web} (needs emsdk).

🤖 Generated with Claude Code

Turn the standalone v26.2 TOI explorer into a docs-integrated, versioned tool
whose rule-bearing output is computed by Redpanda's real code, so it can't
drift from the product.

- src/js/27-iceberg-explorer.js: hydrates the `[iceberg-explorer]` block into
  the interactive tool (controls, config string, schema, sample record).
  Theme-aware CSS in src/css/iceberg-explorer.css.
- iceberg-editor/: compiles Redpanda's real C++ Avro->Iceberg schema mapper
  (iceberg::type_to_iceberg from `dev`) to WebAssembly via Emscripten, with a
  thin Seastar shim. wasm-spike/ is the reproducible feasibility proof; wasm/
  is the browser engine build (build.sh + Embind bindings). Fetched sources,
  deps, and build outputs are git-ignored (reproducible from the scripts).
- Version-aware engine loader: loads iceberg-engine-<version> for the page's
  doc version, falling back to a bundle asset then to the interim JS engine.
  <body data-version> added in default.hbs to expose the version to JS.
- Config DSL string stays JS but is pinned to Redpanda's own
  iceberg_mode_test.cc format vectors via tests/iceberg-dsl (fixed two drift
  bugs: missing value `layout`, wrong option order).
- CI: build-iceberg-engine.yml builds a versioned engine per Redpanda release
  tag; iceberg-dsl-conformance.yml runs the DSL vectors and diffs upstream.

Requires the `[iceberg-explorer]` block from
redpanda-data/docs-extensions-and-macros.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 23, 2026

Copy link
Copy Markdown

Deploy Preview for docs-ui failed. Why did it fail? →

Name Link
🔨 Latest commit 96e322a
🔍 Latest deploy log https://app.netlify.com/projects/docs-ui/deploys/6a61d79fda28ae0008d66bac

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an interactive Iceberg Mode Explorer with deterministic DSL generation, schema and record rendering, and lazy loading of a versioned WebAssembly translation engine. The engine compiles production C++ Avro-to-Iceberg mapping code and exposes JSON through Embind. New scripts provide dependency fetching, source vendoring, staged WASM builds, and smoke tests. Documentation, preview wiring, static assets, styling, npm conformance tests, and GitHub Actions workflows are included.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant Explorer
  participant WASMEngine
  participant IcebergMapper
  Browser->>Explorer: hydrate explorer
  Explorer->>WASMEngine: load versioned engine
  Explorer->>WASMEngine: avroToIcebergJson(schema)
  WASMEngine->>IcebergMapper: map Avro schema to Iceberg type
  IcebergMapper-->>WASMEngine: fields or error
  WASMEngine-->>Explorer: JSON result
  Explorer-->>Browser: render schema and translated record
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: an interactive Iceberg Mode Explorer backed by a real C++ to WASM engine.
Description check ✅ Passed The description matches the changeset and explains the explorer, WASM engine, conformance tests, and CI work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jake/iceberg-mode-explorer

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
.github/workflows/build-iceberg-engine.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

.github/workflows/iceberg-dsl-conformance.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

package.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

  • 2 others
🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (1)
iceberg-editor/wasm-spike/shim/seastar/util/defer.hh (1)

32-35: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Store a decayed callable in the scope guard.

defer preserves lvalue references, so a guard that outlives or is moved beyond its callable can invoke a dangling reference. Return deferred_action<std::decay_t<Func>> instead.

Proposed fix
+#include <type_traits>
+
 template<typename Func>
-deferred_action<Func> defer(Func&& f) {
-    return deferred_action<Func>(std::forward<Func>(f));
+deferred_action<std::decay_t<Func>> defer(Func&& f) {
+    return deferred_action<std::decay_t<Func>>(std::forward<Func>(f));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iceberg-editor/wasm-spike/shim/seastar/util/defer.hh` around lines 32 - 35,
Update the defer function to decay Func when constructing and returning the
deferred_action, so the scope guard owns the callable value rather than
preserving an lvalue reference. Use std::decay_t<Func> consistently for the
return type and deferred_action construction while preserving perfect forwarding
of f.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-iceberg-engine.yml:
- Line 35: Update the workflow step defining REF and the downstream version
handling to pass event-controlled values through step-level env variables rather
than interpolating them in run scripts; validate both ref and derived version
against the accepted safe format before writing GITHUB_OUTPUT. Also set checkout
persist-credentials to false and restrict workflow permissions to the minimum
required, preserving the existing build behavior for valid inputs.

In @.github/workflows/iceberg-dsl-conformance.yml:
- Line 31: Update the actions/checkout@v4 step in the workflow to disable
credential persistence by setting persist-credentials to false. Leave the
checkout behavior otherwise unchanged.

In `@iceberg-editor/wasm-spike/env.sh`:
- Around line 10-11: The Redpanda engine inputs are not pinned to one immutable,
reproducible revision. In iceberg-editor/wasm-spike/env.sh lines 10-11, require
an explicit immutable tag or commit instead of defaulting to mutable dev and
record the resolved commit; in
iceberg-editor/wasm-spike/third_party/fetch-deps.sh lines 20-22, resolve the
Avro SHA and patch blobs from that same revision or consume a per-release
lockfile with checksums.

In `@iceberg-editor/wasm-spike/README.md`:
- Around line 13-15: Declare the fenced code block language for the Avro
conversion output by adding the appropriate text fence marker around the
existing message, preserving the output unchanged.

In `@iceberg-editor/wasm/iceberg_wasm.cc`:
- Around line 35-50: Update escape_json to encode every JSON control character,
not only quotes, backslashes, and newlines; handle tabs, carriage returns, and
other bytes below 0x20 with valid JSON escape sequences or Unicode escapes while
preserving existing escaping behavior.

In `@src/css/iceberg-explorer.css`:
- Around line 9-21: Insert a blank line between the final custom property
declaration, --ice-cyan, and the font-size declaration in the affected CSS rule
to satisfy stylelint’s declaration-empty-line-before requirement.

In `@src/js/27-iceberg-explorer.js`:
- Around line 411-421: Update applyInitialConfig to detect legacy strings using
the section-format delimiter rather than rejecting any string containing a
colon: treat strings without a semicolon as legacy, so value_schema_latest
options still select schema_latest while section-based configs continue through
the section parser.
- Around line 22-67: Reformat the multi-line object literals in VAL_SCHEMA,
RECORD, and AVRO_VALUE_SCHEMA to satisfy object-curly-newline: place each nested
object’s multiple properties on separate lines with matching braces, including
VAL_SCHEMA.shipping_address, RECORD.value and headers entries, and the nested
AVRO_VALUE_SCHEMA shipping_address record. Preserve all values and schema
structure.

In `@src/ui.yml`:
- Around line 4-5: Update the src/ui.yml static_files entries so
iceberg-engine.wasm and iceberg-engine.js are only referenced after being
generated by the web WASM build and available in src/static; alternatively
remove the entries until those artifacts are produced. Ensure the selected
approach occurs before the gulp bundle/package bundle:pack tasks copy static
assets.

---

Nitpick comments:
In `@iceberg-editor/wasm-spike/shim/seastar/util/defer.hh`:
- Around line 32-35: Update the defer function to decay Func when constructing
and returning the deferred_action, so the scope guard owns the callable value
rather than preserving an lvalue reference. Use std::decay_t<Func> consistently
for the return type and deferred_action construction while preserving perfect
forwarding of f.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a0a94e1-703d-4d11-b26f-dab2f81711c2

📥 Commits

Reviewing files that changed from the base of the PR and between dc31210 and 96e322a.

📒 Files selected for processing (34)
  • .github/workflows/build-iceberg-engine.yml
  • .github/workflows/iceberg-dsl-conformance.yml
  • iceberg-editor/README.md
  • iceberg-editor/wasm-spike/.gitignore
  • iceberg-editor/wasm-spike/README.md
  • iceberg-editor/wasm-spike/build-avro-cpp.sh
  • iceberg-editor/wasm-spike/env.sh
  • iceberg-editor/wasm-spike/fetch-sources.sh
  • iceberg-editor/wasm-spike/poc_main.cc
  • iceberg-editor/wasm-spike/shim/base/seastarx.h
  • iceberg-editor/wasm-spike/shim/base/vassert.h
  • iceberg-editor/wasm-spike/shim/iceberg/conversion/conversion_outcome.h
  • iceberg-editor/wasm-spike/shim/seastar/core/chunked_vector.hh
  • iceberg-editor/wasm-spike/shim/seastar/core/sstring.hh
  • iceberg-editor/wasm-spike/shim/seastar/util/bool_class.hh
  • iceberg-editor/wasm-spike/shim/seastar/util/defer.hh
  • iceberg-editor/wasm-spike/shim/seastar/util/log.hh
  • iceberg-editor/wasm-spike/shim/seastar/util/variant_utils.hh
  • iceberg-editor/wasm-spike/shim/ssx/sformat.h
  • iceberg-editor/wasm-spike/stage1-datatypes.sh
  • iceberg-editor/wasm-spike/stage2-schema-avro.sh
  • iceberg-editor/wasm-spike/third_party/fetch-deps.sh
  • iceberg-editor/wasm/.gitignore
  • iceberg-editor/wasm/build.sh
  • iceberg-editor/wasm/iceberg_wasm.cc
  • package.json
  • preview-src/iceberg-explorer-test.adoc
  • preview-src/ui-model.yml
  • src/css/iceberg-explorer.css
  • src/css/site.css
  • src/js/27-iceberg-explorer.js
  • src/layouts/default.hbs
  • src/ui.yml
  • tests/iceberg-dsl/conformance-test.js

- name: Resolve Redpanda ref and docs version
id: ver
run: |
REF="${{ github.event.inputs.redpanda_ref || github.event.client_payload.ref }}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not interpolate event-controlled values into run scripts.

redpanda_ref can break out of this assignment and execute runner commands; the derived version is interpolated again at Line 64. Pass both values through step env, validate the accepted ref/version format before writing $GITHUB_OUTPUT, and set checkout persist-credentials: false with least-privilege workflow permissions.

Proposed direction
-      - name: Resolve Redpanda ref and docs version
+      - name: Resolve Redpanda ref and docs version
         id: ver
+        env:
+          REF: ${{ github.event.inputs.redpanda_ref || github.event.client_payload.ref }}
         run: |
-          REF="${{ github.event.inputs.redpanda_ref || github.event.client_payload.ref }}"
+          [[ "$REF" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ ]] ||
+            { echo "Invalid Redpanda ref" >&2; exit 1; }
           echo "ref=$REF" >> "$GITHUB_OUTPUT"
       - name: Stage versioned artifact
         id: stage
+        env:
+          V: ${{ steps.ver.outputs.version }}
         run: |
-          V="${{ steps.ver.outputs.version }}"
           mkdir -p dist
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
REF="${{ github.event.inputs.redpanda_ref || github.event.client_payload.ref }}"
- name: Resolve Redpanda ref and docs version
id: ver
env:
REF: ${{ github.event.inputs.redpanda_ref || github.event.client_payload.ref }}
run: |
[[ "$REF" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ ]] ||
{ echo "Invalid Redpanda ref" >&2; exit 1; }
echo "ref=$REF" >> "$GITHUB_OUTPUT"
- name: Stage versioned artifact
id: stage
env:
V: ${{ steps.ver.outputs.version }}
run: |
mkdir -p dist
🧰 Tools
🪛 zizmor (1.26.1)

[error] 35-35: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 35-35: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-iceberg-engine.yml at line 35, Update the workflow
step defining REF and the downstream version handling to pass event-controlled
values through step-level env variables rather than interpolating them in run
scripts; validate both ref and derived version against the accepted safe format
before writing GITHUB_OUTPUT. Also set checkout persist-credentials to false and
restrict workflow permissions to the minimum required, preserving the existing
build behavior for valid inputs.

Source: Linters/SAST tools

conformance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Disable credential persistence on checkout.

actions/checkout@v4 defaults to persisting the GITHUB_TOKEN in the runner's git config for the rest of the job. This job never needs to push/authenticate afterwards, so disabling persistence is a cheap hardening step.

🔒 Proposed fix
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
       - uses: actions/setup-node@v4
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 31-31: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/iceberg-dsl-conformance.yml at line 31, Update the
actions/checkout@v4 step in the workflow to disable credential persistence by
setting persist-credentials to false. Leave the checkout behavior otherwise
unchanged.

Source: Linters/SAST tools

Comment on lines +10 to +11
export REDPANDA_REF="${REDPANDA_REF:-dev}"
export REDPANDA_REPO="${REDPANDA_REPO:-redpanda-data/redpanda}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Versioned engine inputs are not locked as one immutable release. The source ref defaults to mutable dev, while the Avro archive and patch source are selected independently; this prevents reproducing or attributing a generated engine to one Redpanda revision.

  • iceberg-editor/wasm-spike/env.sh#L10-L11: require an explicit immutable tag or commit for reproducible/release builds and record the resolved commit.
  • iceberg-editor/wasm-spike/third_party/fetch-deps.sh#L20-L22: resolve the Avro SHA and patch blobs from that same revision, or consume a per-release lockfile with checksums.
📍 Affects 2 files
  • iceberg-editor/wasm-spike/env.sh#L10-L11 (this comment)
  • iceberg-editor/wasm-spike/third_party/fetch-deps.sh#L20-L22
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iceberg-editor/wasm-spike/env.sh` around lines 10 - 11, The Redpanda engine
inputs are not pinned to one immutable, reproducible revision. In
iceberg-editor/wasm-spike/env.sh lines 10-11, require an explicit immutable tag
or commit instead of defaulting to mutable dev and record the resolved commit;
in iceberg-editor/wasm-spike/third_party/fetch-deps.sh lines 20-22, resolve the
Avro SHA and patch blobs from that same revision or consume a per-release
lockfile with checksums.

Comment on lines +13 to +15
```
OK: converted Avro record to iceberg::struct_type with 3 field(s)
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Declare the output fence language.

This fence triggers markdownlint MD040.

Proposed fix
-```
+```text
 OK: converted Avro record to iceberg::struct_type with 3 field(s)
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iceberg-editor/wasm-spike/README.md` around lines 13 - 15, Declare the fenced
code block language for the Avro conversion output by adding the appropriate
text fence marker around the existing message, preserving the output unchanged.

Source: Linters/SAST tools

Comment on lines +35 to +50
void escape_json(const std::string& s, std::string& out) {
for (char c : s) {
switch (c) {
case '"':
out += "\\\"";
break;
case '\\':
out += "\\\\";
break;
case '\n':
out += "\\n";
break;
default:
out += c;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape all JSON control characters.

Tabs, carriage returns, and other control bytes in a conversion error produce invalid JSON, causing the UI’s JSON parse to fail instead of rendering the engine error.

Proposed fix
         case '\n':
             out += "\\n";
             break;
+        case '\r':
+            out += "\\r";
+            break;
+        case '\t':
+            out += "\\t";
+            break;
+        case '\b':
+            out += "\\b";
+            break;
+        case '\f':
+            out += "\\f";
+            break;
         default:
-            out += c;
+            if (static_cast<unsigned char>(c) < 0x20) {
+                static constexpr char hex[] = "0123456789abcdef";
+                const auto byte = static_cast<unsigned char>(c);
+                out += "\\u00";
+                out += hex[byte >> 4];
+                out += hex[byte & 0x0f];
+            } else {
+                out += c;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void escape_json(const std::string& s, std::string& out) {
for (char c : s) {
switch (c) {
case '"':
out += "\\\"";
break;
case '\\':
out += "\\\\";
break;
case '\n':
out += "\\n";
break;
default:
out += c;
}
}
void escape_json(const std::string& s, std::string& out) {
for (char c : s) {
switch (c) {
case '"':
out += "\\\"";
break;
case '\\':
out += "\\\\";
break;
case '\n':
out += "\\n";
break;
case '\r':
out += "\\r";
break;
case '\t':
out += "\\t";
break;
case '\b':
out += "\\b";
break;
case '\f':
out += "\\f";
break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
static constexpr char hex[] = "0123456789abcdef";
const auto byte = static_cast<unsigned char>(c);
out += "\\u00";
out += hex[byte >> 4];
out += hex[byte & 0x0f];
} else {
out += c;
}
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iceberg-editor/wasm/iceberg_wasm.cc` around lines 35 - 50, Update escape_json
to encode every JSON control character, not only quotes, backslashes, and
newlines; handle tabs, carriage returns, and other bytes below 0x20 with valid
JSON escape sequences or Unicode escapes while preserving existing escaping
behavior.

Comment on lines +9 to +21
--ice-accent: var(--product-color, #e2401b);
--ice-muted: var(--color-gray-70, #6b7280);
--ice-border: var(--panel-border-color, #d0d3d9);
--ice-panel: var(--panel-background, #f7f8fa);
--ice-pre: var(--pre-background, #f0f1f4);
--ice-code: var(--code-font-color, var(--body-font-color));
--ice-green: #1a7f5a;
--ice-blue: #2563c9;
--ice-yellow: #9a6b00;
--ice-purple: #7c3aed;
--ice-cyan: #0e7490;
font-size: var(--secondary-font-size, 0.9rem);
margin: 1.25rem 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank line before font-size per stylelint declaration-empty-line-before.

🎨 Proposed fix
   --ice-cyan: `#0e7490`;
+
   font-size: var(--secondary-font-size, 0.9rem);
   margin: 1.25rem 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
--ice-accent: var(--product-color, #e2401b);
--ice-muted: var(--color-gray-70, #6b7280);
--ice-border: var(--panel-border-color, #d0d3d9);
--ice-panel: var(--panel-background, #f7f8fa);
--ice-pre: var(--pre-background, #f0f1f4);
--ice-code: var(--code-font-color, var(--body-font-color));
--ice-green: #1a7f5a;
--ice-blue: #2563c9;
--ice-yellow: #9a6b00;
--ice-purple: #7c3aed;
--ice-cyan: #0e7490;
font-size: var(--secondary-font-size, 0.9rem);
margin: 1.25rem 0;
--ice-accent: var(--product-color, `#e2401b`);
--ice-muted: var(--color-gray-70, `#6b7280`);
--ice-border: var(--panel-border-color, `#d0d3d9`);
--ice-panel: var(--panel-background, `#f7f8fa`);
--ice-pre: var(--pre-background, `#f0f1f4`);
--ice-code: var(--code-font-color, var(--body-font-color));
--ice-green: `#1a7f5a`;
--ice-blue: `#2563c9`;
--ice-yellow: `#9a6b00`;
--ice-purple: `#7c3aed`;
--ice-cyan: `#0e7490`;
font-size: var(--secondary-font-size, 0.9rem);
margin: 1.25rem 0;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 20-20: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/iceberg-explorer.css` around lines 9 - 21, Insert a blank line
between the final custom property declaration, --ice-cyan, and the font-size
declaration in the affected CSS rule to satisfy stylelint’s
declaration-empty-line-before requirement.

Source: Linters/SAST tools

Comment on lines +22 to +67
var VAL_SCHEMA = [
{ name: 'order_id', type: 'string' },
{ name: 'product', type: 'string' },
{ name: 'quantity', type: 'int' },
{ name: 'price', type: 'double' },
{ name: 'shipped', type: 'boolean' },
{ name: 'shipping_address', type: 'struct', fields: [
{ name: 'street', type: 'string' },
{ name: 'city', type: 'string' },
{ name: 'zip', type: 'string' },
{ name: 'tags', type: 'list<string>' },
] },
]
var RECORD = {
partition: 3, offset: 42, timestamp: 1720000000000,
value: {
order_id: 'ORD-98765', product: 'Redpanda Plushie', quantity: 3,
price: 29.99, shipped: false,
shipping_address: { street: '123 Main St', city: 'Seattle', zip: '98101', tags: ['residential', 'priority'] },
},
headers: [
{ key: 'content-type', value: 'application/json' },
{ key: 'x-request-id', value: 'req-abc-123' },
{ key: 'x-trace-id', value: 'trace-00f1e2d3' },
],
}

// The sample value schema expressed as real Avro JSON. When the WASM engine
// is available, this is fed to the REAL Redpanda C++ mapper to compute the
// Iceberg value fields (see below); otherwise the tool falls back to the
// VAL_SCHEMA rules above (interim engine).
var AVRO_VALUE_SCHEMA = JSON.stringify({
type: 'record', name: 'order', fields: [
{ name: 'order_id', type: 'string' },
{ name: 'product', type: 'string' },
{ name: 'quantity', type: 'int' },
{ name: 'price', type: 'double' },
{ name: 'shipped', type: 'boolean' },
{ name: 'shipping_address', type: { type: 'record', name: 'shipping_address', fields: [
{ name: 'street', type: 'string' },
{ name: 'city', type: 'string' },
{ name: 'zip', type: 'string' },
{ name: 'tags', type: { type: 'array', items: 'string' } }
] } }
]
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix ESLint object-curly-newline failures — this is currently breaking the build.

Pipeline logs confirm validate-build / build fails on object-curly-newline at line 28, and static analysis flags the same rule at lines 33, 36, 38, 39, 54. These are the multi-line object literals (VAL_SCHEMA's shipping_address, RECORD, AVRO_VALUE_SCHEMA) that pack multiple properties on one line while the surrounding object itself spans multiple lines.

🐛 Proposed reformat
   var VAL_SCHEMA = [
     { name: 'order_id', type: 'string' },
     { name: 'product', type: 'string' },
     { name: 'quantity', type: 'int' },
     { name: 'price', type: 'double' },
     { name: 'shipped', type: 'boolean' },
-    { name: 'shipping_address', type: 'struct', fields: [
-      { name: 'street', type: 'string' },
-      { name: 'city', type: 'string' },
-      { name: 'zip', type: 'string' },
-      { name: 'tags', type: 'list<string>' },
-    ] },
+    { name: 'shipping_address',
+      type: 'struct',
+      fields: [
+        { name: 'street', type: 'string' },
+        { name: 'city', type: 'string' },
+        { name: 'zip', type: 'string' },
+        { name: 'tags', type: 'list<string>' },
+      ] },
   ]
   var RECORD = {
-    partition: 3, offset: 42, timestamp: 1720000000000,
+    partition: 3,
+    offset: 42,
+    timestamp: 1720000000000,
     value: {
-      order_id: 'ORD-98765', product: 'Redpanda Plushie', quantity: 3,
-      price: 29.99, shipped: false,
+      order_id: 'ORD-98765',
+      product: 'Redpanda Plushie',
+      quantity: 3,
+      price: 29.99,
+      shipped: false,
       shipping_address: { street: '123 Main St', city: 'Seattle', zip: '98101', tags: ['residential', 'priority'] },
     },
     headers: [
       { key: 'content-type', value: 'application/json' },
       { key: 'x-request-id', value: 'req-abc-123' },
       { key: 'x-trace-id', value: 'trace-00f1e2d3' },
     ],
   }
   ...
   var AVRO_VALUE_SCHEMA = JSON.stringify({
-    type: 'record', name: 'order', fields: [
+    type: 'record',
+    name: 'order',
+    fields: [
       { name: 'order_id', type: 'string' },

Note: the nested shipping_address record type inside AVRO_VALUE_SCHEMA (around line 60) has the same multi-prop/multiline shape and may also need reformatting — worth double-checking with the lint config once these are fixed.

🧰 Tools
🪛 GitHub Actions: validate-build / 0_build.txt

[error] 28-28: ESLint: 28:5 error Expected a line break after this opening brace (object-curly-newline).

🪛 GitHub Actions: validate-build / build

[error] 28-28: ESLint (lint:js) error: 28:5 Expected a line break after this opening brace (object-curly-newline).

🪛 GitHub Check: build

[failure] 54-54:
Object properties must go on a new line if they aren't all on the same line


[failure] 39-39:
Object properties must go on a new line if they aren't all on the same line


[failure] 38-38:
Object properties must go on a new line if they aren't all on the same line


[failure] 38-38:
Object properties must go on a new line if they aren't all on the same line


[failure] 36-36:
Object properties must go on a new line if they aren't all on the same line


[failure] 36-36:
Object properties must go on a new line if they aren't all on the same line


[failure] 33-33:
Expected a line break before this closing brace


[failure] 28-28:
Object properties must go on a new line if they aren't all on the same line


[failure] 28-28:
Object properties must go on a new line if they aren't all on the same line


[failure] 28-28:
Expected a line break after this opening brace

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/js/27-iceberg-explorer.js` around lines 22 - 67, Reformat the multi-line
object literals in VAL_SCHEMA, RECORD, and AVRO_VALUE_SCHEMA to satisfy
object-curly-newline: place each nested object’s multiple properties on separate
lines with matching braces, including VAL_SCHEMA.shipping_address, RECORD.value
and headers entries, and the nested AVRO_VALUE_SCHEMA shipping_address record.
Preserve all values and schema structure.

Sources: Linters/SAST tools, Pipeline failures

Comment on lines +411 to +421
// Apply an author-supplied initial config string (data-config) by mapping
// its sections back onto the controls. Best-effort; unknown tokens ignored.
function applyInitialConfig (str) {
if (!str) return
if (str.indexOf(':') === -1) {
// legacy string
if (str === 'key_value') { setRadioValue('val-mode', 'binary') }
else if (str === 'value_schema_id_prefix') { setRadioValue('val-mode', 'schema_id_prefix') }
else if (str.indexOf('value_schema_latest') === 0) { setRadioValue('val-mode', 'schema_latest') }
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Legacy-string detection breaks for legacy strings with options.

str.indexOf(':') === -1 is used to distinguish "legacy" DSL strings from the section-based format, but legacy strings can legitimately contain a colon (e.g. value_schema_latest:subject=my-topic-value, per the conformance vectors in tests/iceberg-dsl/conformance-test.js). Such strings fall through to the section-parser, where prefixFor['value_schema_latest'] is undefined, so the val-mode radio is silently never set to schema_latest. Section-format strings always contain ; (three sections are always joined), so that's a more reliable discriminant.

🐛 Proposed fix
     function applyInitialConfig (str) {
       if (!str) return
-      if (str.indexOf(':') === -1) {
+      if (str.indexOf(';') === -1) {
         // legacy string
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Apply an author-supplied initial config string (data-config) by mapping
// its sections back onto the controls. Best-effort; unknown tokens ignored.
function applyInitialConfig (str) {
if (!str) return
if (str.indexOf(':') === -1) {
// legacy string
if (str === 'key_value') { setRadioValue('val-mode', 'binary') }
else if (str === 'value_schema_id_prefix') { setRadioValue('val-mode', 'schema_id_prefix') }
else if (str.indexOf('value_schema_latest') === 0) { setRadioValue('val-mode', 'schema_latest') }
return
}
// Apply an author-supplied initial config string (data-config) by mapping
// its sections back onto the controls. Best-effort; unknown tokens ignored.
function applyInitialConfig (str) {
if (!str) return
if (str.indexOf(';') === -1) {
// legacy string
if (str === 'key_value') { setRadioValue('val-mode', 'binary') }
else if (str === 'value_schema_id_prefix') { setRadioValue('val-mode', 'schema_id_prefix') }
else if (str.indexOf('value_schema_latest') === 0) { setRadioValue('val-mode', 'schema_latest') }
return
}
🧰 Tools
🪛 ast-grep (0.44.1)

[error] 416-416: React's useState should not be directly called
Context: setRadioValue('val-mode', 'binary')
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 417-417: React's useState should not be directly called
Context: setRadioValue('val-mode', 'schema_id_prefix')
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 418-418: React's useState should not be directly called
Context: setRadioValue('val-mode', 'schema_latest')
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/js/27-iceberg-explorer.js` around lines 411 - 421, Update
applyInitialConfig to detect legacy strings using the section-format delimiter
rather than rejecting any string containing a colon: treat strings without a
semicolon as legacy, so value_schema_latest options still select schema_latest
while section-based configs continue through the section parser.

Comment thread src/ui.yml
Comment on lines +4 to +5
- iceberg-engine.wasm
- iceberg-engine.js

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -i 'iceberg-engine' .
fd -i 'blobl.wasm' src/static 2>/dev/null
cat src/ui.yml

Repository: redpanda-data/docs-ui

Length of output: 312


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## src tree around static files"
git ls-files 'src/*' 'src/**' | sed -n '1,120p'

echo "## package scripts and possible static-file validation"
python3 - <<'PY'
import json
from pathlib import Path
p=Path('package.json')
if p.exists():
    data=json.loads(p.read_text())
    for k,v in data.get('scripts',{}).items():
        print(f"{k}: {v}")
PY

echo "## search for static_files usage"
rg -n "static_files|blobl\.wasm|iceberg-engine" . --glob '!node_modules' --glob '!dist' --glob '!build' --glob '!coverage'

Repository: redpanda-data/docs-ui

Length of output: 7573


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## repository top-level"
git ls-files | sed -n '1,120p'

echo "## iceberg-editor wasm files"
fd -i 'iceberg-engine|build\.sh' iceberg-editor 2>/dev/null | sort

echo "## iceberg-editor/wasm/build.sh relevant sections"
wc -l iceberg-editor/wasm/build.sh 2>/dev/null || true
sed -n '1,110p' iceberg-editor/wasm/build.sh 2>/dev/null || true
sed -n '110,160p' iceberg-editor/wasm/build.sh 2>/dev/null || true

echo "## package scripts"
python3 - <<'PY'
import json
from pathlib import Path
data=json.loads(Path('package.json').read_text())
for name, script in data.get('scripts', {}).items():
    print(f"{name}: {script}")
PY

echo "## search for build:wasm or wamc/emcc in workflows/package"
rg -n "build:wasm|wasm|emcc|wasm-pack|outDir|OUTDIR|iceberg-engine" package.json .github gulpfile.js --glob '!node_modules' || true

echo "## src static existence check"
python3 - <<'PY'
from pathlib import Path
files=['src/static/blobl.wasm','src/static/iceberg-engine.wasm','src/static/iceberg-engine.js']
for p in files:
    path=Path(p)
    print(f"{p}: {'exists_file' if path.is_file() else ('exists_dir' if path.exists() else 'missing')!r}")
PY

Repository: redpanda-data/docs-ui

Length of output: 12044


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## compile-partial.js"
wc -l compile-partial.js
sed -n '1,220p' compile-partial.js

echo "## gulpfile build/static tasks"
wc -l gulpfile.js
sed -n '1,160p' gulpfile.js

echo "## build preview UI docs"
wc -l docs/modules/ROOT/pages/build-preview-ui.adoc
sed -n '1,180p' docs/modules/ROOT/pages/build-preview-ui.adoc

echo "## package.json build-related scripts"
python3 - <<'PY'
import json
from pathlib import Path
data=json.loads(Path('package.json').read_text())
for name in ['build','build:preview','build:deploy','deploy','compile:partials','bundle-react','serve:preview','publish:release']:
    if name in data.get('scripts',{}):
        print(f"{name}: {data['scripts'][name]}")
PY

echo "## static-files docs examples"
sed -n '35,75p' docs/modules/ROOT/pages/add-static-files.adoc

Repository: redpanda-data/docs-ui

Length of output: 15560


Add or produce the Iceberg Engine static assets before referencing them in static_files:.

src/ui.yml now promotes iceberg-engine.wasm and iceberg-engine.js, but neither file is checked into src/static/, and the UI bundle task does not generate them. Either omit these entries until the assets exist, or run iceberg-editor/wasm/build.sh web/check-in the resulting artifacts before gulp bundle/package bundle:pack copies src/static into the UI output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui.yml` around lines 4 - 5, Update the src/ui.yml static_files entries so
iceberg-engine.wasm and iceberg-engine.js are only referenced after being
generated by the web WASM build and available in src/static; alternatively
remove the entries until those artifacts are produced. Ensure the selected
approach occurs before the gulp bundle/package bundle:pack tasks copy static
assets.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant