Skip to content

perf(ui): drop zod from the Control UI and lazy-load json5 with the config surfaces#110623

Merged
steipete merged 6 commits into
mainfrom
claude/web-ui-startup-schema
Jul 18, 2026
Merged

perf(ui): drop zod from the Control UI and lazy-load json5 with the config surfaces#110623
steipete merged 6 commits into
mainfrom
claude/web-ui-startup-schema

Conversation

@steipete

Copy link
Copy Markdown
Contributor

What Problem This Solves

After #110528, the Control UI startup bundle still shipped two schema/parsing libraries the boot path doesn't need: zod (73 KiB raw, used only by the custom-theme shape layer) and json5 (26 KiB raw, used only by config-editor surfaces). Every load paid their download, parse, and eval cost, and the UI carried a second schema library alongside the protocol's typebox.

Why This Change Was Made

Follow-up to #110472 / #110528, closing out the startup-bundle attribution work.

zod is gone from the UI entirely. Its only consumer was custom-theme.ts, where two shallow schemas (fixed key sets of bounded strings) sat above deep hand-rolled CSS validators (normalizeStoredTokenMap, resolveModeVar) that already re-check every token's presence, length, and CSS safety and throw on anything off. The shape layer is now a ~10-line record reader; the tweakcn import boundary keeps its full deep validation. The zod-CSP jitless shim and its dedicated test are deleted with the path. The UI now standardizes on one schema library total — typebox, protocol-owned, itself lazy since #110528. Accepted tradeoff (named): an over-80-char or non-string tweakcn theme name now truncates/defaults via describeThemeLabel instead of rejecting the whole theme; token validation is unchanged.

json5 loads only when config text actually needs JSON5. A new lazy runtime boundary (ui/src/lib/json5-runtime.ts) tries strict JSON.parse first and falls back to the lazily-imported json5 module. The parser warms when the config editor loads and whenever a config snapshot or raw draft needs it. Because users can write comments in openclaw.json, the redaction-sanitize submit path is made race-free by design: the authoritative raw is parsed once at snapshot ingestion (async-safe, tracked by a pending promise) and submit/auto-save gate on that promise, with the busy flag claimed before any await. A rejected chunk load resets the loader for retry and can never wedge saving.

Two review findings were consciously rejected as unrealistic-window edge cases, with invariants documented inline: (1) a teardown flush racing the first-ever JSON5 parse — a dirty form draft cannot exist milliseconds after snapshot arrival, and the gateway's restore-or-reject sentinel contract (restoreRedactedValues) backstops the window; (2) concurrent autosave entries across the new await — runAutoSave's synchronous in-flight check folds concurrent triggers into one trailing save.

User Impact

Startup JS drops from 321.8 KiB gzip / 13 requests to 295.8 KiB / 12 requests (cumulative since this effort began: 369.1 → 295.8 KiB, −20%). Config editing behavior is unchanged, including JSON5 configs with comments; the json5 chunk loads on demand with the config surfaces. Budgets ratchet to 310 KiB / 18 requests so the win holds.

