Repository navigation
show_view: charts, inputs with local formulas, and a sandboxed html view - #471
Conversation
Views could only show static cards in a question-card frame. A Bot can now draw bar, line, area and donut charts with hover, a legend and a menu (table view, copy as CSV, save as PNG), and build small calculators: input values stay in the page and formulas are worked out there, so trying values costs no model turn. A submit component sends the inputs when the user wants the Bot to know them. What the catalog cannot draw goes into an html component that runs in a sandboxed frame with no same-origin and a policy that blocks the network. Display-only views now sit in the reply without a frame, views with controls keep a light one, and the title is an optional small heading.
The bench showed a donut whose center label caught the pointer, a doubled percent on shares already in percent, the chart menu covering the table view, and views capped at bubble width on a narrow window. Fixed those, and added mock scenarios for charts, a calculator and an html view. The widget-views design note now covers charts, inputs with formulas, the sandboxed html component and the lighter frame.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
Devin Review found 4 potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
| for (const field of NUMERIC) if (Object.hasOwn(out, field)) out[field] = numeric(out[field]) | ||
| if (Array.isArray(out.values)) out.values = out.values.map(numeric) |
There was a problem hiding this comment.
🟡 Numeric choice identifiers change type
When normalizeView sees a choice value like "01", it turns that string into 1. Formulas comparing the choice against "01" fail, and replies send 1 instead of the declared identifier.
Learn more
Normalization runs recursively across the entire blueprint. The generic value conversion changes both a choice's selected value and each option's value, even though choice values explicitly allow strings. Formula evaluation and submitted answers then read the changed value rather than the one the Bot supplied.
Example: A choice option {label:'Small',value:'01'} with value:'01' passes validation after normalization, but size == '01' evaluates false and a submit sends size: 1.
Recommended fix: Make numeric-string conversion context-aware. Preserve choice.value and choice.options[].value as supplied, while retaining numeric coercion for numeric input fields, progress, chart values and the existing metric behavior. Add a choice string-identifier test covering formulas and replies.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const values = (series[0]?.values ?? []).map(value => Math.max(0, value)) | ||
| const total = values.reduce((sum, value) => sum + value, 0) |
There was a problem hiding this comment.
🟡 Negative donut values disappear from charts
When donut receives a negative value, it clamps that slice to zero. Validation accepts the value, so the chart disagrees with its table and exported CSV.
Learn more
The chart validator accepts any finite number in every chart kind (checkChart). The donut renderer replaces negative numbers with zero before calculating its slices and total, but chartData retains the original values for the table and CSV. Consequently the same accepted chart presents incompatible figures.
Example: A donut series [-5, 10] is accepted. The donut has only the second slice and displays a total of 10, while the table and copied CSV still contain -5 and 10.
Recommended fix: Reject negative donut values during blueprint validation, including evaluated formula values; for formulas that turn negative after an input changes, display an invalid-value placeholder rather than silently treating them as zero.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const result = evaluate(entry.tree, lookup) | ||
| return result.error === undefined ? result.value : fallback |
There was a problem hiding this comment.
🟡 Division by zero produces false calculator values
When an input makes a formula divide by zero, resolve accepts Infinity as a result. Charts render that value as zero, and metrics display Infinity instead of a placeholder.
Learn more
The host checks formulas only with each input's initial value in checkFormulas. JavaScript division by zero produces Infinity, which is not an evaluator error, so resolve returns it after an input changes. chartData substitutes zero for that result, while WidgetMetric prints Infinity; the page no longer displays the calculated value or the promised error placeholder.
Example: A metric and chart value both use 10 / denominator, with a number input initially at 2 and allowed to reach 0. Changing it to 0 prints Infinity on the metric and plots zero on the chart.
Recommended fix: Reject nonfinite numeric evaluation results at runtime before handing them to display components. Have charts and progress expose a placeholder for failed numeric formulas instead of replacing the result with zero, while retaining the host's finite-value check for initial inputs.
Was this helpful? React with 👍 or 👎 to provide feedback.
| // has no way to answer the Bot. A frame that navigates away is stopped. | ||
|
|
||
| export const FRAME_VARS = ['--bt-ink', '--bt-ink-2', '--bt-ink-3', '--bt-line', '--bt-line-2', '--bt-card', '--bt-main', '--bt-hover', '--bt-accent', '--bt-accent-ink', '--bt-green', '--bt-star', '--bt-danger'] | ||
| export const FRAME_POLICY = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; media-src data: blob:; form-action 'none'; base-uri 'none'" |
There was a problem hiding this comment.
They merged after the 0.2.1 changelog was written; the release now includes them.







Issue
Fixes #468
What changed / 改了什么
chartcomponent — bar, horizontal bar, line, area and donut, drawn as SVG at the width it gets, colors from the theme only. Hover or arrow keys show a category's values and dim the rest; a legend hides a series; a menu shows the data as a table, copies it as CSV, or saves the chart as PNG.slider,number,choice,toggle. Any metric/text/progress/callout/table cell/chart value can be a formula{ "=": "..." }over input ids andletnames. The page works formulas out on every change — no model turn, no tokens. The evaluator (src/shared/formula.js) has no property access, assignment or loops, and is shared by host and client. The host evaluates every formula with the Bot's initial values and rejects bad ones with a path.submitcomponent sends the inputs to the Bot; any answer from a view with inputs carries them, checked server-side (range, option values, booleans). Dragged values are not saved.sandbox="allow-scripts"srcdoc iframe without same-origin, with a no-network CSP written before the markup, theme variables injected, auto height, and no way to answer the Bot. A frame that navigates away is replaced by a notice.row,callout,sources;progresstakesvalue/max, avalueLabeland a tone;texttakestone: muted;metrictakes acaption.titleis now an optional small heading.docs/plans/2026-10-20-widget-views.mdupdated (third version). Group chats still get no views.Changelog
Bots can draw charts, small calculators whose numbers update as you drag, and custom sandboxed views in their replies.
How I checked it / 怎么验证的
npm test(inds-bot/) — 926 tests, 0 failures; newformula.test.js,view-inputs.test.js, client render tests intool-cells.test.jsnode scripts/build-client.mjs --checknpm run lint— 0 errors, no new warningsdsh webstarted by hand becausedev/bench.shneedssetsid)Requests a day=2400; Cache hits=High; no console errorsNot checked: DSH Desktop (PNG download through the desktop shell in particular), macOS/Linux.
Checklist / 检查
src/client/changed →npm run buildran andclient.jsis committed.Co-authored-by.