Skip to content

feat(chat-with-content): scope to the viewer, surface errors, harden startup - #447

Open
amylin1249 wants to merge 9 commits into
cwc-01-helpers-and-cifrom
cwc-02-app-rework
Open

feat(chat-with-content): scope to the viewer, surface errors, harden startup#447
amylin1249 wants to merge 9 commits into
cwc-01-helpers-and-cifrom
cwc-02-app-rework

Conversation

@amylin1249

@amylin1249 amylin1249 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Split out of #433 into small, reviewable PRs (tracking issue #429). Stacked 2/3 — stacked on #446, merge bottom-up.

Scopes the app to the signed-in viewer (visitor-scoped Connect client, setup screen when the session or integration is missing), surfaces errors instead of failing silently, hardens startup (bounded Bedrock probe and Connect API calls), and serializes all replies through one extended task so overlapping streams can't interleave or drop chunks.

The Connect API hardening has two layers, bounding different things:

  • CONNECT_API_TIMEOUT_SECONDS (30s) and CONTENT_LIST_TIMEOUT_SECONDS (120s) cap how long the app waits before reporting a failure.
  • helpers.CONNECT_REQUEST_TIMEOUT_SECONDS (60s) is a read timeout on the SDK's own session, supplied through a requests adapter. The SDK sets none, and asyncio.to_thread can stop waiting on a stuck call but can't interrupt it, so without this a Connect server that goes quiet mid-response ties up a worker thread until the app restarts. Applied to the deploy client and to the viewer-scoped client the token exchange builds, since that one gets a fresh session. Verified against a socket server that accepts and then goes silent: raises ReadTimeout at the deadline, where the unpatched session was still hanging.

requests is now a declared dependency, since helpers.py imports it directly.

@github-actions

Copy link
Copy Markdown
Contributor

Extension release summary

😴 Changed but won't release

The following extensions have code changes but the manifest version hasn't been incremented:

  • chat-with-content: manifest version 0.0.7 ≤ released version 0.0.7

If you intended to release these changes, update the version field in each extension's manifest.json.

See the contributing guide for details.

@amylin1249
amylin1249 force-pushed the cwc-02-app-rework branch 6 times, most recently from 9928535 to 900d730 Compare July 29, 2026 15:33
@amylin1249
amylin1249 marked this pull request as ready for review July 29, 2026 17:40
@amylin1249
amylin1249 requested a review from dotNomad July 29, 2026 19:27
amylin1249 and others added 9 commits August 1, 2026 09:15
Every user-visible error message (chat provider startup, content list/open,
markdownify conversion, chat streaming) interpolated the raw exception text
into the screen or a toast. A viewer can't act on SDK/vendor error detail, and
some of these failures (bad LLM config, a broken provider) are only fixable by
an administrator anyway. Log the raw cause with print() instead, and show a
fixed, non-technical message that tells the viewer what to do next.
Exchanging the session token, reading the viewer's name, listing their
content, and opening a selected item were all blocking Connect API calls made
directly in server() or a plain reactive effect. Both run under Shiny's
single, process-wide reactive lock, so a slow call in any of them stalled
every other session on that worker, not just the caller's.

Move the session/content-list resolution into one extended task (mirroring
the existing markdownify-conversion fix), and wrap the remaining blocking
call (opening a selected item) in asyncio.to_thread, so none of them run on
the shared lock or event loop.
…currently

Fetching the content list and reading the viewer's own name don't depend on
each other, but ran as two sequential Connect API round trips. Run them
concurrently instead, and match the explicit-underscore unpacking style used
everywhere else resolve_session's result is read.
The posit-sdk sets no request timeout, so an unresponsive Connect server
could hang a session-exchange, content-list, identity, or content-open call
indefinitely. asyncio.to_thread can't interrupt a call already in flight, so
this bounds how long the app waits (and reports the failure) rather than how
long the abandoned thread runs, matching the existing timeout treatment for
the Bedrock probe and chat streaming.
CONNECT_API_TIMEOUT_SECONDS (30s) bounded content.find() the same as the
single-item lookups (session exchange, .me, content.get), but it's a
paginated fetch of everything the viewer can see: on a large Connect
instance it can legitimately take longer without anything being wrong.
Give it its own longer CONTENT_LIST_TIMEOUT_SECONDS, use the shared
SESSION_TIMEOUT_DETAIL message for the session-exchange timeout so its
wording matches its two siblings, and fix CONNECT_API_TIMEOUT_SECONDS's
comment to name all of its call sites.
Shiny's fillable page locks body to exactly the viewport height, so when
the setup screen's content is taller than one screen (e.g. both the LLM and
integration steps showing), body's own gradient background stopped at the
viewport edge, leaving a hard line and plain white below it on scroll.

Root cause (verified with a real browser, not guessed): body has 24px
padding from bslib's own CSS that our plain `body { padding: 0 }` can't
override (a class selector always beats a bare element selector), and
.setup-container isn't a Shiny "fill item," so it can grow taller than
body's fixed box. Body's background still covers the normal case (including
that padding); .setup-container's identical background, which already
grows to fit whatever content it holds, seamlessly extends past body's
edge for the overflow case. The 800px reading width moves to .setup-card
since .setup-container itself must stay full width for its background to
reach the sides of the viewport.
Use background-attachment: fixed instead of duplicating the gradient onto
.setup-container. Fixed keeps the background painted relative to the
viewport rather than the element's box, so it fills the visible window
regardless of body's own height -- one property instead of restructuring
which element owns the 800px width cap. Matches the pattern already used
for the same case in simple-shiny-chat-with-mcp's setup screen.
The SDK sets no timeout on its session, so a Connect server that accepts a
connection and then goes quiet hangs the calling thread forever. asyncio can
stop waiting on those calls but cannot interrupt them, so in a Shiny worker the
threads pile up and eventually starve it. A session adapter supplies a default
read timeout through requests' own extension point, applied to the deploy client
and to the viewer-scoped client the token exchange builds.

Co-Authored-By: Claude Opus 5 (1M context) <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