Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e661ba6
initial impl
jnumainville May 5, 2026
e7c0cd1
fix
jnumainville May 8, 2026
9d3b86b
warning
jnumainville May 8, 2026
4cc8e3d
fixt comments and failures
jnumainville May 11, 2026
3c3ae1a
docs
jnumainville May 11, 2026
d18f35a
more docs
jnumainville May 11, 2026
1e79e03
even more docs
jnumainville May 11, 2026
787a36b
snapshots
jnumainville May 11, 2026
06255a2
fixes
jnumainville May 11, 2026
1e478a3
attempt escape
jnumainville May 11, 2026
4989268
missed a spot
jnumainville May 11, 2026
71e6847
maybe this?
jnumainville May 11, 2026
eb9ad57
testing
jnumainville May 11, 2026
660d988
block
jnumainville May 11, 2026
c8dd2e8
another try
jnumainville May 11, 2026
c65db2f
love when docs don't save
jnumainville May 11, 2026
7fc038c
anotha one
jnumainville May 11, 2026
6f14ca7
and now for something completely different
jnumainville May 11, 2026
f30c734
?????
jnumainville May 11, 2026
2a81fd3
try strings
jnumainville May 11, 2026
94f5ff4
these do not want to be fixed
jnumainville May 11, 2026
95a1733
oops
jnumainville May 11, 2026
bc6b59c
maybe need that too
jnumainville May 11, 2026
a45ae84
wip
jnumainville May 11, 2026
47cd622
remove
jnumainville May 11, 2026
b7bc5c1
missing tests
jnumainville May 12, 2026
f5556a6
fix test
jnumainville May 12, 2026
277b686
a few improvements
jnumainville May 13, 2026
e171932
test fixes
jnumainville May 13, 2026
e2e0b22
rework
jnumainville May 18, 2026
4055480
fix tests
jnumainville May 18, 2026
5614407
Merge remote-tracking branch 'origin/main' into 21898_local_routing_impl
jnumainville May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/skills/writing-docs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
name: writing-docs
description: Write documentation. Use when asked to write documentation, update docs, or build plugin docs.
---

## Documenting Functions

For plugins that support it (indicated by the presence of a `make_docs.py` file, e.g. `plugins/ui/make_docs.py` or `plugins/plotly-express/make_docs.py`), document functions using the `dhautofunction` directive rather than building any table or description manually.

The functions themselves should be fully documented with docstrings in the source code. Parameters with multiple lines in their description should use an indented block after the first line.

### Example

````markdown
## API Reference

```{eval-rst}
.. dhautofunction:: deephaven.ui.use_navigate
```
````

Use the fully qualified Python path to the function as the argument to `dhautofunction`. This automatically generates the function signature, parameters, return type, and description from the source docstring.

## Document Structure

Follow this consistent structure when writing docs for components or hooks:

1. **H1 title** — short name of the component or hook
2. **Brief description** — one or two sentences explaining what it does and when to use it
3. **`## Example`** — a minimal, runnable code example
4. **Screenshot** (components only) — `![Alt text](../_assets/component_name.png)`
5. **`## UI recommendations`** (components) or **`## Recommendations`** (hooks) — numbered list of best practices and usage guidance
6. **Additional sections** — more examples showing advanced usage, data sources, variants, etc.
7. **`## API Reference`** — always last, using `dhautofunction`

For plotly-express chart docs, use `## What are X useful for?` with bullet points in place of a recommendations section.

## File Placement

- Component docs: `plugins/ui/docs/components/<component_name>.md`
- Hook docs: `plugins/ui/docs/hooks/<hook_name>.md`
- Plot docs: `plugins/plotly-express/docs/<chart_name>.md`

## Code Block Annotations

Docs use MyST Markdown (`.md` files with embedded RST directives). Python code blocks support two special annotations:

