Skip to content

Commit 4d52c45

Browse files
authored
Merge branch 'main' into bump_versions
2 parents a3f675b + a713ebd commit 4d52c45

File tree

131 files changed

+3628
-2991
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

131 files changed

+3628
-2991
lines changed

.github/workflows/conventional-commits.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ jobs:
2222
feat
2323
fix
2424
perf
25-
style
2625
refactor
26+
release
27+
style
2728
test
2829
- uses: marocchino/sticky-pull-request-comment@v2
2930
if: always() && (steps.lint.outputs.error_message != null)

.github/workflows/deploy-tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ jobs:
109109
- name: Deploy apps and run tests (on `push` or `deploy**` branches)
110110
env:
111111
DEPLOY_APPS: "true"
112-
DEPLOY_CONNECT_SERVER_URL: "${{ (matrix.config.released_connect_server && 'https://connect.posit.it/') || 'https://rsc.radixu.com/' }}"
112+
DEPLOY_CONNECT_SERVER_URL: "${{ (matrix.config.released_connect_server && 'https://connect.posit.it/') || 'https://dogfood.team.pct.posit.it/' }}"
113113
DEPLOY_CONNECT_SERVER_API_KEY: "${{ (matrix.config.released_connect_server && secrets.DEPLOY_CONNECT_POSIT_SERVER_API_KEY) || secrets.DEPLOY_CONNECT_SERVER_API_KEY }}"
114114
DEPLOY_SHINYAPPS_NAME: "${{ matrix.config.test_shinyappsio && secrets.DEPLOY_SHINYAPPS_NAME }}"
115115
DEPLOY_SHINYAPPS_TOKEN: "${{ matrix.config.test_shinyappsio && secrets.DEPLOY_SHINYAPPS_TOKEN }}"

.github/workflows/pytest.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,17 @@ jobs:
191191
working-directory: examples/brownian/shinymediapipe
192192
run: |
193193
npm ci
194+
- name: Checkout py-shiny-templates repository
195+
uses: actions/checkout@v4
196+
with:
197+
repository: posit-dev/py-shiny-templates
198+
path: py-shiny-templates
199+
200+
- name: Install py-shiny-templates dependencies
201+
if: matrix.python-version != '3.9'
202+
# Scikit-learn 1.7.0+ requires Python 3.10+
203+
run: |
204+
make ci-install-py-shiny-templates-deps
194205
195206
- name: Run example app tests
196207
timeout-minutes: 60

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ docs/source/reference/
111111

112112
/.luarc.json
113113

114+
# uv
115+
/uv.lock
116+
114117
# Developer scratch area
115118
_dev/
116119
tests/playwright/deploys/**/requirements.txt

CHANGELOG.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [UNRELEASED]
99

