Skip to content

Webview XSS hardening: HTML/attribute escaping for untrusted sinks, URL scheme guard, safe code-block restore, regression test suites - #224

Open
leonidasbarkas98-cpu wants to merge 22 commits into
andrepimenta:mainfrom
leonidasbarkas98-cpu:fix/webview-xss-hardening
Open

leonidasbarkas98-cpu wants to merge 22 commits into
andrepimenta:mainfrom
leonidasbarkas98-cpu:fix/webview-xss-hardening

Conversation

@leonidasbarkas98-cpu

Copy link
Copy Markdown

Hardens the chat webview against HTML/JS injection from every sink we identified that renders user-, model- or third-party-controlled text, plus a few directly related correctness fixes and the test infrastructure to keep those sinks closed. Context for severity: the webview CSP is default-src * 'unsafe-inline' 'unsafe-eval', so anything that reaches innerHTML unescaped executes. Grouped thematically, roughly in commit order:

  • Message rendering escapes prose, not just code fences. parseSimpleMarkdown escaped fenced code blocks but passed prose and inline-code content through unescaped into innerHTML, so a literal HTML tag in a response — e.g. a <select> Claude wrote outside a code fence — was parsed as real markup, and an unbalanced tag broke that message and everything after it. Text is now escaped right after code blocks are pulled into placeholders and before inline markup is applied. The conversation-history list had the same gap for user-message previews; those are escaped after truncation so entities are never cut apart.
  • MCP server display and edit path. displayMCPServers() interpolated the server command/args/url/type straight into innerHTML, and the server name into an inline handler — onclick="editMCPServer('${name}', ...)" — with the whole config object alongside as JSON in the attribute. All of these can come from external ~/.claude.json/.mcp.json data (e.g. a cloned workspace's own .mcp.json); a single quote in a name broke out of the call, a double quote out of the attribute. Display values are now escaped, the config object never touches an attribute anymore (a per-render map is looked up by name instead), and name/scope travel via data-* + this.dataset. The display-escaping half matches upstream commit f759a955; that commit's unrelated Windows spawn-quoting and CLI-scope changes belong to a separate PR in this series and are not included here.
  • Attribute sinks get their own escaper. escapeHtml only escapes & < >, so every attribute built with it stayed open to a " breakout: formatToolInputUI({file_path: 'a" onmouseover="alert(1)" zz="'}) produced a real onmouseover attribute on the emitted span — and file_path is model-generated tool input. A new escapeAttr in a self-contained src/html-escape.ts (spliced into the webview via .toString()) now covers all attribute sinks in script.ts, skills-script.ts and plugins-script.ts; inline handlers that interpolated values (open-file, plan actions, MCP edit) move to the data-* + dataset pattern. That conversion also fixes pre-existing plain bugs at those call sites: a path containing an apostrophe broke the handler syntactically, and backslashes were never escaped, so C:\tmp\a.txt reached the handler with \t expanded to a tab.
  • Permissions list Remove button actually works again. The always-allow list interpolated tool name and command into an inline onclick through escapeHtml, which leaves ', " and \ untouched — practically every Windows path pattern (python "C:\Users\..." *) broke the attribute or the JS string literal, so clicking Remove silently did nothing. Values now travel via data-tool/data-command + dataset, which also hands the extension the exact raw string its removal filter compares against; the displayed name/command are escaped too. Verified manually for names and commands containing quotes and backslashes.
  • Third-party data and URL schemes. Model id/name/provider from the models API are written into data attributes and card markup with no user interaction (the extension overwrites the bundled model list with API values and the webview re-renders) — a model name of <img src=x onerror=...> produced a live element. Escaped now. href/src values from the open MCP registries were escaped but their scheme was never checked, so "url": "javascript:..." passed through verbatim; a new safeHttpUrl() allows only http:/https: (strips tab/CR/LF and leading control characters first, compares the scheme case-insensitively), and callers omit the attribute entirely instead of emitting a dead one. Also fixed: an env-var row value containing " was truncated by an unescaped value="...".
  • Copy/restore correctness. copyCodeBlock decoded entities a second time by hand after getAttribute had already decoded them, so code containing literal entity text (&quot; etc.) came out of the clipboard mangled — the manual decode is dropped, with the invariant (exactly one attribute writer, all callers pass raw text) checked over 19 inputs including CRLF, Unicode, NUL and 200k-character payloads. Dead createExpandableInput (upstream 73c4a38: no caller, and a hand-written escaper that missed &) is removed — a future upstream merge will conflict at that spot. Separately, the code-block placeholder restore used String.prototype.replace(placeholder, htmlString), which interprets $&/$`/$'/$$ in the replacement as substitution patterns — a code block containing one of these could splice already-rendered HTML across block boundaries. The restore loop moved into src/markdown-restore.ts with a function replacer (literal substitution), plus a full-pipeline regression test through both call sites.
  • Your own messages render as raw text. User messages went through the markdown renderer, so asterisks/underscores/backticks in a prompt were rendered away — src/_test_.ts came back italicised. They now render escaped with white-space: pre-wrap; fenced code blocks are the one intentional exception (keeping language label, copy button and collapse), via a shared extractCodeBlocks() instead of a second implementation. The refactor was verified as a pure move: 28 targeted plus 4000 random inputs produce byte-identical parseSimpleMarkdown output before and after, and 6000 adversarial payloads through the user path produce no tag or attribute outside the fixed code-block vocabulary.
  • Collapsible long code blocks (a small feature the raw-text work builds on): fenced blocks over a configurable line threshold (default 20; new claudeCodeChat.ui.collapseLongCodeBlocks/.collapseCodeBlockLines settings) render as native <details>/<summary> with language and line count in the header, shorter blocks keep byte-identical markup, and every message gets a manual fold button independent of the auto-collapse.
  • YOLO-mode correctness. script.ts declared enableYoloMode twice in the same scope; the surviving declaration dereferenced an undefined menu element on the argument-less inline chat button, which died with a TypeError — consolidated into one function (verified manually). Worse, the chat reported "YOLO Mode enabled!" purely client-side, before and regardless of whether the setting was actually persisted — in a window with no workspace folder the write threw and a bare catch swallowed it. The workspace-then-global write decision now lives in a pure, vscode-free src/settings-batch.ts, and success is only reported after the write went through; failures log, show a VS Code error message and notify the webview instead of vanishing.
  • Settings saves no longer fail as a block. _updateSettings looped over keys under one outer try/catch, so one failing key silently discarded every subsequent key and skipped resending settings to the webview; each key now has its own try/catch.
  • Test infrastructure. Six new plain-mocha suites (each with its own npm run test:<name> script) run the emitted getScript() output in a Node vm sandbox and parse the resulting DOM with parse5; each injection test was verified to fail against the previous code. The brace-counting helper that extracted webview functions for the sandbox didn't understand regex literals and silently extracted a wrong 2727-character chunk; it now asks the TypeScript compiler API (already a devDependency) for the declaration span, with shared DOM helpers deduplicated into src/test/webview-dom-helpers.ts. Full unit run at this tip: 131 passing, 0 failing (97 across the six new suites, plus the 34 pre-existing downloader/model-updater unit tests).

The remaining chore commits translate internal review comments to English, requalify bare issue references in code comments as fork-issue-NN so GitHub doesn't auto-link them to unrelated issues on this repository (genuine upstream references are kept), and fix stale cross-references in comments — no production logic.

This is one of several PRs from our fork submitted together as part of a broader, thematically grouped series (permission/session handling, settings/UI, security hardening, rendering, etc. split across separate branches/PRs). This is the security-hardening branch of that series; it is based directly on current main (ab6e307) and does not stack on any other PR in the series. Note: src/markdown-restore.ts (introduced here) also appears independently, with slightly different content, in the LaTeX/KaTeX rendering PR of this series — a merge conflict is expected if both are merged, as noted there.

tsc --noEmit clean. No new runtime dependencies; parse5 (used only by the new DOM-parsing tests) was previously available transitively via @vscode/vsce → cheerio and is now an explicit devDependency so the build fails loudly if it ever disappears.

🤖 Generated with Claude Code

Jonas Kunert and others added 22 commits July 28, 2026 17:34
…only)

displayMCPServers() built configDisplay (server command, args, url and
type) by interpolating config.command/config.args.join(' ')/config.url/
serverType straight into serverItem.innerHTML with no escaping. Any of
those four values can come from external ~/.claude.json / .mcp.json
data, so a malicious config.type like <img src=x onerror=alert(1)>
rendered as a live element.

Wrap all four interpolations in escapeHtml(), matching the display-
hardening half of upstream commit f759a955 (fork-issue-39, "harden MCP
configuration handling"). That commit also contains a Windows
spawn-quoting fix and a "show CLI local-scope servers" feature; both
are unrelated config-path changes that belong in the MCP-cluster
bundle, not in this XSS-hardening branch, so only the escaping lines
are picked here. Placed first so every later commit in this branch
builds on top of an already-escaped configDisplay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… (fork-issue-40)

parseSimpleMarkdown escaped fenced code blocks but passed prose and
inline-code content through unescaped into innerHTML, so a literal HTML
tag in a response — for example a <select> dropdown Claude wrote
outside a code fence — was parsed as real markup, and an unbalanced tag
broke the rendering of that message and everything after it. Escape the
remaining text right after code blocks are pulled out into placeholders
and before inline markup is applied, so raw < > & become entities while
markdown syntax and the __CODEBLOCK__ placeholders stay intact. This
also closes the same gap in inline code, whose content was unescaped
too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The conversation list interpolated firstUserMessage/lastUserMessage raw
into innerHTML, so raw HTML typed in chat rendered as live elements in
the history. Escape after truncation so entities are never cut apart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…k-issue-48)

Fenced code blocks over a configurable line threshold (default 20)
render as native <details>/<summary> with a caret, language and line
count in the header; shorter blocks keep byte-identical markup. New
claudeCodeChat.ui.collapseLongCodeBlocks / .collapseCodeBlockLines
settings, with a catch-up pass in the webview because settingsData
arrives after history replay. Every user/claude/error message also
gets a manual fold button in its header, independent of the
auto-collapse. Threshold/line-count logic lives in the self-contained,
.toString()-spliced src/collapse-rules.ts, covered by a new
test:collapse-rules suite (18 cases, including a splice-sandbox test).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…bview (fork-issue-49)

Follow-up to fork-issue-40: the permission bubble, the tool-call input view, the
prompt-snippet list and the file picker all interpolated model- or
user-controlled text straight into innerHTML, so a Bash command like
echo "<b>hi</b>" tore the permission bubble apart and a command
containing a double quote broke out of the always-allow title attribute.
escapeHtml() serialises through textContent/innerHTML and therefore
leaves " and ' untouched, so attribute sinks need their own escaper:
add escapeAttr in a self-contained html-escape.ts, spliced into the
webview via .toString() like the collapse helper, and cover it with unit
tests under plain mocha.

Also drop the manual &quot;/&andrepimenta#39; decoding in toggleExpand -- getAttribute
already returns decoded values, so that second pass destroyed any value
containing entity text -- and render the TodoWrite summary as textContent,
which never needed markup in the first place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
script.ts declared function enableYoloMode twice in the same scope; the
later declaration won and dereferenced permissionMenu-undefined on the
argument-less inline chat button, so the button died with a TypeError.
Consolidated into one function: with a permissionId it keeps the
permission-menu behavior (now with a null guard on the menu element),
without one it posts the lightweight {type:'enableYoloMode'} message --
the extension host writes the single permissions.yoloMode key and the
settingsData round-trip updates checkbox and warning banner. The
formerly dead early variant's updateSettings() path was deliberately
not revived: it would push 21 unrelated keys to global settings.
No automated test asserts the declaration appears exactly once in this
branch; verified manually that the button no longer throws. Known edge
(workspace-less window, pre-existing on the menu path) tracked as
fork-issue-59.

The earlier build of this commit's conflict resolution left two unrelated
functions, isOutputTokenLimitError() and openMaxOutputTokensSettings(), in
place of the deleted duplicate -- foreign context from elsewhere with no
caller in src/ and no matching 'openMaxOutputTokensSettings' case in
extension.ts. That dead code is dropped here; the duplicate early
enableYoloMode() is simply removed, per the description above.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onclick (fork-issue-58)

The always-allow list in the settings modal interpolated toolName and
command into an inline onclick handler through escapeHtml, which leaves
', " and \ untouched. Any pattern containing quotes or backslashes --
practically every Windows path pattern like python "C:\Users\..." * --
broke the attribute or the JS string literal, so clicking Remove did
nothing. Move the values into data-tool/data-command via escapeAttr and
read them back through this.dataset (same pattern as the snippet delete
button), which also hands the extension the exact raw string its
removal filter compares against. Escape the tool name and command shown
in the list while at it.

No dedicated automated regression test covers renderPermissions/
removePermission in this branch yet; verified manually that the
always-allow Remove button works again for tool names and commands
containing quotes and backslashes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
escapeHtml only escapes & < >, so every attribute built with it stayed open for
a " breakout. formatToolInputUI({file_path: 'a" onmouseover="alert(1)" zz="'})
produced a real onmouseover attribute on the emitted span, and the webview CSP
(default-src * 'unsafe-inline' 'unsafe-eval') would have fired it -- file_path
is model-generated tool input.

All attribute sinks in script.ts, skills-script.ts and plugins-script.ts now use
escapeAttr. Inline handlers that interpolated a value (openFileInEditor,
sendPlanAction) move to the data-* + this.dataset.* pattern established in fork-issue-58.
That also fixes two older bugs at those call sites: a path containing an
apostrophe broke the handler syntactically, and backslashes were never escaped,
so C:\tmp\a.txt reached the handler with \t expanded to a tab.

Covered by six new tests that run the emitted getScript() output in a vm sandbox
and parse the result with parse5 (src/test/webview-attr-escape.test.ts). parse5
was only available transitively via @vscode/vsce -> cheerio and is now an
explicit devDependency, because the test lives under src/ and tsc would fail
the build if it ever disappeared.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…issue-59)

_enableYoloMode wrote permissions.yoloMode with ConfigurationTarget.Workspace
only, without the global fallback _updateSettings has for the same key. In a
window with no workspace folder open, config.update() threw, the bare catch
swallowed it and _sendCurrentSettings() never ran -- the setting was silently
not persisted.

The chat said "YOLO Mode enabled!" anyway, because that confirmation fired
client-side the moment the button was clicked and never depended on a response
from the extension host. That was the actual defect: the fallback alone would
still have left the message unconditional.

The workspace-then-global decision now lives in one pure, vscode-free function
(settings-batch.ts), shared with _updateSettings' handling of the same key, and
_enableYoloMode reports success only after the write went through -- otherwise
_reportYoloModeEnableFailure logs the error, shows a VS Code error message and
sends a yoloModeEnableFailed response. An outer try/catch makes that hold even
for failures outside the update itself, e.g. a mismatched deploy where
settings-batch.js is stale: previously that would have been an unhandled
rejection with no feedback at all.

Also dropped the module comment's "same pattern as shell-utils/perm-log-redact"
comparison -- neither module exists in this branch (shell-utils.ts belongs to
the separate MCP-cluster bundle; perm-log-redact.ts was removed here as a
fork-issue-51 fix with no caller in this branch, see that commit's absence in
this history).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
displayMCPServers interpolated the server name straight into a JS string
literal inside an HTML attribute -- onclick="editMCPServer('${name}', ...)" --
with no escaping at all, and passed the whole config object alongside it as
JSON.stringify(...).replace(/"/g,'&quot;'). A single quote in the name broke
out of the call; a double quote broke out of the attribute entirely. Server
names can come from a cloned workspace's own .mcp.json (extension.ts:2740,
read in at extension.ts:2770), and the webview CSP is default-src *
'unsafe-inline' 'unsafe-eval'.

The config object can never be escapeAttr'd as an attribute value, so it no
longer touches an attribute: displayMCPServers records it in a per-render map
and editMCPServer(name) looks it up. Name and scope travel through
data-server-name / data-server-scope with escapeAttr plus this.dataset.*, and
serverType is escapeHtml'd before it reaches innerHTML.

Nine tests (src/test/webview-attr-escape.test.ts) cover both payload classes
-- x'); alert(1); (' does not break out of the attribute but is valid
multi-statement JS in the handler, while x" onmouseover="alert(1)" y="
injects a real attribute -- all verified to fail against the previous code.
extractFunction now skips comments: an apostrophe in an existing comment
inside editMCPServer desynchronised its brace matching.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rk-issue-61)

Three sinks handled data from outside the extension with no escaping at all.

renderDropdown, renderAllModels and renderOpenCreditsModelCards wrote model.id,
model.name and model.owned_by/provider straight into data attributes and into
innerHTML. The data comes from fetch(OPENCREDITS_API_URL + '/v1/models'):
model-updater's resolveLatestModels() overwrites the bundled names with the API
values, the extension posts them as updateRecommendedModels, and the webview
replaces its model list and re-renders the cards without any user interaction.
A name of <img src=x onerror=...> produced a live element in the DOM.

href and src from the open MCP registries were escaped after fork-issue-57 but their
scheme was never checked, so a registry entry with "url": "javascript:..." was
passed through verbatim. The new safeHttpUrl() only lets http:/https: through,
strips tab/CR/LF and leading C0 control characters first and compares the
scheme case-insensitively; anything it cannot make sense of is dropped rather
than rendered, and callers omit the attribute instead of emitting a dead one.

addEnvVariableRow built value="..." unescaped, which truncated any value
containing a double quote.

Covered by 21 tests (src/test/webview-attr-escape.test.ts), each one verified
to fail against the previous code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…issue-62)

copyCodeBlock read data-raw-code with getAttribute -- which the HTML parser
has already decoded -- and then decoded &quot;/&lt;/&gt;/&amp; a second time by
hand. For ordinary code that was a no-op, but any code containing those entity
texts literally came out of the clipboard mangled: "literal &quot; entity"
was pasted as 'literal " entity'.

There is exactly one place that writes the attribute (parseSimpleMarkdown, via
escapeAttr) and all four callers pass raw text, so getAttribute already returns
the original. Verified over 19 inputs -- CRLF, Unicode, NUL, 200k characters,
the $& replacement specials from fork-issue-55, placeholder lookalikes -- that the
clipboard text now equals getAttribute exactly in every case.

createExpandableInput was dead code from upstream 73c4a38: no caller anywhere,
no dynamic access, and formatToolInputUI already provides the same
expand/collapse markup with proper escapeAttr escaping, whereas the dead copy
carried a hand-written escaper that missed &. Removed. A future upstream merge
will conflict at that spot.

Covered by five tests (src/test/webview-attr-escape.test.ts) driving the real
extracted parseSimpleMarkdown and copyCodeBlock through parse5. No automated
needle test guards against createExpandableInput reappearing in this branch;
a future upstream merge conflicting at that spot is the practical safeguard.

The removal comment left in script.ts pointed at build/check-webview-syntax.js,
a file that is not part of this branch. Reworded to drop the dangling
reference; the comment's actual point (don't spell out the old identifier in
the emitted <script> text) still stands on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fork-issue-55)

Replacement strings containing $&, $`, $' or $$ were interpreted as
special replacement patterns by String.replace, corrupting restored code
blocks. Extract the restore loop into markdown-restore.ts using a
function replacement (same pattern as escapeAttr's build-time splice),
add test:markdown-restore (9 tests).

Cherry-picked from fork-issue-55's original branch onto this branch's own
history (this branch never had fork-issue-47/KaTeX, so
build/check-webview-syntax.js and its fork-issue-55 assertion additions, plus the
now-irrelevant test:restore-commit-utils/test:perm-log-redact/
test:webview-syntax package.json lines and the renderMathEnabled restore
guard, are intentionally dropped here -- foreign context from fork-issue-55's
original branch that doesn't apply to this one).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-63)

User messages went through parseSimpleMarkdown, so asterisks, underscores and
backticks in a prompt were rendered away: a path like src/_test_.ts came back
italicised, and what you sent was no longer what you read in the transcript.

They now render as raw text -- escapeHtml only, with white-space: pre-wrap so
line breaks survive. Fenced code blocks are the one intentional exception:
pasting code should keep its language label, copy button and fork-issue-48 collapse. To
avoid a second implementation, the fence extraction moved out of
parseSimpleMarkdown into a shared extractCodeBlocks(), which both paths call;
user text then goes through escapeHtml and the placeholders are put back with
restoreCodeBlockPlaceholders (fork-issue-55), so data-raw-code keeps its fork-issue-62 behaviour.

Claude and thinking messages are untouched. The refactor was verified to be a
pure move: 28 targeted plus 4000 random inputs produce byte-identical
parseSimpleMarkdown output before and after, and 6000 adversarial payloads
through the user path produce no tag or attribute outside the fixed code-block
vocabulary.

.message.user .message-content also sets line-height 1.6, which the text
inherited from .message p before it stopped being wrapped in paragraphs.

Also dropped the sandbox helper's stub list 'function isOutputTokenLimitError()
{ return false; }' line for a function addMessage() never actually calls in
this branch (see fork-issue-52's dead-code note) -- along with the comment
referencing it.

fork-issue-55 had not actually been cherry-picked into this branch when
fork-issue-63 first landed here, so renderUserMessageContent's placeholder
restore was rebuilt inline as a plain html.replace(placeholder, str) --
reintroducing the exact "$&"/"$`"/"$'"/"$$" substitution-pattern bug
fork-issue-55 exists to fix, in a second, newly-added call site. fork-issue-55
is now cherry-picked directly before this commit; renderUserMessageContent
calls the real restoreCodeBlockPlaceholders() like the message above always
claimed it did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e-64)

extractFunction found a function's source by counting braces. The scanner knew
about strings and comments but not about regex literals, so escapeAttr's own
.replace(/'/g, '&andrepimenta#39;') made it read the apostrophe as a string start and lose
the count: 362 characters of escapeAttr came back as 2727, dragging safeHttpUrl,
openFileInEditor, formatFilePath, toggleDiffExpansion and toggleResultExpansion
along and loading formatFilePath into the vm sandbox twice.

That was harmless only by accident -- escapeAttr sits at the front of the chunk
and the later, correctly extracted copy won. Editing or moving
toggleResultExpansion would have ended the extraction somewhere else or thrown
"unbalanced braces" and taken every test in the file with it.

TypeScript is already a devDependency, so the helper now asks its parser for the
declaration's span instead of re-implementing a tokenizer. 24 of the 25
extracted functions come back byte-identical; only escapeAttr changes, which is
the fix. The five helpers that had been hand-copied into two test files now live
in src/test/webview-dom-helpers.ts.

No production code changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ers (fork-issue-70)

Issue fork-issue-70 claimed parseSimpleMarkdown/renderUserMessageContent had reintroduced
the fork-issue-55 html.replace(placeholder, string) bug. At this branch's current tip
both call sites already used restoreCodeBlockPlaceholders (fork-issue-55's
function-replacer, already part of this branch's history), cherry-picked before fork-issue-63
landed -- fork-issue-63's own commit message documents that this had already been fixed up
within the same commit. No production code change was needed for the fork-issue-70 finding
itself.

Added the missing full-pipeline regression coverage fork-issue-70 asked for: a code
block containing "$&"/"$`"/"$'"/"$$" must not duplicate the surrounding
prose, exercised through the real parseSimpleMarkdown AND the real
renderUserMessageContent (not just a direct unit call to
restoreCodeBlockPlaceholders, which markdown-restore.test.ts already covered).

While wiring that up, found and fixed an unrelated, pre-existing gap in two
vm-sandbox test loaders: loadCodeBlockSandbox (webview-attr-escape.test.ts)
and loadUserInputPipelineSandbox (user-message-rawtext.test.ts) extracted
parseSimpleMarkdown/renderUserMessageContent but never extracted
restoreCodeBlockPlaceholders itself (script.ts splices it in via
`${restoreCodeBlockPlaceholders.toString()}`, a separate statement, not part
of either function's body) -- every test calling those two functions through
either sandbox threw "ReferenceError: restoreCodeBlockPlaceholders is not
defined" (14 failures, confirmed present before this commit via git stash).
Both loaders now include it alongside their other extracted dependencies.

141 passing, 0 failing (was 127 passing / 14 failing). eslint: 0 errors (25
pre-existing warnings, none in touched files). git grep -il agentdeck: empty.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tch handling

Translates German-language code comments to English and removes internal
process/tracker references (review-tool name, tracker name, personal name)
so the branch reads cleanly as a standalone PR. Also fixes a real bug found
while correcting a stale comment in _updateSettings: the loop over settings
keys had a single outer try/catch, so one failing key silently discarded all
subsequent keys and skipped resending settings/balance to the webview. Each
key now gets its own try/catch instead.
The previous "translate internal review comments" pass removed jargon and
personal names but left every bare #NN comment reference untouched, so
GitHub would still auto-link them to unrelated issues on this repo. Each
bare reference (140 across 9 source and 6 test files) is requalified as
fork-issue-NN. Genuine "upstream #NNN" references (8x "upstream andrepimenta#151",
1x "upstream andrepimenta#63" in script.ts's extractCodeBlocks/parseSimpleMarkdown
comment) are left untouched since they point at the upstream project's
own issue tracker, not ours.
Every other new test file (collapse-rules, html-escape, webview-attr-escape,
markdown-restore, user-message-rawtext) has its own npm run test:<name>
script; settings-batch.test.ts was missing one. No test-coverage change --
the wildcard mocha run in this repo's verification already picks it up.
Several comments in this branch pointed at modules/functions that only exist
on sibling branches (getMathScript()/math-script.ts/math-segments.ts/
findMathSegments/restoreMathSegments/diff-utils/shell-utils/
auto-model-switch) -- none of that exists here; the analogies now point at
collapse-rules.ts/html-escape.ts/markdown-restore.ts/settings-batch.ts,
the sibling pure-logic modules that actually exist in this tree.
script.ts's sendOnEnter/renderMathEnabled comment referenced identifiers that
never existed anywhere else in this file; pointed it at the real
collapseLongCodeBlocks/collapseCodeBlockLines slots instead. Also dropped the
two leftover renderMathEnabled: false stub properties (and the comments
explaining them) from the webview-attr-escape/user-message-rawtext test
sandboxes -- dead, the code under test never reads that variable.

Removed leftover internal process-tracking markers from collapse-script.ts
and extension.ts comments; reworded without changing behaviour.

No production logic changed, comments/test-sandbox-setup only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
script.ts's escapeAttr/restoreCodeBlockPlaceholders splice comments still
compared against "math-script", a module that does not exist on this
branch (only collapse-script.ts does); collapse-script.ts's per-message
fold comment still carried an internal "Phase 2:" planning-round marker.
Neither had a production effect, comments only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Several code comments pointed at lines that no longer match after
earlier edits/rebases; the referenced behaviour itself is unchanged.

- settings-batch.ts:16 dropped a dead "extension.ts's _permLog"
  reference (git grep finds no such symbol anywhere else).
- script.ts's enableYoloMode comment now points at the real
  permission-menu item (~4060, was ~4604), the real inline chat
  button sites (~219/~476, was ~254/~639), and the real settingsData
  handler / updateYoloWarning() locations (~5360/~5392, was
  ~6147/~6150 -- past EOF, the file has 5569 lines).
- script.ts:181's ui-styles.ts reference for .copy-btn's
  margin-left:auto now points at 1129 (was 1152, which is
  .message-collapse-btn's font-size).
- ui-styles.ts:1210's reference for .message p's line-height now
  points at 3855 (was ~4045, past that rule's actual location).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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