- `order=var1,var2,...` — controls which variables are shown in Deephaven and in what order. Variables prefixed with `_` are hidden (useful for intermediate tables or setup code that shouldn't be displayed).
- `skip-test` — excludes the code block from automated testing (use sparingly, e.g. for pseudocode or illustrative snippets).

Example:

````markdown
```python order=line_plot,my_table
import deephaven.plot.express as dx
my_table = dx.data.stocks()
line_plot = dx.line(my_table, x="Timestamp", y="Price")
```
````

## Cross-References

Link to other docs using relative markdown paths:

```markdown
Consider using [`action_button`](./action_button.md) for task-based actions.
```

## Screenshots

Component screenshots are stored in `plugins/ui/docs/_assets/` and named descriptively (e.g. `button_basic.png`). Reference them with a relative path from the component doc:

```markdown
![Button Basic Example](../_assets/button_basic.png)
```
36 changes: 35 additions & 1 deletion plugins/ui/docs/components/link.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Link

Links allow users to navigate to a specified location.
Links allow users to navigate to a specified location. Use `href` for external URLs or full page reloads, and `to` for single-page application (SPA) navigation within Deephaven.

## Example

Expand Down Expand Up @@ -86,6 +86,40 @@ my_link_is_quiet_example = ui.text(
)
```

## SPA Navigation with `to`

The `to` prop enables single-page application (SPA) navigation within Deephaven. It is mutually exclusive with `href` (which triggers a full page reload but can navigate to any URL).

`to` accepts either a string (parsed for path, query params, and fragment) or a `NavigationTarget` dict for explicit control.

> [!NOTE]
> Deephaven and all custom components share the path. Avoid using routers, the path, path parameters, and navigation in shared components to prevent conflicts. Do not use the route segment `/-/` in your application path as it is reserved for internal use by Deephaven.

```python
from deephaven import ui


@ui.component
def nav():
return ui.flex(
# Simple string to the widget homepage
ui.link("Home", to="/"),
# Simple string to a custom path
ui.link("Search", to="/search?q=hello#results"),
# Dict form for explicit control
ui.link("Users", to={"path": "/users", "query_params": {"sort": "name"}}),
Comment thread
jnumainville marked this conversation as resolved.
direction="column",
)


my_nav = nav()
```

## Navigation Recommendations

1. Use `to` for navigation within a Deephaven widget and `href` for external URLs. Do not use both on the same link.
2. For programmatic navigation triggered by events or side effects, use [`use_navigate`](../hooks/use_navigate.md) instead of a link.

## API Reference

```{eval-rst}
Expand Down
156 changes: 156 additions & 0 deletions plugins/ui/docs/components/router.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Router

`ui.router` is a component that matches the current URL path against provided routes and renders the matching route's element. Use it with [`route`](#route) to define hierarchical navigation structures.

> [!NOTE]
> Deephaven and all custom components share the path. Avoid using routers, the path, path parameters, and navigation in shared components to prevent conflicts. Do not use the route segment `/-/` in your application path as it is reserved for internal use by Deephaven.

## Example

```python order=app
from deephaven import ui


@ui.component
def home_page():
return ui.text("Home page")

@ui.component
def app():
# Index routes match the parent path exactly, so this renders home_page at the root URL
return ui.router(
ui.route(index=True, element=home_page),
)


app = app()
```

## Router Options

Build a simple app with a router, nested routes, route parameters, and a fallback "not found" page.

```python order=app
from deephaven import ui


@ui.component
def nav_links():
# Reuse navigation across pages for convenience
navigate = ui.use_navigate()
return ui.button_group(
ui.action_button("Home", on_press=lambda: navigate("/")),
ui.action_button("All Users", on_press=lambda: navigate("/users")),
ui.action_button("User 1", on_press=lambda: navigate("/users/1")),
ui.action_button("User 2", on_press=lambda: navigate("/users/2")),
)


@ui.component
def user_page():
# The use_params hook gives access to route parameters defined in the path
params = ui.use_params()
# user_id is optional due to the ? in the route path, so provide a default value
user_id = params.get("user_id", None)
if user_id is None:
return ui.flex(
nav_links(),
ui.text("All users page"),
direction="column",
)
return ui.flex(
nav_links(),
ui.text(f"User profile for user {user_id}"),
direction="column",
Comment thread
jnumainville marked this conversation as resolved.
)


@ui.component
def dashboard():
return ui.flex(
nav_links(),
ui.text("Dashboard home"),
direction="column",
)


@ui.component
def not_found():
return ui.flex(
nav_links(),
ui.text("Page not found"),
direction="column",
)


@ui.component
def app():
return ui.router(
# Nest routes for hierarchical paths
ui.route(
# Match /users/{user_id} and extract user_id as an optional param
ui.route(
path="{user_id?}",
element=user_page,
),
path="users",
),
# An index route matches the path exactly, so this matches the root path /
ui.route(index=True, element=dashboard),
# Match any unmatched path with a wildcard route
ui.route(path="*", element=not_found),
)


app = app()
```

This produces the following route table:

| URL Path | Matched Element | Params |
| ---------------- | --------------- | ------------------------ |
| `/` | `dashboard` | `{}` |
| `/users` | `user_page` | `{}` |
Comment thread
jnumainville marked this conversation as resolved.
| `/users/42` | `user_page` | `{"user_id": "42"}` |
| `/anything-else` | `not_found` | `{"*": "anything-else"}` |

## Recommendations

1. Include a wildcard route (`path="*"`) as a fallback so unmatched paths render a meaningful "not found" message instead of an error.
2. Use an `index=True` route to define what renders at the exact parent path (such as a landing page at `/`).
3. Use [`use_params`](../hooks/use_params.md) inside routed components to access route parameters, and [`use_path`](../hooks/use_path.md) for the current path.
4. Use [`use_navigate`](../hooks/use_navigate.md) or [`link`](./link.md) with `to` to navigate between routes.

## API Reference

### Router

```{eval-rst}
.. dhautofunction:: deephaven.ui.router
```

#### Matching behavior

1. Static segments are preferred over parameterized segments.
2. Longer matches (more segments) are preferred over shorter ones.
3. Wildcard routes (`*`) have the lowest priority.
4. Optional segments are matched if present but do not prevent a match if absent.
5. Index routes match only the exact parent path.
6. If no route matches, the router renders an error.

### Route

```{eval-rst}
.. dhautofunction:: deephaven.ui.route
```

#### Path patterns

- `{var_name}`: Required dynamic segment
- `{var_name?}`: Optional dynamic segment (matches zero or one segments)
- `*`: Wildcard, matches any remaining path segments
- Static text: Exact match

See [`use_params`](../hooks/use_params.md) for more details on route parameters.

Child paths are appended to parent paths. For example, `ui.route(ui.route(path="{user_id}"), path="users")` produces `/users/{user_id}`.
80 changes: 80 additions & 0 deletions plugins/ui/docs/hooks/use_navigate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# use_navigate

`use_navigate` is a hook that returns a function to trigger single page application (SPA) navigation within Deephaven. For declarative navigation, consider using [`link`](../components/link.md) with the `to` prop instead.

> [!NOTE]
> Deephaven and all custom components share the path. Avoid using routers, the path, path parameters, and navigation in shared components to prevent conflicts. Do not use the route segment `/-/` in your application path as it is reserved for internal use by Deephaven.

## Example

```python order=app
from deephaven import ui


@ui.component
def app():
# Navigate to the settings page
navigate = ui.use_navigate()
return ui.action_button(
"Go to settings", on_press=lambda: navigate("/settings")
)


app = app()
```

## Navigation Options

Use `use_navigate` together with `use_path` and `use_query_params` to build a simple navigation system that updates the path and displays query parameters.

```python order=app
from deephaven import ui


@ui.component
def navigation_demo():
path = ui.use_path()
query_params = ui.use_query_params()
navigate = ui.use_navigate()

def go_dashboard():
# Navigate to a page with a query parameter
navigate("/dashboard", query_params={"welcome": "true"})
Comment thread
jnumainville marked this conversation as resolved.

def go_settings():
# Use replace=False to push a new history entry instead of replacing the current one
navigate("/settings", replace=False)

def scroll_to_section():
# Jump to a fragment on the current page
navigate(fragment="section-2")

def filter_by_tags():
# Update query parameters on the current page
navigate(query_params={"tag": ["python", "java"]})

return ui.flex(
ui.text(f"Current path: {path}"),
ui.text(f"Query params: {query_params}"),
ui.action_button("Dashboard", on_press=go_dashboard),
ui.action_button("Settings (push)", on_press=go_settings),
ui.action_button("Jump to section", on_press=scroll_to_section),
ui.action_button("Filter by tags", on_press=filter_by_tags),
direction="column",
)


app = navigation_demo()
```

## Recommendations

1. Prefer [`link`](../components/link.md) with `to` for user-clickable navigation. Reserve `use_navigate` for programmatic navigation triggered by events or side effects.
2. Use `replace=True` (the default) when navigating in response to a state change to avoid polluting the browser history.
3. Pair with [`router`](../components/router.md) and [`route`](../components/router.md) to define the route structure that `use_navigate` targets.

## API Reference

```{eval-rst}
.. dhautofunction:: deephaven.ui.use_navigate
```
Loading
Loading