10+
### New features
11+
12+
* `ui.sidebar()` is now interactively resizable. (#2020)
13+
14+
* `ui.update_*()` functions now accept `ui.TagChild` (i.e., HTML) as input to the `label` and `icon` arguments. (#2020)
15+
16+
* `playwright.controller.InputActionButton` gains a `expect_icon()` method. As a result, the already existing `expect_label()` no longer includes the icon. (#2020)
17+
18+
### Improvements
19+
20+
* Improved the styling and readability of markdown tables rendered by `ui.Chat()` and `ui.MarkdownStream()`. (#1973)
21+
22+
* `selectize`, `remove_button`, and `options` parameters of `ui.input_select()` have been deprecated; use `ui.input_selectize()` instead. (Thanks, @ErdaradunGaztea!) (#1947)
23+
24+
* Added `timeout_secs` parameter to `create_app_fixture` to allow testing apps with longer startup times. (#2033)
25+
26+
### Bug fixes
27+
28+
* Fixed an issue with `ui.Chat()` sometimes wanting to scroll a parent element. (#1996)
29+
30+
* Explicitly call out module usage in UI input bookmark button documentation. (#1983)
31+
32+
* Fix missing session when trying to display an error duing bookmarking. (#1984)
33+
34+
35+
## [1.4.0] - 2025-04-08
36+
1037
## New features
1138

1239
* Added support for bookmarking Shiny applications. Bookmarking allows users to save the current state of an application and return to it later. This feature is available in both Shiny Core and Shiny Express. (#1870, #1915, #1919, #1920, #1922, #1934, #1938, #1945, #1955)
@@ -17,8 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1744

1845
* Both `ui.Chat()` and `ui.MarkdownStream()` now support the inclusion of Shiny UI elements inside of messages. This allows for gathering input from the user (e.g., `ui.input_select()`), displaying of rich output (e.g., `render.DataGrid()`), and more. (#1868)
1946

20-
* Added a new `.message_stream_context()` method to `ui.Chat()`. This context manager is a useful alternative to `.append_message_stream()` when you want to: (1) Nest a stream within another and/or
21-
(2) Overwrite/replace streaming content. (#1906)
47+
* Added a new `.message_stream_context()` method to `ui.Chat()`. This context manager is a useful alternative to `.append_message_stream()` when you want to: (1) Nest a stream within another and/or (2) Overwrite/replace streaming content. (#1906)
2248

2349
### Changes
2450

@@ -32,6 +58,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3258

3359
### Bug fixes
3460

61+
* Fixed issue where apps run in Workbench were unexpectedly crashing. Apps running in Workbench will now have `ws_per_message_deflate=False` enforced. (#2005)
62+
3563
* Fixed an issue where the `<main>` areas of `ui.page_sidebar()` and `ui.page_navbar()` (with a `sidebar`) were made to be a fillable containers even when `fillable=False`. (#1816)
3664

3765
* Fixed an issue where the `.update_user_input()` method on `ui.Chat()` isn't working in shinylive. (#1891)

Makefile

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ clean-test: FORCE
5656
rm -fr .pytest_cache
5757
rm -rf typings/
5858

59-
typings/appdirs:
60-
@echo "Creating appdirs stubs"
61-
pyright --createstub appdirs
6259
typings/folium:
6360
@echo "Creating folium stubs"
6461
pyright --createstub folium
@@ -75,7 +72,7 @@ typings/matplotlib/__init__.pyi:
7572
mv typings/python-type-stubs/stubs/matplotlib typings/
7673
rm -rf typings/python-type-stubs
7774

78-
pyright-typings: typings/appdirs typings/folium typings/uvicorn typings/seaborn typings/matplotlib/__init__.pyi
75+
pyright-typings: typings/folium typings/uvicorn typings/seaborn typings/matplotlib/__init__.pyi
7976

8077
check: check-format check-lint check-types check-tests ## check code, style, types, and test (basic CI)
8178
check-fix: format check-lint check-types check-tests ## check and format code, style, types, and test
@@ -223,6 +220,8 @@ install-deps: FORCE ## install dependencies
223220
pip install -e ".[dev,test]" --upgrade
224221
ci-install-deps: FORCE
225222
uv pip install -e ".[dev,test]"
223+
ci-install-py-shiny-templates-deps: FORCE
224+
uv pip install -r py-shiny-templates/requirements.txt
226225

227226
install-docs: FORCE
228227
pip install -e ".[dev,test,doc]"

docs/_quartodoc-testing.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ quartodoc:
2626
contents:
2727
- playwright.controller.InputActionButton
2828
- playwright.controller.InputActionLink
29+
- playwright.controller.InputBookmarkButton
2930
- playwright.controller.InputCheckbox
3031
- playwright.controller.InputCheckboxGroup
3132
- playwright.controller.InputDarkMode
@@ -59,6 +60,7 @@ quartodoc:
5960
- playwright.controller.NavsetTab
6061
- playwright.controller.NavsetUnderline
6162
- playwright.controller.NavPanel
63+
- playwright.controller.PageNavbar
6264
- title: Upload and download
6365
desc: Methods for interacting with Shiny app uploading and downloading controller.
6466
contents:

js/build.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,6 @@ const opts: Array<BuildOptions> = [
9393
entryPoints: { "data-frame/data-frame": "data-frame/index.tsx" },
9494
plugins: [sassPlugin({ type: "css-text", sourceMap: false })],
9595
},
96-
{
97-
entryPoints: {
98-
"text-area/textarea-autoresize": "text-area/textarea-autoresize.ts",
99-
},
100-
},
10196
{
10297
entryPoints: {
10398
"page-output/page-output": "page-output/page-output.ts",

js/chat/chat.ts

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@ interface ChatInputSetInputOptions {
131131
}
132132

133133
class ChatInput extends LightElement {
134-
private _disabled = false;
135-
136134
@property() placeholder = "Enter a message...";
137135
// disabled is reflected manually because `reflect: true` doesn't work with LightElement
138136
@property({ type: Boolean })
@@ -155,6 +153,27 @@ class ChatInput extends LightElement {
155153
this.#onInput();
156154
}
157155

156+
private _disabled = false;
157+
inputVisibleObserver?: IntersectionObserver;
158+
159+
connectedCallback(): void {
160+
super.connectedCallback();
161+
162+
this.inputVisibleObserver = new IntersectionObserver((entries) => {
163+
entries.forEach((entry) => {
164+
if (entry.isIntersecting) this.#updateHeight();
165+
});
166+
});
167+
168+
this.inputVisibleObserver.observe(this);
169+
}
170+
171+
disconnectedCallback(): void {
172+
super.disconnectedCallback();
173+
this.inputVisibleObserver?.disconnect();
174+
this.inputVisibleObserver = undefined;
175+
}
176+
158177
attributeChangedCallback(
159178
name: string,
160179
_old: string | null,
@@ -189,7 +208,7 @@ class ChatInput extends LightElement {
189208
return html`
190209
<textarea
191210
id="${this.id}"
192-
class="form-control textarea-autoresize"
211+
class="form-control"
193212
rows="1"
194213
placeholder="${this.placeholder}"
195214
@keydown=${this.#onKeyDown}
@@ -217,6 +236,7 @@ class ChatInput extends LightElement {
217236
}
218237

219238
#onInput(): void {
239+
this.#updateHeight();
220240
this.button.disabled = this.disabled
221241
? true
222242
: this.value.trim().length === 0;
@@ -247,6 +267,15 @@ class ChatInput extends LightElement {
247267
if (focus) this.textarea.focus();
248268
}
249269

270+
#updateHeight(): void {
271+
const el = this.textarea;
272+
if (el.scrollHeight == 0) {
273+
return;
274+
}
275+
el.style.height = "auto";
276+
el.style.height = `${el.scrollHeight}px`;
277+
}
278+
250279
setInputValue(
251280
value: string,
252281
{ submit = false, focus = false }: ChatInputSetInputOptions = {}
@@ -528,11 +557,19 @@ class ChatContainer extends LightElement {
528557

529558
// ------- Register custom elements and shiny bindings ---------
530559

531-
customElements.define(CHAT_MESSAGE_TAG, ChatMessage);
532-
customElements.define(CHAT_USER_MESSAGE_TAG, ChatUserMessage);
533-
customElements.define(CHAT_MESSAGES_TAG, ChatMessages);
534-
customElements.define(CHAT_INPUT_TAG, ChatInput);
535-
customElements.define(CHAT_CONTAINER_TAG, ChatContainer);
560+
const chatCustomElements = [
561+
{ tag: CHAT_MESSAGE_TAG, component: ChatMessage },
562+
{ tag: CHAT_USER_MESSAGE_TAG, component: ChatUserMessage },
563+
{ tag: CHAT_MESSAGES_TAG, component: ChatMessages },
564+
{ tag: CHAT_INPUT_TAG, component: ChatInput },
565+
{ tag: CHAT_CONTAINER_TAG, component: ChatContainer }
566+
];
567+
568+
chatCustomElements.forEach(({ tag, component }) => {
569+
if (!customElements.get(tag)) {
570+
customElements.define(tag, component);
571+
}
572+
});
536573

537574
window.Shiny.addCustomMessageHandler(
538575
"shinyChatMessage",
@@ -561,3 +598,5 @@ window.Shiny.addCustomMessageHandler(
561598
el.dispatchEvent(evt);
562599
}
563600
);
601+
602+
export { CHAT_CONTAINER_TAG };

js/markdown-stream/markdown-stream.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import ClipboardJS from "clipboard";
66
import hljs from "highlight.js/lib/common";
77
import { Renderer, parse } from "marked";
88

9+
import { CHAT_CONTAINER_TAG } from "../chat/chat";
10+
911
import {
1012
LightElement,
1113
createElement,
@@ -45,28 +47,38 @@ const SVG_DOT = createSVGIcon(
4547
`<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" class="${SVG_DOT_CLASS}" style="margin-left:.25em;margin-top:-.25em"><circle cx="6" cy="6" r="6"/></svg>`
4648
);
4749

48-
// For rendering chat output, we use typical Markdown behavior of passing through raw
49-
// HTML (albeit sanitizing afterwards).
50-
//
51-
// For echoing chat input, we escape HTML. This is not for security reasons but just
52-
// because it's confusing if the user is using tag-like syntax to demarcate parts of
53-
// their prompt for other reasons (like <User>/<Assistant> for providing examples to the
54-
// chat model), and those tags simply vanish.
55-
const rendererEscapeHTML = new Renderer();
56-
rendererEscapeHTML.html = (html: string) =>
50+
// 'markdown' renderer (for assistant messages)
51+
const markdownRenderer = new Renderer();
52+
53+
// Add some basic Bootstrap styling to markdown tables
54+
markdownRenderer.table = (header: string, body: string) => {
55+
return `<table class="table table-striped table-bordered">
56+
<thead>${header}</thead>
57+
<tbody>${body}</tbody>
58+
</table>`;
59+
};
60+
61+
// 'semi-markdown' renderer (for user messages)
62+
const semiMarkdownRenderer = new Renderer();
63+
64+
// Escape HTML, not for security reasons, but just because it's confusing if the user is
65+
// using tag-like syntax to demarcate parts of their prompt for other reasons (like
66+
// <User>/<Assistant> for providing examples to the model), and those tags vanish.
67+
semiMarkdownRenderer.html = (html: string) =>
5768
html
5869
.replaceAll("&", "&amp;")
5970
.replaceAll("<", "&lt;")
6071
.replaceAll(">", "&gt;")
6172
.replaceAll('"', "&quot;")
6273
.replaceAll("'", "&#039;");
63-
const markedEscapeOpts = { renderer: rendererEscapeHTML };
6474

6575
function contentToHTML(content: string, content_type: ContentType) {
6676
if (content_type === "markdown") {
67-
return unsafeHTML(sanitizeHTML(parse(content) as string));
77+
const html = parse(content, { renderer: markdownRenderer });
78+
return unsafeHTML(sanitizeHTML(html as string));
6879
} else if (content_type === "semi-markdown") {
69-
return unsafeHTML(sanitizeHTML(parse(content, markedEscapeOpts) as string));
80+
const html = parse(content, { renderer: semiMarkdownRenderer });
81+
return unsafeHTML(sanitizeHTML(html as string));
7082
} else if (content_type === "html") {
7183
return unsafeHTML(sanitizeHTML(content));
7284
} else if (content_type === "text") {
@@ -272,6 +284,12 @@ class MarkdownElement extends LightElement {
272284
while (el) {
273285
if (el.scrollHeight > el.clientHeight) return el;
274286
el = el.parentElement;
287+
if (el?.tagName?.toLowerCase() === CHAT_CONTAINER_TAG.toLowerCase()) {
288+
// This ensures that we do not accidentally scroll a parent element of the chat
289+
// container. If the chat container itself is scrollable, a scrollable element
290+
// would already have been identified.
291+
break;
292+
}
275293
}
276294
return null;
277295
}

0 commit comments

Comments
 (0)