The gap
When a reactive_output errors, the client can only learn that it errored: useShinyOutputStatus(id) returns "error", but the error message never reaches the app author's code. Vanilla Shiny shows the (sanitized) condition message in the output's error UI, so an app being converted to the ui.tsx pattern cannot reproduce its own original behavior.
This surfaced twice while porting the 10 stock shiny::runExamples() apps as a skills benchmark — both ports had to record it as a forced deliberate divergence rather than a choice:
- 08_html port: the original renders R's error text (e.g.
invalid number of 'breaks' from hist() at n = 0) in the output area; the port could only show a generic "Something went wrong." — "error text not reproduced verbatim since reactive_output only exposes status, not message."
- 09_upload port: same story for
read.csv() failures — "the error message is generic rather than threading the R condition text across the wire."
Sketch
Thread the sanitized error message through the wire the way Shiny itself does (respecting shiny.sanitize.errors / sanitize_errors, and req()'s silent errors staying silent), and expose it client-side — either a new hook (e.g. useShinyOutputError(id) returning {message} | null) or by widening what the status channel carries. Applies to both languages; the registry already tracks per-output status, so the plumbing has a natural home.
Acceptance sketch
- R and Python: an erroring
reactive_output delivers its sanitized condition/exception message to the client.
- Client: the message is readable from a hook;
useShinyOutputStatus semantics unchanged.
- Silent validation errors (
req(), validate()) produce no message, matching Shiny.
- FEATURES.md leaves + tests in both languages per the parity policy.
The gap
When a
reactive_outputerrors, the client can only learn that it errored:useShinyOutputStatus(id)returns"error", but the error message never reaches the app author's code. Vanilla Shiny shows the (sanitized) condition message in the output's error UI, so an app being converted to the ui.tsx pattern cannot reproduce its own original behavior.This surfaced twice while porting the 10 stock
shiny::runExamples()apps as a skills benchmark — both ports had to record it as a forced deliberate divergence rather than a choice:invalid number of 'breaks'fromhist()atn = 0) in the output area; the port could only show a generic "Something went wrong." — "error text not reproduced verbatim sincereactive_outputonly exposes status, not message."read.csv()failures — "the error message is generic rather than threading the R condition text across the wire."Sketch
Thread the sanitized error message through the wire the way Shiny itself does (respecting
shiny.sanitize.errors/sanitize_errors, andreq()'s silent errors staying silent), and expose it client-side — either a new hook (e.g.useShinyOutputError(id)returning{message} | null) or by widening what the status channel carries. Applies to both languages; the registry already tracks per-output status, so the plumbing has a natural home.Acceptance sketch
reactive_outputdelivers its sanitized condition/exception message to the client.useShinyOutputStatussemantics unchanged.req(),validate()) produce no message, matching Shiny.