Skip to content

show_view: charts, inputs with local formulas, and a sandboxed html view - #471

Merged
Fei2411 merged 4 commits into
devfrom
feat/468-view-charts
Oct 23, 2026
Merged

Fei2411 merged 4 commits into
devfrom
feat/468-view-charts

Conversation

@Fei2411

@Fei2411 Fei2411 commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Issue

Fixes #468

What changed / 改了什么

  • Charts: new chart component — 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.
  • Inputs and formulas: slider, number, choice, toggle. Any metric/text/progress/callout/table cell/chart value can be a formula { "=": "..." } over input ids and let names. 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.
  • Sending values: a submit component 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.
  • html: for what the catalog cannot draw. Runs in a 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.
  • Layout pieces: row, callout, sources; progress takes value/max, a valueLabel and a tone; text takes tone: muted; metric takes a caption.
  • Frame: display-only views sit in the reply without a frame; views with controls keep a light one. The "View" tag is gone and title is now an optional small heading.
  • Summaries for handoffs and checkpoints include worked-out formula values, input values and html summaries.
  • Design note docs/plans/2026-10-20-widget-views.md updated (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 (in ds-bot/) — 926 tests, 0 failures; new formula.test.js, view-inputs.test.js, client render tests in tool-cells.test.js
  • node scripts/build-client.mjs --check
  • npm run lint — 0 errors, no new warnings
  • Smoke test — all 52 steps pass (on Windows, with the mock and dsh web started by hand because dev/bench.sh needs setsid)
  • Screenshot attached, for interface changes — light/dark, bubble/flat and a 420 px window on the mock bench; a submitted calculator reached the Bot as Requests a day=2400; Cache hits=High; no console errors

Not checked: DSH Desktop (PNG download through the desktop shell in particular), macOS/Linux.

Checklist / 检查

  • One issue, one subject. / 只做一件事。
  • src/client/ changed → npm run build ran and client.js is committed.
  • No keys, tokens or personal data, also in screenshots.
  • No AI tool as author or Co-authored-by.

Devin Review

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

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-23T09:56:15.669067Z 3d4f5c6 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Fei2411

Fei2411 commented Oct 23, 2026

Copy link
Copy Markdown
Owner Author

Mock bench screenshots: charts (light, bubble), bar hover, chart menu, calculator after dragging, html view after clicks, dark flat, 420 px window.

light-bubble-1-charts

i1-bar-hover

i2-menu

i6-calculator-moved

i5-html-click

dark-flat-4-calculator

narrow-3-calculator

@devin-ai-integration devin-ai-integration 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.

Devin Review found 4 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +90 to +91
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)

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.

🟡 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.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +185 to +186
const values = (series[0]?.values ?? []).map(value => Math.max(0, value))
const total = values.reduce((sum, value) => sum + value, 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.

🟡 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.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +531 to +532
const result = evaluate(entry.tree, lookup)
return result.error === undefined ? result.value : fallback

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.

🟡 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.

Devin Review


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'"

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.

🟥 Sandboxed views can send data by navigation

When model HTML navigates the iframe to an external URL, FRAME_POLICY does not block the request. The load handler removes the frame only afterward, so URL data has already left the application.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@Fei2411
Fei2411 merged commit 94b2190 into dev Oct 23, 2026
13 checks passed
@Fei2411
Fei2411 deleted the feat/468-view-charts branch October 23, 2026 10:58
@Fei2411 Fei2411 mentioned this pull request Oct 23, 2026
1 task done
Fei2411 added a commit that referenced this pull request Oct 23, 2026
elonnzhang pushed a commit to elonnzhang/DeepSeek-Bot that referenced this pull request Oct 23, 2026
They merged after the 0.2.1 changelog was written; the release now includes them.
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