fix(cli): allow single-char function names in docs reference WRITE_GLOBS - #2469
Conversation
The View Helpers agent run (PR #2465) hit a tool-layer constraint when trying to author the reference example for `h()`: the WRITE_GLOBS regex in tools/docs-validation/lib/tools.mjs required filenames matching `[a-z][a-z0-9]+\\.txt` (2+ chars), so `h.txt` got rejected. The agent stopped cleanly with status=needs_human, diagnosed the exact regex line that needed fixing, and drafted the reference body. This change: 1. Tightens `+` to `*` in the WRITE_GLOBS regex so single-char function names work (`h`, `e`, `q`, etc.) 2. Writes the agent's drafted content to vendor/wheels/public/docs/reference/controller/h.txt 3. Flips the state.json entry for `h` from needs_human to done with notes describing the manual resolution Closes the last gap from the v4 API docs validation rollout. With this in, all 8 sections × 378 functions are status=done. https://claude.ai/code/session_014puccJJixwdjRgMx7mPLmz
| // HTML-encoded form: <script>alert(&##x27;xss&##x27;)</script> | ||
|
|
||
| // 2. Encode a variable inline in a cfoutput block | ||
| // Instead of: <cfoutput>##user.bio##</cfoutput> | ||
| // Use: <cfoutput>##h(user.bio)##</cfoutput> |
There was a problem hiding this comment.
🔴 The new `h.txt` doubles all # characters (lines 4, 7, 8) on the assumption that the snapshot pipeline runs through cfoutput, but $getExtendedCodeExamples (vendor/wheels/public/helpers.cfm:544) reads the file via FileRead() and processes it via HtmlEditFormat() — neither touches #. The resulting docs render literal &##x27; (not a real HTML entity) and <cfoutput>##user.bio##</cfoutput> (which, if copy-pasted, evaluates to literal #user.bio# — the opposite of the XSS-safe pattern the example is teaching). Fix: change all ## back to # on lines 4, 7, and 8 to match the convention used by every other reference file (e.g., hattr.txt, cycle.txt, contentfor.txt).
Extended reasoning...
What the bug is
The new vendor/wheels/public/docs/reference/controller/h.txt contains three lines where # characters were doubled to ##:
- Line 4:
&##x27;xss&##x27;(intended:'xss'— the apostrophe HTML entity thatEncodeForHTMLproduces) - Line 7:
<cfoutput>##user.bio##</cfoutput>(intended:<cfoutput>#user.bio#</cfoutput>) - Line 8:
<cfoutput>##h(user.bio)##</cfoutput>(intended:<cfoutput>#h(user.bio)#</cfoutput>)
The PR description explains the doubling: "The &# HTML entities are CFML-escaped as &## so they render correctly through the snapshot pipeline." This premise is incorrect.
Why the pipeline never re-evaluates #
$getExtendedCodeExamples in vendor/wheels/public/helpers.cfm:544 builds the rendered docs as:
local.rv["docs"] = "<pre><code class='javascript'>" & HtmlEditFormat(FileRead(local.rv.path)) & "</code></pre>";FileRead()returns raw bytes —#is not interpreted.HtmlEditFormat()only encodes<,>,&,"— it does not touch#.- The result is later interpolated via
<cfoutput>#meta.extended.docs#</cfoutput>(html.cfm:97) or<cfoutput>#SerializeJSON(docs)#</cfoutput>(json.cfm:3). CFML's##→#collapse only happens when the parser scans template source; it does not re-scan a variable's evaluated string value. So the##bytes flow through unchanged.
Convention check (all sibling reference files use single #)
vendor/wheels/public/docs/reference/controller/hattr.txt:4uses"and renders correctly.vendor/wheels/public/docs/reference/controller/cycle.txt:<tr class="#cycle("odd,even")#">— single#.vendor/wheels/public/docs/reference/controller/contentfor.txt:<cfoutput>#includeContent("sidebar")#</cfoutput>— single#.
Empirical confirmation: docs/api/v3.0.0.json shows textfield.txt's ##i# preserved verbatim as ##i# in the rendered JSON — confirming cfoutput does not collapse ## in variable values during snapshot.
Step-by-step proof for line 4
- File on disk contains the bytes:
alert(&##x27;xss&##x27;) FileRead()→ string"alert(&##x27;xss&##x27;)"HtmlEditFormat()encodes&→&. Result:"alert(&##x27;xss&##x27;)". The#is untouched.- Wrapped:
<pre><code class='javascript'>alert(&##x27;xss&##x27;)</code></pre> - Browser HTML-decodes
&→ user sees rendered text:alert(&##x27;xss&##x27;) &##x27;is not a valid HTML entity — the reader sees garbage instead of the intended'apostrophe entity thath()actually produces.
Step-by-step proof for line 7/8
- File on disk:
<cfoutput>##user.bio##</cfoutput> FileRead()returns it unchanged.HtmlEditFormat()encodes<and>only:<cfoutput>##user.bio##</cfoutput>. The##is untouched.- Browser renders:
<cfoutput>##user.bio##</cfoutput>literally — visible to readers with two#on each side. - Worse: a reader who copies this snippet into their own CFML template gets
<cfoutput>##user.bio##</cfoutput>, which CFML parses as the literal text#user.bio#(since##is the escape for a literal#). The example teaches the opposite of XSS-safe variable interpolation.
Addressing the duplicate-bug refutation
One verifier flagged this as a duplicate of the cfoutput cases on lines 7-8. They are the same root cause and the same one-character fix (## → #), but they manifest at different line locations with different rendering symptoms (an invalid HTML entity on line 4 vs. broken cfoutput examples on lines 7-8). This single comment covers all three occurrences.
Fix
In vendor/wheels/public/docs/reference/controller/h.txt, change ## to # on lines 4, 7, and 8:
-// HTML-encoded form: <script>alert(&##x27;xss&##x27;)</script>
+// HTML-encoded form: <script>alert('xss')</script>
...
-// Instead of: <cfoutput>##user.bio##</cfoutput>
-// Use: <cfoutput>##h(user.bio)##</cfoutput>
+// Instead of: <cfoutput>#user.bio#</cfoutput>
+// Use: <cfoutput>#h(user.bio)#</cfoutput>
Closes the v4 API docs validation rollout (PRs #2440 → #2469) with a retrospective covering: what shipped, what worked, what required iteration, framework bugs caught (polymorphic associations, addErrorToBase, capitalize, singularize, sendFile/redirectTo/checkBox params, formsdate copy-paste bugs, count @reload, helpers.cfm path resolution), cost/turn metrics per section, and what to do differently next time. Then plans the next phase: per-page validation of the 181 v4 guide pages. Reuses the orchestrator + sandbox + state, with a new prompt that targets `{test:*}` annotation coverage rather than reference example authoring. Cost projection ~$27, ~6 turns/page, batched by top-level guides directory. https://claude.ai/code/session_014puccJJixwdjRgMx7mPLmz Co-authored-by: Claude <noreply@anthropic.com>
Summary
Closes the last gap from the v4 API docs validation rollout.
PR #2465 (View Helpers section) ran 88 functions; 87 returned
doneand 1 flaggedneeds_human:h(). The agent diagnosed the issue cleanly — theWRITE_GLOBSregex intools/docs-validation/lib/tools.mjsrequired filenames matching[a-z][a-z0-9]+\.txt(2+ chars), soh.txtwas rejected at the tool layer. The agent stopped, recorded the exact regex line that needed fixing, and drafted the proposed reference body.This PR resolves all three pieces in one commit:
tools/docs-validation/lib/tools.mjs—[a-z][a-z0-9]+→[a-z][a-z0-9]*so single-char function names are writable (h, plus any future single-char additions).vendor/wheels/public/docs/reference/controller/h.txt— created with the agent's drafted three examples (XSS-safe writeOutput, inline cfoutput pattern, non-string passthrough). The&#HTML entities are CFML-escaped as&##so they render correctly through the snapshot pipeline.state.jsonentry forh— flipped fromneeds_humantodone, attempts incremented to 2, with notes describing the manual resolution.After this lands, all 8 sections × 378 functions are
status=done. v4 API docs validation rollout is complete.Risk
Low. Regex broadening only allows additional path patterns through the tool sandbox (1-char names) — no security implication since the broader allowlist is still limited to the same 7 scope subdirectories under
vendor/wheels/public/docs/reference/. The newh.txtis documentation.Test plan
node -e "const {TOOLS} = require('./tools/docs-validation/lib/tools.mjs')"doesn't throw (sanity check syntax)hhasextended.hasExtended: truehttps://api.wheels.dev/v4-0-0-snapshot/global-helpers/h/(or wherever it routes) renders the Examples section after deployhttps://claude.ai/code/session_014puccJJixwdjRgMx7mPLmz
Generated by Claude Code