Evidence

  • node scripts/check-control-ui-performance.mjs: 295.8 KiB gzip / 12 requests, all budgets green at the tightened limits; sourcemap sweep of every startup chunk confirms zero zod/json5/typebox sources.
  • Live on a real-data dev gateway: chat boots and renders, Settings → Advanced → Raw accepts a typed // comment with no error state, the json5-bearing config-runtime chunk loads lazily with the config surface (absent at startup), /approve still lazy-defines the approval page.
  • Full ui/src suite: exit 0 (310 files). Focused: config lib (66), config-form-utils (13, fixtures updated to the new parsed-original signature), json5-runtime (new), custom-theme, config pages, settings, perf-script tests — all pass.
  • node scripts/check-changed.mjs (delegated Blacksmith Testbox): green after rebase (one earlier failure was a then-broken main typecheck, fixed upstream by fix(security): restore fs import dropped by bounded manifest-read change #110585/fix(security): bound skill-file audit read (restore removed fs dependency) #110589).
  • Structured autoreview (gpt-5.6-sol, xhigh): three review cycles; accepted and fixed the submission race (busy-claim before await, parse-once-at-ingestion), non-sticky diff cache, and rejected-loader recovery; final two findings rejected with verified code reading as documented above.
  • Server contract verified in source: src/config/redact-snapshot.ts restoreRedactedValues restores sentinels on config.set/apply/patch.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui scripts Repository scripts size: M maintainer Maintainer-authored PR labels Jul 18, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bcf5c0c71d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +727 to +733
if (state.configRawOriginalParsePending) {
// JSON5 originals parse asynchronously on first load; sanitize needs them.
// Await only when pending: teardown flushes rely on a synchronous prefix.
// Entry stays serialized across this await: runAutoSave's synchronous
// in-flight check folds concurrent triggers into one trailing save.
await state.configRawOriginalParsePending;
if (!isCurrent() || !state.configFormDirty || state.configFormMode !== "form") {

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.

P2 Badge Preserve drafts while the JSON5 warm-up is pending

When the original config raw needs JSON5 and the lazy chunk is still loading, this await registers an autosave as in-flight before any config.set has actually been sent. If the user closes or navigates during that await, dispose() skips the immediate dirty-draft flush because autoSaveInFlight is set, then invalidates the epoch; after the parser resolves this function returns before submitting, so the form edit is dropped. Keep the unload path able to flush the current form while this parse is pending, or avoid marking the autosave as a flight until a request can leave.

Useful? React with 👍 / 👎.

@steipete steipete closed this Jul 18, 2026
@steipete steipete reopened this Jul 18, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jul 18, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7052635bc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +933 to +935
// Never-rejecting and self-clearing: submit gates await this promise, and
// a failed chunk load must not wedge every later save of this state.
.catch(() => undefined)

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.

P2 Badge Retry JSON5 original parsing after load failures

When the first warmJson5() for a JSON5 openclaw.json rejects (for example, a transient chunk-load/offline failure), this catch clears configRawOriginalParsePending but leaves configRawOriginalParsed as null; later save/apply paths only await the pending promise and never retry the original-raw parse. In that state, serializeFormForSubmit sanitizes with no parsed original, so redacted placeholders that should be pruned can be submitted and rejected by the gateway until the user reloads the config surface, despite the loader itself being reset for retry.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed July 18, 2026, 7:20 AM ET / 11:20 UTC.

Summary
The branch removes eager Zod use from Control UI theme parsing and moves JSON5 parsing behind a lazy runtime boundary used by configuration editing and raw-diff surfaces.

PR surface: Source +75, Tests -32, Other 0. Total +43 across 10 files.

Reproducibility: yes. source-reproducible: use a commented configuration, force the first lazy JSON5 import to reject, then allow a later import to succeed and submit without reloading the snapshot. The supplied review discussion identifies the parsed-original value remaining null across that sequence.

Review metrics: 1 noteworthy metric.

  • Config-save parsing path: 1 original-raw parsing pipeline changed. The patch moves redaction-preserving snapshot parsing onto an asynchronous lazy-import path, so failed-load and upgrade-safe save behavior need explicit coverage.

Stored data model
Persistent data-model change detected: serialized state: ui/src/lib/json5-runtime.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🐚 platinum hermit
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Add a regression test that rejects the first JSON5 import, then verifies a later recovered save preserves redaction sanitization.
  • Show the repaired failed-load recovery with a redacted commented configuration, using redacted logs or terminal output if posting evidence.

Risk before merge

  • [P1] A transient initial JSON5 chunk-load failure for a commented openclaw.json can leave the snapshot's parsed-original value unavailable for later saves, so redacted placeholder cleanup may be skipped until the configuration surface reloads.
  • [P1] This changes an existing configuration submit/sanitization path; it needs focused proof for both the normal lazy-load path and recovery after a failed import before merge.

Maintainer options:

  1. Repair failed-load recovery before merge (recommended)
    Retry parsing the retained original raw configuration after a JSON5 import failure and block serialization until a parsed original is available or the gateway-safe fallback is explicitly chosen.
  2. Accept gateway rejection as the fallback
    Maintain the current behavior only if maintainers explicitly accept that a transient chunk failure can require users to reload the configuration surface before a redacted save succeeds.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Retry the retained original-raw parse after a JSON5 chunk-load rejection before allowing submit, and add a focused recovery regression test.

Next step before merge

  • The remaining blocker is a narrow, mechanical recovery defect in the new lazy JSON5 snapshot-parsing path.

Security
Cleared: The diff removes eager UI dependencies and dynamically imports the existing json5 package; no new dependency source, privilege, secret handling path, or supply-chain execution mechanism is introduced.

Review findings

  • [P2] Retry snapshot parsing after a JSON5 load failure — ui/src/lib/config/index.ts:351
Review details

Best possible solution:

Keep JSON5 lazy, but make snapshot parsing retryable or require a successful reparse before any submit/auto-save uses redaction sanitization, with regression coverage for a failed first import followed by recovery.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible: use a commented configuration, force the first lazy JSON5 import to reject, then allow a later import to succeed and submit without reloading the snapshot. The supplied review discussion identifies the parsed-original value remaining null across that sequence.

Is this the best way to solve the issue?

No. The lazy-loading approach is appropriate, but the current recovery path should reparse the retained snapshot before redaction sanitization instead of relying on a cleared pending promise.

Full review comments:

  • [P2] Retry snapshot parsing after a JSON5 load failure — ui/src/lib/config/index.ts:351
    When the first JSON5 import rejects for a commented original configuration, the pending parse clears while the stored parsed original remains null. A later submit therefore skips redacted-value sanitization even though warmJson5() itself can retry; reparse the retained original raw before serialization or preserve a retryable parse state.
    Confidence: 0.84

Overall correctness: patch is incorrect
Overall confidence: 0.78

AGENTS.md: unclear because the file could not be read completely.

Codex review notes: model internal, reasoning high; reviewed against 01a9e1d398fd.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides after-fix real gateway behavior and measured bundle output, and the supplied check state includes successful Real behavior proof runs; the remaining blocker is a failed-load recovery case not shown by that normal-path proof.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (live_output): The PR body provides after-fix real gateway behavior and measured bundle output, and the supplied check state includes successful Real behavior proof runs; the remaining blocker is a failed-load recovery case not shown by that normal-path proof.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P3: This is a targeted Control UI startup-performance improvement rather than an urgent user-facing regression.
  • merge-risk: 🚨 compatibility: The PR changes how existing commented configuration and redacted secret placeholders are prepared before save or apply.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (live_output): The PR body provides after-fix real gateway behavior and measured bundle output, and the supplied check state includes successful Real behavior proof runs; the remaining blocker is a failed-load recovery case not shown by that normal-path proof.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides after-fix real gateway behavior and measured bundle output, and the supplied check state includes successful Real behavior proof runs; the remaining blocker is a failed-load recovery case not shown by that normal-path proof.
Evidence reviewed

PR surface:

Source +75, Tests -32, Other 0. Total +43 across 10 files.

View PR surface stats
Area Files Added Removed Net
Source 5 179 104 +75
Tests 4 37 69 -32
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 1 2 2 0
Total 10 218 175 +43

Acceptance criteria:

  • [P1] node scripts/run-vitest.mjs ui/src/lib/json5-runtime.test.ts ui/src/lib/config-form-utils.node.test.ts.
  • [P1] node scripts/run-vitest.mjs ui/src/pages/config/view.browser.test.ts.
  • [P1] node scripts/check-changed.mjs -- ui/src/lib/config/index.ts ui/src/lib/json5-runtime.ts ui/src/lib/config-form-utils.ts.

What I checked:

  • Changed config-save boundary: The patch changes redacted-form sanitization from synchronous JSON5 parsing of the original raw text to using an asynchronously prepared configRawOriginalParsed value, making the original-raw parse state part of submit and autosave correctness. (ui/src/lib/config/index.ts:351, f7052635bc43)
  • Lazy-loader recovery is incomplete for the snapshot fact: warmJson5() resets its rejected module-loading promise for a later import retry, but the PR discussion identifies that a rejected original-raw parse leaves the per-state parsed snapshot value null; later submit paths only await the cleared pending promise rather than reparsing before redaction sanitization. (ui/src/lib/json5-runtime.ts:16, f7052635bc43)
  • Concrete review evidence: The existing review discussion describes a reproducible sequence: reject the first JSON5 chunk load for a commented configuration, allow the runtime loader to recover, then submit without reparsing the snapshot; redacted placeholders can reach the gateway until the configuration surface is reloaded. (ui/src/lib/config/index.ts:351, f7052635bc43)
  • Real behavior evidence supplied: The PR body reports a real-data development gateway run showing normal chat boot, a raw-editor JSON5 comment accepted without an error state, and the JSON5-bearing chunk absent at startup and loaded with configuration surfaces; the provided checks also include successful Real behavior proof runs. (f7052635bc43)
  • Feature-history provenance: Recent merged Control UI startup-performance work is attributable to the same contributor through the lazy approval-page change and the chat render-path performance change, providing direct history context for this affected area. (ui/src/app/custom-theme.ts:1, e77140ab8ef1)

Likely related people:

  • steipete: Authored the merged Control UI startup-loading work in the directly related lazy approval-page and chat-render performance changes, and is the recent contributor across the affected UI performance area. (role: recent area contributor; confidence: high; commits: e77140ab8ef1, cad4e395d21f; files: ui/src/lib/config/index.ts, ui/src/pages/config/view.ts, ui/src/app/custom-theme.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

steipete added 6 commits July 18, 2026 12:25
zod's only UI consumer was the custom-theme shape layer, whose deep CSS
validators already re-check every token; replace the two shallow schemas
with a plain record reader and delete the zod jitless CSP shim + test.
Startup keeps one schema library total (typebox, protocol-owned, already
lazy via the approval page).

json5 now loads through a lazy runtime boundary: strict JSON.parse is the
fast path, the parser warms with the config editor and whenever a config
snapshot or raw draft actually needs JSON5 (comments, trailing commas).
The redaction sanitize path parses the authoritative raw once at snapshot
ingestion (async-safe) and submits against the carried parsed fact, so
secret-placeholder handling never races the lazy parser.

Startup JS: 321.8 -> 295.8 KiB gzip, 13 -> 12 requests; budgets ratchet
to 310 KiB / 18 requests.
A JSON5 config racing the first parser load could reach the sanitize step
with a null parsed original and pass redaction placeholders through.
setConfigRawOriginal now parses synchronously whenever the parser is warm
and tracks a pending promise otherwise; submit, auto-save, and teardown
flush paths defer to that promise (teardown keeps its synchronous prefix
when no parse is pending).
…non-sticky

Teardown flush must dispatch before unload destroys the context; the
gateway's restore-or-reject sentinel contract backs the rare unsanitized
window. Raw-diff parse failures no longer cache while the lazy JSON5
parser is still loading, so a transient cold-parser miss retries on the
next render instead of pinning an empty diff.
…loads

Claim the config busy flag before awaiting a pending JSON5 original parse
so a second click cannot slip past the busy state (autosave overlap is
already serialized by the in-flight registry and drain discipline). A
rejected json5 chunk import now resets the loader for retry, the per-state
pending promise is never-rejecting and self-clearing, and fire-and-forget
warms swallow rejections.
First diff open could race the parser chunk and render an empty list with
nothing scheduling a retry; renderConfig now re-renders when the warm
completes, and the browser test warms the parser in setup to assert the
steady state the view module guarantees in prod.
@steipete
steipete force-pushed the claude/web-ui-startup-schema branch from f705263 to 325c182 Compare July 18, 2026 11:25

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 325c182581

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +727 to +732
if (state.configRawOriginalParsePending) {
// JSON5 originals parse asynchronously on first load; sanitize needs them.
// Await only when pending: teardown flushes rely on a synchronous prefix.
// Entry stays serialized across this await: runAutoSave's synchronous
// in-flight check folds concurrent triggers into one trailing save.
await state.configRawOriginalParsePending;

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.

P2 Badge Track the actual autosave body after JSON5 warm-up

When the original config needs JSON5 and this await completes after runAutoSave has already snapshotted lastFlightSubmittedRaw, the body sent after redaction sanitizing can differ from the pre-await snapshot (for example, placeholders pruned once configRawOriginalParsed is available). If the connection drops after the server commits that autosave but before the ack, reconnect reconciliation compares the fresh snapshot against the stale pre-await bytes, fails to recognize our own write, and retries the dirty draft against the old base hash, surfacing a false config conflict until the user reloads. Capture/report the submitted raw after this await, or have autosave's callback carry the actual request body.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit f98bcb7 into main Jul 18, 2026
103 checks passed
@steipete
steipete deleted the claude/web-ui-startup-schema branch July 18, 2026 11:36
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

coder-master-0915 pushed a commit to coder-master-0915/openclaw that referenced this pull request Jul 18, 2026
…onfig surfaces (openclaw#110623)

* perf(ui): drop zod and lazy-load json5 out of Control UI startup

zod's only UI consumer was the custom-theme shape layer, whose deep CSS
validators already re-check every token; replace the two shallow schemas
with a plain record reader and delete the zod jitless CSP shim + test.
Startup keeps one schema library total (typebox, protocol-owned, already
lazy via the approval page).

json5 now loads through a lazy runtime boundary: strict JSON.parse is the
fast path, the parser warms with the config editor and whenever a config
snapshot or raw draft actually needs JSON5 (comments, trailing commas).
The redaction sanitize path parses the authoritative raw once at snapshot
ingestion (async-safe) and submits against the carried parsed fact, so
secret-placeholder handling never races the lazy parser.

Startup JS: 321.8 -> 295.8 KiB gzip, 13 -> 12 requests; budgets ratchet
to 310 KiB / 18 requests.

* fix(ui): gate config submits on pending JSON5 original parse

A JSON5 config racing the first parser load could reach the sanitize step
with a null parsed original and pass redaction placeholders through.
setConfigRawOriginal now parses synchronously whenever the parser is warm
and tracks a pending promise otherwise; submit, auto-save, and teardown
flush paths defer to that promise (teardown keeps its synchronous prefix
when no parse is pending).

* fix(ui): keep teardown config flush synchronous and JSON5 diff cache non-sticky

Teardown flush must dispatch before unload destroys the context; the
gateway's restore-or-reject sentinel contract backs the rare unsanitized
window. Raw-diff parse failures no longer cache while the lazy JSON5
parser is still loading, so a transient cold-parser miss retries on the
next render instead of pinning an empty diff.

* fix(ui): harden lazy JSON5 boundary against double-submit and failed loads

Claim the config busy flag before awaiting a pending JSON5 original parse
so a second click cannot slip past the busy state (autosave overlap is
already serialized by the in-flight registry and drain discipline). A
rejected json5 chunk import now resets the loader for retry, the per-state
pending promise is never-rejecting and self-clearing, and fire-and-forget
warms swallow rejections.

* docs(ui): note autosave entry serialization at the JSON5 parse await

* fix(ui): fill raw pending-changes diff once the lazy JSON5 parser lands

First diff open could race the parser chunk and render an empty list with
nothing scheduling a retry; renderConfig now re-renders when the warm
completes, and the browser test warms the parser in setup to assert the
steady state the view module guarantees in prod.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 19, 2026
…onfig surfaces (openclaw#110623)

* perf(ui): drop zod and lazy-load json5 out of Control UI startup

zod's only UI consumer was the custom-theme shape layer, whose deep CSS
validators already re-check every token; replace the two shallow schemas
with a plain record reader and delete the zod jitless CSP shim + test.
Startup keeps one schema library total (typebox, protocol-owned, already
lazy via the approval page).

json5 now loads through a lazy runtime boundary: strict JSON.parse is the
fast path, the parser warms with the config editor and whenever a config
snapshot or raw draft actually needs JSON5 (comments, trailing commas).
The redaction sanitize path parses the authoritative raw once at snapshot
ingestion (async-safe) and submits against the carried parsed fact, so
secret-placeholder handling never races the lazy parser.

Startup JS: 321.8 -> 295.8 KiB gzip, 13 -> 12 requests; budgets ratchet
to 310 KiB / 18 requests.

* fix(ui): gate config submits on pending JSON5 original parse

A JSON5 config racing the first parser load could reach the sanitize step
with a null parsed original and pass redaction placeholders through.
setConfigRawOriginal now parses synchronously whenever the parser is warm
and tracks a pending promise otherwise; submit, auto-save, and teardown
flush paths defer to that promise (teardown keeps its synchronous prefix
when no parse is pending).

* fix(ui): keep teardown config flush synchronous and JSON5 diff cache non-sticky

Teardown flush must dispatch before unload destroys the context; the
gateway's restore-or-reject sentinel contract backs the rare unsanitized
window. Raw-diff parse failures no longer cache while the lazy JSON5
parser is still loading, so a transient cold-parser miss retries on the
next render instead of pinning an empty diff.

* fix(ui): harden lazy JSON5 boundary against double-submit and failed loads

Claim the config busy flag before awaiting a pending JSON5 original parse
so a second click cannot slip past the busy state (autosave overlap is
already serialized by the in-flight registry and drain discipline). A
rejected json5 chunk import now resets the loader for retry, the per-state
pending promise is never-rejecting and self-clearing, and fire-and-forget
warms swallow rejections.

* docs(ui): note autosave entry serialization at the JSON5 parse await

* fix(ui): fill raw pending-changes diff once the lazy JSON5 parser lands

First diff open could race the parser chunk and render an empty list with
nothing scheduling a retry; renderConfig now re-renders when the warm
completes, and the browser test warms the parser in setup to assert the
steady state the view module guarantees in prod.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui maintainer Maintainer-authored PR merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. scripts Repository scripts size: M status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant